Skip to content
This repository was archived by the owner on May 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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