Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions promoter/image/promoter.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ func (p *Promoter) PromoteImages(ctx context.Context, opts *options.Options) err
return fmt.Errorf("parsing manifests: %w", err)
}

if len(mfests) == 0 {
logrus.Info("No manifests to process, nothing to promote")

return pipeline.ErrStopPipeline
}

p.impl.PrintVersion()

promotionEdges, err = p.impl.GetPromotionEdges(ctx, opts, mfests)
Expand Down
37 changes: 36 additions & 1 deletion promoter/image/promoter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,16 @@ import (
"sigs.k8s.io/promo-tools/v4/promoter/image/promotion"
"sigs.k8s.io/promo-tools/v4/promoter/image/provenance"
"sigs.k8s.io/promo-tools/v4/promoter/image/registry"
"sigs.k8s.io/promo-tools/v4/promoter/image/schema"
"sigs.k8s.io/promo-tools/v4/types/image"
)

// nonEmptyManifests returns a minimal manifest slice so that the pipeline
// does not stop early due to an empty manifest list.
func nonEmptyManifests() []schema.Manifest {
return []schema.Manifest{{}}
}

func TestPromoteImages(t *testing.T) {
sut := imagepromoter.Promoter{}
sut.SetProvenanceGenerator(&provenance.PromotionGenerator{})
Expand All @@ -51,7 +58,9 @@ func TestPromoteImages(t *testing.T) {
// No errors
shouldErr: false,
msg: "No errors",
prepare: func(_ *imagefakes.FakePromoterImplementation) {},
prepare: func(fpi *imagefakes.FakePromoterImplementation) {
fpi.ParseManifestsReturns(nonEmptyManifests(), nil)
},
},
{
// ValidateOptions fails
Expand All @@ -78,27 +87,31 @@ func TestPromoteImages(t *testing.T) {
// GetPromotionEdges fails
shouldErr: true,
prepare: func(fpi *imagefakes.FakePromoterImplementation) {
fpi.ParseManifestsReturns(nonEmptyManifests(), nil)
fpi.GetPromotionEdgesReturns(nil, testErr)
},
},
{
// ValidateStagingSignatures fails
shouldErr: true,
prepare: func(fpi *imagefakes.FakePromoterImplementation) {
fpi.ParseManifestsReturns(nonEmptyManifests(), nil)
fpi.ValidateStagingSignaturesReturns(nil, testErr)
},
},
{
// PromoteImages fails
shouldErr: true,
prepare: func(fpi *imagefakes.FakePromoterImplementation) {
fpi.ParseManifestsReturns(nonEmptyManifests(), nil)
fpi.PromoteImagesReturns(testErr)
},
},
{
// SignImages fails
shouldErr: true,
prepare: func(fpi *imagefakes.FakePromoterImplementation) {
fpi.ParseManifestsReturns(nonEmptyManifests(), nil)
fpi.SignImagesReturns(testErr)
},
},
Expand All @@ -107,6 +120,7 @@ func TestPromoteImages(t *testing.T) {
shouldErr: true,
msg: "WriteProvenanceAttestations fails",
prepare: func(fpi *imagefakes.FakePromoterImplementation) {
fpi.ParseManifestsReturns(nonEmptyManifests(), nil)
fpi.WriteProvenanceAttestationsReturns(testErr)
},
},
Expand All @@ -126,6 +140,7 @@ func TestPromoteImages(t *testing.T) {
func TestPromoteImagesParseOnly(t *testing.T) {
sut := imagepromoter.Promoter{}
mock := imagefakes.FakePromoterImplementation{}
mock.ParseManifestsReturns(nonEmptyManifests(), nil)
sut.SetImplementation(&mock)

// ParseOnly should stop after plan phase with no error
Expand All @@ -141,6 +156,7 @@ func TestPromoteImagesParseOnly(t *testing.T) {
func TestPromoteImagesNonConfirm(t *testing.T) {
sut := imagepromoter.Promoter{}
mock := imagefakes.FakePromoterImplementation{}
mock.ParseManifestsReturns(nonEmptyManifests(), nil)
sut.SetImplementation(&mock)
sut.SetProvenanceVerifier(&fakeVerifier{
result: &provenance.Result{Verified: true},
Expand All @@ -156,9 +172,25 @@ func TestPromoteImagesNonConfirm(t *testing.T) {
require.Equal(t, 0, mock.PromoteImagesCallCount())
}

func TestPromoteImagesEmptyManifests(t *testing.T) {
sut := imagepromoter.Promoter{}
mock := imagefakes.FakePromoterImplementation{}
// Return empty manifests (e.g., prow diff found no digests)
mock.ParseManifestsReturns([]schema.Manifest{}, nil)
sut.SetImplementation(&mock)

opts := &options.Options{Confirm: true}
require.NoError(t, sut.PromoteImages(context.Background(), opts))

// No downstream phases should have been called
require.Equal(t, 0, mock.GetPromotionEdgesCallCount())
require.Equal(t, 0, mock.PromoteImagesCallCount())
}

func TestPromoteImagesProvenanceAlwaysRuns(t *testing.T) {
sut := imagepromoter.Promoter{}
mock := imagefakes.FakePromoterImplementation{}
mock.ParseManifestsReturns(nonEmptyManifests(), nil)
mock.GetPromotionEdgesReturns(map[promotion.Edge]any{
testEdge(): nil,
}, nil)
Expand Down Expand Up @@ -199,6 +231,7 @@ func testEdge() promotion.Edge {
func TestPromoteImagesProvenanceFails(t *testing.T) {
sut := imagepromoter.Promoter{}
mock := imagefakes.FakePromoterImplementation{}
mock.ParseManifestsReturns(nonEmptyManifests(), nil)
// Return a non-empty edge set so provenance has something to check
mock.GetPromotionEdgesReturns(map[promotion.Edge]any{
testEdge(): nil,
Expand All @@ -223,6 +256,7 @@ func TestPromoteImagesProvenanceFails(t *testing.T) {
func TestPromoteImagesProvenanceVerifierError(t *testing.T) {
sut := imagepromoter.Promoter{}
mock := imagefakes.FakePromoterImplementation{}
mock.ParseManifestsReturns(nonEmptyManifests(), nil)
mock.GetPromotionEdgesReturns(map[promotion.Edge]any{
testEdge(): nil,
}, nil)
Expand Down Expand Up @@ -325,6 +359,7 @@ func TestNewPromoter(t *testing.T) {
// Verify that a promoter created via New() has the verifier and
// generator configured by running a full pipeline with a mock impl.
mock := imagefakes.FakePromoterImplementation{}
mock.ParseManifestsReturns(nonEmptyManifests(), nil)
p.SetImplementation(&mock)

require.NoError(t, p.PromoteImages(context.Background(), &options.Options{Confirm: true}))
Expand Down
6 changes: 6 additions & 0 deletions promoter/image/schema/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,12 @@ func ParseThinManifestsFromDir(
if err != nil {
return nil, fmt.Errorf("get prow diff files: %w", err)
}

if len(digestsToCheck) == 0 {
logrus.Info("No digests found in prow diff, nothing to promote")

return []Manifest{}, nil
}
}

// Check that the thin manifests dir follows a regular, predefined format.
Expand Down