Update to 0.5.0. Use CRDs
parent
bc59de083a
commit
046cb9ab92
|
|
@ -107,5 +107,7 @@ tags
|
|||
!.vscode/extensions.json
|
||||
.history
|
||||
|
||||
build/
|
||||
bin/
|
||||
|
||||
# End of https://www.gitignore.io/api/go,vim,emacs,visualstudiocode
|
||||
|
|
|
|||
10
Dockerfile
10
Dockerfile
|
|
@ -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"]
|
||||
|
|
@ -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
192
README.md
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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: ''
|
||||
|
||||
|
||||
|
||||
|
|
@ -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: {}
|
||||
|
|
@ -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
|
|
@ -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: {}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
packageName: codeready-workspaces
|
||||
channels:
|
||||
- name: final
|
||||
currentCSV: crwoperator.v1.1.0
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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:
|
||||
- '*'
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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{})
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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}
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -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",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package version
|
||||
|
||||
var (
|
||||
Version = "0.0.1"
|
||||
)
|
||||
Loading…
Reference in New Issue