blob: e9b98a95a13b0e18ef37985068aa139c8cffdf24 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package controllers
import (
"context"
"fmt"
"reflect"
"time"
"github.com/apache/incubator-kie-kogito-serverless-operator/controllers/workflows"
kubeutil "github.com/apache/incubator-kie-kogito-serverless-operator/utils/kubernetes"
"k8s.io/klog/v2"
buildv1 "github.com/openshift/api/build/v1"
imgv1 "github.com/openshift/api/image/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/apache/incubator-kie-kogito-serverless-operator/utils"
operatorapi "github.com/apache/incubator-kie-kogito-serverless-operator/api/v1alpha08"
"github.com/apache/incubator-kie-kogito-serverless-operator/controllers/builder"
"github.com/apache/incubator-kie-kogito-serverless-operator/log"
)
// SonataFlowBuildReconciler reconciles a SonataFlowBuild object
type SonataFlowBuildReconciler struct {
client.Client
Scheme *runtime.Scheme
Recorder record.EventRecorder
Config *rest.Config
}
const (
requeueAfterForNewBuild = 10 * time.Second
requeueAfterForBuildRunning = 30 * time.Second
)
// +kubebuilder:rbac:groups=sonataflow.org,resources=sonataflowbuilds,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=sonataflow.org,resources=sonataflowbuilds/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=sonataflow.org,resources=sonataflowbuilds/finalizers,verbs=update
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// the SonataFlowBuild object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.1/pkg/reconcile
func (r *SonataFlowBuildReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
build := &operatorapi.SonataFlowBuild{}
err := r.Client.Get(ctx, req.NamespacedName, build)
if err != nil {
if errors.IsNotFound(err) {
return ctrl.Result{}, nil
}
klog.V(log.E).ErrorS(err, "Failed to get the SonataFlowBuild")
return ctrl.Result{}, err
}
phase := build.Status.BuildPhase
buildManager, err := builder.NewBuildManager(ctx, r.Client, r.Config, build.Name, build.Namespace)
if err != nil {
klog.V(log.E).ErrorS(err, "Failed to get create a build manager to handle the workflow build")
return ctrl.Result{}, err
}
if phase == operatorapi.BuildPhaseNone || kubeutil.GetAnnotationAsBool(build, operatorapi.BuildRestartAnnotation) {
return r.scheduleNewBuild(ctx, buildManager, build)
} else if phase != operatorapi.BuildPhaseSucceeded && phase != operatorapi.BuildPhaseError && phase != operatorapi.BuildPhaseFailed {
beforeReconcileStatus := build.Status.DeepCopy()
if err = buildManager.Reconcile(build); err != nil {
return ctrl.Result{}, err
}
if !reflect.DeepEqual(build.Status, beforeReconcileStatus) {
if err = r.manageStatusUpdate(ctx, build, beforeReconcileStatus.BuildPhase); err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{RequeueAfter: requeueAfterForBuildRunning}, nil
}
return ctrl.Result{}, nil
}
func (r *SonataFlowBuildReconciler) scheduleNewBuild(ctx context.Context, buildManager builder.BuildManager, build *operatorapi.SonataFlowBuild) (ctrl.Result, error) {
if err := buildManager.Schedule(build); err != nil {
return ctrl.Result{}, err
}
if err := r.manageStatusUpdate(ctx, build, ""); err != nil {
return ctrl.Result{}, err
}
if kubeutil.GetAnnotationAsBool(build, operatorapi.BuildRestartAnnotation) {
// Remove restart annotation to not enter in infinity reconciliation loop
kubeutil.SetAnnotation(build, operatorapi.BuildRestartAnnotation, "false")
if err := r.Update(ctx, build); err != nil {
return ctrl.Result{}, err
}
// Signals to the workflow that we are rebuilding
workflowManager, err := workflows.NewManager(r.Client, ctx, build.Namespace, build.Name)
if err != nil {
return ctrl.Result{}, err
}
if err := workflowManager.SetBuiltStatusToRunning("Build marked to restart"); err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{RequeueAfter: requeueAfterForNewBuild}, nil
}
func (r *SonataFlowBuildReconciler) manageStatusUpdate(ctx context.Context, instance *operatorapi.SonataFlowBuild, beforeReconcilePhase operatorapi.BuildPhase) error {
err := r.Status().Update(ctx, instance)
// Don't need to spam events if the phase hasn't changed
if err == nil && beforeReconcilePhase != instance.Status.BuildPhase {
r.Recorder.Event(instance, corev1.EventTypeNormal, "Updated", fmt.Sprintf("Updated buildphase to %s", instance.Status.BuildPhase))
}
return err
}
// SetupWithManager sets up the controller with the Manager.
func (r *SonataFlowBuildReconciler) SetupWithManager(mgr ctrl.Manager) error {
if utils.IsOpenShift() {
return ctrl.NewControllerManagedBy(mgr).
For(&operatorapi.SonataFlowBuild{}).
Owns(&buildv1.BuildConfig{}).
Owns(&imgv1.ImageStream{}).
Complete(r)
}
return ctrl.NewControllerManagedBy(mgr).
For(&operatorapi.SonataFlowBuild{}).
Complete(r)
}