From e91d4a4e79888c45de1369aed279b344433cdc35 Mon Sep 17 00:00:00 2001 From: Jeff Carter Date: Fri, 3 Apr 2026 10:07:41 -0400 Subject: [PATCH] Update readme to include purpose for the package, along with more examples. --- README.md | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 152 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b0c1647..4a7fd79 100644 --- a/README.md +++ b/README.md @@ -2,26 +2,45 @@ This repository holds functionality related to OCI (Open Container Initiative). -The top level package (`oci`) this module defines a [Go interface](./interface.go) that encapsulates the operations provided by an OCI -registry. +The top-level package (`oci`) defines a [Go interface](./interface.go) that encapsulates the operations provided by an OCI registry — reading blobs and manifests, pushing content, listing tags, and more. -Full reference documentation can be found [here](https://pkg.go.dev/cuelabs.dev/go/oci/oci). +Full reference documentation can be found at [pkg.go.dev/github.com/jcarter3/oci](https://pkg.go.dev/github.com/jcarter3/oci). -It also provides a lightweight in-memory implementation of that interface (`ocimem`) -and an HTTP server that implements the [OCI registry protocol](https://github.com/opencontainers/distribution-spec/blob/main/spec.md) on top of it. - -The server currently passes the [conformance tests](https://pkg.go.dev/github.com/opencontainers/distribution-spec/conformance). - -The aim is to provide an ergonomic interface for defining and layering -OCI registry implementations. +The aim is to provide an ergonomic interface for defining and layering OCI registry implementations. Although the API is fairly stable, it's still in v0 currently, so incompatible changes can't be ruled out. The code was originally derived from [cue-labs/oci](https://github.com/cue-labs/oci) which was originally derived from -the [go-containerregistry](https://pkg.go.dev/github.com/google/go-containerregistry/pkg/registry) package, but has +the [go-containerregistry](https://pkg.go.dev/github.com/google/go-containerregistry/pkg/registry) package, but has considerably diverged since then. -# Usage +## Purpose + +There are other libraries that exist for working with OCI registries, and they are great! However, as we look to invest +in Docker Hub and add new features ahead of them getting standardized into the OCI spec, we need a library that can be +used to interact with those features. + +## Packages + +| Package | Description | +|---------|-------------| +| `oci` | Core interface (`oci.Interface`) and types shared across all packages. | +| `ociclient` | HTTP client that implements `oci.Interface` against a remote OCI registry. | +| `ociserver` | HTTP server that serves the OCI distribution protocol on top of any `oci.Interface`. | +| `ocimem` | Lightweight in-memory `oci.Interface` implementation, useful for testing and caching. | +| `ociauth` | Authentication transport implementing the Docker/OCI token flow, plus helpers for loading credentials from Docker config files. | +| `ocifilter` | Wrappers that expose restricted or transformed views of a registry (read-only, immutable, namespace prefix, custom access control). | +| `ociunify` | Combines two registries into a single unified `oci.Interface`, with configurable read policy. | +| `ocilarge` | Parallel multi-range download (and upload) for large blobs, automatically tuning chunk size to available bandwidth. | +| `ocidebug` | Registry wrapper that logs every operation — useful for tracing and debugging. | +| `ociref` | Reference and digest parsing/validation utilities. | + +The server currently passes the [OCI distribution conformance tests](https://pkg.go.dev/github.com/opencontainers/distribution-spec/conformance). + +## Usage + +### List tags on Docker Hub + ```go package main @@ -56,4 +75,125 @@ func main() { fmt.Printf("%s\n", tag) } } +``` + +### Fetch a manifest by tag + +```go +package main + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/jcarter3/oci/ociauth" + "github.com/jcarter3/oci/ociclient" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +func main() { + cf, err := ociauth.Load(nil) + if err != nil { + panic(err) + } + + ocl, err := ociclient.New("index.docker.io", &ociclient.Options{ + Transport: ociauth.NewStdTransport(ociauth.StdTransportParams{ + Config: cf, + }), + }) + if err != nil { + panic(err) + } + + // Fetch the manifest for alpine:latest. + r, err := ocl.GetTag(context.Background(), "library/alpine", "latest") + if err != nil { + panic(err) + } + defer r.Close() + + fmt.Printf("media type: %s\n", r.Descriptor().MediaType) + fmt.Printf("digest: %s\n", r.Descriptor().Digest) + + var manifest ocispec.Manifest + if err := json.NewDecoder(r).Decode(&manifest); err != nil { + panic(err) + } + fmt.Printf("config digest: %s\n", manifest.Config.Digest) + for i, layer := range manifest.Layers { + fmt.Printf("layer[%d]: %s (%d bytes)\n", i, layer.Digest, layer.Size) + } +} +``` + +### Pull a blob (image layer) + +```go +package main + +import ( + "context" + "io" + "os" + + "github.com/jcarter3/oci/ociauth" + "github.com/jcarter3/oci/ociclient" +) + +func main() { + cf, err := ociauth.Load(nil) + if err != nil { + panic(err) + } + + ocl, err := ociclient.New("index.docker.io", &ociclient.Options{ + Transport: ociauth.NewStdTransport(ociauth.StdTransportParams{ + Config: cf, + }), + }) + if err != nil { + panic(err) + } + + // Pull a specific blob by digest. + const repo = "library/alpine" + const layerDigest = "sha256:bca4290a96390d7a6fc6f2f9929370d06f8dfcacba591c76e3d5c5044e7f420c" + + blob, err := ocl.GetBlob(context.Background(), repo, layerDigest) + if err != nil { + panic(err) + } + defer blob.Close() + + f, err := os.Create("layer.tar.gz") + if err != nil { + panic(err) + } + defer f.Close() + + if _, err := io.Copy(f, blob); err != nil { + panic(err) + } +} +``` + +### Serve a local in-memory registry over HTTP + +```go +package main + +import ( + "net/http" + + "github.com/jcarter3/oci/ocimem" + "github.com/jcarter3/oci/ociserver" +) + +func main() { + backend := ocimem.New() + handler := ociserver.New(backend, nil) + http.ListenAndServe(":5000", handler) +} ``` \ No newline at end of file