The distribution protocol is fairly simple, but correctly implementing authentication is hard.
This package implements an http.RoundTripper
that transparently performs:
for registry clients.
Why not just use the
docker/distribution
client?
Great question! Mostly, because I don't want to depend on prometheus/client_golang
.
As a performance optimization, that client uses a cache to keep track of a mapping between blob digests and their descriptors. Unfortunately, the cache uses prometheus to track hits and misses, so if you want to use that client you have to pull in all of prometheus, which is pretty large.
Why does it matter if you depend on prometheus? Who cares?
It's generally polite to your downstream to reduce the number of dependencies your package requires:
- Downloading your package is faster, which helps our Australian friends and people on airplanes.
- There is less code to compile, which speeds up builds and saves the planet from global warming.
- You reduce the likelihood of inflicting dependency hell upon your consumers.
- Tim Hockin prefers it based on his experience working on Kubernetes, and he's a pretty smart guy.
Okay, what about
containerd/containerd
?
Similar reasons! That ends up pulling in grpc, protobuf, and logrus.
Well... what about
containers/image
?
That just uses the the docker/distribution
client... and more!
Wow, what about this package?
Of course, this package isn't perfect either. transport
depends on authn
,
which in turn depends on docker's config file parsing and handling package,
which you don't strictly need but almost certainly want if you're going to be
interacting with a registry.
These graphs were generated by
kisielk/godepgraph
.
This is heavily used by the
remote
package, which implements higher level image-centric functionality, but this
package is useful if you want to interact directly with the registry to do
something that remote
doesn't support, e.g. to handle with schema 1
images.
This package also includes some error
handling
facilities in the form of
CheckError
,
which will parse the response body into a structured error for unexpected http
status codes.
Here's a "simple" program that writes the result of
listing tags
for gcr.io/google-containers/pause
to stdout.
package main
import (
"io"
"net/http"
"os"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
)
func main() {
repo, err := name.NewRepository("gcr.io/google-containers/pause")
if err != nil {
panic(err)
}
// Fetch credentials based on your docker config file, which is $HOME/.docker/config.json or $DOCKER_CONFIG.
auth, err := authn.DefaultKeychain.Resolve(repo.Registry)
if err != nil {
panic(err)
}
// Construct an http.Client that is authorized to pull from gcr.io/google-containers/pause.
scopes := []string{repo.Scope(transport.PullScope)}
t, err := transport.New(repo.Registry, auth, http.DefaultTransport, scopes)
if err != nil {
panic(err)
}
client := &http.Client{Transport: t}
// Make the actual request.
resp, err := client.Get("https://gcr.io/v2/google-containers/pause/tags/list")
if err != nil {
panic(err)
}
// Assert that we get a 200, otherwise attempt to parse body as a structured error.
if err := transport.CheckError(resp, http.StatusOK); err != nil {
panic(err)
}
// Write the response to stdout.
if _, err := io.Copy(os.Stdout, resp.Body); err != nil {
panic(err)
}
}