CHE-15493: <username>-che as default namespace (#166)

* Set  <username>-che as default namespace for Che workspaces

Signed-off-by: Oleksandr Andriienko <oandriie@redhat.com>
Co-authored-by: Michal Vala <mvala@redhat.com>
Co-authored-by: Anatolii Bazko <abazko@redhat.com>
pull/667/head
Oleksandr Andriienko 2021-02-08 09:56:53 +02:00 committed by GitHub
parent 619c9d943b
commit 4ee509e08b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 3022 additions and 298 deletions

View File

@ -154,10 +154,35 @@ spec:
CHE_MULTIUSER: "false"
```
```
```bash
$ chectl server:update -n <ECLIPSE-CHE-NAMESPACE> --che-operator-cr-patch-yaml <PATH_TO_CR_PATCH_YAML>
```
### Workspace namespace strategy
Workspace namespace strategy defines default namespace in which user's workspaces are created.
It's possible to use <username>, <userid> and <workspaceid> placeholders (e.g.: che-workspace-<username>).
In that case, new namespace will be created for each user (or workspace).
For OpenShift infrastructure this property used to specify Project (instead of namespace conception).
To set up namespace workspace strategy use command line:
```bash
$ kubectl patch checluster/eclipse-che -n <ECLIPSE-CHE-NAMESPACE> --type=merge -p '{"spec":{"server": {"customCheProperties": {"CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT": "che-workspace-<username>"}}}}'
```
or create `cr-patch.yaml` and use it with chectl:
```yaml
spec:
server:
customCheProperties:
CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT: "che-workspace-<username>"
```
```bash
$ chectl server:update -n <ECLIPSE-CHE-NAMESPACE> --che-operator-cr-patch-yaml <PATH_TO_CR_PATCH_YAML>
### OpenShift OAuth
OpenShift clusters include a built-in OAuth server. Che operator supports this authentication method. It's enabled by default.
@ -176,7 +201,7 @@ spec:
openShiftoAuth: false
```
```
```bash
$ chectl server:update -n <ECLIPSE-CHE-NAMESPACE> --che-operator-cr-patch-yaml <PATH_TO_CR_PATCH_YAML>
```
@ -196,7 +221,7 @@ spec:
tlsSupport: false
```
```
```bash
$ chectl server:update -n <ECLIPSE-CHE-NAMESPACE> --che-operator-cr-patch-yaml <PATH_TO_CR_PATCH_YAML>
```
@ -292,6 +317,7 @@ Run the VSCode task: `Format che-operator code` or use the terminal:
```bash
$ go fmt ./...
```
> Notice: if you don't have redhat subscription, use public image registry.access.redhat.com/devtools/go-toolset-rhel7:latest
### Update golang dependencies

View File

@ -15,10 +15,12 @@ import (
"context"
"flag"
"fmt"
"os"
"runtime"
image_puller_api "github.com/che-incubator/kubernetes-image-puller-operator/pkg/apis"
"github.com/eclipse/che-operator/cmd/manager/signal"
"github.com/eclipse/che-operator/pkg/util"
operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
@ -37,7 +39,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
)
var (
@ -170,7 +171,9 @@ func main() {
logrus.Info("Starting the Cmd")
// Start the Cmd
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
period := signal.GetTerminationGracePeriodSeconds(mgr.GetAPIReader(), namespace)
logrus.Info("Create manager")
if err := mgr.Start(signal.SetupSignalHandler(period)); err != nil {
logrus.Error(err, "Manager exited non-zero")
os.Exit(1)
}

View File

@ -0,0 +1,94 @@
//
// Copyright (c) 2012-2020 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 signal
import (
"context"
"os"
"os/signal"
"syscall"
"time"
"github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// SetupSignalHandler set up custom signal handler for main process.
func SetupSignalHandler(terminationPeriod int64) (stopCh <-chan struct{}) {
logrus.Info("Set up process signal handler")
var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM, syscall.SIGINT}
stop := make(chan struct{})
c := make(chan os.Signal, 1)
signal.Notify(c, shutdownSignals...)
go func() {
sig := <-c
printSignal(sig)
// We need provide more time for Che controller go routing to complete finalizers logic.
// Otherwise resource won't be clean up gracefully
// and Che custom resource will stay with non empty "finalizers" field.
time.Sleep(time.Duration(terminationPeriod) * time.Second)
logrus.Info("Stop and exit operator.")
// Stop Che controller
close(stop)
// Exit from main process directly.
os.Exit(1)
}()
return stop
}
func printSignal(signal os.Signal) {
switch signal {
case syscall.SIGHUP:
logrus.Info("Signal SIGHUP")
case syscall.SIGINT:
logrus.Println("Signal SIGINT (ctrl+c)")
case syscall.SIGTERM:
logrus.Println("Signal SIGTERM stop")
case syscall.SIGQUIT:
logrus.Println("Signal SIGQUIT (top and core dump)")
default:
logrus.Println("Unknown signal")
}
}
func GetTerminationGracePeriodSeconds(k8sClient client.Reader, namespace string) int64 {
cheFlavor := os.Getenv("CHE_FLAVOR")
if cheFlavor == "" {
cheFlavor = "che"
}
defaultTerminationGracePeriodSeconds := int64(20)
deployment := &appsv1.Deployment{}
namespacedName := types.NamespacedName{Namespace: namespace, Name: cheFlavor + "-operator"}
if err := k8sClient.Get(context.TODO(), namespacedName, deployment); err != nil {
logrus.Warnf("Unable to find '%s' deployment in namespace '%s', err: %s", cheFlavor+"-operator", namespace, err.Error())
} else {
terminationPeriod := deployment.Spec.Template.Spec.TerminationGracePeriodSeconds
if terminationPeriod != nil {
logrus.Infof("Use 'terminationGracePeriodSeconds' %d sec. from operator deployment.", *terminationPeriod)
return *terminationPeriod
}
}
logrus.Infof("Use default 'terminationGracePeriodSeconds' %d sec.", defaultTerminationGracePeriodSeconds)
return defaultTerminationGracePeriodSeconds
}

View File

@ -65,3 +65,186 @@ rules:
- create
- watch
- update
- get
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterroles
verbs:
- list
- create
- watch
- update
- get
- delete
- apiGroups:
- authorization.openshift.io
resources:
- roles
verbs:
- get
- create
- delete
- apiGroups:
- authorization.openshift.io
resources:
- rolebindings
verbs:
- get
- create
- update
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- roles
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- rolebindings
verbs:
- get
- create
- update
- delete
- apiGroups:
- org.eclipse.che
resources:
- checlusters
- checlusters/finalizers
verbs:
- '*'
- apiGroups:
- project.openshift.io
resources:
- projectrequests
verbs:
- create
- update
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- create
- update
- apiGroups:
- project.openshift.io
resources:
- projects
verbs:
- get
- apiGroups:
- ''
resources:
- serviceaccounts
verbs:
- get
- create
- watch
- apiGroups:
- ''
resources:
- pods/exec
verbs:
- create
- apiGroups:
- apps
resources:
- secrets
verbs:
- list
- apiGroups:
- ''
resources:
- secrets
verbs:
- list
- create
- delete
- apiGroups:
- ''
resources:
- persistentvolumeclaims
verbs:
- create
- get
- list
- watch
- apiGroups:
- ''
resources:
- pods
verbs:
- get
- list
- create
- watch
- delete
- apiGroups:
- apps
resources:
- deployments
verbs:
- get
- list
- create
- patch
- watch
- delete
- apiGroups:
- ''
resources:
- services
verbs:
- list
- create
- delete
- apiGroups:
- ''
resources:
- configmaps
verbs:
- get
- create
- delete
- list
- apiGroups:
- route.openshift.io
resources:
- routes
verbs:
- list
- create
- delete
- apiGroups:
- ''
resources:
- events
verbs:
- watch
- apiGroups:
- apps
resources:
- replicasets
verbs:
- list
- get
- patch
- delete
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- list
- create
- watch
- get
- delete

View File

@ -59,7 +59,7 @@ spec:
# sets mem limit for server deployment. Defaults to 1Gi
serverMemoryLimit: ''
# sets default namespace where new workspaces will be created
workspaceNamespaceDefault: ''
workspaceNamespaceDefault: "<username>-che"
# defines if user is able to specify namespace different from the default
allowUserDefinedWorkspaceNamespaces: false
# Sets the server and workspaces exposure type. Possible values are "multi-host", "single-host", "default-host".

View File

@ -23,4 +23,6 @@ rules:
- namespaces
verbs:
- update
- list
- create
- get

View File

@ -67,7 +67,7 @@ metadata:
"singleHostGatewayImage": "",
"tlsSupport": true,
"useInternalClusterSVCNames": true,
"workspaceNamespaceDefault": ""
"workspaceNamespaceDefault": "<username>-che"
},
"storage": {
"postgresPVCStorageClassName": "",
@ -84,13 +84,13 @@ metadata:
categories: Developer Tools
certified: "false"
containerImage: quay.io/eclipse/che-operator:nightly
createdAt: "2021-01-28T11:15:38Z"
createdAt: "2021-02-05T00:59:18Z"
description: A Kube-native development solution that delivers portable and collaborative
developer workspaces.
operatorframework.io/suggested-namespace: eclipse-che
repository: https://github.com/eclipse/che-operator
support: Eclipse Foundation
name: eclipse-che-preview-kubernetes.v7.26.0-82.nightly
name: eclipse-che-preview-kubernetes.v7.26.0-88.nightly
namespace: placeholder
spec:
apiservicedefinitions: {}
@ -274,6 +274,151 @@ spec:
- create
- watch
- update
- get
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterroles
verbs:
- list
- create
- watch
- update
- get
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- roles
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- rolebindings
verbs:
- get
- create
- update
- delete
- apiGroups:
- org.eclipse.che
resources:
- checlusters
- checlusters/finalizers
verbs:
- '*'
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- create
- update
- apiGroups:
- ""
resources:
- serviceaccounts
verbs:
- get
- create
- watch
- apiGroups:
- ""
resources:
- pods/exec
verbs:
- create
- apiGroups:
- apps
resources:
- secrets
verbs:
- list
- apiGroups:
- ""
resources:
- secrets
verbs:
- list
- create
- delete
- apiGroups:
- ""
resources:
- persistentvolumeclaims
verbs:
- create
- get
- list
- watch
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- create
- watch
- delete
- apiGroups:
- apps
resources:
- deployments
verbs:
- get
- list
- create
- patch
- watch
- delete
- apiGroups:
- ""
resources:
- services
verbs:
- list
- create
- delete
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- create
- delete
- list
- apiGroups:
- ""
resources:
- events
verbs:
- watch
- apiGroups:
- apps
resources:
- replicasets
verbs:
- list
- get
- patch
- delete
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- list
- create
- watch
- get
- delete
serviceAccountName: che-operator
- rules:
- apiGroups:
@ -282,6 +427,8 @@ spec:
- namespaces
verbs:
- update
- list
- create
- get
serviceAccountName: che-namespace-editor
deployments:
@ -398,7 +545,7 @@ spec:
- ALL
restartPolicy: Always
serviceAccountName: che-operator
terminationGracePeriodSeconds: 5
terminationGracePeriodSeconds: 20
permissions:
- rules:
- apiGroups:
@ -464,7 +611,9 @@ spec:
- apiGroups:
- org.eclipse.che
resources:
- '*'
- checlusters
- checlusters/status
- checlusters/finalizers
verbs:
- '*'
- apiGroups:
@ -528,4 +677,4 @@ spec:
maturity: stable
provider:
name: Eclipse Foundation
version: 7.26.0-82.nightly
version: 7.26.0-88.nightly

View File

@ -58,7 +58,7 @@ metadata:
"singleHostGatewayImage": "",
"tlsSupport": true,
"useInternalClusterSVCNames": true,
"workspaceNamespaceDefault": ""
"workspaceNamespaceDefault": "<username>-che"
},
"storage": {
"postgresPVCStorageClassName": "",
@ -75,13 +75,13 @@ metadata:
categories: Developer Tools, OpenShift Optional
certified: "false"
containerImage: quay.io/eclipse/che-operator:nightly
createdAt: "2021-01-28T11:15:46Z"
createdAt: "2021-02-05T00:59:26Z"
description: A Kube-native development solution that delivers portable and collaborative
developer workspaces in OpenShift.
operatorframework.io/suggested-namespace: eclipse-che
repository: https://github.com/eclipse/che-operator
support: Eclipse Foundation
name: eclipse-che-preview-openshift.v7.26.0-82.nightly
name: eclipse-che-preview-openshift.v7.26.0-88.nightly
namespace: placeholder
spec:
apiservicedefinitions: {}
@ -289,6 +289,189 @@ spec:
- create
- watch
- update
- get
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterroles
verbs:
- list
- create
- watch
- update
- get
- delete
- apiGroups:
- authorization.openshift.io
resources:
- roles
verbs:
- get
- create
- delete
- apiGroups:
- authorization.openshift.io
resources:
- rolebindings
verbs:
- get
- create
- update
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- roles
verbs:
- get
- create
- delete
- apiGroups:
- rbac.authorization.k8s.io
resources:
- rolebindings
verbs:
- get
- create
- update
- delete
- apiGroups:
- org.eclipse.che
resources:
- checlusters
- checlusters/finalizers
verbs:
- '*'
- apiGroups:
- project.openshift.io
resources:
- projectrequests
verbs:
- create
- update
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- create
- update
- apiGroups:
- project.openshift.io
resources:
- projects
verbs:
- get
- apiGroups:
- ""
resources:
- serviceaccounts
verbs:
- get
- create
- watch
- apiGroups:
- ""
resources:
- pods/exec
verbs:
- create
- apiGroups:
- apps
resources:
- secrets
verbs:
- list
- apiGroups:
- ""
resources:
- secrets
verbs:
- list
- create
- delete
- apiGroups:
- ""
resources:
- persistentvolumeclaims
verbs:
- create
- get
- list
- watch
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- create
- watch
- delete
- apiGroups:
- apps
resources:
- deployments
verbs:
- get
- list
- create
- patch
- watch
- delete
- apiGroups:
- ""
resources:
- services
verbs:
- list
- create
- delete
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- create
- delete
- list
- apiGroups:
- route.openshift.io
resources:
- routes
verbs:
- list
- create
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- watch
- apiGroups:
- apps
resources:
- replicasets
verbs:
- list
- get
- patch
- delete
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- list
- create
- watch
- get
- delete
serviceAccountName: che-operator
- rules:
- apiGroups:
@ -297,6 +480,8 @@ spec:
- namespaces
verbs:
- update
- list
- create
- get
serviceAccountName: che-namespace-editor
deployments:
@ -411,7 +596,7 @@ spec:
- ALL
restartPolicy: Always
serviceAccountName: che-operator
terminationGracePeriodSeconds: 5
terminationGracePeriodSeconds: 20
permissions:
- rules:
- apiGroups:
@ -484,7 +669,9 @@ spec:
- apiGroups:
- org.eclipse.che
resources:
- '*'
- checlusters
- checlusters/status
- checlusters/finalizers
verbs:
- '*'
- apiGroups:
@ -547,4 +734,4 @@ spec:
maturity: stable
provider:
name: Eclipse Foundation
version: 7.26.0-82.nightly
version: 7.26.0-88.nightly

View File

@ -123,4 +123,4 @@ spec:
cpu: 500m
restartPolicy: Always
serviceAccountName: che-operator
terminationGracePeriodSeconds: 5
terminationGracePeriodSeconds: 20

View File

@ -88,7 +88,9 @@ rules:
- apiGroups:
- org.eclipse.che
resources:
- '*'
- checlusters
- checlusters/status
- checlusters/finalizers
verbs:
- '*'
- apiGroups:

1
go.mod
View File

@ -4,6 +4,7 @@ go 1.13
require (
github.com/che-incubator/kubernetes-image-puller-operator v0.0.0-20200901231735-f852a5a3ea5c
github.com/golang/mock v1.3.1
github.com/google/go-cmp v0.4.0
github.com/openshift/api v3.9.1-0.20190924102528-32369d4db2ad+incompatible
github.com/operator-framework/api v0.3.20

1
go.sum
View File

@ -343,6 +343,7 @@ github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4er
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=

View File

@ -0,0 +1,49 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: pkg/controller/che/permission_checker.go
// Package mock_che is a generated GoMock package.
package mock_che
import (
gomock "github.com/golang/mock/gomock"
v1 "k8s.io/api/rbac/v1"
reflect "reflect"
)
// MockPermissionChecker is a mock of PermissionChecker interface
type MockPermissionChecker struct {
ctrl *gomock.Controller
recorder *MockPermissionCheckerMockRecorder
}
// MockPermissionCheckerMockRecorder is the mock recorder for MockPermissionChecker
type MockPermissionCheckerMockRecorder struct {
mock *MockPermissionChecker
}
// NewMockPermissionChecker creates a new mock instance
func NewMockPermissionChecker(ctrl *gomock.Controller) *MockPermissionChecker {
mock := &MockPermissionChecker{ctrl: ctrl}
mock.recorder = &MockPermissionCheckerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockPermissionChecker) EXPECT() *MockPermissionCheckerMockRecorder {
return m.recorder
}
// GetNotPermittedPolicyRules mocks base method
func (m *MockPermissionChecker) GetNotPermittedPolicyRules(policies []v1.PolicyRule, namespace string) ([]v1.PolicyRule, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetNotPermittedPolicyRules", policies, namespace)
ret0, _ := ret[0].([]v1.PolicyRule)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetNotPermittedPolicyRules indicates an expected call of GetNotPermittedPolicyRules
func (mr *MockPermissionCheckerMockRecorder) GetNotPermittedPolicyRules(policies, namespace interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotPermittedPolicyRules", reflect.TypeOf((*MockPermissionChecker)(nil).GetNotPermittedPolicyRules), policies, namespace)
}

View File

@ -77,10 +77,11 @@ func newReconciler(mgr manager.Manager) (reconcile.Reconciler, error) {
return nil, err
}
return &ReconcileChe{
client: mgr.GetClient(),
nonCachedClient: noncachedClient,
scheme: mgr.GetScheme(),
discoveryClient: discoveryClient,
client: mgr.GetClient(),
nonCachedClient: noncachedClient,
scheme: mgr.GetScheme(),
discoveryClient: discoveryClient,
permissionChecker: &K8sApiPermissionChecker{},
}, nil
}
@ -259,8 +260,15 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
return nil
}
var _ reconcile.Reconciler = &ReconcileChe{}
var oAuthFinalizerName = "oauthclients.finalizers.che.eclipse.org"
var (
_ reconcile.Reconciler = &ReconcileChe{}
oAuthFinalizerName = "oauthclients.finalizers.che.eclipse.org"
cheWorkspacesClusterPermissionsFinalizerName = "cheWorkspaces.clusterpermissions.finalizers.che.eclipse.org"
// CheServiceAccountName - service account name for che-server.
CheServiceAccountName = "che"
)
// ReconcileChe reconciles a CheCluster object
type ReconcileChe struct {
@ -274,9 +282,10 @@ type ReconcileChe struct {
nonCachedClient client.Client
// A discovery client to check for the existence of certain APIs registered
// in the API Server
discoveryClient discovery.DiscoveryInterface
scheme *runtime.Scheme
tests bool
discoveryClient discovery.DiscoveryInterface
scheme *runtime.Scheme
permissionChecker PermissionChecker
tests bool
}
const (
@ -330,6 +339,9 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e
return reconcile.Result{}, err
}
}
if r.reconcileWorkspacePermissionsFinalizer(instance, deployContext); err != nil {
return reconcile.Result{}, err
}
// Reconcile the imagePuller section of the CheCluster
imagePullerResult, err := deploy.ReconcileImagePuller(deployContext)
@ -535,10 +547,10 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e
return reconcile.Result{}, 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
cheSA, err := deploy.SyncServiceAccountToCluster(deployContext, "che")
// Create service account "che" for che-server component.
// "che" is the one which token is used to create workspace objects.
// Notice: Also we have on more "che-workspace" SA used by plugins like exec, terminal, metrics with limited privileges.
cheSA, err := deploy.SyncServiceAccountToCluster(deployContext, CheServiceAccountName)
if cheSA == nil {
logrus.Info("Waiting on service account 'che' to be created")
if err != nil {
@ -549,49 +561,50 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e
}
}
cheWorkspaceSA, err := deploy.SyncServiceAccountToCluster(deployContext, "che-workspace")
if cheWorkspaceSA == nil {
logrus.Info("Waiting on service account 'che-workspace' to be created")
if !util.IsOAuthEnabled(instance) && !util.IsWorkspaceInSameNamespaceWithChe(instance) {
clusterRole, err := deploy.GetClusterRole(CheWorkspacesClusterRoleNameTemplate, deployContext.ClusterAPI.Client)
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{RequeueAfter: time.Second}, err
}
if clusterRole == nil {
policies := append(getCheWorkspacesNamespacePolicy(), getCheWorkspacesPolicy()...)
deniedRules, err := r.permissionChecker.GetNotPermittedPolicyRules(policies, "")
if err != nil {
logrus.Error(err)
return reconcile.Result{RequeueAfter: time.Second}, err
}
// fall back to the "narrower" workspace namespace strategy
if len(deniedRules) > 0 {
logrus.Warnf("Not enough permissions to start a workspace in dedicated namespace. Denied policies: %v", deniedRules)
logrus.Warnf("Fall back to '%s' namespace for workspaces.", instance.Namespace)
delete(instance.Spec.Server.CustomCheProperties, "CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT")
instance.Spec.Server.WorkspaceNamespaceDefault = instance.Namespace
r.UpdateCheCRSpec(instance, "Default namespace for workspaces", instance.Namespace);
if err != nil {
logrus.Error(err)
return reconcile.Result{RequeueAfter: time.Second * 1}, err
}
} else {
reconcileResult, err := r.delegateWorkspacePermissionsInTheDifferNamespaceThanChe(instance, deployContext)
if err != nil {
logrus.Error(err)
return reconcile.Result{RequeueAfter: time.Second}, err
}
if reconcileResult.Requeue {
return reconcileResult, err
}
}
}
}
// create exec role for CheCluster server and workspaces
execRole, err := deploy.SyncExecRoleToCluster(deployContext)
if execRole == nil {
logrus.Info("Waiting on role 'exec' to be created")
if util.IsOAuthEnabled(instance) || util.IsWorkspaceInSameNamespaceWithChe(instance) {
reconcile, err := r.delegateWorkspacePermissionsInTheSameNamespaceWithChe(deployContext)
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{RequeueAfter: time.Second}, err
}
}
// create view role for CheCluster server and workspaces
viewRole, err := deploy.SyncViewRoleToCluster(deployContext)
if viewRole == nil {
logrus.Info("Waiting on role 'view' to be created")
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{RequeueAfter: time.Second}, err
}
}
cheRoleBinding, err := deploy.SyncRoleBindingToCluster(deployContext, "che", "che", "edit", "ClusterRole")
if cheRoleBinding == nil {
logrus.Info("Waiting on role binding 'che' to be created")
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{RequeueAfter: time.Second}, err
if reconcile.Requeue && !tests {
return reconcile, err
}
}
@ -613,28 +626,6 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e
}
}
cheWSExecRoleBinding, err := deploy.SyncRoleBindingToCluster(deployContext, "che-workspace-exec", "che-workspace", "exec", "Role")
if cheWSExecRoleBinding == nil {
logrus.Info("Waiting on role binding 'che-workspace-exec' to be created")
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{RequeueAfter: time.Second}, err
}
}
cheWSViewRoleBinding, err := deploy.SyncRoleBindingToCluster(deployContext, "che-workspace-view", "che-workspace", "view", "Role")
if cheWSViewRoleBinding == nil {
logrus.Info("Waiting on role binding 'che-workspace-view' to be created")
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{RequeueAfter: time.Second}, err
}
}
// If the user specified an additional cluster role to use for the Che workspace, create a role binding for it
// Use a role binding instead of a cluster role binding to keep the additional access scoped to the workspace's namespace
workspaceClusterRole := instance.Spec.Server.CheWorkspaceClusterRole
@ -950,7 +941,7 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e
deleted, err := r.ReconcileIdentityProvider(instance, isOpenShift4)
if deleted {
for {
if err := r.DeleteFinalizer(instance); err != nil &&
if err := r.DeleteOAuthFinalizer(instance); err != nil &&
errors.IsConflict(err) {
instance, _ = r.GetCR(request)
continue

View File

@ -14,6 +14,7 @@ package che
import (
"context"
"fmt"
mocks "github.com/eclipse/che-operator/mocks"
"io/ioutil"
"os"
@ -22,6 +23,7 @@ import (
chev1alpha1 "github.com/che-incubator/kubernetes-image-puller-operator/pkg/apis/che/v1alpha1"
identity_provider "github.com/eclipse/che-operator/pkg/deploy/identity-provider"
"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
"github.com/eclipse/che-operator/pkg/deploy"
@ -41,8 +43,8 @@ import (
"github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbac "k8s.io/api/rbac/v1"
rbacapi "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -185,6 +187,7 @@ var (
},
},
}
route = &routev1.Route{}
)
func init() {
@ -340,6 +343,7 @@ func TestCaseAutoDetectOAuth(t *testing.T) {
scheme.AddKnownTypes(oauth.SchemeGroupVersion, oAuthClient)
scheme.AddKnownTypes(userv1.SchemeGroupVersion, &userv1.UserList{}, &userv1.User{})
scheme.AddKnownTypes(oauth_config.SchemeGroupVersion, &oauth_config.OAuth{})
scheme.AddKnownTypes(routev1.GroupVersion, route)
initCR := InitCheWithSimpleCR()
initCR.Spec.Auth.OpenShiftoAuth = testCase.initialOAuthValue
@ -360,6 +364,7 @@ func TestCaseAutoDetectOAuth(t *testing.T) {
nonCachedClient: nonCachedClient,
discoveryClient: fakeDiscovery,
scheme: scheme,
tests: true,
}
req := reconcile.Request{
NamespacedName: types.NamespacedName{
@ -541,6 +546,7 @@ func TestImagePullerConfiguration(t *testing.T) {
operatorsv1alpha1.AddToScheme(scheme.Scheme)
operatorsv1.AddToScheme(scheme.Scheme)
chev1alpha1.AddToScheme(scheme.Scheme)
routev1.AddToScheme(scheme.Scheme)
testCase.initObjects = append(testCase.initObjects, testCase.initCR)
cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...)
nonCachedClient := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...)
@ -580,6 +586,7 @@ func TestImagePullerConfiguration(t *testing.T) {
nonCachedClient: nonCachedClient,
discoveryClient: fakeDiscovery,
scheme: scheme.Scheme,
tests: true,
}
req := reconcile.Request{
NamespacedName: types.NamespacedName{
@ -746,7 +753,7 @@ func TestCheController(t *testing.T) {
}
// Get the custom role binding that should have been created for the role we passed in
rb := &rbacapi.RoleBinding{}
rb := &rbac.RoleBinding{}
if err := cl.Get(context.TODO(), types.NamespacedName{Name: "che-workspace-custom", Namespace: cheCR.Namespace}, rb); err != nil {
t.Errorf("Custom role binding %s not found: %s", rb.Name, err)
}
@ -942,8 +949,8 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
os.Setenv("OPENSHIFT_VERSION", "3")
type testCase struct {
name string
cheCR *orgv1.CheCluster
name string
cheCR *orgv1.CheCluster
expectedIdentityProviderInternalURL string
}
@ -959,8 +966,8 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: name,
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
@ -968,9 +975,9 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
UseInternalClusterSVCNames: true,
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
OpenShiftoAuth: util.NewBoolPointer(false),
ExternalIdentityProvider: true,
IdentityProviderURL: "http://external-keycloak",
IdentityProviderURL: "http://external-keycloak",
},
},
},
@ -984,8 +991,8 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: name,
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
@ -993,9 +1000,9 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
UseInternalClusterSVCNames: false,
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
OpenShiftoAuth: util.NewBoolPointer(false),
ExternalIdentityProvider: true,
IdentityProviderURL: "http://external-keycloak",
IdentityProviderURL: "http://external-keycloak",
},
},
},
@ -1009,8 +1016,8 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: name,
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
@ -1018,7 +1025,7 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
UseInternalClusterSVCNames: false,
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
OpenShiftoAuth: util.NewBoolPointer(false),
ExternalIdentityProvider: false,
},
},
@ -1033,8 +1040,8 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: name,
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
@ -1042,7 +1049,7 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
UseInternalClusterSVCNames: true,
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
OpenShiftoAuth: util.NewBoolPointer(false),
ExternalIdentityProvider: false,
},
},
@ -1057,7 +1064,7 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
orgv1.SchemeBuilder.AddToScheme(scheme)
scheme.AddKnownTypes(routev1.SchemeGroupVersion, &routev1.Route{})
cli := fake.NewFakeClientWithScheme(scheme, testCase.cheCR, )
cli := fake.NewFakeClientWithScheme(scheme, testCase.cheCR)
nonCachedClient := fake.NewFakeClientWithScheme(scheme, testCase.cheCR)
clientSet := fakeclientset.NewSimpleClientset()
fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery)
@ -1070,7 +1077,7 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
nonCachedClient: nonCachedClient,
discoveryClient: fakeDiscovery,
scheme: scheme,
tests: true,
tests: true,
}
req := reconcile.Request{
NamespacedName: types.NamespacedName{
@ -1122,12 +1129,12 @@ func TestShouldSetUpCorrectlyInternalIdentityProviderServiceURL(t *testing.T) {
}
}
func TestShouldUsePublicUrlForExternalPluginRegistryWhenInternalNetworkEnabled(t *testing.T) {
func TestShouldSetUpCorrectlyInternalPluginRegistryServiceURL(t *testing.T) {
os.Setenv("OPENSHIFT_VERSION", "3")
type testCase struct {
name string
cheCR *orgv1.CheCluster
name string
cheCR *orgv1.CheCluster
expectedPluginRegistryInternalURL string
}
@ -1143,15 +1150,15 @@ func TestShouldUsePublicUrlForExternalPluginRegistryWhenInternalNetworkEnabled(t
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: name,
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
UseInternalClusterSVCNames: true,
ExternalPluginRegistry: true,
PluginRegistryUrl: "http://external-plugin-registry",
ExternalPluginRegistry: true,
PluginRegistryUrl: "http://external-plugin-registry",
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
@ -1168,15 +1175,15 @@ func TestShouldUsePublicUrlForExternalPluginRegistryWhenInternalNetworkEnabled(t
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: name,
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
UseInternalClusterSVCNames: false,
ExternalPluginRegistry: true,
PluginRegistryUrl: "http://external-plugin-registry",
ExternalPluginRegistry: true,
PluginRegistryUrl: "http://external-plugin-registry",
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
@ -1193,14 +1200,14 @@ func TestShouldUsePublicUrlForExternalPluginRegistryWhenInternalNetworkEnabled(t
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: name,
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
UseInternalClusterSVCNames: false,
ExternalPluginRegistry: false,
ExternalPluginRegistry: false,
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
@ -1217,14 +1224,14 @@ func TestShouldUsePublicUrlForExternalPluginRegistryWhenInternalNetworkEnabled(t
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: name,
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
UseInternalClusterSVCNames: true,
ExternalPluginRegistry: false,
ExternalPluginRegistry: false,
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
@ -1241,7 +1248,7 @@ func TestShouldUsePublicUrlForExternalPluginRegistryWhenInternalNetworkEnabled(t
orgv1.SchemeBuilder.AddToScheme(scheme)
scheme.AddKnownTypes(routev1.SchemeGroupVersion, &routev1.Route{})
cli := fake.NewFakeClientWithScheme(scheme, testCase.cheCR, )
cli := fake.NewFakeClientWithScheme(scheme, testCase.cheCR)
nonCachedClient := fake.NewFakeClientWithScheme(scheme, testCase.cheCR)
clientSet := fakeclientset.NewSimpleClientset()
fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery)
@ -1254,7 +1261,7 @@ func TestShouldUsePublicUrlForExternalPluginRegistryWhenInternalNetworkEnabled(t
nonCachedClient: nonCachedClient,
discoveryClient: fakeDiscovery,
scheme: scheme,
tests: true,
tests: true,
}
req := reconcile.Request{
NamespacedName: types.NamespacedName{
@ -1274,7 +1281,7 @@ func TestShouldUsePublicUrlForExternalPluginRegistryWhenInternalNetworkEnabled(t
// Set up che host for route
cheRoute, _ := deploy.GetSpecRoute(deployContext, deploy.DefaultCheFlavor(testCase.cheCR), "che-host", "che-host", 8080, "", "che")
r.client.Create(context.TODO(), cheRoute);
r.client.Create(context.TODO(), cheRoute)
// Set up keycloak host for route
keycloakRoute, _ := deploy.GetSpecRoute(deployContext, deploy.IdentityProviderName, "keycloak", deploy.IdentityProviderName, 8080, "", deploy.IdentityProviderName)
r.client.Create(context.TODO(), keycloakRoute)
@ -1284,7 +1291,6 @@ func TestShouldUsePublicUrlForExternalPluginRegistryWhenInternalNetworkEnabled(t
// Set up devfile registry host for route
devfileRegistryRoute, _ := deploy.GetSpecRoute(deployContext, deploy.DevfileRegistryName, "devfile-registry", deploy.DevfileRegistryName, 8080, "", deploy.DevfileRegistryName)
r.client.Create(context.TODO(), devfileRegistryRoute)
_, err := r.Reconcile(req)
if err != nil {
@ -1309,12 +1315,12 @@ func TestShouldUsePublicUrlForExternalPluginRegistryWhenInternalNetworkEnabled(t
}
}
func TestShouldUsePublicUrlForExternalDevfileRegistryWhenInternalNetworkEnabled(t *testing.T) {
func TestShouldSetUpCorrectlyInternalDevfileRegistryServiceURL(t *testing.T) {
os.Setenv("OPENSHIFT_VERSION", "3")
type testCase struct {
name string
cheCR *orgv1.CheCluster
name string
cheCR *orgv1.CheCluster
expectedDevfileRegistryInternalURL string
}
@ -1330,15 +1336,15 @@ func TestShouldUsePublicUrlForExternalDevfileRegistryWhenInternalNetworkEnabled(
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: name,
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
UseInternalClusterSVCNames: true,
ExternalDevfileRegistry: true,
DevfileRegistryUrl: "http://external-devfile-registry",
ExternalDevfileRegistry: true,
DevfileRegistryUrl: "http://external-devfile-registry",
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
@ -1355,15 +1361,15 @@ func TestShouldUsePublicUrlForExternalDevfileRegistryWhenInternalNetworkEnabled(
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: name,
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
UseInternalClusterSVCNames: false,
ExternalDevfileRegistry: true,
DevfileRegistryUrl: "http://external-devfile-registry",
ExternalDevfileRegistry: true,
DevfileRegistryUrl: "http://external-devfile-registry",
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
@ -1380,14 +1386,14 @@ func TestShouldUsePublicUrlForExternalDevfileRegistryWhenInternalNetworkEnabled(
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: name,
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
UseInternalClusterSVCNames: false,
ExternalDevfileRegistry: false,
ExternalDevfileRegistry: false,
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
@ -1404,14 +1410,14 @@ func TestShouldUsePublicUrlForExternalDevfileRegistryWhenInternalNetworkEnabled(
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: name,
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
UseInternalClusterSVCNames: true,
ExternalDevfileRegistry: false,
ExternalDevfileRegistry: false,
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
@ -1428,7 +1434,7 @@ func TestShouldUsePublicUrlForExternalDevfileRegistryWhenInternalNetworkEnabled(
orgv1.SchemeBuilder.AddToScheme(scheme)
scheme.AddKnownTypes(routev1.SchemeGroupVersion, &routev1.Route{})
cli := fake.NewFakeClientWithScheme(scheme, testCase.cheCR, )
cli := fake.NewFakeClientWithScheme(scheme, testCase.cheCR)
nonCachedClient := fake.NewFakeClientWithScheme(scheme, testCase.cheCR)
clientSet := fakeclientset.NewSimpleClientset()
fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery)
@ -1441,7 +1447,7 @@ func TestShouldUsePublicUrlForExternalDevfileRegistryWhenInternalNetworkEnabled(
nonCachedClient: nonCachedClient,
discoveryClient: fakeDiscovery,
scheme: scheme,
tests: true,
tests: true,
}
req := reconcile.Request{
NamespacedName: types.NamespacedName{
@ -1461,7 +1467,7 @@ func TestShouldUsePublicUrlForExternalDevfileRegistryWhenInternalNetworkEnabled(
// Set up che host for route
cheRoute, _ := deploy.GetSpecRoute(deployContext, deploy.DefaultCheFlavor(testCase.cheCR), "che-host", "che-host", 8080, "", "che")
r.client.Create(context.TODO(), cheRoute);
r.client.Create(context.TODO(), cheRoute)
// Set up keycloak host for route
keycloakRoute, _ := deploy.GetSpecRoute(deployContext, deploy.IdentityProviderName, "keycloak", deploy.IdentityProviderName, 8080, "", deploy.IdentityProviderName)
r.client.Create(context.TODO(), keycloakRoute)
@ -1495,12 +1501,12 @@ func TestShouldUsePublicUrlForExternalDevfileRegistryWhenInternalNetworkEnabled(
}
}
func TestShouldUseCorrectUrlForInternalCheServerURLWhenInternalNetworkEnabled(t *testing.T) {
func TestShouldSetUpCorrectlyInternalCheServerURL(t *testing.T) {
os.Setenv("OPENSHIFT_VERSION", "3")
type testCase struct {
name string
cheCR *orgv1.CheCluster
name string
cheCR *orgv1.CheCluster
expectedCheServerInternalURL string
}
@ -1516,8 +1522,8 @@ func TestShouldUseCorrectUrlForInternalCheServerURLWhenInternalNetworkEnabled(t
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: name,
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
@ -1539,14 +1545,14 @@ func TestShouldUseCorrectUrlForInternalCheServerURLWhenInternalNetworkEnabled(t
APIVersion: "org.eclipse.che/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Name: name,
Namespace: namespace,
ResourceVersion: "1",
},
Spec: orgv1.CheClusterSpec{
Server: orgv1.CheClusterSpecServer{
UseInternalClusterSVCNames: true,
ExternalDevfileRegistry: false,
ExternalDevfileRegistry: false,
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
@ -1563,7 +1569,7 @@ func TestShouldUseCorrectUrlForInternalCheServerURLWhenInternalNetworkEnabled(t
orgv1.SchemeBuilder.AddToScheme(scheme)
scheme.AddKnownTypes(routev1.SchemeGroupVersion, &routev1.Route{})
cli := fake.NewFakeClientWithScheme(scheme, testCase.cheCR, )
cli := fake.NewFakeClientWithScheme(scheme, testCase.cheCR)
nonCachedClient := fake.NewFakeClientWithScheme(scheme, testCase.cheCR)
clientSet := fakeclientset.NewSimpleClientset()
fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery)
@ -1576,7 +1582,7 @@ func TestShouldUseCorrectUrlForInternalCheServerURLWhenInternalNetworkEnabled(t
nonCachedClient: nonCachedClient,
discoveryClient: fakeDiscovery,
scheme: scheme,
tests: true,
tests: true,
}
req := reconcile.Request{
NamespacedName: types.NamespacedName{
@ -1596,7 +1602,7 @@ func TestShouldUseCorrectUrlForInternalCheServerURLWhenInternalNetworkEnabled(t
// Set up che host for route
cheRoute, _ := deploy.GetSpecRoute(deployContext, deploy.DefaultCheFlavor(testCase.cheCR), "che-host", "che-host", 8080, "", "che")
r.client.Create(context.TODO(), cheRoute);
r.client.Create(context.TODO(), cheRoute)
// Set up keycloak host for route
keycloakRoute, _ := deploy.GetSpecRoute(deployContext, deploy.IdentityProviderName, "keycloak", deploy.IdentityProviderName, 8080, "", deploy.IdentityProviderName)
r.client.Create(context.TODO(), keycloakRoute)
@ -1606,7 +1612,7 @@ func TestShouldUseCorrectUrlForInternalCheServerURLWhenInternalNetworkEnabled(t
// Set up devfile registry host for route
devfileRegistryRoute, _ := deploy.GetSpecRoute(deployContext, deploy.DevfileRegistryName, "devfile-registry", deploy.DevfileRegistryName, 8080, "", deploy.DevfileRegistryName)
r.client.Create(context.TODO(), devfileRegistryRoute)
_, err := r.Reconcile(req)
if err != nil {
t.Fatalf("Error reconciling: %v", err)
@ -1630,6 +1636,266 @@ func TestShouldUseCorrectUrlForInternalCheServerURLWhenInternalNetworkEnabled(t
}
}
func TestShouldDelegatePermissionsForCheWorkspaces(t *testing.T) {
os.Setenv("OPENSHIFT_VERSION", "3")
type testCase struct {
name string
initObjects []runtime.Object
clusterRole bool
checluster *orgv1.CheCluster
}
// the same namespace with Che
crWsInTheSameNs1 := InitCheWithSimpleCR().DeepCopy()
crWsInTheSameNs1.Spec.Server.WorkspaceNamespaceDefault = crWsInTheSameNs1.Namespace
crWsInTheSameNs2 := InitCheWithSimpleCR().DeepCopy()
crWsInTheSameNs2.Spec.Server.WorkspaceNamespaceDefault = ""
crWsInTheSameNs3 := InitCheWithSimpleCR().DeepCopy()
crWsInTheSameNs3.Spec.Server.CustomCheProperties = make(map[string]string)
crWsInTheSameNs3.Spec.Server.CustomCheProperties["CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"] = ""
crWsInTheSameNs4 := InitCheWithSimpleCR().DeepCopy()
crWsInTheSameNs4.Spec.Server.CustomCheProperties = make(map[string]string)
crWsInTheSameNs4.Spec.Server.CustomCheProperties["CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"] = crWsInTheSameNs1.Namespace
// differ namespace with Che
crWsInAnotherNs1 := InitCheWithSimpleCR().DeepCopy()
crWsInAnotherNs1.Spec.Server.WorkspaceNamespaceDefault = "some-test-namespace"
crWsInAnotherNs2 := InitCheWithSimpleCR().DeepCopy()
crWsInAnotherNs2.Spec.Server.CustomCheProperties = make(map[string]string)
crWsInAnotherNs2.Spec.Server.CustomCheProperties["CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"] = "some-test-namespace"
crWsInAnotherNs3 := InitCheWithSimpleCR().DeepCopy()
crWsInAnotherNs3.Spec.Server.CustomCheProperties = make(map[string]string)
crWsInAnotherNs3.Spec.Server.CustomCheProperties["CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"] = crWsInTheSameNs1.Namespace
crWsInAnotherNs3.Spec.Server.WorkspaceNamespaceDefault = "some-test-namespace"
testCases := []testCase{
{
name: "che-operator should delegate permission for workspaces in the same namespace with Che. WorkspaceNamespaceDefault=" + crWsInTheSameNs1.Namespace,
initObjects: []runtime.Object{},
clusterRole: false,
checluster: crWsInTheSameNs1,
},
{
name: "che-operator should delegate permission for workspaces in the same namespace with Che. WorkspaceNamespaceDefault=''",
initObjects: []runtime.Object{},
clusterRole: false,
checluster: crWsInTheSameNs2,
},
{
name: "che-operator should delegate permission for workspaces in the same namespace with Che. Property CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT=''",
initObjects: []runtime.Object{},
clusterRole: false,
checluster: crWsInTheSameNs3,
},
{
name: "che-operator should delegate permission for workspaces in the same namespace with Che. Property CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT=" + crWsInTheSameNs1.Namespace,
initObjects: []runtime.Object{},
clusterRole: false,
checluster: crWsInTheSameNs4,
},
{
name: "che-operator should delegate permission for workspaces in differ namespace than Che. WorkspaceNamespaceDefault = 'some-test-namespace'",
initObjects: []runtime.Object{},
clusterRole: true,
checluster: crWsInAnotherNs1,
},
{
name: "che-operator should delegate permission for workspaces in differ namespace than Che. Property CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT = 'some-test-namespace'",
initObjects: []runtime.Object{},
clusterRole: true,
checluster: crWsInAnotherNs2,
},
{
name: "che-operator should delegate permission for workspaces in differ namespace than Che. Property CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT points to Che namespace with higher priority WorkspaceNamespaceDefault = 'some-test-namespace'.",
initObjects: []runtime.Object{},
clusterRole: false,
checluster: crWsInAnotherNs3,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
logf.SetLogger(zap.LoggerTo(os.Stdout, true))
scheme := scheme.Scheme
orgv1.SchemeBuilder.AddToScheme(scheme)
scheme.AddKnownTypes(oauth.SchemeGroupVersion, oAuthClient)
scheme.AddKnownTypes(userv1.SchemeGroupVersion, &userv1.UserList{}, &userv1.User{})
scheme.AddKnownTypes(oauth_config.SchemeGroupVersion, &oauth_config.OAuth{})
scheme.AddKnownTypes(routev1.GroupVersion, route)
initCR := testCase.checluster
initCR.Spec.Auth.OpenShiftoAuth = util.NewBoolPointer(false)
testCase.initObjects = append(testCase.initObjects, initCR)
cli := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...)
nonCachedClient := fake.NewFakeClientWithScheme(scheme, testCase.initObjects...)
clientSet := fakeclientset.NewSimpleClientset()
// todo do we need fake discovery
fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery)
fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{}
if !ok {
t.Fatal("Error creating fake discovery client")
}
var m *mocks.MockPermissionChecker
if testCase.clusterRole {
ctrl := gomock.NewController(t)
m = mocks.NewMockPermissionChecker(ctrl)
m.EXPECT().GetNotPermittedPolicyRules(gomock.Any(), "").Return([]rbac.PolicyRule{}, nil).MaxTimes(2)
defer ctrl.Finish()
}
r := &ReconcileChe{
client: cli,
nonCachedClient: nonCachedClient,
discoveryClient: fakeDiscovery,
scheme: scheme,
permissionChecker: m,
tests: true,
}
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: name,
Namespace: namespace,
},
}
_, err := r.Reconcile(req)
if err != nil {
t.Fatalf("Error reconciling: %v", err)
}
_, err = r.Reconcile(req)
if err != nil {
t.Fatalf("Error reconciling: %v", err)
}
if !testCase.clusterRole {
viewRole := &rbac.Role{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.ViewRoleName, Namespace: namespace}, viewRole); err != nil {
t.Errorf("role '%s' not found", deploy.ViewRoleName)
}
viewRoleBinding := &rbac.RoleBinding{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: ViewRoleBindingName, Namespace: namespace}, viewRoleBinding); err != nil {
t.Errorf("rolebinding '%s' not found", ViewRoleBindingName)
}
execRole := &rbac.Role{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: deploy.ExecRoleName, Namespace: namespace}, execRole); err != nil {
t.Errorf("role '%s' not found", deploy.ExecRoleName)
}
execRoleBinding := &rbac.RoleBinding{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: ExecRoleBindingName, Namespace: namespace}, execRoleBinding); err != nil {
t.Errorf("rolebinding '%s' not found", ExecRoleBindingName)
}
editRoleBinding := &rbac.RoleBinding{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: EditRoleBindingName, Namespace: namespace}, editRoleBinding); err != nil {
t.Errorf("rolebinding '%s' not found", EditRoleBindingName)
}
} else {
manageNamespacesClusterRoleName := fmt.Sprintf(CheWorkspacesNamespaceClusterRoleNameTemplate, namespace)
cheManageNamespaceClusterRole := &rbac.ClusterRole{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: manageNamespacesClusterRoleName}, cheManageNamespaceClusterRole); err != nil {
t.Errorf("role '%s' not found", manageNamespacesClusterRoleName)
}
cheManageNamespaceClusterRoleBinding := &rbac.ClusterRoleBinding{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: manageNamespacesClusterRoleName}, cheManageNamespaceClusterRoleBinding); err != nil {
t.Errorf("rolebinding '%s' not found", manageNamespacesClusterRoleName)
}
cheWorkspacesClusterRoleName := fmt.Sprintf(CheWorkspacesClusterRoleNameTemplate, namespace)
cheWorkspacesClusterRole := &rbac.ClusterRole{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: cheWorkspacesClusterRoleName}, cheWorkspacesClusterRole); err != nil {
t.Errorf("role '%s' not found", cheWorkspacesClusterRole)
}
cheWorkspacesClusterRoleBinding := &rbac.ClusterRoleBinding{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: cheWorkspacesClusterRoleName}, cheWorkspacesClusterRoleBinding); err != nil {
t.Errorf("rolebinding '%s' not found", cheWorkspacesClusterRole)
}
}
})
}
}
func TestShouldFallBackWorspaceNamespaceDefaultBecauseNotEnoughtPermissions(t *testing.T) {
// the same namespace with Che
cr := InitCheWithSimpleCR().DeepCopy()
cr.Spec.Server.WorkspaceNamespaceDefault = "che-workspace-<username>"
logf.SetLogger(zap.LoggerTo(os.Stdout, true))
scheme := scheme.Scheme
orgv1.SchemeBuilder.AddToScheme(scheme)
scheme.AddKnownTypes(oauth.SchemeGroupVersion, oAuthClient)
scheme.AddKnownTypes(userv1.SchemeGroupVersion, &userv1.UserList{}, &userv1.User{})
scheme.AddKnownTypes(oauth_config.SchemeGroupVersion, &oauth_config.OAuth{})
scheme.AddKnownTypes(routev1.GroupVersion, route)
cr.Spec.Auth.OpenShiftoAuth = util.NewBoolPointer(false)
cli := fake.NewFakeClientWithScheme(scheme, cr)
nonCachedClient := fake.NewFakeClientWithScheme(scheme, cr)
clientSet := fakeclientset.NewSimpleClientset()
// todo do we need fake discovery
fakeDiscovery, ok := clientSet.Discovery().(*fakeDiscovery.FakeDiscovery)
fakeDiscovery.Fake.Resources = []*metav1.APIResourceList{}
if !ok {
t.Fatal("Error creating fake discovery client")
}
var m *mocks.MockPermissionChecker
ctrl := gomock.NewController(t)
m = mocks.NewMockPermissionChecker(ctrl)
m.EXPECT().GetNotPermittedPolicyRules(gomock.Any(), "").Return([]rbac.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"namespaces"},
Verbs: []string{"get", "create", "update"},
},
}, nil).MaxTimes(2)
defer ctrl.Finish()
r := &ReconcileChe{
client: cli,
nonCachedClient: nonCachedClient,
discoveryClient: fakeDiscovery,
scheme: scheme,
permissionChecker: m,
tests: true,
}
req := reconcile.Request{
NamespacedName: types.NamespacedName{
Name: name,
Namespace: namespace,
},
}
_, err := r.Reconcile(req)
if err != nil {
t.Fatalf("Error reconciling: %v", err)
}
_, err = r.Reconcile(req)
if err != nil {
t.Fatalf("Error reconciling: %v", err)
}
cheCluster := &orgv1.CheCluster{}
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, cheCluster); err != nil {
t.Errorf("Unable to get checluster")
}
if cheCluster.Spec.Server.WorkspaceNamespaceDefault != namespace {
t.Error("Failed fallback workspaceNamespaceDefault to execute workspaces in the same namespace with Che")
}
}
func Init() (client.Client, discovery.DiscoveryInterface, runtime.Scheme) {
objs, ds, scheme := createAPIObjects()
@ -1695,7 +1961,7 @@ func createAPIObjects() ([]runtime.Object, discovery.DiscoveryInterface, runtime
cli := fakeclientset.NewSimpleClientset()
fakeDiscovery, ok := cli.Discovery().(*fakeDiscovery.FakeDiscovery)
if !ok {
fmt.Errorf("Error creating fake discovery client")
logrus.Error("Error creating fake discovery client")
os.Exit(1)
}
@ -1714,6 +1980,9 @@ func InitCheWithSimpleCR() *orgv1.CheCluster {
Server: orgv1.CheClusterSpecServer{
CheWorkspaceClusterRole: "cluster-admin",
},
Auth: orgv1.CheClusterSpecAuth{
OpenShiftoAuth: util.NewBoolPointer(false),
},
},
}
}

View File

@ -0,0 +1,66 @@
//
// Copyright (c) 2012-2020 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"
"fmt"
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"
)
func (r *ReconcileChe) ReconcileCheWorkspacesClusterPermissionsFinalizer(instance *orgv1.CheCluster) (err error) {
if instance.ObjectMeta.DeletionTimestamp.IsZero() {
if !util.ContainsString(instance.ObjectMeta.Finalizers, cheWorkspacesClusterPermissionsFinalizerName) {
instance.ObjectMeta.Finalizers = append(instance.ObjectMeta.Finalizers, cheWorkspacesClusterPermissionsFinalizerName)
if err := r.client.Update(context.Background(), instance); err != nil {
return err
}
}
} else {
r.RemoveCheWorkspacesClusterPermissions(instance)
}
return nil
}
func (r *ReconcileChe) RemoveCheWorkspacesClusterPermissions(instance *orgv1.CheCluster) (err error) {
if util.ContainsString(instance.ObjectMeta.Finalizers, cheWorkspacesClusterPermissionsFinalizerName) {
logrus.Infof("Removing '%s'", cheWorkspacesClusterPermissionsFinalizerName)
cheWorkspacesNamespaceClusterRoleName := fmt.Sprintf(CheWorkspacesNamespaceClusterRoleNameTemplate, instance.Namespace)
cheWorkspacesClusterRoleName := fmt.Sprintf(CheWorkspacesClusterRoleNameTemplate, instance.Namespace)
if err := deploy.DeleteClusterRole(cheWorkspacesNamespaceClusterRoleName, r.nonCachedClient); err != nil {
return err
}
if err := deploy.DeleteClusterRoleBinding(cheWorkspacesNamespaceClusterRoleName, r.nonCachedClient); err != nil {
return err
}
if err := deploy.DeleteClusterRole(cheWorkspacesClusterRoleName, r.nonCachedClient); err != nil {
return err
}
if err := deploy.DeleteClusterRoleBinding(cheWorkspacesClusterRoleName, r.nonCachedClient); err != nil {
return err
}
instance.ObjectMeta.Finalizers = util.DoRemoveString(instance.ObjectMeta.Finalizers, cheWorkspacesClusterPermissionsFinalizerName)
if err := r.client.Update(context.Background(), instance); err != nil {
return err
}
}
return nil
}

View File

@ -35,7 +35,7 @@ func (r *ReconcileChe) ReconcileFinalizer(instance *orgv1.CheCluster) (err error
oAuthClient, err := r.GetOAuthClient(oAuthClientName)
if err == nil {
if err := r.client.Delete(context.TODO(), oAuthClient); err != nil {
logrus.Errorf("Failed to delete %s oAuthClient: %s", oAuthClientName, err)
logrus.Errorf("Failed to delete %s oAuthClient: %s", oAuthClientName, err.Error())
return err
}
} else if !errors.IsNotFound(err) {
@ -43,7 +43,6 @@ func (r *ReconcileChe) ReconcileFinalizer(instance *orgv1.CheCluster) (err error
return err
}
instance.ObjectMeta.Finalizers = util.DoRemoveString(instance.ObjectMeta.Finalizers, oAuthFinalizerName)
logrus.Infof("Updating %s CR", instance.Name)
if err := r.client.Update(context.Background(), instance); err != nil {
logrus.Errorf("Failed to update %s CR: %s", instance.Name, err)
@ -55,7 +54,7 @@ func (r *ReconcileChe) ReconcileFinalizer(instance *orgv1.CheCluster) (err error
return nil
}
func (r *ReconcileChe) DeleteFinalizer(instance *orgv1.CheCluster) (err error) {
func (r *ReconcileChe) DeleteOAuthFinalizer(instance *orgv1.CheCluster) (err error) {
instance.ObjectMeta.Finalizers = util.DoRemoveString(instance.ObjectMeta.Finalizers, oAuthFinalizerName)
logrus.Infof("Removing OAuth finalizer on %s CR", instance.Name)
if err := r.client.Update(context.Background(), instance); err != nil {

View File

@ -0,0 +1,61 @@
//
// Copyright (c) 2012-2020 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: // Contributors:
// Red Hat, Inc. - initial API and implementation // Red Hat, Inc. - initial API and implementation
//
package che
import (
"fmt"
"github.com/eclipse/che-operator/pkg/util"
authorizationv1 "k8s.io/api/authorization/v1"
rbac "k8s.io/api/rbac/v1"
)
type PermissionChecker interface {
GetNotPermittedPolicyRules(policies []rbac.PolicyRule, namespace string) ([]rbac.PolicyRule, error)
}
type K8sApiPermissionChecker struct {
}
func (pc *K8sApiPermissionChecker) GetNotPermittedPolicyRules(policies []rbac.PolicyRule, namespace string) ([]rbac.PolicyRule, error) {
var notPermittedPolicyRules []rbac.PolicyRule = []rbac.PolicyRule{}
for _, policy := range policies {
for _, apiGroup := range policy.APIGroups {
for _, verb := range policy.Verbs {
for _, resource := range policy.Resources {
resourceAttribute := &authorizationv1.ResourceAttributes{
Namespace: namespace,
Verb: verb,
Group: apiGroup,
Resource: resource,
}
ok, err := util.K8sclient.IsResourceOperationPermitted(resourceAttribute)
if err != nil {
return notPermittedPolicyRules, fmt.Errorf("failed to check policy rule: %v", policy)
}
if !ok {
if len(notPermittedPolicyRules) == 0 {
notPermittedPolicyRules = append(notPermittedPolicyRules, policy)
} else {
lastNotPermittedRule := notPermittedPolicyRules[len(notPermittedPolicyRules)-1]
if lastNotPermittedRule.Resources[0] != policy.Resources[0] && lastNotPermittedRule.APIGroups[0] != policy.APIGroups[0] {
notPermittedPolicyRules = append(notPermittedPolicyRules, policy)
}
}
}
}
}
}
}
return notPermittedPolicyRules, nil
}

View File

@ -13,7 +13,7 @@ package che
import (
"context"
"github.com/eclipse/che-operator/pkg/deploy"
"github.com/eclipse/che-operator/pkg/deploy"
orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
identity_provider "github.com/eclipse/che-operator/pkg/deploy/identity-provider"

View File

@ -0,0 +1,378 @@
//
// 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: // Contributors:
// Red Hat, Inc. - initial API and implementation // Red Hat, Inc. - initial API and implementation
//
package che
import (
"fmt"
"time"
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"
rbac "k8s.io/api/rbac/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
const (
// EditClusterRoleName - default "edit" cluster role. This role is pre-created on the cluster.
// See more: https://kubernetes.io/blog/2017/10/using-rbac-generally-available-18/#granting-access-to-users
EditClusterRoleName = "edit"
// EditRoleBindingName - "edit" rolebinding for che-server.
EditRoleBindingName = "che"
// CheWorkspacesServiceAccount - service account created for Che workspaces.
CheWorkspacesServiceAccount = "che-workspace"
// ViewRoleBindingName - "view" role for "che-workspace" service account.
ViewRoleBindingName = "che-workspace-view"
// ExecRoleBindingName - "exec" role for "che-workspace" service account.
ExecRoleBindingName = "che-workspace-exec"
// CheWorkspacesNamespaceClusterRoleNameTemplate - manage namespaces "cluster role" and "clusterrolebinding" template name
CheWorkspacesNamespaceClusterRoleNameTemplate = "%s-cheworkspaces-namespaces-clusterrole"
// CheWorkspacesClusterRoleNameTemplate - manage workspaces "cluster role" and "clusterrolebinding" template name
CheWorkspacesClusterRoleNameTemplate = "%s-cheworkspaces-clusterrole"
)
// delegateWorkspacePermissionsInTheSameNamespaceWithChe - creates "che-workspace" service account(for Che workspaces) and
// delegates "che-operator" SA permissions to the service accounts: "che" and "che-workspace".
// Also this method binds "edit" default k8s clusterrole using rolebinding to "che" SA.
func (r *ReconcileChe) delegateWorkspacePermissionsInTheSameNamespaceWithChe(deployContext *deploy.DeployContext) (reconcile.Result, error) {
tests := r.tests
// Create "che-workspace" service account.
// Che workspace components use this service account.
cheWorkspaceSA, err := deploy.SyncServiceAccountToCluster(deployContext, CheWorkspacesServiceAccount)
if cheWorkspaceSA == nil {
logrus.Infof("Waiting on service account '%s' to be created", CheWorkspacesServiceAccount)
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{}, err
}
}
// Create view role for "che-workspace" service account.
// This role used by exec terminals, tasks, metric che-theia plugin and so on.
viewRole, err := deploy.SyncViewRoleToCluster(deployContext)
if viewRole == nil {
logrus.Infof("Waiting on role '%s' to be created", deploy.ViewRoleName)
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{}, err
}
}
cheWSViewRoleBinding, err := deploy.SyncRoleBindingToCluster(deployContext, ViewRoleBindingName, CheWorkspacesServiceAccount, deploy.ViewRoleName, "Role")
if cheWSViewRoleBinding == nil {
logrus.Infof("Waiting on role binding '%s' to be created", ViewRoleBindingName)
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{}, err
}
}
// Create exec role for "che-workspaces" service account.
// This role used by exec terminals, tasks and so on.
execRole, err := deploy.SyncExecRoleToCluster(deployContext)
if execRole == nil {
logrus.Infof("Waiting on role '%s' to be created", deploy.ExecRoleName)
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{}, err
}
}
cheWSExecRoleBinding, err := deploy.SyncRoleBindingToCluster(deployContext, ExecRoleBindingName, CheWorkspacesServiceAccount, deploy.ExecRoleName, "Role")
if cheWSExecRoleBinding == nil {
logrus.Infof("Waiting on role binding '%s' to be created", ExecRoleBindingName)
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{}, err
}
}
// Bind "edit" cluster role for "che" service account.
// che-operator doesn't create "edit" role. This role is pre-created on the cluster.
// Warning: operator binds clusterrole using rolebinding(not clusterrolebinding).
// That's why "che" service account has got permissions only in the one namespace!
// So permissions are binding in "non-cluster" scope.
cheRoleBinding, err := deploy.SyncRoleBindingToCluster(deployContext, EditRoleBindingName, CheServiceAccountName, EditClusterRoleName, "ClusterRole")
if cheRoleBinding == nil {
logrus.Infof("Waiting on role binding '%s' to be created", EditRoleBindingName)
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{}, err
}
}
return reconcile.Result{}, nil
}
// Create cluster roles and cluster role bindings for "che" service account.
// che-server uses "che" service account for creation new workspaces and workspace components.
// Operator will create two cluster roles:
// - "<workspace-namespace/project-name>-cheworkspaces-namespaces-clusterrole" - cluster role to mange namespace(for Kubernetes platform)
// or project(for Openshift platform) for new workspace.
// - "<workspace-namespace/project-name>-cheworkspaces-clusterrole" - cluster role to create and manage k8s objects required for
// workspace components.
// Notice: After permission delegation che-server will create service account "che-workspace" ITSELF with
// "exec" and "view" roles for each new workspace.
func (r *ReconcileChe) delegateWorkspacePermissionsInTheDifferNamespaceThanChe(instance *orgv1.CheCluster, deployContext *deploy.DeployContext) (reconcile.Result, error) {
tests := r.tests
сheWorkspacesNamespaceClusterRoleName := fmt.Sprintf(CheWorkspacesNamespaceClusterRoleNameTemplate, instance.Namespace)
сheWorkspacesNamespaceClusterRoleBindingName := сheWorkspacesNamespaceClusterRoleName
// Create clusterrole "<workspace-namespace/project-name>-clusterrole-manage-namespaces" to manage namespace/projects for Che workspaces.
provisioned, err := deploy.SyncClusterRoleToCheCluster(deployContext, сheWorkspacesNamespaceClusterRoleName, getCheWorkspacesNamespacePolicy())
if !provisioned {
logrus.Infof("Waiting on clusterrole '%s' to be created", сheWorkspacesNamespaceClusterRoleName)
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{RequeueAfter: time.Second}, err
}
}
сheWorkspacesNamespaceClusterRoleBinding, err := deploy.SyncClusterRoleBindingToCluster(deployContext, сheWorkspacesNamespaceClusterRoleBindingName, CheServiceAccountName, сheWorkspacesNamespaceClusterRoleName)
if сheWorkspacesNamespaceClusterRoleBinding == nil {
logrus.Infof("Waiting on clusterrolebinding '%s' to be created", сheWorkspacesNamespaceClusterRoleBindingName)
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{RequeueAfter: time.Second}, err
}
}
сheWorkspacesClusterRoleName := fmt.Sprintf(CheWorkspacesClusterRoleNameTemplate, instance.Namespace)
сheWorkspacesClusterRoleBindingName := сheWorkspacesClusterRoleName
// Create clusterrole "<workspace-namespace/project-name>-cheworkspaces-namespaces-clusterrole" to create k8s components for Che workspaces.
provisioned, err = deploy.SyncClusterRoleToCheCluster(deployContext, сheWorkspacesClusterRoleName, getCheWorkspacesPolicy())
if !provisioned {
logrus.Infof("Waiting on clusterrole '%s' to be created", сheWorkspacesClusterRoleName)
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{RequeueAfter: time.Second}, err
}
}
cheManageNamespacesRolebinding, err := deploy.SyncClusterRoleBindingToCluster(deployContext, сheWorkspacesClusterRoleBindingName, CheServiceAccountName, сheWorkspacesClusterRoleName)
if cheManageNamespacesRolebinding == nil {
logrus.Infof("Waiting on clusterrolebinding '%s' to be created", сheWorkspacesClusterRoleBindingName)
if err != nil {
logrus.Error(err)
}
if !tests {
return reconcile.Result{RequeueAfter: time.Second}, err
}
}
return reconcile.Result{}, nil
}
// DeleteWorkspacesInSameNamespaceWithChePermissions - removes workspaces in same namespace with Che role and rolebindings.
func (r *ReconcileChe) DeleteWorkspacesInSameNamespaceWithChePermissions(instance *orgv1.CheCluster, cli client.Client) error {
if err := deploy.DeleteRole(deploy.ExecRoleName, instance.Namespace, cli); err != nil {
return err
}
if err := deploy.DeleteRoleBinding(ExecRoleBindingName, instance.Namespace, cli); err != nil {
return err
}
if err := deploy.DeleteRole(deploy.ViewRoleName, instance.Namespace, cli); err != nil {
return err
}
if err := deploy.DeleteRoleBinding(ViewRoleBindingName, instance.Namespace, cli); err != nil {
return err
}
if err := deploy.DeleteRoleBinding(EditRoleBindingName, instance.Namespace, cli); err != nil {
return err
}
return nil
}
func (r *ReconcileChe) reconcileWorkspacePermissionsFinalizer(instance *orgv1.CheCluster, deployContext *deploy.DeployContext) error {
if !util.IsOAuthEnabled(instance) {
if util.IsWorkspaceInSameNamespaceWithChe(instance) {
// Delete workspaces cluster permission set and finalizer from CR if deletion timestamp is not 0.
if err := r.RemoveCheWorkspacesClusterPermissions(instance); err != nil {
logrus.Errorf("workspace permissions finalizers was not removed from CR, cause %s", err.Error())
return err
}
} else {
// Delete permission set for configuration "same namespace for Che and workspaces".
if err := r.DeleteWorkspacesInSameNamespaceWithChePermissions(instance, deployContext.ClusterAPI.Client); err != nil {
logrus.Errorf("unable to delete workspaces in same namespace permission set, cause %s", err.Error())
return err
}
// Add workspaces cluster permission finalizer to the CR if deletion timestamp is 0.
// Or delete workspaces cluster permission set and finalizer from CR if deletion timestamp is not 0.
if err := r.ReconcileCheWorkspacesClusterPermissionsFinalizer(instance); err != nil {
logrus.Errorf("unable to add workspace permissions finalizers to the CR, cause %s", err.Error())
return err
}
}
} else {
// Delete workspaces cluster permission set and finalizer from CR if deletion timestamp is not 0.
if err := r.RemoveCheWorkspacesClusterPermissions(instance); err != nil {
logrus.Errorf("workspace permissions finalizers was not removed from CR, cause %s", err.Error())
return err
}
}
return nil
}
func getCheWorkspacesNamespacePolicy() []rbac.PolicyRule {
k8sPolicies := []rbac.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"namespaces"},
Verbs: []string{"get", "create", "update"},
},
}
openshiftPolicies := []rbac.PolicyRule{
{
APIGroups: []string{"project.openshift.io"},
Resources: []string{"projectrequests"},
Verbs: []string{"create", "update"},
},
{
APIGroups: []string{"project.openshift.io"},
Resources: []string{"projects"},
Verbs: []string{"get"},
},
}
if util.IsOpenShift {
return append(k8sPolicies, openshiftPolicies...)
}
return k8sPolicies
}
func getCheWorkspacesPolicy() []rbac.PolicyRule {
k8sPolicies := []rbac.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"serviceaccounts"},
Verbs: []string{"get", "create", "watch"},
},
{
APIGroups: []string{""},
Resources: []string{"pods/exec"},
Verbs: []string{"create"},
},
{
APIGroups: []string{""},
Resources: []string{"persistentvolumeclaims", "configmaps"},
Verbs: []string{"list"},
},
{
APIGroups: []string{""},
Resources: []string{"secrets"},
Verbs: []string{"list", "create", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"persistentvolumeclaims"},
Verbs: []string{"get", "create", "watch"},
},
{
APIGroups: []string{""},
Resources: []string{"pods"},
Verbs: []string{"get", "create", "list", "watch", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"services"},
Verbs: []string{"create", "list", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"configmaps"},
Verbs: []string{"get", "create", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"events"},
Verbs: []string{"watch"},
},
{
APIGroups: []string{"apps"},
Resources: []string{"secrets"},
Verbs: []string{"list"},
},
{
APIGroups: []string{"apps"},
Resources: []string{"deployments"},
Verbs: []string{"get", "create", "list", "watch", "patch", "delete"},
},
{
APIGroups: []string{"apps"},
Resources: []string{"replicasets"},
Verbs: []string{"list", "get", "patch", "delete"},
},
{
APIGroups: []string{"extensions"},
Resources: []string{"ingresses"},
Verbs: []string{"list", "create", "watch", "get", "delete"},
},
{
APIGroups: []string{"rbac.authorization.k8s.io"},
Resources: []string{"roles"},
Verbs: []string{"get", "create"},
},
{
APIGroups: []string{"rbac.authorization.k8s.io"},
Resources: []string{"rolebindings"},
Verbs: []string{"get", "update", "create"},
},
}
openshiftPolicies := []rbac.PolicyRule{
{
APIGroups: []string{"route.openshift.io"},
Resources: []string{"routes"},
Verbs: []string{"list", "create", "delete"},
},
{
APIGroups: []string{"authorization.openshift.io"},
Resources: []string{"roles"},
Verbs: []string{"get", "create"},
},
{
APIGroups: []string{"authorization.openshift.io"},
Resources: []string{"rolebindings"},
Verbs: []string{"get", "update", "create"},
},
}
if util.IsOpenShift {
return append(k8sPolicies, openshiftPolicies...)
}
return k8sPolicies
}

110
pkg/deploy/clusterrole.go Normal file
View File

@ -0,0 +1,110 @@
//
// 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 (
"context"
"fmt"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/sirupsen/logrus"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
runtimeClient "sigs.k8s.io/controller-runtime/pkg/client"
)
var clusterRoleDiffOpts = cmp.Options{
cmpopts.IgnoreFields(rbac.ClusterRole{}, "TypeMeta", "ObjectMeta"),
cmpopts.IgnoreFields(rbac.PolicyRule{}, "ResourceNames", "NonResourceURLs"),
}
func SyncClusterRoleToCheCluster(
deployContext *DeployContext,
name string,
policyRule []rbac.PolicyRule) (bool, error) {
specClusterRole, err := getSpecClusterRole(deployContext, name, policyRule)
if err != nil {
return false, err
}
clusterRole, err := GetClusterRole(specClusterRole.Name, deployContext.ClusterAPI.Client)
if err != nil {
return false, err
}
if clusterRole == nil {
logrus.Infof("Creating a new object: %s, name %s", specClusterRole.Kind, specClusterRole.Name)
err := deployContext.ClusterAPI.Client.Create(context.TODO(), specClusterRole)
return false, err
}
diff := cmp.Diff(clusterRole, specClusterRole, clusterRoleDiffOpts)
if len(diff) > 0 {
logrus.Infof("Updating existed object: %s, name: %s", clusterRole.Kind, clusterRole.Name)
fmt.Printf("Difference:\n%s", diff)
clusterRole.Rules = specClusterRole.Rules
err := deployContext.ClusterAPI.Client.Update(context.TODO(), clusterRole)
return false, err
}
return true, nil
}
func GetClusterRole(name string, client runtimeClient.Client) (*rbac.ClusterRole, error) {
clusterRole := &rbac.ClusterRole{}
namespacedName := types.NamespacedName{
Name: name,
}
err := client.Get(context.TODO(), namespacedName, clusterRole)
if err != nil {
if errors.IsNotFound(err) {
return nil, nil
}
return nil, err
}
return clusterRole, nil
}
func getSpecClusterRole(deployContext *DeployContext, name string, policyRule []rbac.PolicyRule) (*rbac.ClusterRole, error) {
labels := GetLabels(deployContext.CheCluster, DefaultCheFlavor(deployContext.CheCluster))
clusterRole := &rbac.ClusterRole{
TypeMeta: metav1.TypeMeta{
Kind: "ClusterRole",
APIVersion: rbac.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: labels,
},
Rules: policyRule,
}
return clusterRole, nil
}
func DeleteClusterRole(clusterRoleName string, client runtimeClient.Client) error {
clusterRole := &rbac.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: clusterRoleName,
},
}
err := client.Delete(context.TODO(), clusterRole)
if err != nil && !errors.IsNotFound(err) {
return err
}
return nil
}

View File

@ -40,7 +40,7 @@ func SyncClusterRoleBindingToCluster(
return nil, err
}
clusterRB, err := getClusterRoleBiding(specCRB.Name, deployContext.ClusterAPI.Client)
clusterRB, err := GetClusterRoleBiding(specCRB.Name, deployContext.ClusterAPI.Client)
if err != nil {
return nil, err
}
@ -64,7 +64,7 @@ func SyncClusterRoleBindingToCluster(
return clusterRB, nil
}
func getClusterRoleBiding(name string, client runtimeClient.Client) (*rbac.ClusterRoleBinding, error) {
func GetClusterRoleBiding(name string, client runtimeClient.Client) (*rbac.ClusterRoleBinding, error) {
clusterRoleBinding := &rbac.ClusterRoleBinding{}
crbName := types.NamespacedName{Name: name}
err := client.Get(context.TODO(), crbName, clusterRoleBinding)
@ -112,3 +112,17 @@ func getSpecClusterRoleBinding(
return clusterRoleBinding, nil
}
func DeleteClusterRoleBinding(clusterRoleBindingName string, client runtimeClient.Client) error {
clusterRoleBinding := &rbac.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: clusterRoleBindingName,
},
}
err := client.Delete(context.TODO(), clusterRoleBinding)
if err != nil && !errors.IsNotFound(err) {
return err
}
return nil
}

View File

@ -26,6 +26,13 @@ import (
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
const (
// ViewRoleName role to get k8s object needed for Workspace components(metrics plugin, Che terminals, tasks etc.)
ViewRoleName = "view"
// ExecRoleName - role name to create Che terminals and tasks in the workspace.
ExecRoleName = "exec"
)
var roleDiffOpts = cmp.Options{
cmpopts.IgnoreFields(rbac.Role{}, "TypeMeta", "ObjectMeta"),
cmpopts.IgnoreFields(rbac.PolicyRule{}, "ResourceNames", "NonResourceURLs"),
@ -62,7 +69,7 @@ func SyncExecRoleToCluster(deployContext *DeployContext) (*rbac.Role, error) {
},
},
}
return SyncRoleToCluster(deployContext, "exec", execPolicyRule)
return SyncRoleToCluster(deployContext, ExecRoleName, execPolicyRule)
}
func SyncViewRoleToCluster(deployContext *DeployContext) (*rbac.Role, error) {
@ -90,7 +97,7 @@ func SyncViewRoleToCluster(deployContext *DeployContext) (*rbac.Role, error) {
},
},
}
return SyncRoleToCluster(deployContext, "view", viewPolicyRule)
return SyncRoleToCluster(deployContext, ViewRoleName, viewPolicyRule)
}
func SyncRoleToCluster(
@ -103,7 +110,7 @@ func SyncRoleToCluster(
return nil, err
}
clusterRole, err := getClusterRole(specRole.Name, specRole.Namespace, deployContext.ClusterAPI.Client)
clusterRole, err := getCheClusterRole(specRole.Name, specRole.Namespace, deployContext.ClusterAPI.Client)
if err != nil {
return nil, err
}
@ -126,7 +133,7 @@ func SyncRoleToCluster(
return clusterRole, nil
}
func getClusterRole(name string, namespace string, client runtimeClient.Client) (*rbac.Role, error) {
func getCheClusterRole(name string, namespace string, client runtimeClient.Client) (*rbac.Role, error) {
role := &rbac.Role{}
namespacedName := types.NamespacedName{
Namespace: namespace,
@ -164,3 +171,17 @@ func getSpecRole(deployContext *DeployContext, name string, policyRule []rbac.Po
return role, nil
}
func DeleteRole(name string, namespace string, client runtimeClient.Client) error {
role := &rbac.Role{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
},
}
err := client.Delete(context.TODO(), role)
if err != nil && !errors.IsNotFound(err) {
return err
}
return nil
}

View File

@ -104,3 +104,18 @@ func getSpecRoleBinding(
return roleBinding, nil
}
func DeleteRoleBinding(name string, namespace string, client runtimeClient.Client) error {
roleBinding := &rbac.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
},
}
err := client.Delete(context.TODO(), roleBinding)
if err != nil && !errors.IsNotFound(err) {
return err
}
return nil
}

View File

@ -115,17 +115,12 @@ func GetCheConfigMapData(deployContext *deploy.DeployContext) (cheEnv map[string
}
tls := "false"
openShiftIdentityProviderId := "NULL"
defaultTargetNamespaceDefault := deployContext.CheCluster.Namespace // By default Che SA has right in the namespace where Che in installed ...
if isOpenShift && util.IsOAuthEnabled(deployContext.CheCluster) {
// ... But if the workspace is created under the openshift identity of the end-user,
// Then we'll have rights to create any new namespace
defaultTargetNamespaceDefault = "<username>-" + cheFlavor
openShiftIdentityProviderId = "openshift-v3"
if isOpenshift4 {
openShiftIdentityProviderId = "openshift-v4"
}
}
defaultTargetNamespace := util.GetValue(deployContext.CheCluster.Spec.Server.WorkspaceNamespaceDefault, defaultTargetNamespaceDefault)
namespaceAllowUserDefined := strconv.FormatBool(deployContext.CheCluster.Spec.Server.AllowUserDefinedWorkspaceNamespaces)
tlsSupport := deployContext.CheCluster.Spec.Server.TlsSupport
protocol := "http"
@ -180,6 +175,7 @@ func GetCheConfigMapData(deployContext *deploy.DeployContext) (cheEnv map[string
cheMultiUser := deploy.GetCheMultiUser(deployContext.CheCluster)
workspaceExposure := deploy.GetSingleHostExposureType(deployContext.CheCluster)
singleHostGatewayConfigMapLabels := labels.FormatLabels(util.GetMapValue(deployContext.CheCluster.Spec.Server.SingleHostGatewayConfigMapLabels, deploy.DefaultSingleHostGatewayConfigMapLabels))
workspaceNamespaceDefault := util.GetWorkspaceNamespaceDefault(deployContext.CheCluster)
cheAPI := protocol + "://" + cheHost + "/api"
var keycloakInternalURL, pluginRegistryInternalURL, devfileRegistryInternalURL, cheInternalAPI string
@ -219,7 +215,7 @@ func GetCheConfigMapData(deployContext *deploy.DeployContext) (cheEnv map[string
CheDebugServer: cheDebug,
CheInfrastructureActive: infra,
CheInfraKubernetesServiceAccountName: "che-workspace",
DefaultTargetNamespace: defaultTargetNamespace,
DefaultTargetNamespace: workspaceNamespaceDefault,
NamespaceAllowUserDefined: namespaceAllowUserDefined,
PvcStrategy: pvcStrategy,
PvcClaimSize: pvcClaimSize,
@ -284,10 +280,7 @@ func GetCheConfigMapData(deployContext *deploy.DeployContext) (cheEnv map[string
// Add TLS key and server certificate to properties when user workspaces should be created in another
// than Che server namespace, from where the Che TLS secret is not accessable
k8sDefaultNamespace := deployContext.CheCluster.Spec.Server.CustomCheProperties["CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"]
if (defaultTargetNamespace != "" && defaultTargetNamespace != deployContext.CheCluster.Namespace) ||
(k8sDefaultNamespace != "" && k8sDefaultNamespace != deployContext.CheCluster.Namespace) {
if !util.IsWorkspaceInSameNamespaceWithChe(deployContext.CheCluster) {
cheTLSSecret, err := deploy.GetClusterSecret(deployContext.CheCluster.Spec.K8s.TlsSecretName, deployContext.CheCluster.ObjectMeta.Namespace, deployContext.ClusterAPI)
if err != nil {
return nil, err

View File

@ -18,6 +18,7 @@ import (
v1 "github.com/eclipse/che-operator/pkg/apis/org/v1"
"github.com/sirupsen/logrus"
authorizationv1 "k8s.io/api/authorization/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
@ -220,3 +221,18 @@ func (cl *k8s) RunExec(command []string, podName, namespace string) (string, str
return stdout.String(), stderr.String(), nil
}
func (cl *k8s) IsResourceOperationPermitted(resourceAttr *authorizationv1.ResourceAttributes) (ok bool, err error) {
lsar := &authorizationv1.SelfSubjectAccessReview{
Spec: authorizationv1.SelfSubjectAccessReviewSpec{
ResourceAttributes: resourceAttr,
},
}
ssar, err := cl.clientset.AuthorizationV1().SelfSubjectAccessReviews().Create(lsar)
if err != nil {
return false, err
}
return ssar.Status.Allowed, nil
}

View File

@ -347,7 +347,7 @@ func NewBoolPointer(value bool) *bool {
return &variable
}
// IsOAuthEnabled return true when oAuth is enable for CheCluster resource, otherwise false.
// IsOAuthEnabled returns true when oAuth is enable for CheCluster resource, otherwise false.
func IsOAuthEnabled(c *orgv1.CheCluster) bool {
if c.Spec.Auth.OpenShiftoAuth != nil && *c.Spec.Auth.OpenShiftoAuth {
return true
@ -355,6 +355,27 @@ func IsOAuthEnabled(c *orgv1.CheCluster) bool {
return false
}
// IsWorkspaceInSameNamespaceWithChe return true when Che workspaces will be executed in the same namespace with Che, otherwise returns false.
func IsWorkspaceInSameNamespaceWithChe(cr *orgv1.CheCluster) bool {
return GetWorkspaceNamespaceDefault(cr) == cr.Namespace
}
// GetWorkspaceNamespaceDefault - returns workspace namespace default strategy, which points on the namespaces used for workspaces execution.
func GetWorkspaceNamespaceDefault(cr *orgv1.CheCluster) string {
if cr.Spec.Server.CustomCheProperties != nil {
k8sNamespaceDefault := cr.Spec.Server.CustomCheProperties["CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"]
if k8sNamespaceDefault != "" {
return k8sNamespaceDefault
}
}
workspaceNamespaceDefault := cr.Namespace
if IsOpenShift && IsOAuthEnabled(cr) {
workspaceNamespaceDefault = "<username>-" + cr.Spec.Server.CheFlavor
}
return GetValue(cr.Spec.Server.WorkspaceNamespaceDefault, workspaceNamespaceDefault)
}
func GetResourceQuantity(value string, defaultValue string) resource.Quantity {
if value != "" {
return resource.MustParse(value)

12
vendor/github.com/golang/mock/AUTHORS generated vendored Normal file
View File

@ -0,0 +1,12 @@
# This is the official list of GoMock authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS files.
# See the latter for an explanation.
# Names should be added to this file as
# Name or Organization <email address>
# The email address is not required for organizations.
# Please keep the list sorted.
Alex Reece <awreece@gmail.com>
Google Inc.

37
vendor/github.com/golang/mock/CONTRIBUTORS generated vendored Normal file
View File

@ -0,0 +1,37 @@
# This is the official list of people who can contribute (and typically
# have contributed) code to the gomock repository.
# The AUTHORS file lists the copyright holders; this file
# lists people. For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# The submission process automatically checks to make sure
# that people submitting code are listed in this file (by email address).
#
# Names should be added to this file only after verifying that
# the individual or the individual's organization has agreed to
# the appropriate Contributor License Agreement, found here:
#
# http://code.google.com/legal/individual-cla-v1.0.html
# http://code.google.com/legal/corporate-cla-v1.0.html
#
# The agreement for individuals can be filled out on the web.
#
# When adding J Random Contributor's name to this file,
# either J's name or J's organization's name should be
# added to the AUTHORS file, depending on whether the
# individual or corporate CLA was used.
# Names should be added to this file like so:
# Name <email address>
#
# An entry with two email addresses specifies that the
# first address should be used in the submit logs and
# that the second address should be recognized as the
# same person when interacting with Rietveld.
# Please keep the list sorted.
Aaron Jacobs <jacobsa@google.com> <aaronjjacobs@gmail.com>
Alex Reece <awreece@gmail.com>
David Symonds <dsymonds@golang.org>
Ryan Barrett <ryanb@google.com>

202
vendor/github.com/golang/mock/LICENSE generated vendored Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

420
vendor/github.com/golang/mock/gomock/call.go generated vendored Normal file
View File

@ -0,0 +1,420 @@
// Copyright 2010 Google Inc.
//
// 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.
package gomock
import (
"fmt"
"reflect"
"strconv"
"strings"
)
// Call represents an expected call to a mock.
type Call struct {
t TestHelper // for triggering test failures on invalid call setup
receiver interface{} // the receiver of the method call
method string // the name of the method
methodType reflect.Type // the type of the method
args []Matcher // the args
origin string // file and line number of call setup
preReqs []*Call // prerequisite calls
// Expectations
minCalls, maxCalls int
numCalls int // actual number made
// actions are called when this Call is called. Each action gets the args and
// can set the return values by returning a non-nil slice. Actions run in the
// order they are created.
actions []func([]interface{}) []interface{}
}
// newCall creates a *Call. It requires the method type in order to support
// unexported methods.
func newCall(t TestHelper, receiver interface{}, method string, methodType reflect.Type, args ...interface{}) *Call {
t.Helper()
// TODO: check arity, types.
margs := make([]Matcher, len(args))
for i, arg := range args {
if m, ok := arg.(Matcher); ok {
margs[i] = m
} else if arg == nil {
// Handle nil specially so that passing a nil interface value
// will match the typed nils of concrete args.
margs[i] = Nil()
} else {
margs[i] = Eq(arg)
}
}
origin := callerInfo(3)
actions := []func([]interface{}) []interface{}{func([]interface{}) []interface{} {
// Synthesize the zero value for each of the return args' types.
rets := make([]interface{}, methodType.NumOut())
for i := 0; i < methodType.NumOut(); i++ {
rets[i] = reflect.Zero(methodType.Out(i)).Interface()
}
return rets
}}
return &Call{t: t, receiver: receiver, method: method, methodType: methodType,
args: margs, origin: origin, minCalls: 1, maxCalls: 1, actions: actions}
}
// AnyTimes allows the expectation to be called 0 or more times
func (c *Call) AnyTimes() *Call {
c.minCalls, c.maxCalls = 0, 1e8 // close enough to infinity
return c
}
// MinTimes requires the call to occur at least n times. If AnyTimes or MaxTimes have not been called, MinTimes also
// sets the maximum number of calls to infinity.
func (c *Call) MinTimes(n int) *Call {
c.minCalls = n
if c.maxCalls == 1 {
c.maxCalls = 1e8
}
return c
}
// MaxTimes limits the number of calls to n times. If AnyTimes or MinTimes have not been called, MaxTimes also
// sets the minimum number of calls to 0.
func (c *Call) MaxTimes(n int) *Call {
c.maxCalls = n
if c.minCalls == 1 {
c.minCalls = 0
}
return c
}
// DoAndReturn declares the action to run when the call is matched.
// The return values from this function are returned by the mocked function.
// It takes an interface{} argument to support n-arity functions.
func (c *Call) DoAndReturn(f interface{}) *Call {
// TODO: Check arity and types here, rather than dying badly elsewhere.
v := reflect.ValueOf(f)
c.addAction(func(args []interface{}) []interface{} {
vargs := make([]reflect.Value, len(args))
ft := v.Type()
for i := 0; i < len(args); i++ {
if args[i] != nil {
vargs[i] = reflect.ValueOf(args[i])
} else {
// Use the zero value for the arg.
vargs[i] = reflect.Zero(ft.In(i))
}
}
vrets := v.Call(vargs)
rets := make([]interface{}, len(vrets))
for i, ret := range vrets {
rets[i] = ret.Interface()
}
return rets
})
return c
}
// Do declares the action to run when the call is matched. The function's
// return values are ignored to retain backward compatibility. To use the
// return values call DoAndReturn.
// It takes an interface{} argument to support n-arity functions.
func (c *Call) Do(f interface{}) *Call {
// TODO: Check arity and types here, rather than dying badly elsewhere.
v := reflect.ValueOf(f)
c.addAction(func(args []interface{}) []interface{} {
vargs := make([]reflect.Value, len(args))
ft := v.Type()
for i := 0; i < len(args); i++ {
if args[i] != nil {
vargs[i] = reflect.ValueOf(args[i])
} else {
// Use the zero value for the arg.
vargs[i] = reflect.Zero(ft.In(i))
}
}
v.Call(vargs)
return nil
})
return c
}
// Return declares the values to be returned by the mocked function call.
func (c *Call) Return(rets ...interface{}) *Call {
c.t.Helper()
mt := c.methodType
if len(rets) != mt.NumOut() {
c.t.Fatalf("wrong number of arguments to Return for %T.%v: got %d, want %d [%s]",
c.receiver, c.method, len(rets), mt.NumOut(), c.origin)
}
for i, ret := range rets {
if got, want := reflect.TypeOf(ret), mt.Out(i); got == want {
// Identical types; nothing to do.
} else if got == nil {
// Nil needs special handling.
switch want.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
// ok
default:
c.t.Fatalf("argument %d to Return for %T.%v is nil, but %v is not nillable [%s]",
i, c.receiver, c.method, want, c.origin)
}
} else if got.AssignableTo(want) {
// Assignable type relation. Make the assignment now so that the generated code
// can return the values with a type assertion.
v := reflect.New(want).Elem()
v.Set(reflect.ValueOf(ret))
rets[i] = v.Interface()
} else {
c.t.Fatalf("wrong type of argument %d to Return for %T.%v: %v is not assignable to %v [%s]",
i, c.receiver, c.method, got, want, c.origin)
}
}
c.addAction(func([]interface{}) []interface{} {
return rets
})
return c
}
// Times declares the exact number of times a function call is expected to be executed.
func (c *Call) Times(n int) *Call {
c.minCalls, c.maxCalls = n, n
return c
}
// SetArg declares an action that will set the nth argument's value,
// indirected through a pointer. Or, in the case of a slice, SetArg
// will copy value's elements into the nth argument.
func (c *Call) SetArg(n int, value interface{}) *Call {
c.t.Helper()
mt := c.methodType
// TODO: This will break on variadic methods.
// We will need to check those at invocation time.
if n < 0 || n >= mt.NumIn() {
c.t.Fatalf("SetArg(%d, ...) called for a method with %d args [%s]",
n, mt.NumIn(), c.origin)
}
// Permit setting argument through an interface.
// In the interface case, we don't (nay, can't) check the type here.
at := mt.In(n)
switch at.Kind() {
case reflect.Ptr:
dt := at.Elem()
if vt := reflect.TypeOf(value); !vt.AssignableTo(dt) {
c.t.Fatalf("SetArg(%d, ...) argument is a %v, not assignable to %v [%s]",
n, vt, dt, c.origin)
}
case reflect.Interface:
// nothing to do
case reflect.Slice:
// nothing to do
default:
c.t.Fatalf("SetArg(%d, ...) referring to argument of non-pointer non-interface non-slice type %v [%s]",
n, at, c.origin)
}
c.addAction(func(args []interface{}) []interface{} {
v := reflect.ValueOf(value)
switch reflect.TypeOf(args[n]).Kind() {
case reflect.Slice:
setSlice(args[n], v)
default:
reflect.ValueOf(args[n]).Elem().Set(v)
}
return nil
})
return c
}
// isPreReq returns true if other is a direct or indirect prerequisite to c.
func (c *Call) isPreReq(other *Call) bool {
for _, preReq := range c.preReqs {
if other == preReq || preReq.isPreReq(other) {
return true
}
}
return false
}
// After declares that the call may only match after preReq has been exhausted.
func (c *Call) After(preReq *Call) *Call {
c.t.Helper()
if c == preReq {
c.t.Fatalf("A call isn't allowed to be its own prerequisite")
}
if preReq.isPreReq(c) {
c.t.Fatalf("Loop in call order: %v is a prerequisite to %v (possibly indirectly).", c, preReq)
}
c.preReqs = append(c.preReqs, preReq)
return c
}
// Returns true if the minimum number of calls have been made.
func (c *Call) satisfied() bool {
return c.numCalls >= c.minCalls
}
// Returns true iff the maximum number of calls have been made.
func (c *Call) exhausted() bool {
return c.numCalls >= c.maxCalls
}
func (c *Call) String() string {
args := make([]string, len(c.args))
for i, arg := range c.args {
args[i] = arg.String()
}
arguments := strings.Join(args, ", ")
return fmt.Sprintf("%T.%v(%s) %s", c.receiver, c.method, arguments, c.origin)
}
// Tests if the given call matches the expected call.
// If yes, returns nil. If no, returns error with message explaining why it does not match.
func (c *Call) matches(args []interface{}) error {
if !c.methodType.IsVariadic() {
if len(args) != len(c.args) {
return fmt.Errorf("Expected call at %s has the wrong number of arguments. Got: %d, want: %d",
c.origin, len(args), len(c.args))
}
for i, m := range c.args {
if !m.Matches(args[i]) {
return fmt.Errorf("Expected call at %s doesn't match the argument at index %s.\nGot: %v\nWant: %v",
c.origin, strconv.Itoa(i), args[i], m)
}
}
} else {
if len(c.args) < c.methodType.NumIn()-1 {
return fmt.Errorf("Expected call at %s has the wrong number of matchers. Got: %d, want: %d",
c.origin, len(c.args), c.methodType.NumIn()-1)
}
if len(c.args) != c.methodType.NumIn() && len(args) != len(c.args) {
return fmt.Errorf("Expected call at %s has the wrong number of arguments. Got: %d, want: %d",
c.origin, len(args), len(c.args))
}
if len(args) < len(c.args)-1 {
return fmt.Errorf("Expected call at %s has the wrong number of arguments. Got: %d, want: greater than or equal to %d",
c.origin, len(args), len(c.args)-1)
}
for i, m := range c.args {
if i < c.methodType.NumIn()-1 {
// Non-variadic args
if !m.Matches(args[i]) {
return fmt.Errorf("Expected call at %s doesn't match the argument at index %s.\nGot: %v\nWant: %v",
c.origin, strconv.Itoa(i), args[i], m)
}
continue
}
// The last arg has a possibility of a variadic argument, so let it branch
// sample: Foo(a int, b int, c ...int)
if i < len(c.args) && i < len(args) {
if m.Matches(args[i]) {
// Got Foo(a, b, c) want Foo(matcherA, matcherB, gomock.Any())
// Got Foo(a, b, c) want Foo(matcherA, matcherB, someSliceMatcher)
// Got Foo(a, b, c) want Foo(matcherA, matcherB, matcherC)
// Got Foo(a, b) want Foo(matcherA, matcherB)
// Got Foo(a, b, c, d) want Foo(matcherA, matcherB, matcherC, matcherD)
continue
}
}
// The number of actual args don't match the number of matchers,
// or the last matcher is a slice and the last arg is not.
// If this function still matches it is because the last matcher
// matches all the remaining arguments or the lack of any.
// Convert the remaining arguments, if any, into a slice of the
// expected type.
vargsType := c.methodType.In(c.methodType.NumIn() - 1)
vargs := reflect.MakeSlice(vargsType, 0, len(args)-i)
for _, arg := range args[i:] {
vargs = reflect.Append(vargs, reflect.ValueOf(arg))
}
if m.Matches(vargs.Interface()) {
// Got Foo(a, b, c, d, e) want Foo(matcherA, matcherB, gomock.Any())
// Got Foo(a, b, c, d, e) want Foo(matcherA, matcherB, someSliceMatcher)
// Got Foo(a, b) want Foo(matcherA, matcherB, gomock.Any())
// Got Foo(a, b) want Foo(matcherA, matcherB, someEmptySliceMatcher)
break
}
// Wrong number of matchers or not match. Fail.
// Got Foo(a, b) want Foo(matcherA, matcherB, matcherC, matcherD)
// Got Foo(a, b, c) want Foo(matcherA, matcherB, matcherC, matcherD)
// Got Foo(a, b, c, d) want Foo(matcherA, matcherB, matcherC, matcherD, matcherE)
// Got Foo(a, b, c, d, e) want Foo(matcherA, matcherB, matcherC, matcherD)
// Got Foo(a, b, c) want Foo(matcherA, matcherB)
return fmt.Errorf("Expected call at %s doesn't match the argument at index %s.\nGot: %v\nWant: %v",
c.origin, strconv.Itoa(i), args[i:], c.args[i])
}
}
// Check that all prerequisite calls have been satisfied.
for _, preReqCall := range c.preReqs {
if !preReqCall.satisfied() {
return fmt.Errorf("Expected call at %s doesn't have a prerequisite call satisfied:\n%v\nshould be called before:\n%v",
c.origin, preReqCall, c)
}
}
// Check that the call is not exhausted.
if c.exhausted() {
return fmt.Errorf("Expected call at %s has already been called the max number of times.", c.origin)
}
return nil
}
// dropPrereqs tells the expected Call to not re-check prerequisite calls any
// longer, and to return its current set.
func (c *Call) dropPrereqs() (preReqs []*Call) {
preReqs = c.preReqs
c.preReqs = nil
return
}
func (c *Call) call(args []interface{}) []func([]interface{}) []interface{} {
c.numCalls++
return c.actions
}
// InOrder declares that the given calls should occur in order.
func InOrder(calls ...*Call) {
for i := 1; i < len(calls); i++ {
calls[i].After(calls[i-1])
}
}
func setSlice(arg interface{}, v reflect.Value) {
va := reflect.ValueOf(arg)
for i := 0; i < v.Len(); i++ {
va.Index(i).Set(v.Index(i))
}
}
func (c *Call) addAction(action func([]interface{}) []interface{}) {
c.actions = append(c.actions, action)
}

108
vendor/github.com/golang/mock/gomock/callset.go generated vendored Normal file
View File

@ -0,0 +1,108 @@
// Copyright 2011 Google Inc.
//
// 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.
package gomock
import (
"bytes"
"fmt"
)
// callSet represents a set of expected calls, indexed by receiver and method
// name.
type callSet struct {
// Calls that are still expected.
expected map[callSetKey][]*Call
// Calls that have been exhausted.
exhausted map[callSetKey][]*Call
}
// callSetKey is the key in the maps in callSet
type callSetKey struct {
receiver interface{}
fname string
}
func newCallSet() *callSet {
return &callSet{make(map[callSetKey][]*Call), make(map[callSetKey][]*Call)}
}
// Add adds a new expected call.
func (cs callSet) Add(call *Call) {
key := callSetKey{call.receiver, call.method}
m := cs.expected
if call.exhausted() {
m = cs.exhausted
}
m[key] = append(m[key], call)
}
// Remove removes an expected call.
func (cs callSet) Remove(call *Call) {
key := callSetKey{call.receiver, call.method}
calls := cs.expected[key]
for i, c := range calls {
if c == call {
// maintain order for remaining calls
cs.expected[key] = append(calls[:i], calls[i+1:]...)
cs.exhausted[key] = append(cs.exhausted[key], call)
break
}
}
}
// FindMatch searches for a matching call. Returns error with explanation message if no call matched.
func (cs callSet) FindMatch(receiver interface{}, method string, args []interface{}) (*Call, error) {
key := callSetKey{receiver, method}
// Search through the expected calls.
expected := cs.expected[key]
var callsErrors bytes.Buffer
for _, call := range expected {
err := call.matches(args)
if err != nil {
fmt.Fprintf(&callsErrors, "\n%v", err)
} else {
return call, nil
}
}
// If we haven't found a match then search through the exhausted calls so we
// get useful error messages.
exhausted := cs.exhausted[key]
for _, call := range exhausted {
if err := call.matches(args); err != nil {
fmt.Fprintf(&callsErrors, "\n%v", err)
}
}
if len(expected)+len(exhausted) == 0 {
fmt.Fprintf(&callsErrors, "there are no expected calls of the method %q for that receiver", method)
}
return nil, fmt.Errorf(callsErrors.String())
}
// Failures returns the calls that are not satisfied.
func (cs callSet) Failures() []*Call {
failures := make([]*Call, 0, len(cs.expected))
for _, calls := range cs.expected {
for _, call := range calls {
if !call.satisfied() {
failures = append(failures, call)
}
}
}
return failures
}

264
vendor/github.com/golang/mock/gomock/controller.go generated vendored Normal file
View File

@ -0,0 +1,264 @@
// Copyright 2010 Google Inc.
//
// 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.
// Package gomock is a mock framework for Go.
//
// Standard usage:
// (1) Define an interface that you wish to mock.
// type MyInterface interface {
// SomeMethod(x int64, y string)
// }
// (2) Use mockgen to generate a mock from the interface.
// (3) Use the mock in a test:
// func TestMyThing(t *testing.T) {
// mockCtrl := gomock.NewController(t)
// defer mockCtrl.Finish()
//
// mockObj := something.NewMockMyInterface(mockCtrl)
// mockObj.EXPECT().SomeMethod(4, "blah")
// // pass mockObj to a real object and play with it.
// }
//
// By default, expected calls are not enforced to run in any particular order.
// Call order dependency can be enforced by use of InOrder and/or Call.After.
// Call.After can create more varied call order dependencies, but InOrder is
// often more convenient.
//
// The following examples create equivalent call order dependencies.
//
// Example of using Call.After to chain expected call order:
//
// firstCall := mockObj.EXPECT().SomeMethod(1, "first")
// secondCall := mockObj.EXPECT().SomeMethod(2, "second").After(firstCall)
// mockObj.EXPECT().SomeMethod(3, "third").After(secondCall)
//
// Example of using InOrder to declare expected call order:
//
// gomock.InOrder(
// mockObj.EXPECT().SomeMethod(1, "first"),
// mockObj.EXPECT().SomeMethod(2, "second"),
// mockObj.EXPECT().SomeMethod(3, "third"),
// )
//
// TODO:
// - Handle different argument/return types (e.g. ..., chan, map, interface).
package gomock
import (
"context"
"fmt"
"reflect"
"runtime"
"sync"
)
// A TestReporter is something that can be used to report test failures. It
// is satisfied by the standard library's *testing.T.
type TestReporter interface {
Errorf(format string, args ...interface{})
Fatalf(format string, args ...interface{})
}
// TestHelper is a TestReporter that has the Helper method. It is satisfied
// by the standard library's *testing.T.
type TestHelper interface {
TestReporter
Helper()
}
// A Controller represents the top-level control of a mock ecosystem. It
// defines the scope and lifetime of mock objects, as well as their
// expectations. It is safe to call Controller's methods from multiple
// goroutines. Each test should create a new Controller and invoke Finish via
// defer.
//
// func TestFoo(t *testing.T) {
// ctrl := gomock.NewController(st)
// defer ctrl.Finish()
// // ..
// }
//
// func TestBar(t *testing.T) {
// t.Run("Sub-Test-1", st) {
// ctrl := gomock.NewController(st)
// defer ctrl.Finish()
// // ..
// })
// t.Run("Sub-Test-2", st) {
// ctrl := gomock.NewController(st)
// defer ctrl.Finish()
// // ..
// })
// })
type Controller struct {
// T should only be called within a generated mock. It is not intended to
// be used in user code and may be changed in future versions. T is the
// TestReporter passed in when creating the Controller via NewController.
// If the TestReporter does not implement a TestHelper it will be wrapped
// with a nopTestHelper.
T TestHelper
mu sync.Mutex
expectedCalls *callSet
finished bool
}
// NewController returns a new Controller. It is the preferred way to create a
// Controller.
func NewController(t TestReporter) *Controller {
h, ok := t.(TestHelper)
if !ok {
h = nopTestHelper{t}
}
return &Controller{
T: h,
expectedCalls: newCallSet(),
}
}
type cancelReporter struct {
TestHelper
cancel func()
}
func (r *cancelReporter) Errorf(format string, args ...interface{}) {
r.TestHelper.Errorf(format, args...)
}
func (r *cancelReporter) Fatalf(format string, args ...interface{}) {
defer r.cancel()
r.TestHelper.Fatalf(format, args...)
}
// WithContext returns a new Controller and a Context, which is cancelled on any
// fatal failure.
func WithContext(ctx context.Context, t TestReporter) (*Controller, context.Context) {
h, ok := t.(TestHelper)
if !ok {
h = nopTestHelper{t}
}
ctx, cancel := context.WithCancel(ctx)
return NewController(&cancelReporter{h, cancel}), ctx
}
type nopTestHelper struct {
TestReporter
}
func (h nopTestHelper) Helper() {}
// RecordCall is called by a mock. It should not be called by user code.
func (ctrl *Controller) RecordCall(receiver interface{}, method string, args ...interface{}) *Call {
ctrl.T.Helper()
recv := reflect.ValueOf(receiver)
for i := 0; i < recv.Type().NumMethod(); i++ {
if recv.Type().Method(i).Name == method {
return ctrl.RecordCallWithMethodType(receiver, method, recv.Method(i).Type(), args...)
}
}
ctrl.T.Fatalf("gomock: failed finding method %s on %T", method, receiver)
panic("unreachable")
}
// RecordCallWithMethodType is called by a mock. It should not be called by user code.
func (ctrl *Controller) RecordCallWithMethodType(receiver interface{}, method string, methodType reflect.Type, args ...interface{}) *Call {
ctrl.T.Helper()
call := newCall(ctrl.T, receiver, method, methodType, args...)
ctrl.mu.Lock()
defer ctrl.mu.Unlock()
ctrl.expectedCalls.Add(call)
return call
}
// Call is called by a mock. It should not be called by user code.
func (ctrl *Controller) Call(receiver interface{}, method string, args ...interface{}) []interface{} {
ctrl.T.Helper()
// Nest this code so we can use defer to make sure the lock is released.
actions := func() []func([]interface{}) []interface{} {
ctrl.T.Helper()
ctrl.mu.Lock()
defer ctrl.mu.Unlock()
expected, err := ctrl.expectedCalls.FindMatch(receiver, method, args)
if err != nil {
origin := callerInfo(2)
ctrl.T.Fatalf("Unexpected call to %T.%v(%v) at %s because: %s", receiver, method, args, origin, err)
}
// Two things happen here:
// * the matching call no longer needs to check prerequite calls,
// * and the prerequite calls are no longer expected, so remove them.
preReqCalls := expected.dropPrereqs()
for _, preReqCall := range preReqCalls {
ctrl.expectedCalls.Remove(preReqCall)
}
actions := expected.call(args)
if expected.exhausted() {
ctrl.expectedCalls.Remove(expected)
}
return actions
}()
var rets []interface{}
for _, action := range actions {
if r := action(args); r != nil {
rets = r
}
}
return rets
}
// Finish checks to see if all the methods that were expected to be called
// were called. It should be invoked for each Controller. It is not idempotent
// and therefore can only be invoked once.
func (ctrl *Controller) Finish() {
ctrl.T.Helper()
ctrl.mu.Lock()
defer ctrl.mu.Unlock()
if ctrl.finished {
ctrl.T.Fatalf("Controller.Finish was called more than once. It has to be called exactly once.")
}
ctrl.finished = true
// If we're currently panicking, probably because this is a deferred call,
// pass through the panic.
if err := recover(); err != nil {
panic(err)
}
// Check that all remaining expected calls are satisfied.
failures := ctrl.expectedCalls.Failures()
for _, call := range failures {
ctrl.T.Errorf("missing call(s) to %v", call)
}
if len(failures) != 0 {
ctrl.T.Fatalf("aborting test due to missing call(s)")
}
}
func callerInfo(skip int) string {
if _, file, line, ok := runtime.Caller(skip + 1); ok {
return fmt.Sprintf("%s:%d", file, line)
}
return "unknown file"
}

141
vendor/github.com/golang/mock/gomock/matchers.go generated vendored Normal file
View File

@ -0,0 +1,141 @@
// Copyright 2010 Google Inc.
//
// 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.
package gomock
import (
"fmt"
"reflect"
)
// A Matcher is a representation of a class of values.
// It is used to represent the valid or expected arguments to a mocked method.
type Matcher interface {
// Matches returns whether x is a match.
Matches(x interface{}) bool
// String describes what the matcher matches.
String() string
}
type anyMatcher struct{}
func (anyMatcher) Matches(x interface{}) bool {
return true
}
func (anyMatcher) String() string {
return "is anything"
}
type eqMatcher struct {
x interface{}
}
func (e eqMatcher) Matches(x interface{}) bool {
return reflect.DeepEqual(e.x, x)
}
func (e eqMatcher) String() string {
return fmt.Sprintf("is equal to %v", e.x)
}
type nilMatcher struct{}
func (nilMatcher) Matches(x interface{}) bool {
if x == nil {
return true
}
v := reflect.ValueOf(x)
switch v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map,
reflect.Ptr, reflect.Slice:
return v.IsNil()
}
return false
}
func (nilMatcher) String() string {
return "is nil"
}
type notMatcher struct {
m Matcher
}
func (n notMatcher) Matches(x interface{}) bool {
return !n.m.Matches(x)
}
func (n notMatcher) String() string {
// TODO: Improve this if we add a NotString method to the Matcher interface.
return "not(" + n.m.String() + ")"
}
type assignableToTypeOfMatcher struct {
targetType reflect.Type
}
func (m assignableToTypeOfMatcher) Matches(x interface{}) bool {
return reflect.TypeOf(x).AssignableTo(m.targetType)
}
func (m assignableToTypeOfMatcher) String() string {
return "is assignable to " + m.targetType.Name()
}
// Constructors
// Any returns a matcher that always matches.
func Any() Matcher { return anyMatcher{} }
// Eq returns a matcher that matches on equality.
//
// Example usage:
// Eq(5).Matches(5) // returns true
// Eq(5).Matches(4) // returns false
func Eq(x interface{}) Matcher { return eqMatcher{x} }
// Nil returns a matcher that matches if the received value is nil.
//
// Example usage:
// var x *bytes.Buffer
// Nil().Matches(x) // returns true
// x = &bytes.Buffer{}
// Nil().Matches(x) // returns false
func Nil() Matcher { return nilMatcher{} }
// Not reverses the results of its given child matcher.
//
// Example usage:
// Not(Eq(5)).Matches(4) // returns true
// Not(Eq(5)).Matches(5) // returns false
func Not(x interface{}) Matcher {
if m, ok := x.(Matcher); ok {
return notMatcher{m}
}
return notMatcher{Eq(x)}
}
// AssignableToTypeOf is a Matcher that matches if the parameter to the mock
// function is assignable to the type of the parameter to this function.
//
// Example usage:
// var s fmt.Stringer = &bytes.Buffer{}
// AssignableToTypeOf(s).Matches(time.Second) // returns true
// AssignableToTypeOf(s).Matches(99) // returns false
func AssignableToTypeOf(x interface{}) Matcher {
return assignableToTypeOfMatcher{reflect.TypeOf(x)}
}

3
vendor/modules.txt vendored
View File

@ -30,6 +30,8 @@ github.com/gogo/protobuf/proto
github.com/gogo/protobuf/sortkeys
# github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9
github.com/golang/groupcache/lru
# github.com/golang/mock v1.3.1
github.com/golang/mock/gomock
# github.com/golang/protobuf v1.3.2
github.com/golang/protobuf/proto
github.com/golang/protobuf/ptypes
@ -408,7 +410,6 @@ sigs.k8s.io/controller-runtime/pkg/leaderelection
sigs.k8s.io/controller-runtime/pkg/log
sigs.k8s.io/controller-runtime/pkg/log/zap
sigs.k8s.io/controller-runtime/pkg/manager
sigs.k8s.io/controller-runtime/pkg/manager/signals
sigs.k8s.io/controller-runtime/pkg/metrics
sigs.k8s.io/controller-runtime/pkg/predicate
sigs.k8s.io/controller-runtime/pkg/reconcile

View File

@ -1,20 +0,0 @@
/*
Copyright 2017 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.
*/
// Package signals contains libraries for handling signals to gracefully
// shutdown the manager in combination with Kubernetes pod graceful termination
// policy.
package signals

View File

@ -1,43 +0,0 @@
/*
Copyright 2017 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.
*/
package signals
import (
"os"
"os/signal"
)
var onlyOneSignalHandler = make(chan struct{})
// SetupSignalHandler registers for SIGTERM and SIGINT. A stop channel is returned
// which is closed on one of these signals. If a second signal is caught, the program
// is terminated with exit code 1.
func SetupSignalHandler() (stopCh <-chan struct{}) {
close(onlyOneSignalHandler) // panics when called twice
stop := make(chan struct{})
c := make(chan os.Signal, 2)
signal.Notify(c, shutdownSignals...)
go func() {
<-c
close(stop)
<-c
os.Exit(1) // second signal. Exit directly.
}()
return stop
}

View File

@ -1,26 +0,0 @@
// +build !windows
/*
Copyright 2017 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.
*/
package signals
import (
"os"
"syscall"
)
var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM}

View File

@ -1,23 +0,0 @@
/*
Copyright 2017 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.
*/
package signals
import (
"os"
)
var shutdownSignals = []os.Signal{os.Interrupt}