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
parent
619c9d943b
commit
4ee509e08b
32
README.md
32
README.md
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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".
|
||||
|
|
|
|||
|
|
@ -23,4 +23,6 @@ rules:
|
|||
- namespaces
|
||||
verbs:
|
||||
- update
|
||||
- list
|
||||
- create
|
||||
- get
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -123,4 +123,4 @@ spec:
|
|||
cpu: 500m
|
||||
restartPolicy: Always
|
||||
serviceAccountName: che-operator
|
||||
terminationGracePeriodSeconds: 5
|
||||
terminationGracePeriodSeconds: 20
|
||||
|
|
|
|||
|
|
@ -88,7 +88,9 @@ rules:
|
|||
- apiGroups:
|
||||
- org.eclipse.che
|
||||
resources:
|
||||
- '*'
|
||||
- checlusters
|
||||
- checlusters/status
|
||||
- checlusters/finalizers
|
||||
verbs:
|
||||
- '*'
|
||||
- apiGroups:
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -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
1
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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>
|
||||
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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)}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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}
|
||||
|
|
@ -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}
|
||||
Loading…
Reference in New Issue