Skip to content

Instantly share code, notes, and snippets.

@uhthomas
Last active August 27, 2024 22:49
Show Gist options
  • Save uhthomas/d1e890aee1ce3d7ea1dccb6239101b3f to your computer and use it in GitHub Desktop.
Save uhthomas/d1e890aee1ce3d7ea1dccb6239101b3f to your computer and use it in GitHub Desktop.
improved docker distrubtion invalid repository finder and deleter
package main
import (
"context"
"flag"
"fmt"
"log"
"path"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
func isEmptyRepository(ctx context.Context, c *s3.S3, bucket, prefix string) (bool, error) {
res, err := c.ListObjectsV2WithContext(ctx, &s3.ListObjectsV2Input{
Bucket: aws.String(bucket),
Prefix: aws.String(path.Join(prefix, "_manifests")),
MaxKeys: aws.Int64(1),
})
if err != nil {
return false, fmt.Errorf("list objects: %w", err)
}
return len(res.Contents) == 0, nil
}
func removeEmptyRepository(ctx context.Context, c *s3.S3, bucket, prefix string) error {
empty, err := isEmptyRepository(ctx, c, bucket, prefix)
if err != nil {
return fmt.Errorf("is empty repository: %w", err)
}
if !empty {
return nil
}
log.Printf("%s is empty and will be removed", prefix)
var continuationToken *string
for {
res, err := c.ListObjectsV2WithContext(ctx, &s3.ListObjectsV2Input{
Bucket: aws.String(bucket),
Prefix: aws.String(prefix),
ContinuationToken: continuationToken,
})
if err != nil {
return fmt.Errorf("list objects: %w", err)
}
var objects []*s3.ObjectIdentifier
for _, o := range res.Contents {
log.Printf("delete %s", aws.StringValue(o.Key))
objects = append(objects, &s3.ObjectIdentifier{
Key: o.Key,
})
}
if _, err := c.DeleteObjectsWithContext(ctx, &s3.DeleteObjectsInput{
Bucket: aws.String(bucket),
Delete: &s3.Delete{
Objects: objects,
},
}); err != nil {
return fmt.Errorf("delete objects: %w", err)
}
if res.NextContinuationToken == nil {
break
}
continuationToken = res.NextContinuationToken
}
return nil
}
func removeEmptyRepositories(ctx context.Context, c *s3.S3, bucket, prefix string) error {
var continuationToken *string
for {
res, err := c.ListObjectsV2WithContext(ctx, &s3.ListObjectsV2Input{
Bucket: aws.String(bucket),
Prefix: aws.String(prefix),
ContinuationToken: continuationToken,
Delimiter: aws.String("/"),
})
if err != nil {
return fmt.Errorf("list objects: %w", err)
}
for _, p := range res.CommonPrefixes {
if err := removeEmptyRepository(ctx, c, bucket, aws.StringValue(p.Prefix)); err != nil {
return fmt.Errorf("remove empty repository: %w", err)
}
}
if res.NextContinuationToken == nil {
break
}
continuationToken = res.NextContinuationToken
}
return nil
}
func Main(ctx context.Context) error {
region := flag.String("region", "auto", "s3 region")
endpoint := flag.String("endpoint", "", "s3 endpoint, for example https://abc.r2.cloudflarestorage.com")
bucket := flag.String("bucket", "", "s3 bucket")
accessKeyID := flag.String("access-key-id", "", "s3 access key id")
accessKeySecret := flag.String("access-key-secret", "", "s3 access key secret")
flag.Parse()
if *bucket == "" {
return fmt.Errorf("bucket is required")
}
sess, err := session.NewSession(&aws.Config{
Region: region,
Endpoint: endpoint,
Credentials: credentials.NewStaticCredentials(*accessKeyID, *accessKeySecret, ""),
})
if err != nil {
return fmt.Errorf("new session: %w", err)
}
const prefix = "docker/registry/v2/repositories/"
if err := removeEmptyRepositories(ctx, s3.New(sess), *bucket, prefix); err != nil {
return fmt.Errorf("remove empty repositories: %w", err)
}
return nil
}
func main() {
if err := Main(context.Background()); err != nil {
log.Fatal(err)
}
}
@uhthomas
Copy link
Author

uhthomas commented Aug 27, 2024

❯ go run . -endpoint=https://.r2.cloudflarestorage.com -access-key-id= -access-key-secret= -bucket=distribution-test
2024/08/27 23:46:22 docker/registry/v2/repositories/caddy/ is empty and will be removed
2024/08/27 23:46:22 delete docker/registry/v2/repositories/caddy/_layers/sha256/025823b4426f1156a81c3af7b9be4bfc1e1766cb5f8454012b8650af08ebb41e/link
2024/08/27 23:46:22 delete docker/registry/v2/repositories/caddy/_layers/sha256/04f2b318172a1d2862336f8011c9c221bdfd0ac670d86bee334c4aac33cebcc3/link
2024/08/27 23:46:22 delete docker/registry/v2/repositories/caddy/_layers/sha256/4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1/link
2024/08/27 23:46:22 delete docker/registry/v2/repositories/caddy/_layers/sha256/8f119043738166ba92d2b49ec5cf2d796e29e8841253cc96dd51d2bffd5c9440/link
2024/08/27 23:46:22 delete docker/registry/v2/repositories/caddy/_layers/sha256/adf4aeafa6ae032949b25ab359e9ac5598c4ee93cb4dae3c2235593ec0929a3c/link
2024/08/27 23:46:22 delete docker/registry/v2/repositories/caddy/_layers/sha256/c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6/link
2024/08/27 23:48:39 docker/registry/v2/repositories/alpine/ is empty and will be removed
2024/08/27 23:48:40 delete docker/registry/v2/repositories/alpine/_layers/sha256/324bc02ae1231fd9255658c128086395d3fa0aedd5a41ab6b034fd649d1a9260/link
2024/08/27 23:48:40 delete docker/registry/v2/repositories/alpine/_layers/sha256/c6a83fedfae6ed8a4f5f7cbb6a7b6f1c1ec3d86fea8cb9e5ba2e5e6673fde9f6/link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment