From 75da259f72a5f8f34995fc6da9c0339f0ca1035e Mon Sep 17 00:00:00 2001 From: Anatolii Bazko Date: Wed, 17 Feb 2021 14:32:04 +0200 Subject: [PATCH] Allow to configure github and bitbucket oauth config (#677) * Allow to configure github and bitbucket oauth config Signed-off-by: Anatolii Bazko --- Dockerfile | 2 +- .../che-operator.clusterserviceversion.yaml | 8 +- .../che-operator.clusterserviceversion.yaml | 8 +- deploy/operator.yaml | 2 +- go.sum | 1 + pkg/deploy/defaults.go | 9 +- pkg/deploy/deployment.go | 5 +- .../identity-provider/deployment_keycloak.go | 49 +++- .../deployment_keycloak_test.go | 103 ++++++++ .../identity-provider/identity_provider.go | 59 +++-- .../identity_provider_test.go | 38 ++- pkg/deploy/postgres/deployment_postgres.go | 16 +- pkg/deploy/secret.go | 42 ++++ pkg/deploy/sercet_test.go | 165 +++++++++++++ pkg/deploy/server/che_configmap.go | 20 ++ pkg/deploy/server/che_configmap_test.go | 233 ++++++++++++++---- pkg/deploy/server/deployment_che.go | 63 ++++- pkg/deploy/server/deployment_che_test.go | 124 ++++++++++ pkg/util/test_util.go | 30 ++- pkg/util/util.go | 11 + 20 files changed, 879 insertions(+), 109 deletions(-) create mode 100644 pkg/deploy/sercet_test.go diff --git a/Dockerfile b/Dockerfile index 43827b7ba..b5bf2416e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ RUN export ARCH="$(uname -m)" && if [[ ${ARCH} == "x86_64" ]]; then export ARCH= GOOS=linux GOARCH=${ARCH} CGO_ENABLED=0 go build -mod=vendor -o /tmp/che-operator/che-operator cmd/manager/main.go # https://access.redhat.com/containers/?tab=tags#/registry.access.redhat.com/ubi8-minimal -FROM registry.access.redhat.com/ubi8-minimal:8.3-230 +FROM registry.access.redhat.com/ubi8-minimal:8.3-291 COPY --from=builder /tmp/che-operator/che-operator /usr/local/bin/che-operator COPY --from=builder /che-operator/templates/keycloak-provision.sh /tmp/keycloak-provision.sh diff --git a/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/che-operator.clusterserviceversion.yaml b/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/che-operator.clusterserviceversion.yaml index 9e8283ff3..cc180c47f 100644 --- a/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/che-operator.clusterserviceversion.yaml +++ b/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/che-operator.clusterserviceversion.yaml @@ -85,13 +85,13 @@ metadata: categories: Developer Tools certified: "false" containerImage: quay.io/eclipse/che-operator:nightly - createdAt: "2021-02-15T16:16:52Z" + createdAt: "2021-02-17T11:12:17Z" 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.27.0-102.nightly + name: eclipse-che-preview-kubernetes.v7.27.0-104.nightly namespace: placeholder spec: apiservicedefinitions: {} @@ -482,7 +482,7 @@ spec: - name: RELATED_IMAGE_che_tls_secrets_creation_job value: quay.io/eclipse/che-tls-secret-creator:alpine-d1ed4ad - name: RELATED_IMAGE_pvc_jobs - value: registry.access.redhat.com/ubi8-minimal:8.3-230 + value: registry.access.redhat.com/ubi8-minimal:8.3-291 - name: RELATED_IMAGE_postgres value: quay.io/eclipse/che--centos--postgresql-96-centos7:9.6-b681d78125361519180a6ac05242c296f8906c11eab7e207b5ca9a89b6344392 - name: RELATED_IMAGE_keycloak @@ -685,4 +685,4 @@ spec: maturity: stable provider: name: Eclipse Foundation - version: 7.27.0-102.nightly + version: 7.27.0-104.nightly diff --git a/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/che-operator.clusterserviceversion.yaml b/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/che-operator.clusterserviceversion.yaml index 411b2ccb7..c1500e9e1 100644 --- a/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/che-operator.clusterserviceversion.yaml +++ b/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/che-operator.clusterserviceversion.yaml @@ -76,13 +76,13 @@ metadata: categories: Developer Tools, OpenShift Optional certified: "false" containerImage: quay.io/eclipse/che-operator:nightly - createdAt: "2021-02-15T16:16:59Z" + createdAt: "2021-02-17T11:12:27Z" 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.27.0-102.nightly + name: eclipse-che-preview-openshift.v7.27.0-104.nightly namespace: placeholder spec: apiservicedefinitions: {} @@ -548,7 +548,7 @@ spec: - name: RELATED_IMAGE_devfile_registry value: quay.io/eclipse/che-devfile-registry:nightly - name: RELATED_IMAGE_pvc_jobs - value: registry.access.redhat.com/ubi8-minimal:8.3-230 + value: registry.access.redhat.com/ubi8-minimal:8.3-291 - name: RELATED_IMAGE_postgres value: quay.io/eclipse/che--centos--postgresql-96-centos7:9.6-b681d78125361519180a6ac05242c296f8906c11eab7e207b5ca9a89b6344392 - name: RELATED_IMAGE_keycloak @@ -757,4 +757,4 @@ spec: maturity: stable provider: name: Eclipse Foundation - version: 7.27.0-102.nightly + version: 7.27.0-104.nightly diff --git a/deploy/operator.yaml b/deploy/operator.yaml index 0d4592bce..bd5a094c5 100644 --- a/deploy/operator.yaml +++ b/deploy/operator.yaml @@ -58,7 +58,7 @@ spec: - name: RELATED_IMAGE_che_tls_secrets_creation_job value: quay.io/eclipse/che-tls-secret-creator:alpine-d1ed4ad - name: RELATED_IMAGE_pvc_jobs - value: registry.access.redhat.com/ubi8-minimal:8.3-230 + value: registry.access.redhat.com/ubi8-minimal:8.3-291 - name: RELATED_IMAGE_postgres value: quay.io/eclipse/che--centos--postgresql-96-centos7:9.6-b681d78125361519180a6ac05242c296f8906c11eab7e207b5ca9a89b6344392 - name: RELATED_IMAGE_keycloak diff --git a/go.sum b/go.sum index df02cbff7..970e1e533 100644 --- a/go.sum +++ b/go.sum @@ -1014,6 +1014,7 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6 h1:UXl+Zk3jqqcbEVV7ace5lrt4YdA4tXiz3f/KbmD29Vo= google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= diff --git a/pkg/deploy/defaults.go b/pkg/deploy/defaults.go index f75a0edd6..b2b27af5a 100644 --- a/pkg/deploy/defaults.go +++ b/pkg/deploy/defaults.go @@ -101,7 +101,8 @@ const ( KubernetesInstanceLabelKey = "app.kubernetes.io/instance" KubernetesNameLabelKey = "app.kubernetes.io/name" - CheEclipseOrg = "che.eclipse.org" + CheEclipseOrg = "che.eclipse.org" + OAuthScmConfiguration = "oauth-scm-configuration" // che.eclipse.org annotations CheEclipseOrgMountPath = "che.eclipse.org/mount-path" @@ -109,6 +110,8 @@ const ( CheEclipseOrgEnvName = "che.eclipse.org/env-name" CheEclipseOrgNamespace = "che.eclipse.org/namespace" CheEclipseOrgGithubOAuthCredentials = "che.eclipse.org/github-oauth-credentials" + CheEclipseOrgOAuthScmServer = "che.eclipse.org/oauth-scm-server" + CheEclipseOrgScmServerEndpoint = "che.eclipse.org/scm-server-endpoint" // components IdentityProviderName = "keycloak" @@ -141,6 +144,10 @@ const ( DefaultPostgresMemoryRequest = "512Mi" DefaultPostgresCpuLimit = "500m" DefaultPostgresCpuRequest = "100m" + + BitBucketOAuthConfigMountPath = "/che-conf/oauth/bitbucket" + BitBucketOAuthConfigPrivateKey = "private.key" + BitBucketOAuthConfigConsumerKey = "consumer.key" ) func InitDefaults(defaultsPath string) { diff --git a/pkg/deploy/deployment.go b/pkg/deploy/deployment.go index 705015ea4..6aed43e4e 100644 --- a/pkg/deploy/deployment.go +++ b/pkg/deploy/deployment.go @@ -137,6 +137,7 @@ func MountSecrets(specDeployment *appsv1.Deployment, deployContext *DeployContex return strings.Compare(secrets.Items[i].Name, secrets.Items[j].Name) < 0 }) + container := &specDeployment.Spec.Template.Spec.Containers[0] for _, secretObj := range secrets.Items { switch secretObj.Annotations[CheEclipseOrgMountAs] { case "file": @@ -157,7 +158,7 @@ func MountSecrets(specDeployment *appsv1.Deployment, deployContext *DeployContex } specDeployment.Spec.Template.Spec.Volumes = append(specDeployment.Spec.Template.Spec.Volumes, volume) - specDeployment.Spec.Template.Spec.Containers[0].VolumeMounts = append(specDeployment.Spec.Template.Spec.Containers[0].VolumeMounts, volumeMount) + container.VolumeMounts = append(container.VolumeMounts, volumeMount) case "env": secret, err := GetSecret(deployContext, secretObj.Name, deployContext.CheCluster.Namespace) @@ -203,7 +204,7 @@ func MountSecrets(specDeployment *appsv1.Deployment, deployContext *DeployContex }, }, } - specDeployment.Spec.Template.Spec.Containers[0].Env = append(specDeployment.Spec.Template.Spec.Containers[0].Env, env) + container.Env = append(container.Env, env) } } } diff --git a/pkg/deploy/identity-provider/deployment_keycloak.go b/pkg/deploy/identity-provider/deployment_keycloak.go index 2a967ddb3..b7450b12b 100644 --- a/pkg/deploy/identity-provider/deployment_keycloak.go +++ b/pkg/deploy/identity-provider/deployment_keycloak.go @@ -89,12 +89,15 @@ func GetSpecKeycloakDeployment( } if clusterDeployment != nil { - env := clusterDeployment.Spec.Template.Spec.Containers[0].Env - for _, e := range env { - // To be compatible with prev deployments when "TRUSTPASS" env was used - if "TRUSTPASS" == e.Name || "SSO_TRUSTSTORE_PASSWORD" == e.Name { - trustpass = e.Value - break + // To be compatible with prev deployments when "TRUSTPASS" env was used + clusterContainer := &clusterDeployment.Spec.Template.Spec.Containers[0] + env := util.FindEnv(clusterContainer.Env, "TRUSTPASS") + if env != nil { + trustpass = env.Value + } else { + env := util.FindEnv(clusterContainer.Env, "SSO_TRUSTSTORE_PASSWORD") + if env != nil { + trustpass = env.Value } } } @@ -477,6 +480,40 @@ func GetSpecKeycloakDeployment( } } + // Mount GITHUB_CLIENT_ID and GITHUB_SECRET to keycloak container + secrets, err := deploy.GetSecrets(deployContext, map[string]string{ + deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, + deploy.KubernetesComponentLabelKey: deploy.OAuthScmConfiguration, + }, map[string]string{ + deploy.CheEclipseOrgOAuthScmServer: "github", + }) + + if err != nil { + return nil, err + } else if len(secrets) == 1 { + keycloakEnv = append(keycloakEnv, corev1.EnvVar{ + Name: "GITHUB_CLIENT_ID", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + Key: "id", + LocalObjectReference: corev1.LocalObjectReference{ + Name: secrets[0].Name, + }, + }, + }, + }, corev1.EnvVar{ + Name: "GITHUB_SECRET", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + Key: "secret", + LocalObjectReference: corev1.LocalObjectReference{ + Name: secrets[0].Name, + }, + }, + }, + }) + } + for _, envvar := range proxyEnvVars { keycloakEnv = append(keycloakEnv, envvar) } diff --git a/pkg/deploy/identity-provider/deployment_keycloak_test.go b/pkg/deploy/identity-provider/deployment_keycloak_test.go index 6d5b5495c..8a3ff17df 100644 --- a/pkg/deploy/identity-provider/deployment_keycloak_test.go +++ b/pkg/deploy/identity-provider/deployment_keycloak_test.go @@ -13,12 +13,15 @@ package identity_provider import ( "os" + "reflect" "github.com/eclipse/che-operator/pkg/util" + "github.com/google/go-cmp/cmp" "github.com/eclipse/che-operator/pkg/deploy" orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" @@ -117,3 +120,103 @@ func TestDeployment(t *testing.T) { }) } } + +func TestMountGitHubOAuthEnvVar(t *testing.T) { + type testCase struct { + name string + initObjects []runtime.Object + cheCluster *orgv1.CheCluster + expectedIdEnv corev1.EnvVar + expectedSecretEnv corev1.EnvVar + } + + testCases := []testCase{ + { + name: "Test", + initObjects: []runtime.Object{ + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "github-oauth-config", + Namespace: "eclipse-che", + Labels: map[string]string{ + "app.kubernetes.io/part-of": "che.eclipse.org", + "app.kubernetes.io/component": "oauth-scm-configuration", + }, + Annotations: map[string]string{ + "che.eclipse.org/oauth-scm-server": "github", + }, + }, + Data: map[string][]byte{ + "id": []byte("some__id"), + "secret": []byte("some_secret"), + }, + }, + }, + cheCluster: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + }, + }, + expectedIdEnv: corev1.EnvVar{ + Name: "GITHUB_CLIENT_ID", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + Key: "id", + LocalObjectReference: corev1.LocalObjectReference{ + Name: "github-oauth-config", + }, + }, + }, + }, + expectedSecretEnv: corev1.EnvVar{ + Name: "GITHUB_SECRET", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + Key: "secret", + LocalObjectReference: corev1.LocalObjectReference{ + Name: "github-oauth-config", + }, + }, + }, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + logf.SetLogger(zap.LoggerTo(os.Stdout, true)) + orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) + testCase.initObjects = append(testCase.initObjects) + cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) + + deployContext := &deploy.DeployContext{ + CheCluster: testCase.cheCluster, + ClusterAPI: deploy.ClusterAPI{ + Client: cli, + Scheme: scheme.Scheme, + }, + Proxy: &deploy.Proxy{}, + } + + deployment, err := GetSpecKeycloakDeployment(deployContext, nil) + if err != nil { + t.Fatalf("Error creating deployment: %v", err) + } + + container := &deployment.Spec.Template.Spec.Containers[0] + idEnv := util.FindEnv(container.Env, "GITHUB_CLIENT_ID") + if !reflect.DeepEqual(testCase.expectedIdEnv, *idEnv) { + t.Errorf("Expected Env and Env returned from API server differ (-want, +got): %v", cmp.Diff(testCase.expectedIdEnv, idEnv)) + } + + secretEnv := util.FindEnv(container.Env, "GITHUB_SECRET") + if !reflect.DeepEqual(testCase.expectedSecretEnv, *secretEnv) { + t.Errorf("Expected CR and CR returned from API server differ (-want, +got): %v", cmp.Diff(testCase.expectedSecretEnv, secretEnv)) + } + }) + } +} diff --git a/pkg/deploy/identity-provider/identity_provider.go b/pkg/deploy/identity-provider/identity_provider.go index 966854728..35e74a2ef 100644 --- a/pkg/deploy/identity-provider/identity_provider.go +++ b/pkg/deploy/identity-provider/identity_provider.go @@ -13,22 +13,18 @@ package identity_provider import ( "context" + "errors" "fmt" "strings" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/selection" - orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" "github.com/eclipse/che-operator/pkg/deploy" "github.com/eclipse/che-operator/pkg/deploy/expose" "github.com/eclipse/che-operator/pkg/util" "github.com/google/go-cmp/cmp/cmpopts" oauth "github.com/openshift/api/oauth/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" ) var ( @@ -128,7 +124,7 @@ func syncKeycloakResources(deployContext *deploy.DeployContext) (bool, error) { for { cr.Status.KeycloakProvisoned = true if err := deploy.UpdateCheCRStatus(deployContext, "status: provisioned with Keycloak", "true"); err != nil && - errors.IsConflict(err) { + apierrors.IsConflict(err) { reload(deployContext) continue @@ -197,7 +193,7 @@ func SyncOpenShiftIdentityProviderItems(deployContext *deploy.DeployContext) (bo for { cr.Status.OpenShiftoAuthProvisioned = true if err := deploy.UpdateCheCRStatus(deployContext, "status: provisioned with OpenShift identity provider", "true"); err != nil && - errors.IsConflict(err) { + apierrors.IsConflict(err) { reload(deployContext) continue @@ -209,34 +205,37 @@ func SyncOpenShiftIdentityProviderItems(deployContext *deploy.DeployContext) (bo return true, nil } -// SyncGitHubOAuth provisions GitHub OAuth if secret with -// annotation `che.eclipse.org/github-oauth-credentials=true` is mounted into a container +// SyncGitHubOAuth provisions GitHub OAuth if secret with annotation +// `che.eclipse.org/github-oauth-credentials=true` or `che.eclipse.org/oauth-scm-configuration=github` +// is mounted into a container func SyncGitHubOAuth(deployContext *deploy.DeployContext) (bool, error) { - cr := deployContext.CheCluster - - // find mounted GitHug OAuth credentials - secrets := &corev1.SecretList{} - - kubernetesPartOfLabelSelectorRequirement, _ := labels.NewRequirement(deploy.KubernetesPartOfLabelKey, selection.Equals, []string{deploy.CheEclipseOrg}) - kubernetesComponentLabelSelectorRequirement, _ := labels.NewRequirement(deploy.KubernetesComponentLabelKey, selection.Equals, []string{deploy.IdentityProviderName + "-secret"}) - - listOptions := &client.ListOptions{ - LabelSelector: labels.NewSelector(). - Add(*kubernetesPartOfLabelSelectorRequirement). - Add(*kubernetesComponentLabelSelectorRequirement), - } - if err := deployContext.ClusterAPI.Client.List(context.TODO(), secrets, listOptions); err != nil { + // get legacy secret + legacySecrets, err := deploy.GetSecrets(deployContext, map[string]string{ + deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, + deploy.KubernetesComponentLabelKey: deploy.IdentityProviderName + "-secret", + }, map[string]string{ + deploy.CheEclipseOrgGithubOAuthCredentials: "true", + }) + if err != nil { return false, err } - isGitHubOAuthCredentialsExists := false - for _, secret := range secrets.Items { - if secret.Annotations[deploy.CheEclipseOrgGithubOAuthCredentials] == "true" { - isGitHubOAuthCredentialsExists = true - break - } + secrets, err := deploy.GetSecrets(deployContext, map[string]string{ + deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, + deploy.KubernetesComponentLabelKey: deploy.OAuthScmConfiguration, + }, map[string]string{ + deploy.CheEclipseOrgOAuthScmServer: "github", + }) + + if err != nil { + return false, err + } else if len(secrets)+len(legacySecrets) > 1 { + return false, errors.New("More than 1 GitHub OAuth configuration secrets found") } + isGitHubOAuthCredentialsExists := len(secrets) == 1 || len(legacySecrets) == 1 + cr := deployContext.CheCluster + if isGitHubOAuthCredentialsExists { if !cr.Status.GitHubOAuthProvisioned { if !util.IsTestMode() { diff --git a/pkg/deploy/identity-provider/identity_provider_test.go b/pkg/deploy/identity-provider/identity_provider_test.go index d43e816d7..8c6ccc931 100644 --- a/pkg/deploy/identity-provider/identity_provider_test.go +++ b/pkg/deploy/identity-provider/identity_provider_test.go @@ -41,7 +41,7 @@ func TestSyncGitHubOAuth(t *testing.T) { testCases := []testCase{ { - name: "Should provision GitHub OAuth", + name: "Should provision GitHub OAuth with legacy secret", initCR: &orgv1.CheCluster{ ObjectMeta: metav1.ObjectMeta{ Namespace: "eclipse-che", @@ -79,6 +79,42 @@ func TestSyncGitHubOAuth(t *testing.T) { }, }, }, + { + name: "Should provision GitHub OAuth", + initCR: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + }, + }, + expectedCR: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + ResourceVersion: "1", + }, + Status: orgv1.CheClusterStatus{ + GitHubOAuthProvisioned: true, + }, + }, + initObjects: []runtime.Object{ + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "github-oauth-config", + Namespace: "eclipse-che", + Labels: map[string]string{ + "app.kubernetes.io/part-of": "che.eclipse.org", + "app.kubernetes.io/component": "oauth-scm-configuration", + }, + Annotations: map[string]string{ + "che.eclipse.org/oauth-scm-server": "github", + }, + }, + }, + }, + }, { name: "Should not provision GitHub OAuth", initCR: &orgv1.CheCluster{ diff --git a/pkg/deploy/postgres/deployment_postgres.go b/pkg/deploy/postgres/deployment_postgres.go index b44c2fc52..32b349d2b 100644 --- a/pkg/deploy/postgres/deployment_postgres.go +++ b/pkg/deploy/postgres/deployment_postgres.go @@ -52,12 +52,10 @@ func GetSpecPostgresDeployment(deployContext *deploy.DeployContext, clusterDeplo pullPolicy := corev1.PullPolicy(util.GetValue(string(deployContext.CheCluster.Spec.Database.PostgresImagePullPolicy), deploy.DefaultPullPolicyFromDockerImage(postgresImage))) if clusterDeployment != nil { - env := clusterDeployment.Spec.Template.Spec.Containers[0].Env - for _, e := range env { - if "POSTGRESQL_ADMIN_PASSWORD" == e.Name { - postgresAdminPassword = e.Value - break - } + clusterContainer := &clusterDeployment.Spec.Template.Spec.Containers[0] + env := util.FindEnv(clusterContainer.Env, "POSTGRESQL_ADMIN_PASSWORD") + if env != nil { + postgresAdminPassword = env.Value } } @@ -179,9 +177,11 @@ func GetSpecPostgresDeployment(deployContext *deploy.DeployContext, clusterDeplo }, } + container := &deployment.Spec.Template.Spec.Containers[0] + chePostgresSecret := deployContext.CheCluster.Spec.Database.ChePostgresSecret if len(chePostgresSecret) > 0 { - deployment.Spec.Template.Spec.Containers[0].Env = append(deployment.Spec.Template.Spec.Containers[0].Env, + container.Env = append(container.Env, corev1.EnvVar{ Name: "POSTGRESQL_USER", ValueFrom: &corev1.EnvVarSource{ @@ -204,7 +204,7 @@ func GetSpecPostgresDeployment(deployContext *deploy.DeployContext, clusterDeplo }, }) } else { - deployment.Spec.Template.Spec.Containers[0].Env = append(deployment.Spec.Template.Spec.Containers[0].Env, + container.Env = append(container.Env, corev1.EnvVar{ Name: "POSTGRESQL_USER", Value: deployContext.CheCluster.Spec.Database.ChePostgresUser, diff --git a/pkg/deploy/secret.go b/pkg/deploy/secret.go index 535f96f92..9bece38f5 100644 --- a/pkg/deploy/secret.go +++ b/pkg/deploy/secret.go @@ -24,6 +24,8 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8slabels "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/types" ) @@ -97,6 +99,46 @@ func GetSecret(deployContext *DeployContext, name string, namespace string) (*co return secret, nil } +// Get all secrets by labels and annotations +func GetSecrets(deployContext *DeployContext, labels map[string]string, annotations map[string]string) ([]corev1.Secret, error) { + secrets := []corev1.Secret{} + + labelSelector := k8slabels.NewSelector() + for k, v := range labels { + req, err := k8slabels.NewRequirement(k, selection.Equals, []string{v}) + if err != nil { + return secrets, err + } + labelSelector = labelSelector.Add(*req) + } + + listOptions := &client.ListOptions{ + Namespace: deployContext.CheCluster.Namespace, + LabelSelector: labelSelector, + } + secretList := &corev1.SecretList{} + if err := deployContext.ClusterAPI.Client.List(context.TODO(), secretList, listOptions); err != nil { + return secrets, err + } + + for _, secret := range secretList.Items { + annotationsOk := true + for k, v := range annotations { + _, annotationExists := secret.Annotations[k] + if !annotationExists || secret.Annotations[k] != v { + annotationsOk = false + break + } + } + + if annotationsOk { + secrets = append(secrets, secret) + } + } + + return secrets, nil +} + // GetSpecSecret return default secret config for given data func GetSpecSecret(deployContext *DeployContext, name string, namespace string, data map[string][]byte) (*corev1.Secret, error) { labels := GetLabels(deployContext.CheCluster, DefaultCheFlavor(deployContext.CheCluster)) diff --git a/pkg/deploy/sercet_test.go b/pkg/deploy/sercet_test.go new file mode 100644 index 000000000..2138d799d --- /dev/null +++ b/pkg/deploy/sercet_test.go @@ -0,0 +1,165 @@ +// +// 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 ( + "os" + + orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" + + "testing" +) + +func TestGetSecrets(t *testing.T) { + type testCase struct { + name string + labels map[string]string + annotations map[string]string + initObjects []runtime.Object + expectedAmount int + } + + runtimeSecrets := []runtime.Object{ + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test2", + Namespace: "eclipse-che", + Labels: map[string]string{ + "l1": "v1", + "l2": "v2", + }, + Annotations: map[string]string{ + "a1": "v1", + "a2": "v2", + }, + }, + }, + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test1", + Namespace: "eclipse-che", + Labels: map[string]string{ + "l1": "v1", + "l3": "v3", + }, + Annotations: map[string]string{ + "a1": "v1", + "a3": "v3", + }, + }, + }, + } + + testCases := []testCase{ + { + name: "Get secrets", + initObjects: []runtime.Object{}, + labels: map[string]string{ + "l1": "v1", + }, + annotations: map[string]string{ + "a1": "v1", + }, + expectedAmount: 2, + }, + { + name: "Get secrets", + initObjects: []runtime.Object{}, + labels: map[string]string{ + "l1": "v1", + }, + annotations: map[string]string{ + "a1": "v1", + "a2": "v2", + }, + expectedAmount: 1, + }, + { + name: "Get secrets", + initObjects: []runtime.Object{}, + labels: map[string]string{ + "l1": "v1", + "l2": "v2", + }, + annotations: map[string]string{ + "a1": "v1", + }, + expectedAmount: 1, + }, + { + name: "Get secrets, unknown label", + initObjects: []runtime.Object{}, + labels: map[string]string{ + "l4": "v4", + }, + annotations: map[string]string{}, + expectedAmount: 0, + }, + { + name: "Get secrets, unknown annotation", + initObjects: []runtime.Object{}, + labels: map[string]string{ + "l1": "v1", + }, + annotations: map[string]string{ + "a4": "v4", + }, + expectedAmount: 0, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + logf.SetLogger(zap.LoggerTo(os.Stdout, true)) + orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) + testCase.initObjects = append(testCase.initObjects, runtimeSecrets...) + cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) + + deployContext := &DeployContext{ + CheCluster: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + }, + }, + ClusterAPI: ClusterAPI{ + Client: cli, + NonCachedClient: cli, + Scheme: scheme.Scheme, + }, + } + + secrets, err := GetSecrets(deployContext, testCase.labels, testCase.annotations) + if err != nil { + t.Fatalf("Error getting secrets: %v", err) + } + + if len(secrets) != testCase.expectedAmount { + t.Fatalf("Expected %d but found: %d", testCase.expectedAmount, len(secrets)) + } + }) + } +} diff --git a/pkg/deploy/server/che_configmap.go b/pkg/deploy/server/che_configmap.go index e6b5673b5..f11179c8b 100644 --- a/pkg/deploy/server/che_configmap.go +++ b/pkg/deploy/server/che_configmap.go @@ -304,6 +304,26 @@ func GetCheConfigMapData(deployContext *deploy.DeployContext) (cheEnv map[string } addMap(cheEnv, deployContext.CheCluster.Spec.Server.CustomCheProperties) + + // Update BitBucket endpoints + secrets, err := deploy.GetSecrets(deployContext, map[string]string{ + deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, + deploy.KubernetesComponentLabelKey: deploy.OAuthScmConfiguration, + }, map[string]string{ + deploy.CheEclipseOrgOAuthScmServer: "bitbucket", + }) + if err != nil { + return nil, err + } else if len(secrets) == 1 { + serverEndpoint := secrets[0].Annotations[deploy.CheEclipseOrgScmServerEndpoint] + endpoints, exists := cheEnv["CHE_INTEGRATION_BITBUCKET_SERVER__ENDPOINTS"] + if exists { + cheEnv["CHE_INTEGRATION_BITBUCKET_SERVER__ENDPOINTS"] = endpoints + "," + serverEndpoint + } else { + cheEnv["CHE_INTEGRATION_BITBUCKET_SERVER__ENDPOINTS"] = serverEndpoint + } + } + return cheEnv, nil } diff --git a/pkg/deploy/server/che_configmap_test.go b/pkg/deploy/server/che_configmap_test.go index 32a0e58fa..166b9993a 100644 --- a/pkg/deploy/server/che_configmap_test.go +++ b/pkg/deploy/server/che_configmap_test.go @@ -13,7 +13,6 @@ package server import ( "os" - "strings" "testing" "github.com/eclipse/che-operator/pkg/deploy" @@ -30,53 +29,74 @@ import ( ) func TestNewCheConfigMap(t *testing.T) { + type testCase struct { + name string + isOpenShift bool + isOpenShift4 bool + initObjects []runtime.Object + cheCluster *orgv1.CheCluster + expectedData map[string]string + } - // since all values are retrieved from CR or auto-generated - // some of them are explicitly set for this test to avoid using fake kube - // and creating a CR with all spec fields pre-populated - cr := &orgv1.CheCluster{} - cr.Spec.Server.CheHost = "myhostname.com" - cr.Spec.Server.TlsSupport = true - cr.Spec.Auth.OpenShiftoAuth = util.NewBoolPointer(true) - deployContext := &deploy.DeployContext{ - CheCluster: cr, - Proxy: &deploy.Proxy{}, - ClusterAPI: deploy.ClusterAPI{}, + testCases := []testCase{ + { + name: "Test", + initObjects: []runtime.Object{}, + isOpenShift: true, + isOpenShift4: true, + cheCluster: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + }, + Spec: orgv1.CheClusterSpec{ + Server: orgv1.CheClusterSpecServer{ + CheHost: "myhostname.com", + TlsSupport: true, + CustomCheProperties: map[string]string{ + "CHE_WORKSPACE_NO_PROXY": "myproxy.myhostname.com", + }, + }, + Auth: orgv1.CheClusterSpecAuth{ + OpenShiftoAuth: util.NewBoolPointer(true), + }, + }, + }, + expectedData: map[string]string{ + "CHE_INFRA_OPENSHIFT_OAUTH__IDENTITY__PROVIDER": "openshift-v4", + "CHE_API": "https://myhostname.com/api", + "CHE_WORKSPACE_NO_PROXY": "myproxy.myhostname.com", + }, + }, } - expectedIdentityProvider := "openshift-v4" - util.IsOpenShift = true - util.IsOpenShift4 = true - cheEnv, _ := GetCheConfigMapData(deployContext) - testCm, _ := deploy.GetSpecConfigMap(deployContext, CheConfigMapName, cheEnv, CheConfigMapName) + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + logf.SetLogger(zap.LoggerTo(os.Stdout, true)) + orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) + testCase.initObjects = append(testCase.initObjects) + cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) + nonCachedClient := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) - identityProvider := testCm.Data["CHE_INFRA_OPENSHIFT_OAUTH__IDENTITY__PROVIDER"] - protocol := strings.Split(testCm.Data["CHE_API"], "://")[0] - if identityProvider != expectedIdentityProvider { - t.Errorf("Test failed. Expecting identity provider to be '%s' while got '%s'", expectedIdentityProvider, identityProvider) - } - if protocol != "https" { - t.Errorf("Test failed. Expecting 'https' protocol, got '%s'", protocol) - } -} + deployContext := &deploy.DeployContext{ + CheCluster: testCase.cheCluster, + ClusterAPI: deploy.ClusterAPI{ + Client: cli, + NonCachedClient: nonCachedClient, + Scheme: scheme.Scheme, + }, + Proxy: &deploy.Proxy{}, + } -func TestConfigMapOverride(t *testing.T) { - cr := &orgv1.CheCluster{} - cr.Spec.Server.CheHost = "myhostname.com" - cr.Spec.Server.TlsSupport = true - cr.Spec.Server.CustomCheProperties = map[string]string{ - "CHE_WORKSPACE_NO_PROXY": "myproxy.myhostname.com", - } - cr.Spec.Auth.OpenShiftoAuth = util.NewBoolPointer(true) - deployContext := &deploy.DeployContext{ - CheCluster: cr, - Proxy: &deploy.Proxy{}, - ClusterAPI: deploy.ClusterAPI{}, - } - cheEnv, _ := GetCheConfigMapData(deployContext) - testCm, _ := deploy.GetSpecConfigMap(deployContext, CheConfigMapName, cheEnv, CheConfigMapName) - if testCm.Data["CHE_WORKSPACE_NO_PROXY"] != "myproxy.myhostname.com" { - t.Errorf("Test failed. Expected myproxy.myhostname.com but was %s", testCm.Data["CHE_WORKSPACE_NO_PROXY"]) + util.IsOpenShift = testCase.isOpenShift + util.IsOpenShift4 = testCase.isOpenShift4 + + actualData, err := GetCheConfigMapData(deployContext) + if err != nil { + t.Fatalf("Error creating ConfigMap data: %v", err) + } + + util.ValidateContainData(actualData, testCase.expectedData, t) + }) } } @@ -177,3 +197,130 @@ func TestConfigMap(t *testing.T) { }) } } + +func TestUpdateBitBucketEndpoints(t *testing.T) { + type testCase struct { + name string + initObjects []runtime.Object + cheCluster *orgv1.CheCluster + expectedData map[string]string + } + + testCases := []testCase{ + { + name: "Test set BitBucket endpoints from secret", + initObjects: []runtime.Object{ + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "github-oauth-config", + Namespace: "eclipse-che", + Labels: map[string]string{ + "app.kubernetes.io/part-of": "che.eclipse.org", + "app.kubernetes.io/component": "oauth-scm-configuration", + }, + Annotations: map[string]string{ + "che.eclipse.org/oauth-scm-server": "bitbucket", + "che.eclipse.org/scm-server-endpoint": "bitbucket_endpoint_2", + }, + }, + }, + }, + cheCluster: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + }, + }, + expectedData: map[string]string{ + "CHE_INTEGRATION_BITBUCKET_SERVER__ENDPOINTS": "bitbucket_endpoint_2", + }, + }, + { + name: "Test update BitBucket endpoints", + initObjects: []runtime.Object{ + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "github-oauth-config", + Namespace: "eclipse-che", + Labels: map[string]string{ + "app.kubernetes.io/part-of": "che.eclipse.org", + "app.kubernetes.io/component": "oauth-scm-configuration", + }, + Annotations: map[string]string{ + "che.eclipse.org/oauth-scm-server": "bitbucket", + "che.eclipse.org/scm-server-endpoint": "bitbucket_endpoint_2", + }, + }, + }, + }, + cheCluster: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + }, + Spec: orgv1.CheClusterSpec{ + Server: orgv1.CheClusterSpecServer{ + CustomCheProperties: map[string]string{ + "CHE_INTEGRATION_BITBUCKET_SERVER__ENDPOINTS": "bitbucket_endpoint_1", + }, + }, + }, + }, + expectedData: map[string]string{ + "CHE_INTEGRATION_BITBUCKET_SERVER__ENDPOINTS": "bitbucket_endpoint_1,bitbucket_endpoint_2", + }, + }, + { + name: "Test don't update BitBucket endpoints", + initObjects: []runtime.Object{}, + cheCluster: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + }, + Spec: orgv1.CheClusterSpec{ + Server: orgv1.CheClusterSpecServer{ + CustomCheProperties: map[string]string{ + "CHE_INTEGRATION_BITBUCKET_SERVER__ENDPOINTS": "bitbucket_endpoint_1", + }, + }, + }, + }, + expectedData: map[string]string{ + "CHE_INTEGRATION_BITBUCKET_SERVER__ENDPOINTS": "bitbucket_endpoint_1", + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + logf.SetLogger(zap.LoggerTo(os.Stdout, true)) + orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) + testCase.initObjects = append(testCase.initObjects) + cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) + nonCachedClient := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) + + deployContext := &deploy.DeployContext{ + CheCluster: testCase.cheCluster, + ClusterAPI: deploy.ClusterAPI{ + Client: cli, + NonCachedClient: nonCachedClient, + Scheme: scheme.Scheme, + }, + Proxy: &deploy.Proxy{}, + } + + actualData, err := GetCheConfigMapData(deployContext) + if err != nil { + t.Fatalf("Error creating ConfigMap data: %v", err) + } + + util.ValidateContainData(actualData, testCase.expectedData, t) + }) + } +} diff --git a/pkg/deploy/server/deployment_che.go b/pkg/deploy/server/deployment_che.go index 5e90e7734..b4b944bad 100644 --- a/pkg/deploy/server/deployment_che.go +++ b/pkg/deploy/server/deployment_che.go @@ -12,6 +12,7 @@ package server import ( + "errors" "strconv" "strings" @@ -273,11 +274,17 @@ func GetSpecCheDeployment(deployContext *deploy.DeployContext) (*appsv1.Deployme }, } + err = MountBitBucketOAuthConfig(deployContext, deployment) + if err != nil { + return nil, err + } + + container := &deployment.Spec.Template.Spec.Containers[0] cheMultiUser := deploy.GetCheMultiUser(deployContext.CheCluster) if cheMultiUser == "true" { chePostgresSecret := deployContext.CheCluster.Spec.Database.ChePostgresSecret if len(chePostgresSecret) > 0 { - deployment.Spec.Template.Spec.Containers[0].Env = append(deployment.Spec.Template.Spec.Containers[0].Env, + container.Env = append(container.Env, corev1.EnvVar{ Name: "CHE_JDBC_USERNAME", ValueFrom: &corev1.EnvVarSource{ @@ -311,7 +318,7 @@ func GetSpecCheDeployment(deployContext *deploy.DeployContext) (*appsv1.Deployme }, }, }} - deployment.Spec.Template.Spec.Containers[0].VolumeMounts = []corev1.VolumeMount{ + container.VolumeMounts = []corev1.VolumeMount{ { MountPath: deploy.DefaultCheVolumeMountPath, Name: deploy.DefaultCheVolumeClaimName, @@ -321,7 +328,7 @@ func GetSpecCheDeployment(deployContext *deploy.DeployContext) (*appsv1.Deployme // configure probes if debug isn't set cheDebug := util.GetValue(deployContext.CheCluster.Spec.Server.CheDebug, deploy.DefaultCheDebug) if cheDebug != "true" { - deployment.Spec.Template.Spec.Containers[0].ReadinessProbe = &corev1.Probe{ + container.ReadinessProbe = &corev1.Probe{ Handler: corev1.Handler{ HTTPGet: &corev1.HTTPGetAction{ Path: "/api/system/state", @@ -340,7 +347,7 @@ func GetSpecCheDeployment(deployContext *deploy.DeployContext) (*appsv1.Deployme PeriodSeconds: 10, SuccessThreshold: 1, } - deployment.Spec.Template.Spec.Containers[0].LivenessProbe = &corev1.Probe{ + container.LivenessProbe = &corev1.Probe{ Handler: corev1.Handler{ HTTPGet: &corev1.HTTPGetAction{ Path: "/api/system/state", @@ -406,3 +413,51 @@ func GetFullCheServerImageLink(checluster *orgv1.CheCluster) string { imageParts := strings.Split(defaultCheServerImage, separator) return imageParts[0] + ":" + checluster.Spec.Server.CheImageTag } + +func MountBitBucketOAuthConfig(deployContext *deploy.DeployContext, deployment *appsv1.Deployment) error { + // mount BitBucket configuration + secrets, err := deploy.GetSecrets(deployContext, map[string]string{ + deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, + deploy.KubernetesComponentLabelKey: deploy.OAuthScmConfiguration, + }, map[string]string{ + deploy.CheEclipseOrgOAuthScmServer: "bitbucket", + }) + + if err != nil { + return err + } else if len(secrets) > 1 { + return errors.New("More than 1 BitBucket OAuth configuration secrets found") + } else if len(secrets) == 1 { + // mount secrets + container := &deployment.Spec.Template.Spec.Containers[0] + deployment.Spec.Template.Spec.Volumes = append(deployment.Spec.Template.Spec.Volumes, + corev1.Volume{ + Name: secrets[0].Name, + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secrets[0].Name, + }, + }, + }) + container.VolumeMounts = append(container.VolumeMounts, + corev1.VolumeMount{ + Name: secrets[0].Name, + MountPath: deploy.BitBucketOAuthConfigMountPath, + }) + + // mount env + endpoint := secrets[0].Annotations[deploy.CheEclipseOrgScmServerEndpoint] + container.Env = append(container.Env, corev1.EnvVar{ + Name: "CHE_OAUTH1_BITBUCKET_CONSUMERKEYPATH", + Value: deploy.BitBucketOAuthConfigMountPath + "/" + deploy.BitBucketOAuthConfigConsumerKey, + }, corev1.EnvVar{ + Name: "CHE_OAUTH1_BITBUCKET_PRIVATEKEYPATH", + Value: deploy.BitBucketOAuthConfigMountPath + "/" + deploy.BitBucketOAuthConfigPrivateKey, + }, corev1.EnvVar{ + Name: "CHE_OAUTH1_BITBUCKET_ENDPOINT", + Value: endpoint, + }) + } + + return nil +} diff --git a/pkg/deploy/server/deployment_che_test.go b/pkg/deploy/server/deployment_che_test.go index d1840ea2a..e914d7ff9 100644 --- a/pkg/deploy/server/deployment_che_test.go +++ b/pkg/deploy/server/deployment_che_test.go @@ -13,12 +13,15 @@ package server import ( "os" + "reflect" "github.com/eclipse/che-operator/pkg/util" + "github.com/google/go-cmp/cmp" "github.com/eclipse/che-operator/pkg/deploy" orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" @@ -110,3 +113,124 @@ func TestDeployment(t *testing.T) { }) } } + +func TestMountBitBucketOAuthEnvVar(t *testing.T) { + type testCase struct { + name string + initObjects []runtime.Object + cheCluster *orgv1.CheCluster + expectedConsumerKeyPathEnv corev1.EnvVar + expectedPrivateKeyPathEnv corev1.EnvVar + expectedEndpointEnv corev1.EnvVar + expectedVolume corev1.Volume + expectedVolumeMount corev1.VolumeMount + } + + testCases := []testCase{ + { + name: "Test", + initObjects: []runtime.Object{ + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "github-oauth-config", + Namespace: "eclipse-che", + Labels: map[string]string{ + "app.kubernetes.io/part-of": "che.eclipse.org", + "app.kubernetes.io/component": "oauth-scm-configuration", + }, + Annotations: map[string]string{ + "che.eclipse.org/oauth-scm-server": "bitbucket", + "che.eclipse.org/scm-server-endpoint": "bitbucket_endpoint", + }, + }, + Data: map[string][]byte{ + "private.key": []byte("private_key"), + "consumer.key": []byte("consumer_key"), + }, + }, + }, + cheCluster: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + }, + }, + expectedConsumerKeyPathEnv: corev1.EnvVar{ + Name: "CHE_OAUTH1_BITBUCKET_CONSUMERKEYPATH", + Value: "/che-conf/oauth/bitbucket/consumer.key", + }, + expectedPrivateKeyPathEnv: corev1.EnvVar{ + Name: "CHE_OAUTH1_BITBUCKET_PRIVATEKEYPATH", + Value: "/che-conf/oauth/bitbucket/private.key", + }, + expectedEndpointEnv: corev1.EnvVar{ + Name: "CHE_OAUTH1_BITBUCKET_ENDPOINT", + Value: "bitbucket_endpoint", + }, + expectedVolume: corev1.Volume{ + Name: "github-oauth-config", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: "github-oauth-config", + }, + }, + }, + expectedVolumeMount: corev1.VolumeMount{ + Name: "github-oauth-config", + MountPath: "/che-conf/oauth/bitbucket", + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + logf.SetLogger(zap.LoggerTo(os.Stdout, true)) + orgv1.SchemeBuilder.AddToScheme(scheme.Scheme) + testCase.initObjects = append(testCase.initObjects) + cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) + + deployContext := &deploy.DeployContext{ + CheCluster: testCase.cheCluster, + ClusterAPI: deploy.ClusterAPI{ + Client: cli, + Scheme: scheme.Scheme, + }, + Proxy: &deploy.Proxy{}, + } + + deployment, err := GetSpecCheDeployment(deployContext) + if err != nil { + t.Fatalf("Error creating deployment: %v", err) + } + + container := &deployment.Spec.Template.Spec.Containers[0] + env := util.FindEnv(container.Env, "CHE_OAUTH1_BITBUCKET_CONSUMERKEYPATH") + if !reflect.DeepEqual(testCase.expectedConsumerKeyPathEnv, *env) { + t.Errorf("Expected Env and Env returned from API server differ (-want, +got): %v", cmp.Diff(testCase.expectedConsumerKeyPathEnv, env)) + } + + env = util.FindEnv(container.Env, "CHE_OAUTH1_BITBUCKET_PRIVATEKEYPATH") + if !reflect.DeepEqual(testCase.expectedPrivateKeyPathEnv, *env) { + t.Errorf("Expected Env and Env returned from API server differ (-want, +got): %v", cmp.Diff(testCase.expectedPrivateKeyPathEnv, env)) + } + + env = util.FindEnv(container.Env, "CHE_OAUTH1_BITBUCKET_ENDPOINT") + if !reflect.DeepEqual(testCase.expectedEndpointEnv, *env) { + t.Errorf("Expected Env and Env returned from API server differ (-want, +got): %v", cmp.Diff(testCase.expectedEndpointEnv, env)) + } + + volume := util.FindVolume(deployment.Spec.Template.Spec.Volumes, "github-oauth-config") + if !reflect.DeepEqual(testCase.expectedVolume, volume) { + t.Errorf("Expected Volume and Volume returned from API server differ (-want, +got): %v", cmp.Diff(testCase.expectedVolume, volume)) + } + + volumeMount := util.FindVolumeMount(container.VolumeMounts, "github-oauth-config") + if !reflect.DeepEqual(testCase.expectedVolumeMount, volumeMount) { + t.Errorf("Expected VolumeMount and VolumeMount returned from API server differ (-want, +got): %v", cmp.Diff(testCase.expectedVolumeMount, volumeMount)) + } + }) + } +} diff --git a/pkg/util/test_util.go b/pkg/util/test_util.go index 69ffe4402..87a1c4d33 100644 --- a/pkg/util/test_util.go +++ b/pkg/util/test_util.go @@ -15,6 +15,7 @@ import ( "testing" appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" ) @@ -26,30 +27,31 @@ type TestExpectedResources struct { } func CompareResources(actualDeployment *appsv1.Deployment, expected TestExpectedResources, t *testing.T) { + container := &actualDeployment.Spec.Template.Spec.Containers[0] compareQuantity( "Memory limits", - actualDeployment.Spec.Template.Spec.Containers[0].Resources.Limits.Memory(), + container.Resources.Limits.Memory(), expected.MemoryLimit, t, ) compareQuantity( "Memory requests", - actualDeployment.Spec.Template.Spec.Containers[0].Resources.Requests.Memory(), + container.Resources.Requests.Memory(), expected.MemoryRequest, t, ) compareQuantity( "CPU limits", - actualDeployment.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu(), + container.Resources.Limits.Cpu(), expected.CpuLimit, t, ) compareQuantity( "CPU requests", - actualDeployment.Spec.Template.Spec.Containers[0].Resources.Requests.Cpu(), + container.Resources.Requests.Cpu(), expected.CpuRequest, t, ) @@ -80,3 +82,23 @@ func ValidateContainData(actualData map[string]string, expectedData map[string]s } } } + +func FindVolume(volumes []corev1.Volume, name string) corev1.Volume { + for _, volume := range volumes { + if volume.Name == name { + return volume + } + } + + return corev1.Volume{} +} + +func FindVolumeMount(volumes []corev1.VolumeMount, name string) corev1.VolumeMount { + for _, volumeMount := range volumes { + if volumeMount.Name == name { + return volumeMount + } + } + + return corev1.VolumeMount{} +} diff --git a/pkg/util/util.go b/pkg/util/util.go index fc48a1628..e6ff75c00 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -410,3 +410,14 @@ func GetResourceQuantity(value string, defaultValue string) resource.Quantity { } return resource.MustParse(defaultValue) } + +// Finds Env by a given name +func FindEnv(envs []corev1.EnvVar, name string) *corev1.EnvVar { + for _, env := range envs { + if env.Name == name { + return &env + } + } + + return nil +}