diff --git a/.vscode/launch.json b/.vscode/launch.json index e14fdd8a3..3c0bd7a3c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -99,7 +99,7 @@ "stable", "che" ] - }, + }, { "type": "bashdb", "request": "launch", @@ -145,7 +145,8 @@ "mode": "test", "program": "${file}", "env": { - "MOCK_API": true + "MOCK_API": true, + "CHE_FLAVOR": "che" }, }, { diff --git a/Dockerfile b/Dockerfile index 5e724a7ef..dcf825f69 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,8 +29,10 @@ RUN export ARCH="$(uname -m)" && if [[ ${ARCH} == "x86_64" ]]; then export ARCH= FROM registry.access.redhat.com/ubi8-minimal:8.3-230 COPY --from=builder /tmp/che-operator/che-operator /usr/local/bin/che-operator -COPY --from=builder /che-operator/templates/keycloak_provision /tmp/keycloak_provision -COPY --from=builder /che-operator/templates/oauth_provision /tmp/oauth_provision +COPY --from=builder /che-operator/templates/keycloak-provision.sh /tmp/keycloak-provision.sh +COPY --from=builder /che-operator/templates/oauth-provision.sh /tmp/oauth-provision.sh +COPY --from=builder /che-operator/templates/delete-identity-provider.sh /tmp/delete-identity-provider.sh +COPY --from=builder /che-operator/templates/create-github-identity-provider.sh /tmp/create-github-identity-provider.sh # apply CVE fixes, if required RUN microdnf update -y librepo libnghttp2 && microdnf clean all && rm -rf /var/cache/yum && echo "Installed Packages" && rpm -qa | sort -V && echo "End Of Installed Packages" CMD ["che-operator"] diff --git a/deploy/crds/org_v1_che_crd.yaml b/deploy/crds/org_v1_che_crd.yaml index 6af154863..02554088a 100644 --- a/deploy/crds/org_v1_che_crd.yaml +++ b/deploy/crds/org_v1_che_crd.yaml @@ -696,6 +696,10 @@ spec: devfileRegistryURL: description: Public URL to the Devfile registry type: string + gitHubOAuthProvisioned: + description: Indicates whether an Identity Provider instance (Keycloak + / RH SSO) has been configured to integrate with the GitHub OAuth. + type: boolean helpLink: description: A URL that can point to some URL where to find help related to the current Operator status. 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 49aad21d2..94cd28f26 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 @@ -84,13 +84,13 @@ metadata: categories: Developer Tools certified: "false" containerImage: quay.io/eclipse/che-operator:nightly - createdAt: "2021-01-08T14:09:35Z" + createdAt: "2021-01-13T13:41:43Z" 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.25.0-66.nightly + name: eclipse-che-preview-kubernetes.v7.25.0-68.nightly namespace: placeholder spec: apiservicedefinitions: {} @@ -514,4 +514,4 @@ spec: maturity: stable provider: name: Eclipse Foundation - version: 7.25.0-66.nightly + version: 7.25.0-68.nightly diff --git a/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml b/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml index 6af154863..02554088a 100644 --- a/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml +++ b/deploy/olm-catalog/eclipse-che-preview-kubernetes/manifests/org_v1_che_crd.yaml @@ -696,6 +696,10 @@ spec: devfileRegistryURL: description: Public URL to the Devfile registry type: string + gitHubOAuthProvisioned: + description: Indicates whether an Identity Provider instance (Keycloak + / RH SSO) has been configured to integrate with the GitHub OAuth. + type: boolean helpLink: description: A URL that can point to some URL where to find help related to the current Operator status. 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 d4e0074f9..c12bfca7a 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 @@ -75,13 +75,13 @@ metadata: categories: Developer Tools, OpenShift Optional certified: "false" containerImage: quay.io/eclipse/che-operator:nightly - createdAt: "2021-01-08T14:09:42Z" + createdAt: "2021-01-13T13:41:52Z" 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.25.0-66.nightly + name: eclipse-che-preview-openshift.v7.25.0-68.nightly namespace: placeholder spec: apiservicedefinitions: {} @@ -533,4 +533,4 @@ spec: maturity: stable provider: name: Eclipse Foundation - version: 7.25.0-66.nightly + version: 7.25.0-68.nightly diff --git a/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml b/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml index d4034837d..e4b3cf7aa 100644 --- a/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml +++ b/deploy/olm-catalog/eclipse-che-preview-openshift/manifests/org_v1_che_crd.yaml @@ -697,6 +697,10 @@ spec: devfileRegistryURL: description: Public URL to the Devfile registry type: string + gitHubOAuthProvisioned: + description: Indicates whether an Identity Provider instance (Keycloak + / RH SSO) has been configured to integrate with the GitHub OAuth. + type: boolean helpLink: description: A URL that can point to some URL where to find help related to the current Operator status. diff --git a/local-debug.sh b/local-debug.sh index 9b4a6e38e..313a8ccce 100755 --- a/local-debug.sh +++ b/local-debug.sh @@ -57,8 +57,9 @@ echo "[INFO] CR file path: ${CR}" kubectl apply -f deploy/crds/org_v1_che_crd.yaml kubectl apply -f "${CR}" -n $CHE_NAMESPACE -cp templates/keycloak_provision /tmp/keycloak_provision -cp templates/oauth_provision /tmp/oauth_provision +cp templates/keycloak-provision.sh /tmp/keycloak-provision.sh +cp templates/delete-identity-provider.sh /tmp/delete-identity-provider.sh +cp templates/create-github-identity-provider.sh /tmp/create-github-identity-provider.sh ENV_FILE=/tmp/che-operator-debug.env rm -rf "${ENV_FILE}" @@ -72,7 +73,7 @@ echo "WATCH_NAMESPACE='${CHE_NAMESPACE}'" >> ${ENV_FILE} echo "[WARN] Make sure that your CR contains valid ingress domain!" -operator-sdk run --local --namespace ${CHE_NAMESPACE} --enable-delve & +operator-sdk run --local --watch-namespace ${CHE_NAMESPACE} --enable-delve & OPERATOR_SDK_PID=$! wait ${OPERATOR_SDK_PID} diff --git a/pkg/apis/org/v1/che_types.go b/pkg/apis/org/v1/che_types.go index 75edf7615..e1e8e6d2a 100644 --- a/pkg/apis/org/v1/che_types.go +++ b/pkg/apis/org/v1/che_types.go @@ -553,6 +553,9 @@ type CheClusterStatus struct { // Indicates whether an Identity Provider instance (Keycloak / RH SSO) has been configured to integrate with the OpenShift OAuth. // +optional OpenShiftoAuthProvisioned bool `json:"openShiftoAuthProvisioned"` + // Indicates whether an Identity Provider instance (Keycloak / RH SSO) has been configured to integrate with the GitHub OAuth. + // +optional + GitHubOAuthProvisioned bool `json:"gitHubOAuthProvisioned"` // Status of a Che installation. Can be `Available`, `Unavailable`, or `Available, Rolling Update in Progress` // +optional // +operator-sdk:gen-csv:customresourcedefinitions.statusDescriptors=true diff --git a/pkg/controller/che/che_controller.go b/pkg/controller/che/che_controller.go index 65c884103..f5f2e7a94 100644 --- a/pkg/controller/che/che_controller.go +++ b/pkg/controller/che/che_controller.go @@ -718,15 +718,15 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e } // Create a new Postgres deployment - deploymentStatus := postgres.SyncPostgresDeploymentToCluster(deployContext) + provisioned, err := postgres.SyncPostgresDeploymentToCluster(deployContext) if !tests { - if !deploymentStatus.Continue { + if !provisioned { logrus.Infof("Waiting on deployment '%s' to be ready", postgres.PostgresDeploymentName) - if deploymentStatus.Err != nil { - logrus.Error(deploymentStatus.Err) + if err != nil { + logrus.Error(err) } - return reconcile.Result{Requeue: deploymentStatus.Requeue}, deploymentStatus.Err + return reconcile.Result{}, err } } @@ -741,15 +741,16 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e } identityProviderPostgresPassword = password } - pgCommand := identity_provider.GetPostgresProvisionCommand(identityProviderPostgresPassword) dbStatus := instance.Status.DbProvisoned // provision Db and users for Che and Keycloak servers if !dbStatus { - podToExec, err := util.K8sclient.GetDeploymentPod(postgres.PostgresDeploymentName, instance.Namespace) - if err != nil { - return reconcile.Result{}, err - } - _, err = util.K8sclient.ExecIntoPod(podToExec, pgCommand, "create Keycloak DB, user, privileges", instance.Namespace) + _, err := util.K8sclient.ExecIntoPod( + instance, + postgres.PostgresDeploymentName, + func(cr *orgv1.CheCluster) (string, error) { + return identity_provider.GetPostgresProvisionCommand(identityProviderPostgresPassword), nil + }, + "create Keycloak DB, user, privileges") if err == nil { for { instance.Status.DbProvisoned = true @@ -842,7 +843,7 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e if err != nil { logrus.Errorf("Error provisioning the identity provider to cluster: %v", err) } - return reconcile.Result{RequeueAfter: time.Second * 1}, err + return reconcile.Result{}, err } } @@ -886,12 +887,12 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e } // Create a new che deployment - deploymentStatus := server.SyncCheDeploymentToCluster(deployContext) + provisioned, err = server.SyncCheDeploymentToCluster(deployContext) if !tests { - if !deploymentStatus.Continue { + if !provisioned { logrus.Infof("Waiting on deployment '%s' to be ready", cheFlavor) - if deploymentStatus.Err != nil { - logrus.Error(deploymentStatus.Err) + if err != nil { + logrus.Error(err) } deployment, err := r.GetEffectiveDeployment(instance, cheFlavor) @@ -912,7 +913,7 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e } } } - return reconcile.Result{Requeue: deploymentStatus.Requeue}, deploymentStatus.Err + return reconcile.Result{}, err } } // Update available status diff --git a/pkg/controller/che/che_controller_test.go b/pkg/controller/che/che_controller_test.go index 62580b1ad..8eb112255 100644 --- a/pkg/controller/che/che_controller_test.go +++ b/pkg/controller/che/che_controller_test.go @@ -805,7 +805,7 @@ func TestCheController(t *testing.T) { if err = r.client.Get(context.TODO(), types.NamespacedName{Name: cheCR.Name, Namespace: cheCR.Namespace}, cheCR); err != nil { t.Errorf("Failed to get the Che custom resource %s: %s", cheCR.Name, err) } - if err = identity_provider.SyncIdentityProviderItems(deployContext, "che"); err != nil { + if _, err = identity_provider.SyncOpenShiftIdentityProviderItems(deployContext); err != nil { t.Errorf("Failed to create the items for the identity provider: %s", err) } oAuthClientName := cheCR.Spec.Auth.OAuthClientName diff --git a/pkg/controller/che/update.go b/pkg/controller/che/update.go index deb8f9fd0..b03222c13 100644 --- a/pkg/controller/che/update.go +++ b/pkg/controller/che/update.go @@ -13,6 +13,7 @@ package che import ( "context" + identity_provider "github.com/eclipse/che-operator/pkg/deploy/identity-provider" orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" @@ -51,12 +52,18 @@ func (r *ReconcileChe) ReconcileIdentityProvider(instance *orgv1.CheCluster, isO if err := r.client.Get(context.TODO(), types.NamespacedName{Name: "keycloak", Namespace: instance.Namespace}, keycloakDeployment); err != nil { logrus.Errorf("Deployment %s not found: %s", keycloakDeployment.Name, err) } - deleteOpenShiftIdentityProviderProvisionCommand := identity_provider.GetDeleteOpenShiftIdentityProviderProvisionCommand(instance, isOpenShift4) - podToExec, err := util.K8sclient.GetDeploymentPod(keycloakDeployment.Name, instance.Namespace) - if err != nil { - logrus.Errorf("Failed to retrieve pod name. Further exec will fail") + + providerName := "openshift-v3" + if isOpenShift4 { + providerName = "openshift-v4" } - _, err = util.K8sclient.ExecIntoPod(podToExec, deleteOpenShiftIdentityProviderProvisionCommand, "delete OpenShift identity provider", instance.Namespace) + _, err := util.K8sclient.ExecIntoPod( + instance, + keycloakDeployment.Name, + func(cr *orgv1.CheCluster) (string, error) { + return identity_provider.GetIdentityProviderDeleteCommand(instance, providerName) + }, + "delete OpenShift identity provider") if err == nil { oAuthClient := &oauth.OAuthClient{} oAuthClientName := instance.Spec.Auth.OAuthClientName diff --git a/pkg/deploy/defaults.go b/pkg/deploy/defaults.go index 4ef374bec..a2f4c9065 100644 --- a/pkg/deploy/defaults.go +++ b/pkg/deploy/defaults.go @@ -109,9 +109,10 @@ const ( CheEclipseOrg = "che.eclipse.org" // che.eclipse.org annotations - CheEclipseOrgMountPath = "che.eclipse.org/mount-path" - CheEclipseOrgMountAs = "che.eclipse.org/mount-as" - CheEclipseOrgEnvName = "che.eclipse.org/env-name" + CheEclipseOrgMountPath = "che.eclipse.org/mount-path" + CheEclipseOrgMountAs = "che.eclipse.org/mount-as" + CheEclipseOrgEnvName = "che.eclipse.org/env-name" + CheEclipseOrgGithubOAuthCredentials = "che.eclipse.org/github-oauth-credentials" ) func InitDefaults(defaultsPath string) { diff --git a/pkg/deploy/deployment.go b/pkg/deploy/deployment.go index 8c1f6f3ee..b4e877d5a 100644 --- a/pkg/deploy/deployment.go +++ b/pkg/deploy/deployment.go @@ -46,37 +46,26 @@ var DeploymentDiffOpts = cmp.Options{ }), } -type DeploymentProvisioningStatus struct { - ProvisioningStatus - Deployment *appsv1.Deployment -} - func SyncDeploymentToCluster( deployContext *DeployContext, specDeployment *appsv1.Deployment, clusterDeployment *appsv1.Deployment, additionalDeploymentDiffOpts cmp.Options, - additionalDeploymentMerge func(*appsv1.Deployment, *appsv1.Deployment) *appsv1.Deployment) DeploymentProvisioningStatus { + additionalDeploymentMerge func(*appsv1.Deployment, *appsv1.Deployment) *appsv1.Deployment) (bool, error) { if err := MountSecrets(specDeployment, deployContext); err != nil { - return DeploymentProvisioningStatus{ - ProvisioningStatus: ProvisioningStatus{Requeue: true, Err: err}, - } + return false, err } clusterDeployment, err := GetClusterDeployment(specDeployment.Name, specDeployment.Namespace, deployContext.ClusterAPI.Client) if err != nil { - return DeploymentProvisioningStatus{ - ProvisioningStatus: ProvisioningStatus{Err: err}, - } + return false, err } if clusterDeployment == nil { logrus.Infof("Creating a new object: %s, name %s", specDeployment.Kind, specDeployment.Name) err := deployContext.ClusterAPI.Client.Create(context.TODO(), specDeployment) - return DeploymentProvisioningStatus{ - ProvisioningStatus: ProvisioningStatus{Requeue: true, Err: err}, - } + return false, err } // 2-step comparation process @@ -89,9 +78,7 @@ func SyncDeploymentToCluster( fmt.Printf("Difference:\n%s", diff) clusterDeployment = additionalDeploymentMerge(specDeployment, clusterDeployment) err := deployContext.ClusterAPI.Client.Update(context.TODO(), clusterDeployment) - return DeploymentProvisioningStatus{ - ProvisioningStatus: ProvisioningStatus{Requeue: true, Err: err}, - } + return false, err } } @@ -101,19 +88,15 @@ func SyncDeploymentToCluster( fmt.Printf("Difference:\n%s", diff) clusterDeployment.Spec = specDeployment.Spec err := deployContext.ClusterAPI.Client.Update(context.TODO(), clusterDeployment) - return DeploymentProvisioningStatus{ - ProvisioningStatus: ProvisioningStatus{Requeue: true, Err: err}, - } + return false, err } if clusterDeployment.Spec.Strategy.Type == appsv1.RollingUpdateDeploymentStrategyType && clusterDeployment.Status.Replicas > 1 { logrus.Infof("Deployment %s is in the rolling update state.", specDeployment.Name) } - return DeploymentProvisioningStatus{ - ProvisioningStatus: ProvisioningStatus{Continue: clusterDeployment.Status.AvailableReplicas == 1 && clusterDeployment.Status.Replicas == 1}, - Deployment: clusterDeployment, - } + provisioned := clusterDeployment.Status.AvailableReplicas == 1 && clusterDeployment.Status.Replicas == 1 + return provisioned, nil } func GetClusterDeployment(name string, namespace string, client runtimeClient.Client) (*appsv1.Deployment, error) { diff --git a/pkg/deploy/devfile-registry/deployment.go b/pkg/deploy/devfile-registry/deployment.go index d0669261d..d55c753ca 100644 --- a/pkg/deploy/devfile-registry/deployment.go +++ b/pkg/deploy/devfile-registry/deployment.go @@ -18,7 +18,7 @@ import ( v1 "k8s.io/api/core/v1" ) -func SyncDevfileRegistryDeploymentToCluster(deployContext *deploy.DeployContext) deploy.DeploymentProvisioningStatus { +func SyncDevfileRegistryDeploymentToCluster(deployContext *deploy.DeployContext) (bool, error) { registryType := "devfile" registryImage := util.GetValue(deployContext.CheCluster.Spec.Server.DevfileRegistryImage, deploy.DefaultDevfileRegistryImage(deployContext.CheCluster)) registryImagePullPolicy := v1.PullPolicy(util.GetValue(string(deployContext.CheCluster.Spec.Server.PluginRegistryPullPolicy), deploy.DefaultPullPolicyFromDockerImage(registryImage))) @@ -29,9 +29,7 @@ func SyncDevfileRegistryDeploymentToCluster(deployContext *deploy.DeployContext) clusterDeployment, err := deploy.GetClusterDeployment(deploy.DevfileRegistry, deployContext.CheCluster.Namespace, deployContext.ClusterAPI.Client) if err != nil { - return deploy.DeploymentProvisioningStatus{ - ProvisioningStatus: deploy.ProvisioningStatus{Err: err}, - } + return false, err } specDeployment, err := registry.GetSpecRegistryDeployment( @@ -45,9 +43,7 @@ func SyncDevfileRegistryDeploymentToCluster(deployContext *deploy.DeployContext) probePath) if err != nil { - return deploy.DeploymentProvisioningStatus{ - ProvisioningStatus: deploy.ProvisioningStatus{Err: err}, - } + return false, err } return deploy.SyncDeploymentToCluster(deployContext, specDeployment, clusterDeployment, nil, nil) diff --git a/pkg/deploy/devfile-registry/devfile_registry.go b/pkg/deploy/devfile-registry/devfile_registry.go index e40efe77b..280e84f2f 100644 --- a/pkg/deploy/devfile-registry/devfile_registry.go +++ b/pkg/deploy/devfile-registry/devfile_registry.go @@ -76,15 +76,14 @@ func SyncDevfileRegistryToCluster(deployContext *deploy.DeployContext, cheHost s deployContext.InternalService.DevfileRegistryHost = fmt.Sprintf("http://%s.%s.svc:8080", deploy.DevfileRegistry, deployContext.CheCluster.Namespace) // Deploy devfile registry - deploymentStatus := SyncDevfileRegistryDeploymentToCluster(deployContext) + provisioned, err := SyncDevfileRegistryDeploymentToCluster(deployContext) if !util.IsTestMode() { - if !deploymentStatus.Continue { + if !provisioned { logrus.Info("Waiting on deployment '" + deploy.DevfileRegistry + "' to be ready") - if deploymentStatus.Err != nil { - logrus.Error(deploymentStatus.Err) + if err != nil { + logrus.Error(err) } - - return false, deploymentStatus.Err + return provisioned, err } } } diff --git a/pkg/deploy/identity-provider/deployment_keycloak.go b/pkg/deploy/identity-provider/deployment_keycloak.go index 98cddc5b2..aa0ccfe13 100644 --- a/pkg/deploy/identity-provider/deployment_keycloak.go +++ b/pkg/deploy/identity-provider/deployment_keycloak.go @@ -35,7 +35,6 @@ import ( ) const ( - KeycloakDeploymentName = "keycloak" selectSslRequiredCommand = "OUT=$(psql keycloak -tAc \"SELECT 1 FROM REALM WHERE id = 'master'\"); " + "if [ $OUT -eq 1 ]; then psql keycloak -tAc \"SELECT ssl_required FROM REALM WHERE id = 'master'\"; fi" updateSslRequiredCommand = "psql keycloak -c \"update REALM set ssl_required='NONE' where id = 'master'\"" @@ -59,19 +58,15 @@ var ( } ) -func SyncKeycloakDeploymentToCluster(deployContext *deploy.DeployContext) deploy.DeploymentProvisioningStatus { - clusterDeployment, err := deploy.GetClusterDeployment(KeycloakDeploymentName, deployContext.CheCluster.Namespace, deployContext.ClusterAPI.Client) +func SyncKeycloakDeploymentToCluster(deployContext *deploy.DeployContext) (bool, error) { + clusterDeployment, err := deploy.GetClusterDeployment(IdentityProviderDeploymentName, deployContext.CheCluster.Namespace, deployContext.ClusterAPI.Client) if err != nil { - return deploy.DeploymentProvisioningStatus{ - ProvisioningStatus: deploy.ProvisioningStatus{Err: err}, - } + return false, err } specDeployment, err := getSpecKeycloakDeployment(deployContext, clusterDeployment) if err != nil { - return deploy.DeploymentProvisioningStatus{ - ProvisioningStatus: deploy.ProvisioningStatus{Err: err}, - } + return false, err } return deploy.SyncDeploymentToCluster(deployContext, specDeployment, clusterDeployment, keycloakCustomDiffOpts, keycloakAdditionalDeploymentMerge) @@ -81,7 +76,7 @@ func getSpecKeycloakDeployment( deployContext *deploy.DeployContext, clusterDeployment *appsv1.Deployment) (*appsv1.Deployment, error) { optionalEnv := true - labels := deploy.GetLabels(deployContext.CheCluster, KeycloakDeploymentName) + labels := deploy.GetLabels(deployContext.CheCluster, IdentityProviderDeploymentName) cheFlavor := deploy.DefaultCheFlavor(deployContext.CheCluster) keycloakImage := util.GetValue(deployContext.CheCluster.Spec.Auth.IdentityProviderImage, deploy.DefaultKeycloakImage(deployContext.CheCluster)) pullPolicy := corev1.PullPolicy(util.GetValue(string(deployContext.CheCluster.Spec.Auth.IdentityProviderImagePullPolicy), deploy.DefaultPullPolicyFromDockerImage(keycloakImage))) @@ -553,7 +548,7 @@ func getSpecKeycloakDeployment( APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ - Name: KeycloakDeploymentName, + Name: IdentityProviderDeploymentName, Namespace: deployContext.CheCluster.Namespace, Labels: labels, Annotations: map[string]string{ @@ -577,7 +572,7 @@ func getSpecKeycloakDeployment( }, Containers: []corev1.Container{ { - Name: KeycloakDeploymentName, + Name: IdentityProviderDeploymentName, Image: keycloakImage, ImagePullPolicy: pullPolicy, Command: []string{ @@ -586,7 +581,7 @@ func getSpecKeycloakDeployment( Args: args, Ports: []corev1.ContainerPort{ { - Name: KeycloakDeploymentName, + Name: IdentityProviderDeploymentName, ContainerPort: 8080, Protocol: "TCP", }, @@ -672,7 +667,7 @@ func isSslRequiredUpdatedForMasterRealm(deployContext *deploy.DeployContext) boo return false } - clusterDeployment, _ := deploy.GetClusterDeployment(KeycloakDeploymentName, deployContext.CheCluster.Namespace, deployContext.ClusterAPI.Client) + clusterDeployment, _ := deploy.GetClusterDeployment(IdentityProviderDeploymentName, deployContext.CheCluster.Namespace, deployContext.ClusterAPI.Client) if clusterDeployment == nil { return false } @@ -686,23 +681,25 @@ func isSslRequiredUpdatedForMasterRealm(deployContext *deploy.DeployContext) boo return dbValue == "NONE" } -func getSslRequiredForMasterRealm(checluster *orgv1.CheCluster) (string, error) { - podName, err := util.K8sclient.GetDeploymentPod(postgres.PostgresDeploymentName, checluster.Namespace) - if err != nil { - return "", err - } - - stdout, err := util.K8sclient.ExecIntoPod(podName, selectSslRequiredCommand, "", checluster.Namespace) +func getSslRequiredForMasterRealm(cr *orgv1.CheCluster) (string, error) { + stdout, err := util.K8sclient.ExecIntoPod( + cr, + postgres.PostgresDeploymentName, + func(cr *orgv1.CheCluster) (string, error) { + return selectSslRequiredCommand, nil + }, + "") return strings.TrimSpace(stdout), err } -func updateSslRequiredForMasterRealm(checluster *orgv1.CheCluster) error { - podName, err := util.K8sclient.GetDeploymentPod(postgres.PostgresDeploymentName, checluster.Namespace) - if err != nil { - return err - } - - _, err = util.K8sclient.ExecIntoPod(podName, updateSslRequiredCommand, "Update ssl_required to NONE", checluster.Namespace) +func updateSslRequiredForMasterRealm(cr *orgv1.CheCluster) error { + _, err := util.K8sclient.ExecIntoPod( + cr, + postgres.PostgresDeploymentName, + func(cr *orgv1.CheCluster) (string, error) { + return updateSslRequiredCommand, nil + }, + "Update ssl_required to NONE") return err } @@ -721,13 +718,11 @@ func ProvisionKeycloakResources(deployContext *deploy.DeployContext) error { } } - keycloakProvisionCommand := GetKeycloakProvisionCommand(deployContext.CheCluster) - podToExec, err := util.K8sclient.GetDeploymentPod(KeycloakDeploymentName, deployContext.CheCluster.Namespace) - if err != nil { - logrus.Errorf("Failed to retrieve pod name. Further exec will fail") - } - - _, err = util.K8sclient.ExecIntoPod(podToExec, keycloakProvisionCommand, "create realm, client and user", deployContext.CheCluster.Namespace) + _, err := util.K8sclient.ExecIntoPod( + deployContext.CheCluster, + IdentityProviderDeploymentName, + GetKeycloakProvisionCommand, + "create realm, client and user") return err } diff --git a/pkg/deploy/identity-provider/exec.go b/pkg/deploy/identity-provider/exec.go index aedf3f889..960c6e34e 100644 --- a/pkg/deploy/identity-provider/exec.go +++ b/pkg/deploy/identity-provider/exec.go @@ -14,7 +14,6 @@ package identity_provider import ( "bytes" "io/ioutil" - "strings" "text/template" v1 "github.com/eclipse/che-operator/pkg/apis/org/v1" @@ -34,152 +33,140 @@ func GetPostgresProvisionCommand(identityProviderPostgresPassword string) (comma return command } -func GetKeycloakProvisionCommand(cr *v1.CheCluster) (command string) { - requiredActions := "" - updateAdminPassword := cr.Spec.Auth.UpdateAdminPassword +func GetKeycloakProvisionCommand(cr *v1.CheCluster) (command string, err error) { cheFlavor := deploy.DefaultCheFlavor(cr) - keycloakRealm := util.GetValue(cr.Spec.Auth.IdentityProviderRealm, cheFlavor) - keycloakClientId := util.GetValue(cr.Spec.Auth.IdentityProviderClientId, cheFlavor+"-public") - keycloakUserEnvVar := "${KEYCLOAK_USER}" - keycloakPasswordEnvVar := "${KEYCLOAK_PASSWORD}" + requiredActions := (map[bool]string{true: "\"UPDATE_PASSWORD\"", false: ""})[cr.Spec.Auth.UpdateAdminPassword] + keycloakTheme := (map[bool]string{true: "rh-sso", false: "che"})[cheFlavor == "codeready"] + realmDisplayName := (map[bool]string{true: "CodeReady Workspaces", false: "Eclipse Che"})[cheFlavor == "codeready"] - if updateAdminPassword { - requiredActions = "\"UPDATE_PASSWORD\"" + script, keycloakRealm, keycloakClientId, keycloakUserEnvVar, keycloakPasswordEnvVar := getDefaults(cr) + data := struct { + Script string + KeycloakAdminUserName string + KeycloakAdminPassword string + KeycloakRealm string + RealmDisplayName string + KeycloakTheme string + CheHost string + KeycloakClientId string + RequiredActions string + }{ + script, + keycloakUserEnvVar, + keycloakPasswordEnvVar, + keycloakRealm, + realmDisplayName, + keycloakTheme, + cr.Spec.Server.CheHost, + keycloakClientId, + requiredActions, } - file, err := ioutil.ReadFile("/tmp/keycloak_provision") - if err != nil { - logrus.Errorf("Failed to locate keycloak entrypoint file: %s", err) - } - keycloakTheme := "che" - realmDisplayName := "Eclipse Che" - script := "/opt/jboss/keycloak/bin/kcadm.sh" - if cheFlavor == "codeready" { - keycloakTheme = "rh-sso" - realmDisplayName = "CodeReady Workspaces" - script = "/opt/eap/bin/kcadm.sh" - keycloakUserEnvVar = "${SSO_ADMIN_USERNAME}" - keycloakPasswordEnvVar = "${SSO_ADMIN_PASSWORD}" - } - str := string(file) - r := strings.NewReplacer("$script", script, - "$keycloakAdminUserName", keycloakUserEnvVar, - "$keycloakAdminPassword", keycloakPasswordEnvVar, - "$keycloakRealm", keycloakRealm, - "$realmDisplayName", realmDisplayName, - "$keycloakClientId", keycloakClientId, - "$keycloakTheme", keycloakTheme, - "$cheHost", cr.Spec.Server.CheHost, - "$requiredActions", requiredActions) - createRealmClientUserCommand := r.Replace(str) - if cheFlavor == "che" { - command = "cd /scripts && export JAVA_TOOL_OPTIONS=-Duser.home=. && " + createRealmClientUserCommand - } else { - command = "cd /home/jboss && " + createRealmClientUserCommand - } - return command + return getCommandFromTemplateFile(cr, "/tmp/keycloak-provision.sh", data) } -func GetOpenShiftIdentityProviderProvisionCommand(cr *v1.CheCluster, oAuthClientName string, oauthSecret string, isOpenShift4 bool) (command string, err error) { - cheFlavor := deploy.DefaultCheFlavor(cr) +func GetOpenShiftIdentityProviderProvisionCommand(cr *v1.CheCluster, oAuthClientName string, oauthSecret string) (string, error) { + isOpenShift4 := util.IsOpenShift4 + providerId := (map[bool]string{true: "openshift-v4", false: "openshift-v3"})[isOpenShift4] openShiftApiUrl, err := util.GetClusterPublicHostname(isOpenShift4) if err != nil { logrus.Errorf("Failed to auto-detect public OpenShift API URL. Configure it in Identity provider details page in Keycloak admin console: %s", err) return "", err } - keycloakUserEnvVar := "${KEYCLOAK_USER}" - keycloakPasswordEnvVar := "${KEYCLOAK_PASSWORD}" - keycloakRealm := util.GetValue(cr.Spec.Auth.IdentityProviderRealm, cheFlavor) - script := "/opt/jboss/keycloak/bin/kcadm.sh" - if cheFlavor == "codeready" { - script = "/opt/eap/bin/kcadm.sh" - keycloakUserEnvVar = "${SSO_ADMIN_USERNAME}" - keycloakPasswordEnvVar = "${SSO_ADMIN_PASSWORD}" + script, keycloakRealm, keycloakClientId, keycloakUserEnvVar, keycloakPasswordEnvVar := getDefaults(cr) + data := struct { + Script string + KeycloakAdminUserName string + KeycloakAdminPassword string + KeycloakRealm string + ProviderId string + OAuthClientName string + OauthSecret string + OpenShiftApiUrl string + KeycloakClientId string + }{ + script, + keycloakUserEnvVar, + keycloakPasswordEnvVar, + keycloakRealm, + providerId, + oAuthClientName, + oauthSecret, + openShiftApiUrl, + keycloakClientId, } - keycloakClientId := util.GetValue(cr.Spec.Auth.IdentityProviderClientId, cheFlavor+"-public") + return getCommandFromTemplateFile(cr, "/tmp/oauth-provision.sh", data) +} - providerId := "openshift-v3" - if isOpenShift4 { - providerId = "openshift-v4" +func GetGitHubIdentityProviderCreateCommand(deployContext *deploy.DeployContext) (string, error) { + cr := deployContext.CheCluster + script, keycloakRealm, _, keycloakUserEnvVar, keycloakPasswordEnvVar := getDefaults(cr) + data := struct { + Script string + KeycloakAdminUserName string + KeycloakAdminPassword string + KeycloakRealm string + ProviderId string + }{ + script, + keycloakUserEnvVar, + keycloakPasswordEnvVar, + keycloakRealm, + "github", } + return getCommandFromTemplateFile(cr, "/tmp/create-github-identity-provider.sh", data) +} - file, err := ioutil.ReadFile("/tmp/oauth_provision") - if err != nil { - logrus.Errorf("Failed to locate keycloak oauth provisioning file: %s", err) +func GetIdentityProviderDeleteCommand(cr *v1.CheCluster, identityProvider string) (string, error) { + script, keycloakRealm, _, keycloakUserEnvVar, keycloakPasswordEnvVar := getDefaults(cr) + data := struct { + Script string + KeycloakRealm string + KeycloakAdminUserName string + KeycloakAdminPassword string + ProviderId string + }{ + script, + keycloakRealm, + keycloakUserEnvVar, + keycloakPasswordEnvVar, + identityProvider, } - createOpenShiftIdentityProviderTemplate := string(file) - /* - In order to have the token-exchange currently working and easily usable, we should (in case of Keycloak) be able to - - Automatically redirect the user to its Keycloak account page to set those required values when the email is empty (instead of failing here: https://github.com/eclipse/che/blob/master/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakEnvironmentInitalizationFilter.java#L125) - - Or at least point with a link to the place where it can be set (the KeycloakSettings PROFILE_ENDPOINT_SETTING value) - (cf. here: https://github.com/eclipse/che/blob/master/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/KeycloakSettings.java#L117) - */ + return getCommandFromTemplateFile(cr, "/tmp/delete-identity-provider.sh", data) +} - template, err := template.New("IdentityProviderProvisioning").Parse(createOpenShiftIdentityProviderTemplate) +func getCommandFromTemplateFile(cr *v1.CheCluster, templateFile string, data interface{}) (string, error) { + cheFlavor := deploy.DefaultCheFlavor(cr) + + file, err := ioutil.ReadFile(templateFile) if err != nil { return "", err } + + template, err := template.New("Template").Parse(string(file)) + if err != nil { + return "", err + } + buffer := new(bytes.Buffer) - err = template.Execute( - buffer, - struct { - Script string - KeycloakAdminUserName string - KeycloakAdminPassword string - KeycloakRealm string - ProviderId string - OAuthClientName string - OauthSecret string - OpenShiftApiUrl string - KeycloakClientId string - }{ - script, - keycloakUserEnvVar, - keycloakPasswordEnvVar, - keycloakRealm, - providerId, - oAuthClientName, - oauthSecret, - openShiftApiUrl, - keycloakClientId, - }) + err = template.Execute(buffer, data) if err != nil { return "", err } if cheFlavor == "che" { - command = "cd /scripts && export JAVA_TOOL_OPTIONS=-Duser.home=. && " + buffer.String() - } else { - command = "cd /home/jboss && " + buffer.String() + return "cd /scripts && export JAVA_TOOL_OPTIONS=-Duser.home=. && " + buffer.String(), nil } - return command, nil + return "cd /home/jboss && " + buffer.String(), nil } -func GetDeleteOpenShiftIdentityProviderProvisionCommand(cr *v1.CheCluster, isOpenShift4 bool) (command string) { +func getDefaults(cr *v1.CheCluster) (string, string, string, string, string) { cheFlavor := deploy.DefaultCheFlavor(cr) keycloakRealm := util.GetValue(cr.Spec.Auth.IdentityProviderRealm, cheFlavor) - script := "/opt/jboss/keycloak/bin/kcadm.sh" - keycloakUserEnvVar := "${KEYCLOAK_USER}" - keycloakPasswordEnvVar := "${KEYCLOAK_PASSWORD}" + keycloakClientId := util.GetValue(cr.Spec.Auth.IdentityProviderClientId, cheFlavor+"-public") if cheFlavor == "codeready" { - script = "/opt/eap/bin/kcadm.sh" - keycloakUserEnvVar = "${SSO_ADMIN_USERNAME}" - keycloakPasswordEnvVar = "${SSO_ADMIN_PASSWORD}" + return "/opt/eap/bin/kcadm.sh", keycloakRealm, keycloakClientId, "${SSO_ADMIN_USERNAME}", "${SSO_ADMIN_PASSWORD}" } - providerName := "openshift-v3" - if isOpenShift4 { - providerName = "openshift-v4" - } - deleteOpenShiftIdentityProviderCommand := - script + " config credentials --server http://0.0.0.0:8080/auth " + - "--realm master --user " + keycloakUserEnvVar + " --password " + keycloakPasswordEnvVar + " && " + - "if " + script + " get identity-provider/instances/" + providerName + " -r " + keycloakRealm + " ; then " + - script + " delete identity-provider/instances/" + providerName + " -r " + keycloakRealm + " ; fi" - if cheFlavor == "che" { - command = "cd /scripts && export JAVA_TOOL_OPTIONS=-Duser.home=. && " + deleteOpenShiftIdentityProviderCommand - } else { - command = "cd /home/jboss && " + deleteOpenShiftIdentityProviderCommand - } - return command + return "/opt/jboss/keycloak/bin/kcadm.sh", keycloakRealm, keycloakClientId, "${KEYCLOAK_USER}", "${KEYCLOAK_PASSWORD}" } diff --git a/pkg/deploy/identity-provider/identity_provider.go b/pkg/deploy/identity-provider/identity_provider.go index 35fb84802..b77d98085 100644 --- a/pkg/deploy/identity-provider/identity_provider.go +++ b/pkg/deploy/identity-provider/identity_provider.go @@ -16,106 +16,128 @@ import ( "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" - "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" ) const ( - Keycloak = "keycloak" + IdentityProviderServiceName = "keycloak" + IdentityProviderExposureName = "keycloak" + IdentityProviderDeploymentName = "keycloak" ) var ( oAuthClientDiffOpts = cmpopts.IgnoreFields(oauth.OAuthClient{}, "TypeMeta", "ObjectMeta") + syncItems = []func(*deploy.DeployContext) (bool, error){ + syncService, + syncExposure, + SyncKeycloakDeploymentToCluster, + syncKeycloakResources, + syncOpenShiftIdentityProvider, + SyncGitHubOAuth, + } ) // SyncIdentityProviderToCluster instantiates the identity provider (Keycloak) in the cluster. Returns true if // the provisioning is complete, false if requeue of the reconcile request is needed. func SyncIdentityProviderToCluster(deployContext *deploy.DeployContext) (bool, error) { - instance := deployContext.CheCluster - cheHost := instance.Spec.Server.CheHost - protocol := "http" - if instance.Spec.Server.TlsSupport { - protocol = "https" - } - cheFlavor := deploy.DefaultCheFlavor(instance) - cheMultiUser := deploy.GetCheMultiUser(instance) - tests := util.IsTestMode() - isOpenShift := util.IsOpenShift - - if instance.Spec.Auth.ExternalIdentityProvider { + cr := deployContext.CheCluster + if cr.Spec.Auth.ExternalIdentityProvider { return true, nil } + cheMultiUser := deploy.GetCheMultiUser(cr) if cheMultiUser == "false" { - if util.K8sclient.IsDeploymentExists("keycloak", instance.Namespace) { - util.K8sclient.DeleteDeployment("keycloak", instance.Namespace) + if util.K8sclient.IsDeploymentExists("keycloak", cr.Namespace) { + util.K8sclient.DeleteDeployment("keycloak", cr.Namespace) } - return true, nil } - keycloakLabels := deploy.GetLabels(instance, "keycloak") - - serviceStatus := deploy.SyncServiceToCluster(deployContext, "keycloak", []string{"http"}, []int32{8080}, keycloakLabels) - if !tests { - if !serviceStatus.Continue { - logrus.Info("Waiting on service 'keycloak' to be ready") - if serviceStatus.Err != nil { - logrus.Error(serviceStatus.Err) + for _, syncItem := range syncItems { + provisioned, err := syncItem(deployContext) + if !util.IsTestMode() { + if !provisioned { + return false, err } - - return false, serviceStatus.Err } } - additionalLabels := (map[bool]string{true: instance.Spec.Auth.IdentityProviderRoute.Labels, false: instance.Spec.Auth.IdentityProviderIngress.Labels})[util.IsOpenShift] - endpoint, done, err := expose.Expose(deployContext, cheHost, Keycloak, additionalLabels) + return true, nil +} + +func syncService(deployContext *deploy.DeployContext) (bool, error) { + cr := deployContext.CheCluster + labels := deploy.GetLabels(cr, IdentityProviderServiceName) + + serviceStatus := deploy.SyncServiceToCluster( + deployContext, + IdentityProviderServiceName, + []string{"http"}, + []int32{8080}, + labels) + + return serviceStatus.Continue, serviceStatus.Err +} + +func syncExposure(deployContext *deploy.DeployContext) (bool, error) { + cr := deployContext.CheCluster + + protocol := (map[bool]string{ + true: "https", + false: "http"})[cr.Spec.Server.TlsSupport] + additionalLabels := (map[bool]string{ + true: cr.Spec.Auth.IdentityProviderRoute.Labels, + false: cr.Spec.Auth.IdentityProviderIngress.Labels})[util.IsOpenShift] + + endpoint, done, err := expose.Expose( + deployContext, + cr.Spec.Server.CheHost, + IdentityProviderExposureName, + additionalLabels) if !done { return false, err } - keycloakURL := protocol + "://" + endpoint - deployContext.InternalService.KeycloakHost = fmt.Sprintf("%s://%s.%s.svc:%d", "http", "keycloak", deployContext.CheCluster.Namespace, 8080) - if instance.Spec.Auth.IdentityProviderURL != keycloakURL { - instance.Spec.Auth.IdentityProviderURL = keycloakURL + keycloakURL := protocol + "://" + endpoint + deployContext.InternalService.KeycloakHost = fmt.Sprintf("%s://%s.%s.svc:%d", "http", "keycloak", cr.Namespace, 8080) + + if cr.Spec.Auth.IdentityProviderURL != keycloakURL { + cr.Spec.Auth.IdentityProviderURL = keycloakURL if err := deploy.UpdateCheCRSpec(deployContext, "Keycloak URL", keycloakURL); err != nil { return false, err } - instance.Status.KeycloakURL = keycloakURL + cr.Status.KeycloakURL = keycloakURL if err := deploy.UpdateCheCRStatus(deployContext, "Keycloak URL", keycloakURL); err != nil { return false, err } } - deploymentStatus := SyncKeycloakDeploymentToCluster(deployContext) - if !tests { - if !deploymentStatus.Continue { - logrus.Info("Waiting on deployment 'keycloak' to be ready") - if deploymentStatus.Err != nil { - logrus.Error(deploymentStatus.Err) - } + return true, nil +} - return false, deploymentStatus.Err - } - } - - if !tests { - if !instance.Status.KeycloakProvisoned { +func syncKeycloakResources(deployContext *deploy.DeployContext) (bool, error) { + if !util.IsTestMode() { + cr := deployContext.CheCluster + if !cr.Status.KeycloakProvisoned { if err := ProvisionKeycloakResources(deployContext); err != nil { - logrus.Error(err) return false, err } for { - instance.Status.KeycloakProvisoned = true + cr.Status.KeycloakProvisoned = true if err := deploy.UpdateCheCRStatus(deployContext, "status: provisioned with Keycloak", "true"); err != nil && errors.IsConflict(err) { @@ -127,61 +149,64 @@ func SyncIdentityProviderToCluster(deployContext *deploy.DeployContext) (bool, e } } - if isOpenShift && util.IsOAuthEnabled(instance) { - if err := SyncIdentityProviderItems(deployContext, cheFlavor); err != nil { + return true, nil +} + +func syncOpenShiftIdentityProvider(deployContext *deploy.DeployContext) (bool, error) { + cr := deployContext.CheCluster + if util.IsOpenShift && util.IsOAuthEnabled(cr) { + return SyncOpenShiftIdentityProviderItems(deployContext) + } + return true, nil +} + +func SyncOpenShiftIdentityProviderItems(deployContext *deploy.DeployContext) (bool, error) { + cr := deployContext.CheCluster + + oAuthClientName := cr.Spec.Auth.OAuthClientName + if len(oAuthClientName) < 1 { + oAuthClientName = cr.Name + "-openshift-identity-provider-" + strings.ToLower(util.GeneratePasswd(6)) + cr.Spec.Auth.OAuthClientName = oAuthClientName + if err := deploy.UpdateCheCRSpec(deployContext, "oAuthClient name", oAuthClientName); err != nil { return false, err } } - return true, nil -} - -func SyncIdentityProviderItems(deployContext *deploy.DeployContext, cheFlavor string) error { - instance := deployContext.CheCluster - tests := util.IsTestMode() - isOpenShift4 := util.IsOpenShift4 - keycloakDeploymentName := KeycloakDeploymentName - oAuthClientName := instance.Spec.Auth.OAuthClientName - if len(oAuthClientName) < 1 { - oAuthClientName = instance.Name + "-openshift-identity-provider-" + strings.ToLower(util.GeneratePasswd(6)) - instance.Spec.Auth.OAuthClientName = oAuthClientName - if err := deploy.UpdateCheCRSpec(deployContext, "oAuthClient name", oAuthClientName); err != nil { - return err - } - } - oauthSecret := instance.Spec.Auth.OAuthSecret + oauthSecret := cr.Spec.Auth.OAuthSecret if len(oauthSecret) < 1 { oauthSecret = util.GeneratePasswd(12) - instance.Spec.Auth.OAuthSecret = oauthSecret + cr.Spec.Auth.OAuthSecret = oauthSecret if err := deploy.UpdateCheCRSpec(deployContext, "oAuth secret name", oauthSecret); err != nil { - return err + return false, err } } - keycloakURL := instance.Spec.Auth.IdentityProviderURL - keycloakRealm := util.GetValue(instance.Spec.Auth.IdentityProviderRealm, cheFlavor) - oAuthClient := deploy.NewOAuthClient(oAuthClientName, oauthSecret, keycloakURL, keycloakRealm, isOpenShift4) - if _, err := deploy.Sync(deployContext, oAuthClient, oAuthClientDiffOpts); err != nil { - return err + keycloakURL := cr.Spec.Auth.IdentityProviderURL + cheFlavor := deploy.DefaultCheFlavor(cr) + keycloakRealm := util.GetValue(cr.Spec.Auth.IdentityProviderRealm, cheFlavor) + oAuthClient := deploy.NewOAuthClient(oAuthClientName, oauthSecret, keycloakURL, keycloakRealm, util.IsOpenShift4) + provisioned, err := deploy.Sync(deployContext, oAuthClient, oAuthClientDiffOpts) + if !provisioned { + return false, err } - if !tests && !instance.Status.OpenShiftoAuthProvisioned { - // note that this uses the instance.Spec.Auth.IdentityProviderRealm and instance.Spec.Auth.IdentityProviderClientId. - // because we're not doing much of a change detection on those fields, we can't react on them changing here. - openShiftIdentityProviderCommand, err := GetOpenShiftIdentityProviderProvisionCommand(instance, oAuthClientName, oauthSecret, isOpenShift4) - if err != nil { - logrus.Errorf("Failed to build identity provider provisioning command") - return err - } - podToExec, err := util.K8sclient.GetDeploymentPod(keycloakDeploymentName, instance.Namespace) - if err != nil { - logrus.Errorf("Failed to retrieve pod name. Further exec will fail") - return err - } - _, err = util.K8sclient.ExecIntoPod(podToExec, openShiftIdentityProviderCommand, "create OpenShift identity provider", instance.Namespace) - if err == nil { + if !util.IsTestMode() { + if !cr.Status.OpenShiftoAuthProvisioned { + // note that this uses the instance.Spec.Auth.IdentityProviderRealm and instance.Spec.Auth.IdentityProviderClientId. + // because we're not doing much of a change detection on those fields, we can't react on them changing here. + _, err := util.K8sclient.ExecIntoPod( + cr, + IdentityProviderDeploymentName, + func(cr *orgv1.CheCluster) (string, error) { + return GetOpenShiftIdentityProviderProvisionCommand(cr, oAuthClientName, oauthSecret) + }, + "Create OpenShift identity provider") + if err != nil { + return false, err + } + for { - instance.Status.OpenShiftoAuthProvisioned = true + cr.Status.OpenShiftoAuthProvisioned = true if err := deploy.UpdateCheCRStatus(deployContext, "status: provisioned with OpenShift identity provider", "true"); err != nil && errors.IsConflict(err) { @@ -192,7 +217,80 @@ func SyncIdentityProviderItems(deployContext *deploy.DeployContext, cheFlavor st } } } - return nil + return true, nil +} + +// SyncGitHubOAuth provisions GitHub OAuth if secret with +// annotation `che.eclipse.org/github-oauth-credentials=true` 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{IdentityProviderDeploymentName + "-secret"}) + + listOptions := &client.ListOptions{ + LabelSelector: labels.NewSelector(). + Add(*kubernetesPartOfLabelSelectorRequirement). + Add(*kubernetesComponentLabelSelectorRequirement), + } + if err := deployContext.ClusterAPI.Client.List(context.TODO(), secrets, listOptions); err != nil { + return false, err + } + + isGitHubOAuthCredentialsExists := false + for _, secret := range secrets.Items { + if secret.Annotations[deploy.CheEclipseOrgGithubOAuthCredentials] == "true" { + isGitHubOAuthCredentialsExists = true + break + } + } + + if isGitHubOAuthCredentialsExists { + if !cr.Status.GitHubOAuthProvisioned { + if !util.IsTestMode() { + _, err := util.K8sclient.ExecIntoPod( + cr, + IdentityProviderDeploymentName, + func(cr *orgv1.CheCluster) (string, error) { + return GetGitHubIdentityProviderCreateCommand(deployContext) + }, + "Create GitHub OAuth") + if err != nil { + return false, err + } + } + + cr.Status.GitHubOAuthProvisioned = true + if err := deploy.UpdateCheCRStatus(deployContext, "status: GitHub OAuth provisioned", "true"); err != nil { + return false, err + } + } + } else { + if cr.Status.GitHubOAuthProvisioned { + if !util.IsTestMode() { + _, err := util.K8sclient.ExecIntoPod( + cr, + IdentityProviderDeploymentName, + func(cr *orgv1.CheCluster) (string, error) { + return GetIdentityProviderDeleteCommand(cr, "github") + }, + "Delete GitHub OAuth") + if err != nil { + return false, err + } + } + + cr.Status.GitHubOAuthProvisioned = false + if err := deploy.UpdateCheCRStatus(deployContext, "status: GitHub OAuth provisioned", "false"); err != nil { + return false, err + } + } + } + + return true, nil } func reload(deployContext *deploy.DeployContext) error { diff --git a/pkg/deploy/identity-provider/identity_provider_test.go b/pkg/deploy/identity-provider/identity_provider_test.go new file mode 100644 index 000000000..d43e816d7 --- /dev/null +++ b/pkg/deploy/identity-provider/identity_provider_test.go @@ -0,0 +1,162 @@ +// +// 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 identity_provider + +import ( + "os" + "reflect" + + "github.com/eclipse/che-operator/pkg/deploy" + + "github.com/google/go-cmp/cmp" + + 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 TestSyncGitHubOAuth(t *testing.T) { + type testCase struct { + name string + initCR *orgv1.CheCluster + expectedCR *orgv1.CheCluster + initObjects []runtime.Object + } + + testCases := []testCase{ + { + 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-credentials", + Namespace: "eclipse-che", + Labels: map[string]string{ + deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, + deploy.KubernetesComponentLabelKey: "keycloak-secret", + }, + Annotations: map[string]string{ + deploy.CheEclipseOrgGithubOAuthCredentials: "true", + }, + }, + Data: map[string][]byte{ + "key": []byte("key-data"), + }, + }, + }, + }, + { + name: "Should not provision GitHub OAuth", + initCR: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + }, + }, + expectedCR: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + }, + }, + initObjects: []runtime.Object{ + &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "github-credentials", + Namespace: "eclipse-che", + Labels: map[string]string{ + deploy.KubernetesPartOfLabelKey: deploy.CheEclipseOrg, + deploy.KubernetesComponentLabelKey: "keycloak-secret", + }, + }, + Data: map[string][]byte{ + "key": []byte("key-data"), + }, + }, + }, + }, + { + name: "Should delete GitHub OAuth", + initCR: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + }, + Status: orgv1.CheClusterStatus{ + GitHubOAuthProvisioned: true, + }, + }, + expectedCR: &orgv1.CheCluster{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "eclipse-che", + ResourceVersion: "1", + }, + Status: orgv1.CheClusterStatus{ + GitHubOAuthProvisioned: false, + }, + }, + initObjects: []runtime.Object{}, + }, + } + + 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, testCase.initCR) + cli := fake.NewFakeClientWithScheme(scheme.Scheme, testCase.initObjects...) + + deployContext := &deploy.DeployContext{ + CheCluster: testCase.initCR, + ClusterAPI: deploy.ClusterAPI{ + Client: cli, + Scheme: scheme.Scheme, + }, + } + + _, err := SyncGitHubOAuth(deployContext) + if err != nil { + t.Fatalf("Error mounting secret: %v", err) + } + + if !reflect.DeepEqual(testCase.expectedCR, testCase.initCR) { + t.Errorf("Expected CR and CR returned from API server differ (-want, +got): %v", cmp.Diff(testCase.expectedCR, testCase.initCR)) + } + }) + } +} diff --git a/pkg/deploy/plugin-registry/deployment.go b/pkg/deploy/plugin-registry/deployment.go index bc0bc493d..8d6dfa9a3 100644 --- a/pkg/deploy/plugin-registry/deployment.go +++ b/pkg/deploy/plugin-registry/deployment.go @@ -18,7 +18,7 @@ import ( corev1 "k8s.io/api/core/v1" ) -func SyncPluginRegistryDeploymentToCluster(deployContext *deploy.DeployContext) deploy.DeploymentProvisioningStatus { +func SyncPluginRegistryDeploymentToCluster(deployContext *deploy.DeployContext) (bool, error) { registryType := "plugin" registryImage := util.GetValue(deployContext.CheCluster.Spec.Server.PluginRegistryImage, deploy.DefaultPluginRegistryImage(deployContext.CheCluster)) registryImagePullPolicy := corev1.PullPolicy(util.GetValue(string(deployContext.CheCluster.Spec.Server.PluginRegistryPullPolicy), deploy.DefaultPullPolicyFromDockerImage(registryImage))) @@ -29,9 +29,7 @@ func SyncPluginRegistryDeploymentToCluster(deployContext *deploy.DeployContext) clusterDeployment, err := deploy.GetClusterDeployment(deploy.PluginRegistry, deployContext.CheCluster.Namespace, deployContext.ClusterAPI.Client) if err != nil { - return deploy.DeploymentProvisioningStatus{ - ProvisioningStatus: deploy.ProvisioningStatus{Err: err}, - } + return false, err } specDeployment, err := registry.GetSpecRegistryDeployment( @@ -45,9 +43,7 @@ func SyncPluginRegistryDeploymentToCluster(deployContext *deploy.DeployContext) probePath) if err != nil { - return deploy.DeploymentProvisioningStatus{ - ProvisioningStatus: deploy.ProvisioningStatus{Err: err}, - } + return false, err } return deploy.SyncDeploymentToCluster(deployContext, specDeployment, clusterDeployment, nil, nil) diff --git a/pkg/deploy/plugin-registry/plugin_registry.go b/pkg/deploy/plugin-registry/plugin_registry.go index 159d49508..aa59c0e43 100644 --- a/pkg/deploy/plugin-registry/plugin_registry.go +++ b/pkg/deploy/plugin-registry/plugin_registry.go @@ -14,6 +14,7 @@ package plugin_registry import ( "encoding/json" "fmt" + "github.com/eclipse/che-operator/pkg/deploy" "github.com/eclipse/che-operator/pkg/deploy/expose" "github.com/eclipse/che-operator/pkg/util" @@ -77,15 +78,14 @@ func SyncPluginRegistryToCluster(deployContext *deploy.DeployContext, cheHost st deployContext.InternalService.PluginRegistryHost = fmt.Sprintf("http://%s.%s.svc:8080/v3", deploy.PluginRegistry, deployContext.CheCluster.Namespace) // Deploy plugin registry - deploymentStatus := SyncPluginRegistryDeploymentToCluster(deployContext) + provisioned, err := SyncPluginRegistryDeploymentToCluster(deployContext) if !util.IsTestMode() { - if !deploymentStatus.Continue { + if !provisioned { logrus.Info("Waiting on deployment '" + deploy.PluginRegistry + "' to be ready") - if deploymentStatus.Err != nil { - logrus.Error(deploymentStatus.Err) + if err != nil { + logrus.Error(err) } - - return false, deploymentStatus.Err + return provisioned, err } } } diff --git a/pkg/deploy/postgres/deployment_postgres.go b/pkg/deploy/postgres/deployment_postgres.go index 198d8a866..9d8a06a2b 100644 --- a/pkg/deploy/postgres/deployment_postgres.go +++ b/pkg/deploy/postgres/deployment_postgres.go @@ -30,19 +30,15 @@ var ( postgresAdminPassword = util.GeneratePasswd(12) ) -func SyncPostgresDeploymentToCluster(deployContext *deploy.DeployContext) deploy.DeploymentProvisioningStatus { +func SyncPostgresDeploymentToCluster(deployContext *deploy.DeployContext) (bool, error) { clusterDeployment, err := deploy.GetClusterDeployment(PostgresDeploymentName, deployContext.CheCluster.Namespace, deployContext.ClusterAPI.Client) if err != nil { - return deploy.DeploymentProvisioningStatus{ - ProvisioningStatus: deploy.ProvisioningStatus{Err: err}, - } + return false, err } specDeployment, err := getSpecPostgresDeployment(deployContext, clusterDeployment) if err != nil { - return deploy.DeploymentProvisioningStatus{ - ProvisioningStatus: deploy.ProvisioningStatus{Err: err}, - } + return false, err } return deploy.SyncDeploymentToCluster(deployContext, specDeployment, clusterDeployment, nil, nil) diff --git a/pkg/deploy/server/deployment_che.go b/pkg/deploy/server/deployment_che.go index 692bb0b86..70bae7870 100644 --- a/pkg/deploy/server/deployment_che.go +++ b/pkg/deploy/server/deployment_che.go @@ -27,19 +27,15 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -func SyncCheDeploymentToCluster(deployContext *deploy.DeployContext) deploy.DeploymentProvisioningStatus { +func SyncCheDeploymentToCluster(deployContext *deploy.DeployContext) (bool, error) { clusterDeployment, err := deploy.GetClusterDeployment(deploy.DefaultCheFlavor(deployContext.CheCluster), deployContext.CheCluster.Namespace, deployContext.ClusterAPI.Client) if err != nil { - return deploy.DeploymentProvisioningStatus{ - ProvisioningStatus: deploy.ProvisioningStatus{Err: err}, - } + return false, err } specDeployment, err := getSpecCheDeployment(deployContext) if err != nil { - return deploy.DeploymentProvisioningStatus{ - ProvisioningStatus: deploy.ProvisioningStatus{Err: err}, - } + return false, err } return deploy.SyncDeploymentToCluster(deployContext, specDeployment, clusterDeployment, nil, nil) diff --git a/pkg/deploy/sync.go b/pkg/deploy/sync.go index 7373b67ce..cdab3d19b 100644 --- a/pkg/deploy/sync.go +++ b/pkg/deploy/sync.go @@ -100,9 +100,7 @@ func update(deployContext *DeployContext, actual runtime.Object, blueprint metav } err = clusterAPI.Client.Create(context.TODO(), obj) - if err != nil { - return false, err - } + return false, err } else { obj, err := setOwnerReferenceAndConvertToRuntime(deployContext, blueprint) if err != nil { @@ -113,14 +111,10 @@ func update(deployContext *DeployContext, actual runtime.Object, blueprint metav obj.(metav1.Object).SetResourceVersion(actualMeta.GetResourceVersion()) err = clusterAPI.Client.Update(context.TODO(), obj) - if err != nil { - return false, err - } + return false, err } - - return true, nil } - return false, nil + return true, nil } func isUpdateUsingDeleteCreate(kind string) bool { diff --git a/pkg/util/k8s_helpers.go b/pkg/util/k8s_helpers.go index 4cc7f9362..7429979ff 100644 --- a/pkg/util/k8s_helpers.go +++ b/pkg/util/k8s_helpers.go @@ -16,6 +16,7 @@ import ( "fmt" "io" + v1 "github.com/eclipse/che-operator/pkg/apis/org/v1" "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -53,7 +54,26 @@ func GetK8Client() *k8s { return nil } -func (cl *k8s) ExecIntoPod(podName string, command string, reason string, namespace string) (string, error) { +func (cl *k8s) ExecIntoPod( + cr *v1.CheCluster, + deploymentName string, + getCommand func(*v1.CheCluster) (string, error), + reason string) (string, error) { + + command, err := getCommand(cr) + if err != nil { + return "", err + } + + pod, err := cl.GetDeploymentPod(deploymentName, cr.Namespace) + if err != nil { + return "", err + } + + return cl.DoExecIntoPod(cr.Namespace, pod, command, reason) +} + +func (cl *k8s) DoExecIntoPod(namespace string, podName string, command string, reason string) (string, error) { if reason != "" { logrus.Infof("Running exec for '%s' in the pod '%s'", reason, podName) } diff --git a/templates/create-github-identity-provider.sh b/templates/create-github-identity-provider.sh new file mode 100644 index 000000000..dcfe3ef95 --- /dev/null +++ b/templates/create-github-identity-provider.sh @@ -0,0 +1,41 @@ +# +# Copyright (c) 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 +# + +connectToKeycloak() { + {{ .Script }} config credentials --server http://0.0.0.0:8080/auth --realm master --user {{ .KeycloakAdminUserName }} --password {{ .KeycloakAdminPassword }} +} + +createIdentityProvider() { + {{ .Script }} get identity-provider/instances/{{ .ProviderId }} -r {{ .KeycloakRealm }} + if [ $? -eq 0 ]; then + echo "{{ .ProviderId }} identity provider exists." + else + echo "Create new {{ .ProviderId }} identity provider." + if [ -z "${GITHUB_CLIENT_ID}" ] || [ -z "${GITHUB_SECRET}" ]; then + echo "Either 'GITHUB_CLIENT_ID' or 'GITHUB_SECRET' aren't set" 1>&2 + exit 1 + fi + {{ .Script }} create identity-provider/instances \ + -r {{ .KeycloakRealm }} \ + -s alias={{ .ProviderId }} \ + -s providerId={{ .ProviderId }} \ + -s enabled=true \ + -s storeToken=true \ + -s config.useJwksUrl=true \ + -s config.clientId=${GITHUB_CLIENT_ID} \ + -s config.clientSecret=${GITHUB_SECRET} \ + -s config.defaultScope=repo,user,write:public_key + fi +} + +connectToKeycloak +createIdentityProvider diff --git a/templates/delete-identity-provider.sh b/templates/delete-identity-provider.sh new file mode 100644 index 000000000..2533de8bd --- /dev/null +++ b/templates/delete-identity-provider.sh @@ -0,0 +1,28 @@ +# +# Copyright (c) 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 +# + +connectToKeycloak() { + {{ .Script }} config credentials --server http://0.0.0.0:8080/auth --realm master --user {{ .KeycloakAdminUserName }} --password {{ .KeycloakAdminPassword }} +} + +deleteIdentityProvider() { + {{ .Script }} get identity-provider/instances/{{ .ProviderId }} -r {{ .KeycloakRealm }} + if [ ! $? -eq 0 ]; then + echo "{{ .ProviderId }} identity provider does not exists." + else + echo "Delete {{ .ProviderId }} identity provider." + {{ .Script }} delete identity-provider/instances/{{ .ProviderId }} -r {{ .KeycloakRealm }} + fi +} + +connectToKeycloak +deleteIdentityProvider diff --git a/templates/keycloak-provision.sh b/templates/keycloak-provision.sh new file mode 100644 index 000000000..52f93f204 --- /dev/null +++ b/templates/keycloak-provision.sh @@ -0,0 +1,74 @@ +# +# Copyright (c) 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 +# + +connectToKeycloak() { + {{ .Script }} config credentials --server http://0.0.0.0:8080/auth --realm master --user {{ .KeycloakAdminUserName }} --password {{ .KeycloakAdminPassword }} +} + +provisionKeycloak() { + {{ .Script }} update realms/master -s sslRequired=none + {{ .Script }} config truststore --trustpass ${SSO_TRUSTSTORE_PASSWORD} ${SSO_TRUSTSTORE_DIR}/${SSO_TRUSTSTORE} + + {{ .Script }} get realms/{{ .KeycloakRealm }} + if [ $? -eq 0 ]; then + echo "{{ .KeycloakRealm }} realm exists." + exit 0 + fi + + echo "Provision {{ .KeycloakRealm }} realm." + {{ .Script }} create realms \ + -s realm='{{ .KeycloakRealm }}' \ + -s displayName='{{ .RealmDisplayName }}' \ + -s enabled=true \ + -s sslRequired=none \ + -s registrationAllowed=false \ + -s resetPasswordAllowed=true \ + -s loginTheme={{ .KeycloakTheme }} \ + -s accountTheme={{ .KeycloakTheme }} \ + -s adminTheme={{ .KeycloakTheme }} \ + -s emailTheme={{ .KeycloakTheme }} + + {{ .Script }} create clients \ + -r '{{ .KeycloakRealm }}' \ + -s clientId={{ .KeycloakClientId }} \ + -s id={{ .KeycloakClientId }} \ + -s webOrigins='["http://{{ .CheHost }}", "https://{{ .CheHost }}"]' \ + -s redirectUris='["http://{{ .CheHost }}/dashboard/*", "https://{{ .CheHost }}/dashboard/*", "http://{{ .CheHost }}/workspace-loader/*", "https://{{ .CheHost }}/workspace-loader/*", "http://{{ .CheHost }}/_app/*", "https://{{ .CheHost }}/_app/*", "http://{{ .CheHost }}/swagger/*", "https://{{ .CheHost }}/swagger/*"]' \ + -s directAccessGrantsEnabled=true \ + -s publicClient=true + + {{ .Script }} create users \ + -r '{{ .KeycloakRealm }}' \ + -s username=admin \ + -s email=\"admin@admin.com\" \ + -s enabled=true \ + -s requiredActions='[{{ .RequiredActions }}]' + + {{ .Script }} set-password \ + -r '{{ .KeycloakRealm }}' \ + --username admin \ + --new-password admin + + {{ .Script }} add-roles \ + -r '{{ .KeycloakRealm }}' \ + --uusername admin \ + --cclientid broker \ + --rolename read-token + + CLIENT_ID=$({{ .Script }} get clients -r '{{ .KeycloakRealm }}' -q clientId=broker | sed -n 's/.*"id" *: *"\([^"]\+\).*/\1/p') + {{ .Script }} update clients/${CLIENT_ID} \ + -r '{{ .KeycloakRealm }}' \ + -s "defaultRoles+=read-token" +} + +connectToKeycloak +provisionKeycloak diff --git a/templates/keycloak_provision b/templates/keycloak_provision deleted file mode 100644 index 989981f48..000000000 --- a/templates/keycloak_provision +++ /dev/null @@ -1,37 +0,0 @@ -$script config credentials --server http://0.0.0.0:8080/auth \ - --realm master \ - --user $keycloakAdminUserName \ - --password $keycloakAdminPassword \ -&& $script update realms/master -s sslRequired=none \ -&& $script config truststore --trustpass ${SSO_TRUSTSTORE_PASSWORD} ${SSO_TRUSTSTORE_DIR}/${SSO_TRUSTSTORE} \ -&& $script get realms/$keycloakRealm; \ -if [ $? -eq 0 ]; then echo "Realm exists"; exit 0; fi \ -&& $script create realms -s realm='$keycloakRealm' \ - -s displayName='$realmDisplayName' \ - -s enabled=true \ - -s sslRequired=none \ - -s registrationAllowed=false \ - -s resetPasswordAllowed=true \ - -s loginTheme=$keycloakTheme \ - -s accountTheme=$keycloakTheme \ - -s adminTheme=$keycloakTheme \ - -s emailTheme=$keycloakTheme \ -&& $script create clients -r '$keycloakRealm' \ - -s clientId=$keycloakClientId \ - -s id=$keycloakClientId \ - -s 'webOrigins=["http://$cheHost", "https://$cheHost"]' \ - -s 'redirectUris=["http://$cheHost/dashboard/*", "https://$cheHost/dashboard/*", "http://$cheHost/workspace-loader/*", "https://$cheHost/workspace-loader/*", "http://$cheHost/_app/*", "https://$cheHost/_app/*", "http://$cheHost/swagger/*", "https://$cheHost/swagger/*"]' \ - -s 'directAccessGrantsEnabled'=true \ - -s publicClient=true \ -&& $script create users -s username=admin \ - -s email=\"admin@admin.com\" \ - -s enabled=true -r '$keycloakRealm' \ - -s 'requiredActions=[$requiredActions]' \ -&& $script set-password -r '$keycloakRealm' --username admin \ - --new-password admin \ -&& $script add-roles -r '$keycloakRealm' \ - --uusername admin \ - --cclientid broker \ - --rolename read-token \ -&& CLIENT_ID=$($script get clients -r '$keycloakRealm' -q clientId=broker | sed -n 's/.*"id" *: *"\([^"]\+\).*/\1/p') \ -&& $script update clients/$CLIENT_ID -r '$keycloakRealm' -s "defaultRoles+=read-token" diff --git a/templates/oauth_provision b/templates/oauth-provision.sh similarity index 94% rename from templates/oauth_provision rename to templates/oauth-provision.sh index c55c0b1ba..2dca983fb 100644 --- a/templates/oauth_provision +++ b/templates/oauth-provision.sh @@ -1,3 +1,15 @@ +# +# Copyright (c) 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 +# + connect_to_keycloak() { {{ .Script }} config credentials --server http://0.0.0.0:8080/auth --realm master --user {{ .KeycloakAdminUserName }} --password {{ .KeycloakAdminPassword }} {{ .Script }} config truststore --trustpass ${SSO_TRUSTSTORE_PASSWORD} ${SSO_TRUSTSTORE_DIR}/${SSO_TRUSTSTORE}