Update to 0.5.0. Use CRDs

pull/6/head
Eugene Ivantsov 2019-03-19 15:59:37 +02:00
parent bc59de083a
commit 046cb9ab92
55 changed files with 5413 additions and 121 deletions

2
.gitignore vendored
View File

@ -107,5 +107,7 @@ tags
!.vscode/extensions.json
.history
build/
bin/
# End of https://www.gitignore.io/api/go,vim,emacs,visualstudiocode

View File

@ -20,12 +20,12 @@ USER root
#RUN subscription-manager register --username me --password mypwd --auto-attach
#RUN subscription-manager repos --enable rhel-7-server-optional-rpms --enable rhel-server-rhscl-7-rpms
ADD . /go/src/github.com/eclipse/che-operator
RUN cd /go/src/github.com/eclipse/che-operator && go test -v ./... && \
RUN cd /go/src/github.com/eclipse/che-operator && export MOCK_API=true && go test -v ./... && \
OOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o /tmp/che-operator/che-operator \
/go/src/github.com/eclipse/che-operator/cmd/che-operator/main.go && cd ..
/go/src/github.com/eclipse/che-operator/cmd/manager/main.go && cd ..
# https://access.redhat.com/containers/?tab=tags#/registry.access.redhat.com/rhel7
FROM registry.access.redhat.com/rhel7:7.6-151.1550575774
FROM registry.access.redhat.com/rhel7:7.6-202
ENV SUMMARY="Red Hat CodeReady Workspaces Operator container" \
DESCRIPTION="Red Hat CodeReady Workspaces Operator container" \
@ -41,7 +41,7 @@ LABEL summary="$SUMMARY" \
io.openshift.tags="$PRODNAME,$COMPNAME" \
com.redhat.component="$PRODNAME-$COMPNAME" \
name="$PRODNAME/$COMPNAME" \
version="1.0" \
version="1.1" \
license="EPLv2" \
maintainer="Nick Boldt <nboldt@redhat.com>" \
io.openshift.expose-services="" \
@ -50,4 +50,4 @@ LABEL summary="$SUMMARY" \
COPY --from=builder /tmp/che-operator/che-operator /usr/local/bin/che-operator
COPY --from=builder /go/src/github.com/eclipse/che-operator/deploy/keycloak_provision /tmp/keycloak_provision
RUN yum list installed && echo "End Of Installed Packages"
CMD ["che-operator"]
CMD ["che-operator"]

186
Gopkg.toml Normal file
View File

@ -0,0 +1,186 @@
#
# Copyright (c) 2012-2018 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
# Force dep to vendor the code generators, which aren't imported just used at dev time.
required = [
"k8s.io/code-generator/cmd/defaulter-gen",
"k8s.io/code-generator/cmd/deepcopy-gen",
"k8s.io/code-generator/cmd/conversion-gen",
"k8s.io/code-generator/cmd/client-gen",
"k8s.io/code-generator/cmd/lister-gen",
"k8s.io/code-generator/cmd/informer-gen",
"k8s.io/code-generator/cmd/openapi-gen",
"k8s.io/gengo/args",
]
[[override]]
name = "k8s.io/code-generator"
# revision for tag "kubernetes-1.12.3"
revision = "3dcf91f64f638563e5106f21f50c31fa361c918d"
[[override]]
name = "k8s.io/api"
# revision for tag "kubernetes-1.12.3"
revision = "b503174bad5991eb66f18247f52e41c3258f6348"
[[override]]
name = "k8s.io/apiextensions-apiserver"
# revision for tag "kubernetes-1.12.3"
revision = "0cd23ebeb6882bd1cdc2cb15fc7b2d72e8a86a5b"
[[override]]
name = "k8s.io/apimachinery"
# revision for tag "kubernetes-1.12.3"
revision = "eddba98df674a16931d2d4ba75edc3a389bf633a"
[[override]]
name = "k8s.io/client-go"
# revision for tag "kubernetes-1.12.3"
revision = "d082d5923d3cc0bfbb066ee5fbdea3d0ca79acf8"
[[override]]
name = "sigs.k8s.io/controller-runtime"
version = "=v0.1.8"
[[constraint]]
name = "github.com/operator-framework/operator-sdk"
version = "=v0.5.0" #osdk_version_annotation
[[override]]
name = "github.com/PuerkitoBio/urlesc"
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
[[override]]
name = "github.com/alecthomas/template"
revision = "a0175ee3bccc567396460bf5acd36800cb10c49c"
[[override]]
name = "github.com/alecthomas/units"
revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
[[override]]
name = "github.com/beorn7/perks"
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[override]]
name = "github.com/docker/spdystream"
revision = "449fdfce4d962303d702fec724ef0ad181c92528"
[[override]]
name = "github.com/go-logr/logr"
revision = "9fb12b3b21c5415d16ac18dc5cd42c1cfdd40c4e"
[[override]]
name = "github.com/go-openapi/jsonpointer"
version = "v0.16.0"
[[override]]
name = "github.com/go-openapi/jsonreference"
version = "v0.16.0"
[[override]]
name = "github.com/go-openapi/spec"
version = "v0.16.0"
[[override]]
name = "github.com/go-openapi/swag"
version = "v0.16.0"
[[override]]
name = "github.com/golang/glog"
revision = "44145f04b68cf362d9c4df2182967c2275eaefed"
[[override]]
name = "github.com/golang/groupcache"
revision = "c65c006176ff7ff98bb916961c7abbc6b0afc0aa"
[[override]]
name = "github.com/google/btree"
revision = "4030bb1f1f0c35b30ca7009e9ebd06849dd45306"
[[override]]
name = "github.com/google/gofuzz"
revision = "44d81051d367757e1c7c6a5a86423ece9afcf63c"
[[override]]
name = "github.com/gregjones/httpcache"
revision = "9cad4c3443a7200dd6400aef47183728de563a38"
[[override]]
name = "github.com/json-iterator/go"
version = "v1.1.3"
[[override]]
name = "github.com/mailru/easyjson"
revision = "60711f1a8329503b04e1c88535f419d0bb440bff"
[[override]]
name = "github.com/mattbaird/jsonpatch"
revision = "81af80346b1a01caae0cbc27fd3c1ba5b11e189f"
[[override]]
name = "github.com/modern-go/reflect2"
version = "1.0.0"
[[override]]
name = "github.com/petar/GoLLRB"
revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4"
[[override]]
name = "github.com/prometheus/client_golang"
version = "0.9.2"
[[override]]
name = "github.com/prometheus/client_model"
revision = "fd36f4220a901265f90734c3183c5f0c91daa0b8"
[[override]]
name = "github.com/prometheus/common"
revision = "bf857faf208676b0294bd49959aa34c8d98163ed"
[[override]]
name = "github.com/prometheus/procfs"
revision = "6ed1f7e1041181781dd2826d3001075d011a80cc"
[[override]]
name = "github.com/sirupsen/logrus"
version = "0.11.5"
[[override]]
name = "github.com/spf13/pflag"
version = "v1.0.1"
[[override]]
name = "google.golang.org/appengine"
version = "v1.2.0"
[[override]]
name = "gopkg.in/inf.v0"
revision = "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4"
[[override]]
name = "gopkg.in/yaml.v2"
version = "v2.2.1"
[[override]]
name = "k8s.io/gengo"
revision = "fd15ee9cc2f77baa4f31e59e6acbf21146455073"
[[override]]
name = "k8s.io/kube-openapi"
revision = "0317810137be915b9cf888946c6e115c1bfac693"
[prune]
go-tests = true
non-go = true
[[prune.project]]
name = "k8s.io/code-generator"
non-go = false

192
README.md
View File

@ -1,156 +1,116 @@
# Che/Codeready Operator
## Che/CodeReady Workspaces Operator
Che Operator creates Eclipse Che k8s and OpenShift resources such as pvcs, services, deployments, routes, ingresses etc.
Che/CodeReady workspaces operator uses [Operator SDK](https://github.com/operator-framework/operator-sdk) and [Go Kube client](https://github.com/kubernetes/client-go) to deploy, update and manage K8S/OpenShift resources that constitute a multi user Eclipse Che/CodeReady Workspaces cluster.
The operator is a k8s a pod that runs an image with Go runtime and a compiled binary of an operator itself.
The operator watches for a Custom Resource of Kind `CheCluster`, and operator controller executes its business logic when a new Che object is created, namely:
Though operator-sdk framework is used, Che operator is rather an installer since no CRD and API group are created,
and thus, the operator does not watch resources. Once deployment is completed, the operator pod exits.
* creates k8s/OpenShift objects
* verifies successful deployment of Postgres, Keycloak and Che
* runs exec into Postgres and Keycloak pods to provisions databases, users, realm and clients
* updates CR spec and status (passwords, URLs, provisioning statuses etc.)
* continuously watches CR, update Che ConfigMap accordingly and schedule a new Che deployment
* changes state of certain objects depending on CR fields:
* turn on/off TLS mode (reconfigure routes, update ConfigMap)
* turn on/off OpenShift oAuth (login with OpenShift in Che) (create identity provider, oAuth client, update Che ConfigMap)
* updates Che deployment with a new image:tag when a new operator version brings in a new Che tag
## Pre-Reqs
## Project State: Beta
OpenShift/K8S cluster with at least 4GB or RAM and 2 PVs, local `oc` or `kubectl`.
The project is in its early development and breaking changes are possible.
## How to deploy
## How to Deploy
Deploy script will create a namespace, operator service account and a rolebinding for it (admin privileges within the namespace),
and run an operator pod that will create all required objects and perform provisioning:
```
./deploy.sh
```
The script will create sa, role, role binding, operator deployment, CRD and CR.
Wait until Che deployment is scaled to 1 and Che route is created.
When on pure k8s, make sure you provide a global ingress domain in `deploy/crds/org_v1_che_cr.yaml` for example:
```bash
deploy/deploy.sh $infra $namespace
k8s:
ingressDomain: '192.168.99.101.nip.io'
```
Deploy to MiniKube to a default namespace:
### OpenShift oAuth
Bear in mind that che-operator service account needs to have cluster admin privileges so that the operator can create oauthclient at a cluster scope.
There is `oc adm` command in both scripts. Uncomment it if you need these features.
Make sure your current user has cluster-admin privileges.
### TLS
#### OpenShift
When using self-signed certificates make sure you set `server.selfSignedCerts` to true and grant che-operator service account cluster admin privileges
or create a secret called `self-signed-certificate` in a target namespace with ca.crt holding your OpenShift router crt body.
#### K8S
When enabling TLS, make sure you create a secret with crt and key, and let the Operator know about it in `k8s.tlsSecretName`
## 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.
## How to Build Operator Image
```bash
deploy/deploy.sh minikube
```
`minikube` as infra argument will instruct the script to detect MiniKube IP and pass global ingress domain value to the operator.
Create default namespace eclipse-che and deploy to k8s:
```
deploy/deploy.sh k8s
docker build -t $registry/$repo:$tag
```
Create a default eclipse-che namespace and deploy to OpenShift:
You can then use the resulting image in operator deployment (deploy/operator.yaml)
## Build and Deploy to a local cluster:
There's a little script that will build a Docker image and deploy an operator to a selected namespace,
as well as create service account, role, role binding, CRD and example CR.
```
deploy/deploy.sh
```
Create a namespace of choice and deploy to OpenShift:
oc new-project $namespace
build_deploy_local.sh $namespace
```
deploy/deploy.sh openshift myproject
```
If a namespace cannot be created (for example, it exists), a new namespace with a generated name (`eclipse-che${random int}`) will be created.
## How to Run/Debug Locally
This will deploy Che operator with the default settings:
You can run/debug this operator on your local machine (not deployed to a k8s cluster),
provided that the below pre-reqs are met.
* upstream Che
* no tls
* no login with OpenShift in Che
* Postgres passwords are auto-generated, Keycloak admin password is `admin`
* Some object names are default ones (eg databases, users etc)
* Common PVC strategy (all workspaces use one shared PVC)
* All workspace objects get created in a target namespace (Che server uses service account token)
* Multi-host ingress strategy when on k8s
### Pre-Reqs: Local kubeconfig
Go client grabs kubeconfig either from InClusterConfig or ~/.kube locally.
Make sure you oc login (or your current kubectl context points to a target cluster and namespace),
and current user/server account can create objects in a target namespace.
## Deploy custom image
### Pre-Reqs: WATCH_NAMESPACE Environment Variable
Provide your own image in `deploy/config.yaml`:
The operator detects namespace to watch by getting value of `WATCH_NAMESPACE` environment variable.
You can set it in Run configuration in the IDE, or export this env before executing the binary.
```shell
CHE_IMAGE: "myRegistry/myImage:myTag"
```
This applies both to Run and Debug.
## Deploy CodeReady Workspaces
### Pre-Reqs: /tmp/keycloak_provision file
In `deploy/config.yaml`:
The operator grabs this file and replaces values to get a string used as exec command to create Keycloak realm, client and user.
Make sure you run the following before running/debugging:
```
CHE_FLAVOR: "codeready"
cp deploy/keycloak_provision /tmp/keycloak_provision
```
Che flavor is a string that will be used in names of certain objects like routes, realms, clients etc.
This file is added to a Docker image, thus this step isn't required when deploying an operator image.
## Defaults and Configuration
To deploy to OpenShift with all defaults, no user input is required. You may configure Che installation in a configmap `deploy/config.yaml`.
The operator will use envs from this configmap and make decisions accordingly.
Currently, only the most critical envs are added to configmap, and it will expand in time, making it possible to fine tune Che before deploying
## What is deployed?
The Operator creates a handful of objects for:
* Postgres DB
* Keycloak/Red Hat SSO
* Che server itself
After Postgres and Keycloak pods start and health checks confirm the services are up, the operator run execs into db and keycloak pods to provision
databases, users, Keycloak realm, client and user. The operator watches respective deployments to get events signalling that the deployment is scaled to 1.
## How to configure installation
`deploy/config.yaml` is a config map with env variables that influences choices an operator makes. Each env is commented, and each one has defaults.
This configmap is an operator's env, and evn variables are then taken to Che server configmap.
What can be configured:
* external DB and Keycloak: `CHE_EXTERNAL_DB` and `CHE_EXTERNAL_KEYCLOAK` default to false.
If you do not need instances of Postgres and Keycloak and want to connect to own infra, set both envs to `true` and provide connection details in envs below the above booleans.
Your DB user **MUST** be a `SUPERUSER`.
**Important!** The operator does not perform Postgres and Keycloak provisioning if external instances are used.
Thus, you need to pre-create db and user for Che.
Also create (or use existing) realm and client that should be public and should have:
Redirect URIs: `${PROTOCOL}://${CHE_HOST}/*`
WebOrigins: `${PROTOCOL}://${CHE_HOST}`
* Login with OpenShift in Che. Not supported on k8s infra
* TLS. Set `TLS_SUPPORT` to true if you want to deploy Che in https mode.
When on k8s, make sure you create a secret in che namespace and provide its name in `TLS_SECRET_NAME`
* TLS and self signed certs. Provide a base64 string of your self signed cert in `SELF_SIGNED_CERT`. When on k8s, you can get it from crt part of the secret.
* Fake dns. If, for example, you want to deploy on k8s with a fake ingress domain of example.com and you need to point it to your Minikube IP, `HOST_ALIAS_IP` and `HOST_ALIAS_HOSTNAME` are the two envs to use.
## How to build and deploy own operator image
### Build an image
To build an Operator and package it into a Docker image run:
`docker build -t che-operator .`
If you want to deploy your custom Operator image to a remote k8s/OpenShift cluster, make sure you push an image and change its name in
`--image` and `--overrides` in kubectl/oc run command in the script.
### Build and deploy to a local cluster
Run `deploy/build_deploy_local.sh $infra $namespace`
Minishift and Minikube users will need to execute the below command to use VM Docker daemon when building an image:
```
eval $(minikube docker-env)
```
## How to add new envs to configmap?
If you need to add new envs to be added to Che configmap by default:
* add key:value to deploy/config.yaml
* add operator variable to `pkg/operator/vars.go` that will take its value from environment
* add env and operator variable as value to `pkg/operator/che_cm.go`
## Deploy Script
It is something quick and dirty and is likely to be substituted with a feature rich CLI.

28
build_deploy_local.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/bash
#
# Copyright (c) 2012-2018 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
set -e
BASE_DIR=$(cd "$(dirname "$0")"; pwd)
docker build -t che/operator .
kubectl apply -f ${BASE_DIR}/deploy/service_account.yaml -n=$1
kubectl apply -f ${BASE_DIR}/deploy/role.yaml -n=$1
kubectl apply -f ${BASE_DIR}/deploy/role_binding.yaml -n=$1
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
kubectl apply -f ${BASE_DIR}/deploy/crds/org_v1_che_cr.yaml -n=$1

112
cmd/manager/main.go Normal file
View File

@ -0,0 +1,112 @@
//
// 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
//
package main
import (
"context"
"flag"
"fmt"
"github.com/eclipse/che-operator/pkg/util"
"github.com/operator-framework/operator-sdk/pkg/k8sutil"
"github.com/prometheus/common/log"
"github.com/sirupsen/logrus"
"os"
"runtime"
"github.com/eclipse/che-operator/pkg/apis"
"github.com/eclipse/che-operator/pkg/controller"
"github.com/operator-framework/operator-sdk/pkg/leader"
"github.com/operator-framework/operator-sdk/pkg/ready"
sdkVersion "github.com/operator-framework/operator-sdk/version"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"
//logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
)
func printVersion() {
logrus.Infof(fmt.Sprintf("Go Version: %s", runtime.Version()))
logrus.Infof(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH))
logrus.Infof(fmt.Sprintf("operator-sdk Version: %v", sdkVersion.Version))
isOpenShift, err := util.DetectOpenShift()
if err != nil {
logrus.Fatalf("Operator is exiting. An error occurred when detecting current infra: %s", err)
}
infra := "Kubernetes"
if isOpenShift {
infra = "OpenShift"
}
logrus.Infof(fmt.Sprintf("Operator is running on %v", infra))
}
func main() {
flag.Parse()
//logf.SetLogger(logf.ZapLogger(false))
printVersion()
namespace, err := k8sutil.GetWatchNamespace()
if err != nil {
logrus.Errorf( "Failed to get watch namespace. Using default namespace eclipse-che: %s", err)
namespace = "eclipse-che"
}
// Get a config to talk to the apiserver
cfg, err := config.GetConfig()
if err != nil {
log.Error(err, "")
os.Exit(1)
}
// Become the leader before proceeding
leader.Become(context.TODO(), "che-operator-lock")
r := ready.NewFileReady()
err = r.Set()
if err != nil {
log.Error(err, "")
os.Exit(1)
}
defer r.Unset()
// Create a new Cmd to provide shared dependencies and start components
mgr, err := manager.New(cfg, manager.Options{Namespace: namespace})
if err != nil {
log.Error(err, "")
os.Exit(1)
}
logrus.Info("Registering Components")
// Setup Scheme for all resources
if err := apis.AddToScheme(mgr.GetScheme()); err != nil {
logrus.Error(err, "")
os.Exit(1)
}
// Setup all Controllers
if err := controller.AddToManager(mgr); err != nil {
log.Error(err, "")
os.Exit(1)
}
logrus.Info("Starting the Cmd")
// Start the Cmd
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
logrus.Error(err, "Manager exited non-zero")
os.Exit(1)
}
}

25
deploy.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
#
# Copyright (c) 2012-2018 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
#set -e
BASE_DIR=$(cd "$(dirname "$0")"; pwd)
oc apply -f ${BASE_DIR}/service_account.yaml
oc apply -f ${BASE_DIR}/role.yaml
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

View File

@ -0,0 +1,72 @@
#
# 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
apiVersion: org.eclipse.che/v1
kind: CheCluster
metadata:
name: eclipse-che
spec:
server:
cheImage: ''
cheImageTag: ''
# defaults to `che`. When set to `codeready`, CodeReady Workspaces is deployed
# the difference is in images, labels, exec commands
cheFlavor: ''
# when set to true the operator will attempt to get a secret in openshift router namespace
# to add it to Java trust store of Che server. Requires cluster-admin provileges for operator service account
selfSignedCert:
# TLS mode for Che. Make sure you either have public cert, or set selfSignedCert to true
tlsSupport:
proxyURL: ''
proxyPort: ''
proxyUser: ''
proxyPassword: ''
nonProxyHosts: ''
pluginRegistryUrl: ''
database:
# when set to true, the operator skips deploying Postgres, and passes connection details of existing DB to Che server
externalDb:
chePostgresHostname: ''
chePostgresPort: ''
chePostgresUser: ''
chePostgresPassword: ''
chePostgresDb: ''
storage:
# defaults to 'common' (one PVC for all workspacees). Can be 'unique' (PVC per volume), or 'per-workspace'
pvcStrategy: ''
# default to 1Gi
pvcClaimSize: ''
# use a special pod to pre-create subpaths in a common volume
preCreateSubPaths: true
auth:
# when set to true, the operator skips deploying Keycloak,
#and passes connection details of existing Keycloak auth server to Che server
externalKeycloak:
keycloakURL: ''
keycloakPostgresPassword: ''
keycloakAdminUserName: ''
keycloakAdminPassword: 'admin'
keycloakRealm: ''
keycloakClientId: ''
openShiftoAuth:
openShiftApiUrl: ''
k8s:
# your global ingress domain
ingressDomain: '192.168.99.101.nip.io'
# defaults to nginx
ingressClass: ''
# default to multi-host - <ingress-name>-<namespace>.<global-ingress-domain>
ingressStrategy: ''
# tls secret name will be used in ingress tls spec
tlsSecretName: ''

View File

@ -0,0 +1,25 @@
#
# 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
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: checlusters.org.eclipse.che
spec:
group: org.eclipse.che
names:
kind: CheCluster
listKind: CheClusterList
plural: checlusters
singular: checluster
scope: Namespaced
version: v1
subresources:
status: {}

34
deploy/keycloak_provision Normal file
View File

@ -0,0 +1,34 @@
$script config credentials --server http://0.0.0.0:8080/auth \
--realm master \
--user $keycloakAdminUserName \
--password $keycloakAdminPassword \
&& $script update realms/master -s sslRequired=none \
&& $script get realms/$keycloakRealm; \
if [ $? -eq 0 ]; then echo "Realm exists"; exit 0; fi \
&& $script create realms -s realm='$keycloakRealm' \
-s displayName='$realmDisplayName' \
-s enabled=true \
-s sslRequired=none \
-s registrationAllowed=true \
-s resetPasswordAllowed=true \
-s loginTheme=$keycloakTheme \
-s accountTheme=$keycloakTheme \
-s adminTheme=$keycloakTheme \
-s emailTheme=$keycloakTheme \
&& $script create clients -r '$keycloakRealm' \
-s clientId=$keycloakClientId \
-s id=$keycloakClientId \
-s 'webOrigins=["http://$cheHost", "https://$cheHost"]' \
-s 'redirectUris=["http://$cheHost/*", "https://$cheHost/*"]' \
-s 'directAccessGrantsEnabled'=true \
-s publicClient=true \
&& $script create users -s username=admin \
-s email=\"admin@admin.com\" \
-s enabled=true -r '$keycloakRealm' \
-s 'requiredActions=[$requiredActions]' \
&& $script set-password -r '$keycloakRealm' --username admin \
--new-password admin \
&& $script add-roles -r '$keycloakRealm' \
--uusername admin \
--cclientid broker \
--rolename read-token

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,15 @@
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: checlusters.org.eclipse.che
spec:
group: org.eclipse.che
names:
kind: CheCluster
listKind: CheClusterList
plural: checlusters
singular: checluster
scope: Namespaced
version: v1
subresources:
status: {}

View File

@ -0,0 +1,4 @@
packageName: codeready-workspaces
channels:
- name: final
currentCSV: crwoperator.v1.1.0

View File

@ -0,0 +1,53 @@
#
# 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
apiVersion: apps/v1
kind: Deployment
metadata:
name: che-operator
spec:
replicas: 1
selector:
matchLabels:
name: che-operator
template:
metadata:
labels:
name: che-operator
spec:
serviceAccountName: che-operator
containers:
- name: che-operator
image: che/operator
ports:
- containerPort: 60000
name: metrics
command:
- che-operator
imagePullPolicy: IfNotPresent
readinessProbe:
exec:
command:
- stat
- /tmp/operator-sdk-ready
initialDelaySeconds: 4
periodSeconds: 10
failureThreshold: 1
env:
- name: WATCH_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: OPERATOR_NAME
value: "che-operator"

53
deploy/operator.yaml Normal file
View File

@ -0,0 +1,53 @@
#
# 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
apiVersion: apps/v1
kind: Deployment
metadata:
name: che-operator
spec:
replicas: 1
selector:
matchLabels:
name: che-operator
template:
metadata:
labels:
name: che-operator
spec:
serviceAccountName: che-operator
containers:
- name: che-operator
image: eivantsov/operator-container
ports:
- containerPort: 60000
name: metrics
command:
- che-operator
imagePullPolicy: IfNotPresent
readinessProbe:
exec:
command:
- stat
- /tmp/operator-sdk-ready
initialDelaySeconds: 4
periodSeconds: 10
failureThreshold: 1
env:
- name: WATCH_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: OPERATOR_NAME
value: "che-operator"

78
deploy/role.yaml Normal file
View File

@ -0,0 +1,78 @@
#
# 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
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
creationTimestamp: null
name: che-operator
rules:
- apiGroups:
- extensions/v1beta1
resources:
- ingresses
verbs:
- "*"
- apiGroups:
- route.openshift.io
resources:
- routes
verbs:
- "*"
- apiGroups:
- rbac.authorization.k8s.io
resources:
- roles
- rolebindings
- clusterroles
- clusterrolebindings
verbs:
- "*"
- apiGroups:
- ""
resources:
- pods
- services
- serviceaccounts
- endpoints
- persistentvolumeclaims
- events
- configmaps
- secrets
- pods/exec
- pods/log
verbs:
- '*'
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- apiGroups:
- apps
resources:
- deployments
verbs:
- '*'
- apiGroups:
- monitoring.coreos.com
resources:
- servicemonitors
verbs:
- get
- create
- apiGroups:
- org.eclipse.che
resources:
- '*'
verbs:
- '*'

21
deploy/role_binding.yaml Normal file
View File

@ -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: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: che-operator
subjects:
- kind: ServiceAccount
name: che-operator
roleRef:
kind: Role
name: che-operator
apiGroup: rbac.authorization.k8s.io

View File

@ -0,0 +1,14 @@
#
# 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
apiVersion: v1
kind: ServiceAccount
metadata:
name: che-operator

View File

@ -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
//
package apis
import (
"github.com/eclipse/che-operator/pkg/apis/org/v1"
)
func init() {
// Register the types with the Scheme so the components can map objects to GroupVersionKinds and back
AddToSchemes = append(AddToSchemes, v1.SchemeBuilder.AddToScheme)
}

24
pkg/apis/apis.go Normal file
View File

@ -0,0 +1,24 @@
//
// 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
//
package apis
import (
"k8s.io/apimachinery/pkg/runtime"
)
// AddToSchemes may be used to add all resources defined in the project to a Scheme
var AddToSchemes runtime.SchemeBuilder
// AddToScheme adds all Resources to the Scheme
func AddToScheme(s *runtime.Scheme) error {
return AddToSchemes.AddToScheme(s)
}

View File

@ -0,0 +1,129 @@
//
// 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
//
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// CheClusterSpec defines the desired state of CheCluster
type CheClusterSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
Server CheClusterSpecServer `json:"server"`
Database CheClusterSpecDB `json:"database"`
Auth CheClusterSpecAuth `json:"auth"`
Storage CheClusterSpecStorage `json:"storage"`
K8SOnly CheClusterSpecK8SOnly `json:"k8s"`
}
type CheClusterSpecServer struct {
CheImage string `json:"cheImage"`
CheImageTag string `json:"cheImageTag"`
CheFlavor string `json:"cheFlavor"`
CheHost string `json:"cheHost"`
CheLogLevel string `json:"cheLogLevel"`
CheDebug string `json:"cheDebug"`
SelfSignedCert bool `json:"selfSignedCert"`
TlsSupport bool `json:"tlsSupport"`
PluginRegistryUrl string `json:"pluginRegistryUrl"`
ProxyURL string `json:"proxyURL"`
ProxyPort string `json:"proxyPort"`
NonProxyHosts string `json:"nonProxyHosts"`
ProxyUser string `json:"proxyUser"`
ProxyPassword string `json:"proxyPassword"`
}
type CheClusterSpecDB struct {
ExternalDB bool `json:"externalDb"`
ChePostgresDBHostname string `json:"chePostgresHostName"`
ChePostgresPort string `json:"chePostgresPort"`
ChePostgresUser string `json:"chePostgresUser"`
ChePostgresPassword string `json:"chePostgresPassword"`
ChePostgresDb string `json:"chePostgresDb"`
PostgresImage string `json:"postgresImage"`
}
type CheClusterSpecAuth struct {
ExternalKeycloak bool `json:"externalKeycloak"`
KeycloakURL string `json:"keycloakURL"`
KeycloakAdminUserName string `json:"keycloakAdminUserName"`
KeycloakAdminPassword string `json:"keycloakAdminPassword"`
KeycloakRealm string `json:"keycloakRealm"`
KeycloakClientId string `json:"keycloakClientId"`
KeycloakPostgresPassword string `json:"keycloakPostgresPassword"`
UpdateAdminPassword bool `json:"updateAdminPassword"`
OpenShiftOauth bool `json:"openShiftoAuth"`
OauthClientName string `json:"oAuthClientName"`
OauthSecret string `json:"oAuthSecret"`
KeycloakImage string `json:"keycloakImage"`
}
type CheClusterSpecStorage struct {
PvcStrategy string `json:"pvcStrategy"`
PvcClaimSize string `json:"pvcClaimSize"`
PreCreateSubPaths bool `json:"preCreateSubPaths"`
PvcJobsImage string `json:"pvcJobsImage"`
}
type CheClusterSpecK8SOnly struct {
IngressDomain string `json:"ingressDomain"`
IngressStrategy string `json:"ingressStrategy"`
IngressClass string `json:"ingressClass"`
TlsSecretName string `json:"tlsSecretName"`
}
// CheClusterStatus defines the observed state of CheCluster
type CheClusterStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
DbProvisoned bool `json:"dbProvisioned"`
KeycloakProvisoned bool `json:"keycloakProvisioned"`
OpenShiftoAuthProvisioned bool `json:"openShiftoAuthProvisioned"`
CheClusterRunning string `json:"cheClusterRunning"`
CheVersion string `json:"cheVersion"`
CheURL string `json:"cheURL"`
KeycloakURL string `json:"keycloakURL"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// CheCluster is the Schema for the ches API
// +k8s:openapi-gen=true
type CheCluster struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec CheClusterSpec `json:"spec,omitempty"`
Status CheClusterStatus `json:"status,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// CheClusterList contains a list of CheCluster
type CheClusterList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []CheCluster `json:"items"`
}
func init() {
SchemeBuilder.Register(&CheCluster{}, &CheClusterList{})
}

15
pkg/apis/org/v1/doc.go Normal file
View File

@ -0,0 +1,15 @@
//
// 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
//
// Package v1 contains API Schema definitions for the org v1 API group
// +k8s:deepcopy-gen=package,register
// +groupName=org.eclipse.che
package v1

View File

@ -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
//
// NOTE: Boilerplate only. Ignore this file.
// Package v1 contains API Schema definitions for the org v1 API group
// +k8s:deepcopy-gen=package,register
// +groupName=org.eclipse.che
package v1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/runtime/scheme"
)
var (
// SchemeGroupVersion is group version used to register these objects
SchemeGroupVersion = schema.GroupVersion{Group: "org.eclipse.che", Version: "v1"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
)

View File

@ -0,0 +1,214 @@
//
// 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
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CheCluster) DeepCopyInto(out *CheCluster) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
out.Status = in.Status
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheCluster.
func (in *CheCluster) DeepCopy() *CheCluster {
if in == nil {
return nil
}
out := new(CheCluster)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CheCluster) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CheClusterList) DeepCopyInto(out *CheClusterList) {
*out = *in
out.TypeMeta = in.TypeMeta
out.ListMeta = in.ListMeta
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]CheCluster, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheClusterList.
func (in *CheClusterList) DeepCopy() *CheClusterList {
if in == nil {
return nil
}
out := new(CheClusterList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CheClusterList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CheClusterSpec) DeepCopyInto(out *CheClusterSpec) {
*out = *in
out.Server = in.Server
out.Database = in.Database
out.Auth = in.Auth
out.Storage = in.Storage
out.K8SOnly = in.K8SOnly
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheClusterSpec.
func (in *CheClusterSpec) DeepCopy() *CheClusterSpec {
if in == nil {
return nil
}
out := new(CheClusterSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CheClusterSpecAuth) DeepCopyInto(out *CheClusterSpecAuth) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheClusterSpecAuth.
func (in *CheClusterSpecAuth) DeepCopy() *CheClusterSpecAuth {
if in == nil {
return nil
}
out := new(CheClusterSpecAuth)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CheClusterSpecDB) DeepCopyInto(out *CheClusterSpecDB) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheClusterSpecDB.
func (in *CheClusterSpecDB) DeepCopy() *CheClusterSpecDB {
if in == nil {
return nil
}
out := new(CheClusterSpecDB)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CheClusterSpecK8SOnly) DeepCopyInto(out *CheClusterSpecK8SOnly) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheClusterSpecK8SOnly.
func (in *CheClusterSpecK8SOnly) DeepCopy() *CheClusterSpecK8SOnly {
if in == nil {
return nil
}
out := new(CheClusterSpecK8SOnly)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CheClusterSpecServer) DeepCopyInto(out *CheClusterSpecServer) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheClusterSpecServer.
func (in *CheClusterSpecServer) DeepCopy() *CheClusterSpecServer {
if in == nil {
return nil
}
out := new(CheClusterSpecServer)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CheClusterSpecStorage) DeepCopyInto(out *CheClusterSpecStorage) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheClusterSpecStorage.
func (in *CheClusterSpecStorage) DeepCopy() *CheClusterSpecStorage {
if in == nil {
return nil
}
out := new(CheClusterSpecStorage)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CheClusterStatus) DeepCopyInto(out *CheClusterStatus) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheClusterStatus.
func (in *CheClusterStatus) DeepCopy() *CheClusterStatus {
if in == nil {
return nil
}
out := new(CheClusterStatus)
in.DeepCopyInto(out)
return out
}

21
pkg/controller/add_che.go Normal file
View File

@ -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
//
package controller
import (
"github.com/eclipse/che-operator/pkg/controller/che"
)
func init() {
// AddToManagerFuncs is a list of functions to create controllers and add them to a manager.
AddToManagerFuncs = append(AddToManagerFuncs, che.Add)
}

View File

@ -0,0 +1,640 @@
//
// 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
//
package che
import (
"context"
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/deploy"
"github.com/eclipse/che-operator/pkg/util"
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"
"k8s.io/api/extensions/v1beta1"
rbac "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"time"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
"sigs.k8s.io/controller-runtime/pkg/source"
)
var log = logf.Log.WithName("controller_che")
var (
k8sclient = GetK8Client()
)
// Add creates a new CheCluster Controller and adds it to the Manager. The Manager will set fields on the Controller
// and Start it when the Manager is Started.
func Add(mgr manager.Manager) error {
return add(mgr, newReconciler(mgr))
}
// newReconciler returns a new reconcile.Reconciler
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
return &ReconcileChe{client: mgr.GetClient(), scheme: mgr.GetScheme()}
}
// add adds a new Controller to mgr with r as the reconcile.Reconciler
func add(mgr manager.Manager, r reconcile.Reconciler) error {
isOpenShift, err := util.DetectOpenShift()
if err != nil {
logrus.Errorf("An error occurred when detecting current infra: %s", err)
}
// Create a new controller
c, err := controller.New("che-controller", mgr, controller.Options{Reconciler: r})
if err != nil {
return err
}
// register OpenShift routes in the scheme
if isOpenShift {
if err := routev1.AddToScheme(mgr.GetScheme()); err != nil {
logrus.Errorf("Failed to add OpenShift route to scheme: %", err)
}
if err := oauth.AddToScheme(mgr.GetScheme()); err != nil {
logrus.Errorf("Failed to add oAuth to scheme: %", err)
}
}
// register RBAC in the scheme
if err := rbac.AddToScheme(mgr.GetScheme()); err != nil {
logrus.Errorf("Failed to add RBAC to scheme: %", err)
}
// Watch for changes to primary resource CheCluster
err = c.Watch(&source.Kind{Type: &orgv1.CheCluster{}}, &handler.EnqueueRequestForObject{})
if err != nil {
return err
}
// Watch for changes to secondary resources and requeue the owner CheCluster
if err = c.Watch(&source.Kind{Type: &corev1.Service{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
}); err != nil {
return err
}
if err = c.Watch(&source.Kind{Type: &corev1.Secret{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
}); err != nil {
return err
}
err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
err = c.Watch(&source.Kind{Type: &rbac.Role{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
err = c.Watch(&source.Kind{Type: &rbac.RoleBinding{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
err = c.Watch(&source.Kind{Type: &corev1.ServiceAccount{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
if isOpenShift {
err = c.Watch(&source.Kind{Type: &routev1.Route{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
} else {
err = c.Watch(&source.Kind{Type: &v1beta1.Ingress{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
}
err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
err = c.Watch(&source.Kind{Type: &corev1.PersistentVolumeClaim{}}, &handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &orgv1.CheCluster{},
})
if err != nil {
return err
}
return nil
}
var _ reconcile.Reconciler = &ReconcileChe{}
// ReconcileChe reconciles a CheCluster object
type ReconcileChe struct {
// This client, initialized using mgr.Client() above, is a split client
// that reads objects from the cache and writes to the apiserver
client client.Client
scheme *runtime.Scheme
tests bool
}
// Reconcile reads that state of the cluster for a CheCluster object and makes changes based on the state read
// and what is in the CheCluster.Spec. The Controller will requeue the Request to be processed again if the returned error is non-nil or
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, error) {
// Fetch the CheCluster instance
tests := r.tests
var instance, err = r.GetCR(request)
if err != nil {
if errors.IsNotFound(err) {
// Request object not found, could have been deleted after reconcile request.
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
// Return and don't requeue
return reconcile.Result{}, nil
}
// Error reading the object - requeue the request.
return reconcile.Result{}, err
}
isOpenShift, err := util.DetectOpenShift()
if err != nil {
logrus.Errorf("An error occurred when detecting current infra: %s", err)
}
// create a secret with router tls cert if self signed certs are in use
// requires cluster admin privileges
selfSignedCert := instance.Spec.Server.SelfSignedCert
if isOpenShift && selfSignedCert {
crt, err := k8sclient.GetDefaultRouterCert("openshift-ingress")
if err != nil {
logrus.Errorf("Default router tls secret not found. Self signed cert isn't added to CheCluster deployment")
return reconcile.Result{}, err
} else {
secret := deploy.NewSecret(instance, "self-signed-certificate", crt)
if err := r.CreateNewSecret(instance, secret); err != nil {
return reconcile.Result{}, err
}
}
}
if !tests {
deployment := &appsv1.Deployment{}
name := "che"
cheFlavor := instance.Spec.Server.CheFlavor
if cheFlavor == "codeready" {
name = cheFlavor
}
err = r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.Namespace}, deployment)
if err != nil && instance.Status.CheClusterRunning != UnavailableStatus {
if err := r.SetCheUnavailableStatus(instance, request); err != nil {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
}
// create service accounts:
// che is the one which token is used to create workspace objects
// che-workspace is SA used by plugins like exec and terminal with limited privileges like view and exec
cheServiceAccount := deploy.NewServiceAccount(instance, "che")
if err := r.CreateServiceAccount(instance, cheServiceAccount); err != nil {
return reconcile.Result{}, err
}
workspaceServiceAccount := deploy.NewServiceAccount(instance, "che-workspace")
if err := r.CreateServiceAccount(instance, workspaceServiceAccount); err != nil {
return reconcile.Result{}, err
}
// create exec and view roles for CheCluster server and workspaces
execRole := deploy.NewRole(instance, "exec", []string{"pods/exec"}, []string{"*"})
if err := r.CreateNewRole(instance, execRole); err != nil {
return reconcile.Result{}, err
}
viewRole := deploy.NewRole(instance, "view", []string{"pods"}, []string{"list"})
if err := r.CreateNewRole(instance, viewRole); err != nil {
return reconcile.Result{}, err
}
// create RoleBindings for created (and existing ClusterRole) roles and service accounts
cheRoleBinding := deploy.NewRoleBinding(instance, "che", cheServiceAccount.Name, "edit", "ClusterRole")
if err := r.CreateNewRoleBinding(instance, cheRoleBinding); err != nil {
return reconcile.Result{}, err
}
execRoleBinding := deploy.NewRoleBinding(instance, "che-workspace-exec", workspaceServiceAccount.Name, execRole.Name, "Role")
if err = r.CreateNewRoleBinding(instance, execRoleBinding); err != nil {
return reconcile.Result{}, err
}
viewRoleBinding := deploy.NewRoleBinding(instance, "che-workspace-view", workspaceServiceAccount.Name, viewRole.Name, "Role")
if err := r.CreateNewRoleBinding(instance, viewRoleBinding); err != nil {
return reconcile.Result{}, err
}
if err := r.GenerateAndSaveFields(instance, request); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
chePostgresPassword := instance.Spec.Database.ChePostgresPassword
keycloakPostgresPassword := instance.Spec.Auth.KeycloakPostgresPassword
keycloakAdminPassword := instance.Spec.Auth.KeycloakAdminPassword
// Create Postgres resources and provisioning unless an external DB is used
externalDB := instance.Spec.Database.ExternalDB
if !externalDB {
// Create a new postgres service
postgresLabels := deploy.GetLabels(instance, "postgres")
if err := r.CreateService(instance, "postgres", postgresLabels, "postgres", 5432); err != nil {
return reconcile.Result{}, err
}
// Create a new Postgres PVC object
pvc := deploy.NewPvc(instance, "postgres-data", "1Gi", postgresLabels)
if err := r.CreatePVC(instance, pvc); err != nil {
return reconcile.Result{}, err
}
if !tests {
err = r.client.Get(context.TODO(), types.NamespacedName{Name: pvc.Name, Namespace: instance.Namespace}, pvc)
if pvc.Status.Phase != "Bound" {
k8sclient.GetPostgresStatus(pvc, instance.Namespace)
}
}
// Create a new Postgres deployment
postgresDeployment := deploy.NewPostgresDeployment(instance, chePostgresPassword)
if err := r.CreateNewDeployment(instance, postgresDeployment); err != nil {
return reconcile.Result{}, err
}
time.Sleep(time.Duration(1) * time.Second)
pgDeployment, err := r.GetEffectiveDeployment(instance, postgresDeployment.Name)
if err != nil {
logrus.Errorf("Failed to get %s deployment: %s", postgresDeployment.Name, err)
return reconcile.Result{}, err
}
if !tests {
if pgDeployment.Status.AvailableReplicas != 1 {
scaled := k8sclient.GetDeploymentStatus("postgres", instance.Namespace)
if !scaled {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, err
}
}
pgCommand := deploy.GetPostgresProvisionCommand(instance)
dbStatus := instance.Status.DbProvisoned
// provision Db and users for Che and Keycloak servers
if !dbStatus {
podToExec, err := k8sclient.GetDeploymentPod(pgDeployment.Name, instance.Namespace)
if err != nil {
return reconcile.Result{}, err
}
provisioned := ExecIntoPod(podToExec, pgCommand, "create Keycloak DB, user, privileges", instance.Namespace)
if provisioned {
for {
instance.Status.DbProvisoned = true
if err := r.UpdateCheCRStatus(instance, "status: provisioned with DB and user", "true"); err != nil {
instance, _ = r.GetCR(request)
} else {
break
}
}
} else {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, err
}
}
}
}
cheFlavor := util.GetValue(instance.Spec.Server.CheFlavor, deploy.DefaultCheFlavor)
ingressStrategy := util.GetValue(instance.Spec.K8SOnly.IngressStrategy, deploy.DefaultIngressStrategy)
ingressDomain := instance.Spec.K8SOnly.IngressDomain
tlsSupport := instance.Spec.Server.TlsSupport
protocol := "http"
if tlsSupport {
protocol = "https"
}
// create Che service and route
cheLabels := deploy.GetLabels(instance, util.GetValue(instance.Spec.Server.CheFlavor, deploy.DefaultCheFlavor))
if err := r.CreateService(instance, "che-host", cheLabels, "http", 8080); err != nil {
return reconcile.Result{}, err
}
if !isOpenShift {
cheIngress := deploy.NewIngress(instance, cheFlavor, "che-host", 8080)
if err := r.CreateNewIngress(instance, cheIngress); err != nil {
return reconcile.Result{}, err
}
cheHost := ingressDomain
if ingressStrategy == "multi-host" {
cheHost = cheFlavor + "-" + instance.Namespace + "." + ingressDomain
}
if len(instance.Spec.Server.CheHost) == 0 {
instance.Spec.Server.CheHost = cheHost
if err := r.UpdateCheCRSpec(instance, "CheHost URL", cheHost); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
} else {
cheRoute := deploy.NewRoute(instance, cheFlavor, "che-host")
if tlsSupport {
cheRoute = deploy.NewTlsRoute(instance, cheFlavor, "che-host")
}
if err := r.CreateNewRoute(instance, cheRoute); err != nil {
return reconcile.Result{}, err
}
if len(instance.Spec.Server.CheHost) == 0 {
instance.Spec.Server.CheHost = cheRoute.Spec.Host
if len(cheRoute.Spec.Host) < 1 {
cheRoute := r.GetEffectiveRoute(instance, cheRoute.Name)
instance.Spec.Server.CheHost = cheRoute.Spec.Host
}
if err := r.UpdateCheCRSpec(instance, "CheHost URL", instance.Spec.Server.CheHost); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
}
// create and provision Keycloak related objects
ExternalKeycloak := instance.Spec.Auth.ExternalKeycloak
if !ExternalKeycloak {
keycloakLabels := deploy.GetLabels(instance, "keycloak")
if err := r.CreateService(instance, "keycloak", keycloakLabels, "http", 8080); err != nil {
return reconcile.Result{}, err
}
// create Keycloak ingresses when on k8s
if !isOpenShift {
keycloakIngress := deploy.NewIngress(instance, "keycloak", "keycloak", 8080)
if err := r.CreateNewIngress(instance, keycloakIngress); err != nil {
return reconcile.Result{}, err
}
keycloakURL := protocol + "://" + ingressDomain
if ingressStrategy == "multi-host" {
keycloakURL = protocol + "://keycloak-" + instance.Namespace + "." + ingressDomain
}
if len(instance.Spec.Auth.KeycloakURL) == 0 {
instance.Spec.Auth.KeycloakURL = keycloakURL
if err := r.UpdateCheCRSpec(instance, "Keycloak URL", instance.Spec.Auth.KeycloakURL); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
} else {
// create Keycloak route
keycloakRoute := deploy.NewRoute(instance, "keycloak", "keycloak")
if tlsSupport {
keycloakRoute = deploy.NewTlsRoute(instance, "keycloak", "keycloak")
}
if err = r.CreateNewRoute(instance, keycloakRoute); err != nil {
return reconcile.Result{}, err
}
keycloakURL := keycloakRoute.Spec.Host
if len(instance.Spec.Auth.KeycloakURL) == 0 {
instance.Spec.Auth.KeycloakURL = protocol + "://" + keycloakURL
if len(keycloakURL) < 1 {
keycloakURL := r.GetEffectiveRoute(instance, keycloakRoute.Name).Spec.Host
instance.Spec.Auth.KeycloakURL = protocol + "://" + keycloakURL
}
if err := r.UpdateCheCRSpec(instance, "Keycloak URL", instance.Spec.Auth.KeycloakURL); err != nil {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
instance.Status.KeycloakURL = protocol + "://" + keycloakURL
if err := r.UpdateCheCRStatus(instance, "status: Keycloak URL", instance.Spec.Auth.KeycloakURL); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
}
keycloakDeployment := deploy.NewKeycloakDeployment(instance, keycloakPostgresPassword, keycloakAdminPassword, cheFlavor)
if err := r.CreateNewDeployment(instance, keycloakDeployment); err != nil {
return reconcile.Result{}, err
}
time.Sleep(time.Duration(1) * time.Second)
deployment, err := r.GetEffectiveDeployment(instance, keycloakDeployment.Name)
if err != nil {
logrus.Errorf("Failed to get %s deployment: %s", keycloakDeployment.Name, err)
return reconcile.Result{}, err
}
if !tests {
if deployment.Status.AvailableReplicas != 1 {
scaled := k8sclient.GetDeploymentStatus(keycloakDeployment.Name, instance.Namespace)
if !scaled {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, err
}
}
keycloakRealmClientStatus := instance.Status.KeycloakProvisoned
if !keycloakRealmClientStatus {
if err := r.CreateKyecloakResources(instance, request, keycloakDeployment.Name); err != nil {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, err
}
}
}
if isOpenShift {
doInstallOpenShiftoAuthProvider := instance.Spec.Auth.OpenShiftOauth
if doInstallOpenShiftoAuthProvider {
openShiftIdentityProviderStatus := instance.Status.OpenShiftoAuthProvisioned
if !openShiftIdentityProviderStatus {
if err := r.CreateIdentityProviderItems(instance, request, cheFlavor, keycloakDeployment.Name); err != nil {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, err
}
}
}
}
}
// create Che ConfigMap
cheHost := instance.Spec.Server.CheHost
cheEnv := deploy.GetConfigMapData(instance)
cheConfigMap := deploy.NewCheConfigMap(instance, cheEnv)
if err := r.CreateNewConfigMap(instance, cheConfigMap); err != nil {
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
customCM := &corev1.ConfigMap{
Data: map[string]string{"SAMPLE_KEY": "SAMPLE_VALUE"},
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap"},
ObjectMeta: metav1.ObjectMeta{
Name: "custom",
Namespace: instance.Namespace,
Labels: cheLabels}}
if err := r.CreateNewConfigMap(instance, customCM); err != nil {
return reconcile.Result{}, err
}
cmResourceVersion := cheConfigMap.ResourceVersion
// create Che deployment
cheImageRepo := util.GetValue(instance.Spec.Server.CheImage, deploy.DefaultCheServerImageRepo)
cheImageTag := util.GetValue(instance.Spec.Server.CheImageTag, deploy.DefaultCheServerImageTag)
if cheFlavor == "codeready" {
cheImageRepo = util.GetValue(instance.Spec.Server.CheImage, deploy.DefaultCodeReadyServerImageRepo)
cheImageTag = util.GetValue(instance.Spec.Server.CheImageTag, deploy.DefaultCodeReadyServerImageTag)
}
cheDeployment := deploy.NewCheDeployment(instance, cheImageRepo, cheImageTag, cmResourceVersion)
if err := r.CreateNewDeployment(instance, cheDeployment); err != nil {
return reconcile.Result{}, err
}
// sometimes Get cannot find deployment right away
time.Sleep(time.Duration(1) * time.Second)
deployment, err := r.GetEffectiveDeployment(instance, cheDeployment.Name)
if err != nil {
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)
if err := r.SetCheUnavailableStatus(instance, request); err != nil {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
scaled := k8sclient.GetDeploymentStatus(cheDeployment.Name, instance.Namespace)
if !scaled {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, err
}
err = r.client.Get(context.TODO(), types.NamespacedName{Name: cheDeployment.Name, Namespace: instance.Namespace}, deployment)
if deployment.Status.AvailableReplicas == 1 {
if err := r.SetCheAvailableStatus(instance, request, protocol, cheHost); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
if instance.Status.CheVersion != cheImageTag {
instance.Status.CheVersion = cheImageTag
if err := r.UpdateCheCRStatus(instance, "version", cheImageTag); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
}
}
if deployment.Status.Replicas > 1 {
logrus.Infof("Deployment %s is in the rolling update state", cheDeployment.Name)
if err := r.SetCheRollingUpdateStatus(instance, request); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
k8sclient.GetDeploymentRollingUpdateStatus(cheDeployment.Name, instance.Namespace)
deployment, _ := r.GetEffectiveDeployment(instance, cheDeployment.Name)
if deployment.Status.Replicas == 1 {
if err := r.SetCheAvailableStatus(instance, request, protocol, cheHost); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
}
}
if deployment.Spec.Template.Spec.Containers[0].Image != cheDeployment.Spec.Template.Spec.Containers[0].Image {
if err := controllerutil.SetControllerReference(instance, deployment, r.scheme); err != nil {
logrus.Errorf("An error occurred: %s", err)
}
logrus.Infof("Updating %s %s with image %s:%s", cheDeployment.Name, cheDeployment.Kind, cheImageRepo, cheImageTag)
instance.Status.CheVersion = cheImageTag
if err := r.UpdateCheCRStatus(instance, "version", cheImageTag); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
if err := r.client.Update(context.TODO(), cheDeployment); err != nil {
logrus.Errorf("Failed to update %s %s: %s", deployment.Kind, deployment.Name, err)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
// reconcile routes/ingresses before reconciling Che deployment
activeConfigMap := &corev1.ConfigMap{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: "che", Namespace: instance.Namespace}, activeConfigMap); err != nil {
logrus.Errorf("ConfigMap %s not found: %s", activeConfigMap.Name, err)
}
if !tlsSupport && activeConfigMap.Data["CHE_INFRA_OPENSHIFT_TLS__ENABLED"] == "true" {
routesUpdated, err := r.ReconcileTLSObjects(instance, request, cheFlavor, tlsSupport, isOpenShift)
if err != nil {
logrus.Errorf("An error occurred when updating routes %s", err)
}
if routesUpdated {
logrus.Info("Routes have been updated with TLS config")
}
}
if tlsSupport && activeConfigMap.Data["CHE_INFRA_OPENSHIFT_TLS__ENABLED"] == "false" {
routesUpdated, err := r.ReconcileTLSObjects(instance, request, cheFlavor, tlsSupport, isOpenShift)
if err != nil {
logrus.Errorf("An error occurred when updating routes %s", err)
}
if routesUpdated {
logrus.Info("Routes have been updated with TLS config")
}
}
// Reconcile Che ConfigMap to align with CR spec
cmUpdated, err := r.UpdateConfigMap(instance)
if err != nil {
return reconcile.Result{}, err
}
// Delete OpenShift identity provider if OpenShift oAuth is false in spec
// but OpenShiftoAuthProvisioned is true in CR status, e.g. when oAuth has been turned on and then turned off
deleted, err := r.ReconcileIdentityProvider(instance)
if deleted {
instance.Status.OpenShiftoAuthProvisioned = false
if err := r.UpdateCheCRStatus(instance, "provisioned with OpenShift oAuth", "false"); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
instance.Spec.Auth.OauthSecret = ""
if err := r.UpdateCheCRSpec(instance, "delete oAuth secret name", ""); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
instance.Spec.Auth.OauthClientName = ""
if err := r.UpdateCheCRSpec(instance, "delete oAuth client name", ""); err != nil {
instance, _ = r.GetCR(request)
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err
}
}
if cmUpdated {
// sometimes an old cm resource version is returned ie get happens too fast - before server updates CM
time.Sleep(time.Duration(1) * time.Second)
cm := r.GetEffectiveConfigMap(instance, cheConfigMap.Name)
cmResourceVersion := cm.ResourceVersion
cheDeployment := deploy.NewCheDeployment(instance, cheImageRepo, cheImageTag, cmResourceVersion)
if err := controllerutil.SetControllerReference(instance, cheDeployment, r.scheme); err != nil {
logrus.Errorf("An error occurred: %s", err)
}
if err := r.client.Update(context.TODO(), cheDeployment); err != nil {
return reconcile.Result{}, err
}
}
return reconcile.Result{}, nil
}

View File

@ -0,0 +1,167 @@
//
// 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
//
package che
import (
"context"
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
routev1 "github.com/openshift/api/route/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/scheme"
"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"
)
func TestCheController(t *testing.T) {
// Set the logger to development mode for verbose logs.
logf.SetLogger(logf.ZapLogger(true))
var (
name = "eclipse-che"
namespace = "eclipse-che"
)
pgPod := &corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-pg-pod",
Namespace: "eclipse-che",
Labels: map[string]string{
"component": "postgres",
},
},
}
// A CheCluster custom resource with metadata and spec
cheCR := &orgv1.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: orgv1.CheClusterSpec{
// todo add some spec to check controller ifs like external db, ssl etc
},
}
// Objects to track in the fake client.
objs := []runtime.Object{
cheCR, pgPod,
}
route := &routev1.Route{}
oAuthClient := &oauth.OAuthClient{}
// Register operator types with the runtime scheme
s := scheme.Scheme
s.AddKnownTypes(orgv1.SchemeGroupVersion, cheCR)
s.AddKnownTypes(routev1.SchemeGroupVersion, route)
s.AddKnownTypes(oauth.SchemeGroupVersion, oAuthClient)
// Create a fake client to mock API calls
cl := fake.NewFakeClient(objs...)
tests := true
// Create a ReconcileChe object with the scheme and fake client
r := &ReconcileChe{client: cl, scheme: s, tests: tests}
// Mock request to simulate Reconcile() being called on an event for a
// watched resource .
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: name,
Namespace: namespace,
},
}
res, err := r.Reconcile(req)
if err != nil {
t.Fatalf("reconcile: (%v)", err)
}
// Check the result of reconciliation to make sure it has the desired state.
if res.Requeue {
t.Error("Reconcile did not requeue request as expected")
}
// update CR and make sure Che configmap has been updated
cheCR.Spec.Server.TlsSupport = true
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)
}
// get configmap
cm := &corev1.ConfigMap{}
if err := cl.Get(context.TODO(), types.NamespacedName{Name: "che", Namespace: cheCR.Namespace}, cm); err != nil {
t.Errorf("ConfigMap %s not found: %s", cm.Name, err)
}
// run a few checks to make sure the operator reconciled tls routes and updated configmap
if cm.Data["CHE_INFRA_OPENSHIFT_TLS__ENABLED"] != "true" {
t.Errorf("ConfigMap wasn't updated. Extecting true, got: %s", cm.Data["CHE_INFRA_OPENSHIFT_TLS__ENABLED"])
}
if err := cl.Get(context.TODO(), types.NamespacedName{Name: "che", Namespace: cheCR.Namespace}, route); err != nil {
t.Errorf("Route %s not found: %s", cm.Name, err)
}
if route.Spec.TLS.Termination != "edge" {
t.Errorf("Test failed as %s %s is not a TLS route", route.Kind, route.Name)
}
// update CR and make sure Che configmap has been updated
cheCR.Spec.Auth.OpenShiftOauth = true
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)
}
// get configmap and check if identity provider name and workspace project name are correctly set
cm = &corev1.ConfigMap{}
if err := cl.Get(context.TODO(), types.NamespacedName{Name: "che", Namespace: cheCR.Namespace}, cm); err != nil {
t.Errorf("ConfigMap %s not found: %s", cm.Name, err)
}
if cm.Data["CHE_INFRA_OPENSHIFT_PROJECT"] != "" {
t.Errorf("ConfigMap wasn't updated properly. Extecting empty string, got: '%s'", cm.Data["CHE_INFRA_OPENSHIFT_PROJECT"])
}
expectedIdentityProviderName := "openshift-v3"
if cm.Data["CHE_INFRA_OPENSHIFT_OAUTH__IDENTITY__PROVIDER"] != expectedIdentityProviderName {
t.Errorf("ConfigMap wasn't updated properly. Extecting '%s', got: '%s'", expectedIdentityProviderName, cm.Data["CHE_INFRA_OPENSHIFT_OAUTH__IDENTITY__PROVIDER"])
}
err = r.client.Get(context.TODO(), types.NamespacedName{Name: cheCR.Name, Namespace: cheCR.Namespace}, cheCR)
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 {
t.Errorf("Failed to Get oAuthClient %s: %s", oAuthClient.Name, err)
}
if oAuthClient.Secret != oauthSecret {
t.Errorf("Secrets do not match. Expecting %s, got %s", oauthSecret, oAuthClient.Secret)
}
}

View File

@ -0,0 +1,474 @@
//
// 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
//
package che
import (
"context"
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/deploy"
"github.com/eclipse/che-operator/pkg/util"
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"
"k8s.io/api/extensions/v1beta1"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"strings"
)
func (r *ReconcileChe) CreateNewDeployment(instance *orgv1.CheCluster, deployment *appsv1.Deployment) error {
if err := controllerutil.SetControllerReference(instance, deployment, r.scheme); err != nil {
logrus.Errorf("An error occurred: %s", err)
return err
}
deploymentFound := &appsv1.Deployment{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: deployment.Name, Namespace: deployment.Namespace}, deploymentFound)
if err != nil && errors.IsNotFound(err) {
logrus.Infof("Creating a new object: %s, name: %s", deployment.Kind, deployment.Name)
err = r.client.Create(context.TODO(), deployment)
if err != nil {
logrus.Errorf("Failed to create %s %s: %s", deployment.Kind, deployment.Name, err)
return err
}
return nil
} else if err != nil {
logrus.Errorf("An error occurred: %s", err)
return err
}
return nil
}
func (r *ReconcileChe) CreateNewConfigMap(instance *orgv1.CheCluster, configMap *corev1.ConfigMap) error {
if err := controllerutil.SetControllerReference(instance, configMap, r.scheme); err != nil {
logrus.Errorf("An error occurred: %s", err)
return err
}
configMapFound := &corev1.ConfigMap{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: configMap.Name, Namespace: configMap.Namespace}, configMapFound)
if err != nil && errors.IsNotFound(err) {
logrus.Infof("Creating a new object: %s, name: %s", configMap.Kind, configMap.Name)
err = r.client.Create(context.TODO(), configMap)
if err != nil {
logrus.Errorf("Failed to create %s %s: %s", configMap.Kind, configMap.Name, err)
return err
}
return nil
} else if err != nil {
logrus.Errorf("An error occurred: %s", err)
return err
}
return nil
}
func (r *ReconcileChe) CreateServiceAccount(cr *orgv1.CheCluster, serviceAccount *corev1.ServiceAccount) error {
if err := controllerutil.SetControllerReference(cr, serviceAccount, r.scheme); err != nil {
return err
}
serviceAccountFound := &corev1.ServiceAccount{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: serviceAccount.Name, Namespace: serviceAccount.Namespace}, serviceAccountFound)
if err != nil && errors.IsNotFound(err) {
logrus.Infof("Creating a new object: %s, name: %s", serviceAccount.Kind, serviceAccount.Name)
err = r.client.Create(context.TODO(), serviceAccount)
if err != nil {
logrus.Errorf("Failed to create %s %s: %s", serviceAccount.Name, serviceAccount.Kind, err)
return err
}
return nil
} else if err != nil {
return err
}
return nil
}
func (r *ReconcileChe) CreateNewRole(instance *orgv1.CheCluster, role *rbac.Role) error {
if err := controllerutil.SetControllerReference(instance, role, r.scheme); err != nil {
return err
}
roleFound := &rbac.Role{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, roleFound)
if err != nil && errors.IsNotFound(err) {
logrus.Infof("Creating a new object: %s, name: %s", role.Kind, role.Name)
err = r.client.Create(context.TODO(), role)
if err != nil {
logrus.Errorf("Failed to create %s %s: %s", role.Name, role.Kind, err)
return err
}
return nil
} else if err != nil {
logrus.Errorf("An error occurred: %s", err)
return err
}
return nil
}
func (r *ReconcileChe) CreateNewIngress(instance *orgv1.CheCluster, ingress *v1beta1.Ingress) error {
if err := controllerutil.SetControllerReference(instance, ingress, r.scheme); err != nil {
logrus.Errorf("An error occurred %s", err)
return err
}
ingressFound := &v1beta1.Ingress{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: ingress.Name, Namespace: ingress.Namespace}, ingressFound)
if err != nil && errors.IsNotFound(err) {
logrus.Infof("Creating a new object %s, name: %s", ingress.Kind, ingress.Name)
if err := r.client.Create(context.TODO(), ingress); err != nil {
logrus.Errorf("Failed to create %s %s: %s", ingress.Kind, ingress.Name, err)
return err
}
return nil
} else if err != nil {
logrus.Errorf("An error occurred %s", err)
return err
}
return nil
}
func (r *ReconcileChe) CreateNewRoute(instance *orgv1.CheCluster, route *routev1.Route) error {
if err := controllerutil.SetControllerReference(instance, route, r.scheme); err != nil {
logrus.Errorf("An error occurred %s", err)
return err
}
routeFound := &routev1.Route{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: route.Name, Namespace: route.Namespace}, routeFound)
if err != nil && errors.IsNotFound(err) {
logrus.Infof("Creating a new object %s, name: %s", route.Kind, route.Name)
err = r.client.Create(context.TODO(), route)
if err != nil {
logrus.Errorf("Failed to create %s %s: %s", route.Kind, route.Name, err)
return err
}
// Route created successfully - don't requeue
return nil
} else if err != nil {
logrus.Errorf("An error occurred %s", err)
return err
}
return nil
}
func (r *ReconcileChe) CreateNewSecret(instance *orgv1.CheCluster, secret *corev1.Secret) error {
if err := controllerutil.SetControllerReference(instance, secret, r.scheme); err != nil {
logrus.Errorf("An error occurred: %s", err)
return err
}
deploymentFound := &corev1.Secret{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, deploymentFound)
if err != nil && errors.IsNotFound(err) {
logrus.Infof("Creating a new object: %s, name: %s", secret.Kind, secret.Name)
err = r.client.Create(context.TODO(), secret)
if err != nil {
logrus.Errorf("Failed to create %s %s: %s", secret.Kind, secret.Name, err)
return err
}
return nil
} else if err != nil {
logrus.Errorf("An error occurred: %s", err)
return err
}
return nil
}
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) {
logrus.Infof("Creating a new object: %s, name: %s", oAuthClient.Kind, oAuthClient.Name)
err = r.client.Create(context.TODO(), oAuthClient)
if err != nil {
logrus.Errorf("Failed to create %s %s: %s", oAuthClient.Kind, oAuthClient.Name, err)
return err
}
return nil
} else if err != nil {
logrus.Errorf("An error occurred: %s", err)
return err
}
return nil
}
// CreateService creates a service with a given name, port, selector and labels
func (r *ReconcileChe) CreateService(cr *orgv1.CheCluster, name string, labels map[string]string, portName string, portNumber int32) error {
service := deploy.NewService(cr, name, labels, portName, portNumber)
if err := controllerutil.SetControllerReference(cr, service, r.scheme); err != nil {
logrus.Errorf("An error occurred %s", err)
return err
}
serviceFound := &corev1.Service{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: service.Name, Namespace: service.Namespace}, serviceFound)
if err != nil && errors.IsNotFound(err) {
logrus.Infof("Creating a new object %s, name: %s", service.Kind, service.Name)
err = r.client.Create(context.TODO(), service)
if err != nil {
logrus.Errorf("Failed to create %s %s: %s", service.Kind, service.Name)
return err
}
return nil
} else if err != nil {
logrus.Errorf("An error occurred %s", err)
return err
}
return nil
}
func (r *ReconcileChe) CreatePVC(instance *orgv1.CheCluster, pvc *corev1.PersistentVolumeClaim) error {
// Set CheCluster instance as the owner and controller
if err := controllerutil.SetControllerReference(instance, pvc, r.scheme); err != nil {
return err
}
pvcFound := &corev1.PersistentVolumeClaim{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: pvc.Name, Namespace: pvc.Namespace}, pvcFound)
if err != nil && errors.IsNotFound(err) {
logrus.Infof("Creating a new object %s, name: %s", pvc.Kind, pvc.Name)
err = r.client.Create(context.TODO(), pvc)
if err != nil {
return err
}
return nil
} else if err != nil {
return err
}
return nil
}
func (r *ReconcileChe) CreateNewRoleBinding(instance *orgv1.CheCluster, roleBinding *rbac.RoleBinding) error {
if err := controllerutil.SetControllerReference(instance, roleBinding, r.scheme); err != nil {
return err
}
roleBindingFound := &rbac.RoleBinding{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: roleBinding.Name, Namespace: roleBinding.Namespace}, roleBindingFound)
if err != nil && errors.IsNotFound(err) {
logrus.Infof("Creating a new object: %s, name: %s", roleBinding.Kind, roleBinding.Name)
err = r.client.Create(context.TODO(), roleBinding)
if err != nil {
logrus.Errorf("Failed to create %s %s: %s", roleBinding.Name, roleBinding.Kind, err)
return err
}
return nil
} else if err != nil {
logrus.Errorf("An error occurred: %s", err)
return err
}
return nil
}
func (r *ReconcileChe) CreateIdentityProviderItems(instance *orgv1.CheCluster, request reconcile.Request, cheFlavor string, keycloakDeploymentName string) (err error) {
tests := r.tests
keycloakAdminPassword := instance.Spec.Auth.KeycloakAdminPassword
oAuthClientName := instance.Spec.Auth.OauthClientName
if len(oAuthClientName) < 1 {
oAuthClientName = instance.Name + "-openshift-identity-provider-" + strings.ToLower(util.GeneratePasswd(6))
instance.Spec.Auth.OauthClientName = oAuthClientName
if err := r.UpdateCheCRSpec(instance, "oAuthClient name", oAuthClientName); err != nil {
return err
}
}
oauthSecret := instance.Spec.Auth.OauthSecret
if len(oauthSecret) < 1 {
oauthSecret = util.GeneratePasswd(12)
instance.Spec.Auth.OauthSecret = oauthSecret
if err := r.UpdateCheCRSpec(instance, "oAuthC secret name", oauthSecret); err != nil {
return err
}
}
keycloakURL := instance.Spec.Auth.KeycloakURL
keycloakRealm := util.GetValue(instance.Spec.Auth.KeycloakRealm, cheFlavor)
oAuthClient := deploy.NewOAuthClient(oAuthClientName, oauthSecret, keycloakURL, keycloakRealm)
if err := r.CreateNewOauthClient(instance, oAuthClient); err != nil {
return err
}
if !tests {
openShiftIdentityProviderCommand := deploy.GetOpenShiftIdentityProviderProvisionCommand(instance, oAuthClientName, oauthSecret, keycloakAdminPassword)
podToExec, err := k8sclient.GetDeploymentPod(keycloakDeploymentName, instance.Namespace)
if err != nil {
logrus.Errorf("Failed to retrieve pod name. Further exec will fail")
return err
}
provisioned := ExecIntoPod(podToExec, openShiftIdentityProviderCommand, "create OpenShift identity provider", instance.Namespace)
if provisioned {
for {
instance.Status.OpenShiftoAuthProvisioned = true
if err := r.UpdateCheCRStatus(instance, "status: provisioned with OpenShift identity provider", "true"); err != nil {
instance, _ = r.GetCR(request)
} else {
break
}
}
}
return nil
}
return nil
}
func (r *ReconcileChe) GenerateAndSaveFields(instance *orgv1.CheCluster, request reconcile.Request) (err error) {
chePostgresPassword := util.GetValue(instance.Spec.Database.ChePostgresPassword, util.GeneratePasswd(12))
if len(instance.Spec.Database.ChePostgresPassword) < 1 {
instance.Spec.Database.ChePostgresPassword = chePostgresPassword
if err := r.UpdateCheCRSpec(instance, "auto-generated CheCluster DB password", "password-hidden"); err != nil {
return err
}
}
keycloakPostgresPassword := util.GetValue(instance.Spec.Auth.KeycloakPostgresPassword, util.GeneratePasswd(12))
if len(instance.Spec.Auth.KeycloakPostgresPassword) < 1 {
instance.Spec.Auth.KeycloakPostgresPassword = keycloakPostgresPassword
if err := r.UpdateCheCRSpec(instance, "auto-generated Keycloak DB password", "password-hidden"); err != nil {
return err
}
}
if len(instance.Spec.Auth.KeycloakAdminPassword) < 1 {
keycloakAdminPassword := util.GetValue(instance.Spec.Auth.KeycloakAdminPassword, util.GeneratePasswd(12))
instance.Spec.Auth.KeycloakAdminPassword = keycloakAdminPassword
if err := r.UpdateCheCRSpec(instance, "Keycloak admin password", "password hidden"); err != nil {
return err
}
}
if len(instance.Spec.Auth.KeycloakAdminUserName) < 1 {
keycloakAdminUserName := util.GetValue(instance.Spec.Auth.KeycloakAdminUserName, "admin")
instance.Spec.Auth.KeycloakAdminUserName = keycloakAdminUserName
if err := r.UpdateCheCRSpec(instance, "Keycloak admin username", "password hidden"); err != nil {
return err
}
}
chePostgresUser := util.GetValue(instance.Spec.Database.ChePostgresUser, "pgche")
if len(instance.Spec.Database.ChePostgresUser) < 1 {
instance.Spec.Database.ChePostgresUser = chePostgresUser
if err := r.UpdateCheCRSpec(instance, "Postgres User", chePostgresUser); err != nil {
return err
}
}
chePostgresDb := util.GetValue(instance.Spec.Database.ChePostgresDb, "dbche")
if len(instance.Spec.Database.ChePostgresDb) < 1 {
instance.Spec.Database.ChePostgresDb = chePostgresDb
if err := r.UpdateCheCRSpec(instance, "Postgres DB", chePostgresDb); err != nil {
return err
}
}
chePostgresHostName := util.GetValue(instance.Spec.Database.ChePostgresDBHostname, deploy.DefaultChePostgresHostName)
if len(instance.Spec.Database.ChePostgresDBHostname) < 1 {
instance.Spec.Database.ChePostgresDBHostname = chePostgresHostName
if err := r.UpdateCheCRSpec(instance, "Postgres hostname", chePostgresHostName); err != nil {
return err
}
}
chePostgresPort := util.GetValue(instance.Spec.Database.ChePostgresPort, deploy.DefaultChePostgresPort)
if len(instance.Spec.Database.ChePostgresPort) < 1 {
instance.Spec.Database.ChePostgresPort = chePostgresPort
if err := r.UpdateCheCRSpec(instance, "Postgres port", chePostgresPort); err != nil {
return err
}
}
cheFlavor := util.GetValue(instance.Spec.Server.CheFlavor, deploy.DefaultCheFlavor)
if len(instance.Spec.Server.CheFlavor) < 1 {
instance.Spec.Server.CheFlavor = cheFlavor
if err := r.UpdateCheCRSpec(instance, "installation flavor", cheFlavor); err != nil {
return err
}
}
defaultPostgresImage := deploy.DefaultPostgresUpstreamImage
if cheFlavor == "codeready" {
defaultPostgresImage = deploy.DefaultPostgresImage
}
postgresImage := util.GetValue(instance.Spec.Database.PostgresImage, defaultPostgresImage)
if len(instance.Spec.Database.PostgresImage) < 1 {
instance.Spec.Database.PostgresImage = postgresImage
if err := r.UpdateCheCRSpec(instance, "DB image:tag", postgresImage); err != nil {
return err
}
}
defaultKeycloakImage := deploy.DefaultKeycloakUpstreamImage
if cheFlavor == "codeready" {
defaultKeycloakImage = deploy.DefaultKeycloakImage
}
keycloakImage := util.GetValue(instance.Spec.Auth.KeycloakImage, defaultKeycloakImage)
if len(instance.Spec.Auth.KeycloakImage) < 1 {
instance.Spec.Auth.KeycloakImage = keycloakImage
if err := r.UpdateCheCRSpec(instance, "Keycloak image:tag", keycloakImage); err != nil {
return err
}
}
keycloakRealm := util.GetValue(instance.Spec.Auth.KeycloakRealm, cheFlavor)
if len(instance.Spec.Auth.KeycloakRealm) < 1 {
instance.Spec.Auth.KeycloakRealm = keycloakRealm
if err := r.UpdateCheCRSpec(instance, "Keycloak realm", keycloakRealm); err != nil {
return err
}
}
keycloakClientId := util.GetValue(instance.Spec.Auth.KeycloakClientId, cheFlavor+"-public")
if len(instance.Spec.Auth.KeycloakClientId) < 1 {
instance.Spec.Auth.KeycloakClientId = keycloakClientId
if err := r.UpdateCheCRSpec(instance, "Keycloak client ID", keycloakClientId); err != nil {
return err
}
}
pluginRegistryUrl := util.GetValue(instance.Spec.Server.PluginRegistryUrl, deploy.DefaultPluginRegistryUrl)
if len(instance.Spec.Server.PluginRegistryUrl) < 1 {
instance.Spec.Server.PluginRegistryUrl = pluginRegistryUrl
if err := r.UpdateCheCRSpec(instance, "plugin registry URL", pluginRegistryUrl); err != nil {
return err
}
}
cheLogLevel := util.GetValue(instance.Spec.Server.CheLogLevel, deploy.DefaultCheLogLevel)
if len(instance.Spec.Server.CheLogLevel) < 1 {
instance.Spec.Server.CheLogLevel = cheLogLevel
if err := r.UpdateCheCRSpec(instance, "log level", cheLogLevel); err != nil {
return err
}
}
cheDebug := util.GetValue(instance.Spec.Server.CheDebug, deploy.DefaultCheDebug)
if len(instance.Spec.Server.CheDebug) < 1 {
instance.Spec.Server.CheDebug = cheDebug
if err := r.UpdateCheCRSpec(instance, "debug", cheDebug); err != nil {
return err
}
}
pvcStrategy := util.GetValue(instance.Spec.Storage.PvcStrategy, deploy.DefaultPvcStrategy)
if len(instance.Spec.Storage.PvcStrategy) < 1 {
instance.Spec.Storage.PvcStrategy = pvcStrategy
if err := r.UpdateCheCRSpec(instance, "pvc strategy", pvcStrategy); err != nil {
return err
}
}
pvcClaimSize := util.GetValue(instance.Spec.Storage.PvcClaimSize, deploy.DefaultPvcClaimSize)
if len(instance.Spec.Storage.PvcClaimSize) < 1 {
instance.Spec.Storage.PvcClaimSize = pvcClaimSize
if err := r.UpdateCheCRSpec(instance, "pvc claim size", pvcClaimSize); err != nil {
return err
}
}
pvcJobsImage := util.GetValue(instance.Spec.Storage.PvcJobsImage, deploy.DefaultPvcJobsImage)
if len(instance.Spec.Storage.PvcJobsImage) < 1 {
instance.Spec.Storage.PvcJobsImage = pvcJobsImage
if err := r.UpdateCheCRSpec(instance, "pvc jobs image", pvcJobsImage); err != nil {
return err
}
}
return nil
}

111
pkg/controller/che/exec.go Normal file
View File

@ -0,0 +1,111 @@
//
// 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
//
package che
import (
"bytes"
"fmt"
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/deploy"
"github.com/sirupsen/logrus"
"io"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/remotecommand"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
func ExecIntoPod(podName string, provisionCommand string, reason string, ns string) (provisioned bool) {
command := []string{"/bin/bash", "-c", provisionCommand}
logrus.Infof("Running exec to %s in pod %s", reason, podName)
// print std if operator is run in debug mode (TODO)
_, stderr, err := k8sclient.RunExec(command, podName, ns)
if err != nil {
logrus.Errorf("Error exec'ing into pod: %v: , command: %s", err, command)
logrus.Errorf(stderr)
return false
}
logrus.Info("Exec successfully completed")
return true
}
func (cl *k8s) RunExec(command []string, podName, namespace string) (string, string, error) {
req := cl.clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec")
req.VersionedParams(&corev1.PodExecOptions{
Command: command,
Stdin: false,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
cfg, _ := config.GetConfig()
exec, err := remotecommand.NewSPDYExecutor(cfg, "POST", req.URL())
if err != nil {
return "", "", fmt.Errorf("error while creating executor: %v", err)
}
var stdout, stderr bytes.Buffer
var stdin io.Reader
err = exec.Stream(remotecommand.StreamOptions{
Stdin: stdin,
Stdout: &stdout,
Stderr: &stderr,
Tty: false,
})
if err != nil {
return stdout.String(), stderr.String(), err
}
return stdout.String(), stderr.String(), nil
}
func (r *ReconcileChe) CreateKyecloakResources(instance *orgv1.CheCluster, request reconcile.Request, deploymentName string) (err error) {
cheHost := instance.Spec.Server.CheHost
keycloakProvisionCommand := deploy.GetKeycloakProvisionCommand(instance, cheHost)
podToExec, err := k8sclient.GetDeploymentPod(deploymentName, instance.Namespace)
if err != nil {
logrus.Errorf("Failed to retrieve pod name. Further exec will fail")
}
provisioned := ExecIntoPod(podToExec, keycloakProvisionCommand, "create realm, client and user", instance.Namespace)
if provisioned {
instance, err := r.GetCR(request)
if err != nil {
if errors.IsNotFound(err) {
logrus.Errorf("CR %s not found: %s", instance.Name, err)
return err
}
logrus.Errorf("Error when getting %s CR: %s", instance.Name, err)
return err
}
for {
instance.Status.KeycloakProvisoned = true
if err := r.UpdateCheCRStatus(instance, "status: provisioned with Keycloak", "true"); err != nil {
instance, _ = r.GetCR(request)
} else {
break
}
}
return nil
}
return err
}

78
pkg/controller/che/get.go Normal file
View File

@ -0,0 +1,78 @@
//
// 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
//
package che
import (
"context"
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
routev1 "github.com/openshift/api/route/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) {
deployment = &appsv1.Deployment{}
err = r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.Namespace}, deployment)
if err != nil {
logrus.Errorf("Failed to get %s deployment: %s", name, err)
return nil, err
}
return deployment, nil
}
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 {
logrus.Errorf("Failed to get %s ingress: %s", name, err)
return nil
}
return ingress
}
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 {
logrus.Errorf("Failed to get %s route: %s", name, err)
return nil
}
return route
}
func (r *ReconcileChe) GetEffectiveConfigMap(instance *orgv1.CheCluster, name string) (configMap *corev1.ConfigMap) {
configMap = &corev1.ConfigMap{}
err := r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: instance.Namespace}, configMap)
if err != nil {
logrus.Errorf("Failed to get %s route: %s", name, err)
return nil
}
return configMap
}
func (r *ReconcileChe) GetCR(request reconcile.Request) (instance *orgv1.CheCluster, err error) {
instance = &orgv1.CheCluster{}
err = r.client.Get(context.TODO(), request.NamespacedName, instance)
if err != nil {
logrus.Errorf("Failed to get %s CR: %s", instance.Name, err)
return nil, err
}
return instance, nil
}

View File

@ -0,0 +1,263 @@
//
// 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
//
package che
import (
"bytes"
"github.com/eclipse/che-operator/pkg/util"
"github.com/sirupsen/logrus"
"io"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
type k8s struct {
clientset kubernetes.Interface
}
func GetK8Client() *k8s {
tests := util.IsTestMode()
if !tests {
cfg, err := config.GetConfig()
if err != nil {
logrus.Errorf(err.Error())
}
client := k8s{}
client.clientset, err = kubernetes.NewForConfig(cfg)
if err != nil {
logrus.Errorf(err.Error())
return nil
}
return &client
}
return nil
}
// GetPostgresStatus waits for pvc.status.phase to be Bound
func (cl *k8s) GetPostgresStatus(pvc *corev1.PersistentVolumeClaim, ns string) {
var timeout int64 = 10
listOptions := metav1.ListOptions{
FieldSelector: fields.OneTermEqualSelector("metadata.name", pvc.Name).String(),
TimeoutSeconds: &timeout,
}
watcher, err := cl.clientset.CoreV1().PersistentVolumeClaims(ns).Watch(listOptions)
if err != nil {
log.Error(err, "An error occurred")
}
ch := watcher.ResultChan()
logrus.Infof("Waiting for PVC %s to be bound. Default timeout: %v seconds", pvc.Name, timeout)
for event := range ch {
pvc, ok := event.Object.(*corev1.PersistentVolumeClaim)
if !ok {
log.Error(err, "Unexpected type")
}
// check before watching in case pvc has been already bound
postgresPvc, err := cl.clientset.CoreV1().PersistentVolumeClaims(ns).Get(pvc.Name, metav1.GetOptions{})
if err != nil {
logrus.Errorf("Failed to get %s pvc: %s", postgresPvc.Name, err)
break
}
if postgresPvc.Status.Phase == "Bound" {
volumeName := postgresPvc.Spec.VolumeName
logrus.Infof("PVC %s successfully bound to volume %s", postgresPvc.Name, volumeName)
break
}
switch event.Type {
case watch.Error:
watcher.Stop()
case watch.Modified:
if postgresPvc.Status.Phase == "Bound" {
volumeName := postgresPvc.Spec.VolumeName
logrus.Infof("PVC %s successfully bound to volume %s", postgresPvc.Name, volumeName)
watcher.Stop()
}
}
}
postgresPvc, err := cl.clientset.CoreV1().PersistentVolumeClaims(ns).Get(pvc.Name, metav1.GetOptions{})
if postgresPvc.Status.Phase != "Bound" {
currentPvcPhase := postgresPvc.Status.Phase
logrus.Warnf("Timeout waiting for a PVC %s to be bound. Current phase is %s", postgresPvc.Name, currentPvcPhase)
logrus.Warn("Sometimes PVC can be bound only when the first consumer is created")
}
}
func (cl *k8s) GetDeploymentRollingUpdateStatus(name string, ns string) {
api := cl.clientset.AppsV1()
var timeout int64 = 420
listOptions := metav1.ListOptions{
FieldSelector: fields.OneTermEqualSelector("metadata.name", name).String(),
TimeoutSeconds: &timeout,
}
watcher, err := api.Deployments(ns).Watch(listOptions)
if err != nil {
log.Error(err, "An error occurred")
}
ch := watcher.ResultChan()
logrus.Infof("Waiting for a successful rolling update of deployment %s. Default timeout: %v seconds", name, timeout)
for event := range ch {
dc, ok := event.Object.(*appsv1.Deployment)
if !ok {
log.Error(err, "Unexpected type")
}
// check before watching in case the deployment is already scaled to 1
deployment, err := cl.clientset.AppsV1().Deployments(ns).Get(name, metav1.GetOptions{})
if err != nil {
logrus.Errorf("Failed to get %s deployment: %s", deployment.Name, err)
break
}
if deployment.Status.Replicas == 1 {
logrus.Infof("Rolling update of '%s' deployment finished", deployment.Name)
break
}
switch event.Type {
case watch.Error:
watcher.Stop()
case watch.Modified:
if dc.Status.Replicas == 1 {
logrus.Infof("Rolling update of '%s' deployment finished", deployment.Name)
watcher.Stop()
}
}
}
}
// GetDeploymentStatus listens to deployment events and checks replicas once MODIFIED event is received
func (cl *k8s) GetDeploymentStatus(name string, ns string) (scaled bool) {
api := cl.clientset.AppsV1()
var timeout int64 = 420
listOptions := metav1.ListOptions{
FieldSelector: fields.OneTermEqualSelector("metadata.name", name).String(),
TimeoutSeconds: &timeout,
}
watcher, err := api.Deployments(ns).Watch(listOptions)
if err != nil {
log.Error(err, "An error occurred")
}
ch := watcher.ResultChan()
logrus.Infof("Waiting for deployment %s. Default timeout: %v seconds", name, timeout)
for event := range ch {
dc, ok := event.Object.(*appsv1.Deployment)
if !ok {
log.Error(err, "Unexpected type")
}
// check before watching in case the deployment is already scaled to 1
deployment, err := cl.clientset.AppsV1().Deployments(ns).Get(name, metav1.GetOptions{})
if err != nil {
logrus.Errorf("Failed to get %s deployment: %s", deployment.Name, err)
return false
}
if deployment.Status.AvailableReplicas == 1 {
logrus.Infof("Deployment '%s' successfully scaled to %v", deployment.Name, deployment.Status.AvailableReplicas)
return true
}
switch event.Type {
case watch.Error:
watcher.Stop()
case watch.Modified:
if dc.Status.AvailableReplicas == 1 {
logrus.Infof("Deployment '%s' successfully scaled to %v", deployment.Name, dc.Status.AvailableReplicas)
watcher.Stop()
return true
}
}
}
dc, _ := cl.clientset.AppsV1().Deployments(ns).Get(name, metav1.GetOptions{})
if dc.Status.AvailableReplicas != 1 {
logrus.Errorf("Failed to verify a successful %s deployment", name)
eventList := cl.GetEvents(name, ns).Items
for i := range eventList {
logrus.Errorf("Event message: %v", eventList[i].Message)
}
deploymentPod, err := cl.GetDeploymentPod(name, ns)
if err != nil {
return false
}
cl.GetPodLogs(deploymentPod, ns)
logrus.Errorf("Command to get deployment logs: kubectl logs deployment/%s -n=%s", name, ns)
logrus.Errorf("Get k8s events: kubectl get events "+
"--field-selector "+
"involvedObject.name=$(kubectl get pods -l=component=%s -n=%s"+
" -o=jsonpath='{.items[0].metadata.name}') -n=%s", name, ns, ns)
return false
}
return true
}
// GetEvents returns a list of events filtered by involvedObject
func (cl *k8s) GetEvents(deploymentName string, ns string) (list *corev1.EventList) {
eventListOptions := metav1.ListOptions{FieldSelector: fields.OneTermEqualSelector("involvedObject.fieldPath", "spec.containers{"+deploymentName+"}").String()}
deploymentEvents, _ := cl.clientset.CoreV1().Events(ns).List(eventListOptions)
return deploymentEvents
}
// GetLogs prints stderr or stdout from a selected pod. Log size is capped at 60000 bytes
func (cl *k8s) GetPodLogs(podName string, ns string) () {
var limitBytes int64 = 60000
req := cl.clientset.CoreV1().Pods(ns).GetLogs(podName, &corev1.PodLogOptions{LimitBytes: &limitBytes},
)
readCloser, err := req.Stream()
if err != nil {
logrus.Errorf("Pod error log: %v", err)
} else {
buf := new(bytes.Buffer)
_, err = io.Copy(buf, readCloser)
logrus.Infof("Pod log: %v", buf.String())
}
}
//GetDeploymentPod queries all pods is a selected namespace by LabelSelector
func (cl *k8s) GetDeploymentPod(name string, ns string) (podName string, err error) {
api := cl.clientset.CoreV1()
listOptions := metav1.ListOptions{
LabelSelector: "component=" + name,
}
podList, _ := api.Pods(ns).List(listOptions)
podListItems := podList.Items
if len(podListItems) == 0 {
logrus.Errorf("Failed to find pod to exec into. List of pods: %s", podListItems)
return "", err
}
// expecting only one pod to be there so, taking the first one
// todo maybe add a unique label to deployments?
podName = podListItems[0].Name
return podName, nil
}
// GetDefaultRouterCert retrieves secret with OpenShift router certificate and extracts it
// The cert is then used to create self-signed-certificate secret consumed by CheCluster server and workspaces
func (cl *k8s) GetDefaultRouterCert(ns string) (crt []byte, err error) {
options := metav1.GetOptions{}
secret, err := cl.clientset.CoreV1().Secrets(ns).Get("router-certs-default", options)
if err != nil {
// in 3.11 it's default namespace and router-certs secret
secret, err = cl.clientset.CoreV1().Secrets("default").Get("router-certs", options)
if err != nil {
logrus.Errorf("Failed to get a secret in both namespace %s and default: %s", ns, err)
return nil, err
}
}
secretData := secret.Data
crt = secretData["tls.crt"]
return crt, nil
}

View File

@ -0,0 +1,104 @@
//
// 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
//
package che
import (
"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
"testing"
)
var (
fakeK8s = fakeClientSet()
namespace = "eclipse-che"
)
func fakeClientSet() *k8s {
client := k8s{}
client.clientset = fake.NewSimpleClientset()
return &client
}
func TestGetDeploymentPod(t *testing.T) {
// create a fake pod
_, err := fakeK8s.clientset.CoreV1().Pods(namespace).Create(&corev1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-pod",
Namespace: namespace,
Labels: map[string]string{
"component": "postgres",
},
},
})
if err != nil {
panic(err)
}
pod, err := fakeK8s.GetDeploymentPod("postgres", namespace)
if err != nil {
t.Errorf("Failed to det deployment pod: %s", err)
}
if len(pod) == 0 {
t.Errorf("Test failed. No pods found by label")
}
logrus.Infof("Test passed. Pod %s found", pod)
}
func TestGetEvents(t *testing.T) {
// fire up an event with fake-pod as involvedObject
message := "This is a fake event about a fake pod"
_, err := fakeK8s.clientset.CoreV1().Events(namespace).Create(&corev1.Event{
TypeMeta: metav1.TypeMeta{
Kind: "Event",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-event",
Namespace: "eclipse-che",
},
InvolvedObject: corev1.ObjectReference{
FieldPath: "spec.containers{fake-pod}",
Kind: "Pod",
},
Message: message,
Reason: "Testing event filtering",
Type: "Normal",
})
if err != nil {
panic(err)
}
events := fakeK8s.GetEvents("fake-pod", namespace)
fakePodEvents := events.Items
if len(fakePodEvents) == 0 {
logrus.Fatal("Test failed No events found")
} else {
logrus.Infof("Test passed. Found %v event", len(fakePodEvents))
}
// test if event message matches
fakePodEventMessage := events.Items[0].Message
if len(fakePodEventMessage) != len(message) {
t.Errorf("Test failed. Message to be received: %s, but got %s ", message, fakePodEventMessage)
} else {
logrus.Infof("Test passed. Expected event message: %s. Received event message %s", message, fakePodEventMessage)
}
}

View File

@ -0,0 +1,72 @@
//
// 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
//
package che
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/deploy"
"github.com/eclipse/che-operator/pkg/util"
"github.com/sirupsen/logrus"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
const (
AvailableStatus = "Available"
UnavailableStatus = "Unavailable"
RollingUpdateInProgressStatus = "Available: Rolling update in progress"
)
func (r *ReconcileChe) SetCheAvailableStatus(instance *orgv1.CheCluster, request reconcile.Request, protocol string, cheHost string) (err error) {
cheFlavor := util.GetValue(instance.Spec.Server.CheFlavor, deploy.DefaultCheFlavor)
name := "Eclipse Che"
if cheFlavor == "codeready" {
name = "CodeReady Workspaces"
}
keycloakURL := instance.Spec.Auth.KeycloakURL
instance.Status.KeycloakURL = keycloakURL
if err := r.UpdateCheCRStatus(instance, "Keycloak URL status", keycloakURL); err != nil {
instance, _ = r.GetCR(request)
return err
}
instance.Status.CheClusterRunning = AvailableStatus
if err := r.UpdateCheCRStatus(instance, "status: "+name+" server", AvailableStatus); err != nil {
instance, _ = r.GetCR(request)
return err
}
instance.Status.CheURL = protocol + "://" + cheHost
if err := r.UpdateCheCRStatus(instance, name+" server URL", protocol+"://"+cheHost); err != nil {
instance, _ = r.GetCR(request)
return err
}
logrus.Infof(name+" is now available at: %s://%s", protocol, cheHost)
return nil
}
func (r *ReconcileChe) SetCheUnavailableStatus(instance *orgv1.CheCluster, request reconcile.Request) (err error) {
instance.Status.CheClusterRunning = UnavailableStatus
if err:= r.UpdateCheCRStatus(instance, "status: Che API", UnavailableStatus); err != nil {
instance, _ = r.GetCR(request)
return err
}
return nil
}
func (r *ReconcileChe) SetCheRollingUpdateStatus(instance *orgv1.CheCluster, request reconcile.Request) (err error){
instance.Status.CheClusterRunning = RollingUpdateInProgressStatus
if err:= r.UpdateCheCRStatus(instance, "status", RollingUpdateInProgressStatus); err != nil {
instance, _ = r.GetCR(request)
return err
}
return nil
}

View File

@ -0,0 +1,219 @@
//
// 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
//
package che
import (
"context"
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/deploy"
"github.com/eclipse/che-operator/pkg/util"
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"
"k8s.io/apimachinery/pkg/types"
"reflect"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
func (r *ReconcileChe) UpdateCheCRStatus(instance *orgv1.CheCluster, updatedField string, value string) (err error) {
logrus.Infof("Updating %s CR with %s: %s", instance.Name, updatedField, value)
err = r.client.Status().Update(context.TODO(), instance)
if err != nil {
logrus.Warnf("Failed to update %s CR. Fetching the latest CR version: %s", instance.Name, err)
return err
}
logrus.Infof("Custom resource %s updated", instance.Name)
return nil
}
func (r *ReconcileChe) UpdateCheCRSpec1(instance *orgv1.CheCluster, updatedField string, value string) (err error) {
logrus.Infof("Updating %s CR with %s: %s", instance.Name, updatedField, value)
if err = r.client.Update(context.TODO(), instance); err != nil {
return err
}
logrus.Infof("Custom resource %s updated", instance.Name)
return nil
}
func (r *ReconcileChe) UpdateCheCRSpec(instance *orgv1.CheCluster, updatedField string, value string) (err error) {
logrus.Infof("Updating %s CR with %s: %s", instance.Name, updatedField, value)
err = r.client.Update(context.TODO(), instance)
if err != nil {
logrus.Warnf("Failed to update %s CR: %s", instance.Name, err)
return err
}
logrus.Infof("Custom resource %s updated", instance.Name)
return nil
}
// UpdateConfigMap compares existing ConfigMap retrieved from API with a current ConfigMap
// i.e. ConfigMap.Data consuming current CheCluster.Spec fields, and updates an existing
// ConfigMap with up-to-date .Data.
func (r *ReconcileChe) UpdateConfigMap(instance *orgv1.CheCluster) (updated bool, err error) {
activeConfigMap := &corev1.ConfigMap{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: "che", Namespace: instance.Namespace}, activeConfigMap); err != nil {
logrus.Errorf("ConfigMap %s not found: %s", activeConfigMap.Name, err)
}
// compare ConfigMap.Data with current CM on server
cheEnv := deploy.GetConfigMapData(instance)
cm := deploy.NewCheConfigMap(instance, cheEnv)
equal := reflect.DeepEqual(cm.Data, activeConfigMap.Data)
if !equal {
logrus.Infof("Updating %s ConfigMap", activeConfigMap.Name)
if err := controllerutil.SetControllerReference(instance, cm, r.scheme); err != nil {
logrus.Errorf("Failed to set OwnersReference for %s %s: %s", activeConfigMap.Kind, activeConfigMap.Name, err)
return false, err
}
if err := r.client.Update(context.TODO(), cm); err != nil {
logrus.Errorf("Failed to update %s %s: %s", activeConfigMap.Kind, activeConfigMap.Name, err)
return false, err
}
return true, nil
}
return false, nil
}
func (r *ReconcileChe) ReconcileTLSObjects(instance *orgv1.CheCluster, request reconcile.Request, cheFlavor string, tlsSupport bool, isOpenShift bool) (updated bool, err error) {
// reconcile ingresses
if !isOpenShift {
ingressDomain := instance.Spec.K8SOnly.IngressDomain
ingressStrategy := util.GetValue(instance.Spec.K8SOnly.IngressStrategy, deploy.DefaultIngressStrategy)
currentCheIngress := r.GetEffectiveIngress(instance, cheFlavor)
if currentCheIngress == nil {
return false, err
}
protocol := "http"
logrus.Infof("Deleting ingress %s", currentCheIngress.Name)
if err := r.client.Delete(context.TODO(), currentCheIngress); err != nil {
logrus.Errorf("Failed to delete %s ingress: %s", currentCheIngress.Name, err)
return false, err
}
cheIngress := deploy.NewIngress(instance, cheFlavor, "che-host", 8080)
if err := r.CreateNewIngress(instance, cheIngress); err != nil {
logrus.Errorf("Failed to create %s %s: %s", cheIngress.Name, cheIngress.Kind, err)
return false, err
}
currentKeycloakIngress := r.GetEffectiveIngress(instance, "keycloak")
if currentKeycloakIngress == nil {
return false, err
} else {
keycloakURL := protocol + "://" + ingressDomain
if ingressStrategy == "multi-host" {
keycloakURL = protocol + "://keycloak-" + instance.Namespace + "." + ingressDomain
}
instance.Spec.Auth.KeycloakURL = keycloakURL
if err := r.UpdateCheCRSpec(instance, "Keycloak URL", keycloakURL); err != nil {
return false, err
}
}
logrus.Infof("Deleting route %s", currentKeycloakIngress.Name)
if err := r.client.Delete(context.TODO(), currentKeycloakIngress); err != nil {
logrus.Errorf("Failed to delete %s ingress: %s", currentKeycloakIngress.Name, err)
return false, err
}
keycloakIngress := deploy.NewIngress(instance, "keycloak", "keycloak", 8080)
if err := r.CreateNewIngress(instance, keycloakIngress); err != nil {
logrus.Errorf("Failed to create Keycloak ingress: %s", err)
return false, err
}
return true, nil
}
protocol := "http"
currentCheRoute := r.GetEffectiveRoute(instance, cheFlavor)
if currentCheRoute == nil {
return false, err
}
logrus.Infof("Deleting route %s", currentCheRoute.Name)
if err := r.client.Delete(context.TODO(), currentCheRoute); err != nil {
logrus.Errorf("Failed to delete %s route: %s", currentCheRoute.Name, err)
return false, err
}
cheRoute := deploy.NewRoute(instance, cheFlavor, "che-host")
if tlsSupport {
cheRoute = deploy.NewTlsRoute(instance, cheFlavor, "che-host")
protocol = "https"
}
if err := r.CreateNewRoute(instance, cheRoute); err != nil {
logrus.Errorf("Failed to create %s %s: %s", cheRoute.Name, cheRoute.Kind, err)
return false, err
}
currentKeycloakRoute := &routev1.Route{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: "keycloak", Namespace: instance.Namespace}, currentKeycloakRoute); err != nil {
logrus.Errorf("Failed to get %s route: %s", currentKeycloakRoute.Name, err)
return false, err
} else {
keycloakURL := currentKeycloakRoute.Spec.Host
instance.Spec.Auth.KeycloakURL = protocol + "://" + keycloakURL
if err := r.UpdateCheCRSpec(instance, "Keycloak URL", protocol+"://"+keycloakURL); err != nil {
return false, err
}
}
logrus.Infof("Deleting route %s", currentKeycloakRoute.Name)
if err := r.client.Delete(context.TODO(), currentKeycloakRoute); err != nil {
logrus.Errorf("Failed to delete %s route: %s", currentKeycloakRoute.Name, err)
return false, err
}
keycloakRoute := deploy.NewRoute(instance, "keycloak", "keycloak")
if tlsSupport {
keycloakRoute = deploy.NewTlsRoute(instance, "keycloak", "keycloak")
}
if err := r.CreateNewRoute(instance, keycloakRoute); err != nil {
logrus.Errorf("Failed to create Keycloak route: %s", err)
return false, err
}
return true, nil
}
func (r *ReconcileChe) ReconcileIdentityProvider(instance *orgv1.CheCluster) (deleted bool, err error) {
if instance.Spec.Auth.OpenShiftOauth == false && instance.Status.OpenShiftoAuthProvisioned == true {
keycloakAdminPassword := instance.Spec.Auth.KeycloakAdminPassword
keycloakDeployment := &appsv1.Deployment{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: "keycloak", Namespace: instance.Namespace}, keycloakDeployment); err != nil {
logrus.Errorf("Deployment %s not found: %s", keycloakDeployment.Name, err)
}
deleteOpenShiftIdentityProviderProvisionCommand := deploy.GetDeleteOpenShiftIdentityProviderProvisionCommand(instance, keycloakAdminPassword)
podToExec, err := k8sclient.GetDeploymentPod(keycloakDeployment.Name, instance.Namespace)
if err != nil {
logrus.Errorf("Failed to retrieve pod name. Further exec will fail")
}
provisioned := ExecIntoPod(podToExec, deleteOpenShiftIdentityProviderProvisionCommand, "delete OpenShift identity provider", instance.Namespace)
if provisioned {
oAuthClient := &oauth.OAuthClient{}
oAuthClientName := instance.Spec.Auth.OauthClientName
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: oAuthClientName, Namespace: ""}, oAuthClient); err != nil {
logrus.Errorf("%s %s not found: %s", oAuthClient.Name, err)
}
if err := r.client.Delete(context.TODO(), oAuthClient); err != nil {
logrus.Errorf("Failed to delete %s %s: %s", oAuthClient.Kind, oAuthClient.Name, err)
}
return true, nil
}
return false, err
}
return false, nil
}

View File

@ -0,0 +1,29 @@
//
// 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
//
package controller
import (
"sigs.k8s.io/controller-runtime/pkg/manager"
)
// AddToManagerFuncs is a list of functions to add all Controllers to the Manager
var AddToManagerFuncs []func(manager.Manager) error
// AddToManager adds all Controllers to the Manager
func AddToManager(m manager.Manager) error {
for _, f := range AddToManagerFuncs {
if err := f(m); err != nil {
return err
}
}
return nil
}

216
pkg/deploy/che_configmap.go Normal file
View File

@ -0,0 +1,216 @@
//
// 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
//
package deploy
import (
"encoding/json"
"fmt"
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/util"
"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func addMap(a map[string]string, b map[string]string) {
for k, v := range b {
a[k] = v
}
}
type CheConfigMap struct {
CheHost string `json:"CHE_HOST"`
CheMultiUser string `json:"CHE_MULTIUSER"`
ChePort string `json:"CHE_PORT"`
CheApi string `json:"CHE_API"`
CheWebSocketEndpoint string `json:"CHE_WEBSOCKET_ENDPOINT"`
CheDebugServer string `json:"CHE_DEBUG_SERVER"`
CheInfrastructureActive string `json:"CHE_INFRASTRUCTURE_ACTIVE"`
BootstrapperBinaryUrl string `json:"CHE_INFRA_KUBERNETES_BOOTSTRAPPER_BINARY__URL"`
WorkspacesNamespace string `json:"CHE_INFRA_OPENSHIFT_PROJECT"`
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"`
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"`
DatabaseURL string `json:"CHE_JDBC_URL"`
DbUserName string `json:"CHE_JDBC_USERNAME"`
DbPassword string `json:"CHE_JDBC_PASSWORD"`
CheLogLevel string `json:"CHE_LOG_LEVEL"`
KeycloakURL string `json:"CHE_KEYCLOAK_AUTH__SERVER__URL"`
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"`
WorkspaceProxyJavaOpts string `json:"CHE_WORKSPACE_HTTP__PROXY__JAVA__OPTIONS"`
WorkspaceHttpProxy string `json:"CHE_WORKSPACE_HTTP__PROXY"`
WorkspaceHttpsProxy string `json:"CHE_WORKSPACE_HTTPS__PROXY"`
WorkspaceNoProxy string `json:"CHE_WORKSPACE_NO__PROXY"`
PluginRegistryUrl string `json:"CHE_WORKSPACE_PLUGIN__REGISTRY__URL"`
WebSocketEndpointMinor string `json:"CHE_WEBSOCKET_ENDPOINT__MINOR"`
}
// 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) {
cheHost := cr.Spec.Server.CheHost
keycloakURL := cr.Spec.Auth.KeycloakURL
isOpenShift, err := util.DetectOpenShift()
if err != nil {
logrus.Errorf("Failed to get current infra: %s", err)
}
cheFlavor := util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor)
chePostgresPassword := cr.Spec.Database.ChePostgresPassword
infra := "kubernetes"
if isOpenShift {
infra = "openshift"
}
workspacesNamespace := cr.Namespace
tls := "false"
openShiftIdentityProviderId := "NULL"
openshiftOAuth := cr.Spec.Auth.OpenShiftOauth
if openshiftOAuth && isOpenShift {
workspacesNamespace = ""
openShiftIdentityProviderId = "openshift-v3"
}
tlsSupport := cr.Spec.Server.TlsSupport
protocol := "http"
wsprotocol := "ws"
if tlsSupport {
protocol = "https"
wsprotocol = "wss"
tls = "true"
}
proxyJavaOpts := ""
proxyUser := cr.Spec.Server.ProxyUser
proxyPassword := cr.Spec.Server.ProxyPassword
if len(cr.Spec.Server.ProxyURL) > 1 {
proxyJavaOpts = util.GenerateProxyJavaOpts(cr.Spec.Server.ProxyURL, cr.Spec.Server.ProxyPort, cr.Spec.Server.NonProxyHosts, proxyUser, proxyPassword)
}
cheWorkspaceHttpProxy := ""
cheWorkspaceNoProxy := ""
if len(cr.Spec.Server.ProxyURL) > 1 {
cheWorkspaceHttpProxy, cheWorkspaceNoProxy = util.GenerateProxyEnvs(cr.Spec.Server.ProxyURL, cr.Spec.Server.ProxyPort, cr.Spec.Server.NonProxyHosts, proxyUser, proxyPassword)
}
ingressDomain := cr.Spec.K8SOnly.IngressDomain
tlsSecretName := cr.Spec.K8SOnly.TlsSecretName
pvcStrategy := util.GetValue(cr.Spec.Storage.PvcStrategy, DefaultPvcStrategy)
pvcClaimSize := util.GetValue(cr.Spec.Storage.PvcClaimSize, DefaultPvcClaimSize)
pvcJobsImage := util.GetValue(cr.Spec.Storage.PvcJobsImage, DefaultPvcJobsImage)
preCreateSubPaths := "true"
if !cr.Spec.Storage.PreCreateSubPaths {
preCreateSubPaths = "false"
}
chePostgresHostName := util.GetValue(cr.Spec.Database.ChePostgresDBHostname, DefaultChePostgresHostName)
chePostgresUser := util.GetValue(cr.Spec.Database.ChePostgresUser, DefaultChePostgresUser)
chePostgresPort := util.GetValue(cr.Spec.Database.ChePostgresPort, DefaultChePostgresPort)
chePostgresDb := util.GetValue(cr.Spec.Database.ChePostgresDb, DefaultChePostgresDb)
keycloakRealm := util.GetValue(cr.Spec.Auth.KeycloakRealm, cheFlavor)
keycloakClientId := util.GetValue(cr.Spec.Auth.KeycloakClientId, cheFlavor+"-public")
ingressStrategy := util.GetValue(cr.Spec.K8SOnly.IngressStrategy, DefaultIngressStrategy)
ingressClass := util.GetValue(cr.Spec.K8SOnly.IngressClass, DefaultIngressClass)
pluginRegistryUrl := util.GetValue(cr.Spec.Server.PluginRegistryUrl, DefaultPluginRegistryUrl)
cheLogLevel := util.GetValue(cr.Spec.Server.CheLogLevel, DefaultCheLogLevel)
cheDebug := util.GetValue(cr.Spec.Server.CheDebug, DefaultCheDebug)
data := &CheConfigMap{
CheMultiUser: "true",
CheHost: cheHost,
ChePort: "8080",
CheApi: protocol + "://" + cheHost + "/api",
CheWebSocketEndpoint: wsprotocol + "://" + cheHost + "/api/websocket",
WebSocketEndpointMinor: wsprotocol + "://" + cheHost + "/api/websocket-minor",
CheDebugServer: cheDebug,
CheInfrastructureActive: infra,
BootstrapperBinaryUrl: protocol + "://" + cheHost + "/agent-binaries/linux_amd64/bootstrapper/bootstrapper",
WorkspacesNamespace: workspacesNamespace,
PvcStrategy: pvcStrategy,
PvcClaimSize: pvcClaimSize,
PvcJobsImage: pvcJobsImage,
PreCreateSubPaths: preCreateSubPaths,
TlsSupport: tls,
K8STrustCerts: tls,
DatabaseURL: "jdbc:postgresql://" + chePostgresHostName + ":" + chePostgresPort + "/" + chePostgresDb,
DbUserName: chePostgresUser,
DbPassword: chePostgresPassword,
CheLogLevel: cheLogLevel,
KeycloakURL: keycloakURL + "/auth",
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,
}
out, err := json.Marshal(data)
if err != nil {
fmt.Println(err)
}
err = json.Unmarshal(out, &cheEnv)
// k8s specific envs
k8sCheEnv := map[string]string{
"CHE_INFRA_KUBERNETES_POD_SECURITY__CONTEXT_FS__GROUP": "0",
"CHE_INFRA_KUBERNETES_POD_SECURITY__CONTEXT_RUN__AS__USER": "0",
"CHE_INFRA_KUBERNETES_NAMESPACE": workspacesNamespace,
"CHE_INFRA_KUBERNETES_INGRESS_DOMAIN": ingressDomain,
"CHE_INFRA_KUBERNETES_SERVER__STRATEGY": ingressStrategy,
"CHE_INFRA_KUBERNETES_TLS__SECRET": tlsSecretName,
"CHE_INFRA_KUBERNETES_INGRESS_ANNOTATIONS__JSON": "{\"kubernetes.io/ingress.class\": " + ingressClass + ", \"nginx.ingress.kubernetes.io/rewrite-target\": \"/\",\"nginx.ingress.kubernetes.io/ssl-redirect\": " + tls + ",\"nginx.ingress.kubernetes.io/proxy-connect-timeout\": \"3600\",\"nginx.ingress.kubernetes.io/proxy-read-timeout\": \"3600\"}",
}
if !isOpenShift {
addMap(cheEnv, k8sCheEnv)
}
return cheEnv
}
func NewCheConfigMap(cr *orgv1.CheCluster, cheEnv map[string]string) *corev1.ConfigMap {
labels := GetLabels(cr, util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor))
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "che",
Namespace: cr.Namespace,
Labels: labels,
},
Data: cheEnv,
}
}

View File

@ -0,0 +1,39 @@
//
// 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
//
package deploy
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"strings"
"testing"
)
func TestNewCheConfigMap(t *testing.T) {
// since all values are retrieved from CR or auto-generated
// some of them are explicitly set for this test to avoid using fake kube
// and creating a CR with all spec fields pre-populated
cr := &orgv1.CheCluster{}
cr.Spec.Server.CheHost = "myhostname.com"
cr.Spec.Server.TlsSupport = true
cr.Spec.Auth.OpenShiftOauth = true
cheEnv := GetConfigMapData(cr)
testCm := NewCheConfigMap(cr, cheEnv)
identityProvider := testCm.Data["CHE_INFRA_OPENSHIFT_OAUTH__IDENTITY__PROVIDER"]
protocol := strings.Split(testCm.Data["CHE_INFRA_KUBERNETES_BOOTSTRAPPER_BINARY__URL"], "://")[0]
if identityProvider != "openshift-v3" {
t.Errorf("Test failed. Expecting identity provider to be 'openshift-v3' while got '%s'", identityProvider)
}
if protocol != "https" {
t.Errorf("Test failed. Expecting 'https' protocol, got '%s'", protocol)
}
}

37
pkg/deploy/defaults.go Normal file
View File

@ -0,0 +1,37 @@
//
// 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
//
package deploy
const (
DefaultCheServerImageRepo = "eclipse/che-server"
DefaultCodeReadyServerImageRepo = "registry.access.redhat.com/codeready-workspaces/server"
DefaultCheServerImageTag = "6.19.0"
DefaultCodeReadyServerImageTag = "1.0"
DefaultCheFlavor = "che"
DefaultChePostgresUser = "pgche"
DefaultChePostgresHostName = "postgres"
DefaultChePostgresPort = "5432"
DefaultChePostgresDb = "dbche"
DefaultPvcStrategy = "common"
DefaultPvcClaimSize = "1Gi"
DefaultIngressStrategy = "multi-host"
DefaultIngressClass = "nginx"
DefaultPluginRegistryUrl = "https://che-plugin-registry.openshift.io"
DefaultKeycloakAdminUserName = "admin"
DefaultCheLogLevel = "INFO"
DefaultCheDebug = "false"
DefaultPvcJobsImage = "registry.access.redhat.com/rhel7-minimal:7.6-154"
DefaultPostgresImage = "registry.access.redhat.com/rhscl/postgresql-96-rhel7:1-25"
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"
)

View File

@ -0,0 +1,151 @@
//
// 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
//
package deploy
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/util"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
func NewCheDeployment(cr *orgv1.CheCluster, cheImage string, cheTag string, cmRevision string) *appsv1.Deployment {
labels := GetLabels(cr, util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor))
optionalEnv := true
cheFlavor := util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor)
return &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "apps/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: cheFlavor,
Namespace: cr.Namespace,
Labels: labels,
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{MatchLabels: labels},
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RollingUpdateDeploymentStrategyType,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: corev1.PodSpec{
ServiceAccountName: "che",
Containers: []corev1.Container{
{
Name: cheFlavor,
ImagePullPolicy: corev1.PullIfNotPresent,
Image: cheImage + ":" + cheTag,
Ports: []corev1.ContainerPort{
{
Name: "http",
ContainerPort: 8080,
Protocol: "TCP",
},
{
Name: "http-debug",
ContainerPort: 8000,
Protocol: "TCP",
},
{
Name: "jgroups-ping",
ContainerPort: 8888,
Protocol: "TCP",
},
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("512Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("1Gi"),
},
},
ReadinessProbe: &corev1.Probe{
Handler: corev1.Handler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/api/system/state",
Port: intstr.IntOrString{
Type: intstr.Int,
IntVal: int32(8080),
},
Scheme: corev1.URISchemeHTTP,
},
},
InitialDelaySeconds: 25,
FailureThreshold: 5,
TimeoutSeconds: 5,
},
LivenessProbe: &corev1.Probe{
Handler: corev1.Handler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/api/system/state",
Port: intstr.IntOrString{
Type: intstr.Int,
IntVal: int32(8080),
},
Scheme: corev1.URISchemeHTTP,
},
},
InitialDelaySeconds: 50,
FailureThreshold: 3,
TimeoutSeconds: 3,
},
EnvFrom: []corev1.EnvFromSource{
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{Name: "che"},
},
},
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{Name: "custom"},
Optional: &optionalEnv,
},
},
},
Env: []corev1.EnvVar{
{
Name: "CM_REVISION",
Value: cmRevision,
},
{
Name: "OPENSHIFT_KUBE_PING_NAMESPACE",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.namespace"}},
},
{
Name: "CHE_SELF__SIGNED__CERT",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
Key: "ca.crt",
LocalObjectReference: corev1.LocalObjectReference{
Name: "self-signed-certificate",
},
Optional: &optionalEnv,
},
},
},
}},
},
},
},
},
}
}

View File

@ -0,0 +1,217 @@
//
// 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
//
package deploy
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/util"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
func NewKeycloakDeployment(cr *orgv1.CheCluster, keycloakPostgresPassword string, keycloakAdminPassword string, cheFlavor string) *appsv1.Deployment {
keycloakName := "keycloak"
labels := GetLabels(cr, keycloakName)
keycloakImage := util.GetValue(cr.Spec.Auth.KeycloakImage, DefaultKeycloakImage)
trustpass := util.GeneratePasswd(12)
jbossDir := "/opt/eap"
if cheFlavor == "che" {
// writable dir in the upstream Keycloak image
jbossDir = "/scripts"
}
// add crt to Java trust store so that Keycloak can connect to k8s API
addCertToTrustStoreCommand := "keytool -importcert -alias HOSTDOMAIN" +
" -keystore " + jbossDir +"/openshift.jks" +
" -file /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -storepass " + trustpass + " -noprompt" +
" && keytool -importkeystore -srckeystore $JAVA_HOME/jre/lib/security/cacerts" +
" -destkeystore " + jbossDir + "/openshift.jks" +
" -srcstorepass changeit -deststorepass " + trustpass
startCommand := "sed -i 's/WILDCARD/ANY/g' /opt/eap/bin/launch/keycloak-spi.sh && /opt/eap/bin/openshift-launch.sh -b 0.0.0.0"
// upstream Keycloak has a bit different mechanism of adding jks
changeConfigCommand := "echo -e \"embed-server --server-config=standalone.xml --std-out=echo \n" +
"/subsystem=keycloak-server/spi=truststore/:add \n" +
"/subsystem=keycloak-server/spi=truststore/provider=file/:add(properties={file => " +
"\"" + jbossDir + "/openshift.jks\", password => \"" + trustpass + "\", disabled => \"false\" },enabled=true) \n" +
"stop-embedded-server\" > /scripts/add_openshift_certificate.cli && " +
"/opt/jboss/keycloak/bin/jboss-cli.sh --file=/scripts/add_openshift_certificate.cli"
keycloakAdminUserName := util.GetValue(cr.Spec.Auth.KeycloakAdminUserName, DefaultKeycloakAdminUserName)
keycloakEnv := []corev1.EnvVar{
{
Name: "PROXY_ADDRESS_FORWARDING",
Value: "true",
},
{
Name: "KEYCLOAK_USER",
Value: keycloakAdminUserName,
},
{
Name: "KEYCLOAK_PASSWORD",
Value: keycloakAdminPassword,
},
{
Name: "DB_VENDOR",
Value: "POSTGRES",
},
{
Name: "POSTGRES_PORT_5432_TCP_ADDR",
Value: "postgres",
},
{
Name: "POSTGRES_PORT_5432_TCP_PORT",
Value: "5432",
},
{
Name: "POSTGRES_DATABASE",
Value: "keycloak",
},
{
Name: "POSTGRES_USER",
Value: "keycloak",
},
{
Name: "POSTGRES_PASSWORD",
Value: keycloakPostgresPassword,
},
}
if cheFlavor == "codeready" {
keycloakEnv = []corev1.EnvVar{
{
Name: "PROXY_ADDRESS_FORWARDING",
Value: "true",
},
{
Name: "DB_SERVICE_PREFIX_MAPPING",
Value: "keycloak-postgresql=DB",
},
{
Name: "KEYCLOAK_POSTGRESQL_SERVICE_HOST",
Value: "postgres",
},
{
Name: "KEYCLOAK_POSTGRESQL_SERVICE_PORT",
Value: "5432",
},
{
Name: "DB_DATABASE",
Value: keycloakName,
},
{
Name: "DB_USERNAME",
Value: keycloakName,
},
{
Name: "DB_PASSWORD",
Value: keycloakPostgresPassword,
},
{
Name: "SSO_ADMIN_USERNAME",
Value: keycloakAdminUserName,
},
{
Name: "SSO_ADMIN_PASSWORD",
Value: keycloakAdminPassword,
},
{
Name: "DB_VENDOR",
Value: "POSTGRES",
},
{
Name: "SSO_TRUSTSTORE",
Value: "openshift.jks",
},
{
Name: "SSO_TRUSTSTORE_DIR",
Value: jbossDir,
},
{
Name: "SSO_TRUSTSTORE_PASSWORD",
Value: trustpass,
},
}
}
command := addCertToTrustStoreCommand + " && " + changeConfigCommand + " && /opt/jboss/docker-entrypoint.sh -b 0.0.0.0"
if cheFlavor == "codeready" {
command = addCertToTrustStoreCommand + " && " + startCommand
}
return &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "apps/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: keycloakName,
Namespace: cr.Namespace,
Labels: labels,
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{MatchLabels: labels},
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.RollingUpdateDeploymentStrategyType,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: keycloakName,
Image: keycloakImage,
ImagePullPolicy: corev1.PullIfNotPresent,
Command: []string{
"/bin/sh",
},
Args: []string{
"-c",
command,
},
Ports: []corev1.ContainerPort{
{
Name: keycloakName,
ContainerPort: 8080,
Protocol: "TCP",
},
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("512Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("2Gi"),
},
},
ReadinessProbe: &corev1.Probe{
Handler: corev1.Handler{
HTTPGet: &corev1.HTTPGetAction{
Path: "auth/js/keycloak.js",
Port: intstr.IntOrString{
Type: intstr.Int,
IntVal: int32(8080),
},
Scheme: corev1.URISchemeHTTP,
},
},
InitialDelaySeconds: 25,
FailureThreshold: 10,
TimeoutSeconds: 5,
},
Env: keycloakEnv,
},
},
},
},
},
}
}

View File

@ -0,0 +1,125 @@
//
// 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
//
package deploy
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/util"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func NewPostgresDeployment(cr *orgv1.CheCluster, chePostgresPassword string) *appsv1.Deployment {
chePostgresUser := util.GetValue(cr.Spec.Database.ChePostgresUser, "pgche")
chePostgresDb := util.GetValue(cr.Spec.Database.ChePostgresDb, "dbche")
postgresAdminPassword := util.GeneratePasswd(12)
postgresImage := util.GetValue(cr.Spec.Database.PostgresImage, DefaultPostgresImage)
name := "postgres"
labels := GetLabels(cr, name)
return &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
Kind: "Deployment",
APIVersion: "apps/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "postgres",
Namespace: cr.Namespace,
Labels: labels,
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{MatchLabels: labels},
Strategy: appsv1.DeploymentStrategy{
Type: appsv1.DeploymentStrategyType("Recreate"),
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: corev1.PodSpec{
Volumes: []corev1.Volume{
{
Name: name + "-data",
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: name + "-data",
},
},
},
},
Containers: []corev1.Container{
{
Name: name,
Image: postgresImage,
ImagePullPolicy: corev1.PullIfNotPresent,
Ports: []corev1.ContainerPort{
{
Name: name,
ContainerPort: 5432,
Protocol: "TCP",
},
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("512Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("1Gi"),
},
},
VolumeMounts: []corev1.VolumeMount{
{
Name: name + "-data",
MountPath: "/var/lib/pgsql/data",
},
},
ReadinessProbe: &corev1.Probe{
Handler: corev1.Handler{
Exec: &corev1.ExecAction{
Command: []string{
"/bin/sh",
"-i",
"-c",
"psql -h 127.0.0.1 -U " + chePostgresUser + " -q -d " + chePostgresDb + " -c 'SELECT 1'",
},
},
},
InitialDelaySeconds: 15,
FailureThreshold: 10,
SuccessThreshold: 1,
TimeoutSeconds: 5,
},
Env: []corev1.EnvVar{
{
Name: "POSTGRESQL_USER",
Value: chePostgresUser,
},
{
Name: "POSTGRESQL_PASSWORD",
Value: chePostgresPassword,
},
{
Name: "POSTGRESQL_DATABASE",
Value: chePostgresDb,
},
{
Name: "POSTGRESQL_ADMIN_PASSWORD",
Value: postgresAdminPassword,
},
}},
},
},
},
},
}
}

134
pkg/deploy/exec_commands.go Normal file
View File

@ -0,0 +1,134 @@
//
// 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
//
package deploy
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/util"
"github.com/sirupsen/logrus"
"io/ioutil"
"strings"
)
func GetPostgresProvisionCommand(cr *orgv1.CheCluster) (command string) {
chePostgresUser := util.GetValue(cr.Spec.Database.ChePostgresUser, DefaultChePostgresUser)
keycloakPostgresPassword := cr.Spec.Auth.KeycloakPostgresPassword
command = "OUT=$(psql postgres -tAc \"SELECT 1 FROM pg_roles WHERE rolname='keycloak'\"); " +
"if [ $OUT -eq 1 ]; then echo \"DB exists\"; exit 0; fi " +
"&& psql -c \"CREATE USER keycloak WITH PASSWORD '" + keycloakPostgresPassword + "'\" " +
"&& psql -c \"CREATE DATABASE keycloak\" " +
"&& psql -c \"GRANT ALL PRIVILEGES ON DATABASE keycloak TO keycloak\" " +
"&& psql -c \"ALTER USER " + chePostgresUser + " WITH SUPERUSER\""
return command
}
func GetKeycloakProvisionCommand(cr *orgv1.CheCluster, cheHost string) (command string) {
keycloakAdminUserName := util.GetValue(cr.Spec.Auth.KeycloakAdminUserName,"admin")
keycloakAdminPassword := util.GetValue(cr.Spec.Auth.KeycloakAdminPassword,"admin")
requiredActions := ""
updateAdminPassword := cr.Spec.Auth.UpdateAdminPassword
cheFlavor := util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor)
keycloakRealm := util.GetValue(cr.Spec.Auth.KeycloakRealm, cheFlavor)
keycloakClientId := util.GetValue(cr.Spec.Auth.KeycloakClientId, cheFlavor+"-public")
if updateAdminPassword {
requiredActions = "\"UPDATE_PASSWORD\""
}
file, err := ioutil.ReadFile("/tmp/keycloak_provision")
if err != nil {
logrus.Errorf("Failed to find keycloak entrypoint file %s", err)
}
keycloakTheme := "che"
realmDisplayName := "Eclipse Che"
script := "/opt/jboss/keycloak/bin/kcadm.sh"
if cheFlavor == "codeready" {
keycloakTheme = "rh-sso"
realmDisplayName = "CodeReady Workspaces"
script = "/opt/eap/bin/kcadm.sh"
}
str := string(file)
r := strings.NewReplacer("$script", script,
"$keycloakAdminUserName", keycloakAdminUserName,
"$keycloakAdminPassword", keycloakAdminPassword,
"$keycloakRealm", keycloakRealm,
"$realmDisplayName", realmDisplayName,
"$keycloakClientId", keycloakClientId,
"$keycloakTheme", keycloakTheme,
"$cheHost", cheHost,
"$requiredActions", requiredActions)
createRealmClientUserCommand := r.Replace(str)
command = createRealmClientUserCommand
if cheFlavor == "che" {
command = "cd /scripts && " +createRealmClientUserCommand
}
return command
}
func GetOpenShiftIdentityProviderProvisionCommand(cr *orgv1.CheCluster, oAuthClientName string, oauthSecret string, keycloakAdminPassword string) (command string) {
cheFlavor := util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor)
openShiftApiUrl, err := util.GetClusterPublicHostname()
if err != nil {
logrus.Errorf("Failed to auto-detect public OpenShift API URL. Configure it in Identity provider details page in Keycloak admin console: %s", err)
openShiftApiUrl = "RECPLACE_ME"
}
keycloakRealm := util.GetValue(cr.Spec.Auth.KeycloakRealm, cheFlavor)
keycloakAdminUserName := util.GetValue(cr.Spec.Auth.KeycloakAdminUserName, DefaultKeycloakAdminUserName)
script := "/opt/jboss/keycloak/bin/kcadm.sh"
if cheFlavor == "codeready" {
script = "/opt/eap/bin/kcadm.sh"
}
createOpenShiftIdentityProviderCommand :=
script + " config credentials --server http://0.0.0.0:8080/auth " +
"--realm master --user " + keycloakAdminUserName + " --password " + keycloakAdminPassword + " && " + script +
" get identity-provider/instances/openshift-v3 -r " + keycloakRealm + "; " +
"if [ $? -eq 0 ]; then echo \"Provider exists\"; exit 0; fi && " + script +
" create identity-provider/instances -r " + keycloakRealm +
" -s alias=openshift-v3 -s providerId=openshift-v3 -s enabled=true -s storeToken=true" +
" -s addReadTokenRoleOnCreate=true -s config.useJwksUrl=true" +
" -s config.clientId=" + oAuthClientName + " -s config.clientSecret=" + oauthSecret +
" -s config.baseUrl=" + openShiftApiUrl +
" -s config.defaultScope=user:full"
command = createOpenShiftIdentityProviderCommand
if cheFlavor == "che" {
command = "cd /scripts && " + createOpenShiftIdentityProviderCommand
}
return command
}
func GetDeleteOpenShiftIdentityProviderProvisionCommand(cr *orgv1.CheCluster, keycloakAdminPassword string) (command string) {
cheFlavor := util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor)
keycloakRealm := util.GetValue(cr.Spec.Auth.KeycloakRealm, cheFlavor)
keycloakAdminUserName := util.GetValue(cr.Spec.Auth.KeycloakAdminUserName, DefaultKeycloakAdminUserName)
script := "/opt/jboss/keycloak/bin/kcadm.sh"
if cheFlavor == "codeready" {
script = "/opt/eap/bin/kcadm.sh"
}
deleteOpenShiftIdentityProviderCommand :=
script + " config credentials --server http://0.0.0.0:8080/auth " +
"--realm master --user " + keycloakAdminUserName + " --password " + keycloakAdminPassword + " && " +
script + " delete identity-provider/instances/openshift-v3 -r " + keycloakRealm
command = deleteOpenShiftIdentityProviderCommand
if cheFlavor == "che" {
command = "cd /scripts && " + deleteOpenShiftIdentityProviderCommand
}
return command
}

101
pkg/deploy/ingress.go Normal file
View File

@ -0,0 +1,101 @@
//
// 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
//
package deploy
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/util"
"k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
func NewIngress(cr *orgv1.CheCluster, name string, serviceName string, port int) *v1beta1.Ingress {
tlsSupport := cr.Spec.Server.TlsSupport
ingressStrategy := cr.Spec.K8SOnly.IngressStrategy
if len(ingressStrategy) < 1 {
ingressStrategy = "multi-host"
}
ingressDomain := cr.Spec.K8SOnly.IngressDomain
ingressClass := util.GetValue(cr.Spec.K8SOnly.IngressClass, DefaultIngressClass)
labels := GetLabels(cr, util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor))
tlsSecretName := cr.Spec.K8SOnly.TlsSecretName
if name == "keycloak" {
labels = GetLabels(cr, name)
}
tls := "false"
if tlsSupport {
tls = "true"
}
host := ""
path := "/"
if name == "keycloak" && ingressStrategy != "multi-host" {
path = "/auth"
}
if ingressStrategy == "multi-host" {
host = name + "-" + cr.Namespace + "." + ingressDomain
} else if ingressStrategy == "single-host" {
host = ingressDomain
}
tlsIngress := []v1beta1.IngressTLS{}
if tlsSupport {
tlsIngress = []v1beta1.IngressTLS{
{
Hosts: []string{
ingressDomain,
},
SecretName: tlsSecretName,
},
}
}
return &v1beta1.Ingress{
TypeMeta: metav1.TypeMeta{
Kind: "Ingress",
APIVersion: v1beta1.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: cr.Namespace,
Labels: labels,
Annotations: map[string]string{
"kubernetes.io/ingress.class": ingressClass,
"nginx.ingress.kubernetes.io/proxy-read-timeout": "3600",
"nginx.ingress.kubernetes.io/proxy-connect-timeout": "3600",
"nginx.ingress.kubernetes.io/ssl-redirect": tls,
},
},
Spec: v1beta1.IngressSpec{
TLS: tlsIngress,
Rules: []v1beta1.IngressRule{
{
Host: host,
IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []v1beta1.HTTPIngressPath{
{
Backend: v1beta1.IngressBackend{
ServiceName: serviceName,
ServicePort: intstr.FromInt(port),
},
Path: path,
},
},
},
},
},
},
},
}
}

22
pkg/deploy/labels.go Normal file
View File

@ -0,0 +1,22 @@
//
// 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
//
package deploy
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
)
func GetLabels(cr *orgv1.CheCluster, component string) (labels map[string]string) {
cheFlavor := cr.Spec.Server.CheFlavor
labels = map[string]string{"app": cheFlavor, "component": component}
return labels
}

38
pkg/deploy/oauthclient.go Normal file
View File

@ -0,0 +1,38 @@
//
// 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
//
package deploy
import (
oauth "github.com/openshift/api/oauth/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func NewOAuthClient(name string, oauthSecret string, keycloakURL string, keycloakRealm string) *oauth.OAuthClient {
return &oauth.OAuthClient{
TypeMeta: metav1.TypeMeta{
Kind: "OAuthClient",
APIVersion: oauth.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: map[string]string{"app":"che"},
},
Secret: oauthSecret,
RedirectURIs: []string{
keycloakURL + "/auth/realms/" + keycloakRealm +"/broker/openshift-v3/endpoint",
},
GrantMethod: oauth.GrantHandlerPrompt,
}
}

47
pkg/deploy/pvc.go Normal file
View File

@ -0,0 +1,47 @@
//
// 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
//
package deploy
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func NewPvc(cr *orgv1.CheCluster, name string, pvcClaimSize string, labels map[string]string) *corev1.PersistentVolumeClaim {
//value := true
return &corev1.PersistentVolumeClaim{
TypeMeta: metav1.TypeMeta{
Kind: "PersistentVolumeClaim",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
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),
},
},
},
}
}

46
pkg/deploy/role.go Normal file
View File

@ -0,0 +1,46 @@
//
// 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
//
package deploy
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/util"
rbac "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func NewRole(cr *orgv1.CheCluster, name string, resources []string, verbs []string) *rbac.Role {
labels := GetLabels(cr, util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor))
return &rbac.Role{
TypeMeta: metav1.TypeMeta{
Kind: "Role",
APIVersion: rbac.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: cr.Namespace,
Labels: labels,
},
Rules: []rbac.PolicyRule{
{
APIGroups: []string{
"",
},
Resources: resources,
Verbs: verbs,
},
},
}
}

47
pkg/deploy/rolebinding.go Normal file
View File

@ -0,0 +1,47 @@
//
// 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
//
package deploy
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/util"
rbac "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func NewRoleBinding(cr *orgv1.CheCluster, name string, serviceAccountName string, roleName string, roleKind string) *rbac.RoleBinding {
labels := GetLabels(cr, util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor))
return &rbac.RoleBinding{
TypeMeta: metav1.TypeMeta{
Kind: "RoleBinding",
APIVersion: rbac.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: cr.Namespace,
Labels: labels,
},
Subjects: []rbac.Subject{
{
Kind: rbac.ServiceAccountKind,
Name: serviceAccountName,
Namespace: cr.Namespace,
},
},
RoleRef: rbac.RoleRef{
Name: roleName,
Kind: roleKind,
APIGroup: "rbac.authorization.k8s.io",
},
}
}

73
pkg/deploy/route.go Normal file
View File

@ -0,0 +1,73 @@
//
// 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
//
package deploy
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/util"
routev1 "github.com/openshift/api/route/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func NewRoute(cr *orgv1.CheCluster, name string, serviceName string) *routev1.Route {
labels := GetLabels(cr, util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor))
if name == "keycloak" {
labels = GetLabels(cr, name)
}
return &routev1.Route{
TypeMeta: metav1.TypeMeta{
Kind: "Route",
APIVersion: routev1.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: cr.Namespace,
Labels: labels,
},
Spec: routev1.RouteSpec{
To: routev1.RouteTargetReference{
Kind: "Service",
Name: serviceName,
},
},
}
}
func NewTlsRoute(cr *orgv1.CheCluster, name string, serviceName string) *routev1.Route {
labels := GetLabels(cr, util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor))
if name == "keycloak" {
labels = GetLabels(cr, name)
}
return &routev1.Route{
TypeMeta: metav1.TypeMeta{
Kind: "Route",
APIVersion: routev1.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: cr.Namespace,
Labels: labels,
},
Spec: routev1.RouteSpec{
To: routev1.RouteTargetReference{
Kind: "Service",
Name: serviceName,
},
TLS: &routev1.TLSConfig{
InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect,
Termination: routev1.TLSTerminationEdge,
},
},
}
}

37
pkg/deploy/secret.go Normal file
View File

@ -0,0 +1,37 @@
//
// 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
//
package deploy
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/util"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func NewSecret(cr *orgv1.CheCluster, name string, crt []byte) *corev1.Secret {
labels := GetLabels(cr, util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor))
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: cr.Namespace,
Labels: labels,
},
Data: map[string][]byte{
"ca.crt": crt,
},
}
}

45
pkg/deploy/service.go Normal file
View File

@ -0,0 +1,45 @@
//
// 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
//
package deploy
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func NewService(cr *orgv1.CheCluster, name string, labels map[string]string, portName string, portNumber int32) *corev1.Service {
return &corev1.Service{
TypeMeta: metav1.TypeMeta{
Kind: "Service",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: cr.Namespace,
Labels: labels,
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Name: portName,
Port: portNumber,
Protocol: "TCP",
},
},
Selector: labels,
},
}
}

View File

@ -0,0 +1,34 @@
//
// 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
//
package deploy
import (
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/eclipse/che-operator/pkg/util"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func NewServiceAccount(cr *orgv1.CheCluster, name string) *corev1.ServiceAccount {
labels := GetLabels(cr, util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor))
return &corev1.ServiceAccount{
TypeMeta: metav1.TypeMeta{
Kind: "ServiceAccount",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: cr.Namespace,
Labels: labels,
},
}
}

143
pkg/util/util.go Normal file
View File

@ -0,0 +1,143 @@
//
// 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
//
package util
import (
"crypto/tls"
"encoding/json"
"github.com/sirupsen/logrus"
"io/ioutil"
"k8s.io/client-go/discovery"
"math/rand"
"net/http"
"os"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"strings"
"time"
)
func GeneratePasswd(stringLength int) (passwd string) {
rand.Seed(time.Now().UnixNano())
chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz" +
"0123456789")
length := stringLength
buf := make([]rune, length)
for i := range buf {
buf[i] = chars[rand.Intn(len(chars))]
}
passwd = string(buf)
return passwd
}
func DetectOpenShift() (bool, error) {
tests := IsTestMode()
if !tests {
kubeconfig, err := config.GetConfig()
if err != nil {
return false, err
}
discoveryClient, err := discovery.NewDiscoveryClientForConfig(kubeconfig)
if err != nil {
return false, err
}
apiList, err := discoveryClient.ServerGroups()
if err != nil {
return false, err
}
apiGroups := apiList.Groups
for i := 0; i < len(apiGroups); i++ {
if apiGroups[i].Name == "route.openshift.io" {
return true, nil
}
}
return false, nil
}
return true, nil
}
func GetValue(key string, defaultValue string) (value string) {
value = key
if len(key) < 1 {
value = defaultValue
}
return value
}
func IsTestMode() (isTesting bool) {
testMode := os.Getenv("MOCK_API")
if len(testMode) == 0 {
return false
}
return true
}
// GetClusterPublicHostname is a hacky way to get OpenShift API public DNS/IP
// to be used in OpenShift oAuth provider as baseURL
func GetClusterPublicHostname() (hostname string, err error) {
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
client := &http.Client{}
kubeApi := os.Getenv("KUBERNETES_PORT_443_TCP_ADDR")
url := "https://" + kubeApi + "/.well-known/oauth-authorization-server"
req, err := http.NewRequest("GET", url, nil)
resp, err := client.Do(req)
if err != nil {
logrus.Errorf("An error occurred when getting API public hostname: %s", err)
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logrus.Errorf("An error occurred when getting API public hostname: %s", err)
return "", err
}
var jsonData map[string]interface{}
err = json.Unmarshal(body, &jsonData)
if err != nil {
logrus.Errorf("An error occurred when unmarshalling: %s")
return "", err
}
hostname = jsonData["issuer"].(string)
return hostname, nil
}
func GenerateProxyJavaOpts(proxyURL string, proxyPort string, nonProxyHosts string, proxyUser string, proxyPassword string) (javaOpts string) {
proxyHost := strings.TrimLeft(proxyURL, "https://")
proxyUserPassword := ""
if len(proxyUser) > 1 && len(proxyPassword) > 1 {
proxyUserPassword =
" -Dhttp.proxyUser=" + proxyUser + " -Dhttp.proxyPassword=" + proxyPassword +
" -Dhttps.proxyUser=" + proxyUser + " -Dhttps.proxyPassword=" + proxyPassword
}
javaOpts =
" -Dhttp.proxyHost=" + proxyHost + " -Dhttp.proxyPort=" + proxyPort +
" -Dhttps.proxyHost=" + proxyHost + " -Dhttps.proxyPort=" + proxyPort +
" -Dhttp.nonProxyHosts='" + nonProxyHosts + "|172.30.0.1'" + proxyUserPassword
return javaOpts
}
func GenerateProxyEnvs(proxyHost string, proxyPort string, nonProxyHosts string, proxyUser string, proxyPassword string) (proxyUrl string, noProxy string) {
proxyUrl = proxyHost + ":" + proxyPort
if len(proxyUser) > 1 && len(proxyPassword) > 1 {
protocol := strings.Split(proxyHost, "://")[0]
host := strings.Split(proxyHost, "://")[1]
proxyUrl = protocol + "://" + proxyUser + ":" + proxyPassword + "@" + host + ":" + proxyPort
}
noProxy = strings.Replace(nonProxyHosts, "|", ",", -1)
return proxyUrl, noProxy
}

99
pkg/util/util_test.go Normal file
View File

@ -0,0 +1,99 @@
//
// 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
//
package util
import (
"reflect"
"testing"
)
const (
proxyHost = "https://myproxy.com"
proxyPort = "1234"
nonProxyHosts = "localhost|myhost.com"
proxyUser = "user"
proxyPassword = "password"
expectedProxyURLWithUsernamePassword = "https://user:password@myproxy.com:1234"
expectedProxyURLWithoutUsernamePassword = "https://myproxy.com:1234"
expectedNoProxy = "localhost,myhost.com"
)
func TestGenerateProxyEnvs(t *testing.T) {
proxyUrl, noProxy := GenerateProxyEnvs(proxyHost, proxyPort, nonProxyHosts, proxyUser, proxyPassword)
if !reflect.DeepEqual(proxyUrl, expectedProxyURLWithUsernamePassword) {
t.Errorf("Test failed. Expected %s but got %s", expectedProxyURLWithUsernamePassword, proxyUrl)
}
if !reflect.DeepEqual(noProxy, expectedNoProxy) {
t.Errorf("Test failed. Expected %s but got %s", expectedNoProxy, noProxy)
}
proxyUrl, _ = GenerateProxyEnvs(proxyHost, proxyPort, nonProxyHosts, "", proxyPassword)
if !reflect.DeepEqual(proxyUrl, expectedProxyURLWithoutUsernamePassword) {
t.Errorf("Test failed. Expected %s but got %s", expectedProxyURLWithoutUsernamePassword, proxyUrl)
}
}
func TestGenerateProxyJavaOpts(t *testing.T) {
javaOpts := GenerateProxyJavaOpts(proxyHost, proxyPort, nonProxyHosts, proxyUser, proxyPassword)
expectedJavaOpts := " -Dhttp.proxyHost=myproxy.com -Dhttp.proxyPort=1234 -Dhttps.proxyHost=myproxy.com " +
"-Dhttps.proxyPort=1234 -Dhttp.nonProxyHosts='localhost|myhost.com|172.30.0.1' -Dhttp.proxyUser=user " +
"-Dhttp.proxyPassword=password -Dhttps.proxyUser=user -Dhttps.proxyPassword=password"
if !reflect.DeepEqual(javaOpts,expectedJavaOpts) {
t.Errorf("Test failed. Expected '%s' but got '%s'", expectedJavaOpts, javaOpts)
}
javaOpts = GenerateProxyJavaOpts(proxyHost, proxyPort, nonProxyHosts, "", proxyPassword)
expectedJavaOptsWithoutUsernamePassword := " -Dhttp.proxyHost=myproxy.com -Dhttp.proxyPort=1234 -Dhttps.proxyHost=myproxy.com " +
"-Dhttps.proxyPort=1234 -Dhttp.nonProxyHosts='localhost|myhost.com|172.30.0.1'"
if !reflect.DeepEqual(javaOpts ,expectedJavaOptsWithoutUsernamePassword) {
t.Errorf("Test failed. Expected '%s' but got '%s'", expectedJavaOptsWithoutUsernamePassword, javaOpts)
}
}
func TestGeneratePasswd(t *testing.T) {
chars := 12
passwd := GeneratePasswd(chars)
expectedCharsNumber := 12
if !reflect.DeepEqual(len(passwd), expectedCharsNumber) {
t.Errorf("Test failed. Expected %v chars, got %v chars", expectedCharsNumber, len(passwd))
}
passwd1 := GeneratePasswd(12)
if reflect.DeepEqual (passwd, passwd1) {
t.Errorf("Test failed. Passwords are identical, %s: %s", passwd, passwd1)
}
}
func TestGetValue(t *testing.T) {
key := "myvalue"
defaultValue := "myDefaultValue"
var1 := GetValue(key, defaultValue)
var2 := GetValue("", defaultValue)
if !reflect.DeepEqual(var1, key) {
t.Errorf("Test failed. Expected '%s', but got '%s'", key, var1)
}
if !reflect.DeepEqual(var2, defaultValue) {
t.Errorf("Test failed. Expected '%s', but got '%s'", var2, defaultValue)
}
}

5
version/version.go Normal file
View File

@ -0,0 +1,5 @@
package version
var (
Version = "0.0.1"
)