moby/distribution/registry.go
Sebastiaan van Stijn 566c8db66d
distribution: add GetRepository(), un-export NewV2Repository, ValidateRepoName
These were only exported to facilitate ImageService.GetRepository() (used for
the `GET /distribution/{name:.*}/json` endpoint.

Moving the core functionality of that to the distribution package makes it
more consistent with (e.g.) "pull" operations, and allows us to keep more things
internal.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-04-21 23:12:02 +02:00

152 lines
4.5 KiB
Go

package distribution // import "github.com/docker/docker/distribution"
import (
"context"
"fmt"
"net"
"net/http"
"time"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/schema2"
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/client"
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/api/types"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/registry"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
var (
// defaultImageTypes represents the schema2 config types for images
defaultImageTypes = []string{
schema2.MediaTypeImageConfig,
ocispec.MediaTypeImageConfig,
// Handle unexpected values from https://github.com/docker/distribution/issues/1621
// (see also https://github.com/docker/docker/issues/22378,
// https://github.com/docker/docker/issues/30083)
"application/octet-stream",
"application/json",
"text/html",
// Treat defaulted values as images, newer types cannot be implied
"",
}
// pluginTypes represents the schema2 config types for plugins
pluginTypes = []string{
schema2.MediaTypePluginConfig,
}
mediaTypeClasses map[string]string
)
func init() {
// initialize media type classes with all know types for images and plugins.
mediaTypeClasses = map[string]string{}
for _, t := range defaultImageTypes {
mediaTypeClasses[t] = "image"
}
for _, t := range pluginTypes {
mediaTypeClasses[t] = "plugin"
}
}
// newRepository returns a repository (v2 only). It creates an HTTP transport
// providing timeout settings and authentication support, and also verifies the
// remote API version.
func newRepository(
ctx context.Context, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint,
metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string,
) (repo distribution.Repository, err error) {
repoName := repoInfo.Name.Name()
// If endpoint does not support CanonicalName, use the RemoteName instead
if endpoint.TrimHostname {
repoName = reference.Path(repoInfo.Name)
}
direct := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
// TODO(dmcgowan): Call close idle connections when complete, use keep alive
base := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: direct.DialContext,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: endpoint.TLSConfig,
// TODO(dmcgowan): Call close idle connections when complete and use keep alive
DisableKeepAlives: true,
}
modifiers := registry.Headers(dockerversion.DockerUserAgent(ctx), metaHeaders)
authTransport := transport.NewTransport(base, modifiers...)
challengeManager, err := registry.PingV2Registry(endpoint.URL, authTransport)
if err != nil {
transportOK := false
if responseErr, ok := err.(registry.PingResponseError); ok {
transportOK = true
err = responseErr.Err
}
return nil, fallbackError{
err: err,
transportOK: transportOK,
}
}
if authConfig.RegistryToken != "" {
passThruTokenHandler := &existingTokenHandler{token: authConfig.RegistryToken}
modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler))
} else {
scope := auth.RepositoryScope{
Repository: repoName,
Actions: actions,
Class: repoInfo.Class,
}
creds := registry.NewStaticCredentialStore(authConfig)
tokenHandlerOptions := auth.TokenHandlerOptions{
Transport: authTransport,
Credentials: creds,
Scopes: []auth.Scope{scope},
ClientID: registry.AuthClientID,
}
tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
basicHandler := auth.NewBasicHandler(creds)
modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
}
tr := transport.NewTransport(base, modifiers...)
repoNameRef, err := reference.WithName(repoName)
if err != nil {
return nil, fallbackError{
err: err,
transportOK: true,
}
}
repo, err = client.NewRepository(repoNameRef, endpoint.URL.String(), tr)
if err != nil {
err = fallbackError{
err: err,
transportOK: true,
}
}
return
}
type existingTokenHandler struct {
token string
}
func (th *existingTokenHandler) Scheme() string {
return "bearer"
}
func (th *existingTokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", th.token))
return nil
}