Skip to content

Commit

Permalink
feat: apply kstatus feature from ocm-controller (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
Skarlso authored Nov 14, 2023
1 parent c8c050b commit 7a9bcfa
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 160 deletions.
13 changes: 13 additions & 0 deletions apis/delivery/v1alpha1/sync_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package v1alpha1

import (
"fmt"
"time"

"github.com/fluxcd/pkg/apis/meta"
Expand Down Expand Up @@ -63,6 +64,18 @@ type SyncStatus struct {
PullRequestID int `json:"pullRequestID,omitempty"`
}

func (in *Sync) GetVID() map[string]string {
vid := fmt.Sprintf("%d:%s", in.Status.PullRequestID, in.Status.Digest)
metadata := make(map[string]string)
metadata[GroupVersion.Group+"/sync"] = vid

return metadata
}

func (in *Sync) SetObservedGeneration(v int64) {
in.Status.ObservedGeneration = v
}

// GetConditions returns the conditions of the ComponentVersion.
func (in *Sync) GetConditions() []metav1.Condition {
return in.Status.Conditions
Expand Down
11 changes: 11 additions & 0 deletions apis/mpas/v1alpha1/repository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,17 @@ func (in Repository) GetRepositoryURL() string {
return fmt.Sprintf("https://%s/%s/%s", domain, in.Spec.Owner, in.GetName())
}

func (in *Repository) GetVID() map[string]string {
metadata := make(map[string]string)
metadata[GroupVersion.Group+"/repository"] = fmt.Sprintf("%s/%s", in.Spec.Provider, in.Name)

return metadata
}

func (in *Repository) SetObservedGeneration(v int64) {
in.Status.ObservedGeneration = v
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status

Expand Down
65 changes: 28 additions & 37 deletions controllers/delivery/sync_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/fluxcd/pkg/runtime/patch"
rreconcile "github.com/fluxcd/pkg/runtime/reconcile"
"github.com/open-component-model/ocm-controller/pkg/status"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -22,7 +24,6 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"

ocmv1 "github.com/open-component-model/ocm-controller/api/v1alpha1"
Expand Down Expand Up @@ -56,8 +57,6 @@ type SyncReconciler struct {
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
func (r *SyncReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, err error) {
log := log.FromContext(ctx)

obj := &v1alpha1.Sync{}
if err = r.Get(ctx, req.NamespacedName, obj); err != nil {
if apierrors.IsNotFound(err) {
Expand All @@ -66,7 +65,6 @@ func (r *SyncReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr

return ctrl.Result{}, fmt.Errorf("failed to get git sync object: %w", err)
}
log.V(4).Info("found reconciling object", "sync", obj)

// The replication controller doesn't need a shouldReconcile, because it should always reconcile,
// that is its purpose.
Expand All @@ -79,39 +77,46 @@ func (r *SyncReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr
return
}

// Set status observed generation option if the component is stalled or ready.
if conditions.IsReady(obj) {
obj.Status.ObservedGeneration = obj.Generation
}

// Update the object.
if perr := patchHelper.Patch(ctx, obj); perr != nil {
err = errors.Join(err, perr)
if derr := status.UpdateStatus(ctx, patchHelper, obj, r.EventRecorder, obj.GetRequeueAfter()); derr != nil {
err = errors.Join(err, derr)
}
}()

// Starts the progression by setting ReconcilingCondition.
// This will be checked in defer.
// Should only be deleted on a success.
rreconcile.ProgressiveStatus(false, obj, meta.ProgressingReason, "reconciliation in progress for resource: %s", obj.Name)

// it's important that this happens here so any residual status condition can be overwritten / set.
if obj.Status.Digest != "" {
log.Info("Sync object already synced; status contains digest information", "digest", obj.Status.Digest)
event.New(r.EventRecorder, obj, eventv1.EventSeverityInfo, fmt.Sprintf("sync object already synced with digest %s", obj.Status.Digest), nil)
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "Reconciliation success")

return ctrl.Result{}, nil
}

if obj.Generation != obj.Status.ObservedGeneration {
rreconcile.ProgressiveStatus(
false,
obj,
meta.ProgressingReason,
"processing object: new generation %d -> %d",
obj.Status.ObservedGeneration,
obj.Generation,
)
}

snapshot := &ocmv1.Snapshot{}
if err = r.Get(ctx, types.NamespacedName{
Namespace: obj.Namespace,
Name: obj.Spec.SnapshotRef.Name,
}, snapshot); err != nil {
err = fmt.Errorf("failed to find snapshot: %w", err)
r.markAndEmitEvent(obj, v1alpha1.SnapshotGetFailedReason, err)
status.MarkNotReady(r.EventRecorder, obj, v1alpha1.SnapshotGetFailedReason, err.Error())

return ctrl.Result{}, err
}

log.V(4).Info("found target snapshot")

namespace := obj.Spec.RepositoryRef.Namespace
if namespace == "" {
namespace = obj.Namespace
Expand All @@ -123,26 +128,22 @@ func (r *SyncReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr
Name: obj.Spec.RepositoryRef.Name,
}, repository); err != nil {
err = fmt.Errorf("failed to find repository: %w", err)
r.markAndEmitEvent(obj, v1alpha1.RepositoryGetFailedReason, err)
status.MarkNotReady(r.EventRecorder, obj, v1alpha1.RepositoryGetFailedReason, err.Error())

return ctrl.Result{}, err
}

log.V(4).Info("found target repository")

authSecret := &corev1.Secret{}
if err = r.Get(ctx, types.NamespacedName{
Namespace: repository.Namespace,
Name: repository.Spec.Credentials.SecretRef.Name,
}, authSecret); err != nil {
err = fmt.Errorf("failed to find authentication secret: %w", err)
r.markAndEmitEvent(obj, v1alpha1.CredentialsNotFoundReason, err)
status.MarkNotReady(r.EventRecorder, obj, v1alpha1.CredentialsNotFoundReason, err.Error())

return ctrl.Result{}, err
}

log.V(4).Info("found authentication secret")

baseBranch := obj.Spec.CommitTemplate.BaseBranch
if baseBranch == "" {
baseBranch = "main"
Expand All @@ -153,15 +154,13 @@ func (r *SyncReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr
targetBranch = fmt.Sprintf("branch-%d", time.Now().Unix())
} else if targetBranch == "" && !obj.Spec.AutomaticPullRequestCreation {
err = fmt.Errorf("branch cannot be empty if automatic pull request creation is not enabled")
r.markAndEmitEvent(obj, v1alpha1.GitRepositoryPushFailedReason, err)
status.MarkNotReady(r.EventRecorder, obj, v1alpha1.GitRepositoryPushFailedReason, err.Error())

return ctrl.Result{}, err
}

log.Info("preparing to push snapshot content", "base", baseBranch, "target", targetBranch)
rreconcile.ProgressiveStatus(false, obj, meta.ProgressingReason, "preparing to push snapshot content with base branch %s and target %s", baseBranch, targetBranch)

// trim any trailing `/` and then just add.
log.V(4).Info("crafting artifact URL to download from", "url", snapshot.Status.RepositoryURL)
opts := &pkg.PushOptions{
URL: repository.GetRepositoryURL(),
Message: obj.Spec.CommitTemplate.Message,
Expand All @@ -179,41 +178,33 @@ func (r *SyncReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctr
digest, err = r.Git.Push(ctx, opts)
if err != nil {
err = fmt.Errorf("failed to push to git repository: %w", err)
r.markAndEmitEvent(obj, v1alpha1.GitRepositoryPushFailedReason, err)
status.MarkNotReady(r.EventRecorder, obj, v1alpha1.GitRepositoryPushFailedReason, err.Error())

return ctrl.Result{}, err
}

log.Info("target content pushed with digest", "base", baseBranch, "target", targetBranch, "digest", digest)

obj.Status.Digest = digest

if obj.Spec.AutomaticPullRequestCreation {
log.Info("automatic pull-request creation is enabled, preparing to create a pull request")
rreconcile.ProgressiveStatus(false, obj, meta.ProgressingReason, "creating pull request")

id, err := r.Provider.CreatePullRequest(ctx, targetBranch, *obj, *repository)
if err != nil {
err = fmt.Errorf("failed to create pull request: %w", err)
r.markAndEmitEvent(obj, v1alpha1.CreatePullRequestFailedReason, err)
status.MarkNotReady(r.EventRecorder, obj, v1alpha1.CreatePullRequestFailedReason, err.Error())

return ctrl.Result{}, err
}

obj.Status.PullRequestID = id
}

log.Info("successfully reconciled sync object")
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "Reconciliation success")
event.New(r.EventRecorder, obj, eventv1.EventSeverityInfo, "Reconciliation success", nil)

return ctrl.Result{}, nil
}

func (r *SyncReconciler) markAndEmitEvent(obj *v1alpha1.Sync, reason string, err error) {
event.New(r.EventRecorder, obj, eventv1.EventSeverityError, err.Error(), nil)
conditions.MarkFalse(obj, meta.ReadyCondition, reason, err.Error())
}

// SetupWithManager sets up the controller with the Manager.
func (r *SyncReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Expand Down
63 changes: 37 additions & 26 deletions controllers/mpas/repository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ import (
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/fluxcd/pkg/runtime/patch"
rreconcile "github.com/fluxcd/pkg/runtime/reconcile"
"github.com/open-component-model/ocm-controller/pkg/status"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
kuberecorder "k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"

mpasv1alpha1 "github.com/open-component-model/git-controller/apis/mpas/v1alpha1"
Expand All @@ -42,9 +43,6 @@ type RepositoryReconciler struct {
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
logger := log.FromContext(ctx).WithName("repository")
logger.V(4).Info("entering repository loop...")

obj := &mpasv1alpha1.Repository{}

if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
Expand All @@ -64,17 +62,16 @@ func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return
}

// Set status observed generation option if the component is stalled or ready.
if conditions.IsReady(obj) {
obj.Status.ObservedGeneration = obj.Generation
}

// Update the object.
if perr := patchHelper.Patch(ctx, obj); perr != nil {
err = errors.Join(err, perr)
if derr := status.UpdateStatus(ctx, patchHelper, obj, r.EventRecorder, obj.GetRequeueAfter()); derr != nil {
err = errors.Join(err, derr)
}
}()

// Starts the progression by setting ReconcilingCondition.
// This will be checked in defer.
// Should only be deleted on a success.
rreconcile.ProgressiveStatus(false, obj, meta.ProgressingReason, "reconciliation in progress for resource: %s", obj.Name)

return r.reconcile(ctx, obj)
}

Expand All @@ -86,34 +83,48 @@ func (r *RepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error {
}

func (r *RepositoryReconciler) reconcile(ctx context.Context, obj *mpasv1alpha1.Repository) (ctrl.Result, error) {
logger := log.FromContext(ctx)
if obj.Generation != obj.Status.ObservedGeneration {
rreconcile.ProgressiveStatus(
false,
obj,
meta.ProgressingReason,
"processing object: new generation %d -> %d",
obj.Status.ObservedGeneration,
obj.Generation,
)
}

rreconcile.ProgressiveStatus(false, obj, meta.ProgressingReason, "creating repository: %s", obj.Name)

logger.Info("creating or adopting repository")
if err := r.Provider.CreateRepository(ctx, *obj); err != nil {
event.New(r.EventRecorder, obj, eventv1.EventSeverityError, err.Error(), nil)
conditions.MarkFalse(obj, meta.ReadyCondition, mpasv1alpha1.RepositoryCreateFailedReason, err.Error())
err := fmt.Errorf("failed to create repository: %w", err)
status.MarkNotReady(r.EventRecorder, obj, mpasv1alpha1.RepositoryCreateFailedReason, err.Error())

return ctrl.Result{}, fmt.Errorf("failed to create repository: %w", err)
return ctrl.Result{}, err
}

logger.Info("updating branch protection rules")
rreconcile.ProgressiveStatus(false, obj, meta.ProgressingReason, "setting up branch protection rules: %s", obj.Name)

if err := r.Provider.CreateBranchProtection(ctx, *obj); err != nil {
if errors.Is(err, providers.NotSupportedError) {
// ignore and return without branch protection rules.
logger.Error(err, fmt.Sprintf("provider %s does not support updating branch protection rules", obj.Spec.Provider))
r.markAsDone(obj)

// ignore and return without branch protection rules.
return ctrl.Result{}, nil
}

conditions.MarkFalse(obj, meta.ReadyCondition, mpasv1alpha1.UpdatingBranchProtectionFailedReason, err.Error())
event.New(r.EventRecorder, obj, eventv1.EventSeverityError, err.Error(), nil)
err := fmt.Errorf("failed to update branch protection rules: %w", err)
status.MarkNotReady(r.EventRecorder, obj, mpasv1alpha1.UpdatingBranchProtectionFailedReason, err.Error())

return ctrl.Result{}, fmt.Errorf("failed to update branch protection rules: %w", err)
return ctrl.Result{}, err
}

logger.Info("done reconciling repository")
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "Reconciliation success")
event.New(r.EventRecorder, obj, eventv1.EventSeverityInfo, "Reconciliation success", nil)
r.markAsDone(obj)

return ctrl.Result{}, nil
}

func (r *RepositoryReconciler) markAsDone(obj *mpasv1alpha1.Repository) {
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "Reconciliation success")
event.New(r.EventRecorder, obj, eventv1.EventSeverityInfo, "Reconciliation success", nil)
}
Loading

0 comments on commit 7a9bcfa

Please sign in to comment.