Skip to content

Go Library

s3lo exposes its core packages for use in other Go programs.

go get github.com/OuFinx/s3lo

Packages

Package Description
github.com/OuFinx/s3lo/pkg/image High-level operations: push, pull, copy, list, inspect, delete, GC, stats, scan
github.com/OuFinx/s3lo/pkg/ref Parse s3://, gs://, az://, local:// references
github.com/OuFinx/s3lo/pkg/storage Storage backend interface + AWS S3, GCS, Azure Blob, and local filesystem implementations
github.com/OuFinx/s3lo/pkg/oci OCI manifest and config types

All functions accept context.Context as the first argument.

Push

import "github.com/OuFinx/s3lo/pkg/image"

err := image.Push(ctx, "myapp:v1.0", "s3://my-bucket/myapp:v1.0", image.PushOptions{})

With progress callback:

err := image.Push(ctx, "myapp:v1.0", "s3://my-bucket/myapp:v1.0", image.PushOptions{
    OnBlob: func(digest string, size int64, skipped bool) {
        if skipped {
            fmt.Printf("skip %s\n", digest[:12])
        } else {
            fmt.Printf("upload %s (%d bytes)\n", digest[:12], size)
        }
    },
})

Pull

err := image.Pull(ctx, "s3://my-bucket/myapp:v1.0", "myapp:v1.0", image.PullOptions{})

Pull a specific platform from a multi-arch image:

err := image.Pull(ctx, "s3://my-bucket/alpine:latest", "", image.PullOptions{
    Platform: "linux/amd64",
})

Copy

result, err := image.Copy(ctx, "alpine:latest", "s3://my-bucket/alpine:latest", image.CopyOptions{})
fmt.Printf("copied %d blobs, skipped %d\n", result.BlobsCopied, result.BlobsSkipped)

Copy a specific platform:

result, err := image.Copy(ctx, "alpine:latest", "s3://my-bucket/alpine:latest", image.CopyOptions{
    Platform: "linux/amd64",
})

List

images, err := image.List(ctx, "s3://my-bucket/")
for _, img := range images {
    fmt.Println(img) // "myapp:v1.0"
}

Inspect

info, err := image.Inspect(ctx, "s3://my-bucket/myapp:v1.0")
fmt.Printf("layers: %d, size: %d bytes\n", len(info.Layers), info.TotalSize)

if info.IsIndex {
    for _, p := range info.Platforms {
        fmt.Printf("platform: %s, layers: %d\n", p.Platform, len(p.Layers))
    }
}

Delete

err := image.Delete(ctx, "s3://my-bucket/myapp:v1.0")

Garbage collect

// Dry run
result, err := image.GC(ctx, "s3://my-bucket/", true)
fmt.Printf("would delete %d blobs (%d bytes)\n", result.Candidates, result.FreedBytes)

// Apply
result, err = image.GC(ctx, "s3://my-bucket/", false)
fmt.Printf("deleted %d blobs (%d bytes freed)\n", result.Deleted, result.FreedBytes)

Stats

stats, err := image.Stats(ctx, "s3://my-bucket/")
fmt.Printf("images: %d, tags: %d, size: %d bytes\n", stats.Images, stats.Tags, stats.ActualBytes)
fmt.Printf("dedup savings: %d bytes\n", stats.LogicalBytes-stats.ActualBytes)

Scan

import "github.com/OuFinx/s3lo/pkg/image"

// Scan an image — Trivy must be installed.
exitCode, err := image.Scan(ctx, "s3://my-bucket/myapp:v1.0", image.ScanOptions{
    TrivyPath: "/usr/local/bin/trivy",
    Severity:  "HIGH,CRITICAL",
})
if exitCode != 0 {
    fmt.Println("vulnerabilities found")
}

With progress callback and platform selection:

exitCode, err := image.Scan(ctx, "s3://my-bucket/alpine:latest", image.ScanOptions{
    TrivyPath: trivyPath,
    Platform:  "linux/amd64",
    Severity:  "HIGH,CRITICAL",
    Format:    "json",
    OnBlob: func(digest string, size int64) {
        fmt.Printf("downloaded %s (%d bytes)\n", digest[:12], size)
    },
})

Parse a reference

import "github.com/OuFinx/s3lo/pkg/ref"

r, err := ref.Parse("s3://my-bucket/myapp:v1.0")
fmt.Println(r.Scheme)            // "s3"
fmt.Println(r.Bucket)            // "my-bucket"
fmt.Println(r.Image)             // "myapp"
fmt.Println(r.Tag)               // "v1.0"
fmt.Println(r.ManifestsPrefix()) // "manifests/myapp/v1.0/"

All schemes are supported:

ref.Parse("gs://my-gcs-bucket/myapp:v1.0")    // GCS
ref.Parse("az://my-container/myapp:v1.0")     // Azure Blob
ref.Parse("local://./store/myapp:v1.0")       // local filesystem

S3-compatible endpoints

To use MinIO, Cloudflare R2, or Ceph, pass the endpoint via context:

import "github.com/OuFinx/s3lo/pkg/storage"

ctx = storage.WithEndpoint(ctx, "http://localhost:9000")
err := image.Push(ctx, "myapp:v1.0", "s3://my-bucket/myapp:v1.0", image.PushOptions{})