Add finalizers. Make storageclass configurable. More tests (#12)
* Add finalizers. Make storageclass configurable. More tests * Fix logspull/13/head
parent
d88e2bc677
commit
9dc8a3c50b
|
|
@ -65,7 +65,12 @@ spec:
|
|||
# instruct Che server to launch a special pod to precreate a subpath in a PV
|
||||
preCreateSubPaths: true
|
||||
# image:tag for preCreateSubPaths jobs
|
||||
pvcJobsImage:
|
||||
pvcJobsImage: ''
|
||||
# keep blank unless you need to use a non default storage class for Postgres PVC
|
||||
postgresPVCStorageClassName: ''
|
||||
# keep blank unless you need to use a non default storage class for workspace PVC(s)
|
||||
workspacePVCStorageClassName: ''
|
||||
|
||||
auth:
|
||||
# instructs operator on whether or not to deploy Keycloak/RH SSO instance. When set to true provision connection details
|
||||
externalKeycloak:
|
||||
|
|
|
|||
|
|
@ -120,6 +120,10 @@ type CheClusterSpecStorage struct {
|
|||
PreCreateSubPaths bool `json:"preCreateSubPaths"`
|
||||
// PvcJobsImage is image:tag for preCreateSubPaths jobs
|
||||
PvcJobsImage string `json:"pvcJobsImage"`
|
||||
// PostgresPVCStorageClassName is storage class for a postgres pvc. Empty string by default, which means default storage class is used
|
||||
PostgresPVCStorageClassName string `json:"postgresPVCStorageClassName"`
|
||||
// WorkspacePVCStorageClassName is storage class for a workspaces pvc. Empty string by default, which means default storage class is used
|
||||
WorkspacePVCStorageClassName string `json:"workspacePVCStorageClassName"`
|
||||
}
|
||||
|
||||
type CheClusterSpecK8SOnly struct {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,3 @@
|
|||
//
|
||||
// Copyright (c) 2012-2019 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
|
||||
//
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
|
|||
}
|
||||
|
||||
var _ reconcile.Reconciler = &ReconcileChe{}
|
||||
var oAuthFinalizerName = "oauthclients.finalizers.che.eclipse.org"
|
||||
|
||||
// ReconcileChe reconciles a CheCluster object
|
||||
type ReconcileChe struct {
|
||||
|
|
@ -207,6 +208,15 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e
|
|||
if err != nil {
|
||||
logrus.Errorf("An error occurred when detecting current infra: %s", err)
|
||||
}
|
||||
|
||||
// delete oAuthClient before CR is deleted
|
||||
doInstallOpenShiftoAuthProvider := instance.Spec.Auth.OpenShiftOauth
|
||||
if doInstallOpenShiftoAuthProvider {
|
||||
if err := r.ReconcileFinalizer(instance); err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// create a secret with router tls cert
|
||||
if isOpenShift {
|
||||
secret := &corev1.Secret{}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
|
||||
oauth "github.com/openshift/api/oauth/v1"
|
||||
routev1 "github.com/openshift/api/route/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
|
@ -25,6 +26,7 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
|
||||
"time"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
|
@ -180,4 +182,49 @@ func TestCheController(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Fatalf("Deployment postgres shoud not exist")
|
||||
}
|
||||
|
||||
// check of storageClassName ends up in pvc spec
|
||||
fakeStorageClassName := "fake-storage-class-name"
|
||||
cheCR.Spec.Storage.PostgresPVCStorageClassName = fakeStorageClassName
|
||||
cheCR.Spec.Database.ExternalDB = false
|
||||
if err := r.client.Update(context.TODO(), cheCR); err != nil {
|
||||
t.Fatalf("Failed to update %s CR: %s", cheCR.Name, err)
|
||||
}
|
||||
pvc := &corev1.PersistentVolumeClaim{}
|
||||
if err = r.client.Get(context.TODO(), types.NamespacedName{Name: "postgres-data", Namespace: cheCR.Namespace}, pvc); err != nil {
|
||||
t.Fatalf("Failed to get PVC: %s", err)
|
||||
}
|
||||
if err = r.client.Delete(context.TODO(), pvc); err != nil {
|
||||
t.Fatalf("Failed to delete PVC %s: %s", pvc.Name, err)
|
||||
}
|
||||
res, err = r.Reconcile(req)
|
||||
if err != nil {
|
||||
t.Fatalf("reconcile: (%v)", err)
|
||||
}
|
||||
pvc = &corev1.PersistentVolumeClaim{}
|
||||
if err = r.client.Get(context.TODO(), types.NamespacedName{Name: "postgres-data", Namespace: cheCR.Namespace}, pvc); err != nil {
|
||||
t.Fatalf("Failed to get PVC: %s", err)
|
||||
}
|
||||
actualStorageClassName := pvc.Spec.StorageClassName
|
||||
if len(*actualStorageClassName) != len(fakeStorageClassName) {
|
||||
t.Fatalf("Expecting %s storageClassName, got %s", fakeStorageClassName, *actualStorageClassName )
|
||||
}
|
||||
|
||||
// check if oAuthClient is deleted after CR is deleted (finalizer logic)
|
||||
// since fake api does not set deletion timestamp, CR is updated in tests rather than deleted
|
||||
logrus.Info("Updating CR with deletion timestamp")
|
||||
deletionTimestamp := &metav1.Time{Time: time.Now()}
|
||||
cheCR.DeletionTimestamp = deletionTimestamp
|
||||
if err := r.client.Update(context.TODO(), cheCR); err != nil {
|
||||
t.Fatalf("Failed to update CR: %s", err)
|
||||
}
|
||||
if err := r.ReconcileFinalizer(cheCR); err != nil {
|
||||
t.Fatal("Failed to reconcile oAuthClient")
|
||||
}
|
||||
oauthClientName := cheCR.Spec.Auth.OauthClientName
|
||||
_, err = r.GetOAuthClient(oauthClientName)
|
||||
if err == nil {
|
||||
t.Fatalf("OauthClient %s has not been deleted", oauthClientName)
|
||||
}
|
||||
logrus.Infof("Disregard the error above. OauthClient %s has been deleted", oauthClientName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package che
|
||||
|
||||
import (
|
||||
"context"
|
||||
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
|
||||
"github.com/eclipse/che-operator/pkg/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (r *ReconcileChe) ReconcileFinalizer(instance *orgv1.CheCluster) (err error) {
|
||||
if instance.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||
if !util.ContainsString(instance.ObjectMeta.Finalizers, oAuthFinalizerName) {
|
||||
instance.ObjectMeta.Finalizers = append(instance.ObjectMeta.Finalizers, oAuthFinalizerName)
|
||||
if err := r.client.Update(context.Background(), instance); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if util.ContainsString(instance.ObjectMeta.Finalizers, oAuthFinalizerName) {
|
||||
oAuthClientName := instance.Spec.Auth.OauthClientName
|
||||
logrus.Infof("Custom resource %s is being deleted. Deleting oAuthClient %s first", instance.Name, oAuthClientName)
|
||||
oAuthClient, err := r.GetOAuthClient(oAuthClientName)
|
||||
if err != nil {
|
||||
logrus.Errorf("Failed to get %s oAuthClient: %s", oAuthClientName, err)
|
||||
return err
|
||||
}
|
||||
if err := r.client.Delete(context.TODO(), oAuthClient); err != nil {
|
||||
logrus.Errorf("Failed to delete %s oAuthClient: %s", oAuthClientName, err)
|
||||
return err
|
||||
}
|
||||
instance.ObjectMeta.Finalizers = util.DoRemoveString(instance.ObjectMeta.Finalizers, oAuthFinalizerName)
|
||||
logrus.Infof("Updating %s CR", instance.Name)
|
||||
|
||||
if err := r.client.Update(context.Background(), instance); err != nil {
|
||||
logrus.Errorf("Failed to update %s CR: %s", instance.Name, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -14,16 +14,17 @@ package che
|
|||
import (
|
||||
"context"
|
||||
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
|
||||
oauth "github.com/openshift/api/oauth/v1"
|
||||
routev1 "github.com/openshift/api/route/v1"
|
||||
"github.com/sirupsen/logrus"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
routev1 "github.com/openshift/api/route/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
)
|
||||
|
||||
func(r *ReconcileChe) GetEffectiveDeployment(instance *orgv1.CheCluster, name string) (deployment *appsv1.Deployment, err error) {
|
||||
func (r *ReconcileChe) GetEffectiveDeployment(instance *orgv1.CheCluster, name string) (deployment *appsv1.Deployment, err error) {
|
||||
deployment = &appsv1.Deployment{}
|
||||
err = r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.Namespace}, deployment)
|
||||
if err != nil {
|
||||
|
|
@ -33,8 +34,7 @@ func(r *ReconcileChe) GetEffectiveDeployment(instance *orgv1.CheCluster, name st
|
|||
return deployment, nil
|
||||
}
|
||||
|
||||
|
||||
func(r *ReconcileChe) GetEffectiveIngress(instance *orgv1.CheCluster, name string) (ingress *v1beta1.Ingress) {
|
||||
func (r *ReconcileChe) GetEffectiveIngress(instance *orgv1.CheCluster, name string) (ingress *v1beta1.Ingress) {
|
||||
ingress = &v1beta1.Ingress{}
|
||||
err := r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.Namespace}, ingress)
|
||||
if err != nil {
|
||||
|
|
@ -44,9 +44,7 @@ func(r *ReconcileChe) GetEffectiveIngress(instance *orgv1.CheCluster, name strin
|
|||
return ingress
|
||||
}
|
||||
|
||||
|
||||
|
||||
func(r *ReconcileChe) GetEffectiveRoute(instance *orgv1.CheCluster, name string) (route *routev1.Route) {
|
||||
func (r *ReconcileChe) GetEffectiveRoute(instance *orgv1.CheCluster, name string) (route *routev1.Route) {
|
||||
route = &routev1.Route{}
|
||||
err := r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.Namespace}, route)
|
||||
if err != nil {
|
||||
|
|
@ -75,4 +73,13 @@ func (r *ReconcileChe) GetCR(request reconcile.Request) (instance *orgv1.CheClus
|
|||
return nil, err
|
||||
}
|
||||
return instance, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReconcileChe) GetOAuthClient(oAuthClientName string) (oAuthClient *oauth.OAuthClient, err error) {
|
||||
oAuthClient = &oauth.OAuthClient{}
|
||||
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: oAuthClientName, Namespace: ""}, oAuthClient); err != nil {
|
||||
logrus.Errorf("Failed to Get oAuthClient %s: %s", oAuthClientName, err)
|
||||
return nil, err
|
||||
}
|
||||
return oAuthClient, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ type CheConfigMap struct {
|
|||
PvcStrategy string `json:"CHE_INFRA_KUBERNETES_PVC_STRATEGY"`
|
||||
PvcClaimSize string `json:"CHE_INFRA_KUBERNETES_PVC_QUANTITY"`
|
||||
PvcJobsImage string `json:"CHE_INFRA_KUBERNETES_PVC_JOBS_IMAGE"`
|
||||
WorkspacePvcStorageClassName string `json:"CHE_INFRA_KUBERNETES_PVC_STORAGE__CLASS__NAME"`
|
||||
PreCreateSubPaths string `json:"CHE_INFRA_KUBERNETES_PVC_PRECREATE__SUBPATHS"`
|
||||
TlsSupport string `json:"CHE_INFRA_OPENSHIFT_TLS__ENABLED"`
|
||||
K8STrustCerts string `json:"CHE_INFRA_KUBERNETES_TRUST__CERTS"`
|
||||
|
|
@ -62,7 +63,7 @@ type CheConfigMap struct {
|
|||
WebSocketEndpointMinor string `json:"CHE_WEBSOCKET_ENDPOINT__MINOR"`
|
||||
}
|
||||
|
||||
func GetCustomConfigMapData()(cheEnv map[string]string) {
|
||||
func GetCustomConfigMapData() (cheEnv map[string]string) {
|
||||
|
||||
cheEnv = map[string]string{
|
||||
"CHE_PREDEFINED_STACKS_RELOAD__ON__START": "true",
|
||||
|
|
@ -122,6 +123,7 @@ func GetConfigMapData(cr *orgv1.CheCluster) (cheEnv map[string]string) {
|
|||
tlsSecretName := cr.Spec.K8SOnly.TlsSecretName
|
||||
pvcStrategy := util.GetValue(cr.Spec.Storage.PvcStrategy, DefaultPvcStrategy)
|
||||
pvcClaimSize := util.GetValue(cr.Spec.Storage.PvcClaimSize, DefaultPvcClaimSize)
|
||||
workspacePvcStorageClassName := cr.Spec.Storage.WorkspacePVCStorageClassName
|
||||
pvcJobsImage := util.GetValue(cr.Spec.Storage.PvcJobsImage, DefaultPvcJobsImage)
|
||||
preCreateSubPaths := "true"
|
||||
if !cr.Spec.Storage.PreCreateSubPaths {
|
||||
|
|
@ -152,6 +154,7 @@ func GetConfigMapData(cr *orgv1.CheCluster) (cheEnv map[string]string) {
|
|||
WorkspacesNamespace: workspacesNamespace,
|
||||
PvcStrategy: pvcStrategy,
|
||||
PvcClaimSize: pvcClaimSize,
|
||||
WorkspacePvcStorageClassName: workspacePvcStorageClassName,
|
||||
PvcJobsImage: pvcJobsImage,
|
||||
PreCreateSubPaths: preCreateSubPaths,
|
||||
TlsSupport: tls,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,26 @@ import (
|
|||
)
|
||||
|
||||
func NewPvc(cr *orgv1.CheCluster, name string, pvcClaimSize string, labels map[string]string) *corev1.PersistentVolumeClaim {
|
||||
//value := true
|
||||
|
||||
accessModes := []corev1.PersistentVolumeAccessMode{
|
||||
// todo Make configurable
|
||||
corev1.ReadWriteOnce,
|
||||
}
|
||||
resources := corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceName(corev1.ResourceStorage): resource.MustParse(pvcClaimSize),
|
||||
}}
|
||||
pvcSpec := corev1.PersistentVolumeClaimSpec{
|
||||
AccessModes: accessModes,
|
||||
Resources: resources,
|
||||
}
|
||||
if len(cr.Spec.Storage.PostgresPVCStorageClassName) > 1 {
|
||||
pvcSpec = corev1.PersistentVolumeClaimSpec{
|
||||
AccessModes: accessModes,
|
||||
StorageClassName: &cr.Spec.Storage.PostgresPVCStorageClassName,
|
||||
Resources: resources,
|
||||
}
|
||||
}
|
||||
return &corev1.PersistentVolumeClaim{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PersistentVolumeClaim",
|
||||
|
|
@ -30,18 +49,7 @@ func NewPvc(cr *orgv1.CheCluster, name string, pvcClaimSize string, labels map[s
|
|||
Namespace: cr.Namespace,
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: corev1.PersistentVolumeClaimSpec{
|
||||
AccessModes: []corev1.PersistentVolumeAccessMode{
|
||||
// todo Make configurable
|
||||
corev1.ReadWriteOnce,
|
||||
},
|
||||
Resources: corev1.ResourceRequirements{
|
||||
Requests: corev1.ResourceList{
|
||||
corev1.ResourceName(corev1.ResourceStorage): resource.MustParse(pvcClaimSize),
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: pvcSpec,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,26 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
|
||||
func ContainsString(slice []string, s string) bool {
|
||||
for _, item := range slice {
|
||||
if item == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func DoRemoveString(slice []string, s string) (result []string) {
|
||||
for _, item := range slice {
|
||||
if item == s {
|
||||
continue
|
||||
}
|
||||
result = append(result, item)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GeneratePasswd(stringLength int) (passwd string) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||
|
|
|
|||
Loading…
Reference in New Issue