diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 03632bae7..7efbe0bfc 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -108,7 +108,7 @@ func main() { os.Exit(1) } - logrus.Info("Registering Components") + logrus.Info("Registering Che Components Types") // Setup Scheme for all resources if err := apis.AddToScheme(mgr.GetScheme()); err != nil { diff --git a/pkg/controller/che/che_controller.go b/pkg/controller/che/che_controller.go index c38c044e4..7e2e6d38d 100644 --- a/pkg/controller/che/che_controller.go +++ b/pkg/controller/che/che_controller.go @@ -13,11 +13,13 @@ package che import ( "context" + "fmt" "time" orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" "github.com/eclipse/che-operator/pkg/deploy" "github.com/eclipse/che-operator/pkg/util" + consolev1 "github.com/openshift/api/console/v1" oauth "github.com/openshift/api/oauth/v1" routev1 "github.com/openshift/api/route/v1" userv1 "github.com/openshift/api/user/v1" @@ -63,9 +65,9 @@ func newReconciler(mgr manager.Manager) (reconcile.Reconciler, error) { return nil, err } return &ReconcileChe{ - client: mgr.GetClient(), + client: mgr.GetClient(), nonCachedClient: noncachedClient, - scheme: mgr.GetScheme(), + scheme: mgr.GetScheme(), }, nil } @@ -81,7 +83,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { if err != nil { return err } - // register OpenShift routes in the scheme + // register OpenShift specific types in the scheme if isOpenShift { if err := routev1.AddToScheme(mgr.GetScheme()); err != nil { logrus.Errorf("Failed to add OpenShift route to scheme: %s", err) @@ -92,6 +94,11 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { if err := userv1.AddToScheme(mgr.GetScheme()); err != nil { logrus.Errorf("Failed to add OpenShift User to scheme: %s", err) } + if hasConsolelinkObject() { + if err := consolev1.AddToScheme(mgr.GetScheme()); err != nil { + logrus.Errorf("Failed to add OpenShift ConsoleLink to scheme: %s", err) + } + } } // register RBAC in the scheme @@ -202,8 +209,8 @@ type ReconcileChe struct { // to simply read objects thta we don't intend // to further watch nonCachedClient client.Client - scheme *runtime.Scheme - tests bool + scheme *runtime.Scheme + tests bool } const ( @@ -237,6 +244,7 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e if err != nil { logrus.Errorf("An error occurred when detecting current infra: %s", err) } + if isOpenShift { // delete oAuthClient before CR is deleted doInstallOpenShiftoAuthProvider := instance.Spec.Auth.OpenShiftOauth @@ -303,7 +311,7 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e } } } - + if err := r.SetStatusDetails(instance, request, "", "", ""); err != nil { return reconcile.Result{}, err } @@ -499,6 +507,11 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e } } } + + if err := createConsoleLink(isOpenShift4, protocol, instance, r); err != nil { + return reconcile.Result{}, err + } + // create and provision Keycloak related objects ExternalKeycloak := instance.Spec.Auth.ExternalKeycloak @@ -895,7 +908,7 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e effectiveCheDeployment, err = r.GetEffectiveDeployment(instance, cheDeploymentToCreate.Name) } if effectiveCheDeployment.Status.AvailableReplicas == 1 && - instance.Status.CheClusterRunning != AvailableStatus { + instance.Status.CheClusterRunning != AvailableStatus { if err := r.SetCheAvailableStatus(instance, request, protocol, cheHost); err != nil { instance, _ = r.GetCR(request) return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err @@ -1042,3 +1055,67 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e } return reconcile.Result{}, nil } + +func createConsoleLink(isOpenShift4 bool, protocol string, instance *orgv1.CheCluster, r *ReconcileChe) error { + if !isOpenShift4 || !hasConsolelinkObject() { + logrus.Debug("Console link won't be created. It's not supported by cluster") + // console link is supported only on OpenShift >= 4.2 + return nil + } + + if protocol != "https" { + logrus.Debug("Console link won't be created. It's not supported when http connection is used") + // console link is supported only with https + return nil + } + + cheHost := instance.Spec.Server.CheHost + preparedConsoleLink := &consolev1.ConsoleLink{ + ObjectMeta: metav1.ObjectMeta{ + Name: deploy.DefaultConsoleLinkName, + }, + Spec: consolev1.ConsoleLinkSpec{ + Link: consolev1.Link{ + Href: protocol + "://" + cheHost, + Text: deploy.DefaultConsoleLinkDisplayName}, + Location: consolev1.ApplicationMenu, + ApplicationMenu: &consolev1.ApplicationMenuSpec{ + Section: deploy.DefaultConsoleLinkSection, + ImageURL: fmt.Sprintf("%s://%s%s", protocol, cheHost, deploy.DefaultConsoleLinkImage), + }, + }, + } + + existingConsoleLink := &consolev1.ConsoleLink{} + + if getErr := r.nonCachedClient.Get(context.TODO(), client.ObjectKey{Name: deploy.DefaultConsoleLinkName}, existingConsoleLink); getErr == nil { + // if found, update existing one. We need ResourceVersion from current one. + preparedConsoleLink.ResourceVersion = existingConsoleLink.ResourceVersion + logrus.Debugf("Updating the object: ConsoleLink, name: %s", existingConsoleLink.Name) + return r.nonCachedClient.Update(context.TODO(), preparedConsoleLink) + } else { + // if not found, create new one + if statusError, ok := getErr.(*errors.StatusError); ok && + statusError.Status().Reason == metav1.StatusReasonNotFound { + logrus.Infof("Creating a new object: ConsoleLink, name: %s", preparedConsoleLink.Name) + return r.nonCachedClient.Create(context.TODO(), preparedConsoleLink) + } else { + return getErr + } + } +} + +func hasConsolelinkObject() bool { + resourceList, err := util.GetServerResources() + if err != nil { + return false + } + for _, res := range resourceList { + for _, r := range res.APIResources { + if r.Name == "consolelinks" { + return true + } + } + } + return false +} diff --git a/pkg/controller/che/che_controller_test.go b/pkg/controller/che/che_controller_test.go index fd4c03961..3c3c9190e 100644 --- a/pkg/controller/che/che_controller_test.go +++ b/pkg/controller/che/che_controller_test.go @@ -13,6 +13,7 @@ package che import ( "context" + console "github.com/openshift/api/console/v1" "time" orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" @@ -104,6 +105,8 @@ func TestCheController(t *testing.T) { s.AddKnownTypes(oauth.SchemeGroupVersion, oAuthClient) s.AddKnownTypes(userv1.SchemeGroupVersion, users, user) + s.AddKnownTypes(console.GroupVersion, &console.ConsoleLink{}) + // Create a fake client to mock API calls cl := fake.NewFakeClient(objs...) tests := true diff --git a/pkg/deploy/defaults.go b/pkg/deploy/defaults.go index a6c3f89ca..cc7bc3e6b 100644 --- a/pkg/deploy/defaults.go +++ b/pkg/deploy/defaults.go @@ -66,6 +66,12 @@ const ( OldDefaultKeycloakUpstreamImageToDetect = "eclipse/che-keycloak:7.0.0" OldDefaultPvcJobsUpstreamImageToDetect = "registry.access.redhat.com/ubi8-minimal:8.0-127" OldDefaultPostgresUpstreamImageToDetect = "centos/postgresql-96-centos7:9.6" + + // ConsoleLink default + DefaultConsoleLinkName = "che" + DefaultConsoleLinkImage = "/dashboard/assets/branding/che-logo.svg" + DefaultConsoleLinkDisplayName = "Eclipse Che" + DefaultConsoleLinkSection = "Red Hat Applications" ) func DefaultCheServerImageTag(cheFlavor string) string { diff --git a/pkg/util/util.go b/pkg/util/util.go index 71fbc08d3..9ef129138 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -12,11 +12,12 @@ package util import ( - "errors" "crypto/tls" "encoding/json" + "errors" "github.com/sirupsen/logrus" "io/ioutil" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/discovery" "math/rand" "net/http" @@ -63,24 +64,15 @@ func GeneratePasswd(stringLength int) (passwd string) { func DetectOpenShift() (isOpenshift bool, isOpenshift4 bool, anError error) { tests := IsTestMode() if !tests { - kubeconfig, err := config.GetConfig() - if err != nil { + apiGroups, err := getApiList() + if err != nil{ return false, false, err } - discoveryClient, err := discovery.NewDiscoveryClientForConfig(kubeconfig) - if err != nil { - return false, false, err - } - apiList, err := discoveryClient.ServerGroups() - if err != nil { - return false, false, err - } - apiGroups := apiList.Groups - for i := 0; i < len(apiGroups); i++ { - if apiGroups[i].Name == "route.openshift.io" { + for _, apiGroup := range apiGroups { + if apiGroup.Name == "route.openshift.io" { isOpenshift = true } - if apiGroups[i].Name == "config.openshift.io" { + if apiGroup.Name == "config.openshift.io" { isOpenshift4 = true } } @@ -89,6 +81,34 @@ func DetectOpenShift() (isOpenshift bool, isOpenshift4 bool, anError error) { return true, false, nil } +func getDiscoveryClient() (*discovery.DiscoveryClient, error) { + kubeconfig, err := config.GetConfig() + if err != nil { + return nil, err + } + return discovery.NewDiscoveryClientForConfig(kubeconfig) +} + +func getApiList() ([]v1.APIGroup, error) { + discoveryClient, err := getDiscoveryClient() + if err != nil { + return nil, err + } + apiList, err := discoveryClient.ServerGroups() + if err != nil { + return nil, err + } + return apiList.Groups, nil +} + +func GetServerResources() ([]*v1.APIResourceList, error) { + discoveryClient, err := getDiscoveryClient() + if err != nil { + return nil, err + } + return discoveryClient.ServerResources() +} + func GetValue(key string, defaultValue string) (value string) { value = key