diff --git a/README.md b/README.md index 020e68115..1238738ec 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,9 @@ When enabling TLS, make sure you create a secret with crt and key, and let the O ## How to Configure -The operator watches all objects it creates and reconciles them with CR state. It means that if you edit, say, a configMap che, the operator will revert changes. -Since not all Che configuration properties are custom resource spec fields, the operator creates a second configMap called custom. You can use this configmap -for any configuration that is not supported by CR. +The operator watches all objects it creates and reconciles them with CR state. It means that if you edit a configMap **che**, the operator will revert changes. +Since not all Che configuration properties are custom resource spec fields (there are too many of them), the operator creates a second configMap called **custom** +which you can use for any environment variables not supported by CR. The operator will not reconcile configMap custom. ## How to Build Operator Image diff --git a/build_deploy_local.sh b/build_deploy_local.sh index ffa97de8a..86a81dcdb 100755 --- a/build_deploy_local.sh +++ b/build_deploy_local.sh @@ -10,7 +10,7 @@ # Contributors: # Red Hat, Inc. - initial API and implementation -set -e +#set -e BASE_DIR=$(cd "$(dirname "$0")"; pwd) @@ -22,7 +22,9 @@ kubectl apply -f ${BASE_DIR}/deploy/crds/org_v1_che_crd.yaml -n=$1 # sometimes the operator cannot get CRD right away sleep 2 # uncomment when on OpenShift if you need to use self signed certs and login with OpenShift in Che -#oc adm policy add-cluster-role-to-user cluster-admin -z che-operator -n=$1 -kubectl apply -f ${BASE_DIR}/operator-local.yaml -n=$1 +#oc new-app -f ${BASE_DIR}/deploy/role_binding_oauth.yaml -p NAMESPACE=$1 -n=$1 +#oc apply -f ${BASE_DIR}/deploy/cluster_role.yaml -n=$1 +#oc new-app -f ${BASE_DIR}/deploy/role_binding_crt.yaml -p NAMESPACE=$1 +#oc apply -f ${BASE_DIR}/deploy/role_get_tls.yaml +kubectl apply -f ${BASE_DIR}/deploy/operator-local.yaml -n=$1 kubectl apply -f ${BASE_DIR}/deploy/crds/org_v1_che_cr.yaml -n=$1 - diff --git a/deploy.sh b/deploy.sh index 5c31eaa6f..558dbd715 100755 --- a/deploy.sh +++ b/deploy.sh @@ -19,7 +19,12 @@ oc apply -f ${BASE_DIR}/role_binding.yaml oc apply -f ${BASE_DIR}/crds/org_v1_che_crd.yaml # sometimes the operator cannot get CRD right away sleep 2 + # uncomment if you need Login with OpenShift and/or use self signed certificates and tls -#oc adm policy add-cluster-role-to-user cluster-admin -z che-operator -oc apply -f ${BASE_DIR}/operator.yaml -oc apply -f ${BASE_DIR}/crds/org_v1_che_cr.yaml \ No newline at end of file +#oc new-app -f ${BASE_DIR}deploy/role_binding_oauth.yaml -p NAMESPACE=$1 -n=$1 +#oc apply -f ${BASE_DIR}deploy/cluster_role.yaml -n=$1 +#oc new-app -f ${BASE_DIR}deploy/role_binding_crt.yaml -p NAMESPACE=$1 +#oc apply -f ${BASE_DIR}deploy/role_get_tls.yaml + +oc apply -f ${BASE_DIR}/deploy/operator.yaml +oc apply -f ${BASE_DIR}/deploy/crds/org_v1_che_cr.yaml \ No newline at end of file diff --git a/deploy/cluster_role.yaml b/deploy/cluster_role.yaml new file mode 100644 index 000000000..3fb2de35c --- /dev/null +++ b/deploy/cluster_role.yaml @@ -0,0 +1,21 @@ +# +# 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 +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: che-operator +rules: + - apiGroups: + - oauth.openshift.io + resources: + - "*" + verbs: + - "*" \ No newline at end of file diff --git a/deploy/crds/org_v1_che_cr.yaml b/deploy/crds/org_v1_che_cr.yaml index 17b6b0e74..629d9a5b5 100644 --- a/deploy/crds/org_v1_che_cr.yaml +++ b/deploy/crds/org_v1_che_cr.yaml @@ -45,7 +45,7 @@ spec: # otherwise a Postgres deployment is created externalDb: # Postgres Database hostname that Che server uses to connect to. Defaults to postgres - chePostgresHostname: '' + chePostgresHostName: '' # Postgres Database port that Che server uses to connect to. Defaults to 5432 chePostgresPort: '' # Postgres user that Che server when making a db connection. Defaults to pgche diff --git a/deploy/operator.yaml b/deploy/operator.yaml index 8e917f52b..5ffa95e12 100644 --- a/deploy/operator.yaml +++ b/deploy/operator.yaml @@ -25,7 +25,7 @@ spec: serviceAccountName: che-operator containers: - name: che-operator - image: eivantsov/operator-container + image: eivantsov/operator ports: - containerPort: 60000 name: metrics diff --git a/deploy/role.yaml b/deploy/role.yaml index edc6b8095..315fd542b 100644 --- a/deploy/role.yaml +++ b/deploy/role.yaml @@ -8,12 +8,12 @@ # # Contributors: # Red Hat, Inc. - initial API and implementation +--- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: creationTimestamp: null name: che-operator - rules: - apiGroups: - extensions/v1beta1 @@ -32,6 +32,11 @@ rules: resources: - roles - rolebindings + verbs: + - "*" + - apiGroups: + - rbac.authorization.k8s.io + resources: - clusterroles - clusterrolebindings verbs: @@ -75,4 +80,4 @@ rules: resources: - '*' verbs: - - '*' \ No newline at end of file + - '*' diff --git a/deploy/role_binding_crt.yaml b/deploy/role_binding_crt.yaml new file mode 100644 index 000000000..4848cf232 --- /dev/null +++ b/deploy/role_binding_crt.yaml @@ -0,0 +1,33 @@ +# +# 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 +kind: Template +apiVersion: v1 +metadata: + name: che-operator +objects: +- kind: RoleBinding + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: che-operator + namespace: default + subjects: + - kind: ServiceAccount + name: che-operator + namespace: ${NAMESPACE} + roleRef: + kind: Role + name: secret-reader + apiGroup: rbac.authorization.k8s.io +parameters: + - name: NAMESPACE + displayName: Namespace + description: Namespace + required: true \ No newline at end of file diff --git a/deploy/role_binding_oauth.yaml b/deploy/role_binding_oauth.yaml new file mode 100644 index 000000000..9bf04c487 --- /dev/null +++ b/deploy/role_binding_oauth.yaml @@ -0,0 +1,32 @@ +# +# 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 +kind: Template +apiVersion: v1 +metadata: + name: che-operator +objects: +- kind: ClusterRoleBinding + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: che-operator + subjects: + - kind: ServiceAccount + name: che-operator + namespace: ${NAMESPACE} + roleRef: + kind: ClusterRole + name: che-operator + apiGroup: rbac.authorization.k8s.io +parameters: + - name: NAMESPACE + displayName: Namespace + description: Namespace + required: true \ No newline at end of file diff --git a/deploy/role_get_tls.yaml b/deploy/role_get_tls.yaml new file mode 100644 index 000000000..2f4840af8 --- /dev/null +++ b/deploy/role_get_tls.yaml @@ -0,0 +1,30 @@ +# +# 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 +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + namespace: default + name: secret-reader +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get"] +#--- +#kind: Role +#apiVersion: rbac.authorization.k8s.io/v1 +#metadata: +# namespace: openshift-ingress +# name: secret-reader-4-0 +#rules: +# - apiGroups: [""] +# resources: ["secrets"] +# verbs: ["get"] \ No newline at end of file diff --git a/pkg/controller/che/che_controller.go b/pkg/controller/che/che_controller.go index d0412d7dd..c5a68d8be 100644 --- a/pkg/controller/che/che_controller.go +++ b/pkg/controller/che/che_controller.go @@ -41,7 +41,7 @@ import ( var log = logf.Log.WithName("controller_che") var ( - k8sclient = GetK8Client() + k8sclient = GetK8Client() ) // Add creates a new CheCluster Controller and adds it to the Manager. The Manager will set fields on the Controller @@ -474,7 +474,8 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e } } } - // create Che ConfigMap + // create Che ConfigMap which is synced with CR and is not supposed to be manually edited + // controller will reconcile this CM with CR spec cheHost := instance.Spec.Server.CheHost cheEnv := deploy.GetConfigMapData(instance) cheConfigMap := deploy.NewCheConfigMap(instance, cheEnv) @@ -482,10 +483,10 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e return reconcile.Result{}, err } - // create a custom configmap that won't be synced with CR spec - // to be able to override envs and not clutter CR spec with fields + // create a custom ConfigMap that won't be synced with CR spec + // to be able to override envs and not clutter CR spec with fields which are too numerous customCM := &corev1.ConfigMap{ - Data: map[string]string{"SAMPLE_KEY": "SAMPLE_VALUE"}, + Data: deploy.GetCustomConfigMapData(), TypeMeta: metav1.TypeMeta{ Kind: "ConfigMap"}, ObjectMeta: metav1.ObjectMeta{ @@ -495,7 +496,8 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e if err := r.CreateNewConfigMap(instance, customCM); err != nil { return reconcile.Result{}, err } - + // configMap resource version will be an env in Che deployment to easily update it when a ConfigMap changes + // which will automatically trigger Che rolling update cmResourceVersion := cheConfigMap.ResourceVersion // create Che deployment cheImageRepo := util.GetValue(instance.Spec.Server.CheImage, deploy.DefaultCheServerImageRepo) @@ -515,7 +517,6 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e logrus.Errorf("Failed to get %s deployment: %s", cheDeployment.Name, err) return reconcile.Result{}, err } - if !tests { if deployment.Status.AvailableReplicas != 1 { instance, _ := r.GetCR(request) diff --git a/pkg/controller/che/che_controller_test.go b/pkg/controller/che/che_controller_test.go index 3140bdcf7..ab7e98c13 100644 --- a/pkg/controller/che/che_controller_test.go +++ b/pkg/controller/che/che_controller_test.go @@ -14,7 +14,9 @@ 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" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -23,7 +25,6 @@ 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" - oauth "github.com/openshift/api/oauth/v1" "testing" ) @@ -134,7 +135,6 @@ func TestCheController(t *testing.T) { if err := cl.Update(context.TODO(), cheCR); err != nil { t.Error("Failed to update CheCluster custom resource") } - // reconcile again res, err = r.Reconcile(req) if err != nil { t.Fatalf("reconcile: (%v)", err) @@ -154,7 +154,7 @@ func TestCheController(t *testing.T) { } err = r.client.Get(context.TODO(), types.NamespacedName{Name: cheCR.Name, Namespace: cheCR.Namespace}, cheCR) - err = r.CreateIdentityProviderItems(cheCR,req, "che", "keycloak") + err = r.CreateIdentityProviderItems(cheCR, req, "che", "keycloak") oAuthClientName := cheCR.Spec.Auth.OauthClientName oauthSecret := cheCR.Spec.Auth.OauthSecret if err = r.client.Get(context.TODO(), types.NamespacedName{Name: oAuthClientName, Namespace: ""}, oAuthClient); err != nil { @@ -164,4 +164,20 @@ func TestCheController(t *testing.T) { t.Errorf("Secrets do not match. Expecting %s, got %s", oauthSecret, oAuthClient.Secret) } + // check if a new Postgres deployment is not created when spec.Database.ExternalDB is true + cheCR.Spec.Database.ExternalDB = true + if err := cl.Update(context.TODO(), cheCR); err != nil { + t.Error("Failed to update CheCluster custom resource") + } + postgresDeployment := &appsv1.Deployment{} + err = r.client.Get(context.TODO(), types.NamespacedName{Name: "postgres", Namespace: cheCR.Namespace}, postgresDeployment) + err = r.client.Delete(context.TODO(), postgresDeployment) + res, err = r.Reconcile(req) + if err != nil { + t.Fatalf("reconcile: (%v)", err) + } + err = r.client.Get(context.TODO(), types.NamespacedName{Name: "postgres", Namespace: cheCR.Namespace}, postgresDeployment) + if err == nil { + t.Fatalf("Deployment postgres shoud not exist") + } } diff --git a/pkg/controller/che/create.go b/pkg/controller/che/create.go index 42987322c..a222fe267 100644 --- a/pkg/controller/che/create.go +++ b/pkg/controller/che/create.go @@ -185,10 +185,6 @@ func (r *ReconcileChe) CreateNewSecret(instance *orgv1.CheCluster, secret *corev } func (r *ReconcileChe) CreateNewOauthClient(instance *orgv1.CheCluster, oAuthClient *oauth.OAuthClient) error { - if err := controllerutil.SetControllerReference(instance, oAuthClient, r.scheme); err != nil { - logrus.Errorf("An error occurred: %s", err) - return err - } oAuthClientFound := &oauth.OAuthClient{} err := r.client.Get(context.TODO(), types.NamespacedName{Name: oAuthClient.Name, Namespace: oAuthClient.Namespace}, oAuthClientFound) if err != nil && errors.IsNotFound(err) { diff --git a/pkg/controller/che/k8s_helpers.go b/pkg/controller/che/k8s_helpers.go index 673af9277..6f05703c5 100644 --- a/pkg/controller/che/k8s_helpers.go +++ b/pkg/controller/che/k8s_helpers.go @@ -50,6 +50,7 @@ func GetK8Client() *k8s { // GetPostgresStatus waits for pvc.status.phase to be Bound func (cl *k8s) GetPostgresStatus(pvc *corev1.PersistentVolumeClaim, ns string) { + // short timeout if a PVC is waiting for a first consumer to be bound var timeout int64 = 10 listOptions := metav1.ListOptions{ FieldSelector: fields.OneTermEqualSelector("metadata.name", pvc.Name).String(), diff --git a/pkg/deploy/che_configmap.go b/pkg/deploy/che_configmap.go index 5198b6392..19386c7ea 100644 --- a/pkg/deploy/che_configmap.go +++ b/pkg/deploy/che_configmap.go @@ -51,11 +51,6 @@ type CheConfigMap struct { KeycloakRealm string `json:"CHE_KEYCLOAK_REALM"` KeycloakClientId string `json:"CHE_KEYCLOAK_CLIENT__ID"` OpenShiftIdentityProvider string `json:"CHE_INFRA_OPENSHIFT_OAUTH__IDENTITY__PROVIDER"` - ReloadStacksOnStart string `json:"CHE_PREDEFINED_STACKS_RELOAD__ON__START"` - WorkspaceServiceAccountName string `json:"CHE_INFRA_KUBERNETES_SERVICE__ACCOUNT__NAME"` - WorkspaceAutoStart string `json:"CHE_WORKSPACE_AUTO_START"` - UnrecoverableEvents string `json:"CHE_INFRA_KUBERNETES_WORKSPACE__UNRECOVERABLE__EVENTS"` - InactiveWorkspaceStopTimeout string `json:"CHE_WORKSPACE_AGENT_DEV_INACTIVE__STOP__TIMEOUT__MS"` JavaOpts string `json:"JAVA_OPTS"` WorkspaceJavaOpts string `json:"CHE_WORKSPACE_JAVA__OPTIONS"` WorkspaceMavenOpts string `json:"CHE_WORKSPACE_MAVEN__OPTIONS"` @@ -67,6 +62,19 @@ type CheConfigMap struct { WebSocketEndpointMinor string `json:"CHE_WEBSOCKET_ENDPOINT__MINOR"` } +func GetCustomConfigMapData()(cheEnv map[string]string) { + + cheEnv = map[string]string{ + "CHE_PREDEFINED_STACKS_RELOAD__ON__START": "true", + "CHE_INFRA_KUBERNETES_SERVICE__ACCOUNT__NAME": "che-workspace", + "CHE_WORKSPACE_AUTO_START": "true", + "CHE_INFRA_KUBERNETES_WORKSPACE__UNRECOVERABLE__EVENTS": "FailedMount,FailedScheduling,MountVolume.SetUp failed,Failed to pull image", + "CHE_WORKSPACE_AGENT_DEV_INACTIVE__STOP__TIMEOUT__MS": "-1", + } + return cheEnv + +} + // GetConfigMapData gets env values from CR spec and returns a map with key:value // which is used in CheCluster ConfigMap to configure CheCluster master behavior func GetConfigMapData(cr *orgv1.CheCluster) (cheEnv map[string]string) { @@ -156,24 +164,14 @@ func GetConfigMapData(cr *orgv1.CheCluster) (cheEnv map[string]string) { KeycloakRealm: keycloakRealm, KeycloakClientId: keycloakClientId, OpenShiftIdentityProvider: openShiftIdentityProviderId, - ReloadStacksOnStart: "true", - WorkspaceServiceAccountName: "che-workspace", - WorkspaceAutoStart: "true", - UnrecoverableEvents: "FailedMount,FailedScheduling,MountVolume.SetUp failed,Failed to pull image", - InactiveWorkspaceStopTimeout: "-1", - JavaOpts: "-XX:MaxRAMFraction=2 -XX:+UseParallelGC -XX:MinHeapFreeRatio=10 " + - "-XX:MaxHeapFreeRatio=20 -XX:GCTimeRatio=4 " + - "-XX:AdaptiveSizePolicyWeight=90 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap " + - "-Dsun.zip.disableMemoryMapping=true -Xms20m " + proxyJavaOpts, - WorkspaceJavaOpts: "-XX:MaxRAM=150m -XX:MaxRAMFraction=2 -XX:+UseParallelGC " + - "-XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=20 -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 " + - "-Dsun.zip.disableMemoryMapping=true " + - "-Xms20m -Djava.security.egd=file:/dev/./urandom " + proxyJavaOpts, - WorkspaceProxyJavaOpts: proxyJavaOpts, - WorkspaceHttpProxy: cheWorkspaceHttpProxy, - WorkspaceHttpsProxy: cheWorkspaceHttpProxy, - WorkspaceNoProxy: cheWorkspaceNoProxy, - PluginRegistryUrl: pluginRegistryUrl, + JavaOpts: DefaultJavaOpts + " " + proxyJavaOpts, + WorkspaceJavaOpts: DefaultWorkspaceJavaOpts + " " + proxyJavaOpts, + WorkspaceMavenOpts: DefaultWorkspaceJavaOpts + " " + proxyJavaOpts, + WorkspaceProxyJavaOpts: proxyJavaOpts, + WorkspaceHttpProxy: cheWorkspaceHttpProxy, + WorkspaceHttpsProxy: cheWorkspaceHttpProxy, + WorkspaceNoProxy: cheWorkspaceNoProxy, + PluginRegistryUrl: pluginRegistryUrl, } out, err := json.Marshal(data) diff --git a/pkg/deploy/defaults.go b/pkg/deploy/defaults.go index 873248414..e8c72243a 100644 --- a/pkg/deploy/defaults.go +++ b/pkg/deploy/defaults.go @@ -14,7 +14,7 @@ package deploy const ( DefaultCheServerImageRepo = "eclipse/che-server" DefaultCodeReadyServerImageRepo = "registry.access.redhat.com/codeready-workspaces/server" - DefaultCheServerImageTag = "6.19.0" + DefaultCheServerImageTag = "7.0.0-beta-2.0" DefaultCodeReadyServerImageTag = "1.1" DefaultCheFlavor = "che" DefaultChePostgresUser = "pgche" @@ -34,4 +34,12 @@ const ( DefaultPostgresUpstreamImage = "centos/postgresql-96-centos7:9.6" DefaultKeycloakImage = "registry.access.redhat.com/redhat-sso-7/sso72-openshift:1.2-8" DefaultKeycloakUpstreamImage = "eclipse/che-keycloak:6.19.0" + DefaultJavaOpts = "-XX:MaxRAMFraction=2 -XX:+UseParallelGC -XX:MinHeapFreeRatio=10 " + + "-XX:MaxHeapFreeRatio=20 -XX:GCTimeRatio=4 " + + "-XX:AdaptiveSizePolicyWeight=90 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap " + + "-Dsun.zip.disableMemoryMapping=true -Xms20m" + DefaultWorkspaceJavaOpts = "-XX:MaxRAM=150m -XX:MaxRAMFraction=2 -XX:+UseParallelGC " + + "-XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=20 -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 " + + "-Dsun.zip.disableMemoryMapping=true " + + "-Xms20m -Djava.security.egd=file:/dev/./urandom" )