@@ -3,6 +3,7 @@ package storage
3
3
import (
4
4
"context"
5
5
"encoding/json"
6
+ "errors"
6
7
"fmt"
7
8
"io"
8
9
"strings"
@@ -12,7 +13,7 @@ import (
12
13
godigest "github.com/opencontainers/go-digest"
13
14
ispec "github.com/opencontainers/image-spec/specs-go/v1"
14
15
15
- "zotregistry.io/zot/errors"
16
+ zerr "zotregistry.io/zot/errors"
16
17
"zotregistry.io/zot/pkg/common"
17
18
storageTypes "zotregistry.io/zot/pkg/storage/types"
18
19
)
@@ -85,33 +86,20 @@ func CheckImageStoreBlobsIntegrity(ctx context.Context, imgStore storageTypes.Im
85
86
return results , nil
86
87
}
87
88
89
+ // CheckRepo is the main entry point for the scrub task
90
+ // We aim for eventual consistency (locks, etc) since this task contends with data path.
88
91
func CheckRepo (ctx context.Context , imageName string , imgStore storageTypes.ImageStore ) ([]ScrubImageResult , error ) {
89
92
results := []ScrubImageResult {}
90
93
91
- var lockLatency time.Time
92
-
93
- imgStore .RLock (& lockLatency )
94
- defer imgStore .RUnlock (& lockLatency )
95
-
96
- // check image structure / layout
97
- ok , err := imgStore .ValidateRepo (imageName )
98
- if err != nil {
99
- return results , err
100
- }
101
-
102
- if ! ok {
103
- return results , errors .ErrRepoBadLayout
104
- }
105
-
106
- // check "index.json" content
107
- indexContent , err := imgStore .GetIndexContent (imageName )
94
+ // getIndex holds the lock
95
+ indexContent , err := getIndex (imageName , imgStore )
108
96
if err != nil {
109
97
return results , err
110
98
}
111
99
112
100
var index ispec.Index
113
101
if err := json .Unmarshal (indexContent , & index ); err != nil {
114
- return results , errors .ErrRepoNotFound
102
+ return results , zerr .ErrRepoNotFound
115
103
}
116
104
117
105
scrubbedManifests := make (map [godigest.Digest ]ScrubImageResult )
@@ -122,46 +110,107 @@ func CheckRepo(ctx context.Context, imageName string, imgStore storageTypes.Imag
122
110
}
123
111
124
112
tag := manifest .Annotations [ispec .AnnotationRefName ]
125
- scrubManifest (ctx , manifest , imgStore , imageName , tag , scrubbedManifests )
126
- results = append (results , scrubbedManifests [manifest .Digest ])
113
+
114
+ // checkImage holds the lock
115
+ layers , err := checkImage (manifest , imgStore , imageName , tag , scrubbedManifests )
116
+ if err == nil && len (layers ) > 0 {
117
+ // CheckLayers doesn't use locks
118
+ imgRes := CheckLayers (imageName , tag , layers , imgStore )
119
+ scrubbedManifests [manifest .Digest ] = imgRes
120
+ }
121
+
122
+ // ignore the manifest if it isn't found
123
+ if ! errors .Is (err , zerr .ErrManifestNotFound ) {
124
+ results = append (results , scrubbedManifests [manifest .Digest ])
125
+ }
127
126
}
128
127
129
128
return results , nil
130
129
}
131
130
132
- func scrubManifest (
133
- ctx context. Context , manifest ispec.Descriptor , imgStore storageTypes.ImageStore , imageName , tag string ,
131
+ func checkImage (
132
+ manifest ispec.Descriptor , imgStore storageTypes.ImageStore , imageName , tag string ,
134
133
scrubbedManifests map [godigest.Digest ]ScrubImageResult ,
135
- ) {
134
+ ) ([]ispec.Descriptor , error ) {
135
+ var lockLatency time.Time
136
+
137
+ imgStore .RLock (& lockLatency )
138
+ defer imgStore .RUnlock (& lockLatency )
139
+
140
+ manifestContent , err := imgStore .GetBlobContent (imageName , manifest .Digest )
141
+ if err != nil {
142
+ // ignore if the manifest is not found(probably it was deleted after we got the list of manifests)
143
+ return []ispec.Descriptor {}, zerr .ErrManifestNotFound
144
+ }
145
+
146
+ return scrubManifest (manifest , imgStore , imageName , tag , manifestContent , scrubbedManifests )
147
+ }
148
+
149
+ func getIndex (imageName string , imgStore storageTypes.ImageStore ) ([]byte , error ) {
150
+ var lockLatency time.Time
151
+
152
+ imgStore .RLock (& lockLatency )
153
+ defer imgStore .RUnlock (& lockLatency )
154
+
155
+ // check image structure / layout
156
+ ok , err := imgStore .ValidateRepo (imageName )
157
+ if err != nil {
158
+ return []byte {}, err
159
+ }
160
+
161
+ if ! ok {
162
+ return []byte {}, zerr .ErrRepoBadLayout
163
+ }
164
+
165
+ // check "index.json" content
166
+ indexContent , err := imgStore .GetIndexContent (imageName )
167
+ if err != nil {
168
+ return []byte {}, err
169
+ }
170
+
171
+ return indexContent , nil
172
+ }
173
+
174
+ func scrubManifest (
175
+ manifest ispec.Descriptor , imgStore storageTypes.ImageStore , imageName , tag string ,
176
+ manifestContent []byte , scrubbedManifests map [godigest.Digest ]ScrubImageResult ,
177
+ ) ([]ispec.Descriptor , error ) {
178
+ layers := []ispec.Descriptor {}
179
+
136
180
res , ok := scrubbedManifests [manifest .Digest ]
137
181
if ok {
138
182
scrubbedManifests [manifest .Digest ] = newScrubImageResult (imageName , tag , res .Status ,
139
183
res .AffectedBlob , res .Error )
140
184
141
- return
185
+ return layers , nil
142
186
}
143
187
144
188
switch manifest .MediaType {
145
189
case ispec .MediaTypeImageIndex :
146
- buf , err := imgStore .GetBlobContent (imageName , manifest .Digest )
147
- if err != nil {
148
- imgRes := getResult (imageName , tag , manifest .Digest , errors .ErrBadBlobDigest )
149
- scrubbedManifests [manifest .Digest ] = imgRes
150
-
151
- return
152
- }
153
-
154
190
var idx ispec.Index
155
- if err := json .Unmarshal (buf , & idx ); err != nil {
156
- imgRes := getResult (imageName , tag , manifest .Digest , errors .ErrBadBlobDigest )
191
+ if err := json .Unmarshal (manifestContent , & idx ); err != nil {
192
+ imgRes := getResult (imageName , tag , manifest .Digest , zerr .ErrBadBlobDigest )
157
193
scrubbedManifests [manifest .Digest ] = imgRes
158
194
159
- return
195
+ return layers , err
160
196
}
161
197
162
198
// check all manifests
163
199
for _ , man := range idx .Manifests {
164
- scrubManifest (ctx , man , imgStore , imageName , tag , scrubbedManifests )
200
+ buf , err := imgStore .GetBlobContent (imageName , man .Digest )
201
+ if err != nil {
202
+ imgRes := getResult (imageName , tag , man .Digest , zerr .ErrBadBlobDigest )
203
+ scrubbedManifests [man .Digest ] = imgRes
204
+ scrubbedManifests [manifest .Digest ] = imgRes
205
+
206
+ return layers , err
207
+ }
208
+
209
+ layersToScrub , err := scrubManifest (man , imgStore , imageName , tag , buf , scrubbedManifests )
210
+
211
+ if err == nil {
212
+ layers = append (layers , layersToScrub ... )
213
+ }
165
214
166
215
// if the manifest is affected then this index is also affected
167
216
if scrubbedManifests [man .Digest ].Error != "" {
@@ -170,115 +219,116 @@ func scrubManifest(
170
219
scrubbedManifests [manifest .Digest ] = newScrubImageResult (imageName , tag , mRes .Status ,
171
220
mRes .AffectedBlob , mRes .Error )
172
221
173
- return
222
+ return layers , err
174
223
}
175
224
}
176
225
177
- // at this point, before starting to check de subject we can consider the index is ok
226
+ // at this point, before starting to check the subject we can consider the index is ok
178
227
scrubbedManifests [manifest .Digest ] = getResult (imageName , tag , "" , nil )
179
228
180
229
// check subject if exists
181
230
if idx .Subject != nil {
182
- scrubManifest (ctx , * idx .Subject , imgStore , imageName , tag , scrubbedManifests )
231
+ buf , err := imgStore .GetBlobContent (imageName , idx .Subject .Digest )
232
+ if err != nil {
233
+ imgRes := getResult (imageName , tag , idx .Subject .Digest , zerr .ErrBadBlobDigest )
234
+ scrubbedManifests [idx .Subject .Digest ] = imgRes
235
+ scrubbedManifests [manifest .Digest ] = imgRes
236
+
237
+ return layers , err
238
+ }
239
+
240
+ layersToScrub , err := scrubManifest (* idx .Subject , imgStore , imageName , tag , buf , scrubbedManifests )
241
+
242
+ if err == nil {
243
+ layers = append (layers , layersToScrub ... )
244
+ }
183
245
184
246
subjectRes := scrubbedManifests [idx .Subject .Digest ]
185
247
186
248
scrubbedManifests [manifest .Digest ] = newScrubImageResult (imageName , tag , subjectRes .Status ,
187
249
subjectRes .AffectedBlob , subjectRes .Error )
250
+
251
+ return layers , err
188
252
}
253
+
254
+ return layers , nil
189
255
case ispec .MediaTypeImageManifest :
190
- imgRes := CheckIntegrity (ctx , imageName , tag , manifest , imgStore )
191
- scrubbedManifests [manifest .Digest ] = imgRes
256
+ affectedBlob , man , err := CheckManifestAndConfig (imageName , manifest , manifestContent , imgStore )
257
+ if err == nil {
258
+ layers = append (layers , man .Layers ... )
259
+ }
260
+
261
+ scrubbedManifests [manifest .Digest ] = getResult (imageName , tag , affectedBlob , err )
192
262
193
263
// if integrity ok then check subject if exists
194
- if imgRes .Error == "" {
195
- manifestContent , _ := imgStore .GetBlobContent (imageName , manifest .Digest )
264
+ if err == nil && man .Subject != nil {
265
+ buf , err := imgStore .GetBlobContent (imageName , man .Subject .Digest )
266
+ if err != nil {
267
+ imgRes := getResult (imageName , tag , man .Subject .Digest , zerr .ErrBadBlobDigest )
268
+ scrubbedManifests [man .Subject .Digest ] = imgRes
269
+ scrubbedManifests [manifest .Digest ] = imgRes
270
+
271
+ return layers , err
272
+ }
196
273
197
- var man ispec. Manifest
274
+ layersToScrub , err := scrubManifest ( * man . Subject , imgStore , imageName , tag , buf , scrubbedManifests )
198
275
199
- _ = json .Unmarshal (manifestContent , & man )
276
+ if err == nil {
277
+ layers = append (layers , layersToScrub ... )
278
+ }
200
279
201
- if man .Subject != nil {
202
- scrubManifest (ctx , * man .Subject , imgStore , imageName , tag , scrubbedManifests )
280
+ subjectRes := scrubbedManifests [man .Subject .Digest ]
203
281
204
- subjectRes := scrubbedManifests [man .Subject .Digest ]
282
+ scrubbedManifests [manifest .Digest ] = newScrubImageResult (imageName , tag , subjectRes .Status ,
283
+ subjectRes .AffectedBlob , subjectRes .Error )
205
284
206
- scrubbedManifests [manifest .Digest ] = newScrubImageResult (imageName , tag , subjectRes .Status ,
207
- subjectRes .AffectedBlob , subjectRes .Error )
208
- }
285
+ return layers , err
209
286
}
287
+
288
+ return layers , err
210
289
default :
211
- scrubbedManifests [manifest .Digest ] = getResult (imageName , tag , manifest .Digest , errors .ErrBadManifest )
212
- }
213
- }
290
+ scrubbedManifests [manifest .Digest ] = getResult (imageName , tag , manifest .Digest , zerr .ErrBadManifest )
214
291
215
- func CheckIntegrity (
216
- ctx context.Context , imageName , tagName string , manifest ispec.Descriptor , imgStore storageTypes.ImageStore ,
217
- ) ScrubImageResult {
218
- // check manifest and config
219
- if affectedBlob , err := CheckManifestAndConfig (imageName , manifest , imgStore ); err != nil {
220
- return getResult (imageName , tagName , affectedBlob , err )
292
+ return layers , zerr .ErrBadManifest
221
293
}
222
-
223
- // check layers
224
- return CheckLayers (ctx , imageName , tagName , manifest , imgStore )
225
294
}
226
295
227
296
func CheckManifestAndConfig (
228
- imageName string , manifestDesc ispec.Descriptor , imgStore storageTypes.ImageStore ,
229
- ) (godigest.Digest , error ) {
297
+ imageName string , manifestDesc ispec.Descriptor , manifestContent [] byte , imgStore storageTypes.ImageStore ,
298
+ ) (godigest.Digest , ispec. Manifest , error ) {
230
299
// Q oras artifacts?
231
300
if manifestDesc .MediaType != ispec .MediaTypeImageManifest {
232
- return manifestDesc .Digest , errors .ErrBadManifest
233
- }
234
-
235
- manifestContent , err := imgStore .GetBlobContent (imageName , manifestDesc .Digest )
236
- if err != nil {
237
- return manifestDesc .Digest , err
301
+ return manifestDesc .Digest , ispec.Manifest {}, zerr .ErrBadManifest
238
302
}
239
303
240
304
var manifest ispec.Manifest
241
305
242
- err = json .Unmarshal (manifestContent , & manifest )
306
+ err : = json .Unmarshal (manifestContent , & manifest )
243
307
if err != nil {
244
- return manifestDesc .Digest , errors .ErrBadManifest
308
+ return manifestDesc .Digest , ispec. Manifest {}, zerr .ErrBadManifest
245
309
}
246
310
247
311
configContent , err := imgStore .GetBlobContent (imageName , manifest .Config .Digest )
248
312
if err != nil {
249
- return manifest .Config .Digest , err
313
+ return manifest .Config .Digest , ispec. Manifest {}, err
250
314
}
251
315
252
316
var config ispec.Image
253
317
254
318
err = json .Unmarshal (configContent , & config )
255
319
if err != nil {
256
- return manifest .Config .Digest , errors .ErrBadConfig
320
+ return manifest .Config .Digest , ispec. Manifest {}, zerr .ErrBadConfig
257
321
}
258
322
259
- return "" , nil
323
+ return "" , manifest , nil
260
324
}
261
325
262
326
func CheckLayers (
263
- ctx context. Context , imageName , tagName string , manifest ispec.Descriptor , imgStore storageTypes.ImageStore ,
327
+ imageName , tagName string , layers [] ispec.Descriptor , imgStore storageTypes.ImageStore ,
264
328
) ScrubImageResult {
265
329
imageRes := ScrubImageResult {}
266
330
267
- buf , err := imgStore .GetBlobContent (imageName , manifest .Digest )
268
- if err != nil {
269
- imageRes = getResult (imageName , tagName , manifest .Digest , err )
270
-
271
- return imageRes
272
- }
273
-
274
- var man ispec.Manifest
275
- if err := json .Unmarshal (buf , & man ); err != nil {
276
- imageRes = getResult (imageName , tagName , manifest .Digest , errors .ErrBadManifest )
277
-
278
- return imageRes
279
- }
280
-
281
- for _ , layer := range man .Layers {
331
+ for _ , layer := range layers {
282
332
if err := imgStore .VerifyBlobDigestValue (imageName , layer .Digest ); err != nil {
283
333
imageRes = getResult (imageName , tagName , layer .Digest , err )
284
334
0 commit comments