OCIRepo: Add observed content config in status
Replace content config checksum with explicit artifact content config observations. It makes the observations of the controller more transparent and easier to debug. Introduces `observedIgnore` and `observedLayerSelector` status fields. Signed-off-by: Sunny <darkowlzz@protonmail.com>
This commit is contained in:
parent
70d9f126f9
commit
278a223bc6
|
|
@ -211,9 +211,22 @@ type OCIRepositoryStatus struct {
|
|||
// be used to determine if the content configuration has changed and the
|
||||
// artifact needs to be rebuilt.
|
||||
// It has the format of `<algo>:<checksum>`, for example: `sha256:<checksum>`.
|
||||
//
|
||||
// Deprecated: Replaced with explicit fields for observed artifact content
|
||||
// config in the status.
|
||||
// +optional
|
||||
ContentConfigChecksum string `json:"contentConfigChecksum,omitempty"`
|
||||
|
||||
// ObservedIgnore is the observed exclusion patterns used for constructing
|
||||
// the source artifact.
|
||||
// +optional
|
||||
ObservedIgnore *string `json:"observedIgnore,omitempty"`
|
||||
|
||||
// ObservedLayerSelector is the observed layer selector used for constructing
|
||||
// the source artifact.
|
||||
// +optional
|
||||
ObservedLayerSelector *OCILayerSelector `json:"observedLayerSelector,omitempty"`
|
||||
|
||||
meta.ReconcileRequestStatus `json:",inline"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -777,6 +777,16 @@ func (in *OCIRepositoryStatus) DeepCopyInto(out *OCIRepositoryStatus) {
|
|||
*out = new(Artifact)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ObservedIgnore != nil {
|
||||
in, out := &in.ObservedIgnore, &out.ObservedIgnore
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.ObservedLayerSelector != nil {
|
||||
in, out := &in.ObservedLayerSelector, &out.ObservedLayerSelector
|
||||
*out = new(OCILayerSelector)
|
||||
**out = **in
|
||||
}
|
||||
out.ReconcileRequestStatus = in.ReconcileRequestStatus
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -301,12 +301,14 @@ spec:
|
|||
type: object
|
||||
type: array
|
||||
contentConfigChecksum:
|
||||
description: 'ContentConfigChecksum is a checksum of all the configurations
|
||||
description: "ContentConfigChecksum is a checksum of all the configurations
|
||||
related to the content of the source artifact: - .spec.ignore -
|
||||
.spec.layerSelector observed in .status.observedGeneration version
|
||||
of the object. This can be used to determine if the content configuration
|
||||
has changed and the artifact needs to be rebuilt. It has the format
|
||||
of `<algo>:<checksum>`, for example: `sha256:<checksum>`.'
|
||||
of `<algo>:<checksum>`, for example: `sha256:<checksum>`. \n Deprecated:
|
||||
Replaced with explicit fields for observed artifact content config
|
||||
in the status."
|
||||
type: string
|
||||
lastHandledReconcileAt:
|
||||
description: LastHandledReconcileAt holds the value of the most recent
|
||||
|
|
@ -317,6 +319,29 @@ spec:
|
|||
description: ObservedGeneration is the last observed generation.
|
||||
format: int64
|
||||
type: integer
|
||||
observedIgnore:
|
||||
description: ObservedIgnore is the observed exclusion patterns used
|
||||
for constructing the source artifact.
|
||||
type: string
|
||||
observedLayerSelector:
|
||||
description: ObservedLayerSelector is the observed layer selector
|
||||
used for constructing the source artifact.
|
||||
properties:
|
||||
mediaType:
|
||||
description: MediaType specifies the OCI media type of the layer
|
||||
which should be extracted from the OCI Artifact. The first layer
|
||||
matching this type is selected.
|
||||
type: string
|
||||
operation:
|
||||
description: Operation specifies how the selected layer should
|
||||
be processed. By default, the layer compressed content is extracted
|
||||
to storage. When the operation is set to 'copy', the layer compressed
|
||||
content is persisted to storage as it is.
|
||||
enum:
|
||||
- extract
|
||||
- copy
|
||||
type: string
|
||||
type: object
|
||||
url:
|
||||
description: URL is the download link for the artifact output of the
|
||||
last OCI Repository sync.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package controllers
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
|
|
@ -44,6 +43,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
kuberecorder "k8s.io/client-go/tools/record"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
|
|
@ -427,10 +427,9 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
|
|||
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of revision %s", revision)
|
||||
}
|
||||
|
||||
// Skip pulling if the artifact revision and the content config checksum has
|
||||
// Skip pulling if the artifact revision and the source configuration has
|
||||
// not changed.
|
||||
if obj.GetArtifact().HasRevision(revision) &&
|
||||
r.calculateContentConfigChecksum(obj) == obj.Status.ContentConfigChecksum {
|
||||
if obj.GetArtifact().HasRevision(revision) && !ociContentConfigChanged(obj) {
|
||||
conditions.Delete(obj, sourcev1.FetchFailedCondition)
|
||||
return sreconcile.ResultSuccess, nil
|
||||
}
|
||||
|
|
@ -918,13 +917,9 @@ func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context, obj *so
|
|||
artifact := r.Storage.NewArtifactFor(obj.Kind, obj, revision,
|
||||
fmt.Sprintf("%s.tar.gz", r.digestFromRevision(revision)))
|
||||
|
||||
// Calculate the content config checksum.
|
||||
ccc := r.calculateContentConfigChecksum(obj)
|
||||
|
||||
// Set the ArtifactInStorageCondition if there's no drift.
|
||||
defer func() {
|
||||
if obj.GetArtifact().HasRevision(artifact.Revision) &&
|
||||
obj.Status.ContentConfigChecksum == ccc {
|
||||
if obj.GetArtifact().HasRevision(artifact.Revision) && !ociContentConfigChanged(obj) {
|
||||
conditions.Delete(obj, sourcev1.ArtifactOutdatedCondition)
|
||||
conditions.MarkTrue(obj, sourcev1.ArtifactInStorageCondition, meta.SucceededReason,
|
||||
"stored artifact for digest '%s'", artifact.Revision)
|
||||
|
|
@ -932,8 +927,7 @@ func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context, obj *so
|
|||
}()
|
||||
|
||||
// The artifact is up-to-date
|
||||
if obj.GetArtifact().HasRevision(artifact.Revision) &&
|
||||
obj.Status.ContentConfigChecksum == ccc {
|
||||
if obj.GetArtifact().HasRevision(artifact.Revision) && !ociContentConfigChanged(obj) {
|
||||
r.eventLogf(ctx, obj, events.EventTypeTrace, sourcev1.ArtifactUpToDateReason,
|
||||
"artifact up-to-date with remote revision: '%s'", artifact.Revision)
|
||||
return sreconcile.ResultSuccess, nil
|
||||
|
|
@ -1008,10 +1002,12 @@ func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context, obj *so
|
|||
}
|
||||
}
|
||||
|
||||
// Record it on the object
|
||||
// Record the observations on the object.
|
||||
obj.Status.Artifact = artifact.DeepCopy()
|
||||
obj.Status.Artifact.Metadata = metadata.Metadata
|
||||
obj.Status.ContentConfigChecksum = ccc
|
||||
obj.Status.ContentConfigChecksum = "" // To be removed in the next API version.
|
||||
obj.Status.ObservedIgnore = obj.Spec.Ignore
|
||||
obj.Status.ObservedLayerSelector = obj.Spec.LayerSelector
|
||||
|
||||
// Update symlink on a "best effort" basis
|
||||
url, err := r.Storage.Symlink(artifact, "latest.tar.gz")
|
||||
|
|
@ -1141,24 +1137,6 @@ func (r *OCIRepositoryReconciler) notify(ctx context.Context, oldObj, newObj *so
|
|||
}
|
||||
}
|
||||
|
||||
// calculateContentConfigChecksum calculates a checksum of all the
|
||||
// configurations that result in a change in the source artifact. It can be used
|
||||
// to decide if further reconciliation is needed when an artifact already exists
|
||||
// for a set of configurations.
|
||||
func (r *OCIRepositoryReconciler) calculateContentConfigChecksum(obj *sourcev1.OCIRepository) string {
|
||||
c := []byte{}
|
||||
// Consider the ignore rules.
|
||||
if obj.Spec.Ignore != nil {
|
||||
c = append(c, []byte(*obj.Spec.Ignore)...)
|
||||
}
|
||||
// Consider the layer selector.
|
||||
if obj.Spec.LayerSelector != nil {
|
||||
c = append(c, []byte(obj.GetLayerMediaType()+obj.GetLayerOperation())...)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("sha256:%x", sha256.Sum256(c))
|
||||
}
|
||||
|
||||
// craneOptions sets the auth headers, timeout and user agent
|
||||
// for all operations against remote container registries.
|
||||
func craneOptions(ctx context.Context, insecure bool) []crane.Option {
|
||||
|
|
@ -1208,3 +1186,31 @@ type remoteOptions struct {
|
|||
craneOpts []crane.Option
|
||||
verifyOpts []remote.Option
|
||||
}
|
||||
|
||||
// ociContentConfigChanged evaluates the current spec with the observations
|
||||
// of the artifact in the status to determine if artifact content configuration
|
||||
// has changed and requires rebuilding the artifact.
|
||||
func ociContentConfigChanged(obj *sourcev1.OCIRepository) bool {
|
||||
if !pointer.StringEqual(obj.Spec.Ignore, obj.Status.ObservedIgnore) {
|
||||
return true
|
||||
}
|
||||
|
||||
if !layerSelectorEqual(obj.Spec.LayerSelector, obj.Status.ObservedLayerSelector) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns true if both arguments are nil or both arguments
|
||||
// dereference to the same value.
|
||||
// Based on k8s.io/utils/pointer/pointer.go pointer value equality.
|
||||
func layerSelectorEqual(a, b *sourcev1.OCILayerSelector) bool {
|
||||
if (a == nil) != (b == nil) {
|
||||
return false
|
||||
}
|
||||
if a == nil {
|
||||
return true
|
||||
}
|
||||
return *a == *b
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,8 +69,6 @@ import (
|
|||
"github.com/fluxcd/source-controller/pkg/git"
|
||||
)
|
||||
|
||||
const ociRepoEmptyContentConfigChecksum = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
|
||||
func TestOCIRepository_Reconcile(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
|
|
@ -1290,21 +1288,48 @@ func TestOCIRepository_reconcileSource_noop(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "noop - artifact revisions and ccc match",
|
||||
name: "noop - artifact revisions match",
|
||||
beforeFunc: func(obj *sourcev1.OCIRepository) {
|
||||
obj.Status.Artifact = &sourcev1.Artifact{
|
||||
Revision: testRevision,
|
||||
}
|
||||
obj.Status.ContentConfigChecksum = ociRepoEmptyContentConfigChecksum
|
||||
},
|
||||
afterFunc: func(g *WithT, artifact *sourcev1.Artifact) {
|
||||
g.Expect(artifact.Metadata).To(BeEmpty())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "full reconcile - same rev, different ccc",
|
||||
name: "full reconcile - same rev, unobserved ignore",
|
||||
beforeFunc: func(obj *sourcev1.OCIRepository) {
|
||||
obj.Status.ContentConfigChecksum = "some-checksum"
|
||||
obj.Status.ObservedIgnore = pointer.String("aaa")
|
||||
obj.Status.Artifact = &sourcev1.Artifact{
|
||||
Revision: testRevision,
|
||||
}
|
||||
},
|
||||
afterFunc: func(g *WithT, artifact *sourcev1.Artifact) {
|
||||
g.Expect(artifact.Metadata).ToNot(BeEmpty())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "noop - same rev, observed ignore",
|
||||
beforeFunc: func(obj *sourcev1.OCIRepository) {
|
||||
obj.Spec.Ignore = pointer.String("aaa")
|
||||
obj.Status.ObservedIgnore = pointer.String("aaa")
|
||||
obj.Status.Artifact = &sourcev1.Artifact{
|
||||
Revision: testRevision,
|
||||
}
|
||||
},
|
||||
afterFunc: func(g *WithT, artifact *sourcev1.Artifact) {
|
||||
g.Expect(artifact.Metadata).To(BeEmpty())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "full reconcile - same rev, unobserved layer selector",
|
||||
beforeFunc: func(obj *sourcev1.OCIRepository) {
|
||||
obj.Spec.LayerSelector = &sourcev1.OCILayerSelector{
|
||||
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
Operation: sourcev1.OCILayerCopy,
|
||||
}
|
||||
obj.Status.Artifact = &sourcev1.Artifact{
|
||||
Revision: testRevision,
|
||||
}
|
||||
|
|
@ -1320,10 +1345,13 @@ func TestOCIRepository_reconcileSource_noop(t *testing.T) {
|
|||
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
Operation: sourcev1.OCILayerCopy,
|
||||
}
|
||||
obj.Status.ObservedLayerSelector = &sourcev1.OCILayerSelector{
|
||||
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
Operation: sourcev1.OCILayerCopy,
|
||||
}
|
||||
obj.Status.Artifact = &sourcev1.Artifact{
|
||||
Revision: testRevision,
|
||||
}
|
||||
obj.Status.ContentConfigChecksum = "sha256:fcfd705e10431a341f2df5b05ecee1fb54facd9a5e88b0be82276bdf533b6c64"
|
||||
},
|
||||
afterFunc: func(g *WithT, artifact *sourcev1.Artifact) {
|
||||
g.Expect(artifact.Metadata).To(BeEmpty())
|
||||
|
|
@ -1336,10 +1364,13 @@ func TestOCIRepository_reconcileSource_noop(t *testing.T) {
|
|||
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
Operation: sourcev1.OCILayerExtract,
|
||||
}
|
||||
obj.Status.ObservedLayerSelector = &sourcev1.OCILayerSelector{
|
||||
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
Operation: sourcev1.OCILayerCopy,
|
||||
}
|
||||
obj.Status.Artifact = &sourcev1.Artifact{
|
||||
Revision: testRevision,
|
||||
}
|
||||
obj.Status.ContentConfigChecksum = "sha256:fcfd705e10431a341f2df5b05ecee1fb54facd9a5e88b0be82276bdf533b6c64"
|
||||
},
|
||||
afterFunc: func(g *WithT, artifact *sourcev1.Artifact) {
|
||||
g.Expect(artifact.Metadata).ToNot(BeEmpty())
|
||||
|
|
@ -1449,7 +1480,6 @@ func TestOCIRepository_reconcileArtifact(t *testing.T) {
|
|||
obj.Status.Artifact = &sourcev1.Artifact{
|
||||
Revision: "revision",
|
||||
}
|
||||
obj.Status.ContentConfigChecksum = ociRepoEmptyContentConfigChecksum
|
||||
},
|
||||
assertArtifact: &sourcev1.Artifact{
|
||||
Revision: "revision",
|
||||
|
|
@ -1467,14 +1497,13 @@ func TestOCIRepository_reconcileArtifact(t *testing.T) {
|
|||
beforeFunc: func(obj *sourcev1.OCIRepository) {
|
||||
obj.Status.Artifact = &sourcev1.Artifact{Revision: "revision"}
|
||||
obj.Spec.Ignore = pointer.String("aaa")
|
||||
obj.Status.ContentConfigChecksum = ociRepoEmptyContentConfigChecksum
|
||||
},
|
||||
want: sreconcile.ResultSuccess,
|
||||
assertPaths: []string{
|
||||
"latest.tar.gz",
|
||||
},
|
||||
afterFunc: func(g *WithT, obj *sourcev1.OCIRepository) {
|
||||
g.Expect(obj.Status.ContentConfigChecksum).To(Equal("sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0"))
|
||||
g.Expect(*obj.Status.ObservedIgnore).To(Equal("aaa"))
|
||||
},
|
||||
assertConditions: []metav1.Condition{
|
||||
*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for digest"),
|
||||
|
|
@ -1489,14 +1518,13 @@ func TestOCIRepository_reconcileArtifact(t *testing.T) {
|
|||
beforeFunc: func(obj *sourcev1.OCIRepository) {
|
||||
obj.Spec.LayerSelector = &sourcev1.OCILayerSelector{MediaType: "foo"}
|
||||
obj.Status.Artifact = &sourcev1.Artifact{Revision: "revision"}
|
||||
obj.Status.ContentConfigChecksum = ociRepoEmptyContentConfigChecksum
|
||||
},
|
||||
want: sreconcile.ResultSuccess,
|
||||
assertPaths: []string{
|
||||
"latest.tar.gz",
|
||||
},
|
||||
afterFunc: func(g *WithT, obj *sourcev1.OCIRepository) {
|
||||
g.Expect(obj.Status.ContentConfigChecksum).To(Equal("sha256:82410edf339ab2945d97e26b92b6499e57156db63b94c17654b6ab97fbf86dbb"))
|
||||
g.Expect(obj.Status.ObservedLayerSelector.MediaType).To(Equal("foo"))
|
||||
},
|
||||
assertConditions: []metav1.Condition{
|
||||
*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for digest"),
|
||||
|
|
@ -1515,14 +1543,14 @@ func TestOCIRepository_reconcileArtifact(t *testing.T) {
|
|||
Operation: sourcev1.OCILayerCopy,
|
||||
}
|
||||
obj.Status.Artifact = &sourcev1.Artifact{Revision: "revision"}
|
||||
obj.Status.ContentConfigChecksum = ociRepoEmptyContentConfigChecksum
|
||||
},
|
||||
want: sreconcile.ResultSuccess,
|
||||
assertPaths: []string{
|
||||
"latest.tar.gz",
|
||||
},
|
||||
afterFunc: func(g *WithT, obj *sourcev1.OCIRepository) {
|
||||
g.Expect(obj.Status.ContentConfigChecksum).To(Equal("sha256:0e0e1c82f6403c8ee74fdf51349c8b5d98c508b5374c507c7ffb2e41dbc875df"))
|
||||
g.Expect(obj.Status.ObservedLayerSelector.MediaType).To(Equal("foo"))
|
||||
g.Expect(obj.Status.ObservedLayerSelector.Operation).To(Equal(sourcev1.OCILayerCopy))
|
||||
},
|
||||
assertConditions: []metav1.Condition{
|
||||
*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, meta.SucceededReason, "stored artifact for digest"),
|
||||
|
|
@ -1538,7 +1566,8 @@ func TestOCIRepository_reconcileArtifact(t *testing.T) {
|
|||
obj.Spec.Ignore = pointer.String("aaa")
|
||||
obj.Spec.LayerSelector = &sourcev1.OCILayerSelector{MediaType: "foo"}
|
||||
obj.Status.Artifact = &sourcev1.Artifact{Revision: "revision"}
|
||||
obj.Status.ContentConfigChecksum = "sha256:0b56187b81cab6c3485583a46bec631f5ea08a1f69b769457f0e4aafb47884e3"
|
||||
obj.Status.ObservedIgnore = pointer.String("aaa")
|
||||
obj.Status.ObservedLayerSelector = &sourcev1.OCILayerSelector{MediaType: "foo"}
|
||||
},
|
||||
want: sreconcile.ResultSuccess,
|
||||
assertArtifact: &sourcev1.Artifact{
|
||||
|
|
@ -2245,26 +2274,131 @@ func createTLSServer() (*httptest.Server, []byte, []byte, []byte, tls.Certificat
|
|||
return srv, rootCertPEM, clientCertPEM, clientKeyPEM, clientTLSCert, err
|
||||
}
|
||||
|
||||
func TestOCIRepository_calculateContentConfigChecksum(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
obj := &sourcev1.OCIRepository{}
|
||||
r := &OCIRepositoryReconciler{}
|
||||
|
||||
emptyChecksum := r.calculateContentConfigChecksum(obj)
|
||||
g.Expect(emptyChecksum).To(Equal(ociRepoEmptyContentConfigChecksum))
|
||||
|
||||
// Ignore modified.
|
||||
obj.Spec.Ignore = pointer.String("some-rule")
|
||||
ignoreModChecksum := r.calculateContentConfigChecksum(obj)
|
||||
g.Expect(emptyChecksum).ToNot(Equal(ignoreModChecksum))
|
||||
|
||||
// LayerSelector modified.
|
||||
obj.Spec.LayerSelector = &sourcev1.OCILayerSelector{
|
||||
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
func TestOCIContentConfigChanged(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
spec sourcev1.OCIRepositorySpec
|
||||
status sourcev1.OCIRepositoryStatus
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "same ignore, no layer selector",
|
||||
spec: sourcev1.OCIRepositorySpec{
|
||||
Ignore: pointer.String("nnn"),
|
||||
},
|
||||
status: sourcev1.OCIRepositoryStatus{
|
||||
ObservedIgnore: pointer.String("nnn"),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "different ignore, no layer selector",
|
||||
spec: sourcev1.OCIRepositorySpec{
|
||||
Ignore: pointer.String("nnn"),
|
||||
},
|
||||
status: sourcev1.OCIRepositoryStatus{
|
||||
ObservedIgnore: pointer.String("mmm"),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "same ignore, same layer selector",
|
||||
spec: sourcev1.OCIRepositorySpec{
|
||||
Ignore: pointer.String("nnn"),
|
||||
LayerSelector: &sourcev1.OCILayerSelector{
|
||||
MediaType: "foo",
|
||||
Operation: sourcev1.OCILayerExtract,
|
||||
},
|
||||
},
|
||||
status: sourcev1.OCIRepositoryStatus{
|
||||
ObservedIgnore: pointer.String("nnn"),
|
||||
ObservedLayerSelector: &sourcev1.OCILayerSelector{
|
||||
MediaType: "foo",
|
||||
Operation: sourcev1.OCILayerExtract,
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "same ignore, different layer selector operation",
|
||||
spec: sourcev1.OCIRepositorySpec{
|
||||
Ignore: pointer.String("nnn"),
|
||||
LayerSelector: &sourcev1.OCILayerSelector{
|
||||
MediaType: "foo",
|
||||
Operation: sourcev1.OCILayerCopy,
|
||||
},
|
||||
},
|
||||
status: sourcev1.OCIRepositoryStatus{
|
||||
ObservedIgnore: pointer.String("nnn"),
|
||||
ObservedLayerSelector: &sourcev1.OCILayerSelector{
|
||||
MediaType: "foo",
|
||||
Operation: sourcev1.OCILayerExtract,
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "same ignore, different layer selector mediatype",
|
||||
spec: sourcev1.OCIRepositorySpec{
|
||||
Ignore: pointer.String("nnn"),
|
||||
LayerSelector: &sourcev1.OCILayerSelector{
|
||||
MediaType: "bar",
|
||||
Operation: sourcev1.OCILayerExtract,
|
||||
},
|
||||
},
|
||||
status: sourcev1.OCIRepositoryStatus{
|
||||
ObservedIgnore: pointer.String("nnn"),
|
||||
ObservedLayerSelector: &sourcev1.OCILayerSelector{
|
||||
MediaType: "foo",
|
||||
Operation: sourcev1.OCILayerExtract,
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "no ignore, same layer selector",
|
||||
spec: sourcev1.OCIRepositorySpec{
|
||||
LayerSelector: &sourcev1.OCILayerSelector{
|
||||
MediaType: "foo",
|
||||
Operation: sourcev1.OCILayerExtract,
|
||||
},
|
||||
},
|
||||
status: sourcev1.OCIRepositoryStatus{
|
||||
ObservedLayerSelector: &sourcev1.OCILayerSelector{
|
||||
MediaType: "foo",
|
||||
Operation: sourcev1.OCILayerExtract,
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "no ignore, different layer selector",
|
||||
spec: sourcev1.OCIRepositorySpec{
|
||||
LayerSelector: &sourcev1.OCILayerSelector{
|
||||
MediaType: "bar",
|
||||
Operation: sourcev1.OCILayerExtract,
|
||||
},
|
||||
},
|
||||
status: sourcev1.OCIRepositoryStatus{
|
||||
ObservedLayerSelector: &sourcev1.OCILayerSelector{
|
||||
MediaType: "foo",
|
||||
Operation: sourcev1.OCILayerExtract,
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
obj := &sourcev1.OCIRepository{
|
||||
Spec: tt.spec,
|
||||
Status: tt.status,
|
||||
}
|
||||
|
||||
g.Expect(ociContentConfigChanged(obj)).To(Equal(tt.want))
|
||||
})
|
||||
}
|
||||
mediaTypeChecksum := r.calculateContentConfigChecksum(obj)
|
||||
g.Expect(ignoreModChecksum).ToNot(Equal(mediaTypeChecksum))
|
||||
obj.Spec.LayerSelector.Operation = sourcev1.OCILayerCopy
|
||||
layerCopyChecksum := r.calculateContentConfigChecksum(obj)
|
||||
g.Expect(mediaTypeChecksum).ToNot(Equal(layerCopyChecksum))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2608,7 +2608,8 @@ string
|
|||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#source.toolkit.fluxcd.io/v1beta2.OCIRepositorySpec">OCIRepositorySpec</a>)
|
||||
<a href="#source.toolkit.fluxcd.io/v1beta2.OCIRepositorySpec">OCIRepositorySpec</a>,
|
||||
<a href="#source.toolkit.fluxcd.io/v1beta2.OCIRepositoryStatus">OCIRepositoryStatus</a>)
|
||||
</p>
|
||||
<p>OCILayerSelector specifies which layer should be extracted from an OCI Artifact</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
|
|
@ -3010,6 +3011,36 @@ observed in .status.observedGeneration version of the object. This can
|
|||
be used to determine if the content configuration has changed and the
|
||||
artifact needs to be rebuilt.
|
||||
It has the format of <code><algo>:<checksum></code>, for example: <code>sha256:<checksum></code>.</p>
|
||||
<p>Deprecated: Replaced with explicit fields for observed artifact content
|
||||
config in the status.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>observedIgnore</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ObservedIgnore is the observed exclusion patterns used for constructing
|
||||
the source artifact.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>observedLayerSelector</code><br>
|
||||
<em>
|
||||
<a href="#source.toolkit.fluxcd.io/v1beta2.OCILayerSelector">
|
||||
OCILayerSelector
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ObservedLayerSelector is the observed layer selector used for constructing
|
||||
the source artifact.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -868,6 +868,53 @@ configurations of the OCIRepository that indicate a change in source and
|
|||
records it in `.status.contentConfigChecksum`. This field is used to determine
|
||||
if the source artifact needs to be rebuilt.
|
||||
|
||||
**Deprecation Note:** `contentConfigChecksum` is no longer used and will be
|
||||
removed in the next API version. The individual components used for generating
|
||||
content configuration checksum now have explicit fields in the status. This
|
||||
makes the observations used by the controller for making artifact rebuild
|
||||
decisions more transparent and easier to debug.
|
||||
|
||||
### Observed Ignore
|
||||
|
||||
The source-controller reports an observed ignore in the OCIRepository's
|
||||
`.status.observedIgnore`. The observed ignore is the latest `.spec.ignore` value
|
||||
which resulted in a [ready state](#ready-ocirepository), or stalled due to error
|
||||
it can not recover from without human intervention. The value is the same as the
|
||||
[ignore in spec](#ignore). It indicates the ignore rules used in building the
|
||||
current artifact in storage. It is also used by the controller to determine if
|
||||
an artifact needs to be rebuilt.
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
status:
|
||||
...
|
||||
observedIgnore: |
|
||||
hpa.yaml
|
||||
build
|
||||
...
|
||||
```
|
||||
|
||||
### Observed Layer Selector
|
||||
|
||||
The source-controller reports an observed layer selector in the OCIRepository's
|
||||
`.status.observedLayerSelector`. The observed layer selector is the latest
|
||||
`.spec.layerSelector` value which resulted in a [ready state](#ready-ocirepository),
|
||||
or stalled due to error it can not recover from without human intervention.
|
||||
The value is the same as the [layer selector in spec](#layer-selector).
|
||||
It indicates the layer selection configuration used in building the current
|
||||
artifact in storage. It is also used by the controller to determine if an
|
||||
artifact needs to be rebuilt.
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
status:
|
||||
...
|
||||
observedLayerSelector:
|
||||
mediaType: application/vnd.docker.image.rootfs.diff.tar.gzip
|
||||
operation: copy
|
||||
...
|
||||
```
|
||||
|
||||
### Observed Generation
|
||||
|
||||
The source-controller reports an [observed generation][typical-status-properties]
|
||||
|
|
|
|||
Loading…
Reference in New Issue