checontroller refactoring (#1138)

* chore: Introduce reconcilemanager, update status when reconciliation failed.

Signed-off-by: Anatolii Bazko <abazko@redhat.com>
pull/1141/head
Anatolii Bazko 2021-10-19 10:56:49 +03:00 committed by GitHub
parent 06aa35c92c
commit f838cf48d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1128 additions and 935 deletions

View File

@ -1,44 +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 che
import (
"fmt"
"strings"
orgv1 "github.com/eclipse-che/che-operator/api/v1"
"github.com/eclipse-che/che-operator/pkg/util"
)
// ValidateCheCR checks Che CR configuration.
// It should detect:
// - configurations which miss required field(s) to deploy Che
// - self-contradictory configurations
// - configurations with which it is impossible to deploy Che
func ValidateCheCR(checluster *orgv1.CheCluster) error {
if !util.IsOpenShift {
if checluster.Spec.K8s.IngressDomain == "" {
return fmt.Errorf("Required field \"spec.K8s.IngressDomain\" is not set")
}
}
workspaceNamespaceDefault := util.GetWorkspaceNamespaceDefault(checluster)
if strings.Index(workspaceNamespaceDefault, "<username>") == -1 && strings.Index(workspaceNamespaceDefault, "<userid>") == -1 {
return fmt.Errorf(`Namespace strategies other than 'per user' is not supported anymore. Using the <username> or <userid> placeholder is required in the 'spec.server.workspaceNamespaceDefault' field. The current value is: %s`, workspaceNamespaceDefault)
}
if !util.IsCheMultiUser(checluster) {
return fmt.Errorf("Single user authentication mode is not supported anymore. To backup your data you can commit workspace configuration to an SCM server and use factories to restore it in multi user mode. To switch to multi user authentication mode set 'spec.server.customCheProperties.CHE_MULTIUSER' to 'true' in %s CheCluster custom resource. Switching to multi user authentication mode without backing up your data will cause data loss.", checluster.Name)
}
return nil
}

View File

@ -36,7 +36,7 @@ import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/discovery"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
@ -53,16 +53,7 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
)
var (
// CheServiceAccountName - service account name for che-server.
CheServiceAccountName = "che"
)
const (
failedValidationReason = "InstallOrUpdateFailed"
failedNoOpenshiftUser = "NoOpenshiftUsers"
failedNoIdentityProviders = "NoIdentityProviders"
failedUnableToGetOAuth = "UnableToGetOpenshiftOAuth"
warningNoIdentityProvidersMessage = "No Openshift identity providers."
AddIdentityProviderMessage = "Openshift oAuth was disabled. How to add identity provider read in the Help Link:"
@ -76,7 +67,7 @@ const (
// CheClusterReconciler reconciles a CheCluster object
type CheClusterReconciler struct {
Log logr.Logger
Scheme *runtime.Scheme
Scheme *k8sruntime.Scheme
// This client, initialized using mgr.Client() above, is a split client
// that reads objects from the cache and writes to the apiserver
@ -89,31 +80,41 @@ type CheClusterReconciler struct {
nonCachedClient client.Client
// A discovery client to check for the existence of certain APIs registered
// in the API Server
discoveryClient discovery.DiscoveryInterface
tests bool
userHandler OpenShiftOAuthUserHandler
permissionChecker PermissionChecker
discoveryClient discovery.DiscoveryInterface
tests bool
userHandler OpenShiftOAuthUserHandler
reconcileManager *deploy.ReconcileManager
// the namespace to which to limit the reconciliation. If empty, all namespaces are considered
namespace string
}
// NewReconciler returns a new CheClusterReconciler
func NewReconciler(mgr ctrl.Manager, namespace string, discoveryClient *discovery.DiscoveryClient) (*CheClusterReconciler, error) {
noncachedClient, err := client.New(mgr.GetConfig(), client.Options{Scheme: mgr.GetScheme()})
if err != nil {
return nil, err
func NewReconciler(
k8sclient client.Client,
noncachedClient client.Client,
discoveryClient discovery.DiscoveryInterface,
scheme *k8sruntime.Scheme,
namespace string) *CheClusterReconciler {
reconcileManager := deploy.NewReconcileManager()
// order does matter
if !util.IsTestMode() {
reconcileManager.RegisterReconciler(NewCheClusterValidator())
}
reconcileManager.RegisterReconciler(deploy.NewImagePuller())
return &CheClusterReconciler{
Scheme: mgr.GetScheme(),
Scheme: scheme,
Log: ctrl.Log.WithName("controllers").WithName("CheCluster"),
client: mgr.GetClient(),
nonCachedClient: noncachedClient,
discoveryClient: discoveryClient,
userHandler: NewOpenShiftOAuthUserHandler(noncachedClient),
permissionChecker: &K8sApiPermissionChecker{},
namespace: namespace,
}, nil
client: k8sclient,
nonCachedClient: noncachedClient,
discoveryClient: discoveryClient,
userHandler: NewOpenShiftOAuthUserHandler(noncachedClient),
namespace: namespace,
reconcileManager: reconcileManager,
}
}
// SetupWithManager sets up the controller with the Manager.
@ -229,15 +230,16 @@ func (r *CheClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = r.Log.WithValues("checluster", req.NamespacedName)
tests := r.tests
clusterAPI := deploy.ClusterAPI{
Client: r.client,
NonCachedClient: r.nonCachedClient,
DiscoveryClient: r.discoveryClient,
Scheme: r.Scheme,
}
// Fetch the CheCluster instance
tests := r.tests
instance, err := r.GetCR(req)
checluster, err := r.GetCR(req)
if err != nil {
if errors.IsNotFound(err) {
@ -252,10 +254,10 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
deployContext := &deploy.DeployContext{
ClusterAPI: clusterAPI,
CheCluster: instance,
CheCluster: checluster,
}
if isCheGoingToBeUpdated(instance) {
if isCheGoingToBeUpdated(checluster) {
// Current operator is newer than deployed Che
backupCR, err := getBackupCRForUpdate(deployContext)
if err != nil {
@ -278,41 +280,33 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
// Proceed anyway
}
if deployContext.CheCluster.ObjectMeta.DeletionTimestamp.IsZero() {
result, done, err := r.reconcileManager.ReconcileAll(deployContext)
if !done {
return result, err
// TODO: uncomment when all items added to ReconcilerManager
// } else {
// logrus.Info("Successfully reconciled.")
// return ctrl.Result{}, nil
}
} else {
r.reconcileManager.FinalizeAll(deployContext)
}
// Reconcile finalizers before CR is deleted
// TODO remove in favor of r.reconcileManager.FinalizeAll(deployContext)
r.reconcileFinalizers(deployContext)
// Reconcile the imagePuller section of the CheCluster
imagePullerResult, err := deploy.ReconcileImagePuller(deployContext)
if err != nil {
return imagePullerResult, err
}
if imagePullerResult.Requeue || imagePullerResult.RequeueAfter > 0 {
return imagePullerResult, err
}
// Check Che CR correctness
if !util.IsTestMode() {
if err := ValidateCheCR(instance); err != nil {
// Che cannot be deployed with current configuration.
// Print error message in logs and wait until the configuration is changed.
logrus.Error(err)
if err := deploy.SetStatusDetails(deployContext, failedValidationReason, err.Error(), ""); err != nil {
return reconcile.Result{}, err
}
return reconcile.Result{}, nil
}
}
if util.IsOpenShift4 && util.IsDeleteOAuthInitialUser(instance) {
if util.IsOpenShift4 && util.IsDeleteOAuthInitialUser(checluster) {
if err := r.userHandler.DeleteOAuthInitialUser(deployContext); err != nil {
logrus.Errorf("Unable to delete initial OpenShift OAuth user from a cluster. Cause: %s", err.Error())
instance.Spec.Auth.InitialOpenShiftOAuthUser = nil
checluster.Spec.Auth.InitialOpenShiftOAuthUser = nil
err := deploy.UpdateCheCRSpec(deployContext, "initialOpenShiftOAuthUser", "nil")
return reconcile.Result{}, err
}
instance.Spec.Auth.OpenShiftoAuth = nil
instance.Spec.Auth.InitialOpenShiftOAuthUser = nil
checluster.Spec.Auth.OpenShiftoAuth = nil
checluster.Spec.Auth.InitialOpenShiftOAuthUser = nil
updateFields := map[string]string{
"openShiftoAuth": "nil",
"initialOpenShiftOAuthUser": "nil",
@ -326,30 +320,30 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}
// Update status if OpenShift initial user is deleted (in the previous step)
if instance.Spec.Auth.InitialOpenShiftOAuthUser == nil && instance.Status.OpenShiftOAuthUserCredentialsSecret != "" {
if checluster.Spec.Auth.InitialOpenShiftOAuthUser == nil && checluster.Status.OpenShiftOAuthUserCredentialsSecret != "" {
secret := &corev1.Secret{}
exists, err := getOpenShiftOAuthUserCredentialsSecret(deployContext, secret)
if err != nil {
// We should `Requeue` since we deal with cluster scope objects
return ctrl.Result{RequeueAfter: time.Second}, err
} else if !exists {
instance.Status.OpenShiftOAuthUserCredentialsSecret = ""
checluster.Status.OpenShiftOAuthUserCredentialsSecret = ""
if err := deploy.UpdateCheCRStatus(deployContext, "openShiftOAuthUserCredentialsSecret", ""); err != nil {
return reconcile.Result{}, err
}
}
}
if util.IsOpenShift && instance.Spec.DevWorkspace.Enable && instance.Spec.Auth.NativeUserMode == nil {
if util.IsOpenShift && checluster.Spec.DevWorkspace.Enable && checluster.Spec.Auth.NativeUserMode == nil {
newNativeUserModeValue := util.NewBoolPointer(true)
instance.Spec.Auth.NativeUserMode = newNativeUserModeValue
checluster.Spec.Auth.NativeUserMode = newNativeUserModeValue
if err := deploy.UpdateCheCRSpec(deployContext, "nativeUserMode", strconv.FormatBool(*newNativeUserModeValue)); err != nil {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
if util.IsOpenShift && instance.Spec.Auth.OpenShiftoAuth == nil {
if reconcileResult, err := r.autoEnableOAuth(deployContext, req, util.IsOpenShift4); err != nil {
if util.IsOpenShift && checluster.Spec.Auth.OpenShiftoAuth == nil {
if reconcileResult, err := r.autoEnableOAuth(deployContext); err != nil {
return reconcileResult, err
}
}
@ -376,7 +370,7 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
if proxy.TrustedCAMapName != "" {
provisioned, err := r.putOpenShiftCertsIntoConfigMap(deployContext)
if !provisioned {
configMapName := instance.Spec.Server.ServerTrustStoreConfigMapName
configMapName := checluster.Spec.Server.ServerTrustStoreConfigMapName
if err != nil {
r.Log.Error(err, "Error on provisioning", "config map", configMapName)
} else {
@ -399,13 +393,13 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
// To use Openshift v4 OAuth, the OAuth endpoints are served from a namespace
// and NOT from the Openshift API Master URL (as in v3)
// So we also need the self-signed certificate to access them (same as the Che server)
(util.IsOpenShift4 && util.IsOAuthEnabled(instance) && !instance.Spec.Server.TlsSupport) {
(util.IsOpenShift4 && util.IsOAuthEnabled(checluster) && !checluster.Spec.Server.TlsSupport) {
if err := deploy.CreateTLSSecretFromEndpoint(deployContext, "", deploy.CheTLSSelfSignedCertificateSecretName); err != nil {
return ctrl.Result{}, err
}
}
if util.IsOAuthEnabled(instance) {
if util.IsOAuthEnabled(checluster) {
// create a secret with OpenShift API crt to be added to keystore that RH SSO will consume
apiUrl, apiInternalUrl, err := util.GetOpenShiftAPIUrls()
if err != nil {
@ -419,8 +413,8 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}
} else {
// Handle Che TLS certificates on Kubernetes infrastructure
if instance.Spec.Server.TlsSupport {
if instance.Spec.K8s.TlsSecretName != "" {
if checluster.Spec.Server.TlsSupport {
if checluster.Spec.K8s.TlsSecretName != "" {
// Self-signed certificate should be created to secure Che ingresses
result, err := deploy.K8sHandleCheTLSSecrets(deployContext)
if result.Requeue || result.RequeueAfter > 0 {
@ -452,14 +446,10 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return ctrl.Result{}, err
}
if err := deploy.SetStatusDetails(deployContext, "", "", ""); err != nil {
return ctrl.Result{}, err
}
// 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(deployContext, CheServiceAccountName)
done, err = deploy.SyncServiceAccountToCluster(deployContext, deploy.CheServiceAccountName)
if !done {
if err != nil {
logrus.Error(err)
@ -484,12 +474,12 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return ctrl.Result{RequeueAfter: time.Second}, err
}
if len(instance.Spec.Server.CheClusterRoles) > 0 {
cheClusterRoles := strings.Split(instance.Spec.Server.CheClusterRoles, ",")
if len(checluster.Spec.Server.CheClusterRoles) > 0 {
cheClusterRoles := strings.Split(checluster.Spec.Server.CheClusterRoles, ",")
for _, cheClusterRole := range cheClusterRoles {
cheClusterRole := strings.TrimSpace(cheClusterRole)
cheClusterRoleBindingName := cheClusterRole
done, err := deploy.SyncClusterRoleBindingAndAddFinalizerToCluster(deployContext, cheClusterRoleBindingName, CheServiceAccountName, cheClusterRole)
done, err := deploy.SyncClusterRoleBindingAndAddFinalizerToCluster(deployContext, cheClusterRoleBindingName, deploy.CheServiceAccountName, cheClusterRole)
if !tests {
if !done {
logrus.Infof("Waiting on cluster role binding '%s' to be created", cheClusterRoleBindingName)
@ -504,7 +494,7 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
// If the user specified an additional cluster role to use for the Che workspace, create a role binding for it
// Use a role binding instead of a cluster role binding to keep the additional access scoped to the workspace's namespace
workspaceClusterRole := instance.Spec.Server.CheWorkspaceClusterRole
workspaceClusterRole := checluster.Spec.Server.CheWorkspaceClusterRole
if workspaceClusterRole != "" {
done, err := deploy.SyncRoleBindingToCluster(deployContext, "che-workspace-custom", "view", workspaceClusterRole, "ClusterRole")
if !done {
@ -515,8 +505,8 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}
}
if err := r.GenerateAndSaveFields(deployContext, req); err != nil {
instance, _ = r.GetCR(req)
if err := r.GenerateAndSaveFields(deployContext); err != nil {
_ = deploy.ReloadCheClusterCR(deployContext)
return ctrl.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
@ -543,7 +533,7 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}
// create and provision Keycloak related objects
if !instance.Spec.Auth.ExternalIdentityProvider {
if !checluster.Spec.Auth.ExternalIdentityProvider {
provisioned, err := identity_provider.SyncIdentityProviderToCluster(deployContext)
if !provisioned {
if err != nil {
@ -552,9 +542,9 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return ctrl.Result{}, err
}
} else {
keycloakURL := instance.Spec.Auth.IdentityProviderURL
if instance.Status.KeycloakURL != keycloakURL {
instance.Status.KeycloakURL = keycloakURL
keycloakURL := checluster.Spec.Auth.IdentityProviderURL
if checluster.Status.KeycloakURL != keycloakURL {
checluster.Status.KeycloakURL = keycloakURL
if err := deploy.UpdateCheCRStatus(deployContext, "status: Keycloak URL", keycloakURL); err != nil {
return reconcile.Result{}, err
}
@ -562,7 +552,7 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}
devfileRegistry := devfileregistry.NewDevfileRegistry(deployContext)
if !instance.Spec.Server.ExternalDevfileRegistry {
if !checluster.Spec.Server.ExternalDevfileRegistry {
done, err := devfileRegistry.SyncAll()
if !done {
if err != nil {
@ -572,7 +562,7 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}
}
if !instance.Spec.Server.ExternalPluginRegistry {
if !checluster.Spec.Server.ExternalPluginRegistry {
pluginRegistry := pluginregistry.NewPluginRegistry(deployContext)
done, err := pluginRegistry.SyncAll()
if !done {
@ -582,9 +572,9 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return ctrl.Result{}, err
}
} else {
if instance.Spec.Server.PluginRegistryUrl != instance.Status.PluginRegistryURL {
instance.Status.PluginRegistryURL = instance.Spec.Server.PluginRegistryUrl
if err := deploy.UpdateCheCRStatus(deployContext, "status: Plugin Registry URL", instance.Spec.Server.PluginRegistryUrl); err != nil {
if checluster.Spec.Server.PluginRegistryUrl != checluster.Status.PluginRegistryURL {
checluster.Status.PluginRegistryURL = checluster.Spec.Server.PluginRegistryUrl
if err := deploy.UpdateCheCRStatus(deployContext, "status: Plugin Registry URL", checluster.Spec.Server.PluginRegistryUrl); err != nil {
return reconcile.Result{}, err
}
}
@ -630,39 +620,37 @@ func (r *CheClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
// ignore error
deploy.DeleteFinalizer(deployContext, deploy.OAuthFinalizerName)
for {
instance.Status.OpenShiftoAuthProvisioned = false
checluster.Status.OpenShiftoAuthProvisioned = false
if err := deploy.UpdateCheCRStatus(deployContext, "status: provisioned with OpenShift identity provider", "false"); err != nil &&
errors.IsConflict(err) {
instance, _ = r.GetCR(req)
_ = deploy.ReloadCheClusterCR(deployContext)
continue
}
break
}
for {
instance.Spec.Auth.OAuthSecret = ""
instance.Spec.Auth.OAuthClientName = ""
checluster.Spec.Auth.OAuthSecret = ""
checluster.Spec.Auth.OAuthClientName = ""
if err := deploy.UpdateCheCRStatus(deployContext, "clean oAuth secret name and client name", ""); err != nil &&
errors.IsConflict(err) {
instance, _ = r.GetCR(req)
_ = deploy.ReloadCheClusterCR(deployContext)
continue
}
break
}
}
logrus.Info("Successfully reconciled.")
return ctrl.Result{}, nil
}
func (r *CheClusterReconciler) autoEnableOAuth(deployContext *deploy.DeployContext, request ctrl.Request, isOpenShift4 bool) (reconcile.Result, error) {
var message, reason string
func (r *CheClusterReconciler) autoEnableOAuth(deployContext *deploy.DeployContext) (reconcile.Result, error) {
oauth := false
cr := deployContext.CheCluster
if isOpenShift4 {
if util.IsOpenShift4 {
openshitOAuth, err := GetOpenshiftOAuth(deployContext.ClusterAPI.NonCachedClient)
if err != nil {
message = "Unable to get Openshift oAuth. Cause: " + err.Error()
logrus.Error(message)
reason = failedUnableToGetOAuth
logrus.Error("Unable to get Openshift oAuth. Cause: " + err.Error())
} else {
if len(openshitOAuth.Spec.IdentityProviders) > 0 {
oauth = true
@ -673,10 +661,8 @@ func (r *CheClusterReconciler) autoEnableOAuth(deployContext *deploy.DeployConte
} else if util.IsInitialOpenShiftOAuthUserEnabled(cr) {
provisioned, err := r.userHandler.SyncOAuthInitialUser(openshitOAuth, deployContext)
if err != nil {
message = warningNoIdentityProvidersMessage + " Operator tried to create initial OpenShift OAuth user for HTPasswd identity provider, but failed. Cause: " + err.Error()
logrus.Error(message)
logrus.Error(warningNoIdentityProvidersMessage + " Operator tried to create initial OpenShift OAuth user for HTPasswd identity provider, but failed. Cause: " + err.Error())
logrus.Info("To enable OpenShift OAuth, please add identity provider first: " + howToAddIdentityProviderLinkOS4)
reason = failedNoIdentityProviders
// Don't try to create initial user any more, che-operator shouldn't hang on this step.
cr.Spec.Auth.InitialOpenShiftOAuthUser = nil
if err := deploy.UpdateCheCRStatus(deployContext, "initialOpenShiftOAuthUser", ""); err != nil {
@ -701,15 +687,11 @@ func (r *CheClusterReconciler) autoEnableOAuth(deployContext *deploy.DeployConte
users := &userv1.UserList{}
listOptions := &client.ListOptions{}
if err := r.nonCachedClient.List(context.TODO(), users, listOptions); err != nil {
message = failedUnableToGetOpenshiftUsers + " Cause: " + err.Error()
logrus.Error(message)
reason = failedNoOpenshiftUser
logrus.Error(failedUnableToGetOpenshiftUsers + " Cause: " + err.Error())
} else {
oauth = len(users.Items) >= 1
if !oauth {
message = warningNoRealUsersMessage + " " + howToConfigureOAuthLinkOS3
logrus.Warn(message)
reason = failedNoOpenshiftUser
logrus.Warn(warningNoRealUsersMessage + " " + howToConfigureOAuthLinkOS3)
}
}
}
@ -722,12 +704,6 @@ func (r *CheClusterReconciler) autoEnableOAuth(deployContext *deploy.DeployConte
}
}
if message != "" && reason != "" {
if err := deploy.SetStatusDetails(deployContext, message, reason, ""); err != nil {
return reconcile.Result{}, err
}
}
return reconcile.Result{}, nil
}
@ -775,7 +751,7 @@ func (r *CheClusterReconciler) reconcileFinalizers(deployContext *deploy.DeployC
}
// Removes any legacy CRB https://github.com/eclipse/che/issues/19506
cheClusterRoleBindingName = deploy.GetLegacyUniqueClusterRoleBindingName(deployContext, CheServiceAccountName, cheClusterRole)
cheClusterRoleBindingName = deploy.GetLegacyUniqueClusterRoleBindingName(deployContext, deploy.CheServiceAccountName, cheClusterRole)
if err := deploy.ReconcileLegacyClusterRoleBindingFinalizer(deployContext, cheClusterRoleBindingName); err != nil {
logrus.Error(err)
}

View File

@ -17,8 +17,6 @@ import (
"io/ioutil"
"os"
"strconv"
"strings"
"unicode/utf8"
mocks "github.com/eclipse-che/che-operator/mocks"
@ -30,7 +28,6 @@ import (
identity_provider "github.com/eclipse-che/che-operator/pkg/deploy/identity-provider"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/eclipse-che/che-operator/pkg/deploy"
"github.com/eclipse-che/che-operator/pkg/util"
@ -59,7 +56,6 @@ import (
fakeDiscovery "k8s.io/client-go/discovery/fake"
fakeclientset "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
@ -73,51 +69,7 @@ import (
)
var (
namespace = "eclipse-che"
csvName = "kubernetes-imagepuller-operator.v0.0.9"
packageManifest = &packagesv1.PackageManifest{
ObjectMeta: metav1.ObjectMeta{
Name: "kubernetes-imagepuller-operator",
Namespace: namespace,
},
Status: packagesv1.PackageManifestStatus{
CatalogSource: "community-operators",
CatalogSourceNamespace: "olm",
DefaultChannel: "stable",
PackageName: "kubernetes-imagepuller-operator",
},
}
operatorGroup = &operatorsv1.OperatorGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "kubernetes-imagepuller-operator",
Namespace: namespace,
},
Spec: operatorsv1.OperatorGroupSpec{
TargetNamespaces: []string{
namespace,
},
},
}
subscription = &operatorsv1alpha1.Subscription{
ObjectMeta: metav1.ObjectMeta{
Name: "kubernetes-imagepuller-operator",
Namespace: namespace,
},
Spec: &operatorsv1alpha1.SubscriptionSpec{
CatalogSource: "community-operators",
Channel: "stable",
CatalogSourceNamespace: "olm",
InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic,
Package: "kubernetes-imagepuller-operator",
},
}
valueTrue = true
clusterServiceVersion = &operatorsv1alpha1.ClusterServiceVersion{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: csvName,
},
}
namespace = "eclipse-che"
nonEmptyUserList = &userv1.UserList{
Items: []userv1.User{
{
@ -158,7 +110,6 @@ var (
Name: "cluster",
},
}
defaultImagePullerImages string
)
func init() {
@ -170,8 +121,6 @@ func init() {
os.Setenv(env.Name, env.Value)
}
}
defaultImagePullerImages = "che-workspace-plugin-broker-metadata=" + os.Getenv("RELATED_IMAGE_che_workspace_plugin_broker_metadata") +
";che-workspace-plugin-broker-artifacts=" + os.Getenv("RELATED_IMAGE_che_workspace_plugin_broker_artifacts") + ";"
}
func TestNativeUserModeEnabled(t *testing.T) {
@ -249,14 +198,9 @@ func TestNativeUserModeEnabled(t *testing.T) {
t.Fatal("Error creating fake discovery client")
}
r := &CheClusterReconciler{
client: cli,
nonCachedClient: nonCachedClient,
discoveryClient: fakeDiscovery,
Scheme: scheme,
tests: true,
Log: ctrl.Log.WithName("controllers").WithName("CheCluster"),
}
r := NewReconciler(cli, nonCachedClient, fakeDiscovery, scheme, "")
r.tests = true
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: os.Getenv("CHE_FLAVOR"),
@ -468,15 +412,10 @@ func TestCaseAutoDetectOAuth(t *testing.T) {
defer ctrl.Finish()
}
r := &CheClusterReconciler{
client: cli,
nonCachedClient: nonCachedClient,
discoveryClient: fakeDiscovery,
Scheme: scheme,
tests: true,
userHandler: userHandlerMock,
Log: ctrl.Log.WithName("controllers").WithName("CheCluster"),
}
r := NewReconciler(cli, nonCachedClient, fakeDiscovery, scheme, "")
r.userHandler = userHandlerMock
r.tests = true
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: os.Getenv("CHE_FLAVOR"),
@ -566,14 +505,9 @@ func TestEnsureServerExposureStrategy(t *testing.T) {
t.Fatal("Error creating fake discovery client")
}
r := &CheClusterReconciler{
client: cli,
nonCachedClient: nonCachedClient,
discoveryClient: fakeDiscovery,
Scheme: scheme,
tests: true,
Log: ctrl.Log.WithName("controllers").WithName("CheCluster"),
}
r := NewReconciler(cli, nonCachedClient, fakeDiscovery, scheme, "")
r.tests = true
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: os.Getenv("CHE_FLAVOR"),
@ -690,14 +624,9 @@ func TestShouldSetUpCorrectlyDevfileRegistryURL(t *testing.T) {
}
fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{}
r := &CheClusterReconciler{
client: cli,
nonCachedClient: nonCachedClient,
discoveryClient: fakeDiscovery,
Scheme: scheme,
tests: true,
Log: ctrl.Log.WithName("controllers").WithName("CheCluster"),
}
r := NewReconciler(cli, nonCachedClient, fakeDiscovery, scheme, "")
r.tests = true
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: os.Getenv("CHE_FLAVOR"),
@ -725,376 +654,6 @@ func TestShouldSetUpCorrectlyDevfileRegistryURL(t *testing.T) {
}
}
func TestImagePullerConfiguration(t *testing.T) {
oldBrokerMetaDataImage := strings.Split(os.Getenv("RELATED_IMAGE_che_workspace_plugin_broker_metadata"), ":")[0] + ":old"
oldBrokerArtifactsImage := strings.Split(os.Getenv("RELATED_IMAGE_che_workspace_plugin_broker_artifacts"), ":")[0] + ":old"
type testCase struct {
name string
initCR *orgv1.CheCluster
initObjects []runtime.Object
expectedCR *orgv1.CheCluster
expectedOperatorGroup *operatorsv1.OperatorGroup
expectedSubscription *operatorsv1alpha1.Subscription
expectedImagePuller *chev1alpha1.KubernetesImagePuller
shouldDelete bool
}
testCases := []testCase{
{
name: "image puller enabled, no operatorgroup, should create an operatorgroup",
initCR: InitCheCRWithImagePullerEnabled(),
initObjects: []runtime.Object{
packageManifest,
},
expectedOperatorGroup: operatorGroup,
},
{
name: "image puller enabled, operatorgroup exists, should create a subscription",
initCR: InitCheCRWithImagePullerEnabled(),
initObjects: []runtime.Object{
packageManifest,
operatorGroup,
},
expectedSubscription: subscription,
},
{
name: "image puller enabled, subscription created, should add finalizer",
initCR: InitCheCRWithImagePullerEnabled(),
expectedCR: ExpectedCheCRWithImagePullerFinalizer(),
initObjects: []runtime.Object{
packageManifest,
operatorGroup,
subscription,
},
},
{
name: "image puller enabled with finalizer but default values are empty, subscription exists, should update the CR",
initCR: InitCheCRWithImagePullerFinalizer(),
expectedCR: &orgv1.CheCluster{
TypeMeta: metav1.TypeMeta{
Kind: "CheCluster",
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
ResourceVersion: "2",
Finalizers: []string{
"kubernetesimagepullers.finalizers.che.eclipse.org",
},
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
Spec: chev1alpha1.KubernetesImagePullerSpec{
DeploymentName: "kubernetes-image-puller",
ConfigMapName: "k8s-image-puller",
},
},
Server: orgv1.CheClusterSpecServer{
ServerExposureStrategy: "multi-host",
},
},
},
initObjects: []runtime.Object{
packageManifest,
operatorGroup,
subscription,
},
},
{
name: "image puller enabled default values already set, subscription exists, should create a KubernetesImagePuller",
initCR: InitCheCRWithImagePullerEnabledAndDefaultValuesSet(),
initObjects: []runtime.Object{
packageManifest,
operatorGroup,
subscription,
},
expectedImagePuller: InitImagePuller(ImagePullerOptions{SpecImages: defaultImagePullerImages, ObjectMetaResourceVersion: "1"}),
},
{
name: "image puller enabled, user images set, subscription exists, should create a KubernetesImagePuller with user images",
initCR: InitCheCRWithImagePullerEnabledAndImagesSet("image=image_url"),
initObjects: []runtime.Object{
packageManifest,
operatorGroup,
subscription,
},
expectedImagePuller: InitImagePuller(ImagePullerOptions{SpecImages: "image=image_url", ObjectMetaResourceVersion: "1"}),
},
{
name: "image puller enabled, one default image set, subscription exists, should update KubernetesImagePuller default image",
initCR: InitCheCRWithImagePullerEnabledAndImagesSet("che-workspace-plugin-broker-metadata=" + oldBrokerMetaDataImage + ";"),
initObjects: []runtime.Object{
packageManifest,
operatorGroup,
subscription,
InitImagePuller(ImagePullerOptions{SpecImages: "che-workspace-plugin-broker-metadata=" + oldBrokerMetaDataImage + ";", ObjectMetaResourceVersion: "1"}),
},
expectedImagePuller: InitImagePuller(ImagePullerOptions{SpecImages: "che-workspace-plugin-broker-metadata=" + os.Getenv("RELATED_IMAGE_che_workspace_plugin_broker_metadata") + ";", ObjectMetaResourceVersion: "2"}),
},
{
name: "image puller enabled, one default image set, subscription exists, should update KubernetesImagePuller default images while keeping user image",
initCR: InitCheCRWithImagePullerEnabledAndImagesSet("image=image_url;che-workspace-plugin-broker-metadata=" + oldBrokerMetaDataImage + ";"),
initObjects: []runtime.Object{
packageManifest,
operatorGroup,
subscription,
InitImagePuller(ImagePullerOptions{SpecImages: "image=image_url;che-workspace-plugin-broker-metadata=" + oldBrokerMetaDataImage + ";", ObjectMetaResourceVersion: "1"}),
},
expectedImagePuller: InitImagePuller(ImagePullerOptions{SpecImages: "image=image_url;che-workspace-plugin-broker-metadata=" + os.Getenv("RELATED_IMAGE_che_workspace_plugin_broker_metadata") + ";", ObjectMetaResourceVersion: "2"}),
},
{
name: "image puller enabled, default images set, subscription exists, should update KubernetesImagePuller default images",
initCR: InitCheCRWithImagePullerEnabledAndImagesSet("che-workspace-plugin-broker-metadata=" + oldBrokerMetaDataImage + ";che-workspace-plugin-broker-artifacts=" + oldBrokerArtifactsImage + ";"),
initObjects: []runtime.Object{
packageManifest,
operatorGroup,
subscription,
InitImagePuller(ImagePullerOptions{SpecImages: "che-workspace-plugin-broker-metadata=" + oldBrokerMetaDataImage + ";che-workspace-plugin-broker-artifacts=" + oldBrokerArtifactsImage + ";", ObjectMetaResourceVersion: "1"}),
},
expectedImagePuller: InitImagePuller(ImagePullerOptions{SpecImages: defaultImagePullerImages, ObjectMetaResourceVersion: "2"}),
},
{
name: "image puller enabled, latest default images set, subscription exists, should not update KubernetesImagePuller default images",
initCR: InitCheCRWithImagePullerEnabledAndImagesSet(defaultImagePullerImages),
initObjects: []runtime.Object{
packageManifest,
operatorGroup,
subscription,
InitImagePuller(ImagePullerOptions{SpecImages: defaultImagePullerImages, ObjectMetaResourceVersion: "1"}),
},
expectedImagePuller: InitImagePuller(ImagePullerOptions{SpecImages: defaultImagePullerImages, ObjectMetaResourceVersion: "1"}),
},
{
name: "image puller enabled, default images not set, subscription exists, should not set KubernetesImagePuller default images",
initCR: InitCheCRWithImagePullerEnabledAndImagesSet("image=image_url;"),
initObjects: []runtime.Object{
packageManifest,
operatorGroup,
subscription,
InitImagePuller(ImagePullerOptions{SpecImages: "image=image_url;", ObjectMetaResourceVersion: "1"}),
},
expectedImagePuller: InitImagePuller(ImagePullerOptions{SpecImages: "image=image_url;", ObjectMetaResourceVersion: "1"}),
},
{
name: "image puller enabled, KubernetesImagePuller created and spec in CheCluster is different, should update the KubernetesImagePuller",
initCR: InitCheCRWithImagePullerEnabledAndNewValuesSet(),
initObjects: []runtime.Object{
packageManifest,
operatorGroup,
subscription,
getDefaultImagePuller(),
},
expectedImagePuller: &chev1alpha1.KubernetesImagePuller{
TypeMeta: metav1.TypeMeta{Kind: "KubernetesImagePuller", APIVersion: "che.eclipse.org/v1alpha1"},
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
Name: os.Getenv("CHE_FLAVOR") + "-image-puller",
Namespace: namespace,
Labels: map[string]string{
"app": "che",
"component": "kubernetes-image-puller",
"app.kubernetes.io/part-of": os.Getenv("CHE_FLAVOR"),
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "org.eclipse.che/v1",
Kind: "CheCluster",
BlockOwnerDeletion: &valueTrue,
Controller: &valueTrue,
Name: os.Getenv("CHE_FLAVOR"),
},
},
},
Spec: chev1alpha1.KubernetesImagePullerSpec{
ConfigMapName: "k8s-image-puller-trigger-update",
DeploymentName: "kubernetes-image-puller-trigger-update",
},
},
},
{
name: "image puller already created, imagePuller disabled, should delete everything",
initCR: InitCheCRWithImagePullerDisabled(),
initObjects: []runtime.Object{
packageManifest,
operatorGroup,
subscription,
clusterServiceVersion,
getDefaultImagePuller(),
},
expectedCR: InitCheCRWithImagePullerDisabled(),
shouldDelete: true,
},
{
name: "image puller already created, finalizer deleted",
initCR: InitCheCRWithImagePullerFinalizerAndDeletionTimestamp(),
initObjects: []runtime.Object{
packageManifest,
operatorGroup,
subscription,
clusterServiceVersion,
getDefaultImagePuller(),
},
shouldDelete: true,
expectedCR: nil,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true)))
orgv1.SchemeBuilder.AddToScheme(scheme.Scheme)
packagesv1.AddToScheme(scheme.Scheme)
operatorsv1alpha1.AddToScheme(scheme.Scheme)
operatorsv1.AddToScheme(scheme.Scheme)
chev1alpha1.AddToScheme(scheme.Scheme)
routev1.AddToScheme(scheme.Scheme)
testCase.initObjects = append(testCase.initObjects, testCase.initCR)
cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...)
nonCachedClient := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...)
clientSet := fakeclientset.NewSimpleClientset()
fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery)
fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{
{
GroupVersion: "packages.operators.coreos.com/v1",
APIResources: []metav1.APIResource{
{
Kind: "PackageManifest",
},
},
},
{
GroupVersion: "operators.coreos.com/v1alpha1",
APIResources: []metav1.APIResource{
{Kind: "OperatorGroup"},
{Kind: "Subscription"},
{Kind: "ClusterServiceVersion"},
},
},
{
GroupVersion: "che.eclipse.org/v1alpha1",
APIResources: []metav1.APIResource{
{Kind: "KubernetesImagePuller"},
},
},
}
if !ok {
t.Error("Error creating fake discovery client")
os.Exit(1)
}
r := &CheClusterReconciler{
client: cli,
nonCachedClient: nonCachedClient,
discoveryClient: fakeDiscovery,
Scheme: scheme.Scheme,
tests: true,
Log: ctrl.Log.WithName("controllers").WithName("CheCluster"),
}
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
},
}
_, err := r.Reconcile(context.TODO(), req)
if err != nil {
t.Fatalf("Error reconciling: %v", err)
}
if testCase.expectedOperatorGroup != nil {
gotOperatorGroup := &operatorsv1.OperatorGroup{}
err := r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: testCase.expectedOperatorGroup.Namespace, Name: testCase.expectedOperatorGroup.Name}, gotOperatorGroup)
if err != nil {
t.Errorf("Error getting OperatorGroup: %v", err)
}
if !reflect.DeepEqual(testCase.expectedOperatorGroup.Spec.TargetNamespaces, gotOperatorGroup.Spec.TargetNamespaces) {
t.Errorf("Error expected target namespace %v but got %v", testCase.expectedOperatorGroup.Spec.TargetNamespaces, gotOperatorGroup.Spec.TargetNamespaces)
}
}
if testCase.expectedSubscription != nil {
gotSubscription := &operatorsv1alpha1.Subscription{}
err := r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: testCase.expectedSubscription.Namespace, Name: testCase.expectedSubscription.Name}, gotSubscription)
if err != nil {
t.Errorf("Error getting Subscription: %v", err)
}
if !reflect.DeepEqual(testCase.expectedSubscription.Spec, gotSubscription.Spec) {
t.Errorf("Error, subscriptions differ (-want +got) %v", cmp.Diff(testCase.expectedSubscription.Spec, gotSubscription.Spec))
}
}
// if expectedCR is not set, don't check it
if testCase.expectedCR != nil && !reflect.DeepEqual(testCase.initCR, testCase.expectedCR) {
gotCR := &orgv1.CheCluster{}
err = r.client.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: os.Getenv("CHE_FLAVOR")}, gotCR)
if err != nil {
t.Errorf("Error getting CheCluster: %v", err)
}
if !reflect.DeepEqual(testCase.expectedCR, gotCR) {
t.Errorf("Expected CR and CR returned from API server are different (-want +got): %v", cmp.Diff(testCase.expectedCR, gotCR))
}
}
if testCase.expectedImagePuller != nil {
gotImagePuller := &chev1alpha1.KubernetesImagePuller{}
err = r.client.Get(context.TODO(), types.NamespacedName{Namespace: testCase.expectedImagePuller.Namespace, Name: testCase.expectedImagePuller.Name}, gotImagePuller)
if err != nil {
t.Errorf("Error getting KubernetesImagePuller: %v", err)
}
diff := cmp.Diff(testCase.expectedImagePuller, gotImagePuller, cmpopts.IgnoreFields(chev1alpha1.KubernetesImagePullerSpec{}, "Images"))
if diff != "" {
t.Errorf("Expected KubernetesImagePuller and KubernetesImagePuller returned from API server differ (-want, +got): %v", diff)
}
expectedImages := nonEmptySplit(testCase.expectedImagePuller.Spec.Images, ";")
if len(nonEmptySplit(testCase.expectedImagePuller.Spec.Images, ";")) != len(expectedImages) {
t.Errorf("Expected KubernetesImagePuller returns %d images", len(expectedImages))
}
for _, expectedImage := range expectedImages {
if !strings.Contains(gotImagePuller.Spec.Images, expectedImage) {
t.Errorf("Expected KubernetesImagePuller returned image: %s, but it did not", expectedImage)
}
}
}
if testCase.shouldDelete {
if testCase.expectedCR == nil {
gotCR := &orgv1.CheCluster{}
err = r.client.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: os.Getenv("CHE_FLAVOR")}, gotCR)
if !errors.IsNotFound(err) {
t.Fatal("CR CheCluster should be removed")
}
}
imagePuller := &chev1alpha1.KubernetesImagePuller{}
err = r.client.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: os.Getenv("CHE_FLAVOR") + "-image-puller"}, imagePuller)
if err == nil || !errors.IsNotFound(err) {
t.Fatalf("Should not have found KubernetesImagePuller: %v", err)
}
clusterServiceVersion := &operatorsv1alpha1.ClusterServiceVersion{}
err = r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: csvName}, clusterServiceVersion)
if err == nil || !errors.IsNotFound(err) {
t.Fatalf("Should not have found ClusterServiceVersion: %v", err)
}
subscription := &operatorsv1alpha1.Subscription{}
err = r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: "kubernetes-imagepuller-operator"}, subscription)
if err == nil || !errors.IsNotFound(err) {
t.Fatalf("Should not have found Subscription: %v", err)
}
operatorGroup := &operatorsv1.OperatorGroup{}
err = r.nonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: "kubernetes-imagepuller-operator"}, operatorGroup)
if err == nil || !errors.IsNotFound(err) {
t.Fatalf("Should not have found OperatorGroup: %v", err)
}
}
})
}
}
func TestCheController(t *testing.T) {
var err error
@ -1104,7 +663,8 @@ func TestCheController(t *testing.T) {
cl, dc, scheme := Init()
// Create a ReconcileChe object with the scheme and fake client
r := &CheClusterReconciler{client: cl, nonCachedClient: cl, Scheme: &scheme, discoveryClient: dc, tests: true, Log: ctrl.Log.WithName("controllers").WithName("CheCluster")}
r := NewReconciler(cl, cl, dc, &scheme, "")
r.tests = true
// get CR
cheCR := &orgv1.CheCluster{
@ -1343,7 +903,8 @@ func TestConfiguringLabelsForRoutes(t *testing.T) {
cl, dc, scheme := Init()
// Create a ReconcileChe object with the scheme and fake client
r := &CheClusterReconciler{client: cl, nonCachedClient: cl, Scheme: &scheme, discoveryClient: dc, tests: true, Log: ctrl.Log.WithName("controllers").WithName("CheCluster")}
r := NewReconciler(cl, cl, dc, &scheme, "")
r.tests = true
// get CR
cheCR := &orgv1.CheCluster{}
@ -1479,15 +1040,9 @@ func TestShouldDelegatePermissionsForCheWorkspaces(t *testing.T) {
defer ctrl.Finish()
}
r := &CheClusterReconciler{
client: cli,
nonCachedClient: nonCachedClient,
discoveryClient: fakeDiscovery,
Scheme: scheme,
permissionChecker: m,
tests: true,
Log: ctrl.Log.WithName("controllers").WithName("CheCluster"),
}
r := NewReconciler(cli, nonCachedClient, fakeDiscovery, scheme, "")
r.tests = true
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: os.Getenv("CHE_FLAVOR"),
@ -1642,261 +1197,3 @@ func InitCheWithSimpleCR() *orgv1.CheCluster {
},
}
}
func InitCheCRWithImagePullerEnabled() *orgv1.CheCluster {
return &orgv1.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
ResourceVersion: "0",
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
},
Server: orgv1.CheClusterSpecServer{
ServerExposureStrategy: "multi-host",
},
},
}
}
func InitCheCRWithImagePullerFinalizer() *orgv1.CheCluster {
return &orgv1.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
Finalizers: []string{
"kubernetesimagepullers.finalizers.che.eclipse.org",
},
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
},
Server: orgv1.CheClusterSpecServer{
ServerExposureStrategy: "multi-host",
},
},
}
}
func InitCheCRWithImagePullerFinalizerAndDeletionTimestamp() *orgv1.CheCluster {
return &orgv1.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
Finalizers: []string{
"kubernetesimagepullers.finalizers.che.eclipse.org",
},
DeletionTimestamp: &metav1.Time{Time: time.Unix(1, 0)},
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
},
Server: orgv1.CheClusterSpecServer{
ServerExposureStrategy: "multi-host",
},
},
}
}
func ExpectedCheCRWithImagePullerFinalizer() *orgv1.CheCluster {
return &orgv1.CheCluster{
TypeMeta: metav1.TypeMeta{
Kind: "CheCluster",
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
Finalizers: []string{
"kubernetesimagepullers.finalizers.che.eclipse.org",
},
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
},
Server: orgv1.CheClusterSpecServer{
ServerExposureStrategy: "multi-host",
},
},
}
}
func InitCheCRWithImagePullerDisabled() *orgv1.CheCluster {
return &orgv1.CheCluster{
TypeMeta: metav1.TypeMeta{
Kind: "CheCluster",
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: false,
},
},
}
}
func InitCheCRWithImagePullerEnabledAndDefaultValuesSet() *orgv1.CheCluster {
return &orgv1.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
Finalizers: []string{
"kubernetesimagepullers.finalizers.che.eclipse.org",
},
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
Spec: chev1alpha1.KubernetesImagePullerSpec{
DeploymentName: "kubernetes-image-puller",
ConfigMapName: "k8s-image-puller",
},
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
},
},
}
}
func InitCheCRWithImagePullerEnabledAndImagesSet(images string) *orgv1.CheCluster {
return &orgv1.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
Finalizers: []string{
"kubernetesimagepullers.finalizers.che.eclipse.org",
},
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
Spec: chev1alpha1.KubernetesImagePullerSpec{
DeploymentName: "kubernetes-image-puller",
ConfigMapName: "k8s-image-puller",
Images: images,
},
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
},
},
}
}
func InitCheCRWithImagePullerEnabledAndNewValuesSet() *orgv1.CheCluster {
return &orgv1.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
Finalizers: []string{
"kubernetesimagepullers.finalizers.che.eclipse.org",
},
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
Spec: chev1alpha1.KubernetesImagePullerSpec{
DeploymentName: "kubernetes-image-puller-trigger-update",
ConfigMapName: "k8s-image-puller-trigger-update",
},
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
},
},
}
}
type ImagePullerOptions struct {
SpecImages string
ObjectMetaResourceVersion string
}
func InitImagePuller(options ImagePullerOptions) *chev1alpha1.KubernetesImagePuller {
return &chev1alpha1.KubernetesImagePuller{
TypeMeta: metav1.TypeMeta{
APIVersion: "che.eclipse.org/v1alpha1",
Kind: "KubernetesImagePuller",
},
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR") + "-image-puller",
Namespace: namespace,
Labels: map[string]string{
"app.kubernetes.io/part-of": os.Getenv("CHE_FLAVOR"),
"app": "che",
"component": "kubernetes-image-puller",
},
ResourceVersion: options.ObjectMetaResourceVersion,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "org.eclipse.che/v1",
Kind: "CheCluster",
Controller: &valueTrue,
BlockOwnerDeletion: &valueTrue,
Name: os.Getenv("CHE_FLAVOR"),
},
},
},
Spec: chev1alpha1.KubernetesImagePullerSpec{
DeploymentName: "kubernetes-image-puller",
ConfigMapName: "k8s-image-puller",
Images: options.SpecImages,
},
}
}
func getDefaultImagePuller() *chev1alpha1.KubernetesImagePuller {
return &chev1alpha1.KubernetesImagePuller{
TypeMeta: metav1.TypeMeta{
APIVersion: "che.eclipse.org/v1alpha1",
Kind: "KubernetesImagePuller",
},
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR") + "-image-puller",
Namespace: namespace,
Labels: map[string]string{
"app.kubernetes.io/part-of": os.Getenv("CHE_FLAVOR"),
"app": "che",
"component": "kubernetes-image-puller",
},
ResourceVersion: "1",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "org.eclipse.che/v1",
Kind: "CheCluster",
Controller: &valueTrue,
BlockOwnerDeletion: &valueTrue,
Name: os.Getenv("CHE_FLAVOR"),
},
},
},
Spec: chev1alpha1.KubernetesImagePullerSpec{
DeploymentName: "kubernetes-image-puller",
ConfigMapName: "k8s-image-puller",
Images: defaultImagePullerImages,
},
}
}
// Split string by separator without empty elems
func nonEmptySplit(lineToSplit string, separator string) []string {
splitFn := func(c rune) bool {
runeChar, _ := utf8.DecodeRuneInString(separator)
return c == runeChar
}
return strings.FieldsFunc(lineToSplit, splitFn)
}

View File

@ -0,0 +1,61 @@
//
// 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 che
import (
"fmt"
"strings"
"github.com/eclipse-che/che-operator/pkg/deploy"
"github.com/eclipse-che/che-operator/pkg/util"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
const (
NamespaceStrategyErrorMessage = "Namespace strategies other than 'per user' is not supported anymore. Using the <username> or <userid> placeholder is required in the 'spec.server.workspaceNamespaceDefault' field. The current value is: %s"
AuthenticationModeErrorMessage = "Single user authentication mode is not supported anymore. To backup your data you can commit workspace configuration to an SCM server and use factories to restore it in multi user mode. To switch to multi user authentication mode set 'spec.server.customCheProperties.CHE_MULTIUSER' to 'true' in %s CheCluster custom resource. Switching to multi user authentication mode without backing up your data will cause data loss."
)
// CheClusterValidator checks CheCluster CR configuration.
// It detect:
// - configurations which miss required field(s) to deploy Che
// - self-contradictory configurations
// - configurations with which it is impossible to deploy Che
type CheClusterValidator struct {
}
func NewCheClusterValidator() *CheClusterValidator {
return &CheClusterValidator{}
}
func (v *CheClusterValidator) Reconcile(ctx *deploy.DeployContext) (reconcile.Result, bool, error) {
if !util.IsOpenShift {
if ctx.CheCluster.Spec.K8s.IngressDomain == "" {
return reconcile.Result{}, false, fmt.Errorf("Required field \"spec.K8s.IngressDomain\" is not set")
}
}
workspaceNamespaceDefault := util.GetWorkspaceNamespaceDefault(ctx.CheCluster)
if strings.Index(workspaceNamespaceDefault, "<username>") == -1 && strings.Index(workspaceNamespaceDefault, "<userid>") == -1 {
return reconcile.Result{}, false, fmt.Errorf(NamespaceStrategyErrorMessage, workspaceNamespaceDefault)
}
if !util.IsCheMultiUser(ctx.CheCluster) {
return reconcile.Result{}, false, fmt.Errorf(AuthenticationModeErrorMessage, ctx.CheCluster.Name)
}
return reconcile.Result{}, true, nil
}
func (v *CheClusterValidator) Finalize(ctx *deploy.DeployContext) (bool, error) {
return true, nil
}

View File

@ -16,10 +16,9 @@ import (
"github.com/eclipse-che/che-operator/pkg/util"
"github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
func (r *CheClusterReconciler) GenerateAndSaveFields(deployContext *deploy.DeployContext, request reconcile.Request) (err error) {
func (r *CheClusterReconciler) GenerateAndSaveFields(deployContext *deploy.DeployContext) (err error) {
cheFlavor := deploy.DefaultCheFlavor(deployContext.CheCluster)
cheNamespace := deployContext.CheCluster.Namespace
if len(deployContext.CheCluster.Spec.Server.CheFlavor) < 1 {

View File

@ -84,7 +84,7 @@ func (r *CheClusterReconciler) delegateWorkspacePermissionsInTheDifferNamespaceT
return false, err
}
done, err = deploy.SyncClusterRoleBindingToCluster(deployContext, сheWorkspacesClusterRoleBindingName, CheServiceAccountName, сheWorkspacesClusterRoleName)
done, err = deploy.SyncClusterRoleBindingToCluster(deployContext, сheWorkspacesClusterRoleBindingName, deploy.CheServiceAccountName, сheWorkspacesClusterRoleName)
if !done {
return false, err
}
@ -121,7 +121,7 @@ func (r *CheClusterReconciler) delegateNamespaceEditorPermissions(deployContext
return false, err
}
done, err = deploy.SyncClusterRoleBindingToCluster(deployContext, сheNamespaceEditorClusterRoleBindingName, CheServiceAccountName, сheNamespaceEditorClusterRoleName)
done, err = deploy.SyncClusterRoleBindingToCluster(deployContext, сheNamespaceEditorClusterRoleBindingName, deploy.CheServiceAccountName, сheNamespaceEditorClusterRoleName)
if !done {
return false, err
}
@ -156,7 +156,7 @@ func (r *CheClusterReconciler) delegateDevWorkspacePermissions(deployContext *de
return false, err
}
done, err = deploy.SyncClusterRoleBindingToCluster(deployContext, devWorkspaceClusterRoleBindingName, CheServiceAccountName, devWorkspaceClusterRoleName)
done, err = deploy.SyncClusterRoleBindingToCluster(deployContext, devWorkspaceClusterRoleBindingName, deploy.CheServiceAccountName, devWorkspaceClusterRoleName)
if !done {
return false, err
}

View File

@ -34,6 +34,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
@ -237,11 +238,13 @@ func main() {
os.Exit(1)
}
cheReconciler, err := checontroller.NewReconciler(mgr, watchNamespace, discoveryClient)
noncachedClient, err := client.New(mgr.GetConfig(), client.Options{Scheme: scheme})
if err != nil {
setupLog.Error(err, "unable to create checluster reconciler")
setupLog.Error(err, "unable to initialize non cached client")
os.Exit(1)
}
cheReconciler := checontroller.NewReconciler(mgr.GetClient(), noncachedClient, discoveryClient, mgr.GetScheme(), watchNamespace)
backupReconciler := backupcontroller.NewReconciler(mgr, watchNamespace)
restoreReconciler := restorecontroller.NewReconciler(mgr, watchNamespace)

View File

@ -55,7 +55,3 @@ type Proxy struct {
NoProxy string
TrustedCAMapName string
}
type Syncable interface {
Sync() (bool, error)
}

View File

@ -122,6 +122,9 @@ const (
PluginRegistryName = "plugin-registry"
PostgresName = "postgres"
// CheServiceAccountName - service account name for che-server.
CheServiceAccountName = "che"
// limits
DefaultDashboardMemoryLimit = "256Mi"
DefaultDashboardMemoryRequest = "32Mi"
@ -164,6 +167,8 @@ const (
GitLabOAuthConfigMountPath = "/che-conf/oauth/gitlab"
GitLabOAuthConfigClientIdFileName = "id"
GitLabOAuthConfigClientSecretFileName = "secret"
InstallOrUpdateFailed = "InstallOrUpdateFailed"
)
func InitDefaults(defaultsPath string) {

View File

@ -45,25 +45,35 @@ type ImageAndName struct {
Image string // image (ex. quay.io/test/abc)
}
type ImagePuller struct {
}
func NewImagePuller() *ImagePuller {
return &ImagePuller{}
}
func (ip *ImagePuller) Reconcile(ctx *DeployContext) (reconcile.Result, bool, error) {
return ReconcileImagePuller(ctx)
}
func (ip *ImagePuller) Finalize(ctx *DeployContext) (bool, error) {
return DeleteImagePullerOperatorAndFinalizer(ctx)
}
// Reconcile the imagePuller section of the CheCluster CR. If imagePuller.enable is set to true, install the Kubernetes Image Puller operator and create
// a KubernetesImagePuller CR. Add a finalizer to the CheCluster CR. If false, remove the KubernetesImagePuller CR, uninstall the operator, and remove the finalizer.
func ReconcileImagePuller(ctx *DeployContext) (reconcile.Result, error) {
if !ctx.CheCluster.ObjectMeta.DeletionTimestamp.IsZero() {
return DeleteImagePullerOperatorAndFinalizer(ctx)
}
func ReconcileImagePuller(ctx *DeployContext) (reconcile.Result, bool, error) {
// Determine what server groups the API Server knows about
foundPackagesAPI, foundOperatorsAPI, _, err := CheckNeededImagePullerApis(ctx)
if err != nil {
logrus.Errorf("Error discovering image puller APIs: %v", err)
return reconcile.Result{}, err
return reconcile.Result{}, false, err
}
// If the image puller should be installed but the APIServer doesn't know about PackageManifests/Subscriptions, log a warning and requeue
if ctx.CheCluster.Spec.ImagePuller.Enable && (!foundPackagesAPI || !foundOperatorsAPI) {
logrus.Infof("Couldn't find Operator Lifecycle Manager types to install the Kubernetes Image Puller Operator. Please install Operator Lifecycle Manager to install the operator or disable the image puller by setting spec.imagePuller.enable to false.")
return reconcile.Result{RequeueAfter: time.Second}, nil
return reconcile.Result{RequeueAfter: time.Second}, false, nil
}
if ctx.CheCluster.Spec.ImagePuller.Enable {
@ -72,42 +82,42 @@ func ReconcileImagePuller(ctx *DeployContext) (reconcile.Result, error) {
if err != nil {
if errors.IsNotFound(err) {
logrus.Infof("There is no PackageManifest for the Kubernetes Image Puller Operator. Install the Operator Lifecycle Manager and the Community Operators Catalog")
return reconcile.Result{RequeueAfter: time.Second}, nil
return reconcile.Result{RequeueAfter: time.Second}, false, nil
}
logrus.Errorf("Error getting packagemanifest: %v", err)
return reconcile.Result{}, err
return reconcile.Result{}, false, err
}
createdOperatorGroup, err := CreateOperatorGroupIfNotFound(ctx)
if err != nil {
logrus.Infof("Error creating OperatorGroup: %v", err)
return reconcile.Result{}, err
return reconcile.Result{}, false, err
}
if createdOperatorGroup {
return reconcile.Result{RequeueAfter: time.Second}, nil
return reconcile.Result{RequeueAfter: time.Second}, false, nil
}
createdOperatorSubscription, err := CreateImagePullerSubscription(ctx, packageManifest)
if err != nil {
logrus.Infof("Error creating Subscription: %v", err)
return reconcile.Result{}, err
return reconcile.Result{}, false, err
}
if createdOperatorSubscription {
return reconcile.Result{RequeueAfter: time.Second}, nil
return reconcile.Result{RequeueAfter: time.Second}, false, nil
}
// Add the image puller finalizer
if !HasImagePullerFinalizer(ctx.CheCluster) {
if err := ReconcileImagePullerFinalizer(ctx); err != nil {
return reconcile.Result{}, err
return reconcile.Result{}, false, err
}
return reconcile.Result{RequeueAfter: time.Second}, nil
return reconcile.Result{RequeueAfter: time.Second}, false, nil
}
}
_, _, foundKubernetesImagePullerAPI, err := CheckNeededImagePullerApis(ctx)
if err != nil {
logrus.Errorf("Error discovering image puller APIs: %v", err)
return reconcile.Result{}, err
return reconcile.Result{}, false, err
}
// If the KubernetesImagePuller API service exists, attempt to reconcile creation/update
if foundKubernetesImagePullerAPI {
@ -125,15 +135,15 @@ func ReconcileImagePuller(ctx *DeployContext) (reconcile.Result, error) {
_, err := UpdateImagePullerSpecIfEmpty(ctx)
if err != nil {
logrus.Errorf("Error updating CheCluster: %v", err)
return reconcile.Result{}, err
return reconcile.Result{}, false, err
}
return reconcile.Result{RequeueAfter: time.Second}, nil
return reconcile.Result{RequeueAfter: time.Second}, false, nil
}
if ctx.CheCluster.IsImagePullerImagesEmpty() {
if err = SetDefaultImages(ctx); err != nil {
logrus.Error(err)
return reconcile.Result{}, err
return reconcile.Result{}, false, err
}
}
@ -141,19 +151,19 @@ func ReconcileImagePuller(ctx *DeployContext) (reconcile.Result, error) {
createdImagePuller, err := CreateKubernetesImagePuller(ctx)
if err != nil {
logrus.Error("Error creating KubernetesImagePuller: ", err)
return reconcile.Result{}, err
return reconcile.Result{}, false, err
}
if createdImagePuller {
return reconcile.Result{}, nil
return reconcile.Result{}, false, nil
}
}
logrus.Errorf("Error getting KubernetesImagePuller: %v", err)
return reconcile.Result{}, err
return reconcile.Result{}, false, err
}
if err = UpdateDefaultImagesIfNeeded(ctx); err != nil {
logrus.Error(err)
return reconcile.Result{}, err
return reconcile.Result{}, false, err
}
if ctx.CheCluster.Spec.ImagePuller.Spec.DeploymentName == "" {
@ -172,44 +182,43 @@ func ReconcileImagePuller(ctx *DeployContext) (reconcile.Result, error) {
logrus.Infof("Updating KubernetesImagePuller %v", imagePuller.Name)
if err = ctx.ClusterAPI.Client.Update(context.TODO(), imagePuller, &client.UpdateOptions{}); err != nil {
logrus.Errorf("Error updating KubernetesImagePuller: %v", err)
return reconcile.Result{}, err
return reconcile.Result{}, false, err
}
return reconcile.Result{RequeueAfter: time.Second}, nil
return reconcile.Result{RequeueAfter: time.Second}, false, nil
}
} else {
logrus.Infof("Waiting 15 seconds for kubernetesimagepullers.che.eclipse.org API")
return reconcile.Result{RequeueAfter: 15 * time.Second}, nil
return reconcile.Result{RequeueAfter: 15 * time.Second}, false, nil
}
} else {
if foundOperatorsAPI && foundPackagesAPI {
return DeleteImagePullerOperatorAndFinalizer(ctx)
done, err := DeleteImagePullerOperatorAndFinalizer(ctx)
if !done {
return reconcile.Result{}, false, err
} else {
return reconcile.Result{}, true, nil
}
}
}
return reconcile.Result{}, nil
return reconcile.Result{}, true, nil
}
func DeleteImagePullerOperatorAndFinalizer(ctx *DeployContext) (reconcile.Result, error) {
requeue := false
func DeleteImagePullerOperatorAndFinalizer(ctx *DeployContext) (bool, error) {
if _, err := GetImagePullerOperator(ctx); err == nil {
requeue = true
if _, err := UninstallImagePullerOperator(ctx); err != nil {
logrus.Errorf("Error uninstalling Image Puller: %v", err)
return reconcile.Result{}, err
return false, err
}
}
if HasImagePullerFinalizer(ctx.CheCluster) {
requeue = true
if err := DeleteImagePullerFinalizer(ctx); err != nil {
logrus.Errorf("Error deleting finalizer: %v", err)
return reconcile.Result{}, err
return false, err
}
}
if requeue {
return reconcile.Result{RequeueAfter: time.Second}, nil
}
return reconcile.Result{}, nil
return true, nil
}
func HasImagePullerFinalizer(instance *orgv1.CheCluster) bool {

View File

@ -12,14 +12,416 @@
package deploy
import (
"context"
"io/ioutil"
"os"
"sort"
"testing"
"strings"
"unicode/utf8"
"reflect"
"time"
chev1alpha1 "github.com/che-incubator/kubernetes-image-puller-operator/api/v1alpha1"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"sigs.k8s.io/yaml"
"github.com/eclipse-che/che-operator/pkg/util"
"github.com/google/go-cmp/cmp"
orgv1 "github.com/eclipse-che/che-operator/api/v1"
routev1 "github.com/openshift/api/route/v1"
operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
packagesv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
fakeDiscovery "k8s.io/client-go/discovery/fake"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"testing"
)
var (
namespace = "eclipse-che"
csvName = "kubernetes-imagepuller-operator.v0.0.9"
defaultImagePullerImages string
valueTrue = true
)
func TestImagePullerConfiguration(t *testing.T) {
oldBrokerMetaDataImage := strings.Split(os.Getenv("RELATED_IMAGE_che_workspace_plugin_broker_metadata"), ":")[0] + ":old"
oldBrokerArtifactsImage := strings.Split(os.Getenv("RELATED_IMAGE_che_workspace_plugin_broker_artifacts"), ":")[0] + ":old"
type testCase struct {
name string
initCR *orgv1.CheCluster
initObjects []runtime.Object
expectedCR *orgv1.CheCluster
expectedOperatorGroup *operatorsv1.OperatorGroup
expectedSubscription *operatorsv1alpha1.Subscription
expectedImagePuller *chev1alpha1.KubernetesImagePuller
shouldDelete bool
}
testCases := []testCase{
{
name: "image puller enabled, no operatorgroup, should create an operatorgroup",
initCR: InitCheCRWithImagePullerEnabled(),
initObjects: []runtime.Object{
getPackageManifest(),
},
expectedOperatorGroup: getOperatorGroup(),
},
{
name: "image puller enabled, operatorgroup exists, should create a subscription",
initCR: InitCheCRWithImagePullerEnabled(),
initObjects: []runtime.Object{
getPackageManifest(),
getOperatorGroup(),
},
expectedSubscription: getSubscription(),
},
{
name: "image puller enabled, subscription created, should add finalizer",
initCR: InitCheCRWithImagePullerEnabled(),
expectedCR: ExpectedCheCRWithImagePullerFinalizer(),
initObjects: []runtime.Object{
getPackageManifest(),
getOperatorGroup(),
getSubscription(),
},
},
{
name: "image puller enabled with finalizer but default values are empty, subscription exists, should update the CR",
initCR: InitCheCRWithImagePullerFinalizer(),
expectedCR: &orgv1.CheCluster{
TypeMeta: metav1.TypeMeta{
Kind: "CheCluster",
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
ResourceVersion: "1",
Finalizers: []string{
"kubernetesimagepullers.finalizers.che.eclipse.org",
},
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
Spec: chev1alpha1.KubernetesImagePullerSpec{
DeploymentName: "kubernetes-image-puller",
ConfigMapName: "k8s-image-puller",
},
},
Server: orgv1.CheClusterSpecServer{
ServerExposureStrategy: "multi-host",
},
},
},
initObjects: []runtime.Object{
getPackageManifest(),
getOperatorGroup(),
getSubscription(),
},
},
{
name: "image puller enabled default values already set, subscription exists, should create a KubernetesImagePuller",
initCR: InitCheCRWithImagePullerEnabledAndDefaultValuesSet(),
initObjects: []runtime.Object{
getPackageManifest(),
getOperatorGroup(),
getSubscription(),
},
expectedImagePuller: InitImagePuller(ImagePullerOptions{SpecImages: defaultImagePullerImages, ObjectMetaResourceVersion: "1"}),
},
{
name: "image puller enabled, user images set, subscription exists, should create a KubernetesImagePuller with user images",
initCR: InitCheCRWithImagePullerEnabledAndImagesSet("image=image_url"),
initObjects: []runtime.Object{
getPackageManifest(),
getOperatorGroup(),
getSubscription(),
},
expectedImagePuller: InitImagePuller(ImagePullerOptions{SpecImages: "image=image_url", ObjectMetaResourceVersion: "1"}),
},
{
name: "image puller enabled, one default image set, subscription exists, should update KubernetesImagePuller default image",
initCR: InitCheCRWithImagePullerEnabledAndImagesSet("che-workspace-plugin-broker-metadata=" + oldBrokerMetaDataImage + ";"),
initObjects: []runtime.Object{
getPackageManifest(),
getOperatorGroup(),
getSubscription(),
InitImagePuller(ImagePullerOptions{SpecImages: "che-workspace-plugin-broker-metadata=" + oldBrokerMetaDataImage + ";", ObjectMetaResourceVersion: "1"}),
},
expectedImagePuller: InitImagePuller(ImagePullerOptions{SpecImages: "che-workspace-plugin-broker-metadata=" + os.Getenv("RELATED_IMAGE_che_workspace_plugin_broker_metadata") + ";", ObjectMetaResourceVersion: "2"}),
},
{
name: "image puller enabled, one default image set, subscription exists, should update KubernetesImagePuller default images while keeping user image",
initCR: InitCheCRWithImagePullerEnabledAndImagesSet("image=image_url;che-workspace-plugin-broker-metadata=" + oldBrokerMetaDataImage + ";"),
initObjects: []runtime.Object{
getPackageManifest(),
getOperatorGroup(),
getSubscription(),
InitImagePuller(ImagePullerOptions{SpecImages: "image=image_url;che-workspace-plugin-broker-metadata=" + oldBrokerMetaDataImage + ";", ObjectMetaResourceVersion: "1"}),
},
expectedImagePuller: InitImagePuller(ImagePullerOptions{SpecImages: "image=image_url;che-workspace-plugin-broker-metadata=" + os.Getenv("RELATED_IMAGE_che_workspace_plugin_broker_metadata") + ";", ObjectMetaResourceVersion: "2"}),
},
{
name: "image puller enabled, default images set, subscription exists, should update KubernetesImagePuller default images",
initCR: InitCheCRWithImagePullerEnabledAndImagesSet("che-workspace-plugin-broker-metadata=" + oldBrokerMetaDataImage + ";che-workspace-plugin-broker-artifacts=" + oldBrokerArtifactsImage + ";"),
initObjects: []runtime.Object{
getPackageManifest(),
getOperatorGroup(),
getSubscription(),
InitImagePuller(ImagePullerOptions{SpecImages: "che-workspace-plugin-broker-metadata=" + oldBrokerMetaDataImage + ";che-workspace-plugin-broker-artifacts=" + oldBrokerArtifactsImage + ";", ObjectMetaResourceVersion: "1"}),
},
expectedImagePuller: InitImagePuller(ImagePullerOptions{SpecImages: defaultImagePullerImages, ObjectMetaResourceVersion: "2"}),
},
{
name: "image puller enabled, latest default images set, subscription exists, should not update KubernetesImagePuller default images",
initCR: InitCheCRWithImagePullerEnabledAndImagesSet(defaultImagePullerImages),
initObjects: []runtime.Object{
getPackageManifest(),
getOperatorGroup(),
getSubscription(),
InitImagePuller(ImagePullerOptions{SpecImages: defaultImagePullerImages, ObjectMetaResourceVersion: "1"}),
},
expectedImagePuller: InitImagePuller(ImagePullerOptions{SpecImages: defaultImagePullerImages, ObjectMetaResourceVersion: "1"}),
},
{
name: "image puller enabled, default images not set, subscription exists, should not set KubernetesImagePuller default images",
initCR: InitCheCRWithImagePullerEnabledAndImagesSet("image=image_url;"),
initObjects: []runtime.Object{
getPackageManifest(),
getOperatorGroup(),
getSubscription(),
InitImagePuller(ImagePullerOptions{SpecImages: "image=image_url;", ObjectMetaResourceVersion: "1"}),
},
expectedImagePuller: InitImagePuller(ImagePullerOptions{SpecImages: "image=image_url;", ObjectMetaResourceVersion: "1"}),
},
{
name: "image puller enabled, KubernetesImagePuller created and spec in CheCluster is different, should update the KubernetesImagePuller",
initCR: InitCheCRWithImagePullerEnabledAndNewValuesSet(),
initObjects: []runtime.Object{
getPackageManifest(),
getOperatorGroup(),
getSubscription(),
getDefaultImagePuller(),
},
expectedImagePuller: &chev1alpha1.KubernetesImagePuller{
TypeMeta: metav1.TypeMeta{Kind: "KubernetesImagePuller", APIVersion: "che.eclipse.org/v1alpha1"},
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: "2",
Name: os.Getenv("CHE_FLAVOR") + "-image-puller",
Namespace: namespace,
Labels: map[string]string{
"app": "che",
"component": "kubernetes-image-puller",
"app.kubernetes.io/part-of": os.Getenv("CHE_FLAVOR"),
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "org.eclipse.che/v1",
Kind: "CheCluster",
BlockOwnerDeletion: &valueTrue,
Controller: &valueTrue,
Name: os.Getenv("CHE_FLAVOR"),
},
},
},
Spec: chev1alpha1.KubernetesImagePullerSpec{
ConfigMapName: "k8s-image-puller-trigger-update",
DeploymentName: "kubernetes-image-puller-trigger-update",
},
},
},
{
name: "image puller already created, imagePuller disabled, should delete everything",
initCR: InitCheCRWithImagePullerDisabled(),
initObjects: []runtime.Object{
getPackageManifest(),
getOperatorGroup(),
getSubscription(),
getClusterServiceVersion(),
getDefaultImagePuller(),
},
expectedCR: InitCheCRWithImagePullerDisabled(),
shouldDelete: true,
},
{
name: "image puller already created, finalizer deleted",
initCR: InitCheCRWithImagePullerFinalizerAndDeletionTimestamp(),
initObjects: []runtime.Object{
getPackageManifest(),
getOperatorGroup(),
getSubscription(),
getClusterServiceVersion(),
getDefaultImagePuller(),
},
shouldDelete: true,
expectedCR: nil,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
logf.SetLogger(zap.New(zap.WriteTo(os.Stdout), zap.UseDevMode(true)))
deployContext := GetTestDeployContext(testCase.initCR, []runtime.Object{})
orgv1.SchemeBuilder.AddToScheme(deployContext.ClusterAPI.Scheme)
packagesv1.AddToScheme(deployContext.ClusterAPI.Scheme)
operatorsv1alpha1.AddToScheme(deployContext.ClusterAPI.Scheme)
operatorsv1.AddToScheme(deployContext.ClusterAPI.Scheme)
chev1alpha1.AddToScheme(deployContext.ClusterAPI.Scheme)
routev1.AddToScheme(deployContext.ClusterAPI.Scheme)
for _, obj := range testCase.initObjects {
obj.(metav1.Object).SetResourceVersion("")
err := deployContext.ClusterAPI.NonCachedClient.Create(context.TODO(), obj.(client.Object))
if err != nil {
t.Fatalf(err.Error())
}
}
deployContext.ClusterAPI.DiscoveryClient.(*fakeDiscovery.FakeDiscovery).Fake.Resources = []*metav1.APIResourceList{
{
GroupVersion: "packages.operators.coreos.com/v1",
APIResources: []metav1.APIResource{
{
Kind: "PackageManifest",
},
},
},
{
GroupVersion: "operators.coreos.com/v1alpha1",
APIResources: []metav1.APIResource{
{Kind: "OperatorGroup"},
{Kind: "Subscription"},
{Kind: "ClusterServiceVersion"},
},
},
{
GroupVersion: "che.eclipse.org/v1alpha1",
APIResources: []metav1.APIResource{
{Kind: "KubernetesImagePuller"},
},
},
}
var err error
if testCase.shouldDelete {
_, err = DeleteImagePullerOperatorAndFinalizer(deployContext)
if err != nil {
t.Fatalf("Error reconciling: %v", err)
}
} else {
_, _, err = ReconcileImagePuller(deployContext)
if err != nil {
t.Fatalf("Error reconciling: %v", err)
}
}
if testCase.expectedOperatorGroup != nil {
gotOperatorGroup := &operatorsv1.OperatorGroup{}
err := deployContext.ClusterAPI.NonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: testCase.expectedOperatorGroup.Namespace, Name: testCase.expectedOperatorGroup.Name}, gotOperatorGroup)
if err != nil {
t.Errorf("Error getting OperatorGroup: %v", err)
}
if !reflect.DeepEqual(testCase.expectedOperatorGroup.Spec.TargetNamespaces, gotOperatorGroup.Spec.TargetNamespaces) {
t.Errorf("Error expected target namespace %v but got %v", testCase.expectedOperatorGroup.Spec.TargetNamespaces, gotOperatorGroup.Spec.TargetNamespaces)
}
}
if testCase.expectedSubscription != nil {
gotSubscription := &operatorsv1alpha1.Subscription{}
err := deployContext.ClusterAPI.NonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: testCase.expectedSubscription.Namespace, Name: testCase.expectedSubscription.Name}, gotSubscription)
if err != nil {
t.Errorf("Error getting Subscription: %v", err)
}
if !reflect.DeepEqual(testCase.expectedSubscription.Spec, gotSubscription.Spec) {
t.Errorf("Error, subscriptions differ (-want +got) %v", cmp.Diff(testCase.expectedSubscription.Spec, gotSubscription.Spec))
}
}
// if expectedCR is not set, don't check it
if testCase.expectedCR != nil && !reflect.DeepEqual(testCase.initCR, testCase.expectedCR) {
gotCR := &orgv1.CheCluster{}
err = deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: os.Getenv("CHE_FLAVOR")}, gotCR)
if err != nil {
t.Errorf("Error getting CheCluster: %v", err)
}
if !reflect.DeepEqual(testCase.expectedCR, gotCR) {
t.Errorf("Expected CR and CR returned from API server are different (-want +got): %v", cmp.Diff(testCase.expectedCR, gotCR))
}
}
if testCase.expectedImagePuller != nil {
gotImagePuller := &chev1alpha1.KubernetesImagePuller{}
err = deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Namespace: testCase.expectedImagePuller.Namespace, Name: testCase.expectedImagePuller.Name}, gotImagePuller)
if err != nil {
t.Errorf("Error getting KubernetesImagePuller: %v", err)
}
diff := cmp.Diff(testCase.expectedImagePuller, gotImagePuller, cmpopts.IgnoreFields(chev1alpha1.KubernetesImagePullerSpec{}, "Images"))
if diff != "" {
t.Errorf("Expected KubernetesImagePuller and KubernetesImagePuller returned from API server differ (-want, +got): %v", diff)
}
expectedImages := nonEmptySplit(testCase.expectedImagePuller.Spec.Images, ";")
if len(nonEmptySplit(testCase.expectedImagePuller.Spec.Images, ";")) != len(expectedImages) {
t.Errorf("Expected KubernetesImagePuller returns %d images", len(expectedImages))
}
for _, expectedImage := range expectedImages {
if !strings.Contains(gotImagePuller.Spec.Images, expectedImage) {
t.Errorf("Expected KubernetesImagePuller returned image: %s, but it did not", expectedImage)
}
}
}
if testCase.shouldDelete {
if testCase.expectedCR == nil {
gotCR := &orgv1.CheCluster{}
err = deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: os.Getenv("CHE_FLAVOR")}, gotCR)
if !errors.IsNotFound(err) {
t.Fatal("CR CheCluster should be removed")
}
}
imagePuller := &chev1alpha1.KubernetesImagePuller{}
err = deployContext.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: os.Getenv("CHE_FLAVOR") + "-image-puller"}, imagePuller)
if err == nil || !errors.IsNotFound(err) {
t.Fatalf("Should not have found KubernetesImagePuller: %v", err)
}
clusterServiceVersion := &operatorsv1alpha1.ClusterServiceVersion{}
err = deployContext.ClusterAPI.NonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: csvName}, clusterServiceVersion)
if err == nil || !errors.IsNotFound(err) {
t.Fatalf("Should not have found ClusterServiceVersion: %v", err)
}
subscription := &operatorsv1alpha1.Subscription{}
err = deployContext.ClusterAPI.NonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: "kubernetes-imagepuller-operator"}, subscription)
if err == nil || !errors.IsNotFound(err) {
t.Fatalf("Should not have found Subscription: %v", err)
}
operatorGroup := &operatorsv1.OperatorGroup{}
err = deployContext.ClusterAPI.NonCachedClient.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: "kubernetes-imagepuller-operator"}, operatorGroup)
if err == nil || !errors.IsNotFound(err) {
t.Fatalf("Should not have found OperatorGroup: %v", err)
}
}
})
}
}
func TestEnvVars(t *testing.T) {
type testcase struct {
name string
@ -139,3 +541,341 @@ func sortImages(images []ImageAndName) []ImageAndName {
})
return imagesCopy
}
func InitCheCRWithImagePullerEnabled() *orgv1.CheCluster {
return &orgv1.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
ResourceVersion: "0",
},
TypeMeta: metav1.TypeMeta{
APIVersion: "org.eclipse.che/v1",
Kind: "CheCluster",
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
},
Server: orgv1.CheClusterSpecServer{
ServerExposureStrategy: "multi-host",
},
},
}
}
func InitCheCRWithImagePullerFinalizer() *orgv1.CheCluster {
return &orgv1.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
Finalizers: []string{
"kubernetesimagepullers.finalizers.che.eclipse.org",
},
ResourceVersion: "0",
},
TypeMeta: metav1.TypeMeta{
APIVersion: "org.eclipse.che/v1",
Kind: "CheCluster",
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
},
Server: orgv1.CheClusterSpecServer{
ServerExposureStrategy: "multi-host",
},
},
}
}
func InitCheCRWithImagePullerFinalizerAndDeletionTimestamp() *orgv1.CheCluster {
return &orgv1.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
Finalizers: []string{
"kubernetesimagepullers.finalizers.che.eclipse.org",
},
DeletionTimestamp: &metav1.Time{Time: time.Unix(1, 0)},
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
},
Server: orgv1.CheClusterSpecServer{
ServerExposureStrategy: "multi-host",
},
},
}
}
func ExpectedCheCRWithImagePullerFinalizer() *orgv1.CheCluster {
return &orgv1.CheCluster{
TypeMeta: metav1.TypeMeta{
Kind: "CheCluster",
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
Finalizers: []string{
"kubernetesimagepullers.finalizers.che.eclipse.org",
},
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
},
Server: orgv1.CheClusterSpecServer{
ServerExposureStrategy: "multi-host",
},
},
}
}
func InitCheCRWithImagePullerDisabled() *orgv1.CheCluster {
return &orgv1.CheCluster{
TypeMeta: metav1.TypeMeta{
Kind: "CheCluster",
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
ResourceVersion: "0",
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: false,
},
},
}
}
func InitCheCRWithImagePullerEnabledAndDefaultValuesSet() *orgv1.CheCluster {
return &orgv1.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
Finalizers: []string{
"kubernetesimagepullers.finalizers.che.eclipse.org",
},
},
TypeMeta: metav1.TypeMeta{
APIVersion: "org.eclipse.che/v1",
Kind: "CheCluster",
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
Spec: chev1alpha1.KubernetesImagePullerSpec{
DeploymentName: "kubernetes-image-puller",
ConfigMapName: "k8s-image-puller",
},
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
},
},
}
}
func InitCheCRWithImagePullerEnabledAndImagesSet(images string) *orgv1.CheCluster {
return &orgv1.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
Finalizers: []string{
"kubernetesimagepullers.finalizers.che.eclipse.org",
},
},
TypeMeta: metav1.TypeMeta{
APIVersion: "org.eclipse.che/v1",
Kind: "CheCluster",
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
Spec: chev1alpha1.KubernetesImagePullerSpec{
DeploymentName: "kubernetes-image-puller",
ConfigMapName: "k8s-image-puller",
Images: images,
},
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
},
},
}
}
func InitCheCRWithImagePullerEnabledAndNewValuesSet() *orgv1.CheCluster {
return &orgv1.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR"),
Namespace: namespace,
Finalizers: []string{
"kubernetesimagepullers.finalizers.che.eclipse.org",
},
},
Spec: orgv1.CheClusterSpec{
ImagePuller: orgv1.CheClusterSpecImagePuller{
Enable: true,
Spec: chev1alpha1.KubernetesImagePullerSpec{
DeploymentName: "kubernetes-image-puller-trigger-update",
ConfigMapName: "k8s-image-puller-trigger-update",
},
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
},
},
}
}
type ImagePullerOptions struct {
SpecImages string
ObjectMetaResourceVersion string
}
func InitImagePuller(options ImagePullerOptions) *chev1alpha1.KubernetesImagePuller {
return &chev1alpha1.KubernetesImagePuller{
TypeMeta: metav1.TypeMeta{
APIVersion: "che.eclipse.org/v1alpha1",
Kind: "KubernetesImagePuller",
},
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR") + "-image-puller",
Namespace: namespace,
ResourceVersion: options.ObjectMetaResourceVersion,
Labels: map[string]string{
"app.kubernetes.io/part-of": os.Getenv("CHE_FLAVOR"),
"app": "che",
"component": "kubernetes-image-puller",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "org.eclipse.che/v1",
Kind: "CheCluster",
Controller: &valueTrue,
BlockOwnerDeletion: &valueTrue,
Name: os.Getenv("CHE_FLAVOR"),
},
},
},
Spec: chev1alpha1.KubernetesImagePullerSpec{
DeploymentName: "kubernetes-image-puller",
ConfigMapName: "k8s-image-puller",
Images: options.SpecImages,
},
}
}
func getDefaultImagePuller() *chev1alpha1.KubernetesImagePuller {
return &chev1alpha1.KubernetesImagePuller{
TypeMeta: metav1.TypeMeta{
APIVersion: "che.eclipse.org/v1alpha1",
Kind: "KubernetesImagePuller",
},
ObjectMeta: metav1.ObjectMeta{
Name: os.Getenv("CHE_FLAVOR") + "-image-puller",
Namespace: namespace,
Labels: map[string]string{
"app.kubernetes.io/part-of": os.Getenv("CHE_FLAVOR"),
"app": "che",
"component": "kubernetes-image-puller",
},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "org.eclipse.che/v1",
Kind: "CheCluster",
Controller: &valueTrue,
BlockOwnerDeletion: &valueTrue,
Name: os.Getenv("CHE_FLAVOR"),
},
},
},
Spec: chev1alpha1.KubernetesImagePullerSpec{
DeploymentName: "kubernetes-image-puller",
ConfigMapName: "k8s-image-puller",
Images: defaultImagePullerImages,
},
}
}
// Split string by separator without empty elems
func nonEmptySplit(lineToSplit string, separator string) []string {
splitFn := func(c rune) bool {
runeChar, _ := utf8.DecodeRuneInString(separator)
return c == runeChar
}
return strings.FieldsFunc(lineToSplit, splitFn)
}
func getPackageManifest() *packagesv1.PackageManifest {
return &packagesv1.PackageManifest{
ObjectMeta: metav1.ObjectMeta{
Name: "kubernetes-imagepuller-operator",
Namespace: namespace,
},
Status: packagesv1.PackageManifestStatus{
CatalogSource: "community-operators",
CatalogSourceNamespace: "olm",
DefaultChannel: "stable",
PackageName: "kubernetes-imagepuller-operator",
},
}
}
func getOperatorGroup() *operatorsv1.OperatorGroup {
return &operatorsv1.OperatorGroup{
ObjectMeta: metav1.ObjectMeta{
Name: "kubernetes-imagepuller-operator",
Namespace: namespace,
},
Spec: operatorsv1.OperatorGroupSpec{
TargetNamespaces: []string{
namespace,
},
},
}
}
func getSubscription() *operatorsv1alpha1.Subscription {
return &operatorsv1alpha1.Subscription{
ObjectMeta: metav1.ObjectMeta{
Name: "kubernetes-imagepuller-operator",
Namespace: namespace,
},
Spec: &operatorsv1alpha1.SubscriptionSpec{
CatalogSource: "community-operators",
Channel: "stable",
CatalogSourceNamespace: "olm",
InstallPlanApproval: operatorsv1alpha1.ApprovalAutomatic,
Package: "kubernetes-imagepuller-operator",
},
}
}
func getClusterServiceVersion() *operatorsv1alpha1.ClusterServiceVersion {
return &operatorsv1alpha1.ClusterServiceVersion{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: csvName,
},
}
}
func init() {
operator := &appsv1.Deployment{}
data, err := ioutil.ReadFile("../../config/manager/manager.yaml")
yaml.Unmarshal(data, operator)
if err == nil {
for _, env := range operator.Spec.Template.Spec.Containers[0].Env {
os.Setenv(env.Name, env.Value)
}
}
defaultImagePullerImages = "che-workspace-plugin-broker-metadata=" + os.Getenv("RELATED_IMAGE_che_workspace_plugin_broker_metadata") +
";che-workspace-plugin-broker-artifacts=" + os.Getenv("RELATED_IMAGE_che_workspace_plugin_broker_artifacts") + ";"
}

View File

@ -0,0 +1,78 @@
//
// 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 deploy
import (
"reflect"
"runtime"
"github.com/sirupsen/logrus"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
type Reconcilable interface {
// Reconcile object.
Reconcile(ctx *DeployContext) (result reconcile.Result, done bool, err error)
// Does finalization (removes cluster scope objects, etc)
Finalize(ctx *DeployContext) (done bool, err error)
}
type ReconcileManager struct {
reconcilers []Reconcilable
failedReconciler Reconcilable
}
func NewReconcileManager() *ReconcileManager {
return &ReconcileManager{
reconcilers: make([]Reconcilable, 0),
failedReconciler: nil,
}
}
func (manager *ReconcileManager) RegisterReconciler(reconciler Reconcilable) {
manager.reconcilers = append(manager.reconcilers, reconciler)
}
// 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 *DeployContext) (reconcile.Result, bool, error) {
for _, reconciler := range manager.reconcilers {
result, done, err := reconciler.Reconcile(ctx)
if err != nil {
manager.failedReconciler = reconciler
if err := SetStatusDetails(ctx, InstallOrUpdateFailed, err.Error(), ""); err != nil {
logrus.Errorf("Failed to update checluster status, cause: %v", err)
}
} else if manager.failedReconciler == reconciler {
manager.failedReconciler = nil
if err := SetStatusDetails(ctx, "", "", ""); err != nil {
logrus.Errorf("Failed to update checluster status, cause: %v", err)
}
}
if !done {
return result, done, err
}
}
return reconcile.Result{}, true, nil
}
func (manager *ReconcileManager) FinalizeAll(ctx *DeployContext) {
for _, reconciler := range manager.reconcilers {
_, err := reconciler.Finalize(ctx)
if err != nil {
reconcilerName := runtime.FuncForPC(reflect.ValueOf(reconciler).Pointer()).Name()
logrus.Errorf("Finalization failed for reconciler: `%s`, cause: %v", reconcilerName, err)
}
}
}

View File

@ -0,0 +1,69 @@
//
// 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 deploy
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
type TestReconcilable struct {
shouldFailReconcileOnce bool
alreadyFailed bool
}
func NewTestReconcilable(shouldFailReconcileOnce bool) *TestReconcilable {
return &TestReconcilable{shouldFailReconcileOnce, false}
}
func (tr *TestReconcilable) Reconcile(ctx *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")
} else {
return reconcile.Result{}, true, nil
}
}
func (tr *TestReconcilable) Finalize(ctx *DeployContext) (bool, error) {
return true, nil
}
func TestShouldUpdateAndCleanStatus(t *testing.T) {
deployContext := GetTestDeployContext(nil, []runtime.Object{})
tr := NewTestReconcilable(true)
rm := NewReconcileManager()
rm.RegisterReconciler(tr)
_, done, err := rm.ReconcileAll(deployContext)
assert.False(t, done)
assert.NotNil(t, err)
assert.NotEmpty(t, deployContext.CheCluster.Status.Reason)
assert.Equal(t, "Reconcile error", deployContext.CheCluster.Status.Message)
assert.Equal(t, tr, rm.failedReconciler)
_, done, err = rm.ReconcileAll(deployContext)
assert.True(t, done)
assert.Nil(t, err)
assert.Empty(t, deployContext.CheCluster.Status.Reason)
assert.Empty(t, deployContext.CheCluster.Status.Message)
assert.Nil(t, rm.failedReconciler)
}

View File

@ -31,6 +31,10 @@ func GetTestDeployContext(cheCluster *orgv1.CheCluster, initObjs []runtime.Objec
Name: "eclipse-che",
Namespace: "eclipse-che",
},
TypeMeta: metav1.TypeMeta{
APIVersion: "org.eclipse.che/v1",
Kind: "CheCluster",
},
}
}