Skip to content

Commit

Permalink
Merge pull request #1 from eguzki/eguzki-refactoring-consolelink
Browse files Browse the repository at this point in the history
refactor webconsole controller
  • Loading branch information
myeung18 committed Oct 1, 2020
2 parents a77c4eb + 5a74ed8 commit c8f4fde
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 114 deletions.
68 changes: 61 additions & 7 deletions pkg/controller/webconsole/webconsole_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ package webconsole

import (
"context"
"strings"

"github.com/3scale/3scale-operator/pkg/common"
"github.com/3scale/3scale-operator/pkg/helper"
routev1 "github.com/openshift/api/route/v1"
"time"
"github.com/3scale/3scale-operator/version"

"github.com/3scale/3scale-operator/pkg/reconcilers"
"github.com/go-logr/logr"
consolev1 "github.com/openshift/api/console/v1"
routev1 "github.com/openshift/api/route/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
Expand Down Expand Up @@ -82,13 +88,61 @@ type ReconcileWebConsole struct {
//Reconcile reads the state of the Routes and makes changes to the corresponding Consolelinks
func (r *ReconcileWebConsole) Reconcile(request reconcile.Request) (reconcile.Result, error) {
logger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
logger.Info("Reconciling ReconcileWebConsole")
logger.Info("Reconciling ReconcileWebConsole", "Operator version", version.Version)

if err := helper.ConsoleLinkSupported(); err == nil {
if err = helper.ReconcileConsoleLink(r.Context(), r.Client(), &request); err != nil {
return reconcile.Result{RequeueAfter: time.Duration(200) * time.Microsecond}, nil
}
kindExists, err := r.HasConsoleLink()
if err != nil {
return reconcile.Result{}, err
}
if !kindExists {
logger.Info("Console link not supported in the cluster")
return reconcile.Result{}, nil
}

result, err := r.reconcileMasterLink(request, logger)
if err != nil {
return result, err
}
if result.Requeue {
logger.Info("Master link reconciled. Needs Requeueing.")
return result, nil
}

logger.V(1).Info("END")
return reconcile.Result{}, nil
}

func (r *ReconcileWebConsole) reconcileMasterLink(request reconcile.Request, logger logr.Logger) (reconcile.Result, error) {
if !strings.Contains(request.Name, "zync-3scale-master") {
// Nothing to do
return reconcile.Result{}, nil
}

route := &routev1.Route{}
err := r.Client().Get(r.Context(), request.NamespacedName, route)
if err != nil && !errors.IsNotFound(err) {
// Error reading the object - requeue the request.
return reconcile.Result{}, err
}

if errors.IsNotFound(err) {
logger.V(1).Info("Master route not found", "name", request.Name)
// cluster-scoped resource must not have a namespace-scoped owner
// So consolelinks cannot have owners like apimanager or route object
// delete consolelink if exists
desired := &consolev1.ConsoleLink{
ObjectMeta: metav1.ObjectMeta{
Name: helper.GetMasterConsoleLinkName(request.Namespace),
},
}
common.TagObjectToDelete(desired)
err := r.ReconcileResource(&consolev1.ConsoleLink{}, desired, reconcilers.CreateOnlyMutator)
return reconcile.Result{}, err
}

logger.V(1).Info("Master route found", "name", request.Name)

err = r.ReconcileResource(&consolev1.ConsoleLink{}, helper.GetMasterConsoleLink(route), helper.GenericConsoleLinkMutator)
logger.V(1).Info("Reconcile master consolelink", "err", err)
return reconcile.Result{}, err
}
139 changes: 32 additions & 107 deletions pkg/helper/webconsole.go
Original file line number Diff line number Diff line change
@@ -1,133 +1,53 @@
package helper

import (
"context"
"fmt"
"github.com/RHsyseng/operator-utils/pkg/logs"
"github.com/RHsyseng/operator-utils/pkg/resource/read"
"github.com/RHsyseng/operator-utils/pkg/utils/kubernetes"

"github.com/3scale/3scale-operator/pkg/common"
consolev1 "github.com/openshift/api/console/v1"
routev1 "github.com/openshift/api/route/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"strings"
)

var logger = logs.GetLogger("openshift-webconsole")

// ConsoleLinkText is the text of the consoleLink shown on the webconsole
const ConsoleLinkText = "APIManager - 3scale Master Portal"
const ConsoleLinkText = "APIManager - 3scale"

// ConsoleLinkNamePrefix is the prefix applied to Console link for system master
const ConsoleLinkNamePrefix = "system-master-link"

// ConsoleLinkSupported checks if a ConsoleLink CRD exists
func ConsoleLinkSupported() error {
gvk := schema.GroupVersionKind{Group: "console.openshift.io", Version: "v1", Kind: "ConsoleLink"}
return kubernetes.CustomResourceDefinitionExists(gvk)
}
// ConsoleLinkMasterNamePrefix is the prefix applied to Console link for system master
const ConsoleLinkMasterNamePrefix = "system-master-link"

// ReconcileConsoleLink creates/deletes/updates a ConsoleLink based on the Route
func ReconcileConsoleLink(ctx context.Context, c client.Client, req *reconcile.Request) error {
route := getRoute(ctx, c, req)
if route == nil {
if err := removeConsoleLink(ctx, c, req); err != nil {
return err
}
} else {
if err := reconcileConsoleLink(ctx, c, req, route); err != nil {
return err
}
//GenericConsoleLinkMutator performs the reconciliation for consolelink objects
func GenericConsoleLinkMutator(existingObj, desiredObj common.KubernetesObject) (bool, error) {
existing, ok := existingObj.(*consolev1.ConsoleLink)
if !ok {
return false, fmt.Errorf("%T is not a *consolev1.ConsoleLink", existingObj)
}
return nil
}

// getRoute retrieves all the routes in the current namespace and return the desired one.
func getRoute(ctx context.Context, c client.Client, req *reconcile.Request) *routev1.Route {
reader := read.New(c).WithNamespace(req.Namespace)
deployedRoutes, err := reader.List(&routev1.RouteList{})
if err == nil {
for _, route := range deployedRoutes { //capture targeted route object
realr := route.(*routev1.Route)
if strings.Compare(realr.Spec.To.Name, "system-master") == 0 {
return realr
}
}
desired, ok := desiredObj.(*consolev1.ConsoleLink)
if !ok {
return false, fmt.Errorf("%T is not a *consolev1.ConsoleLink", desiredObj)
}
return nil
}

// reconcileConsoleLink create a ConsoleLink for the Route, or update the existing ConsoleLink if it exists.
// return error if update or creation could not be done
func reconcileConsoleLink(ctx context.Context, c client.Client, req *reconcile.Request, route *routev1.Route) error {
consoleLinkName := fmt.Sprintf("%s-%s", ConsoleLinkNamePrefix, req.Namespace)
consoleLink := &consolev1.ConsoleLink{}
err := c.Get(ctx, types.NamespacedName{Name: consoleLinkName}, consoleLink)
if err != nil && apierrors.IsNotFound(err) {
consoleLink = getConsoleLink(consoleLinkName, route, req)
if err := c.Create(ctx, consoleLink); err != nil {
err = fmt.Errorf("failed to create consolelink %s %w", consoleLinkName, err)
logger.Error(err)
return err
}
logger.Info("consolelink has been created:", consoleLinkName)
} else if err == nil && consoleLink != nil {
if err = updateConsoleLink(ctx, route, consoleLink, c); err != nil {
return err
}
} else {
err = fmt.Errorf("failed to retrieve consolelink %s %w", consoleLinkName, err)
logger.Error(err)
return err
}
return nil
}
update := false

// removeConsoleLink removes a ConsoleLink object if it exists
// error is returned if the deletion is failed.
func removeConsoleLink(ctx context.Context, c client.Client, req *reconcile.Request) error {
consoleLink := &consolev1.ConsoleLink{}
consoleLinkName := fmt.Sprintf("%s-%s", ConsoleLinkNamePrefix, req.Namespace)
err := c.Get(ctx, types.NamespacedName{Name: consoleLinkName}, consoleLink)
if err == nil && consoleLink != nil {
err = c.Delete(ctx, consoleLink)
if err != nil {
err = fmt.Errorf("failed to delete the consolelink %s %w", consoleLinkName, err)
logger.Error(err)
return err
}
logger.Info("deleted the consolelink:", consoleLinkName)
if existing.Spec.Href != desired.Spec.Href {
existing.Spec.Href = desired.Spec.Href
update = true
}
return nil
}

// updateConsoleLink update the ConsoleLink with properties from a Route
// error is returned if update is failed
func updateConsoleLink(ctx context.Context, route *routev1.Route, consoleLink *consolev1.ConsoleLink, c client.Client) error {
url := "https://" + route.Spec.Host
linkTxt := ConsoleLinkText
if url != consoleLink.Spec.Href || linkTxt != consoleLink.Spec.Text {
consoleLink.Spec.Href = url
consoleLink.Spec.Text = linkTxt
if err := c.Update(ctx, consoleLink); err != nil {
err = fmt.Errorf("failed to update consolelink %s %w", consoleLink, err)
logger.Error(err)
return err
}
if existing.Spec.Text != desired.Spec.Text {
existing.Spec.Text = desired.Spec.Text
update = true
}
return nil

return update, nil
}

func getConsoleLink(consoleLinkName string, route *routev1.Route, req *reconcile.Request) *consolev1.ConsoleLink {
//GetMasterConsoleLink creates the consolelink obj for a target
func GetMasterConsoleLink(route *routev1.Route) *consolev1.ConsoleLink {
return &consolev1.ConsoleLink{
ObjectMeta: metav1.ObjectMeta{
Name: consoleLinkName,
Name: GetMasterConsoleLinkName(route.Namespace),
Labels: map[string]string{
"3scale.net/route-name": req.Name,
"3scale.net/route-name": route.Name,
},
},
Spec: consolev1.ConsoleLinkSpec{
Expand All @@ -137,8 +57,13 @@ func getConsoleLink(consoleLinkName string, route *routev1.Route, req *reconcile
},
Location: consolev1.NamespaceDashboard,
NamespaceDashboard: &consolev1.NamespaceDashboardSpec{
Namespaces: []string{req.Namespace},
Namespaces: []string{route.Namespace},
},
},
}
}

//GetMasterConsoleLinkName returns the consolelink name
func GetMasterConsoleLinkName(namespace string) string {
return fmt.Sprintf("%s-%s", ConsoleLinkMasterNamePrefix, namespace)
}
8 changes: 8 additions & 0 deletions pkg/reconcilers/base_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/3scale/3scale-operator/pkg/common"

"github.com/go-logr/logr"
consolev1 "github.com/openshift/api/console/v1"
"github.com/operator-framework/operator-sdk/pkg/k8sutil"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
Expand Down Expand Up @@ -164,3 +166,9 @@ func (b *BaseReconciler) UpdateResourceStatus(obj common.KubernetesObject) error
b.Logger().Info(fmt.Sprintf("Updated status of object '%s/%s'", strings.Replace(fmt.Sprintf("%T", obj), "*", "", 1), obj.GetName()))
return b.Client().Status().Update(context.TODO(), obj)
}

//HasConsoleLink checks if the ConsoleLink is supported in current cluster
func (b *BaseReconciler) HasConsoleLink() (bool, error) {
return k8sutil.ResourceExists(b.DiscoveryClient(),
consolev1.GroupVersion.String(), "ConsoleLink")
}

0 comments on commit c8f4fde

Please sign in to comment.