Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
frewilhelm committed Feb 5, 2025
1 parent 919040c commit 45ce876
Show file tree
Hide file tree
Showing 15 changed files with 438 additions and 343 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ $(ENVTEST): $(LOCALBIN)

.PHONY: zot-registry
zot-registry: $(LOCALBIN) # Download zot registry binary locally if necessary.
@wget "https://github.com/project-zot/zot/releases/download/$(ZOT_VERSION)/zot-$(OS)-$(ARCH)-minimal" \
wget "https://github.com/project-zot/zot/releases/download/$(ZOT_VERSION)/zot-$(OS)-$(ARCH)-minimal" \
-O $(LOCALBIN)/zot-registry \
&& chmod u+x $(LOCALBIN)/zot-registry

Expand Down
36 changes: 36 additions & 0 deletions api/v1alpha1/condition_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,54 @@ const (
// ReconcileArtifactFailedReason is used when we fail in creating an Artifact.
ReconcileArtifactFailedReason = "ReconcileArtifactFailed"

// MarshalFailedReason is used when we fail to marshal a struct.
MarshalFailedReason = "MarshalFailed"

// CreateOCIRepositoryNameFailedReason is used when we fail to create an OCI repository name.
CreateOCIRepositoryNameFailedReason = "CreateOCIRepositoryNameFailed"

// CreateOCIRepositoryFailedReason is used when we fail to create a OCI repository.
CreateOCIRepositoryFailedReason = "CreateOCIRepositoryFailed"

// CreateSnapshotFailedReason is used when we fail to create a snapshot.
CreateSnapshotFailedReason = "CreateSnapshotFailed"

// GetArtifactFailedReason is used when we fail in getting an Artifact.
GetArtifactFailedReason = "GetArtifactFailed"

// GetSnapshotFailedReason is used when we fail in getting a Snapshot.
GetSnapshotFailedReason = "GetSnapshotFailed"

// ResolveResourceFailedReason is used when we fail in resolving a resource.
ResolveResourceFailedReason = "ResolveResourceFailed"

// GetResourceAccessFailedReason is used when we fail in getting a resource access(es).
GetResourceAccessFailedReason = "GetResourceAccessFailed"

// GetBlobAccessFailedReason is used when we fail to get a blob access.
GetBlobAccessFailedReason = "GetBlobAccessFailed"

// VerifyResourceFailedReason is used when we fail to verify a resource.
VerifyResourceFailedReason = "VerifyResourceFailed"

// GetResourceFailedReason is used when we fail to get the resource.
GetResourceFailedReason = "GetResourceFailed"

// PushSnapshotFailedReason is used when we fail to push a snapshot.
PushSnapshotFailedReason = "PushSnapshotFailed"

// FetchSnapshotFailedReason is used when we fail to fetch a snapshot.
FetchSnapshotFailedReason = "FetchSnapshotFailed"

// DeleteSnapshotFailedReason is used when we fail to delete a snapshot.
DeleteSnapshotFailedReason = "DeleteSnapshotFailed"

// GetComponentForArtifactFailedReason is used when we fail in getting a component for an artifact.
GetComponentForArtifactFailedReason = "GetComponentForArtifactFailed"

// GetComponentForSnapshotFailedReason is used when we fail in getting a component for a snapshot.
GetComponentForSnapshotFailedReason = "GetComponentForSnapshotFailed"

// StatusSetFailedReason is used when we fail to set the component status.
StatusSetFailedReason = "StatusSetFailed"

Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const (

// Finalizers for controllers.
const (
// TODO: Remove ArtifactFinalizer
// TODO: Remove ArtifactFinalizer.

// ArtifactFinalizer is the finalizer that is added to artifacts created by the ocm controllers.
ArtifactFinalizer = "finalizers.ocm.software/artifact"
Expand Down
3 changes: 2 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@ func main() {
Scheme: mgr.GetScheme(),
EventRecorder: eventsRecorder,
},
Storage: storage,
Registry: registry,
Storage: storage,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Resource")
os.Exit(1)
Expand Down
44 changes: 23 additions & 21 deletions internal/controller/component/component_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"fmt"

"github.com/Masterminds/semver/v3"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/fluxcd/pkg/runtime/patch"
"github.com/mandelsoft/goutils/sliceutils"
Expand Down Expand Up @@ -59,22 +58,24 @@ var _ ocm.Reconciler = (*Reconciler)(nil)
// SetupWithManager sets up the controller with the Manager.
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
// TODO: Check if we should watch for the snapshots that are created by this controller
For(&v1alpha1.Component{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
Complete(r)
}

// +kubebuilder:rbac:groups=delivery.ocm.software,resources=components,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=delivery.ocm.software,resources=components/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=delivery.ocm.software,resources=components/finalizers,verbs=update

// +kubebuilder:rbac:groups=openfluxcd.ocm.software,resources=artifacts,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=openfluxcd.ocm.software,resources=artifacts/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=openfluxcd.ocm.software,resources=artifacts/finalizers,verbs=update
// +kubebuilder:rbac:groups=delivery.ocm.software,resources=components/finalizers,verbs=updat

// +kubebuilder:rbac:groups="",resources=secrets;configmaps;serviceaccounts,verbs=get;list;watch
// +kubebuilder:rbac:groups="",resources=serviceaccounts/token,verbs=create
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch

// TODO: Remove
// +kubebuilder:rbac:groups=openfluxcd.ocm.software,resources=artifacts,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=openfluxcd.ocm.software,resources=artifacts/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=openfluxcd.ocm.software,resources=artifacts/finalizers,verbs=update

// Reconcile the component object.
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, retErr error) {
component := &v1alpha1.Component{}
Expand Down Expand Up @@ -141,8 +142,8 @@ func (r *Reconciler) reconcile(ctx context.Context, component *v1alpha1.Componen
// not ready as well.
// However, as the component is hard-dependant on the ocmrepository, we decided to mark it not ready as well.
if !conditions.IsReady(repo) {
conditions.Delete(component, meta.ReconcilingCondition)
conditions.MarkFalse(component, meta.ReadyCondition, v1alpha1.RepositoryIsNotReadyReason, "repository is not ready")
logger.Info("repository is not ready", "name", component.Spec.RepositoryRef.Name)
status.MarkNotReady(r.EventRecorder, component, v1alpha1.RepositoryIsNotReadyReason, "repository is not ready yet")

return ctrl.Result{Requeue: true}, nil
}
Expand Down Expand Up @@ -246,36 +247,37 @@ func (r *Reconciler) reconcileComponent(ctx context.Context, octx ocmctx.Context
// TODO: Can I check beforehand if the CD is already downloaded and in the OCI Registry (cached)?
// Compare digest/hash from manifest of the CD from the source storage

logger.Info("pushing descriptors to storage")
ociRepositoryName, err := snapshot.CreateRepositoryName(component.Spec.RepositoryRef.Name, component.GetName())
if err != nil {
status.MarkNotReady(r.EventRecorder, component, v1alpha1.ReconcileArtifactFailedReason, err.Error())
status.MarkNotReady(r.EventRecorder, component, v1alpha1.CreateOCIRepositoryNameFailedReason, err.Error())

return ctrl.Result{}, err
}

ociRepository, err := r.Registry.NewRepository(ctx, ociRepositoryName)
if err != nil {
status.MarkNotReady(r.EventRecorder, component, v1alpha1.ReconcileArtifactFailedReason, err.Error())
status.MarkNotReady(r.EventRecorder, component, v1alpha1.CreateOCIRepositoryFailedReason, err.Error())

return ctrl.Result{}, err
}

descriptorBytes, err := yaml.Marshal(descriptors)
descriptorsBytes, err := yaml.Marshal(descriptors)
if err != nil {
status.MarkNotReady(r.EventRecorder, component, v1alpha1.ReconcileArtifactFailedReason, err.Error())
status.MarkNotReady(r.EventRecorder, component, v1alpha1.MarshalFailedReason, err.Error())

return ctrl.Result{}, err
}

manifestDigest, err := ociRepository.PushSnapshot(ctx, version, descriptorBytes)
manifestDigest, err := ociRepository.PushSnapshot(ctx, version, descriptorsBytes)
if err != nil {
status.MarkNotReady(r.EventRecorder, component, v1alpha1.ReconcileArtifactFailedReason, err.Error())

return ctrl.Result{}, err
}

// Create snapshot
snapshotCR := snapshot.Create(component, ociRepositoryName, manifestDigest.String(), version, digest.FromBytes(descriptorBytes).String(), int64(len(descriptorBytes)))
logger.Info("creating snapshot")
snapshotCR := snapshot.Create(component, ociRepositoryName, manifestDigest.String(), version, digest.FromBytes(descriptorsBytes).String(), int64(len(descriptorsBytes)))

if _, err = controllerutil.CreateOrUpdate(ctx, r.GetClient(), &snapshotCR, func() error {
if snapshotCR.ObjectMeta.CreationTimestamp.IsZero() {
Expand All @@ -284,24 +286,24 @@ func (r *Reconciler) reconcileComponent(ctx context.Context, octx ocmctx.Context
}
}

component.Status.SnapshotRef = corev1.LocalObjectReference{
Name: snapshotCR.GetName(),
}

return nil
}); err != nil {
status.MarkNotReady(r.EventRecorder, component, v1alpha1.ReconcileArtifactFailedReason, err.Error())
status.MarkNotReady(r.EventRecorder, component, v1alpha1.CreateSnapshotFailedReason, err.Error())

return ctrl.Result{}, err
}

// Update component status
logger.Info("updating status")
component.Status.Component = v1alpha1.ComponentInfo{
RepositorySpec: repository.Spec.RepositorySpec,
Component: component.Spec.Component,
Version: version,
}

component.Status.SnapshotRef = corev1.LocalObjectReference{
Name: snapshotCR.GetName(),
}

component.Status.EffectiveOCMConfig = configs

status.MarkReady(r.EventRecorder, component, "Applied version %s", version)
Expand Down
99 changes: 77 additions & 22 deletions internal/controller/component/component_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@ package component
import (
"context"
"fmt"
"io"
"os"
"time"

. "github.com/mandelsoft/goutils/testutils"
"github.com/mandelsoft/vfs/pkg/vfs"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "ocm.software/ocm/api/helper/builder"
"ocm.software/ocm/api/utils/accessobj"
"sigs.k8s.io/yaml"

"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
Expand All @@ -37,9 +41,12 @@ import (
environment "ocm.software/ocm/api/helper/env"
"ocm.software/ocm/api/ocm/extensions/repositories/ctf"
"ocm.software/ocm/api/utils/accessio"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest/komega"

"github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1"
"github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm"
"github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot"
)

const (
Expand Down Expand Up @@ -133,20 +140,30 @@ var _ = Describe("Component Controller", func() {
Status: v1alpha1.ComponentStatus{},
}
Expect(k8sClient.Create(ctx, component)).To(Succeed())
DeferCleanup(func(ctx SpecContext) {
Expect(k8sClient.Delete(ctx, component, client.PropagationPolicy(metav1.DeletePropagationForeground))).To(Succeed())
})

By("check that snapshot has been created successfully")
By("checking that the component has been reconciled successfully")
Eventually(komega.Object(component), "5m").Should(
HaveField("Status.ObservedGeneration", Equal(int64(1))))

Eventually(komega.Object(component), "15s").Should(
HaveField("Status.SnapshotRef.Name", Not(BeEmpty())))
By("checking that the snapshot has been created successfully")
Expect(component).To(HaveField("Status.SnapshotRef.Name", Not(BeEmpty())))
snapshotComponent := Must(snapshot.GetSnapshotForOwner(ctx, k8sClient, component))

By("checking if the snapshot can be received")
snapshot := &v1alpha1.Snapshot{
ObjectMeta: metav1.ObjectMeta{
Namespace: component.Namespace,
Name: component.Status.SnapshotRef.Name,
},
}
Eventually(komega.Get(snapshot)).Should(Succeed())
By("checking that the snapshot contains the correct content")
snapshotRepository := Must(registry.NewRepository(ctx, snapshotComponent.Spec.Repository))
snapshotComponentContentReader := Must(snapshotRepository.FetchSnapshot(ctx, snapshotComponent.GetDigest()))
snapshotComponentContent := Must(io.ReadAll(snapshotComponentContentReader))
snapshotDescriptors := &ocm.Descriptors{}
MustBeSuccessful(yaml.Unmarshal(snapshotComponentContent, snapshotDescriptors))

repo := Must(ctf.Open(env, accessobj.ACC_WRITABLE, ctfpath, vfs.FileMode(vfs.O_RDWR), env))
cv := Must(repo.LookupComponentVersion(Component, Version1))
expectedDescriptors := Must(ocm.ListComponentDescriptors(ctx, cv, repo))

Expect(snapshotDescriptors).To(YAMLEqual(expectedDescriptors))
})

It("does not reconcile when the repository is not ready", func() {
Expand All @@ -172,6 +189,9 @@ var _ = Describe("Component Controller", func() {
Status: v1alpha1.ComponentStatus{},
}
Expect(k8sClient.Create(ctx, component)).To(Succeed())
DeferCleanup(func(ctx SpecContext) {
Expect(k8sClient.Delete(ctx, component, client.PropagationPolicy(metav1.DeletePropagationForeground))).To(Succeed())
})

By("check that no snapshot has been created")
Eventually(komega.Object(component), "15s").Should(
Expand All @@ -197,11 +217,17 @@ var _ = Describe("Component Controller", func() {
Status: v1alpha1.ComponentStatus{},
}
Expect(k8sClient.Create(ctx, component)).To(Succeed())
DeferCleanup(func(ctx SpecContext) {
Expect(k8sClient.Delete(ctx, component, client.PropagationPolicy(metav1.DeletePropagationForeground))).To(Succeed())
})

By("check that snapshot has been created successfully")
By("checking that the component has been reconciled successfully")
Eventually(komega.Object(component), "5m").Should(
HaveField("Status.ObservedGeneration", Equal(int64(1))))

Eventually(komega.Object(component), "15s").Should(
HaveField("Status.SnapshotRef.Name", Not(BeEmpty())))
By("checking that the snapshot has been created successfully")
Expect(component).To(HaveField("Status.SnapshotRef.Name", Not(BeEmpty())))
Must(snapshot.GetSnapshotForOwner(ctx, k8sClient, component))

Expect(k8sClient.Get(ctx, types.NamespacedName{Name: component.Name, Namespace: component.Namespace}, component)).To(Succeed())
Expect(component.Status.Component.Version).To(Equal(Version1))
Expand Down Expand Up @@ -254,10 +280,17 @@ var _ = Describe("Component Controller", func() {
Status: v1alpha1.ComponentStatus{},
}
Expect(k8sClient.Create(ctx, component)).To(Succeed())
DeferCleanup(func(ctx SpecContext) {
Expect(k8sClient.Delete(ctx, component, client.PropagationPolicy(metav1.DeletePropagationForeground))).To(Succeed())
})

By("check that snapshot has been created successfully")
By("checking that the component has been reconciled successfully")
Eventually(komega.Object(component), "5m").Should(
HaveField("Status.ObservedGeneration", Equal(int64(1))))

Eventually(komega.Object(component), "15s").Should(HaveField("Status.SnapshotRef.Name", Not(BeEmpty())))
By("checking that the snapshot has been created successfully")
Expect(component).To(HaveField("Status.SnapshotRef.Name", Not(BeEmpty())))
Must(snapshot.GetSnapshotForOwner(ctx, k8sClient, component))

Expect(k8sClient.Get(ctx, types.NamespacedName{Name: component.Name, Namespace: component.Namespace}, component)).To(Succeed())
Expect(component.Status.Component.Version).To(Equal("0.0.3"))
Expand Down Expand Up @@ -303,9 +336,17 @@ var _ = Describe("Component Controller", func() {
},
}
Expect(k8sClient.Create(ctx, component)).To(Succeed())
DeferCleanup(func(ctx SpecContext) {
Expect(k8sClient.Delete(ctx, component, client.PropagationPolicy(metav1.DeletePropagationForeground))).To(Succeed())
})

By("check that snapshot has been created successfully")
Eventually(komega.Object(component), "15s").Should(HaveField("Status.SnapshotRef.Name", Not(BeEmpty())))
By("checking that the component has been reconciled successfully")
Eventually(komega.Object(component), "5m").Should(
HaveField("Status.ObservedGeneration", Equal(int64(1))))

By("checking that the snapshot has been created successfully")
Expect(component).To(HaveField("Status.SnapshotRef.Name", Not(BeEmpty())))
Must(snapshot.GetSnapshotForOwner(ctx, k8sClient, component))

Expect(k8sClient.Get(ctx, types.NamespacedName{Name: component.Name, Namespace: component.Namespace}, component)).To(Succeed())
Expect(component.Status.Component.Version).To(Equal("0.0.3"))
Expand Down Expand Up @@ -349,11 +390,17 @@ var _ = Describe("Component Controller", func() {
Status: v1alpha1.ComponentStatus{},
}
Expect(k8sClient.Create(ctx, component)).To(Succeed())
DeferCleanup(func(ctx SpecContext) {
Expect(k8sClient.Delete(ctx, component, client.PropagationPolicy(metav1.DeletePropagationForeground))).To(Succeed())
})

By("check that snapshot has been created successfully")
By("checking that the component has been reconciled successfully")
Eventually(komega.Object(component), "5m").Should(
HaveField("Status.ObservedGeneration", Equal(int64(1))))

Eventually(komega.Object(component), "15s").Should(
HaveField("Status.SnapshotRef.Name", Not(BeEmpty())))
By("checking that the snapshot has been created successfully")
Expect(component).To(HaveField("Status.SnapshotRef.Name", Not(BeEmpty())))
Must(snapshot.GetSnapshotForOwner(ctx, k8sClient, component))

Expect(k8sClient.Get(ctx, types.NamespacedName{Name: component.Name, Namespace: component.Namespace}, component)).To(Succeed())
Expect(component.Status.Component.Version).To(Equal("0.0.3"))
Expand All @@ -365,7 +412,7 @@ var _ = Describe("Component Controller", func() {
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: component.Name, Namespace: component.Namespace}, component)).To(Succeed())

return component.Status.Component.Version == "0.0.2"
}).WithTimeout(15 * time.Second).Should(BeTrue())
}).WithTimeout(60 * time.Second).Should(BeTrue())
})
})

Expand Down Expand Up @@ -559,6 +606,14 @@ var _ = Describe("Component Controller", func() {
}
Expect(k8sClient.Create(ctx, component)).To(Succeed())

By("checking that the component has been reconciled successfully")
Eventually(komega.Object(component), "5m").Should(
HaveField("Status.ObservedGeneration", Equal(int64(1))))

By("checking that the snapshot has been created successfully")
Expect(component).To(HaveField("Status.SnapshotRef.Name", Not(BeEmpty())))
Must(snapshot.GetSnapshotForOwner(ctx, k8sClient, component))

Eventually(komega.Object(component), "15s").Should(
HaveField("Status.EffectiveOCMConfig", ConsistOf(
v1alpha1.OCMConfiguration{
Expand Down
Loading

0 comments on commit 45ce876

Please sign in to comment.