@@ -25,6 +25,7 @@ import (
2525 "github.com/google/go-containerregistry/pkg/v1/random"
2626 "github.com/google/go-containerregistry/pkg/v1/remote"
2727 "github.com/google/go-containerregistry/pkg/v1/types"
28+ ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
2829 "github.com/tektoncd/chains/pkg/chains/formats/simple"
2930 "github.com/tektoncd/chains/pkg/chains/signing"
3031 "github.com/tektoncd/chains/pkg/chains/storage/api"
@@ -108,3 +109,127 @@ func TestSimpleStorer_Store(t *testing.T) {
108109 })
109110 }
110111}
112+
113+ func TestSimpleStorer_Store_Dedup (t * testing.T ) {
114+ s := httptest .NewServer (registry .New ())
115+ defer s .Close ()
116+ registryName := strings .TrimPrefix (s .URL , "http://" )
117+
118+ img , err := random .Image (1024 , 2 )
119+ if err != nil {
120+ t .Fatalf ("failed to create random image: %s" , err )
121+ }
122+ imgDigest , err := img .Digest ()
123+ if err != nil {
124+ t .Fatalf ("failed to get image digest: %v" , err )
125+ }
126+ ref , err := name .NewDigest (fmt .Sprintf ("%s/test/img@%s" , registryName , imgDigest ))
127+ if err != nil {
128+ t .Fatalf ("failed to parse digest: %v" , err )
129+ }
130+ if err := remote .Write (ref , img ); err != nil {
131+ t .Fatalf ("failed to write image to mock registry: %v" , err )
132+ }
133+
134+ storer , err := NewSimpleStorerFromConfig (WithTargetRepository (ref .Repository ))
135+ if err != nil {
136+ t .Fatalf ("failed to create storer: %v" , err )
137+ }
138+
139+ ctx := logtesting .TestContextWithLogger (t )
140+ req := & api.StoreRequest [name.Digest , simple.SimpleContainerImage ]{
141+ Artifact : ref ,
142+ Payload : simple .NewSimpleStruct (ref ),
143+ Bundle : & signing.Bundle {Content : []byte ("payload" ), Signature : []byte ("sig1" )},
144+ }
145+
146+ // Store the same signature twice.
147+ if _ , err := storer .Store (ctx , req ); err != nil {
148+ t .Fatalf ("first Store() failed: %s" , err )
149+ }
150+ if _ , err := storer .Store (ctx , req ); err != nil {
151+ t .Fatalf ("second Store() failed: %s" , err )
152+ }
153+
154+ // Verify only one signature layer exists.
155+ se , err := ociremote .SignedEntity (ref )
156+ if err != nil {
157+ t .Fatalf ("failed to get signed entity: %v" , err )
158+ }
159+ sigs , err := se .Signatures ()
160+ if err != nil {
161+ t .Fatalf ("failed to get signatures: %v" , err )
162+ }
163+ layers , err := sigs .Get ()
164+ if err != nil {
165+ t .Fatalf ("failed to get signature layers: %v" , err )
166+ }
167+ if got := len (layers ); got != 1 {
168+ t .Errorf ("expected 1 signature layer, got %d" , got )
169+ }
170+ }
171+
172+ func TestSimpleStorer_Store_DistinctNotDeduped (t * testing.T ) {
173+ s := httptest .NewServer (registry .New ())
174+ defer s .Close ()
175+ registryName := strings .TrimPrefix (s .URL , "http://" )
176+
177+ img , err := random .Image (1024 , 2 )
178+ if err != nil {
179+ t .Fatalf ("failed to create random image: %s" , err )
180+ }
181+ imgDigest , err := img .Digest ()
182+ if err != nil {
183+ t .Fatalf ("failed to get image digest: %v" , err )
184+ }
185+ ref , err := name .NewDigest (fmt .Sprintf ("%s/test/img@%s" , registryName , imgDigest ))
186+ if err != nil {
187+ t .Fatalf ("failed to parse digest: %v" , err )
188+ }
189+ if err := remote .Write (ref , img ); err != nil {
190+ t .Fatalf ("failed to write image to mock registry: %v" , err )
191+ }
192+
193+ storer , err := NewSimpleStorerFromConfig (WithTargetRepository (ref .Repository ))
194+ if err != nil {
195+ t .Fatalf ("failed to create storer: %v" , err )
196+ }
197+
198+ ctx := logtesting .TestContextWithLogger (t )
199+
200+ // Store two signatures with different content (different layer digests).
201+ req1 := & api.StoreRequest [name.Digest , simple.SimpleContainerImage ]{
202+ Artifact : ref ,
203+ Payload : simple .NewSimpleStruct (ref ),
204+ Bundle : & signing.Bundle {Content : []byte ("payload1" ), Signature : []byte ("sig1" )},
205+ }
206+ req2 := & api.StoreRequest [name.Digest , simple.SimpleContainerImage ]{
207+ Artifact : ref ,
208+ Payload : simple .NewSimpleStruct (ref ),
209+ Bundle : & signing.Bundle {Content : []byte ("payload2" ), Signature : []byte ("sig2" )},
210+ }
211+
212+ if _ , err := storer .Store (ctx , req1 ); err != nil {
213+ t .Fatalf ("first Store() failed: %s" , err )
214+ }
215+ if _ , err := storer .Store (ctx , req2 ); err != nil {
216+ t .Fatalf ("second Store() failed: %s" , err )
217+ }
218+
219+ // Verify both signature layers are kept.
220+ se , err := ociremote .SignedEntity (ref )
221+ if err != nil {
222+ t .Fatalf ("failed to get signed entity: %v" , err )
223+ }
224+ sigs , err := se .Signatures ()
225+ if err != nil {
226+ t .Fatalf ("failed to get signatures: %v" , err )
227+ }
228+ layers , err := sigs .Get ()
229+ if err != nil {
230+ t .Fatalf ("failed to get signature layers: %v" , err )
231+ }
232+ if got := len (layers ); got != 2 {
233+ t .Errorf ("expected 2 distinct signature layers, got %d" , got )
234+ }
235+ }
0 commit comments