diff --git a/PROJECT b/PROJECT index f172fb06..08a89d0e 100644 --- a/PROJECT +++ b/PROJECT @@ -71,13 +71,4 @@ resources: kind: Replication path: github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1 version: v1alpha1 -- api: - crdVersion: v1 - namespaced: true - controller: true - domain: ocm.software - group: delivery - kind: Snapshot - path: github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1 - version: v1alpha1 version: "3" diff --git a/api/v1alpha1/common_types.go b/api/v1alpha1/common_types.go index 861c058f..ac3acd7d 100644 --- a/api/v1alpha1/common_types.go +++ b/api/v1alpha1/common_types.go @@ -125,3 +125,24 @@ type BlobInfo struct { // Can be used to determine how to file should be handled when downloaded (memory/disk) Size int64 `json:"size"` } + +// OCIArtifactInfo contains information on how to locate an OCI Artifact. +type OCIArtifactInfo struct { + // OCI repository name + // +required + Repository string `json:"repository"` + + // Manifest digest (required to delete the manifest and prepare OCI artifact for GC) + // +required + Digest string `json:"digest"` + + // Blob + // +required + Blob *BlobInfo `json:"blob"` +} + +// +k8s:deepcopy-gen=false +type OCIArtifactCreator interface { + GetOCIRepository() string + GetManifestDigest() string +} diff --git a/api/v1alpha1/component_types.go b/api/v1alpha1/component_types.go index 3fa2d179..3e147513 100644 --- a/api/v1alpha1/component_types.go +++ b/api/v1alpha1/component_types.go @@ -20,7 +20,6 @@ import ( "fmt" "time" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -100,12 +99,6 @@ type ComponentStatus struct { // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` - // SnapshotRef references the generated snapshot containing a list of - // component descriptors. This list can be used by other controllers to - // avoid re-downloading (and potentially also re-verifying) the components. - // +optional - SnapshotRef corev1.LocalObjectReference `json:"snapshotRef,omitempty"` - // Component specifies the concrete version of the component that was // fetched after based on the semver constraints during the last successful // reconciliation. @@ -117,6 +110,12 @@ type ComponentStatus struct { // in the order the configuration data was applied. // +optional EffectiveOCMConfig []OCMConfiguration `json:"effectiveOCMConfig,omitempty"` + + // OCIArtifact references the generated OCI artifact containing a list of + // component descriptors. This list can be used by other controllers to + // avoid re-downloading (and potentially also re-verifying) the components. + // +optional + OCIArtifact *OCIArtifactInfo `json:"ociArtifact,omitempty"` } // +kubebuilder:object:root=true @@ -180,8 +179,22 @@ func (in *Component) GetVerifications() []Verification { return in.Spec.Verify } -func (in *Component) GetSnapshotName() string { - return in.Status.SnapshotRef.Name +// GetOCIRepository returns the name of the OCI repository of the OCI artifact in which the component +// descriptors are stored. +func (in *Component) GetOCIRepository() string { + return in.Status.OCIArtifact.Repository +} + +// GetManifestDigest returns the manifest digest of the OCI artifact, in which the component descriptors +// are stored. +func (in *Component) GetManifestDigest() string { + return in.Status.OCIArtifact.Digest +} + +// GetBlobDigest returns the blob digest of the OCI artifact, in which the component descriptors +// are stored. +func (in *Component) GetBlobDigest() string { + return in.Status.OCIArtifact.Blob.Digest } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/condition_types.go b/api/v1alpha1/condition_types.go index 01ac870c..975b90b6 100644 --- a/api/v1alpha1/condition_types.go +++ b/api/v1alpha1/condition_types.go @@ -54,29 +54,26 @@ const ( // MarshalFailedReason is used when we fail to marshal a struct. MarshalFailedReason = "MarshalFailed" + // YamlToJsonDecodeFailedReason is used when we fail to decode yaml to json. + YamlToJsonDecodeFailedReason = "YamlToJsonDecodeFailed" + // CreateOCIRepositoryNameFailedReason is used when we fail to create an OCI repository name. CreateOCIRepositoryNameFailedReason = "CreateOCIRepositoryNameFailed" // CreateOCIRepositoryFailedReason is used when we fail to create an OCI repository. CreateOCIRepositoryFailedReason = "CreateOCIRepositoryFailed" - // OCIRepositoryExistsFailedReason is used when we fail to check the existence of an OCI repository. - OCIRepositoryExistsFailedReason = "OCIRepositoryExistsFailed" - - // DeleteSnapshotFailedReason is used when we fail to delete an OCI repository. - DeleteSnapshotFailedReason = "DeleteOCIRepositoryFailed" + // PushOCIArtifactFailedReason is used when we fail to push an OCI artifact. + PushOCIArtifactFailedReason = "PushOCIArtifactFailed" - // CreateSnapshotFailedReason is used when we fail to create a snapshot. - CreateSnapshotFailedReason = "CreateSnapshotFailed" + // FetchOCIArtifactFailedReason is used when we fail to fetch an OCI artifact. + FetchOCIArtifactFailedReason = "FetchOCIArtifactFailed" - // GetSnapshotFailedReason is used when we fail in getting a Snapshot. - GetSnapshotFailedReason = "GetSnapshotFailed" + // CopyOCIArtifactFailedReason is used when we fail to copy an OCI artifact. + CopyOCIArtifactFailedReason = "CopyOCIArtifactFailed" - // SnapshotReadyFailedReason is used when the snapshot is not ready. - SnapshotReadyFailedReason = "SnapshotReadyFailed" - - // PushSnapshotFailedReason is used when we fail to push a snapshot. - PushSnapshotFailedReason = "PushSnapshotFailed" + // OCIRepositoryExistsFailedReason is used when we fail to check the existence of an OCI repository. + OCIRepositoryExistsFailedReason = "OCIRepositoryExistsFailed" // ResolveResourceFailedReason is used when we fail in resolving a resource. ResolveResourceFailedReason = "ResolveResourceFailed" @@ -93,9 +90,6 @@ const ( // GetResourceFailedReason is used when we fail to get the resource. GetResourceFailedReason = "GetResourceFailed" - // GetComponentForSnapshotFailedReason is used when we fail in getting a component for a snapshot. - GetComponentForSnapshotFailedReason = "GetComponentForSnapshotFailed" - // CompressGzipFailedReason is used when we fail to compress to gzip. CompressGzipFailedReason = "CompressGzipFailed" diff --git a/api/v1alpha1/configuredresource_types.go b/api/v1alpha1/configuredresource_types.go index 587661df..5a70bcf4 100644 --- a/api/v1alpha1/configuredresource_types.go +++ b/api/v1alpha1/configuredresource_types.go @@ -21,7 +21,6 @@ import ( "github.com/fluxcd/pkg/apis/meta" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -91,10 +90,10 @@ type ConfiguredResourceStatus struct { ObservedGeneration int64 `json:"observedGeneration,omitempty"` Conditions []metav1.Condition `json:"conditions,omitempty"` - // The configuration reconcile loop generates a snapshot, which contains the + // The configuration reconcile loop generates an OCI artifact, which contains the // ConfiguredResourceSpec.Target ConfigurationReference after configuration. - // It is filled once the Snapshot is created and the configuration completed. - SnapshotRef corev1.LocalObjectReference `json:"snapshotRef,omitempty"` + // It is filled once the OCI artifact is created and the configuration completed. + OCIArtifact *OCIArtifactInfo `json:"ociArtifact,omitempty"` } // +kubebuilder:object:root=true @@ -109,10 +108,6 @@ type ConfiguredResource struct { Status ConfiguredResourceStatus `json:"status,omitempty"` } -func (in *ConfiguredResource) GetSnapshotName() string { - return in.Status.SnapshotRef.Name -} - // +kubebuilder:object:root=true // ConfiguredResourceList contains a list of ConfiguredResource. diff --git a/api/v1alpha1/constants.go b/api/v1alpha1/constants.go index d467e152..f788eb92 100644 --- a/api/v1alpha1/constants.go +++ b/api/v1alpha1/constants.go @@ -34,8 +34,8 @@ const ( // Finalizers for controllers. const ( - // SnapshotFinalizer is the finalizter that is added to snapshot created by the ocm controllers. - SnapshotFinalizer = "finalizers.ocm.software/snapshot" + // ArtifactFinalizer is the finalizer that is added to an object that handles the lifecycle of an artifact created by the ocm controllers. + ArtifactFinalizer = "finalizers.ocm.software/artifact" ) // OCI related constants. diff --git a/api/v1alpha1/localizedresource_types.go b/api/v1alpha1/localizedresource_types.go index 029b6a12..41cb7f93 100644 --- a/api/v1alpha1/localizedresource_types.go +++ b/api/v1alpha1/localizedresource_types.go @@ -3,7 +3,6 @@ package v1alpha1 import ( "fmt" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -74,10 +73,6 @@ func (in *LocalizedResource) SetTarget(v *ConfigurationReference) { v.DeepCopyInto(&in.Spec.Target) } -func (in *LocalizedResource) GetSnapshotName() string { - return in.Status.SnapshotRef.Name -} - type LocalizedResourceSpec struct { // Target that is to be localized Target ConfigurationReference `json:"target"` @@ -96,15 +91,16 @@ type LocalizedResourceStatus struct { ObservedGeneration int64 `json:"observedGeneration,omitempty"` Conditions []metav1.Condition `json:"conditions,omitempty"` - // The LocalizedResource reports an SnapshotRef which contains the content of the Resource after Localization - SnapshotRef corev1.LocalObjectReference `json:"snapshotRef,omitempty"` + // The OCIArtifact contains the information where to find the OCI artifact which contains + // the content of the Resource after Localization + OCIArtifact *OCIArtifactInfo `json:"ociArtifact,omitempty"` // The LocalizedResource reports a ConfiguredResourceRef which contains a reference to the ConfiguredResource - // that is responsible for generating the SnapshotRef. + // that is responsible for generating the OCI artifact. ConfiguredResourceRef *ObjectKey `json:"configuredResourceRef,omitempty"` // ConfigRef is a reference to the Configuration that was generated by the Localization process - // and is used to setup the ConfiguredResource responsible for generating the SnapshotRef. + // and is used to setup the ConfiguredResource responsible for generating the OCI artifact. ConfigRef *ObjectKey `json:"configRef,omitempty"` // A unique digest of the combination of the config and target resources applied through a LocalizationStrategy diff --git a/api/v1alpha1/resource_types.go b/api/v1alpha1/resource_types.go index 8fd47664..e5cce055 100644 --- a/api/v1alpha1/resource_types.go +++ b/api/v1alpha1/resource_types.go @@ -62,10 +62,10 @@ type ResourceStatus struct { // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` - // SnapshotRef points to the Snapshot which represents the output of the + // OCIArtifact points to the OCI artifact which represents the output of the // last successful Resource sync. // +optional - SnapshotRef corev1.LocalObjectReference `json:"snapshotRef,omitempty"` + OCIArtifact *OCIArtifactInfo `json:"ociArtifact,omitempty"` // +optional Resource *ResourceInfo `json:"resource,omitempty"` @@ -131,8 +131,19 @@ func (in *Resource) GetEffectiveOCMConfig() []OCMConfiguration { return in.Status.EffectiveOCMConfig } -func (in *Resource) GetSnapshotName() string { - return in.Status.SnapshotRef.Name +// GetOCIRepository returns the name of the OCI repository of the OCI artifact in which the resource is stored. +func (in *Resource) GetOCIRepository() string { + return in.Status.OCIArtifact.Repository +} + +// GetManifestDigest returns the manifest digest of the OCI artifact, in which the resource is stored. +func (in *Resource) GetManifestDigest() string { + return in.Status.OCIArtifact.Digest +} + +// GetBlobDigest returns the blob digest of the OCI artifact, in which the resource is stored. +func (in *Resource) GetBlobDigest() string { + return in.Status.OCIArtifact.Blob.Digest } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/snapshot_types.go b/api/v1alpha1/snapshot_types.go deleted file mode 100644 index 975d545e..00000000 --- a/api/v1alpha1/snapshot_types.go +++ /dev/null @@ -1,101 +0,0 @@ -package v1alpha1 - -import ( - "fmt" - - "sigs.k8s.io/controller-runtime/pkg/client" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// SnapshotWriter defines any object which produces a snapshot -// +k8s:deepcopy-gen=false -type SnapshotWriter interface { - client.Object - GetSnapshotName() string - GetKind() string -} - -// SnapshotSpec defines the desired state of Snapshot. -type SnapshotSpec struct { - // OCI repository name - // +required - Repository string `json:"repository"` - - // Manifest digest (required to delete the manifest and prepare OCI artifact for GC) - // +required - Digest string `json:"digest"` - - // Blob - // +required - Blob *BlobInfo `json:"blob"` - - // Suspend stops all operations on this object. - // +optional - Suspend bool `json:"suspend,omitempty"` -} - -// SnapshotStatus defines the observed state of Snapshot. -type SnapshotStatus struct { - // +optional - Conditions []metav1.Condition `json:"conditions,omitempty"` - - // ObservedGeneration is the last reconciled generation. - // +optional - ObservedGeneration int64 `json:"observedGeneration,omitempty"` -} - -func (in *Snapshot) GetVID() map[string]string { - vid := fmt.Sprintf("%s:%s", in.GetNamespace(), in.GetName()) - metadata := make(map[string]string) - metadata[GroupVersion.Group+"/snapshot_version"] = vid - - return metadata -} - -func (in *Snapshot) SetObservedGeneration(v int64) { - in.Status.ObservedGeneration = v -} - -// GetDigest returns the last reconciled digest for the snapshot. -func (in Snapshot) GetDigest() string { - return in.Spec.Digest -} - -// GetConditions returns the status conditions of the object. -func (in Snapshot) GetConditions() []metav1.Condition { - return in.Status.Conditions -} - -// SetConditions sets the status conditions on the object. -func (in *Snapshot) SetConditions(conditions []metav1.Condition) { - in.Status.Conditions = conditions -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// +kubebuilder:resource:shortName=snap -// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="" -// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description="" - -// Snapshot is the Schema for the snapshots API. -type Snapshot struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec SnapshotSpec `json:"spec,omitempty"` - Status SnapshotStatus `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// SnapshotList contains a list of Snapshot. -type SnapshotList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []Snapshot `json:"items"` -} - -func init() { - SchemeBuilder.Register(&Snapshot{}, &SnapshotList{}) -} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 636a6712..dad944ed 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -142,13 +142,17 @@ func (in *ComponentStatus) DeepCopyInto(out *ComponentStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - out.SnapshotRef = in.SnapshotRef in.Component.DeepCopyInto(&out.Component) if in.EffectiveOCMConfig != nil { in, out := &in.EffectiveOCMConfig, &out.EffectiveOCMConfig *out = make([]OCMConfiguration, len(*in)) copy(*out, *in) } + if in.OCIArtifact != nil { + in, out := &in.OCIArtifact, &out.OCIArtifact + *out = new(OCIArtifactInfo) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentStatus. @@ -394,7 +398,11 @@ func (in *ConfiguredResourceStatus) DeepCopyInto(out *ConfiguredResourceStatus) (*in)[i].DeepCopyInto(&(*out)[i]) } } - out.SnapshotRef = in.SnapshotRef + if in.OCIArtifact != nil { + in, out := &in.OCIArtifact, &out.OCIArtifact + *out = new(OCIArtifactInfo) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfiguredResourceStatus. @@ -721,7 +729,11 @@ func (in *LocalizedResourceStatus) DeepCopyInto(out *LocalizedResourceStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - out.SnapshotRef = in.SnapshotRef + if in.OCIArtifact != nil { + in, out := &in.OCIArtifact, &out.OCIArtifact + *out = new(OCIArtifactInfo) + (*in).DeepCopyInto(*out) + } if in.ConfiguredResourceRef != nil { in, out := &in.ConfiguredResourceRef, &out.ConfiguredResourceRef *out = new(ObjectKey) @@ -744,6 +756,26 @@ func (in *LocalizedResourceStatus) DeepCopy() *LocalizedResourceStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OCIArtifactInfo) DeepCopyInto(out *OCIArtifactInfo) { + *out = *in + if in.Blob != nil { + in, out := &in.Blob, &out.Blob + *out = new(BlobInfo) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIArtifactInfo. +func (in *OCIArtifactInfo) DeepCopy() *OCIArtifactInfo { + if in == nil { + return nil + } + out := new(OCIArtifactInfo) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OCMConfiguration) DeepCopyInto(out *OCMConfiguration) { *out = *in @@ -1254,7 +1286,11 @@ func (in *ResourceStatus) DeepCopyInto(out *ResourceStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - out.SnapshotRef = in.SnapshotRef + if in.OCIArtifact != nil { + in, out := &in.OCIArtifact, &out.OCIArtifact + *out = new(OCIArtifactInfo) + (*in).DeepCopyInto(*out) + } if in.Resource != nil { in, out := &in.Resource, &out.Resource *out = new(ResourceInfo) @@ -1277,107 +1313,6 @@ func (in *ResourceStatus) DeepCopy() *ResourceStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Snapshot) DeepCopyInto(out *Snapshot) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Snapshot. -func (in *Snapshot) DeepCopy() *Snapshot { - if in == nil { - return nil - } - out := new(Snapshot) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Snapshot) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SnapshotList) DeepCopyInto(out *SnapshotList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Snapshot, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SnapshotList. -func (in *SnapshotList) DeepCopy() *SnapshotList { - if in == nil { - return nil - } - out := new(SnapshotList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *SnapshotList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SnapshotSpec) DeepCopyInto(out *SnapshotSpec) { - *out = *in - if in.Blob != nil { - in, out := &in.Blob, &out.Blob - *out = new(BlobInfo) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SnapshotSpec. -func (in *SnapshotSpec) DeepCopy() *SnapshotSpec { - if in == nil { - return nil - } - out := new(SnapshotSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SnapshotStatus) DeepCopyInto(out *SnapshotStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SnapshotStatus. -func (in *SnapshotStatus) DeepCopy() *SnapshotStatus { - if in == nil { - return nil - } - out := new(SnapshotStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TransferStatus) DeepCopyInto(out *TransferStatus) { *out = *in diff --git a/cmd/main.go b/cmd/main.go index bed80e92..04ef5ef7 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -48,8 +48,8 @@ import ( "github.com/open-component-model/ocm-k8s-toolkit/internal/controller/replication" "github.com/open-component-model/ocm-k8s-toolkit/internal/controller/resource" "github.com/open-component-model/ocm-k8s-toolkit/internal/controller/snapshot" + snapshotRegistry "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" "github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm" - snapshotRegistry "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" ) var ( diff --git a/config/crd/bases/delivery.ocm.software_components.yaml b/config/crd/bases/delivery.ocm.software_components.yaml index 5a092f95..a8f02601 100644 --- a/config/crd/bases/delivery.ocm.software_components.yaml +++ b/config/crd/bases/delivery.ocm.software_components.yaml @@ -310,23 +310,45 @@ spec: object. format: int64 type: integer - snapshotRef: + ociArtifact: description: |- - SnapshotRef references the generated snapshot containing a list of + OCIArtifact references the generated OCI artifact containing a list of component descriptors. This list can be used by other controllers to avoid re-downloading (and potentially also re-verifying) the components. properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + blob: + description: Blob + properties: + digest: + description: Digest is the digest of the blob in the form + of ':'. + type: string + size: + description: |- + Size is the number of bytes of the blob. + Can be used to determine how to file should be handled when downloaded (memory/disk) + format: int64 + type: integer + tag: + description: Tag/Version of the blob + type: string + required: + - digest + - size + - tag + type: object + digest: + description: Manifest digest (required to delete the manifest + and prepare OCI artifact for GC) + type: string + repository: + description: OCI repository name type: string + required: + - blob + - digest + - repository type: object - x-kubernetes-map-type: atomic type: object required: - spec diff --git a/config/crd/bases/delivery.ocm.software_configuredresources.yaml b/config/crd/bases/delivery.ocm.software_configuredresources.yaml index 5c7c91d4..4ea1e58b 100644 --- a/config/crd/bases/delivery.ocm.software_configuredresources.yaml +++ b/config/crd/bases/delivery.ocm.software_configuredresources.yaml @@ -158,23 +158,45 @@ spec: observedGeneration: format: int64 type: integer - snapshotRef: + ociArtifact: description: |- - The configuration reconcile loop generates a snapshot, which contains the + The configuration reconcile loop generates an OCI artifact, which contains the ConfiguredResourceSpec.Target ConfigurationReference after configuration. - It is filled once the Snapshot is created and the configuration completed. + It is filled once the OCI artifact is created and the configuration completed. properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + blob: + description: Blob + properties: + digest: + description: Digest is the digest of the blob in the form + of ':'. + type: string + size: + description: |- + Size is the number of bytes of the blob. + Can be used to determine how to file should be handled when downloaded (memory/disk) + format: int64 + type: integer + tag: + description: Tag/Version of the blob + type: string + required: + - digest + - size + - tag + type: object + digest: + description: Manifest digest (required to delete the manifest + and prepare OCI artifact for GC) + type: string + repository: + description: OCI repository name type: string + required: + - blob + - digest + - repository type: object - x-kubernetes-map-type: atomic type: object type: object served: true diff --git a/config/crd/bases/delivery.ocm.software_localizedresources.yaml b/config/crd/bases/delivery.ocm.software_localizedresources.yaml index a3a814a8..bf78941b 100644 --- a/config/crd/bases/delivery.ocm.software_localizedresources.yaml +++ b/config/crd/bases/delivery.ocm.software_localizedresources.yaml @@ -157,7 +157,7 @@ spec: configRef: description: |- ConfigRef is a reference to the Configuration that was generated by the Localization process - and is used to setup the ConfiguredResource responsible for generating the SnapshotRef. + and is used to setup the ConfiguredResource responsible for generating the OCI artifact. properties: name: type: string @@ -169,7 +169,7 @@ spec: configuredResourceRef: description: |- The LocalizedResource reports a ConfiguredResourceRef which contains a reference to the ConfiguredResource - that is responsible for generating the SnapshotRef. + that is responsible for generating the OCI artifact. properties: name: type: string @@ -185,21 +185,44 @@ spec: observedGeneration: format: int64 type: integer - snapshotRef: - description: The LocalizedResource reports an SnapshotRef which contains + ociArtifact: + description: |- + The OCIArtifact contains the information where to find the OCI artifact which contains the content of the Resource after Localization properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + blob: + description: Blob + properties: + digest: + description: Digest is the digest of the blob in the form + of ':'. + type: string + size: + description: |- + Size is the number of bytes of the blob. + Can be used to determine how to file should be handled when downloaded (memory/disk) + format: int64 + type: integer + tag: + description: Tag/Version of the blob + type: string + required: + - digest + - size + - tag + type: object + digest: + description: Manifest digest (required to delete the manifest + and prepare OCI artifact for GC) type: string + repository: + description: OCI repository name + type: string + required: + - blob + - digest + - repository type: object - x-kubernetes-map-type: atomic type: object type: object served: true diff --git a/config/crd/bases/delivery.ocm.software_resources.yaml b/config/crd/bases/delivery.ocm.software_resources.yaml index 877d74e6..a180b58b 100644 --- a/config/crd/bases/delivery.ocm.software_resources.yaml +++ b/config/crd/bases/delivery.ocm.software_resources.yaml @@ -262,6 +262,44 @@ spec: object. format: int64 type: integer + ociArtifact: + description: |- + OCIArtifact points to the OCI artifact which represents the output of the + last successful Resource sync. + properties: + blob: + description: Blob + properties: + digest: + description: Digest is the digest of the blob in the form + of ':'. + type: string + size: + description: |- + Size is the number of bytes of the blob. + Can be used to determine how to file should be handled when downloaded (memory/disk) + format: int64 + type: integer + tag: + description: Tag/Version of the blob + type: string + required: + - digest + - size + - tag + type: object + digest: + description: Manifest digest (required to delete the manifest + and prepare OCI artifact for GC) + type: string + repository: + description: OCI repository name + type: string + required: + - blob + - digest + - repository + type: object resource: properties: access: @@ -284,22 +322,6 @@ spec: - name - type type: object - snapshotRef: - description: |- - SnapshotRef points to the Snapshot which represents the output of the - last successful Resource sync. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic type: object required: - spec diff --git a/config/crd/bases/delivery.ocm.software_snapshots.yaml b/config/crd/bases/delivery.ocm.software_snapshots.yaml index 5afbb696..9b171ca6 100644 --- a/config/crd/bases/delivery.ocm.software_snapshots.yaml +++ b/config/crd/bases/delivery.ocm.software_snapshots.yaml @@ -13,7 +13,7 @@ spec: plural: snapshots shortNames: - snap - singular: snapshot + singular: ociartifact scope: Namespaced versions: - additionalPrinterColumns: diff --git a/go.mod b/go.mod index 09ed6342..336b75b1 100644 --- a/go.mod +++ b/go.mod @@ -29,8 +29,8 @@ require ( k8s.io/apiextensions-apiserver v0.32.2 k8s.io/apimachinery v0.32.2 k8s.io/client-go v0.32.2 - oras.land/oras-go/v2 v2.5.0 ocm.software/ocm v0.20.1 + oras.land/oras-go/v2 v2.5.0 sigs.k8s.io/controller-runtime v0.20.2 sigs.k8s.io/yaml v1.4.0 ) diff --git a/internal/controller/component/component_controller.go b/internal/controller/component/component_controller.go index 3ebe5718..af1b1a71 100644 --- a/internal/controller/component/component_controller.go +++ b/internal/controller/component/component_controller.go @@ -39,23 +39,19 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/yaml" - corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" ocmctx "ocm.software/ocm/api/ocm" ctrl "sigs.k8s.io/controller-runtime" "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" + "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" "github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm" - "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" "github.com/open-component-model/ocm-k8s-toolkit/pkg/status" ) -const SnapshotFinalizer = "snapshot-finalizer" - // Reconciler reconciles a Component object. type Reconciler struct { *ocm.BaseReconciler - Registry snapshot.RegistryType + Registry ociartifact.RegistryType } var _ ocm.Reconciler = (*Reconciler)(nil) @@ -86,7 +82,6 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&v1alpha1.Component{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Watches(&v1alpha1.OCMRepository{}, handler.EnqueueRequestsFromMapFunc(r.findOCMRepositories(ocmRepositoryKey))). - Owns(&v1alpha1.Snapshot{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Complete(r) } @@ -149,34 +144,22 @@ func (r *Reconciler) reconcileWithStatusUpdate(ctx context.Context, component *v func (r *Reconciler) reconcileExists(ctx context.Context, component *v1alpha1.Component) (_ ctrl.Result, retErr error) { logger := log.FromContext(ctx) - //nolint:nestif //nested if blocks required if !component.GetDeletionTimestamp().IsZero() { - logger.Info("component is being deleted and cannot be used", "name", component.Name) - - if component.Status.SnapshotRef.Name != "" { - snap := &v1alpha1.Snapshot{} - snap.SetNamespace(component.GetNamespace()) - snap.SetName(component.Status.SnapshotRef.Name) - err := r.Get(ctx, client.ObjectKeyFromObject(snap), snap) - if err == nil { - err = r.Delete(ctx, snap) - } - if err != nil && !k8serrors.IsNotFound(err) { - return ctrl.Result{}, fmt.Errorf("failed to delete snapshot: %w", err) - } - logger.Info("referenced snapshot deleted", "name", snap.GetName()) + if err := ociartifact.DeleteForObject(ctx, r.Registry, component); err != nil { + return ctrl.Result{}, err } - if updated := controllerutil.RemoveFinalizer(component, SnapshotFinalizer); updated { + if updated := controllerutil.RemoveFinalizer(component, v1alpha1.ArtifactFinalizer); updated { if err := r.Update(ctx, component); err != nil { return ctrl.Result{}, fmt.Errorf("failed to remove finalizer: %w", err) } } + logger.Info("component is being deleted and cannot be used", "name", component.Name) return ctrl.Result{Requeue: true}, nil } - if updated := controllerutil.AddFinalizer(component, SnapshotFinalizer); updated { + if updated := controllerutil.AddFinalizer(component, v1alpha1.ArtifactFinalizer); updated { if err := r.Update(ctx, component); err != nil { return ctrl.Result{}, fmt.Errorf("failed to add finalizer: %w", err) } @@ -325,7 +308,7 @@ func (r *Reconciler) reconcileComponent(ctx context.Context, octx ocmctx.Context // Store descriptors and create snapshot logger.Info("pushing descriptors to storage") - ociRepositoryName, err := snapshot.CreateRepositoryName(component.Spec.RepositoryRef.Name, component.GetName()) + ociRepositoryName, err := ociartifact.CreateRepositoryName(component.Spec.RepositoryRef.Name, component.GetName()) if err != nil { status.MarkNotReady(r.EventRecorder, component, v1alpha1.CreateOCIRepositoryNameFailedReason, err.Error()) @@ -346,47 +329,26 @@ func (r *Reconciler) reconcileComponent(ctx context.Context, octx ocmctx.Context return ctrl.Result{}, err } - manifestDigest, err := ociRepository.PushSnapshot(ctx, version, descriptorsBytes) + manifestDigest, err := ociRepository.PushArtifact(ctx, version, descriptorsBytes) if err != nil { status.MarkNotReady(r.EventRecorder, component, v1alpha1.PushSnapshotFailedReason, err.Error()) return ctrl.Result{}, err } - logger.Info("creating snapshot") - snapshotCR := snapshot.Create( - component, - ociRepositoryName, - manifestDigest.String(), - &v1alpha1.BlobInfo{ + ociArtifact := v1alpha1.OCIArtifactInfo{ + Repository: ociRepositoryName, + Digest: manifestDigest.String(), + Blob: &v1alpha1.BlobInfo{ Digest: digest.FromBytes(descriptorsBytes).String(), Tag: version, Size: int64(len(descriptorsBytes)), }, - ) - snapshotCopy := snapshotCR.DeepCopy() - - result, err := controllerutil.CreateOrUpdate(ctx, r.GetClient(), snapshotCR, func() error { - if err := controllerutil.SetControllerReference(component, snapshotCR, r.GetScheme()); err != nil { - return fmt.Errorf("failed to set controller reference: %w", err) - } - - snapshotCR.Spec = snapshotCopy.Spec - - component.Status.SnapshotRef = corev1.LocalObjectReference{ - Name: snapshotCR.GetName(), - } - - return nil - }) - if err != nil { - status.MarkNotReady(r.EventRecorder, component, v1alpha1.CreateSnapshotFailedReason, err.Error()) - - return ctrl.Result{}, err } - logger.Info(fmt.Sprintf("snapshot %s", result), "operation", result) logger.Info("updating status") + component.Status.OCIArtifact = &ociArtifact + component.Status.Component = v1alpha1.ComponentInfo{ RepositorySpec: repository.Spec.RepositorySpec, Component: component.Spec.Component, diff --git a/internal/controller/component/component_controller_test.go b/internal/controller/component/component_controller_test.go index 193d26a4..3ba87819 100644 --- a/internal/controller/component/component_controller_test.go +++ b/internal/controller/component/component_controller_test.go @@ -46,7 +46,6 @@ import ( "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" "github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm" - snapshotpkg "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" ) const ( @@ -126,9 +125,7 @@ var _ = Describe("Component Controller", func() { Expect(k8sClient.List(ctx, components, client.InNamespace(namespace.GetName()))).To(Succeed()) Expect(components.Items).To(HaveLen(0)) - snapshots := &v1alpha1.SnapshotList{} - Expect(k8sClient.List(ctx, snapshots, client.InNamespace(namespace.GetName()))).To(Succeed()) - Expect(snapshots.Items).To(HaveLen(0)) + // TODO: test if OCI artifact was deleted }) It("reconcileComponent a component", func(ctx SpecContext) { @@ -153,12 +150,10 @@ var _ = Describe("Component Controller", func() { By("checking that the component has been reconciled successfully") waitUntilComponentIsReady(ctx, component, "1.0.0") - - By("checking that the snapshot has been created successfully") - snapshot := validateSnapshot(ctx, component, env, ctfpath) + validateArtifact(ctx, component, env, ctfpath) By("delete resources manually") - deleteComponentWithSnapshot(ctx, component, snapshot) + deleteComponent(ctx, component) }) It("does not reconcile when the repository is not ready", func(ctx SpecContext) { @@ -197,17 +192,7 @@ var _ = Describe("Component Controller", func() { }, "15s").WithContext(ctx).Should(BeTrue()) By("checking that reference to snapshot has not been created") - Expect(component).To(HaveField("Status.SnapshotRef.Name", BeEmpty())) - - By("checking that the snapshot has not been created") - snapshot := &v1alpha1.Snapshot{ - ObjectMeta: metav1.ObjectMeta{ - Name: snapshotpkg.GenerateName(component), - Namespace: component.GetNamespace(), - }, - } - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(snapshot), snapshot) - Expect(errors.IsNotFound(err)).To(BeTrue()) + Expect(component).To(HaveField("Status.OCIArtifact", BeNil())) By("deleting the resources manually") Expect(k8sClient.Delete(ctx, component)).To(Succeed()) @@ -240,7 +225,7 @@ var _ = Describe("Component Controller", func() { By("checking that the component has been reconciled successfully") waitUntilComponentIsReady(ctx, component, Version1) - snapshot := validateSnapshot(ctx, component, env, ctfpath) + validateArtifact(ctx, component, env, ctfpath) By("increasing the component version") env.OCMCommonTransport(ctfpath, accessio.FormatDirectory, func() { @@ -256,10 +241,10 @@ var _ = Describe("Component Controller", func() { waitUntilComponentIsReady(ctx, component, Version2) By("checking that increased version is reflected in the snapshot") - snapshot = validateSnapshot(ctx, component, env, ctfpath) + validateArtifact(ctx, component, env, ctfpath) By("delete resources manually") - deleteComponentWithSnapshot(ctx, component, snapshot) + deleteComponent(ctx, component) }) It("grabs lower version if downgrade is allowed", func(ctx SpecContext) { @@ -297,7 +282,7 @@ var _ = Describe("Component Controller", func() { By("checking that the component has been reconciled successfully") waitUntilComponentIsReady(ctx, component, "0.0.3") - snapshot := validateSnapshot(ctx, component, env, ctfpath) + validateArtifact(ctx, component, env, ctfpath) By("decreasing the component version") component.Spec.Semver = "0.0.2" @@ -305,12 +290,10 @@ var _ = Describe("Component Controller", func() { By("checking that the decreased version has been discovered successfully") waitUntilComponentIsReady(ctx, component, "0.0.2") - - By("checking that decreased version is reflected in the snapshot") - snapshot = validateSnapshot(ctx, component, env, ctfpath) + validateArtifact(ctx, component, env, ctfpath) By("delete resources manually") - deleteComponentWithSnapshot(ctx, component, snapshot) + deleteComponent(ctx, component) }) It("does not grab lower version if downgrade is denied", func(ctx SpecContext) { @@ -347,7 +330,7 @@ var _ = Describe("Component Controller", func() { By("checking that the component has been reconciled successfully") waitUntilComponentIsReady(ctx, component, "0.0.3") - snapshot := validateSnapshot(ctx, component, env, ctfpath) + validateArtifact(ctx, component, env, ctfpath) By("trying to decrease component version") component.Spec.Semver = "0.0.2" @@ -361,12 +344,10 @@ var _ = Describe("Component Controller", func() { return cond.Message == "terminal error: component version cannot be downgraded from version 0.0.3 to version 0.0.2" }).WithTimeout(15 * time.Second).Should(BeTrue()) Expect(component.Status.Component.Version).To(Equal("0.0.3")) - snapshot = &v1alpha1.Snapshot{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, snapshot)).To(Succeed()) - Expect(snapshot.Spec.Blob.Tag).To(Equal("0.0.3")) + Expect(component.Status.OCIArtifact.Blob.Tag).To(Equal("0.0.3")) By("delete resources manually") - deleteComponentWithSnapshot(ctx, component, snapshot) + deleteComponent(ctx, component) }) It("can force downgrade even if not allowed by the component", func(ctx SpecContext) { @@ -400,7 +381,7 @@ var _ = Describe("Component Controller", func() { By("checking that the component has been reconciled successfully") waitUntilComponentIsReady(ctx, component, "0.0.3") - snapshot := validateSnapshot(ctx, component, env, ctfpath) + validateArtifact(ctx, component, env, ctfpath) By("decreasing the component version") component.Spec.Semver = "0.0.2" @@ -408,12 +389,10 @@ var _ = Describe("Component Controller", func() { By("checking that the decreased version has been discovered successfully") waitUntilComponentIsReady(ctx, component, "0.0.2") - - By("checking that decreased version is reflected in the snapshot") - snapshot = validateSnapshot(ctx, component, env, ctfpath) + validateArtifact(ctx, component, env, ctfpath) By("delete resources manually") - deleteComponentWithSnapshot(ctx, component, snapshot) + deleteComponent(ctx, component) }) }) @@ -592,12 +571,8 @@ var _ = Describe("Component Controller", func() { g.Expect(k8sClient.List(ctx, components, client.InNamespace(namespace.GetName()))).To(Succeed()) g.Expect(components.Items).To(HaveLen(0)) }, "15s").WithContext(ctx).Should(Succeed()) - By("ensuring no snapshots are left") - Eventually(func(g Gomega, ctx SpecContext) { - snapshots := &v1alpha1.SnapshotList{} - g.Expect(k8sClient.List(ctx, snapshots, client.InNamespace(namespace.GetName()))).To(Succeed()) - g.Expect(snapshots.Items).To(HaveLen(0)) - }, "15s").WithContext(ctx).Should(Succeed()) + + // TODO: check that the OCI artifact is not in the registry anymore }) It("component resolves and propagates config from repository", func(ctx SpecContext) { @@ -633,7 +608,7 @@ var _ = Describe("Component Controller", func() { By("checking that the component has been reconciled successfully") waitUntilComponentIsReady(ctx, component, "1.0.0") - snapshot := validateSnapshot(ctx, component, env, ctfpath) + validateArtifact(ctx, component, env, ctfpath) By("checking component's effective OCM config") Eventually(komega.Object(component), "15s").Should( @@ -660,7 +635,7 @@ var _ = Describe("Component Controller", func() { ) By("delete resources manually") - deleteComponentWithSnapshot(ctx, component, snapshot) + deleteComponent(ctx, component) }) }) }) @@ -678,58 +653,30 @@ func waitUntilComponentIsReady(ctx context.Context, component *v1alpha1.Componen }, "15s").WithContext(ctx).Should(BeTrue()) } -func validateSnapshot(ctx context.Context, component *v1alpha1.Component, env *Builder, ctfpath string) *v1alpha1.Snapshot { +func validateArtifact(ctx context.Context, component *v1alpha1.Component, env *Builder, ctfPath string) { GinkgoHelper() - By("checking that component has a reference to snapshot") + By("checking that component has a reference to OCI artifact") Eventually(komega.Object(component), "15s").Should( - HaveField("Status.SnapshotRef.Name", Not(BeEmpty()))) - - By("checking that the snapshot resource has been created successfully") - snapshot := &v1alpha1.Snapshot{} - Eventually(func(g Gomega, ctx context.Context) bool { - err := k8sClient.Get(ctx, types.NamespacedName{ - Namespace: component.GetNamespace(), - Name: component.Status.SnapshotRef.Name, - }, snapshot) - if err != nil { - return false - } - g.Expect(snapshot).Should(HaveField("Spec.Digest", Not(BeEmpty()))) - g.Expect(snapshot).Should(HaveField("Spec.Blob", Not(BeNil()))) - - return snapshot.Spec.Digest != "" && snapshot.Spec.Blob != nil - }, "15s").WithContext(ctx).Should(BeTrue()) - - By("validating the snapshot's owner references") - ownersReference := snapshot.GetOwnerReferences() - Expect(len(ownersReference)).To(Equal(1), "expected only one ownersReference") - Expect(ownersReference[0].Name).To(Equal(component.GetName()), "expected to be a ownersReference of the component") - - By("checking that the snapshot contains the correct content") - snapshotRepository := Must(registry.NewRepository(ctx, snapshot.Spec.Repository)) - snapshotComponentContent := Must(snapshotRepository.FetchSnapshot(ctx, snapshot.GetDigest())) - - 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.Status.Component.Component, component.Status.Component.Version)) - expectedDescriptors := Must(ocm.ListComponentDescriptors(ctx, cv, repo)) - Expect(snapshotDescriptors).To(YAMLEqual(expectedDescriptors)) - - return snapshot + HaveField("Status.OCIArtifact", Not(BeNil()))) + + By("checking that the OCI artifact contains the correct content") + ociRepo := Must(registry.NewRepository(ctx, component.GetOCIRepository())) + componentContent := Must(ociRepo.FetchArtifact(ctx, component.GetManifestDigest())) + + descriptors := &ocm.Descriptors{} + MustBeSuccessful(yaml.Unmarshal(componentContent, descriptors)) + ctfRepo := Must(ctf.Open(env, accessobj.ACC_WRITABLE, ctfPath, vfs.FileMode(vfs.O_RDWR), env)) + cv := Must(ctfRepo.LookupComponentVersion(component.Status.Component.Component, component.Status.Component.Version)) + expectedDescriptors := Must(ocm.ListComponentDescriptors(ctx, cv, ctfRepo)) + Expect(descriptors).To(YAMLEqual(expectedDescriptors)) } -func deleteComponentWithSnapshot(ctx context.Context, component *v1alpha1.Component, snapshot *v1alpha1.Snapshot) { +func deleteComponent(ctx context.Context, component *v1alpha1.Component) { GinkgoHelper() Expect(k8sClient.Delete(ctx, component)).To(Succeed()) - Eventually(func(ctx context.Context) bool { - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(snapshot), snapshot) - return errors.IsNotFound(err) - }, "15s").WithContext(ctx).Should(BeTrue()) - // Snapshot deletion was triggered by the Component reconciler Eventually(func(ctx context.Context) bool { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(component), component) return errors.IsNotFound(err) diff --git a/internal/controller/component/suite_test.go b/internal/controller/component/suite_test.go index f63934b4..37d2147c 100644 --- a/internal/controller/component/suite_test.go +++ b/internal/controller/component/suite_test.go @@ -37,8 +37,8 @@ import ( metricserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" + snapshotPkg "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" "github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm" - snapshotPkg "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" "github.com/open-component-model/ocm-k8s-toolkit/pkg/test" ) diff --git a/internal/controller/configuration/client/client.go b/internal/controller/configuration/client/client.go index eddec93d..3a5187a9 100644 --- a/internal/controller/configuration/client/client.go +++ b/internal/controller/configuration/client/client.go @@ -10,7 +10,7 @@ import ( "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" "github.com/open-component-model/ocm-k8s-toolkit/internal/controller/configuration/types" - snapshotRegistry "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" + snapshotRegistry "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" ) type Client interface { diff --git a/internal/controller/configuration/configuration_controller.go b/internal/controller/configuration/configuration_controller.go index c302d56b..9138101e 100644 --- a/internal/controller/configuration/configuration_controller.go +++ b/internal/controller/configuration/configuration_controller.go @@ -36,8 +36,8 @@ import ( configurationclient "github.com/open-component-model/ocm-k8s-toolkit/internal/controller/configuration/client" "github.com/open-component-model/ocm-k8s-toolkit/pkg/compression" "github.com/open-component-model/ocm-k8s-toolkit/pkg/index" + snapshotRegistry "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" "github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm" - snapshotRegistry "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" "github.com/open-component-model/ocm-k8s-toolkit/pkg/status" ) @@ -218,7 +218,7 @@ func (r *Reconciler) reconcileExists(ctx context.Context, configuration *v1alpha // - HelmRelease (FluxCD) requires the OCI artifact to have the same tag as the helm chart itself // - But how to get the helm chart version? (User input, parse from content) tag := "dummy" - manifestDigest, err := repository.PushSnapshot(ctx, tag, dataTGZ) + manifestDigest, err := repository.PushArtifact(ctx, tag, dataTGZ) if err != nil { status.MarkNotReady(r.EventRecorder, configuration, v1alpha1.ConfigurationFailedReason, err.Error()) diff --git a/internal/controller/configuration/configuration_controller_test.go b/internal/controller/configuration/configuration_controller_test.go index 3d863d44..62349fbe 100644 --- a/internal/controller/configuration/configuration_controller_test.go +++ b/internal/controller/configuration/configuration_controller_test.go @@ -159,7 +159,7 @@ var _ = Describe("ConfiguredResource Controller", func() { snapshotRepository, err := registry.NewRepository(ctx, snapshotResource.Spec.Repository) Expect(err).NotTo(HaveOccurred()) - snapshotResourceContent, err := snapshotRepository.FetchSnapshot(ctx, snapshotResource.GetDigest()) + snapshotResourceContent, err := snapshotRepository.FetchArtifact(ctx, snapshotResource.GetDigest()) Expect(err).NotTo(HaveOccurred()) dataExtracted, err := compression.ExtractDataFromTGZ(snapshotResourceContent) Expect(err).NotTo(HaveOccurred()) diff --git a/internal/controller/configuration/configure.go b/internal/controller/configuration/configure.go index 667da711..e4febe66 100644 --- a/internal/controller/configuration/configure.go +++ b/internal/controller/configuration/configure.go @@ -18,7 +18,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" - "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" + "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" "github.com/open-component-model/ocm-k8s-toolkit/pkg/substitute" "github.com/open-component-model/ocm-k8s-toolkit/pkg/substitute/steps" "github.com/open-component-model/ocm-k8s-toolkit/pkg/util" @@ -55,7 +55,7 @@ func Configure(ctx context.Context, ) (string, error) { logger := log.FromContext(ctx) targetDir := filepath.Join(basePath, "target") - if err := target.UnpackIntoDirectory(targetDir); errors.Is(err, snapshot.ErrAlreadyUnpacked) { + if err := target.UnpackIntoDirectory(targetDir); errors.Is(err, ociartifact.ErrAlreadyUnpacked) { logger.Info("target was already present, reusing existing directory", "path", targetDir) } else if err != nil { return "", fmt.Errorf("failed to get target directory: %w", err) diff --git a/internal/controller/configuration/suite_test.go b/internal/controller/configuration/suite_test.go index 05825b14..9b133cb9 100644 --- a/internal/controller/configuration/suite_test.go +++ b/internal/controller/configuration/suite_test.go @@ -39,8 +39,8 @@ import ( "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" cfgclient "github.com/open-component-model/ocm-k8s-toolkit/internal/controller/configuration/client" + "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" "github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm" - "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" "github.com/open-component-model/ocm-k8s-toolkit/pkg/test" ) @@ -55,7 +55,7 @@ var k8sManager ctrl.Manager var testEnv *envtest.Environment var recorder record.EventRecorder var zotCmd *exec.Cmd -var registry *snapshot.Registry +var registry *ociartifact.Registry var zotRootDir string func TestControllers(t *testing.T) { diff --git a/internal/controller/configuration/types/configuration_reference.go b/internal/controller/configuration/types/configuration_reference.go index 42b5f8bb..f0222e94 100644 --- a/internal/controller/configuration/types/configuration_reference.go +++ b/internal/controller/configuration/types/configuration_reference.go @@ -1,7 +1,7 @@ package types import ( - "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" + "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" ) // ConfigurationReference can be used both as a source (ConfigurationSource), @@ -12,12 +12,12 @@ type ConfigurationReference interface { } // ConfigurationSource is a source of localization. -// It contains instructions on how to localize an snapshot.Content. +// It contains instructions on how to localize an ociartifact.Content. type ConfigurationSource interface { - snapshot.Content + ociartifact.Content } // ConfigurationTarget is a target for configuration. type ConfigurationTarget interface { - snapshot.Content + ociartifact.Content } diff --git a/internal/controller/localization/client/client.go b/internal/controller/localization/client/client.go index 4d36f318..a3b831af 100644 --- a/internal/controller/localization/client/client.go +++ b/internal/controller/localization/client/client.go @@ -10,7 +10,7 @@ import ( "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" "github.com/open-component-model/ocm-k8s-toolkit/internal/controller/localization/types" - "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" + "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" ) type Client interface { @@ -26,7 +26,7 @@ type Client interface { GetLocalizationConfig(ctx context.Context, ref v1alpha1.ConfigurationReference) (source types.LocalizationConfig, err error) } -func NewClientWithRegistry(c client.Client, registry *snapshot.Registry, scheme *runtime.Scheme) Client { +func NewClientWithRegistry(c client.Client, registry *ociartifact.Registry, scheme *runtime.Scheme) Client { factory := serializer.NewCodecFactory(scheme) info, _ := runtime.SerializerInfoForMediaType(factory.SupportedMediaTypes(), runtime.ContentTypeYAML) encoder := factory.EncoderForVersion(info.Serializer, v1alpha1.GroupVersion) @@ -41,7 +41,7 @@ func NewClientWithRegistry(c client.Client, registry *snapshot.Registry, scheme type localStorageBackedClient struct { client.Client - Registry *snapshot.Registry + Registry *ociartifact.Registry scheme *runtime.Scheme encoder runtime.Encoder } @@ -62,7 +62,7 @@ func (clnt *localStorageBackedClient) GetLocalizationTarget( case v1alpha1.KindLocalizedResource: fallthrough case v1alpha1.KindResource: - return snapshot.GetContentBackedBySnapshotFromComponent(ctx, clnt.Client, clnt.Registry, &ref) + return ociartifact.GetContentBackedBySnapshotFromComponent(ctx, clnt.Client, clnt.Registry, &ref) default: return nil, fmt.Errorf("unsupported localization target kind: %s", ref.Kind) } @@ -74,7 +74,7 @@ func (clnt *localStorageBackedClient) GetLocalizationConfig( ) (types.LocalizationConfig, error) { switch ref.Kind { case v1alpha1.KindResource: - return snapshot.GetContentBackedBySnapshotFromComponent(ctx, clnt.Client, clnt.Registry, &ref) + return ociartifact.GetContentBackedBySnapshotFromComponent(ctx, clnt.Client, clnt.Registry, &ref) case v1alpha1.KindLocalizationConfig: return GetLocalizationConfigFromKubernetes(ctx, clnt.Client, clnt.encoder, ref) default: @@ -98,5 +98,5 @@ func GetLocalizationConfigFromKubernetes(ctx context.Context, clnt client.Reader return nil, fmt.Errorf("failed to fetch localization config %s: %w", reference.Name, err) } - return &snapshot.ObjectConfig{Object: &cfg, Encoder: encoder}, nil + return &ociartifact.ObjectConfig{Object: &cfg, Encoder: encoder}, nil } diff --git a/internal/controller/localization/localization_controller.go b/internal/controller/localization/localization_controller.go index 0967f38c..231a7805 100644 --- a/internal/controller/localization/localization_controller.go +++ b/internal/controller/localization/localization_controller.go @@ -32,8 +32,8 @@ import ( localizationclient "github.com/open-component-model/ocm-k8s-toolkit/internal/controller/localization/client" "github.com/open-component-model/ocm-k8s-toolkit/internal/controller/localization/types" "github.com/open-component-model/ocm-k8s-toolkit/pkg/index" + "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" "github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm" - "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" "github.com/open-component-model/ocm-k8s-toolkit/pkg/status" "github.com/open-component-model/ocm-k8s-toolkit/pkg/util" ) @@ -42,7 +42,7 @@ import ( type Reconciler struct { *ocm.BaseReconciler LocalizationClient localizationclient.Client - Registry snapshot.RegistryType + Registry ociartifact.RegistryType } var _ ocm.Reconciler = (*Reconciler)(nil) @@ -274,7 +274,7 @@ func (r *Reconciler) reconcileExists(ctx context.Context, localization *v1alpha1 func localizeRules( ctx context.Context, c client.Client, - r snapshot.RegistryType, + r ociartifact.RegistryType, content LocalizableSnapshotContent, cfg types.LocalizationConfig, ) ( @@ -336,7 +336,7 @@ func localizeRules( // LocalizableSnapshotContent is an artifact content that is backed by a component and resource, allowing it // to be localized (by resolving relative references from the resource & component into absolute values). type LocalizableSnapshotContent interface { - snapshot.Content + ociartifact.Content GetComponent() *v1alpha1.Component GetResource() *v1alpha1.Resource } @@ -344,7 +344,7 @@ type LocalizableSnapshotContent interface { func ComponentDescriptorAndSetFromResource( ctx context.Context, reader client.Reader, - registry snapshot.RegistryType, + registry ociartifact.RegistryType, baseComponent *v1alpha1.Component, ) (compdesc.ComponentVersionResolver, *compdesc.ComponentDescriptor, error) { snapshotResource := &v1alpha1.Snapshot{} diff --git a/internal/controller/localization/localization_controller_test.go b/internal/controller/localization/localization_controller_test.go index a2c863a2..8333111d 100644 --- a/internal/controller/localization/localization_controller_test.go +++ b/internal/controller/localization/localization_controller_test.go @@ -140,7 +140,7 @@ var _ = Describe("Localization Controller", func() { repository, err := registry.NewRepository(ctx, snapshotLocalization.Spec.Repository) Expect(err).ToNot(HaveOccurred()) - data, err := repository.FetchSnapshot(ctx, snapshotLocalization.GetDigest()) + data, err := repository.FetchArtifact(ctx, snapshotLocalization.GetDigest()) Expect(err).ToNot(HaveOccurred()) memFs := vfs.New(memoryfs.New()) diff --git a/internal/controller/localization/suite_test.go b/internal/controller/localization/suite_test.go index 070d6e7a..1e71fdeb 100644 --- a/internal/controller/localization/suite_test.go +++ b/internal/controller/localization/suite_test.go @@ -43,8 +43,8 @@ import ( "github.com/open-component-model/ocm-k8s-toolkit/internal/controller/configuration" cfgclient "github.com/open-component-model/ocm-k8s-toolkit/internal/controller/configuration/client" locclient "github.com/open-component-model/ocm-k8s-toolkit/internal/controller/localization/client" + "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" "github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm" - "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" "github.com/open-component-model/ocm-k8s-toolkit/pkg/test" ) @@ -59,7 +59,7 @@ var k8sManager ctrl.Manager var testEnv *envtest.Environment var recorder record.EventRecorder var zotCmd *exec.Cmd -var registry *snapshot.Registry +var registry *ociartifact.Registry var zotRootDir string var ctx context.Context var cancel context.CancelFunc diff --git a/internal/controller/localization/types/localization_reference.go b/internal/controller/localization/types/localization_reference.go index 61764d19..997951d0 100644 --- a/internal/controller/localization/types/localization_reference.go +++ b/internal/controller/localization/types/localization_reference.go @@ -1,7 +1,7 @@ package types import ( - "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" + "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" ) // LocalizationReference can be used both as a source (LocalizationConfig), @@ -11,12 +11,12 @@ type LocalizationReference interface { LocalizationTarget } -// LocalizationConfig is a configuration on how to localize an snapshot.Content. +// LocalizationConfig is a configuration on how to localize an ociartifact.Content. type LocalizationConfig interface { - snapshot.Content + ociartifact.Content } -// LocalizationTarget is a target snapshot.Content for localization. +// LocalizationTarget is a target ociartifact.Content for localization. type LocalizationTarget interface { - snapshot.Content + ociartifact.Content } diff --git a/internal/controller/resource/resource_controller.go b/internal/controller/resource/resource_controller.go index 5aeb8971..4784aea0 100644 --- a/internal/controller/resource/resource_controller.go +++ b/internal/controller/resource/resource_controller.go @@ -17,6 +17,7 @@ limitations under the License. package resource import ( + "bytes" "context" "encoding/json" "errors" @@ -26,6 +27,7 @@ import ( "github.com/fluxcd/pkg/runtime/patch" "github.com/opencontainers/go-digest" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/yaml" "ocm.software/ocm/api/datacontext" "ocm.software/ocm/api/ocm/compdesc" "ocm.software/ocm/api/ocm/resolvers" @@ -40,7 +42,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ocmctx "ocm.software/ocm/api/ocm" v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" @@ -48,14 +49,14 @@ import ( "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" "github.com/open-component-model/ocm-k8s-toolkit/pkg/compression" + "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" "github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm" - "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" "github.com/open-component-model/ocm-k8s-toolkit/pkg/status" ) type Reconciler struct { *ocm.BaseReconciler - Registry snapshot.RegistryType + Registry ociartifact.RegistryType } var _ ocm.Reconciler = (*Reconciler)(nil) @@ -76,8 +77,6 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&v1alpha1.Resource{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). - // Watch for snapshot-events that are owned by the resource controller - Owns(&v1alpha1.Snapshot{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). // Watch for component-events that are referenced by resources Watches( &v1alpha1.Component{}, @@ -143,7 +142,24 @@ func (r *Reconciler) reconcileExists(ctx context.Context, resource *v1alpha1.Res } if resource.GetDeletionTimestamp() != nil { + if err := ociartifact.DeleteForObject(ctx, r.Registry, resource); err != nil { + return ctrl.Result{}, err + } + + if updated := controllerutil.RemoveFinalizer(resource, v1alpha1.ArtifactFinalizer); updated { + if err := r.Update(ctx, resource); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to remove finalizer: %w", err) + } + } + logger.Info("resource is being deleted and cannot be used", "name", resource.Name) + return ctrl.Result{Requeue: true}, nil + } + + if updated := controllerutil.AddFinalizer(resource, v1alpha1.ArtifactFinalizer); updated { + if err := r.Update(ctx, resource); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to add finalizer: %w", err) + } return ctrl.Result{Requeue: true}, nil } @@ -218,36 +234,31 @@ func (r *Reconciler) reconcileResource(ctx context.Context, octx ocmctx.Context, return ctrl.Result{}, err } - // Get snapshot from component that contains component descriptor - componentSnapshot := &v1alpha1.Snapshot{} - if err := r.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, componentSnapshot); err != nil { - status.MarkNotReady(r.GetEventRecorder(), resource, v1alpha1.GetSnapshotFailedReason, err.Error()) + // Create repository to download the component descriptors + repositoryCD, err := r.Registry.NewRepository(ctx, component.GetOCIRepository()) + if err != nil { + status.MarkNotReady(r.GetEventRecorder(), resource, v1alpha1.CreateOCIRepositoryFailedReason, err.Error()) return ctrl.Result{}, err } - if !conditions.IsReady(componentSnapshot) { - status.MarkNotReady(r.GetEventRecorder(), resource, v1alpha1.SnapshotReadyFailedReason, "snapshot not ready") - - return ctrl.Result{}, errors.New("snapshot not ready") - } - - // Create repository from registry for snapshot - repositoryCD, err := r.Registry.NewRepository(ctx, componentSnapshot.Spec.Repository) + // Get component descriptor set from artifact + data, err := repositoryCD.FetchArtifact(ctx, component.GetManifestDigest()) if err != nil { - status.MarkNotReady(r.GetEventRecorder(), resource, v1alpha1.CreateOCIRepositoryFailedReason, err.Error()) + status.MarkNotReady(r.GetEventRecorder(), resource, v1alpha1.FetchOCIArtifactFailedReason, err.Error()) return ctrl.Result{}, err } - // Get component descriptor set from artifact - cdSet, err := ocm.GetComponentSetForSnapshot(ctx, repositoryCD, componentSnapshot) - if err != nil { - status.MarkNotReady(r.EventRecorder, resource, v1alpha1.GetComponentForSnapshotFailedReason, err.Error()) + cds := &ocm.Descriptors{} + if err := yaml.NewYAMLToJSONDecoder(bytes.NewReader(data)).Decode(cds); err != nil { + status.MarkNotReady(r.GetEventRecorder(), resource, v1alpha1.YamlToJsonDecodeFailedReason, err.Error()) return ctrl.Result{}, err } + cdSet := compdesc.NewComponentVersionSet(cds.List...) + // Get referenced component descriptor from component descriptor set cd, err := cdSet.LookupComponentVersion(component.Status.Component.Component, component.Status.Component.Version) if err != nil { @@ -336,7 +347,7 @@ func (r *Reconciler) reconcileResource(ctx context.Context, octx ocmctx.Context, if resourceAccessSpec.GetType() == "ociArtifact" { manifestDigest, err = repositoryResource.CopyOCIArtifactForResourceAccess(ctx, resourceAccess) if err != nil { - status.MarkNotReady(r.EventRecorder, resource, v1alpha1.PushSnapshotFailedReason, err.Error()) + status.MarkNotReady(r.EventRecorder, resource, v1alpha1.CopyOCIArtifactFailedReason, err.Error()) return ctrl.Result{}, err } @@ -370,9 +381,9 @@ func (r *Reconciler) reconcileResource(ctx context.Context, octx ocmctx.Context, return ctrl.Result{}, fmt.Errorf("failed to auto compress data: %w", err) } - manifestDigest, err = repositoryResource.PushSnapshot(ctx, resourceAccess.Meta().GetVersion(), resourceContentCompressed) + manifestDigest, err = repositoryResource.PushArtifact(ctx, resourceAccess.Meta().GetVersion(), resourceContentCompressed) if err != nil { - status.MarkNotReady(r.GetEventRecorder(), resource, v1alpha1.PushSnapshotFailedReason, err.Error()) + status.MarkNotReady(r.GetEventRecorder(), resource, v1alpha1.PushOCIArtifactFailedReason, err.Error()) return ctrl.Result{}, fmt.Errorf("failed to push snapshot: %w", err) } @@ -380,38 +391,16 @@ func (r *Reconciler) reconcileResource(ctx context.Context, octx ocmctx.Context, blobSize = int64(len(resourceContentCompressed)) } - // Create respective snapshot CR - snapshotCR := snapshot.Create( - resource, - repositoryResourceName, - manifestDigest.String(), - &v1alpha1.BlobInfo{ + // Update status + if err = setResourceStatus(ctx, configs, resource, resourceAccess, &v1alpha1.OCIArtifactInfo{ + Repository: repositoryResourceName, + Digest: manifestDigest.String(), + Blob: &v1alpha1.BlobInfo{ Digest: resourceAccess.Meta().Digest.Value, - Tag: resourceAccess.Meta().GetVersion(), + Tag: resourceAccess.Meta().Version, Size: blobSize, }, - ) - - if _, err = controllerutil.CreateOrUpdate(ctx, r.GetClient(), snapshotCR, func() error { - if snapshotCR.ObjectMeta.CreationTimestamp.IsZero() { - if err := controllerutil.SetControllerReference(resource, snapshotCR, r.GetScheme()); err != nil { - return fmt.Errorf("failed to set controller reference: %w", err) - } - } - - resource.Status.SnapshotRef = corev1.LocalObjectReference{ - Name: snapshotCR.GetName(), - } - - return nil }); err != nil { - status.MarkNotReady(r.EventRecorder, resource, v1alpha1.CreateSnapshotFailedReason, err.Error()) - - return ctrl.Result{}, err - } - - // Update status - if err = setResourceStatus(ctx, configs, resource, resourceAccess); err != nil { status.MarkNotReady(r.EventRecorder, resource, v1alpha1.StatusSetFailedReason, err.Error()) return ctrl.Result{}, fmt.Errorf("failed to set resource status: %w", err) @@ -527,7 +516,7 @@ func verifyResource(ctx context.Context, access ocmctx.ResourceAccess, cv ocmctx } // setResourceStatus updates the resource status with the all required information. -func setResourceStatus(ctx context.Context, configs []v1alpha1.OCMConfiguration, resource *v1alpha1.Resource, resourceAccess ocmctx.ResourceAccess) error { +func setResourceStatus(ctx context.Context, configs []v1alpha1.OCMConfiguration, resource *v1alpha1.Resource, resourceAccess ocmctx.ResourceAccess, ociArtifact *v1alpha1.OCIArtifactInfo) error { log.FromContext(ctx).V(1).Info("updating resource status") // Get the access spec from the resource access @@ -552,5 +541,7 @@ func setResourceStatus(ctx context.Context, configs []v1alpha1.OCMConfiguration, resource.Status.EffectiveOCMConfig = configs + resource.Status.OCIArtifact = ociArtifact + return nil } diff --git a/internal/controller/resource/resource_controller_test.go b/internal/controller/resource/resource_controller_test.go index d9b10f40..9ce8731d 100644 --- a/internal/controller/resource/resource_controller_test.go +++ b/internal/controller/resource/resource_controller_test.go @@ -17,12 +17,9 @@ limitations under the License. package resource import ( - "bytes" - "compress/gzip" "context" _ "embed" "fmt" - "io" "os" "github.com/fluxcd/pkg/runtime/conditions" @@ -30,7 +27,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" . "ocm.software/ocm/api/helper/builder" "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" "ocm.software/ocm/api/utils/mime" @@ -79,368 +75,323 @@ var _ = Describe("Resource Controller", func() { testNumber int ) - BeforeEach(func() { - resourceLocalPath = Must(os.MkdirTemp("", CTFPath)) - DeferCleanup(func() error { - return os.RemoveAll(resourceLocalPath) + Context("component controller", func() { + var componentObj *v1alpha1.Component + var namespace *corev1.Namespace + + BeforeEach(func() { + resourceLocalPath = Must(os.MkdirTemp("", CTFPath)) + DeferCleanup(func() error { + return os.RemoveAll(resourceLocalPath) + }) + + env = NewBuilder(environment.FileSystem(osfs.OsFs)) + DeferCleanup(env.Cleanup) + testNumber++ }) - env = NewBuilder(environment.FileSystem(osfs.OsFs)) - DeferCleanup(env.Cleanup) - testNumber++ - }) + AfterEach(func(ctx SpecContext) { + By("deleting the component") + Expect(k8sClient.Delete(ctx, componentObj)).To(Succeed()) + Eventually(func(ctx context.Context) bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(componentObj), componentObj) + return errors.IsNotFound(err) + }).WithContext(ctx).Should(BeTrue()) + + resources := &v1alpha1.ResourceList{} + Expect(k8sClient.List(ctx, resources, client.InNamespace(namespace.GetName()))).To(Succeed()) + Expect(resources.Items).To(HaveLen(0)) - Context("resource controller", func() { - It("can reconcile a resource: PlainText", func() { - testComponent := fmt.Sprintf("%s-%d", ComponentObj, testNumber) - testResource := fmt.Sprintf("%s-%d", ResourceObj, testNumber) - resourceType := artifacttypes.PLAIN_TEXT - - By("creating an ocm resource from a plain text") - env.OCMCommonTransport(resourceLocalPath, accessio.FormatDirectory, func() { - env.Component(Component, func() { - env.Version(ComponentVersion, func() { - env.Resource(testResource, ResourceVersion, resourceType, v1.LocalRelation, func() { - env.BlobData(mime.MIME_TEXT, []byte(ResourceContent)) + // TODO: test if OCI artifact was deleted + }) + + Context("resource controller", func() { + It("can reconcile a resource: PlainText", func() { + testComponent := fmt.Sprintf("%s-%d", ComponentObj, testNumber) + testResource := fmt.Sprintf("%s-%d", ResourceObj, testNumber) + resourceType := artifacttypes.PLAIN_TEXT + + By("creating an ocm resource from a plain text") + env.OCMCommonTransport(resourceLocalPath, accessio.FormatDirectory, func() { + env.Component(Component, func() { + env.Version(ComponentVersion, func() { + env.Resource(testResource, ResourceVersion, resourceType, v1.LocalRelation, func() { + env.BlobData(mime.MIME_TEXT, []byte(ResourceContent)) + }) }) }) }) - }) - repo, err := ctf.Open(env, accessobj.ACC_WRITABLE, resourceLocalPath, vfs.FileMode(vfs.O_RDWR), env) - Expect(err).NotTo(HaveOccurred()) - cv, err := repo.LookupComponentVersion(Component, ComponentVersion) - Expect(err).NotTo(HaveOccurred()) - cd, err := ocmPkg.ListComponentDescriptors(ctx, cv, repo) - Expect(err).NotTo(HaveOccurred()) - dataCds, err := yaml.Marshal(cd) - Expect(err).NotTo(HaveOccurred()) - - spec, err := ctf.NewRepositorySpec(ctf.ACC_READONLY, resourceLocalPath) - specData, err := spec.MarshalJSON() - - By("creating a mocked component") - component := test.SetupComponentWithDescriptorList(ctx, testComponent, Namespace, dataCds, &test.MockComponentOptions{ - Registry: registry, - Client: k8sClient, - Recorder: recorder, - Info: v1alpha1.ComponentInfo{ - Component: Component, - Version: ComponentVersion, - RepositorySpec: &apiextensionsv1.JSON{Raw: specData}, - }, - Repository: RepositoryObj, - }) + repo, err := ctf.Open(env, accessobj.ACC_WRITABLE, resourceLocalPath, vfs.FileMode(vfs.O_RDWR), env) + Expect(err).NotTo(HaveOccurred()) + cv, err := repo.LookupComponentVersion(Component, ComponentVersion) + Expect(err).NotTo(HaveOccurred()) + cd, err := ocmPkg.ListComponentDescriptors(ctx, cv, repo) + Expect(err).NotTo(HaveOccurred()) + dataCds, err := yaml.Marshal(cd) + Expect(err).NotTo(HaveOccurred()) + + spec, err := ctf.NewRepositorySpec(ctf.ACC_READONLY, resourceLocalPath) + specData, err := spec.MarshalJSON() + + By("creating a mocked component") + componentObj = test.SetupComponentWithDescriptorList(ctx, testComponent, Namespace, dataCds, &test.MockComponentOptions{ + Registry: registry, + Client: k8sClient, + Recorder: recorder, + Info: v1alpha1.ComponentInfo{ + Component: Component, + Version: ComponentVersion, + RepositorySpec: &apiextensionsv1.JSON{Raw: specData}, + }, + Repository: RepositoryObj, + }) - By("creating a resource object") - resource := &v1alpha1.Resource{ - ObjectMeta: k8smetav1.ObjectMeta{ - Namespace: Namespace, - Name: testResource, - }, - Spec: v1alpha1.ResourceSpec{ - ComponentRef: corev1.LocalObjectReference{ - Name: testComponent, + By("creating a resource object") + resource := &v1alpha1.Resource{ + ObjectMeta: k8smetav1.ObjectMeta{ + Namespace: Namespace, + Name: testResource, }, - Resource: v1alpha1.ResourceID{ - ByReference: v1alpha1.ResourceReference{ - Resource: v1.NewIdentity(testResource), + Spec: v1alpha1.ResourceSpec{ + ComponentRef: corev1.LocalObjectReference{ + Name: testComponent, + }, + Resource: v1alpha1.ResourceID{ + ByReference: v1alpha1.ResourceReference{ + Resource: v1.NewIdentity(testResource), + }, }, }, - }, - } - Expect(k8sClient.Create(ctx, resource)).To(Succeed()) - - By("checking that the resource has been reconciled successfully") - Eventually(func(ctx context.Context) bool { - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(resource), resource) - if err != nil { - return false } - return conditions.IsReady(resource) - }, "15s").WithContext(ctx).Should(BeTrue()) - - By("checking that the snapshot has been created successfully") - Eventually(komega.Object(resource), "15s").Should( - HaveField("Status.SnapshotRef.Name", Not(BeEmpty()))) - snapshotResource := &v1alpha1.Snapshot{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: resource.GetNamespace(), Name: resource.GetSnapshotName()}, snapshotResource)).To(Succeed()) - - Expect(resource).To(HaveField("Status.Resource.Name", Equal(testResource))) - Expect(resource).To(HaveField("Status.Resource.Type", Equal(resourceType))) - Expect(resource).To(HaveField("Status.Resource.Version", Equal(ResourceVersion))) - - By("checking that the snapshot contains the correct content") - snapshotRepository, err := registry.NewRepository(ctx, snapshotResource.Spec.Repository) - Expect(err).NotTo(HaveOccurred()) - snapshotResourceContentCompressed, err := snapshotRepository.FetchSnapshot(ctx, snapshotResource.GetDigest()) - Expect(err).NotTo(HaveOccurred()) - gzipReader, err := gzip.NewReader(bytes.NewReader(snapshotResourceContentCompressed)) - Expect(err).NotTo(HaveOccurred()) - snapshotResourceContent, err := io.ReadAll(gzipReader) - Expect(err).NotTo(HaveOccurred()) - Expect(string(snapshotResourceContent)).To(Equal(ResourceContent)) - - // Compare other fields - resourceAcc, err := cv.GetResource(v1.NewIdentity(testResource)) - Expect(err).NotTo(HaveOccurred()) - - Expect(snapshotResource.Name).To(Equal(fmt.Sprintf("resource-%s", testResource))) - Expect(snapshotResource.Spec.Blob.Digest).To(Equal(resourceAcc.Meta().Digest.Value)) - Expect(snapshotResource.Spec.Blob.Tag).To(Equal(ResourceVersion)) - Expect(snapshotResource.Spec.Blob.Size).To(Equal(int64(len(snapshotResourceContentCompressed)))) - - By("delete resources manually") - Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) - Expect(k8sClient.Delete(ctx, snapshotResource)).To(Succeed()) - Eventually(func(ctx context.Context) bool { - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(resource), resource) - return errors.IsNotFound(err) - }, "15s").WithContext(ctx).Should(BeTrue()) - - Expect(k8sClient.Delete(ctx, component)).To(Succeed()) - snapshotComponent := &v1alpha1.Snapshot{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, snapshotComponent)).To(Succeed()) - Expect(k8sClient.Delete(ctx, snapshotComponent)).To(Succeed()) - }) + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + + By("checking that the resource has been reconciled successfully") + Eventually(func(ctx context.Context) bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(resource), resource) + if err != nil { + return false + } + return conditions.IsReady(resource) + }, "15s").WithContext(ctx).Should(BeTrue()) + Expect(resource).To(HaveField("Status.Resource.Name", Equal(testResource))) + Expect(resource).To(HaveField("Status.Resource.Type", Equal(resourceType))) + Expect(resource).To(HaveField("Status.Resource.Version", Equal(ResourceVersion))) + + resourceAcc, err := cv.GetResource(v1.NewIdentity(testResource)) + Expect(err).NotTo(HaveOccurred()) + validateArtifact(ctx, resource, resourceAcc.Meta().Digest.Value) + + By("delete resource manually") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + Eventually(func(ctx context.Context) bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(resource), resource) + return errors.IsNotFound(err) + }, "15s").WithContext(ctx).Should(BeTrue()) + }) - It("can reconcile a resource: Compressed PlainText", func() { - testComponent := fmt.Sprintf("%s-%d", ComponentObj, testNumber) - testResource := fmt.Sprintf("%s-%d", ResourceObj, testNumber) - resourceType := artifacttypes.PLAIN_TEXT - - // The resource controller will only gzip-compress the content, if it is not already compressed. Thus, we - // expect the content to be only compressed once. - resourceContentCompressed, err := compression.AutoCompressAsGzip(ctx, []byte(ResourceContent)) - Expect(err).NotTo(HaveOccurred()) - - By("creating an ocm resource from a plain text") - env.OCMCommonTransport(resourceLocalPath, accessio.FormatDirectory, func() { - env.Component(Component, func() { - env.Version(ComponentVersion, func() { - env.Resource(testResource, ResourceVersion, resourceType, v1.LocalRelation, func() { - env.BlobData(mime.MIME_TEXT, resourceContentCompressed) + It("can reconcile a resource: Compressed PlainText", func() { + testComponent := fmt.Sprintf("%s-%d", ComponentObj, testNumber) + testResource := fmt.Sprintf("%s-%d", ResourceObj, testNumber) + resourceType := artifacttypes.PLAIN_TEXT + + // The resource controller will only gzip-compress the content, if it is not already compressed. Thus, we + // expect the content to be only compressed once. + resourceContentCompressed, err := compression.AutoCompressAsGzip(ctx, []byte(ResourceContent)) + Expect(err).NotTo(HaveOccurred()) + + By("creating an ocm resource from a plain text") + env.OCMCommonTransport(resourceLocalPath, accessio.FormatDirectory, func() { + env.Component(Component, func() { + env.Version(ComponentVersion, func() { + env.Resource(testResource, ResourceVersion, resourceType, v1.LocalRelation, func() { + env.BlobData(mime.MIME_TEXT, resourceContentCompressed) + }) }) }) }) - }) - repo, err := ctf.Open(env, accessobj.ACC_WRITABLE, resourceLocalPath, vfs.FileMode(vfs.O_RDWR), env) - Expect(err).NotTo(HaveOccurred()) - cv, err := repo.LookupComponentVersion(Component, ComponentVersion) - Expect(err).NotTo(HaveOccurred()) - cd, err := ocmPkg.ListComponentDescriptors(ctx, cv, repo) - Expect(err).NotTo(HaveOccurred()) - dataCds, err := yaml.Marshal(cd) - Expect(err).NotTo(HaveOccurred()) - - spec, err := ctf.NewRepositorySpec(ctf.ACC_READONLY, resourceLocalPath) - specData, err := spec.MarshalJSON() - - By("creating a mocked component") - component := test.SetupComponentWithDescriptorList(ctx, testComponent, Namespace, dataCds, &test.MockComponentOptions{ - Registry: registry, - Client: k8sClient, - Recorder: recorder, - Info: v1alpha1.ComponentInfo{ - Component: Component, - Version: ComponentVersion, - RepositorySpec: &apiextensionsv1.JSON{Raw: specData}, - }, - Repository: RepositoryObj, - }) + repo, err := ctf.Open(env, accessobj.ACC_WRITABLE, resourceLocalPath, vfs.FileMode(vfs.O_RDWR), env) + Expect(err).NotTo(HaveOccurred()) + cv, err := repo.LookupComponentVersion(Component, ComponentVersion) + Expect(err).NotTo(HaveOccurred()) + cd, err := ocmPkg.ListComponentDescriptors(ctx, cv, repo) + Expect(err).NotTo(HaveOccurred()) + dataCds, err := yaml.Marshal(cd) + Expect(err).NotTo(HaveOccurred()) + + spec, err := ctf.NewRepositorySpec(ctf.ACC_READONLY, resourceLocalPath) + specData, err := spec.MarshalJSON() + + By("creating a mocked component") + componentObj = test.SetupComponentWithDescriptorList(ctx, testComponent, Namespace, dataCds, &test.MockComponentOptions{ + Registry: registry, + Client: k8sClient, + Recorder: recorder, + Info: v1alpha1.ComponentInfo{ + Component: Component, + Version: ComponentVersion, + RepositorySpec: &apiextensionsv1.JSON{Raw: specData}, + }, + Repository: RepositoryObj, + }) - By("creating a resource object") - resource := &v1alpha1.Resource{ - ObjectMeta: k8smetav1.ObjectMeta{ - Namespace: Namespace, - Name: testResource, - }, - Spec: v1alpha1.ResourceSpec{ - ComponentRef: corev1.LocalObjectReference{ - Name: testComponent, + By("creating a resource object") + resource := &v1alpha1.Resource{ + ObjectMeta: k8smetav1.ObjectMeta{ + Namespace: Namespace, + Name: testResource, }, - Resource: v1alpha1.ResourceID{ - ByReference: v1alpha1.ResourceReference{ - Resource: v1.NewIdentity(testResource), + Spec: v1alpha1.ResourceSpec{ + ComponentRef: corev1.LocalObjectReference{ + Name: testComponent, + }, + Resource: v1alpha1.ResourceID{ + ByReference: v1alpha1.ResourceReference{ + Resource: v1.NewIdentity(testResource), + }, }, }, - }, - } - Expect(k8sClient.Create(ctx, resource)).To(Succeed()) - - By("checking that the resource has been reconciled successfully") - Eventually(func(ctx context.Context) bool { - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(resource), resource) - if err != nil { - return false } - return conditions.IsReady(resource) - }, "15s").WithContext(ctx).Should(BeTrue()) - - By("checking that the snapshot has been created successfully") - Eventually(komega.Object(resource), "15s").Should( - HaveField("Status.SnapshotRef.Name", Not(BeEmpty()))) - snapshotResource := &v1alpha1.Snapshot{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: resource.GetNamespace(), Name: resource.GetSnapshotName()}, snapshotResource)).To(Succeed()) - - Expect(resource).To(HaveField("Status.Resource.Name", Equal(testResource))) - Expect(resource).To(HaveField("Status.Resource.Type", Equal(resourceType))) - Expect(resource).To(HaveField("Status.Resource.Version", Equal(ResourceVersion))) - - By("checking that the snapshot contains the correct content") - snapshotRepository, err := registry.NewRepository(ctx, snapshotResource.Spec.Repository) - Expect(err).NotTo(HaveOccurred()) - snapshotResourceContentCompressed, err := snapshotRepository.FetchSnapshot(ctx, snapshotResource.GetDigest()) - Expect(err).NotTo(HaveOccurred()) - gzipReader, err := gzip.NewReader(bytes.NewReader(snapshotResourceContentCompressed)) - Expect(err).NotTo(HaveOccurred()) - snapshotResourceContent, err := io.ReadAll(gzipReader) - Expect(err).NotTo(HaveOccurred()) - Expect(string(snapshotResourceContent)).To(Equal(ResourceContent)) - - // Compare other fields - resourceAcc, err := cv.GetResource(v1.NewIdentity(testResource)) - Expect(err).NotTo(HaveOccurred()) - - Expect(snapshotResource.Name).To(Equal(fmt.Sprintf("resource-%s", testResource))) - Expect(snapshotResource.Spec.Blob.Digest).To(Equal(resourceAcc.Meta().Digest.Value)) - Expect(snapshotResource.Spec.Blob.Tag).To(Equal(ResourceVersion)) - Expect(snapshotResource.Spec.Blob.Size).To(Equal(int64(len(snapshotResourceContentCompressed)))) - - By("delete resources manually") - Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) - Expect(k8sClient.Delete(ctx, snapshotResource)).To(Succeed()) - Eventually(func(ctx context.Context) bool { - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(resource), resource) - return errors.IsNotFound(err) - }, "15s").WithContext(ctx).Should(BeTrue()) - - Expect(k8sClient.Delete(ctx, component)).To(Succeed()) - snapshotComponent := &v1alpha1.Snapshot{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, snapshotComponent)).To(Succeed()) - Expect(k8sClient.Delete(ctx, snapshotComponent)).To(Succeed()) - }) - - It("can reconcile a resource: OCIArtifact", func() { - testComponent := fmt.Sprintf("%s-%d", ComponentObj, testNumber) - testResource := fmt.Sprintf("%s-%d", ResourceObj, testNumber) - resourceType := artifacttypes.OCI_ARTIFACT - - By("creating an OCI artifact") - repository, err := registry.NewRepository(ctx, testResource) - Expect(err).NotTo(HaveOccurred()) - manifestDigest, err := repository.PushSnapshot(ctx, ResourceVersion, []byte(ResourceContent)) - Expect(err).ToNot(HaveOccurred()) - DeferCleanup(func(ctx SpecContext) { - Expect(repository.DeleteSnapshot(ctx, manifestDigest.String())).To(Succeed()) + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + + By("checking that the resource has been reconciled successfully") + Eventually(func(ctx context.Context) bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(resource), resource) + if err != nil { + return false + } + return conditions.IsReady(resource) + }, "15s").WithContext(ctx).Should(BeTrue()) + Expect(resource).To(HaveField("Status.Resource.Name", Equal(testResource))) + Expect(resource).To(HaveField("Status.Resource.Type", Equal(resourceType))) + Expect(resource).To(HaveField("Status.Resource.Version", Equal(ResourceVersion))) + + resourceAcc, err := cv.GetResource(v1.NewIdentity(testResource)) + Expect(err).NotTo(HaveOccurred()) + validateArtifact(ctx, resource, resourceAcc.Meta().Digest.Value) + + By("delete resource manually") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + Eventually(func(ctx context.Context) bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(resource), resource) + return errors.IsNotFound(err) + }, "15s").WithContext(ctx).Should(BeTrue()) }) - By("creating an ocm resource from an OCI artifact") - env.OCMCommonTransport(resourceLocalPath, accessio.FormatDirectory, func() { - env.Component(Component, func() { - env.Version(ComponentVersion, func() { - env.Resource(testResource, ResourceVersion, resourceType, v1.LocalRelation, func() { - env.Access(ociartifact.New(fmt.Sprintf("http://%s/%s:%s", repository.GetHost(), repository.GetName(), ResourceVersion))) + It("can reconcile a resource: OCIArtifact", func() { + testComponent := fmt.Sprintf("%s-%d", ComponentObj, testNumber) + testResource := fmt.Sprintf("%s-%d", ResourceObj, testNumber) + resourceType := artifacttypes.OCI_ARTIFACT + + By("creating an OCI artifact") + repository, err := registry.NewRepository(ctx, testResource) + Expect(err).NotTo(HaveOccurred()) + manifestDigest, err := repository.PushArtifact(ctx, ResourceVersion, []byte(ResourceContent)) + Expect(err).ToNot(HaveOccurred()) + DeferCleanup(func(ctx SpecContext) { + Expect(repository.DeleteArtifact(ctx, manifestDigest.String())).To(Succeed()) + }) + + By("creating an ocm resource from an OCI artifact") + env.OCMCommonTransport(resourceLocalPath, accessio.FormatDirectory, func() { + env.Component(Component, func() { + env.Version(ComponentVersion, func() { + env.Resource(testResource, ResourceVersion, resourceType, v1.LocalRelation, func() { + env.Access(ociartifact.New(fmt.Sprintf("http://%s/%s:%s", repository.GetHost(), repository.GetName(), ResourceVersion))) + }) }) }) }) - }) - repo, err := ctf.Open(env, accessobj.ACC_WRITABLE, resourceLocalPath, vfs.FileMode(vfs.O_RDWR), env) - Expect(err).NotTo(HaveOccurred()) - cv, err := repo.LookupComponentVersion(Component, ComponentVersion) - Expect(err).NotTo(HaveOccurred()) - cd, err := ocmPkg.ListComponentDescriptors(ctx, cv, repo) - Expect(err).NotTo(HaveOccurred()) - dataCds, err := yaml.Marshal(cd) - Expect(err).NotTo(HaveOccurred()) - - spec, err := ctf.NewRepositorySpec(ctf.ACC_READONLY, resourceLocalPath) - specData, err := spec.MarshalJSON() - Expect(err).NotTo(HaveOccurred()) - - By("creating a mocked component") - component := test.SetupComponentWithDescriptorList(ctx, testComponent, Namespace, dataCds, &test.MockComponentOptions{ - Registry: registry, - Client: k8sClient, - Recorder: recorder, - Info: v1alpha1.ComponentInfo{ - Component: Component, - Version: ComponentVersion, - RepositorySpec: &apiextensionsv1.JSON{Raw: specData}, - }, - Repository: RepositoryObj, - }) + repo, err := ctf.Open(env, accessobj.ACC_WRITABLE, resourceLocalPath, vfs.FileMode(vfs.O_RDWR), env) + Expect(err).NotTo(HaveOccurred()) + cv, err := repo.LookupComponentVersion(Component, ComponentVersion) + Expect(err).NotTo(HaveOccurred()) + cd, err := ocmPkg.ListComponentDescriptors(ctx, cv, repo) + Expect(err).NotTo(HaveOccurred()) + dataCds, err := yaml.Marshal(cd) + Expect(err).NotTo(HaveOccurred()) + + spec, err := ctf.NewRepositorySpec(ctf.ACC_READONLY, resourceLocalPath) + specData, err := spec.MarshalJSON() + Expect(err).NotTo(HaveOccurred()) + + By("creating a mocked component") + componentObj = test.SetupComponentWithDescriptorList(ctx, testComponent, Namespace, dataCds, &test.MockComponentOptions{ + Registry: registry, + Client: k8sClient, + Recorder: recorder, + Info: v1alpha1.ComponentInfo{ + Component: Component, + Version: ComponentVersion, + RepositorySpec: &apiextensionsv1.JSON{Raw: specData}, + }, + Repository: RepositoryObj, + }) - By("creating a resource object") - resource := &v1alpha1.Resource{ - ObjectMeta: k8smetav1.ObjectMeta{ - Namespace: Namespace, - Name: testResource, - }, - Spec: v1alpha1.ResourceSpec{ - ComponentRef: corev1.LocalObjectReference{ - Name: testComponent, + By("creating a resource object") + resource := &v1alpha1.Resource{ + ObjectMeta: k8smetav1.ObjectMeta{ + Namespace: Namespace, + Name: testResource, }, - Resource: v1alpha1.ResourceID{ - ByReference: v1alpha1.ResourceReference{ - Resource: v1.NewIdentity(testResource), + Spec: v1alpha1.ResourceSpec{ + ComponentRef: corev1.LocalObjectReference{ + Name: testComponent, + }, + Resource: v1alpha1.ResourceID{ + ByReference: v1alpha1.ResourceReference{ + Resource: v1.NewIdentity(testResource), + }, }, }, - }, - } - Expect(k8sClient.Create(ctx, resource)).To(Succeed()) - - By("checking that the resource has been reconciled successfully") - Eventually(func(ctx context.Context) bool { - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(resource), resource) - if err != nil { - return false } - return conditions.IsReady(resource) - }, "15s").WithContext(ctx).Should(BeTrue()) - - By("checking that the snapshot has been created successfully") - Eventually(komega.Object(resource), "15s").Should( - HaveField("Status.SnapshotRef.Name", Not(BeEmpty()))) - snapshotResource := &v1alpha1.Snapshot{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: resource.GetNamespace(), Name: resource.GetSnapshotName()}, snapshotResource)).To(Succeed()) - - Expect(resource).To(HaveField("Status.SnapshotRef.Name", Not(BeEmpty()))) - Expect(resource).To(HaveField("Status.Resource.Name", Equal(testResource))) - Expect(resource).To(HaveField("Status.Resource.Type", Equal(resourceType))) - Expect(resource).To(HaveField("Status.Resource.Version", Equal(ResourceVersion))) - - By("checking that the snapshot contains the correct content") - snapshotRepository, err := registry.NewRepository(ctx, snapshotResource.Spec.Repository) - Expect(err).NotTo(HaveOccurred()) - snapshotResourceContent, err := snapshotRepository.FetchSnapshot(ctx, snapshotResource.GetDigest()) - Expect(err).NotTo(HaveOccurred()) - Expect(string(snapshotResourceContent)).To(Equal(ResourceContent)) - - // Compare other fields - resourceAcc, err := cv.GetResource(v1.NewIdentity(testResource)) - Expect(err).NotTo(HaveOccurred()) - - Expect(snapshotResource.Name).To(Equal(fmt.Sprintf("resource-%s", testResource))) - Expect(snapshotResource.Spec.Blob.Digest).To(Equal(resourceAcc.Meta().Digest.Value)) - Expect(snapshotResource.Spec.Blob.Tag).To(Equal(ResourceVersion)) - Expect(snapshotResource.Spec.Blob.Size).To(Equal(int64(0))) - - By("delete resources manually") - Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) - Expect(k8sClient.Delete(ctx, snapshotResource)).To(Succeed()) - Eventually(func(ctx context.Context) bool { - err := k8sClient.Get(ctx, client.ObjectKeyFromObject(resource), resource) - return errors.IsNotFound(err) - }, "15s").WithContext(ctx).Should(BeTrue()) + Expect(k8sClient.Create(ctx, resource)).To(Succeed()) + + By("checking that the resource has been reconciled successfully") + Eventually(func(ctx context.Context) bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(resource), resource) + if err != nil { + return false + } + return conditions.IsReady(resource) + }, "15s").WithContext(ctx).Should(BeTrue()) + Expect(resource).To(HaveField("Status.Resource.Name", Equal(testResource))) + Expect(resource).To(HaveField("Status.Resource.Type", Equal(resourceType))) + Expect(resource).To(HaveField("Status.Resource.Version", Equal(ResourceVersion))) + + resourceAcc, err := cv.GetResource(v1.NewIdentity(testResource)) + Expect(err).NotTo(HaveOccurred()) + validateArtifact(ctx, resource, resourceAcc.Meta().Digest.Value) + + By("delete resource manually") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + Eventually(func(ctx context.Context) bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(resource), resource) + return errors.IsNotFound(err) + }, "15s").WithContext(ctx).Should(BeTrue()) + }) - Expect(k8sClient.Delete(ctx, component)).To(Succeed()) - snapshotComponent := &v1alpha1.Snapshot{} - Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: component.GetNamespace(), Name: component.GetSnapshotName()}, snapshotComponent)).To(Succeed()) + // TODO: Add more testcases }) - - // TODO: Add more testcases }) }) + +func validateArtifact(ctx context.Context, resource *v1alpha1.Resource, digest string) { + GinkgoHelper() + + By("checking that resource has a reference to OCI artifact") + Eventually(komega.Object(resource), "15s").Should( + HaveField("Status.OCIArtifact", Not(BeNil()))) + + By("checking that the OCI artifact contains the correct content") + ociRepo := Must(registry.NewRepository(ctx, resource.GetOCIRepository())) + content := Must(ociRepo.FetchArtifact(ctx, resource.GetManifestDigest())) + + Expect(string(content)).To(Equal(ResourceContent)) + + Expect(resource.GetBlobDigest()).To(Equal(digest)) + Expect(resource.Status.OCIArtifact.Blob.Tag).To(Equal(ResourceVersion)) + Expect(resource.Status.OCIArtifact.Blob.Size).To(Equal(int64(len(content)))) +} diff --git a/internal/controller/resource/suite_test.go b/internal/controller/resource/suite_test.go index daa447fb..ca69b7c7 100644 --- a/internal/controller/resource/suite_test.go +++ b/internal/controller/resource/suite_test.go @@ -39,8 +39,8 @@ import ( metricserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" + "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" "github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm" - "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" "github.com/open-component-model/ocm-k8s-toolkit/pkg/test" ) @@ -55,7 +55,7 @@ var k8sManager ctrl.Manager var testEnv *envtest.Environment var recorder record.EventRecorder var zotCmd *exec.Cmd -var registry *snapshot.Registry +var registry *ociartifact.Registry var zotRootDir string var ctx context.Context var cancel context.CancelFunc diff --git a/internal/controller/snapshot/controller.go b/internal/controller/snapshot/controller.go index bc061510..7149c043 100644 --- a/internal/controller/snapshot/controller.go +++ b/internal/controller/snapshot/controller.go @@ -16,8 +16,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" deliveryv1alpha1 "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" + snapshotRegistry "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" "github.com/open-component-model/ocm-k8s-toolkit/pkg/ocm" - snapshotRegistry "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" "github.com/open-component-model/ocm-k8s-toolkit/pkg/status" ) @@ -78,7 +78,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re return ctrl.Result{}, fmt.Errorf("failed to create a repository: %w", err) } - exists, err := repository.ExistsSnapshot(ctx, snapshotResource.GetDigest()) + exists, err := repository.ExistsArtifact(ctx, snapshotResource.GetDigest()) if err != nil { status.MarkNotReady(r.EventRecorder, snapshotResource, deliveryv1alpha1.OCIRepositoryExistsFailedReason, err.Error()) @@ -86,14 +86,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re } if exists { - if err := repository.DeleteSnapshot(ctx, snapshotResource.GetDigest()); err != nil { + if err := repository.DeleteArtifact(ctx, snapshotResource.GetDigest()); err != nil { status.MarkNotReady(r.EventRecorder, snapshotResource, deliveryv1alpha1.DeleteSnapshotFailedReason, err.Error()) return ctrl.Result{}, fmt.Errorf("failed to delete snapshot: %w", err) } } - if removed := controllerutil.RemoveFinalizer(snapshotResource, deliveryv1alpha1.SnapshotFinalizer); removed { + if removed := controllerutil.RemoveFinalizer(snapshotResource, deliveryv1alpha1.ArtifactFinalizer); removed { if err := r.Update(ctx, snapshotResource); err != nil { return ctrl.Result{}, fmt.Errorf("failed to remove finalizer: %w", err) } @@ -102,7 +102,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re return ctrl.Result{Requeue: true}, nil } - if added := controllerutil.AddFinalizer(snapshotResource, deliveryv1alpha1.SnapshotFinalizer); added { + if added := controllerutil.AddFinalizer(snapshotResource, deliveryv1alpha1.ArtifactFinalizer); added { err := r.Update(ctx, snapshotResource) if err != nil { return ctrl.Result{}, fmt.Errorf("failed to add finalizer: %w", err) @@ -119,7 +119,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Re return ctrl.Result{}, fmt.Errorf("failed to create a repository: %w", err) } - exists, err := repository.ExistsSnapshot(ctx, snapshotResource.GetDigest()) + exists, err := repository.ExistsArtifact(ctx, snapshotResource.GetDigest()) if err != nil { status.MarkNotReady(r.EventRecorder, snapshotResource, deliveryv1alpha1.OCIRepositoryExistsFailedReason, err.Error()) diff --git a/pkg/ociartifact/artifact.go b/pkg/ociartifact/artifact.go new file mode 100644 index 00000000..eee9088b --- /dev/null +++ b/pkg/ociartifact/artifact.go @@ -0,0 +1,31 @@ +package ociartifact + +import ( + "context" + + "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" +) + +// DeleteForObject checks if the object holds a name for an OCI repository, checks if the OCI repository exists, and if +// so, deletes the OCI artifact from the OCI repository. +func DeleteForObject(ctx context.Context, registry RegistryType, obj v1alpha1.OCIArtifactCreator) error { + ociRepositoryName := obj.GetOCIRepository() + + if ociRepositoryName != "" { + ociRepository, err := registry.NewRepository(ctx, ociRepositoryName) + if err != nil { + return err + } + + exists, err := ociRepository.ExistsArtifact(ctx, obj.GetManifestDigest()) + if err != nil { + return err + } + + if exists { + return ociRepository.DeleteArtifact(ctx, obj.GetManifestDigest()) + } + } + + return nil +} diff --git a/pkg/snapshot/object_config.go b/pkg/ociartifact/object_config.go similarity index 98% rename from pkg/snapshot/object_config.go rename to pkg/ociartifact/object_config.go index 97e93458..a52ce1b3 100644 --- a/pkg/snapshot/object_config.go +++ b/pkg/ociartifact/object_config.go @@ -1,4 +1,4 @@ -package snapshot +package ociartifact import ( "bytes" diff --git a/pkg/snapshot/registry.go b/pkg/ociartifact/registry.go similarity index 97% rename from pkg/snapshot/registry.go rename to pkg/ociartifact/registry.go index 850bc448..28a4d14b 100644 --- a/pkg/snapshot/registry.go +++ b/pkg/ociartifact/registry.go @@ -1,4 +1,4 @@ -package snapshot +package ociartifact import ( "context" diff --git a/pkg/snapshot/repository.go b/pkg/ociartifact/repository.go similarity index 92% rename from pkg/snapshot/repository.go rename to pkg/ociartifact/repository.go index 358fdb44..10ec767a 100644 --- a/pkg/snapshot/repository.go +++ b/pkg/ociartifact/repository.go @@ -1,4 +1,4 @@ -package snapshot +package ociartifact import ( "bytes" @@ -31,17 +31,17 @@ type RepositoryType interface { // PushSnapshot is a wrapper to push a single layer OCI artifact with an empty config and a single data layer // containing the blob. As all snapshots are produced and consumed by us, we do not have to care about the // configuration. - PushSnapshot(ctx context.Context, reference string, blob []byte) (digest.Digest, error) + PushArtifact(ctx context.Context, reference string, blob []byte) (digest.Digest, error) // FetchSnapshot is a wrapper to fetch a single layer OCI artifact with a manifest digest. It expects and returns // the single data layer. - FetchSnapshot(ctx context.Context, reference string) ([]byte, error) + FetchArtifact(ctx context.Context, reference string) ([]byte, error) // DeleteSnapshot is a wrapper to delete a single layer OCI artifact with a manifest digest. - DeleteSnapshot(ctx context.Context, digest string) error + DeleteArtifact(ctx context.Context, digest string) error // ExistsSnapshot is a wrapper to check if an OCI repository exists using the manifest digest. - ExistsSnapshot(ctx context.Context, manifestDigest string) (bool, error) + ExistsArtifact(ctx context.Context, manifestDigest string) (bool, error) // CopyOCIArtifactForResourceAccess is a wrapper to copy an OCI artifact from an OCM resource access. CopyOCIArtifactForResourceAccess(ctx context.Context, access ocmctx.ResourceAccess) (digest.Digest, error) @@ -63,7 +63,7 @@ func (r *Repository) GetName() string { return r.Reference.Repository } -func (r *Repository) PushSnapshot(ctx context.Context, tag string, blob []byte) (digest.Digest, error) { +func (r *Repository) PushArtifact(ctx context.Context, tag string, blob []byte) (digest.Digest, error) { logger := log.FromContext(ctx) // Prepare and upload blob @@ -139,7 +139,7 @@ func (r *Repository) PushSnapshot(ctx context.Context, tag string, blob []byte) return manifestDigest, nil } -func (r *Repository) FetchSnapshot(ctx context.Context, manifestDigest string) ([]byte, error) { +func (r *Repository) FetchArtifact(ctx context.Context, manifestDigest string) ([]byte, error) { // Fetch manifest descriptor to get manifest. manifestDescriptor, _, err := r.FetchReference(ctx, manifestDigest) if err != nil { @@ -169,21 +169,27 @@ func (r *Repository) FetchSnapshot(ctx context.Context, manifestDigest string) ( return io.ReadAll(reader) } -func (r *Repository) DeleteSnapshot(ctx context.Context, manifestDigest string) error { +func (r *Repository) DeleteArtifact(ctx context.Context, manifestDigest string) error { + logger := log.FromContext(ctx) + manifestDescriptor, _, err := r.FetchReference(ctx, manifestDigest) if err != nil { return fmt.Errorf("error fetching manifest: %w", err) } + logger.Info("deleting OCI artifact", "digest", manifestDigest) return r.Delete(ctx, manifestDescriptor) } -func (r *Repository) ExistsSnapshot(ctx context.Context, manifestDigest string) (bool, error) { +func (r *Repository) ExistsArtifact(ctx context.Context, manifestDigest string) (bool, error) { + logger := log.FromContext(ctx) + manifestDescriptor, _, err := r.FetchReference(ctx, manifestDigest) if err != nil { return false, fmt.Errorf("error fetching manifest: %w", err) } + logger.Info("checking if OCI artifact exists", "digest", manifestDigest) return r.Exists(ctx, manifestDescriptor) } diff --git a/pkg/snapshot/resource.go b/pkg/ociartifact/resource.go similarity index 98% rename from pkg/snapshot/resource.go rename to pkg/ociartifact/resource.go index 7544111e..7700a075 100644 --- a/pkg/snapshot/resource.go +++ b/pkg/ociartifact/resource.go @@ -1,4 +1,4 @@ -package snapshot +package ociartifact import ( "bytes" @@ -84,7 +84,7 @@ func (r *ContentBackedBySnapshotAndComponent) open() (io.ReadCloser, error) { return nil, fmt.Errorf("failed to open repository: %w", err) } - data, err := repository.FetchSnapshot(ctx, r.Snapshot.GetDigest()) + data, err := repository.FetchArtifact(ctx, r.Snapshot.GetDigest()) if err != nil { return nil, fmt.Errorf("failed to fetch snapshot: %w", err) } diff --git a/pkg/ocm/snapshot.go b/pkg/ocm/snapshot.go deleted file mode 100644 index 09f68d8b..00000000 --- a/pkg/ocm/snapshot.go +++ /dev/null @@ -1,28 +0,0 @@ -package ocm - -import ( - "bytes" - "context" - "fmt" - - "k8s.io/apimachinery/pkg/util/yaml" - "ocm.software/ocm/api/ocm/compdesc" - - "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" - "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" -) - -// GetComponentSetForSnapshot returns the component descriptor set for the given snapshot. -func GetComponentSetForSnapshot(ctx context.Context, repository snapshot.RepositoryType, snapshotResource *v1alpha1.Snapshot) (*compdesc.ComponentVersionSet, error) { - data, err := repository.FetchSnapshot(ctx, snapshotResource.GetDigest()) - if err != nil { - return nil, err - } - - cds := &Descriptors{} - if err := yaml.NewYAMLToJSONDecoder(bytes.NewReader(data)).Decode(cds); err != nil { - return nil, fmt.Errorf("failed to unmarshal component descriptors: %w", err) - } - - return compdesc.NewComponentVersionSet(cds.List...), nil -} diff --git a/pkg/snapshot/snapshot.go b/pkg/snapshot/snapshot.go deleted file mode 100644 index a9126b45..00000000 --- a/pkg/snapshot/snapshot.go +++ /dev/null @@ -1,84 +0,0 @@ -package snapshot - -import ( - "context" - "errors" - "fmt" - "os" - "strings" - - "github.com/fluxcd/pkg/runtime/conditions" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/validation" - "sigs.k8s.io/controller-runtime/pkg/client" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" -) - -// GenerateName generates a name for a snapshot CR. If the name exceeds the character limit, it will be cut off at 256. -func GenerateName(obj v1alpha1.SnapshotWriter) string { - name := strings.ToLower(fmt.Sprintf("%s-%s", obj.GetKind(), obj.GetName())) - - if len(name) > validation.DNS1123SubdomainMaxLength { - return name[:validation.DNS1123SubdomainMaxLength] - } - - return name -} - -func Create(owner v1alpha1.SnapshotWriter, ociRepository, manifestDigest string, blob *v1alpha1.BlobInfo) *v1alpha1.Snapshot { - return &v1alpha1.Snapshot{ - ObjectMeta: metav1.ObjectMeta{ - Name: GenerateName(owner), - Namespace: owner.GetNamespace(), - }, - Spec: v1alpha1.SnapshotSpec{ - Repository: ociRepository, - Digest: manifestDigest, - Blob: blob, - }, - Status: v1alpha1.SnapshotStatus{}, - } -} - -// ValidateSnapshotForOwner verifies if the snapshot for the given collectable is valid. -// This means that the snapshot must be present in the cluster the reader is connected to and -// the snapshot must be present in the OCI registry. -// Additionally, the passed digest must be different from the blob digest stored in the snapshot. -// -// This method can be used to determine if a snapshot needs an update or not because a snapshot that does not -// fulfill these conditions can be considered out of date (not in the cluster, not in the OCI registry, or mismatching -// digest). -func ValidateSnapshotForOwner( - ctx context.Context, - reader client.Reader, - owner v1alpha1.SnapshotWriter, - digest string, -) (bool, error) { - // If the owner cannot return a snapshot name, the snapshot is not created yet. - if owner.GetSnapshotName() == "" { - return false, nil - } - - snapshotResource := &v1alpha1.Snapshot{} - err := reader.Get(ctx, types.NamespacedName{Namespace: owner.GetNamespace(), Name: owner.GetSnapshotName()}, snapshotResource) - if errors.Is(err, os.ErrNotExist) { - return false, nil - } - if client.IgnoreNotFound(err) != nil { - return false, fmt.Errorf("failed to get snapshot: %w", err) - } - - if snapshotResource == nil { - return false, nil - } - - // Snapshot is only ready, if the repository exists in the OCI registry - if !conditions.IsReady(snapshotResource) { - return false, fmt.Errorf("snapshot %s is not ready", snapshotResource.GetName()) - } - - return snapshotResource.Spec.Blob.Digest != digest, nil -} diff --git a/pkg/test/component.go b/pkg/test/component.go index e28a4b87..a49543ff 100644 --- a/pkg/test/component.go +++ b/pkg/test/component.go @@ -2,29 +2,24 @@ package test import ( "context" - "fmt" "time" //nolint:revive,stylecheck // dot import necessary for Ginkgo DSL . "github.com/onsi/gomega" - "github.com/fluxcd/pkg/runtime/conditions" "github.com/fluxcd/pkg/runtime/patch" "github.com/opencontainers/go-digest" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" - "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" + "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" "github.com/open-component-model/ocm-k8s-toolkit/pkg/status" ) type MockComponentOptions struct { - Registry snapshot.RegistryType + Registry ociartifact.RegistryType Client client.Client Recorder record.EventRecorder Info v1alpha1.ComponentInfo @@ -51,46 +46,24 @@ func SetupComponentWithDescriptorList( patchHelper := patch.NewSerialPatcher(component, options.Client) - repositoryName, err := snapshot.CreateRepositoryName(options.Repository, name) + repositoryName, err := ociartifact.CreateRepositoryName(options.Repository, name) Expect(err).ToNot(HaveOccurred()) repository, err := options.Registry.NewRepository(ctx, repositoryName) Expect(err).ToNot(HaveOccurred()) - manifestDigest, err := repository.PushSnapshot(ctx, options.Info.Version, descriptorListData) + manifestDigest, err := repository.PushArtifact(ctx, options.Info.Version, descriptorListData) Expect(err).ToNot(HaveOccurred()) - snapshotCR := snapshot.Create( - component, - repositoryName, - manifestDigest.String(), - &v1alpha1.BlobInfo{ + component.Status.OCIArtifact = &v1alpha1.OCIArtifactInfo{ + Repository: repositoryName, + Digest: manifestDigest.String(), + Blob: &v1alpha1.BlobInfo{ Digest: digest.FromBytes(descriptorListData).String(), Tag: options.Info.Version, Size: int64(len(descriptorListData)), }, - ) - - _, err = controllerutil.CreateOrUpdate(ctx, options.Client, snapshotCR, func() error { - if snapshotCR.ObjectMeta.CreationTimestamp.IsZero() { - if err := controllerutil.SetControllerReference(component, snapshotCR, options.Client.Scheme()); err != nil { - return fmt.Errorf("failed to set controller reference: %w", err) - } - } - - component.Status.SnapshotRef = corev1.LocalObjectReference{ - Name: snapshotCR.GetName(), - } - - component.Status.Component = options.Info - - return nil - }) - Expect(err).ToNot(HaveOccurred()) - - // Marks snapshot as ready - conditions.MarkTrue(snapshotCR, "Ready", "ready", "message") - Expect(options.Client.Status().Update(ctx, snapshotCR)).To(Succeed()) + } Eventually(func(ctx context.Context) error { status.MarkReady(options.Recorder, component, "applied mock component") diff --git a/pkg/test/resource.go b/pkg/test/resource.go index fa830744..2c5ea9ba 100644 --- a/pkg/test/resource.go +++ b/pkg/test/resource.go @@ -22,7 +22,7 @@ import ( "github.com/open-component-model/ocm-k8s-toolkit/api/v1alpha1" "github.com/open-component-model/ocm-k8s-toolkit/pkg/compression" - "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" + "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" "github.com/open-component-model/ocm-k8s-toolkit/pkg/status" ) @@ -34,7 +34,7 @@ type MockResourceOptions struct { ComponentRef v1alpha1.ObjectKey - Registry snapshot.RegistryType + Registry ociartifact.RegistryType Clnt client.Client Recorder record.EventRecorder } @@ -78,14 +78,14 @@ func SetupMockResourceWithData( } version := "dummy" - repositoryName, err := snapshot.CreateRepositoryName(options.ComponentRef.Name, name) + repositoryName, err := ociartifact.CreateRepositoryName(options.ComponentRef.Name, name) Expect(err).ToNot(HaveOccurred()) repository, err := options.Registry.NewRepository(ctx, repositoryName) Expect(err).ToNot(HaveOccurred()) - manifestDigest, err := repository.PushSnapshot(ctx, version, data) + manifestDigest, err := repository.PushArtifact(ctx, version, data) Expect(err).ToNot(HaveOccurred()) - snapshotCR := snapshot.Create( + snapshotCR := ociartifact.Create( res, repositoryName, manifestDigest.String(), diff --git a/pkg/test/zot-registry.go b/pkg/test/zot-registry.go index ec4e8556..dedbfc72 100644 --- a/pkg/test/zot-registry.go +++ b/pkg/test/zot-registry.go @@ -13,14 +13,14 @@ import ( //nolint:revive,stylecheck // dot import necessary for Ginkgo DSL . "github.com/onsi/gomega" - "github.com/open-component-model/ocm-k8s-toolkit/pkg/snapshot" + "github.com/open-component-model/ocm-k8s-toolkit/pkg/ociartifact" ) const ( timeout = 30 * time.Second ) -func SetupRegistry(binPath, rootDir, address, port string) (*exec.Cmd, *snapshot.Registry) { +func SetupRegistry(binPath, rootDir, address, port string) (*exec.Cmd, *ociartifact.Registry) { config := []byte(fmt.Sprintf(`{"storage":{"rootDirectory":"%s"},"http":{"address":"%s","port": "%s"}}`, rootDir, address, port)) configFile := filepath.Join(rootDir, "config.json") err := os.WriteFile(configFile, config, 0o600) @@ -52,7 +52,7 @@ func SetupRegistry(binPath, rootDir, address, port string) (*exec.Cmd, *snapshot return nil }, timeout).Should(Succeed(), "Zot registry did not start in time") - registry, err := snapshot.NewRegistry(fmt.Sprintf("%s:%s", address, port)) + registry, err := ociartifact.NewRegistry(fmt.Sprintf("%s:%s", address, port)) Expect(err).NotTo(HaveOccurred()) registry.PlainHTTP = true