577 lines
19 KiB
Go
577 lines
19 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/client-go/dynamic/fake"
|
|
)
|
|
|
|
const testDigest = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
|
|
func TestRetentionTagOwnership(t *testing.T) {
|
|
t.Parallel()
|
|
for _, test := range []struct {
|
|
tag string
|
|
valid bool
|
|
}{
|
|
{"", true},
|
|
{"stable", true},
|
|
{"sha256-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", true},
|
|
{"sha256-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", false},
|
|
{"sha256-release", true},
|
|
} {
|
|
t.Run(test.tag, func(t *testing.T) {
|
|
image := testImage()
|
|
image.Spec.Digests = []digestSpec{{Digest: testDigest, Tag: test.tag}}
|
|
err := validateImage(&image)
|
|
if (err == nil) != test.valid {
|
|
t.Fatalf("RetentionTags.AliasAllowed: valid=%t err=%v", test.valid, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type fakeRegistry struct {
|
|
scanPhase string
|
|
present map[string]bool
|
|
stored map[string]bool
|
|
}
|
|
|
|
func (f *fakeRegistry) scan(context.Context, string, string, string) (string, error) {
|
|
return f.scanPhase, nil
|
|
}
|
|
|
|
func (f *fakeRegistry) exists(_ context.Context, repository, digest string) (bool, error) {
|
|
return f.present[repository+"@"+digest], nil
|
|
}
|
|
|
|
func (f *fakeRegistry) physicallyPresent(_ context.Context, repository, digest string) (bool, error) {
|
|
if f.stored != nil {
|
|
return f.stored[repository+"@"+digest], nil
|
|
}
|
|
return f.present[repository+"@"+digest], nil
|
|
}
|
|
|
|
func TestCleanupObservedMeansGone(t *testing.T) {
|
|
t.Parallel()
|
|
for _, repository := range []string{"candidates/pause", "mirror/pause"} {
|
|
t.Run(repository, func(t *testing.T) {
|
|
registry := &fakeRegistry{stored: map[string]bool{repository + "@" + testDigest: true}}
|
|
instance := testController(newClient(), registry)
|
|
image := testImage()
|
|
image.Spec.Digests = nil
|
|
image.Status.Digests = []digestStatus{{Digest: testDigest, Cleanup: &attemptStatus{WorkflowName: "cleanup", Phase: "Succeeded"}}}
|
|
reconcileDesired(context.Background(), t, instance, &image)
|
|
if len(image.Status.Digests) != 1 {
|
|
t.Fatal("CleanupObservedMeansGone: physical manifest was forgotten")
|
|
}
|
|
delete(registry.stored, repository+"@"+testDigest)
|
|
reconcileDesired(context.Background(), t, instance, &image)
|
|
if len(image.Status.Digests) != 0 {
|
|
t.Fatal("physically absent digest was retained")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCopyRetryRearmsCleanup(t *testing.T) {
|
|
t.Parallel()
|
|
for _, stage := range []string{prepareStage, promotionStage} {
|
|
for _, readd := range []bool{false, true} {
|
|
t.Run(fmt.Sprintf("%s/readd=%t", stage, readd), func(t *testing.T) {
|
|
ctx := context.Background()
|
|
client := newClient()
|
|
registry := &fakeRegistry{scanPhase: scanAccepted, stored: map[string]bool{}}
|
|
instance := testController(client, registry)
|
|
image := testImage()
|
|
image.Spec.Digests = nil
|
|
image.Status.Digests = []digestStatus{{
|
|
Digest: testDigest, Tag: "stable", Scan: scanAccepted,
|
|
Prepare: &attemptStatus{WorkflowName: "prepare", Phase: "Succeeded"},
|
|
Promotion: &attemptStatus{WorkflowName: "promotion", Phase: "Succeeded"},
|
|
}}
|
|
status := &image.Status.Digests[0]
|
|
status.Cleanup = instance.newAttempt(&image, status, cleanupStage, "")
|
|
status.Cleanup.Phase = "Succeeded"
|
|
oldCleanup := status.Cleanup.WorkflowName
|
|
if _, err := client.Resource(workflowGVR).Namespace("mirrored-image").Create(ctx, workflowObject(oldCleanup, "Succeeded"), metav1.CreateOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
image.Metadata.Annotations = map[string]string{retryAnnotation: stage}
|
|
for pass := 0; pass < 2; pass++ {
|
|
handled, err := instance.reconcileRetry(ctx, &image)
|
|
if err != nil || !handled {
|
|
t.Fatalf("retry pass %d: handled=%t err=%v", pass, handled, err)
|
|
}
|
|
if pass == 0 {
|
|
if image.Status.LastHandledRetry != "" {
|
|
t.Fatal("RetryAcknowledgedAfterCreation")
|
|
}
|
|
if readd {
|
|
image.Spec.Digests = []digestSpec{{Digest: testDigest, Tag: "edited"}}
|
|
}
|
|
}
|
|
}
|
|
if image.Status.LastHandledRetry != stage {
|
|
t.Fatal("retry was not acknowledged")
|
|
}
|
|
copy := status.Prepare
|
|
if stage == promotionStage {
|
|
copy = status.Promotion
|
|
}
|
|
setPhase(ctx, t, client, copy.WorkflowName, "Succeeded")
|
|
registry.stored["candidates/pause@"+testDigest] = true
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
if status.Cleanup == nil || status.Cleanup.WorkflowName == oldCleanup || terminalPhase(status.Cleanup.Phase) {
|
|
t.Fatal("copy retry reused completed cleanup")
|
|
}
|
|
fresh := status.Cleanup.WorkflowName
|
|
if _, err := client.Resource(workflowGVR).Namespace("mirrored-image").Get(ctx, fresh, metav1.GetOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if status.Tag != "stable" {
|
|
t.Fatal("RetryPreservesTagIdentity")
|
|
}
|
|
setPhase(ctx, t, client, fresh, "Succeeded")
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
if len(image.Status.Digests) != 1 {
|
|
t.Fatal("CleanupObservedMeansGone")
|
|
}
|
|
delete(registry.stored, "candidates/pause@"+testDigest)
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
if readd {
|
|
if status.Cleanup != nil || status.Prepare != nil || status.Tag != "edited" {
|
|
t.Fatal("re-addition did not wait for fresh cleanup")
|
|
}
|
|
} else if len(image.Status.Digests) != 0 {
|
|
t.Fatal("retirement did not complete")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDigestLifecycle(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
client := newClient()
|
|
registry := &fakeRegistry{scanPhase: scanAccepted, present: map[string]bool{}}
|
|
instance := testController(client, registry)
|
|
image := testImage()
|
|
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
status := &image.Status.Digests[0]
|
|
if status.Prepare == nil || status.Prepare.WorkflowName == "" {
|
|
t.Fatal("prepare workflow was not recorded")
|
|
}
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
setPhase(ctx, t, client, status.Prepare.WorkflowName, "Succeeded")
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
if image.Status.Digests[0].Scan != scanAccepted {
|
|
t.Fatal("candidate scan was not accepted")
|
|
}
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
status = &image.Status.Digests[0]
|
|
if status.Promotion == nil || status.Promotion.WorkflowName == "" {
|
|
t.Fatal("promotion workflow was not recorded")
|
|
}
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
setPhase(ctx, t, client, status.Promotion.WorkflowName, "Succeeded")
|
|
registry.present["mirror/pause@"+testDigest] = true
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
|
|
image.Metadata.Generation++
|
|
image.Spec.Digests = nil
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
status = &image.Status.Digests[0]
|
|
if status.Cleanup == nil || status.Cleanup.WorkflowName == "" {
|
|
t.Fatal("cleanup workflow was not recorded")
|
|
}
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
setPhase(ctx, t, client, status.Cleanup.WorkflowName, "Succeeded")
|
|
delete(registry.present, "mirror/pause@"+testDigest)
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
if len(image.Status.Digests) != 0 {
|
|
t.Fatalf("cleanup retained %d digest statuses", len(image.Status.Digests))
|
|
}
|
|
}
|
|
|
|
func TestAttemptIsRecordedFirst(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
client := newClient()
|
|
instance := testController(client, &fakeRegistry{present: map[string]bool{}})
|
|
image := testImage()
|
|
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
if image.Status.Digests[0].Prepare == nil {
|
|
t.Fatal("prepare attempt was not recorded")
|
|
}
|
|
workflows, err := client.Resource(workflowGVR).Namespace("mirrored-image").List(ctx, metav1.ListOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(workflows.Items) != 0 {
|
|
t.Fatal("workflow existed before its status record")
|
|
}
|
|
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
workflows, err = client.Resource(workflowGVR).Namespace("mirrored-image").List(ctx, metav1.ListOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(workflows.Items) != 1 || workflows.Items[0].GetName() != image.Status.Digests[0].Prepare.WorkflowName {
|
|
t.Fatal("recorded prepare workflow was not created")
|
|
}
|
|
}
|
|
|
|
func TestCleanupWaitsForAbsence(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
client := newClient()
|
|
registry := &fakeRegistry{present: map[string]bool{"candidates/pause@" + testDigest: true}}
|
|
instance := testController(client, registry)
|
|
image := testImage()
|
|
image.Spec.Digests = nil
|
|
image.Status.Digests = []digestStatus{{
|
|
Digest: testDigest,
|
|
Cleanup: &attemptStatus{WorkflowName: "cleanup", Phase: "Succeeded"},
|
|
}}
|
|
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
if len(image.Status.Digests) != 1 || image.Status.Message != "cleanup: awaiting repository GC" {
|
|
t.Fatal("cleanup completed while the candidate remained present")
|
|
}
|
|
delete(registry.present, "candidates/pause@"+testDigest)
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
if len(image.Status.Digests) != 0 {
|
|
t.Fatal("cleanup state remained after both repositories were absent")
|
|
}
|
|
}
|
|
|
|
func TestReaddWaitsForCleanup(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
client := newClient()
|
|
registry := &fakeRegistry{scanPhase: scanAccepted, present: map[string]bool{}}
|
|
instance := testController(client, registry)
|
|
image := testImage()
|
|
oldPrepare := attemptName(&image, testDigest, prepareStage, image.Metadata.Generation, "")
|
|
image.Metadata.Generation = 3
|
|
image.Status.Digests = []digestStatus{{
|
|
Digest: testDigest,
|
|
Tag: "stable",
|
|
Prepare: &attemptStatus{WorkflowName: oldPrepare, Phase: "Succeeded", Generation: 1},
|
|
Scan: scanAccepted,
|
|
Promotion: &attemptStatus{WorkflowName: "old-promotion", Phase: "Succeeded"},
|
|
Cleanup: &attemptStatus{WorkflowName: "old-cleanup", Phase: "Running"},
|
|
}}
|
|
workflow := workflowObject("old-cleanup", "Running")
|
|
if _, err := client.Resource(workflowGVR).Namespace("mirrored-image").Create(ctx, workflow, metav1.CreateOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
if image.Status.Digests[0].Prepare.WorkflowName != oldPrepare {
|
|
t.Fatal("prepare restarted while cleanup was active")
|
|
}
|
|
setPhase(ctx, t, client, "old-cleanup", "Succeeded")
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
status := image.Status.Digests[0]
|
|
if status.Cleanup != nil || status.Prepare != nil {
|
|
t.Fatal("completed cleanup did not reset digest state")
|
|
}
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
if image.Status.Digests[0].Prepare == nil {
|
|
t.Fatal("prepare did not restart after cleanup")
|
|
}
|
|
if image.Status.Digests[0].Prepare.WorkflowName == oldPrepare {
|
|
t.Fatal("prepare reused the workflow from an earlier generation")
|
|
}
|
|
}
|
|
|
|
func TestRetryCreatesSuccessor(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
client := newClient()
|
|
registry := &fakeRegistry{scanPhase: scanAccepted, present: map[string]bool{}}
|
|
instance := testController(client, registry)
|
|
image := testImage()
|
|
image.Status.Digests = []digestStatus{{
|
|
Digest: testDigest,
|
|
Tag: "stable",
|
|
Prepare: &attemptStatus{WorkflowName: "failed-prepare", Phase: "Failed"},
|
|
}}
|
|
image.Metadata.Annotations = map[string]string{retryAnnotation: "failed-prepare"}
|
|
|
|
retried, err := instance.reconcileRetry(ctx, &image)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !retried {
|
|
t.Fatal("terminal retry was not handled")
|
|
}
|
|
attempt := image.Status.Digests[0].Prepare
|
|
if attempt.RetryOf != "failed-prepare" || attempt.WorkflowName == "failed-prepare" {
|
|
t.Fatalf("invalid successor: %#v", attempt)
|
|
}
|
|
if image.Status.LastHandledRetry != "" {
|
|
t.Fatal("retry was acknowledged before the successor existed")
|
|
}
|
|
retried, err = instance.reconcileRetry(ctx, &image)
|
|
if err != nil || !retried {
|
|
t.Fatalf("create retry successor: retried=%v error=%v", retried, err)
|
|
}
|
|
if image.Status.LastHandledRetry != "failed-prepare" {
|
|
t.Fatal("retry was not acknowledged after the successor existed")
|
|
}
|
|
setPhase(ctx, t, client, attempt.WorkflowName, "Succeeded")
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
promotion := image.Status.Digests[0].Promotion
|
|
if promotion == nil || promotion.WorkflowName == "" {
|
|
t.Fatal("promotion was not created after the retry")
|
|
}
|
|
oldName := attemptName(&image, testDigest, promotionStage, image.Metadata.Generation)
|
|
if promotion.WorkflowName == oldName {
|
|
t.Fatal("promotion reused the predecessor prepare lineage")
|
|
}
|
|
}
|
|
|
|
func TestRetryNameIgnoresGeneration(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
client := newClient()
|
|
instance := testController(client, &fakeRegistry{present: map[string]bool{}})
|
|
image := testImage()
|
|
image.Status.Digests = []digestStatus{{
|
|
Digest: testDigest,
|
|
Prepare: &attemptStatus{WorkflowName: "failed-prepare", Phase: "Failed"},
|
|
}}
|
|
image.Metadata.Annotations = map[string]string{retryAnnotation: "failed-prepare"}
|
|
|
|
if _, err := instance.reconcileRetry(ctx, &image); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
first := image.Status.Digests[0].Prepare.WorkflowName
|
|
image.Metadata.Generation++
|
|
image.Status.Digests[0].Prepare = &attemptStatus{WorkflowName: "failed-prepare", Phase: "Failed"}
|
|
image.Status.LastHandledRetry = ""
|
|
if _, err := instance.reconcileRetry(ctx, &image); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if second := image.Status.Digests[0].Prepare.WorkflowName; second != first {
|
|
t.Fatalf("retry generation changed successor from %q to %q", first, second)
|
|
}
|
|
}
|
|
|
|
func TestRunStopsWhenIdle(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
client := newClient()
|
|
instance := testController(client, &fakeRegistry{present: map[string]bool{}})
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- instance.run(ctx)
|
|
}()
|
|
time.Sleep(20 * time.Millisecond)
|
|
cancel()
|
|
select {
|
|
case err := <-done:
|
|
if !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("run returned %v", err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("run did not stop")
|
|
}
|
|
}
|
|
|
|
func TestRejectedCandidateIsNotPromoted(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
client := newClient()
|
|
registry := &fakeRegistry{scanPhase: scanRejected, present: map[string]bool{}}
|
|
instance := testController(client, registry)
|
|
image := testImage()
|
|
image.Status.Digests = []digestStatus{{
|
|
Digest: testDigest,
|
|
Tag: "stable",
|
|
Prepare: &attemptStatus{WorkflowName: "prepare", Phase: "Succeeded"},
|
|
}}
|
|
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
status := image.Status.Digests[0]
|
|
if status.Scan != scanRejected || status.Promotion != nil {
|
|
t.Fatal("rejected candidate was promoted")
|
|
}
|
|
if image.Status.Message != "scan: Rejected" {
|
|
t.Fatalf("unexpected status message %q", image.Status.Message)
|
|
}
|
|
}
|
|
|
|
func TestTaglessDeclarationIsValid(t *testing.T) {
|
|
t.Parallel()
|
|
image := testImage()
|
|
image.Spec.Digests[0].Tag = ""
|
|
if err := validateImage(&image); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestTagEditPreservesState(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
client := newClient()
|
|
registry := &fakeRegistry{present: map[string]bool{"mirror/pause@" + testDigest: true}}
|
|
instance := testController(client, registry)
|
|
image := testImage()
|
|
image.Spec.Digests[0].Tag = "new"
|
|
image.Status.Digests = []digestStatus{{
|
|
Digest: testDigest,
|
|
Tag: "stable",
|
|
Prepare: &attemptStatus{WorkflowName: "prepare", Phase: "Succeeded"},
|
|
Scan: scanAccepted,
|
|
Promotion: &attemptStatus{WorkflowName: "promotion", Phase: "Succeeded"},
|
|
}}
|
|
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
status := image.Status.Digests[0]
|
|
if status.Tag != "stable" || status.Cleanup != nil {
|
|
t.Fatalf("tag edit changed managed state: %#v", status)
|
|
}
|
|
workflows, err := client.Resource(workflowGVR).Namespace("mirrored-image").List(ctx, metav1.ListOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(workflows.Items) != 0 {
|
|
t.Fatal("tag edit created a workflow")
|
|
}
|
|
}
|
|
|
|
func TestFailureMessage(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
client := newClient()
|
|
registry := &fakeRegistry{present: map[string]bool{}}
|
|
instance := testController(client, registry)
|
|
image := testImage()
|
|
image.Status.Digests = []digestStatus{{
|
|
Digest: testDigest,
|
|
Tag: "stable",
|
|
Prepare: &attemptStatus{WorkflowName: "failed-prepare", Phase: "Failed"},
|
|
}}
|
|
|
|
reconcileDesired(ctx, t, instance, &image)
|
|
if image.Status.Message != "prepare: Failed" {
|
|
t.Fatalf("unexpected status message %q", image.Status.Message)
|
|
}
|
|
}
|
|
|
|
func TestPromotionRescan(t *testing.T) {
|
|
for _, phase := range []string{scanPending, scanRejected, scanAccepted} {
|
|
t.Run(phase, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
client := newClient()
|
|
registry := &fakeRegistry{scanPhase: phase}
|
|
instance := testController(client, registry)
|
|
image := testImage()
|
|
image.Status.Digests = []digestStatus{{
|
|
Digest: testDigest, Scan: scanAccepted,
|
|
Prepare: &attemptStatus{WorkflowName: "prepare", Phase: "Succeeded"},
|
|
Promotion: &attemptStatus{WorkflowName: "promotion", Phase: "Pending"},
|
|
}}
|
|
err := instance.reconcileDesired(ctx, &image)
|
|
if (err == nil) != (phase == scanAccepted) {
|
|
t.Fatalf("scan %s: %v", phase, err)
|
|
}
|
|
workflows, err := client.Resource(workflowGVR).Namespace(image.Metadata.Namespace).List(ctx, metav1.ListOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if (len(workflows.Items) == 1) != (phase == scanAccepted) {
|
|
t.Fatalf("scan %s created %d workflows", phase, len(workflows.Items))
|
|
}
|
|
if phase == scanAccepted {
|
|
registry.scanPhase = scanPending
|
|
if err := instance.reconcileDesired(ctx, &image); err != nil {
|
|
t.Fatalf("existing workflow observation requires a scan: %v", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func testController(client *fake.FakeDynamicClient, registry registryReader) *controller {
|
|
return &controller{client: client, registry: registry, config: controllerConfig{
|
|
prepareTemplate: "image-prepare",
|
|
promotionTemplate: "image-promote",
|
|
cleanupTemplate: "image-cleanup",
|
|
}}
|
|
}
|
|
|
|
func newClient(objects ...runtime.Object) *fake.FakeDynamicClient {
|
|
return fake.NewSimpleDynamicClientWithCustomListKinds(
|
|
runtime.NewScheme(),
|
|
map[schema.GroupVersionResource]string{
|
|
imageGVR: "MirroredImageList",
|
|
workflowGVR: "WorkflowList",
|
|
},
|
|
objects...,
|
|
)
|
|
}
|
|
|
|
func testImage() mirroredImage {
|
|
return mirroredImage{
|
|
Metadata: objectMetadata{Name: "pause", Namespace: "mirrored-image", UID: "image-uid", Generation: 1},
|
|
Spec: mirroredImageSpec{
|
|
Upstream: "registry.k8s.io/pause",
|
|
Policy: scanPolicy{RejectAtOrAbove: "High"},
|
|
Digests: []digestSpec{{Digest: testDigest, Tag: "stable"}},
|
|
},
|
|
}
|
|
}
|
|
|
|
func reconcileDesired(ctx context.Context, t *testing.T, instance *controller, image *mirroredImage) {
|
|
t.Helper()
|
|
if err := instance.reconcileDesired(ctx, image); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func setPhase(ctx context.Context, t *testing.T, client *fake.FakeDynamicClient, name, phase string) {
|
|
t.Helper()
|
|
resource := client.Resource(workflowGVR).Namespace("mirrored-image")
|
|
object, err := resource.Get(ctx, name, metav1.GetOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := unstructured.SetNestedField(object.Object, phase, "status", "phase"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := resource.Update(ctx, object, metav1.UpdateOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func workflowObject(name, phase string) *unstructured.Unstructured {
|
|
return &unstructured.Unstructured{Object: map[string]any{
|
|
"apiVersion": "argoproj.io/v1alpha1",
|
|
"kind": "Workflow",
|
|
"metadata": map[string]any{"name": name, "namespace": "mirrored-image"},
|
|
"status": map[string]any{"phase": phase},
|
|
}}
|
|
}
|