Skip to content

Commit 6ee43ac

Browse files
authored
Merge pull request #1798 from saschagrunert/fix/empty-prow-diff-manifests
Fix promotion timeout when prow diff contains no digests
2 parents 0920cc2 + d2cc3f5 commit 6ee43ac

File tree

3 files changed

+48
-1
lines changed

3 files changed

+48
-1
lines changed

promoter/image/promoter.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ func (p *Promoter) PromoteImages(ctx context.Context, opts *options.Options) err
167167
return fmt.Errorf("parsing manifests: %w", err)
168168
}
169169

170+
if len(mfests) == 0 {
171+
logrus.Info("No manifests to process, nothing to promote")
172+
173+
return pipeline.ErrStopPipeline
174+
}
175+
170176
p.impl.PrintVersion()
171177

172178
promotionEdges, err = p.impl.GetPromotionEdges(ctx, opts, mfests)

promoter/image/promoter_test.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,16 @@ import (
3030
"sigs.k8s.io/promo-tools/v4/promoter/image/promotion"
3131
"sigs.k8s.io/promo-tools/v4/promoter/image/provenance"
3232
"sigs.k8s.io/promo-tools/v4/promoter/image/registry"
33+
"sigs.k8s.io/promo-tools/v4/promoter/image/schema"
3334
"sigs.k8s.io/promo-tools/v4/types/image"
3435
)
3536

37+
// nonEmptyManifests returns a minimal manifest slice so that the pipeline
38+
// does not stop early due to an empty manifest list.
39+
func nonEmptyManifests() []schema.Manifest {
40+
return []schema.Manifest{{}}
41+
}
42+
3643
func TestPromoteImages(t *testing.T) {
3744
sut := imagepromoter.Promoter{}
3845
sut.SetProvenanceGenerator(&provenance.PromotionGenerator{})
@@ -51,7 +58,9 @@ func TestPromoteImages(t *testing.T) {
5158
// No errors
5259
shouldErr: false,
5360
msg: "No errors",
54-
prepare: func(_ *imagefakes.FakePromoterImplementation) {},
61+
prepare: func(fpi *imagefakes.FakePromoterImplementation) {
62+
fpi.ParseManifestsReturns(nonEmptyManifests(), nil)
63+
},
5564
},
5665
{
5766
// ValidateOptions fails
@@ -78,27 +87,31 @@ func TestPromoteImages(t *testing.T) {
7887
// GetPromotionEdges fails
7988
shouldErr: true,
8089
prepare: func(fpi *imagefakes.FakePromoterImplementation) {
90+
fpi.ParseManifestsReturns(nonEmptyManifests(), nil)
8191
fpi.GetPromotionEdgesReturns(nil, testErr)
8292
},
8393
},
8494
{
8595
// ValidateStagingSignatures fails
8696
shouldErr: true,
8797
prepare: func(fpi *imagefakes.FakePromoterImplementation) {
98+
fpi.ParseManifestsReturns(nonEmptyManifests(), nil)
8899
fpi.ValidateStagingSignaturesReturns(nil, testErr)
89100
},
90101
},
91102
{
92103
// PromoteImages fails
93104
shouldErr: true,
94105
prepare: func(fpi *imagefakes.FakePromoterImplementation) {
106+
fpi.ParseManifestsReturns(nonEmptyManifests(), nil)
95107
fpi.PromoteImagesReturns(testErr)
96108
},
97109
},
98110
{
99111
// SignImages fails
100112
shouldErr: true,
101113
prepare: func(fpi *imagefakes.FakePromoterImplementation) {
114+
fpi.ParseManifestsReturns(nonEmptyManifests(), nil)
102115
fpi.SignImagesReturns(testErr)
103116
},
104117
},
@@ -107,6 +120,7 @@ func TestPromoteImages(t *testing.T) {
107120
shouldErr: true,
108121
msg: "WriteProvenanceAttestations fails",
109122
prepare: func(fpi *imagefakes.FakePromoterImplementation) {
123+
fpi.ParseManifestsReturns(nonEmptyManifests(), nil)
110124
fpi.WriteProvenanceAttestationsReturns(testErr)
111125
},
112126
},
@@ -126,6 +140,7 @@ func TestPromoteImages(t *testing.T) {
126140
func TestPromoteImagesParseOnly(t *testing.T) {
127141
sut := imagepromoter.Promoter{}
128142
mock := imagefakes.FakePromoterImplementation{}
143+
mock.ParseManifestsReturns(nonEmptyManifests(), nil)
129144
sut.SetImplementation(&mock)
130145

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

175+
func TestPromoteImagesEmptyManifests(t *testing.T) {
176+
sut := imagepromoter.Promoter{}
177+
mock := imagefakes.FakePromoterImplementation{}
178+
// Return empty manifests (e.g., prow diff found no digests)
179+
mock.ParseManifestsReturns([]schema.Manifest{}, nil)
180+
sut.SetImplementation(&mock)
181+
182+
opts := &options.Options{Confirm: true}
183+
require.NoError(t, sut.PromoteImages(context.Background(), opts))
184+
185+
// No downstream phases should have been called
186+
require.Equal(t, 0, mock.GetPromotionEdgesCallCount())
187+
require.Equal(t, 0, mock.PromoteImagesCallCount())
188+
}
189+
159190
func TestPromoteImagesProvenanceAlwaysRuns(t *testing.T) {
160191
sut := imagepromoter.Promoter{}
161192
mock := imagefakes.FakePromoterImplementation{}
193+
mock.ParseManifestsReturns(nonEmptyManifests(), nil)
162194
mock.GetPromotionEdgesReturns(map[promotion.Edge]any{
163195
testEdge(): nil,
164196
}, nil)
@@ -199,6 +231,7 @@ func testEdge() promotion.Edge {
199231
func TestPromoteImagesProvenanceFails(t *testing.T) {
200232
sut := imagepromoter.Promoter{}
201233
mock := imagefakes.FakePromoterImplementation{}
234+
mock.ParseManifestsReturns(nonEmptyManifests(), nil)
202235
// Return a non-empty edge set so provenance has something to check
203236
mock.GetPromotionEdgesReturns(map[promotion.Edge]any{
204237
testEdge(): nil,
@@ -223,6 +256,7 @@ func TestPromoteImagesProvenanceFails(t *testing.T) {
223256
func TestPromoteImagesProvenanceVerifierError(t *testing.T) {
224257
sut := imagepromoter.Promoter{}
225258
mock := imagefakes.FakePromoterImplementation{}
259+
mock.ParseManifestsReturns(nonEmptyManifests(), nil)
226260
mock.GetPromotionEdgesReturns(map[promotion.Edge]any{
227261
testEdge(): nil,
228262
}, nil)
@@ -325,6 +359,7 @@ func TestNewPromoter(t *testing.T) {
325359
// Verify that a promoter created via New() has the verifier and
326360
// generator configured by running a full pipeline with a mock impl.
327361
mock := imagefakes.FakePromoterImplementation{}
362+
mock.ParseManifestsReturns(nonEmptyManifests(), nil)
328363
p.SetImplementation(&mock)
329364

330365
require.NoError(t, p.PromoteImages(context.Background(), &options.Options{Confirm: true}))

promoter/image/schema/manifest.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,12 @@ func ParseThinManifestsFromDir(
264264
if err != nil {
265265
return nil, fmt.Errorf("get prow diff files: %w", err)
266266
}
267+
268+
if len(digestsToCheck) == 0 {
269+
logrus.Info("No digests found in prow diff, nothing to promote")
270+
271+
return []Manifest{}, nil
272+
}
267273
}
268274

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

0 commit comments

Comments
 (0)