Optimise OCI artifacts reconciliation
- Fetch the upstream digest before validation and pulling - Pull artifact only if the upstream digest is different from the one in storage - Add the image tag to the revision string `<tag>/<digest-hex>` for a better UX - Extract the layer processing to a dedicated function Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
parent
4ec51ca306
commit
aae9d917fb
|
@ -369,47 +369,18 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
|
||||||
return sreconcile.ResultEmpty, e
|
return sreconcile.ResultEmpty, e
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull artifact from the remote container registry
|
// Get the upstream revision from the artifact digest
|
||||||
img, err := crane.Pull(url, options...)
|
revision, err := r.getRevision(url, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e := serror.NewGeneric(
|
e := serror.NewGeneric(
|
||||||
fmt.Errorf("failed to pull artifact from '%s': %w", obj.Spec.URL, err),
|
fmt.Errorf("failed to determine artifact digest: %w", err),
|
||||||
sourcev1.OCIPullFailedReason,
|
sourcev1.OCIPullFailedReason,
|
||||||
)
|
)
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
||||||
return sreconcile.ResultEmpty, e
|
return sreconcile.ResultEmpty, e
|
||||||
}
|
}
|
||||||
|
metaArtifact := &sourcev1.Artifact{Revision: revision}
|
||||||
// Determine the artifact SHA256 digest
|
metaArtifact.DeepCopyInto(metadata)
|
||||||
imgDigest, err := img.Digest()
|
|
||||||
if err != nil {
|
|
||||||
e := serror.NewGeneric(
|
|
||||||
fmt.Errorf("failed to determine artifact digest: %w", err),
|
|
||||||
sourcev1.OCILayerOperationFailedReason,
|
|
||||||
)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the internal revision to the remote digest hex
|
|
||||||
revision := imgDigest.Hex
|
|
||||||
|
|
||||||
// Copy the OCI annotations to the internal artifact metadata
|
|
||||||
manifest, err := img.Manifest()
|
|
||||||
if err != nil {
|
|
||||||
e := serror.NewGeneric(
|
|
||||||
fmt.Errorf("failed to parse artifact manifest: %w", err),
|
|
||||||
sourcev1.OCILayerOperationFailedReason,
|
|
||||||
)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
|
|
||||||
m := &sourcev1.Artifact{
|
|
||||||
Revision: revision,
|
|
||||||
Metadata: manifest.Annotations,
|
|
||||||
}
|
|
||||||
m.DeepCopyInto(metadata)
|
|
||||||
|
|
||||||
// Mark observations about the revision on the object
|
// Mark observations about the revision on the object
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -430,7 +401,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
|
||||||
} else if !obj.GetArtifact().HasRevision(revision) ||
|
} else if !obj.GetArtifact().HasRevision(revision) ||
|
||||||
conditions.GetObservedGeneration(obj, sourcev1.SourceVerifiedCondition) != obj.Generation ||
|
conditions.GetObservedGeneration(obj, sourcev1.SourceVerifiedCondition) != obj.Generation ||
|
||||||
conditions.IsFalse(obj, sourcev1.SourceVerifiedCondition) {
|
conditions.IsFalse(obj, sourcev1.SourceVerifiedCondition) {
|
||||||
err := r.verifyOCISourceSignature(ctx, obj, url, keychain)
|
err := r.verifySignature(ctx, obj, url, keychain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
provider := obj.Spec.Verify.Provider
|
provider := obj.Spec.Verify.Provider
|
||||||
if obj.Spec.Verify.SecretRef == nil {
|
if obj.Spec.Verify.SecretRef == nil {
|
||||||
|
@ -447,121 +418,173 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
|
||||||
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of digest %s", revision)
|
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of digest %s", revision)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the content of the first artifact layer
|
// Skip pulling if the artifact revision hasn't changes
|
||||||
if !obj.GetArtifact().HasRevision(revision) {
|
if obj.GetArtifact().HasRevision(revision) {
|
||||||
layers, err := img.Layers()
|
conditions.Delete(obj, sourcev1.FetchFailedCondition)
|
||||||
|
return sreconcile.ResultSuccess, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull artifact from the remote container registry
|
||||||
|
img, err := crane.Pull(url, options...)
|
||||||
|
if err != nil {
|
||||||
|
e := serror.NewGeneric(
|
||||||
|
fmt.Errorf("failed to pull artifact from '%s': %w", obj.Spec.URL, err),
|
||||||
|
sourcev1.OCIPullFailedReason,
|
||||||
|
)
|
||||||
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
||||||
|
return sreconcile.ResultEmpty, e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the OCI annotations to the internal artifact metadata
|
||||||
|
manifest, err := img.Manifest()
|
||||||
|
if err != nil {
|
||||||
|
e := serror.NewGeneric(
|
||||||
|
fmt.Errorf("failed to parse artifact manifest: %w", err),
|
||||||
|
sourcev1.OCILayerOperationFailedReason,
|
||||||
|
)
|
||||||
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
||||||
|
return sreconcile.ResultEmpty, e
|
||||||
|
}
|
||||||
|
metadata.Metadata = manifest.Annotations
|
||||||
|
|
||||||
|
// Extract the compressed content from the selected layer
|
||||||
|
blob, err := r.getLayerCompressed(obj, img)
|
||||||
|
if err != nil {
|
||||||
|
e := serror.NewGeneric(err, sourcev1.OCILayerOperationFailedReason)
|
||||||
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
||||||
|
return sreconcile.ResultEmpty, e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist layer content to storage using the specified operation
|
||||||
|
switch obj.GetLayerOperation() {
|
||||||
|
case sourcev1.OCILayerExtract:
|
||||||
|
if _, err = untar.Untar(blob, dir); err != nil {
|
||||||
|
e := serror.NewGeneric(
|
||||||
|
fmt.Errorf("failed to extract layer contents from artifact: %w", err),
|
||||||
|
sourcev1.OCILayerOperationFailedReason,
|
||||||
|
)
|
||||||
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
||||||
|
return sreconcile.ResultEmpty, e
|
||||||
|
}
|
||||||
|
case sourcev1.OCILayerCopy:
|
||||||
|
metadata.Path = fmt.Sprintf("%s.tgz", r.digestFromRevision(metadata.Revision))
|
||||||
|
file, err := os.Create(filepath.Join(dir, metadata.Path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e := serror.NewGeneric(
|
e := serror.NewGeneric(
|
||||||
fmt.Errorf("failed to parse artifact layers: %w", err),
|
fmt.Errorf("failed to create file to copy layer to: %w", err),
|
||||||
sourcev1.OCILayerOperationFailedReason,
|
sourcev1.OCILayerOperationFailedReason,
|
||||||
)
|
)
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
||||||
return sreconcile.ResultEmpty, e
|
return sreconcile.ResultEmpty, e
|
||||||
}
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
if len(layers) < 1 {
|
_, err = io.Copy(file, blob)
|
||||||
e := serror.NewGeneric(
|
|
||||||
fmt.Errorf("no layers found in artifact"),
|
|
||||||
sourcev1.OCILayerOperationFailedReason,
|
|
||||||
)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
|
|
||||||
var layer gcrv1.Layer
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case obj.GetLayerMediaType() != "":
|
|
||||||
var found bool
|
|
||||||
for i, l := range layers {
|
|
||||||
md, err := l.MediaType()
|
|
||||||
if err != nil {
|
|
||||||
e := serror.NewGeneric(
|
|
||||||
fmt.Errorf("failed to determine the media type of layer[%v] from artifact: %w", i, err),
|
|
||||||
sourcev1.OCILayerOperationFailedReason,
|
|
||||||
)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
if string(md) == obj.GetLayerMediaType() {
|
|
||||||
layer = layers[i]
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
e := serror.NewGeneric(
|
|
||||||
fmt.Errorf("failed to find layer with media type '%s' in artifact", obj.GetLayerMediaType()),
|
|
||||||
sourcev1.OCILayerOperationFailedReason,
|
|
||||||
)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
layer = layers[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the compressed content from the selected layer
|
|
||||||
blob, err := layer.Compressed()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e := serror.NewGeneric(
|
e := serror.NewGeneric(
|
||||||
fmt.Errorf("failed to extract the first layer from artifact: %w", err),
|
fmt.Errorf("failed to copy layer from artifact: %w", err),
|
||||||
sourcev1.OCILayerOperationFailedReason,
|
|
||||||
)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
|
|
||||||
// Persist layer content to storage using the specified operation
|
|
||||||
switch obj.GetLayerOperation() {
|
|
||||||
case sourcev1.OCILayerExtract:
|
|
||||||
if _, err = untar.Untar(blob, dir); err != nil {
|
|
||||||
e := serror.NewGeneric(
|
|
||||||
fmt.Errorf("failed to extract layer contents from artifact: %w", err),
|
|
||||||
sourcev1.OCILayerOperationFailedReason,
|
|
||||||
)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
case sourcev1.OCILayerCopy:
|
|
||||||
metadata.Path = fmt.Sprintf("%s.tgz", metadata.Revision)
|
|
||||||
file, err := os.Create(filepath.Join(dir, metadata.Path))
|
|
||||||
if err != nil {
|
|
||||||
e := serror.NewGeneric(
|
|
||||||
fmt.Errorf("failed to create file to copy layer to: %w", err),
|
|
||||||
sourcev1.OCILayerOperationFailedReason,
|
|
||||||
)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(file, blob)
|
|
||||||
if err != nil {
|
|
||||||
e := serror.NewGeneric(
|
|
||||||
fmt.Errorf("failed to copy layer from artifact: %w", err),
|
|
||||||
sourcev1.OCILayerOperationFailedReason,
|
|
||||||
)
|
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
|
||||||
return sreconcile.ResultEmpty, e
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
e := serror.NewGeneric(
|
|
||||||
fmt.Errorf("unsupported layer operation: %s", obj.GetLayerOperation()),
|
|
||||||
sourcev1.OCILayerOperationFailedReason,
|
sourcev1.OCILayerOperationFailedReason,
|
||||||
)
|
)
|
||||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
||||||
return sreconcile.ResultEmpty, e
|
return sreconcile.ResultEmpty, e
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
e := serror.NewGeneric(
|
||||||
|
fmt.Errorf("unsupported layer operation: %s", obj.GetLayerOperation()),
|
||||||
|
sourcev1.OCILayerOperationFailedReason,
|
||||||
|
)
|
||||||
|
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
||||||
|
return sreconcile.ResultEmpty, e
|
||||||
}
|
}
|
||||||
|
|
||||||
conditions.Delete(obj, sourcev1.FetchFailedCondition)
|
conditions.Delete(obj, sourcev1.FetchFailedCondition)
|
||||||
return sreconcile.ResultSuccess, nil
|
return sreconcile.ResultSuccess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyOCISourceSignature verifies the authenticity of the given image reference url. First, it tries using a key
|
// getLayerCompressed finds the matching layer and returns its compress contents
|
||||||
|
func (r *OCIRepositoryReconciler) getLayerCompressed(obj *sourcev1.OCIRepository, image gcrv1.Image) (io.ReadCloser, error) {
|
||||||
|
layers, err := image.Layers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse artifact layers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(layers) < 1 {
|
||||||
|
return nil, fmt.Errorf("no layers found in artifact")
|
||||||
|
}
|
||||||
|
|
||||||
|
var layer gcrv1.Layer
|
||||||
|
switch {
|
||||||
|
case obj.GetLayerMediaType() != "":
|
||||||
|
var found bool
|
||||||
|
for i, l := range layers {
|
||||||
|
md, err := l.MediaType()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to determine the media type of layer[%v] from artifact: %w", i, err)
|
||||||
|
}
|
||||||
|
if string(md) == obj.GetLayerMediaType() {
|
||||||
|
layer = layers[i]
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("failed to find layer with media type '%s' in artifact", obj.GetLayerMediaType())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
layer = layers[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
blob, err := layer.Compressed()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to extract the first layer from artifact: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return blob, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRevision fetches the upstream digest and returns the revision in the format `<tag>/<digest>`
|
||||||
|
func (r *OCIRepositoryReconciler) getRevision(url string, options []crane.Option) (string, error) {
|
||||||
|
ref, err := name.ParseReference(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoTag := ""
|
||||||
|
repoName := strings.TrimPrefix(url, ref.Context().RegistryStr())
|
||||||
|
if s := strings.Split(repoName, ":"); len(s) == 2 && !strings.Contains(repoName, "@") {
|
||||||
|
repoTag = s[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if repoTag == "" && !strings.Contains(repoName, "@") {
|
||||||
|
repoTag = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
digest, err := crane.Digest(url, options...)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
digestHash, err := gcrv1.NewHash(digest)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
revision := digestHash.Hex
|
||||||
|
if repoTag != "" {
|
||||||
|
revision = fmt.Sprintf("%s/%s", repoTag, digestHash.Hex)
|
||||||
|
}
|
||||||
|
return revision, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// digestFromRevision extract the digest from the revision string
|
||||||
|
func (r *OCIRepositoryReconciler) digestFromRevision(revision string) string {
|
||||||
|
parts := strings.Split(revision, "/")
|
||||||
|
return parts[len(parts)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifySignature verifies the authenticity of the given image reference url. First, it tries using a key
|
||||||
// if a secret with a valid public key is provided. If not, it falls back to a keyless approach for verification.
|
// if a secret with a valid public key is provided. If not, it falls back to a keyless approach for verification.
|
||||||
func (r *OCIRepositoryReconciler) verifyOCISourceSignature(ctx context.Context, obj *sourcev1.OCIRepository, url string, keychain authn.Keychain) error {
|
func (r *OCIRepositoryReconciler) verifySignature(ctx context.Context, obj *sourcev1.OCIRepository, url string, keychain authn.Keychain) error {
|
||||||
ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
|
ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
@ -856,8 +879,7 @@ func (r *OCIRepositoryReconciler) craneOptions(ctx context.Context, insecure boo
|
||||||
// condition is added.
|
// condition is added.
|
||||||
// The hostname of any URL in the Status of the object are updated, to ensure
|
// The hostname of any URL in the Status of the object are updated, to ensure
|
||||||
// they match the Storage server hostname of current runtime.
|
// they match the Storage server hostname of current runtime.
|
||||||
func (r *OCIRepositoryReconciler) reconcileStorage(ctx context.Context,
|
func (r *OCIRepositoryReconciler) reconcileStorage(ctx context.Context, obj *sourcev1.OCIRepository, _ *sourcev1.Artifact, _ string) (sreconcile.Result, error) {
|
||||||
obj *sourcev1.OCIRepository, _ *sourcev1.Artifact, _ string) (sreconcile.Result, error) {
|
|
||||||
// Garbage collect previous advertised artifact(s) from storage
|
// Garbage collect previous advertised artifact(s) from storage
|
||||||
_ = r.garbageCollect(ctx, obj)
|
_ = r.garbageCollect(ctx, obj)
|
||||||
|
|
||||||
|
@ -892,13 +914,12 @@ func (r *OCIRepositoryReconciler) reconcileStorage(ctx context.Context,
|
||||||
// early.
|
// early.
|
||||||
// On a successful archive, the Artifact in the Status of the object is set,
|
// On a successful archive, the Artifact in the Status of the object is set,
|
||||||
// and the symlink in the Storage is updated to its path.
|
// and the symlink in the Storage is updated to its path.
|
||||||
func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context,
|
func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context, obj *sourcev1.OCIRepository, metadata *sourcev1.Artifact, dir string) (sreconcile.Result, error) {
|
||||||
obj *sourcev1.OCIRepository, metadata *sourcev1.Artifact, dir string) (sreconcile.Result, error) {
|
|
||||||
// Calculate revision
|
|
||||||
revision := metadata.Revision
|
revision := metadata.Revision
|
||||||
|
|
||||||
// Create artifact
|
// Create artifact
|
||||||
artifact := r.Storage.NewArtifactFor(obj.Kind, obj, revision, fmt.Sprintf("%s.tar.gz", revision))
|
artifact := r.Storage.NewArtifactFor(obj.Kind, obj, revision,
|
||||||
|
fmt.Sprintf("%s.tar.gz", r.digestFromRevision(revision)))
|
||||||
|
|
||||||
// Set the ArtifactInStorageCondition if there's no drift.
|
// Set the ArtifactInStorageCondition if there's no drift.
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -1047,8 +1068,7 @@ func (r *OCIRepositoryReconciler) garbageCollect(ctx context.Context, obj *sourc
|
||||||
// This log is different from the debug log in the EventRecorder, in the sense
|
// This log is different from the debug log in the EventRecorder, in the sense
|
||||||
// that this is a simple log. While the debug log contains complete details
|
// that this is a simple log. While the debug log contains complete details
|
||||||
// about the event.
|
// about the event.
|
||||||
func (r *OCIRepositoryReconciler) eventLogf(ctx context.Context,
|
func (r *OCIRepositoryReconciler) eventLogf(ctx context.Context, obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) {
|
||||||
obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) {
|
|
||||||
msg := fmt.Sprintf(messageFmt, args...)
|
msg := fmt.Sprintf(messageFmt, args...)
|
||||||
// Log and emit event.
|
// Log and emit event.
|
||||||
if eventType == corev1.EventTypeWarning {
|
if eventType == corev1.EventTypeWarning {
|
||||||
|
@ -1060,8 +1080,7 @@ func (r *OCIRepositoryReconciler) eventLogf(ctx context.Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify emits notification related to the reconciliation.
|
// notify emits notification related to the reconciliation.
|
||||||
func (r *OCIRepositoryReconciler) notify(ctx context.Context,
|
func (r *OCIRepositoryReconciler) notify(ctx context.Context, oldObj, newObj *sourcev1.OCIRepository, res sreconcile.Result, resErr error) {
|
||||||
oldObj, newObj *sourcev1.OCIRepository, res sreconcile.Result, resErr error) {
|
|
||||||
// Notify successful reconciliation for new artifact and recovery from any
|
// Notify successful reconciliation for new artifact and recovery from any
|
||||||
// failure.
|
// failure.
|
||||||
if resErr == nil && res == sreconcile.ResultSuccess && newObj.Status.Artifact != nil {
|
if resErr == nil && res == sreconcile.ResultSuccess && newObj.Status.Artifact != nil {
|
||||||
|
|
|
@ -92,7 +92,7 @@ func TestOCIRepository_Reconcile(t *testing.T) {
|
||||||
name: "public tag",
|
name: "public tag",
|
||||||
url: podinfoVersions["6.1.6"].url,
|
url: podinfoVersions["6.1.6"].url,
|
||||||
tag: podinfoVersions["6.1.6"].tag,
|
tag: podinfoVersions["6.1.6"].tag,
|
||||||
digest: podinfoVersions["6.1.6"].digest.Hex,
|
digest: fmt.Sprintf("%s/%s", podinfoVersions["6.1.6"].tag, podinfoVersions["6.1.6"].digest.Hex),
|
||||||
mediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
mediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||||
operation: sourcev1.OCILayerCopy,
|
operation: sourcev1.OCILayerCopy,
|
||||||
assertArtifact: []artifactFixture{
|
assertArtifact: []artifactFixture{
|
||||||
|
@ -110,7 +110,7 @@ func TestOCIRepository_Reconcile(t *testing.T) {
|
||||||
name: "public semver",
|
name: "public semver",
|
||||||
url: podinfoVersions["6.1.5"].url,
|
url: podinfoVersions["6.1.5"].url,
|
||||||
semver: ">= 6.1 <= 6.1.5",
|
semver: ">= 6.1 <= 6.1.5",
|
||||||
digest: podinfoVersions["6.1.5"].digest.Hex,
|
digest: fmt.Sprintf("%s/%s", podinfoVersions["6.1.5"].tag, podinfoVersions["6.1.5"].digest.Hex),
|
||||||
assertArtifact: []artifactFixture{
|
assertArtifact: []artifactFixture{
|
||||||
{
|
{
|
||||||
expectedPath: "kustomize/deployment.yaml",
|
expectedPath: "kustomize/deployment.yaml",
|
||||||
|
@ -449,7 +449,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to pull artifact from "),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to determine artifact digest"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -470,7 +470,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
|
||||||
includeSecret: true,
|
includeSecret: true,
|
||||||
},
|
},
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to pull artifact from "),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "UNAUTHORIZED"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -491,7 +491,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
|
||||||
includeSA: true,
|
includeSA: true,
|
||||||
},
|
},
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to pull artifact from "),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "UNAUTHORIZED"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -533,7 +533,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to pull artifact from "),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to determine artifact digest"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -558,7 +558,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to pull artifact from "),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to determine artifact digest"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -683,7 +683,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) {
|
||||||
|
|
||||||
assertConditions := tt.assertConditions
|
assertConditions := tt.assertConditions
|
||||||
for k := range assertConditions {
|
for k := range assertConditions {
|
||||||
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<digest>", img.digest.Hex)
|
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<digest>", fmt.Sprintf("%s/%s", img.tag, img.digest.Hex))
|
||||||
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<url>", repoURL)
|
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<url>", repoURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -871,7 +871,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "no reference (latest tag)",
|
name: "no reference (latest tag)",
|
||||||
want: sreconcile.ResultSuccess,
|
want: sreconcile.ResultSuccess,
|
||||||
wantRevision: img6.digest.Hex,
|
wantRevision: fmt.Sprintf("latest/%s", img6.digest.Hex),
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest"),
|
*conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest"),
|
||||||
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
|
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
|
||||||
|
@ -883,7 +883,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
|
||||||
Tag: "6.1.6",
|
Tag: "6.1.6",
|
||||||
},
|
},
|
||||||
want: sreconcile.ResultSuccess,
|
want: sreconcile.ResultSuccess,
|
||||||
wantRevision: img6.digest.Hex,
|
wantRevision: fmt.Sprintf("%s/%s", img6.tag, img6.digest.Hex),
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest"),
|
*conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest"),
|
||||||
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
|
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
|
||||||
|
@ -895,7 +895,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
|
||||||
SemVer: ">= 6.1.5",
|
SemVer: ">= 6.1.5",
|
||||||
},
|
},
|
||||||
want: sreconcile.ResultSuccess,
|
want: sreconcile.ResultSuccess,
|
||||||
wantRevision: img6.digest.Hex,
|
wantRevision: fmt.Sprintf("%s/%s", img6.tag, img6.digest.Hex),
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest"),
|
*conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest"),
|
||||||
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
|
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
|
||||||
|
@ -921,7 +921,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
|
||||||
want: sreconcile.ResultEmpty,
|
want: sreconcile.ResultEmpty,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to pull artifact"),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, " MANIFEST_UNKNOWN"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -943,7 +943,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
|
||||||
want: sreconcile.ResultEmpty,
|
want: sreconcile.ResultEmpty,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to pull artifact"),
|
*conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.OCIPullFailedReason, "failed to determine artifact digest"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -953,7 +953,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) {
|
||||||
Tag: "6.1.5",
|
Tag: "6.1.5",
|
||||||
},
|
},
|
||||||
want: sreconcile.ResultSuccess,
|
want: sreconcile.ResultSuccess,
|
||||||
wantRevision: img6.digest.Hex,
|
wantRevision: fmt.Sprintf("%s/%s", img6.tag, img6.digest.Hex),
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
*conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest"),
|
*conditions.TrueCondition(meta.ReconcilingCondition, "NewRevision", "new digest"),
|
||||||
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
|
*conditions.TrueCondition(sourcev1.ArtifactOutdatedCondition, "NewRevision", "new digest"),
|
||||||
|
@ -1091,7 +1091,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
|
||||||
beforeFunc: func(obj *sourcev1.OCIRepository) {
|
beforeFunc: func(obj *sourcev1.OCIRepository) {
|
||||||
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, "VerifyFailed", "fail msg")
|
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, "VerifyFailed", "fail msg")
|
||||||
obj.Spec.Verify = nil
|
obj.Spec.Verify = nil
|
||||||
obj.Status.Artifact = &sourcev1.Artifact{Revision: img4.digest.Hex}
|
obj.Status.Artifact = &sourcev1.Artifact{Revision: fmt.Sprintf("%s/%s", img4.tag, img4.digest.Hex)}
|
||||||
},
|
},
|
||||||
want: sreconcile.ResultSuccess,
|
want: sreconcile.ResultSuccess,
|
||||||
},
|
},
|
||||||
|
@ -1101,7 +1101,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
|
||||||
digest: img4.digest.Hex,
|
digest: img4.digest.Hex,
|
||||||
shouldSign: true,
|
shouldSign: true,
|
||||||
beforeFunc: func(obj *sourcev1.OCIRepository) {
|
beforeFunc: func(obj *sourcev1.OCIRepository) {
|
||||||
obj.Status.Artifact = &sourcev1.Artifact{Revision: img4.digest.Hex}
|
obj.Status.Artifact = &sourcev1.Artifact{Revision: fmt.Sprintf("%s/%s", img4.tag, img4.digest.Hex)}
|
||||||
// Set Verified with old observed generation and different reason/message.
|
// Set Verified with old observed generation and different reason/message.
|
||||||
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, "Verified", "verified")
|
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, "Verified", "verified")
|
||||||
// Set new object generation.
|
// Set new object generation.
|
||||||
|
@ -1119,7 +1119,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
|
||||||
shouldSign: true,
|
shouldSign: true,
|
||||||
beforeFunc: func(obj *sourcev1.OCIRepository) {
|
beforeFunc: func(obj *sourcev1.OCIRepository) {
|
||||||
// Artifact present and custom verified condition reason/message.
|
// Artifact present and custom verified condition reason/message.
|
||||||
obj.Status.Artifact = &sourcev1.Artifact{Revision: img4.digest.Hex}
|
obj.Status.Artifact = &sourcev1.Artifact{Revision: fmt.Sprintf("%s/%s", img4.tag, img4.digest.Hex)}
|
||||||
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, "Verified", "verified")
|
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, "Verified", "verified")
|
||||||
},
|
},
|
||||||
want: sreconcile.ResultSuccess,
|
want: sreconcile.ResultSuccess,
|
||||||
|
@ -1213,7 +1213,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
|
||||||
|
|
||||||
assertConditions := tt.assertConditions
|
assertConditions := tt.assertConditions
|
||||||
for k := range assertConditions {
|
for k := range assertConditions {
|
||||||
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<digest>", tt.digest)
|
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<digest>", fmt.Sprintf("%s/%s", tt.reference.Tag, tt.digest))
|
||||||
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<url>", artifactURL)
|
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<url>", artifactURL)
|
||||||
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<provider>", "cosign")
|
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<provider>", "cosign")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue