Skip to content

Commit 09609e1

Browse files
committed
replace storage with oci artifact for resource controller
1 parent f1aab87 commit 09609e1

File tree

15 files changed

+438
-343
lines changed

15 files changed

+438
-343
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ undeploy-cert-manager: ## Undeploy cert-manager from the K8s cluster specified i
220220

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

api/v1alpha1/condition_types.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,54 @@ const (
6363
// ReconcileArtifactFailedReason is used when we fail in creating an Artifact.
6464
ReconcileArtifactFailedReason = "ReconcileArtifactFailed"
6565

66+
// MarshalFailedReason is used when we fail to marshal a struct.
67+
MarshalFailedReason = "MarshalFailed"
68+
69+
// CreateOCIRepositoryNameFailedReason is used when we fail to create an OCI repository name.
70+
CreateOCIRepositoryNameFailedReason = "CreateOCIRepositoryNameFailed"
71+
72+
// CreateOCIRepositoryFailedReason is used when we fail to create a OCI repository.
73+
CreateOCIRepositoryFailedReason = "CreateOCIRepositoryFailed"
74+
75+
// CreateSnapshotFailedReason is used when we fail to create a snapshot.
76+
CreateSnapshotFailedReason = "CreateSnapshotFailed"
77+
6678
// GetArtifactFailedReason is used when we fail in getting an Artifact.
6779
GetArtifactFailedReason = "GetArtifactFailed"
6880

81+
// GetSnapshotFailedReason is used when we fail in getting a Snapshot.
82+
GetSnapshotFailedReason = "GetSnapshotFailed"
83+
6984
// ResolveResourceFailedReason is used when we fail in resolving a resource.
7085
ResolveResourceFailedReason = "ResolveResourceFailed"
7186

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

90+
// GetBlobAccessFailedReason is used when we fail to get a blob access.
91+
GetBlobAccessFailedReason = "GetBlobAccessFailed"
92+
93+
// VerifyResourceFailedReason is used when we fail to verify a resource.
94+
VerifyResourceFailedReason = "VerifyResourceFailed"
95+
96+
// GetResourceFailedReason is used when we fail to get the resource.
97+
GetResourceFailedReason = "GetResourceFailed"
98+
99+
// PushSnapshotFailedReason is used when we fail to push a snapshot.
100+
PushSnapshotFailedReason = "PushSnapshotFailed"
101+
102+
// FetchSnapshotFailedReason is used when we fail to fetch a snapshot.
103+
FetchSnapshotFailedReason = "FetchSnapshotFailed"
104+
105+
// DeleteSnapshotFailedReason is used when we fail to delete a snapshot.
106+
DeleteSnapshotFailedReason = "DeleteSnapshotFailed"
107+
75108
// GetComponentForArtifactFailedReason is used when we fail in getting a component for an artifact.
76109
GetComponentForArtifactFailedReason = "GetComponentForArtifactFailed"
77110

111+
// GetComponentForSnapshotFailedReason is used when we fail in getting a component for a snapshot.
112+
GetComponentForSnapshotFailedReason = "GetComponentForSnapshotFailed"
113+
78114
// StatusSetFailedReason is used when we fail to set the component status.
79115
StatusSetFailedReason = "StatusSetFailed"
80116

api/v1alpha1/constants.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const (
3737

3838
// Finalizers for controllers.
3939
const (
40-
// TODO: Remove ArtifactFinalizer
40+
// TODO: Remove ArtifactFinalizer.
4141

4242
// ArtifactFinalizer is the finalizer that is added to artifacts created by the ocm controllers.
4343
ArtifactFinalizer = "finalizers.ocm.software/artifact"

cmd/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,8 @@ func main() {
211211
Scheme: mgr.GetScheme(),
212212
EventRecorder: eventsRecorder,
213213
},
214-
Storage: storage,
214+
Registry: registry,
215+
Storage: storage,
215216
}).SetupWithManager(mgr); err != nil {
216217
setupLog.Error(err, "unable to create controller", "controller", "Resource")
217218
os.Exit(1)

internal/controller/component/component_controller.go

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"fmt"
2323

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

6666
// +kubebuilder:rbac:groups=delivery.ocm.software,resources=components,verbs=get;list;watch;create;update;patch;delete
6767
// +kubebuilder:rbac:groups=delivery.ocm.software,resources=components/status,verbs=get;update;patch
68-
// +kubebuilder:rbac:groups=delivery.ocm.software,resources=components/finalizers,verbs=update
69-
70-
// +kubebuilder:rbac:groups=openfluxcd.ocm.software,resources=artifacts,verbs=get;list;watch;create;update;patch;delete
71-
// +kubebuilder:rbac:groups=openfluxcd.ocm.software,resources=artifacts/status,verbs=get;update;patch
72-
// +kubebuilder:rbac:groups=openfluxcd.ocm.software,resources=artifacts/finalizers,verbs=update
68+
// +kubebuilder:rbac:groups=delivery.ocm.software,resources=components/finalizers,verbs=updat
7369

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

74+
// TODO: Remove
75+
// +kubebuilder:rbac:groups=openfluxcd.ocm.software,resources=artifacts,verbs=get;list;watch;create;update;patch;delete
76+
// +kubebuilder:rbac:groups=openfluxcd.ocm.software,resources=artifacts/status,verbs=get;update;patch
77+
// +kubebuilder:rbac:groups=openfluxcd.ocm.software,resources=artifacts/finalizers,verbs=update
78+
7879
// Reconcile the component object.
7980
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, retErr error) {
8081
component := &v1alpha1.Component{}
@@ -141,8 +142,8 @@ func (r *Reconciler) reconcile(ctx context.Context, component *v1alpha1.Componen
141142
// not ready as well.
142143
// However, as the component is hard-dependant on the ocmrepository, we decided to mark it not ready as well.
143144
if !conditions.IsReady(repo) {
144-
conditions.Delete(component, meta.ReconcilingCondition)
145-
conditions.MarkFalse(component, meta.ReadyCondition, v1alpha1.RepositoryIsNotReadyReason, "repository is not ready")
145+
logger.Info("repository is not ready", "name", component.Spec.RepositoryRef.Name)
146+
status.MarkNotReady(r.EventRecorder, component, v1alpha1.RepositoryIsNotReadyReason, "repository is not ready yet")
146147

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

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

253255
return ctrl.Result{}, err
254256
}
255257

256258
ociRepository, err := r.Registry.NewRepository(ctx, ociRepositoryName)
257259
if err != nil {
258-
status.MarkNotReady(r.EventRecorder, component, v1alpha1.ReconcileArtifactFailedReason, err.Error())
260+
status.MarkNotReady(r.EventRecorder, component, v1alpha1.CreateOCIRepositoryFailedReason, err.Error())
259261

260262
return ctrl.Result{}, err
261263
}
262264

263-
descriptorBytes, err := yaml.Marshal(descriptors)
265+
descriptorsBytes, err := yaml.Marshal(descriptors)
264266
if err != nil {
265-
status.MarkNotReady(r.EventRecorder, component, v1alpha1.ReconcileArtifactFailedReason, err.Error())
267+
status.MarkNotReady(r.EventRecorder, component, v1alpha1.MarshalFailedReason, err.Error())
266268

267269
return ctrl.Result{}, err
268270
}
269271

270-
manifestDigest, err := ociRepository.PushSnapshot(ctx, version, descriptorBytes)
272+
manifestDigest, err := ociRepository.PushSnapshot(ctx, version, descriptorsBytes)
271273
if err != nil {
272274
status.MarkNotReady(r.EventRecorder, component, v1alpha1.ReconcileArtifactFailedReason, err.Error())
273275

274276
return ctrl.Result{}, err
275277
}
276278

277-
// Create snapshot
278-
snapshotCR := snapshot.Create(component, ociRepositoryName, manifestDigest.String(), version, digest.FromBytes(descriptorBytes).String(), int64(len(descriptorBytes)))
279+
logger.Info("creating snapshot")
280+
snapshotCR := snapshot.Create(component, ociRepositoryName, manifestDigest.String(), version, digest.FromBytes(descriptorsBytes).String(), int64(len(descriptorsBytes)))
279281

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

289+
component.Status.SnapshotRef = corev1.LocalObjectReference{
290+
Name: snapshotCR.GetName(),
291+
}
292+
287293
return nil
288294
}); err != nil {
289-
status.MarkNotReady(r.EventRecorder, component, v1alpha1.ReconcileArtifactFailedReason, err.Error())
295+
status.MarkNotReady(r.EventRecorder, component, v1alpha1.CreateSnapshotFailedReason, err.Error())
290296

291297
return ctrl.Result{}, err
292298
}
293299

294-
// Update component status
300+
logger.Info("updating status")
295301
component.Status.Component = v1alpha1.ComponentInfo{
296302
RepositorySpec: repository.Spec.RepositorySpec,
297303
Component: component.Spec.Component,
298304
Version: version,
299305
}
300306

301-
component.Status.SnapshotRef = corev1.LocalObjectReference{
302-
Name: snapshotCR.GetName(),
303-
}
304-
305307
component.Status.EffectiveOCMConfig = configs
306308

307309
status.MarkReady(r.EventRecorder, component, "Applied version %s", version)

internal/controller/component/component_controller_test.go

Lines changed: 77 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@ package component
1919
import (
2020
"context"
2121
"fmt"
22+
"io"
2223
"os"
2324
"time"
2425

2526
. "github.com/mandelsoft/goutils/testutils"
27+
"github.com/mandelsoft/vfs/pkg/vfs"
2628
. "github.com/onsi/ginkgo/v2"
2729
. "github.com/onsi/gomega"
2830
. "ocm.software/ocm/api/helper/builder"
31+
"ocm.software/ocm/api/utils/accessobj"
32+
"sigs.k8s.io/yaml"
2933

3034
"github.com/fluxcd/pkg/apis/meta"
3135
"github.com/fluxcd/pkg/runtime/conditions"
@@ -37,9 +41,12 @@ import (
3741
environment "ocm.software/ocm/api/helper/env"
3842
"ocm.software/ocm/api/ocm/extensions/repositories/ctf"
3943
"ocm.software/ocm/api/utils/accessio"
44+
"sigs.k8s.io/controller-runtime/pkg/client"
4045
"sigs.k8s.io/controller-runtime/pkg/envtest/komega"
4146

4247
"github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1"
48+
"github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm"
49+
"github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot"
4350
)
4451

4552
const (
@@ -133,20 +140,30 @@ var _ = Describe("Component Controller", func() {
133140
Status: v1alpha1.ComponentStatus{},
134141
}
135142
Expect(k8sClient.Create(ctx, component)).To(Succeed())
143+
DeferCleanup(func(ctx SpecContext) {
144+
Expect(k8sClient.Delete(ctx, component, client.PropagationPolicy(metav1.DeletePropagationForeground))).To(Succeed())
145+
})
136146

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

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

142-
By("checking if the snapshot can be received")
143-
snapshot := &v1alpha1.Snapshot{
144-
ObjectMeta: metav1.ObjectMeta{
145-
Namespace: component.Namespace,
146-
Name: component.Status.SnapshotRef.Name,
147-
},
148-
}
149-
Eventually(komega.Get(snapshot)).Should(Succeed())
155+
By("checking that the snapshot contains the correct content")
156+
snapshotRepository := Must(registry.NewRepository(ctx, snapshotComponent.Spec.Repository))
157+
snapshotComponentContentReader := Must(snapshotRepository.FetchSnapshot(ctx, snapshotComponent.GetDigest()))
158+
snapshotComponentContent := Must(io.ReadAll(snapshotComponentContentReader))
159+
snapshotDescriptors := &ocm.Descriptors{}
160+
MustBeSuccessful(yaml.Unmarshal(snapshotComponentContent, snapshotDescriptors))
161+
162+
repo := Must(ctf.Open(env, accessobj.ACC_WRITABLE, ctfpath, vfs.FileMode(vfs.O_RDWR), env))
163+
cv := Must(repo.LookupComponentVersion(Component, Version1))
164+
expectedDescriptors := Must(ocm.ListComponentDescriptors(ctx, cv, repo))
165+
166+
Expect(snapshotDescriptors).To(YAMLEqual(expectedDescriptors))
150167
})
151168

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

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

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

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

206232
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: component.Name, Namespace: component.Namespace}, component)).To(Succeed())
207233
Expect(component.Status.Component.Version).To(Equal(Version1))
@@ -254,10 +280,17 @@ var _ = Describe("Component Controller", func() {
254280
Status: v1alpha1.ComponentStatus{},
255281
}
256282
Expect(k8sClient.Create(ctx, component)).To(Succeed())
283+
DeferCleanup(func(ctx SpecContext) {
284+
Expect(k8sClient.Delete(ctx, component, client.PropagationPolicy(metav1.DeletePropagationForeground))).To(Succeed())
285+
})
257286

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

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

262295
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: component.Name, Namespace: component.Namespace}, component)).To(Succeed())
263296
Expect(component.Status.Component.Version).To(Equal("0.0.3"))
@@ -303,9 +336,17 @@ var _ = Describe("Component Controller", func() {
303336
},
304337
}
305338
Expect(k8sClient.Create(ctx, component)).To(Succeed())
339+
DeferCleanup(func(ctx SpecContext) {
340+
Expect(k8sClient.Delete(ctx, component, client.PropagationPolicy(metav1.DeletePropagationForeground))).To(Succeed())
341+
})
306342

307-
By("check that snapshot has been created successfully")
308-
Eventually(komega.Object(component), "15s").Should(HaveField("Status.SnapshotRef.Name", Not(BeEmpty())))
343+
By("checking that the component has been reconciled successfully")
344+
Eventually(komega.Object(component), "5m").Should(
345+
HaveField("Status.ObservedGeneration", Equal(int64(1))))
346+
347+
By("checking that the snapshot has been created successfully")
348+
Expect(component).To(HaveField("Status.SnapshotRef.Name", Not(BeEmpty())))
349+
Must(snapshot.GetSnapshotForOwner(ctx, k8sClient, component))
309350

310351
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: component.Name, Namespace: component.Namespace}, component)).To(Succeed())
311352
Expect(component.Status.Component.Version).To(Equal("0.0.3"))
@@ -349,11 +390,17 @@ var _ = Describe("Component Controller", func() {
349390
Status: v1alpha1.ComponentStatus{},
350391
}
351392
Expect(k8sClient.Create(ctx, component)).To(Succeed())
393+
DeferCleanup(func(ctx SpecContext) {
394+
Expect(k8sClient.Delete(ctx, component, client.PropagationPolicy(metav1.DeletePropagationForeground))).To(Succeed())
395+
})
352396

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

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

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

367414
return component.Status.Component.Version == "0.0.2"
368-
}).WithTimeout(15 * time.Second).Should(BeTrue())
415+
}).WithTimeout(60 * time.Second).Should(BeTrue())
369416
})
370417
})
371418

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

609+
By("checking that the component has been reconciled successfully")
610+
Eventually(komega.Object(component), "5m").Should(
611+
HaveField("Status.ObservedGeneration", Equal(int64(1))))
612+
613+
By("checking that the snapshot has been created successfully")
614+
Expect(component).To(HaveField("Status.SnapshotRef.Name", Not(BeEmpty())))
615+
Must(snapshot.GetSnapshotForOwner(ctx, k8sClient, component))
616+
562617
Eventually(komega.Object(component), "15s").Should(
563618
HaveField("Status.EffectiveOCMConfig", ConsistOf(
564619
v1alpha1.OCMConfiguration{

0 commit comments

Comments
 (0)