tangled
alpha
login
or
join now
pierrelf.com
/
grain
0
fork
atom
Rust implementation of OCI Distribution Spec with granular access control
0
fork
atom
overview
issues
9
pulls
pipelines
add blob and manifest delete
pierrelf.com
4 months ago
ed6ef2cd
1f1ab226
+172
-22
3 changed files
expand all
collapse all
unified
split
src
blobs.rs
manifests.rs
storage.rs
+66
-13
src/blobs.rs
···
10
10
// | end-11 | `POST` | `/v2/<name>/blobs/uploads/?mount=<digest>&from=<other_name>` | `201` | `404` |
11
11
12
12
use serde::Deserialize;
13
13
-
use serde_json::{json, Value};
14
13
use std::sync::Arc;
15
14
16
15
use crate::{
···
21
20
body::Body,
22
21
extract::{Path, Query, State},
23
22
http::{HeaderMap, StatusCode},
24
24
-
response::{Json, Response},
23
23
+
response::Response,
25
24
};
26
25
use bytes::Bytes;
27
26
···
348
347
349
348
// end-10 DELETE /v2/:name/blobs/:digest
350
349
pub(crate) async fn delete_blob_by_digest(
351
351
-
State(data): State<Arc<state::App>>,
352
352
-
Path(name): Path<String>,
353
353
-
Path(digest): Path<String>,
354
354
-
) -> Json<Value> {
355
355
-
let status = data.server_status.lock().await;
350
350
+
State(state): State<Arc<state::App>>,
351
351
+
Path((org, repo, digest_string)): Path<(String, String, String)>,
352
352
+
headers: HeaderMap,
353
353
+
) -> Response<Body> {
354
354
+
let host = &state.args.host;
355
355
+
356
356
+
// Authenticate
357
357
+
if auth::get(State(state.clone()), headers).await.status() != StatusCode::OK {
358
358
+
return Response::builder()
359
359
+
.status(StatusCode::UNAUTHORIZED)
360
360
+
.header(
361
361
+
"WWW-Authenticate",
362
362
+
format!("Basic realm=\"{}\", charset=\"UTF-8\"", host),
363
363
+
)
364
364
+
.body(Body::from("401 Unauthorized"))
365
365
+
.unwrap();
366
366
+
}
367
367
+
368
368
+
// Clean digest (strip sha256: prefix if present)
369
369
+
let clean_digest = digest_string
370
370
+
.strip_prefix("sha256:")
371
371
+
.unwrap_or(&digest_string);
372
372
+
356
373
log::info!(
357
357
-
"blobs/delete_blob_by_digest: name: {}, digest: {}",
358
358
-
name,
359
359
-
digest
374
374
+
"blobs/delete_blob_by_digest: org: {}, repo: {}, digest: {}",
375
375
+
org,
376
376
+
repo,
377
377
+
clean_digest
360
378
);
361
361
-
Json(json!({
362
362
-
"not_implemented": format!("name {} digest {} server_status {}", name, digest, status)
363
363
-
}))
379
379
+
380
380
+
// Delete blob
381
381
+
match storage::delete_blob(&org, &repo, clean_digest) {
382
382
+
Ok(()) => {
383
383
+
log::info!("Deleted blob {}/{}/{}", org, repo, clean_digest);
384
384
+
385
385
+
Response::builder()
386
386
+
.status(StatusCode::ACCEPTED)
387
387
+
.body(Body::empty())
388
388
+
.unwrap()
389
389
+
}
390
390
+
Err(e) => {
391
391
+
if e.kind() == std::io::ErrorKind::NotFound {
392
392
+
log::warn!(
393
393
+
"Attempted to delete non-existent blob {}/{}/{}",
394
394
+
org,
395
395
+
repo,
396
396
+
clean_digest
397
397
+
);
398
398
+
Response::builder()
399
399
+
.status(StatusCode::NOT_FOUND)
400
400
+
.body(Body::from("404 Not Found"))
401
401
+
.unwrap()
402
402
+
} else {
403
403
+
log::error!(
404
404
+
"Failed to delete blob {}/{}/{}: {}",
405
405
+
org,
406
406
+
repo,
407
407
+
clean_digest,
408
408
+
e
409
409
+
);
410
410
+
Response::builder()
411
411
+
.status(StatusCode::INTERNAL_SERVER_ERROR)
412
412
+
.body(Body::from("Internal server error"))
413
413
+
.unwrap()
414
414
+
}
415
415
+
}
416
416
+
}
364
417
}
+62
-9
src/manifests.rs
···
188
188
189
189
// end-9 DELETE /v2/:name/manifests/:reference
190
190
pub(crate) async fn delete_manifest_by_reference(
191
191
-
Path(name): Path<String>,
192
192
-
Path(reference): Path<String>,
191
191
+
State(state): State<Arc<state::App>>,
192
192
+
Path((org, repo, reference)): Path<(String, String, String)>,
193
193
+
headers: HeaderMap,
193
194
) -> Response<Body> {
195
195
+
let host = &state.args.host;
196
196
+
197
197
+
// Authenticate
198
198
+
if auth::get(State(state.clone()), headers).await.status() != StatusCode::OK {
199
199
+
return Response::builder()
200
200
+
.status(StatusCode::UNAUTHORIZED)
201
201
+
.header(
202
202
+
"WWW-Authenticate",
203
203
+
format!("Basic realm=\"{}\", charset=\"UTF-8\"", host),
204
204
+
)
205
205
+
.body(Body::from("401 Unauthorized"))
206
206
+
.unwrap();
207
207
+
}
208
208
+
209
209
+
// Clean reference (strip sha256: prefix if present)
210
210
+
let clean_reference = reference.strip_prefix("sha256:").unwrap_or(&reference);
211
211
+
194
212
log::info!(
195
195
-
"manifests/delete_manifest_by_reference: name: {}, reference: {}",
196
196
-
name,
197
197
-
reference
213
213
+
"manifests/delete_manifest_by_reference: org: {}, repo: {}, reference: {}",
214
214
+
org,
215
215
+
repo,
216
216
+
clean_reference
198
217
);
199
199
-
Response::builder()
200
200
-
.status(StatusCode::NOT_IMPLEMENTED)
201
201
-
.body(Body::from("501 Not Implemented"))
202
202
-
.unwrap()
218
218
+
219
219
+
// Delete manifest
220
220
+
match storage::delete_manifest(&org, &repo, clean_reference) {
221
221
+
Ok(()) => {
222
222
+
log::info!("Deleted manifest {}/{}/{}", org, repo, clean_reference);
223
223
+
224
224
+
Response::builder()
225
225
+
.status(StatusCode::ACCEPTED)
226
226
+
.body(Body::empty())
227
227
+
.unwrap()
228
228
+
}
229
229
+
Err(e) => {
230
230
+
if e.kind() == std::io::ErrorKind::NotFound {
231
231
+
log::warn!(
232
232
+
"Attempted to delete non-existent manifest {}/{}/{}",
233
233
+
org,
234
234
+
repo,
235
235
+
clean_reference
236
236
+
);
237
237
+
Response::builder()
238
238
+
.status(StatusCode::NOT_FOUND)
239
239
+
.body(Body::from("404 Not Found"))
240
240
+
.unwrap()
241
241
+
} else {
242
242
+
log::error!(
243
243
+
"Failed to delete manifest {}/{}/{}: {}",
244
244
+
org,
245
245
+
repo,
246
246
+
clean_reference,
247
247
+
e
248
248
+
);
249
249
+
Response::builder()
250
250
+
.status(StatusCode::INTERNAL_SERVER_ERROR)
251
251
+
.body(Body::from("Internal server error"))
252
252
+
.unwrap()
253
253
+
}
254
254
+
}
255
255
+
}
203
256
}
+44
src/storage.rs
···
275
275
);
276
276
std::fs::remove_file(upload_path)
277
277
}
278
278
+
279
279
+
pub(crate) fn delete_manifest(
280
280
+
org: &str,
281
281
+
repo: &str,
282
282
+
reference: &str,
283
283
+
) -> Result<(), std::io::Error> {
284
284
+
let sanitized_org = sanitize_string(org);
285
285
+
let sanitized_repo = sanitize_string(repo);
286
286
+
let sanitized_reference = sanitize_string(reference);
287
287
+
288
288
+
let manifest_path = format!(
289
289
+
"./tmp/manifests/{}/{}/{}",
290
290
+
sanitized_org, sanitized_repo, sanitized_reference
291
291
+
);
292
292
+
293
293
+
if !std::path::Path::new(&manifest_path).exists() {
294
294
+
return Err(std::io::Error::new(
295
295
+
std::io::ErrorKind::NotFound,
296
296
+
"Manifest not found",
297
297
+
));
298
298
+
}
299
299
+
300
300
+
std::fs::remove_file(manifest_path)
301
301
+
}
302
302
+
303
303
+
pub(crate) fn delete_blob(org: &str, repo: &str, digest: &str) -> Result<(), std::io::Error> {
304
304
+
let sanitized_org = sanitize_string(org);
305
305
+
let sanitized_repo = sanitize_string(repo);
306
306
+
let sanitized_digest = sanitize_string(digest);
307
307
+
308
308
+
let blob_path = format!(
309
309
+
"./tmp/blobs/{}/{}/{}",
310
310
+
sanitized_org, sanitized_repo, sanitized_digest
311
311
+
);
312
312
+
313
313
+
if !std::path::Path::new(&blob_path).exists() {
314
314
+
return Err(std::io::Error::new(
315
315
+
std::io::ErrorKind::NotFound,
316
316
+
"Blob not found",
317
317
+
));
318
318
+
}
319
319
+
320
320
+
std::fs::remove_file(blob_path)
321
321
+
}