@@ -3,7 +3,7 @@ package sync
3
3
import (
4
4
"bytes"
5
5
"context"
6
- "crypto/sha256 "
6
+ "crypto/md5 "
7
7
"fmt"
8
8
"io"
9
9
"net/http"
@@ -71,7 +71,7 @@ func pushS3WithSession(ctx context.Context, s3Session *s3.S3, bucket *string, im
71
71
"v2" ,
72
72
acl ,
73
73
aws .String ("application/json" ),
74
- bytes .NewReader ([]byte {} ), // No content is needed, we just need to return a 200.
74
+ bytes .NewReader ([]byte ( "{}" ) ), // No content is needed, we just need to return a 200.
75
75
); err != nil {
76
76
return err
77
77
}
@@ -168,19 +168,20 @@ func pushS3WithSession(ctx context.Context, s3Session *s3.S3, bucket *string, im
168
168
ctx ,
169
169
s3Session ,
170
170
bucket ,
171
- filepath .Join (baseDir , "manifests" , tag ),
171
+ filepath .Join (baseDir , "manifests" , desc . Digest . String () ),
172
172
acl ,
173
173
mediaType ,
174
174
bytes .NewReader (manifest ),
175
175
); err != nil {
176
176
return err
177
177
}
178
178
179
+ // Tag is added last so it can be used to check for duplication.
179
180
if err := syncObject (
180
181
ctx ,
181
182
s3Session ,
182
183
bucket ,
183
- filepath . Join ( baseDir , "manifests" , desc . Digest . String () ),
184
+ manifestKey ( image , tag ),
184
185
acl ,
185
186
mediaType ,
186
187
bytes .NewReader (manifest ),
@@ -191,15 +192,19 @@ func pushS3WithSession(ctx context.Context, s3Session *s3.S3, bucket *string, im
191
192
return nil
192
193
}
193
194
194
- func syncObject (ctx context.Context , s3Session * s3.S3 , bucket * string , key string , acl * string , contentType * string , r io.ReadSeeker ) error {
195
- // FIXME: we are reading the object every time to calculate the digest. This is inefficient.
196
- h := sha256 .New ()
197
- if _ , err := io .Copy (h , r ); err != nil {
198
- return err
195
+ func s3ObjectExists (s3Session * s3.S3 , bucket * string , key string ) (bool , error ) {
196
+ _ , err := s3Session .HeadObject (& s3.HeadObjectInput {
197
+ Bucket : bucket ,
198
+ Key : & key ,
199
+ })
200
+ if err != nil {
201
+ return false , err
199
202
}
200
- calculatedDigest := fmt .Sprintf ("sha256:%x" , h .Sum (nil ))
201
- r .Seek (0 , io .SeekStart )
202
203
204
+ return true , nil
205
+ }
206
+
207
+ func syncObject (ctx context.Context , s3Session * s3.S3 , bucket * string , key string , acl * string , contentType * string , r io.ReadSeeker ) error {
203
208
head , err := s3Session .HeadObject (& s3.HeadObjectInput {
204
209
Bucket : bucket ,
205
210
Key : & key ,
@@ -210,13 +215,32 @@ func syncObject(ctx context.Context, s3Session *s3.S3, bucket *string, key strin
210
215
}
211
216
}
212
217
213
- headMetadataDigest , digestPresent := head .Metadata ["X-Calculated-Digest" ]
218
+ // We store the digest as metadata so we can compare with the ETag without having to download the object.
219
+ headMetadataDigestPtr , digestPresent := head .Metadata ["X-Calculated-Digest" ]
220
+ var headMetadataDigest string
221
+ if digestPresent {
222
+ headMetadataDigest = * headMetadataDigestPtr
223
+ }
224
+
225
+ var etag string
226
+ if head != nil && head .ETag != nil {
227
+ etag = strings .ReplaceAll (* head .ETag , `"` , "" )
228
+ }
214
229
215
230
if head == nil ||
216
231
head .ContentType == nil ||
217
232
* head .ContentType != * contentType ||
218
233
! digestPresent ||
219
- * headMetadataDigest != calculatedDigest {
234
+ headMetadataDigest != etag {
235
+
236
+ r .Seek (0 , io .SeekStart )
237
+ h := md5 .New ()
238
+ if _ , err := io .Copy (h , r ); err != nil {
239
+ return err
240
+ }
241
+ calculatedDigest := fmt .Sprintf ("%x" , h .Sum (nil ))
242
+ r .Seek (0 , io .SeekStart )
243
+
220
244
log .Info ().
221
245
Str ("bucket" , * bucket ).
222
246
Str ("key" , key ).
@@ -239,3 +263,7 @@ func syncObject(ctx context.Context, s3Session *s3.S3, bucket *string, key strin
239
263
240
264
return nil
241
265
}
266
+
267
+ func manifestKey (image * structs.Image , tag string ) string {
268
+ return filepath .Join ("v2" , image .GetSourceRepository (), "manifests" , tag )
269
+ }
0 commit comments