Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 152 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
}
```
Loading