| package distribution // import "github.com/docker/docker/distribution" | |
| import ( | |
| "context" | |
| "fmt" | |
| "net" | |
| "net/http" | |
| "time" | |
| "github.com/distribution/reference" | |
| "github.com/docker/distribution" | |
| "github.com/docker/distribution/manifest/schema2" | |
| "github.com/docker/distribution/registry/client" | |
| "github.com/docker/distribution/registry/client/auth" | |
| "github.com/docker/distribution/registry/client/transport" | |
| registrytypes "github.com/docker/docker/api/types/registry" | |
| "github.com/docker/docker/dockerversion" | |
| "github.com/docker/docker/registry" | |
| ocispec "github.com/opencontainers/image-spec/specs-go/v1" | |
| ) | |
| var ( | |
| // supportedMediaTypes represents acceptable media-type(-prefixes) | |
| // we use this list to prevent obscure errors when trying to pull | |
| // OCI artifacts. | |
| supportedMediaTypes = []string{ | |
| // valid prefixes | |
| "application/vnd.oci.image", | |
| "application/vnd.docker", | |
| // these types may occur on old images, and are copied from | |
| // defaultImageTypes below. | |
| "application/octet-stream", | |
| "application/json", | |
| "text/html", | |
| "", | |
| } | |
| // 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 *registrytypes.AuthConfig, actions ...string, | |
| ) (distribution.Repository, 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 != "" { | |
| modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, &passThruTokenHandler{token: authConfig.RegistryToken})) | |
| } else { | |
| creds := registry.NewStaticCredentialStore(authConfig) | |
| tokenHandler := auth.NewTokenHandlerWithOptions(auth.TokenHandlerOptions{ | |
| Transport: authTransport, | |
| Credentials: creds, | |
| Scopes: []auth.Scope{auth.RepositoryScope{ | |
| Repository: repoName, | |
| Actions: actions, | |
| }}, | |
| ClientID: registry.AuthClientID, | |
| }) | |
| 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 { | |
| return nil, fallbackError{ | |
| err: err, | |
| transportOK: true, | |
| } | |
| } | |
| return repo, nil | |
| } | |
| type passThruTokenHandler struct { | |
| token string | |
| } | |
| func (th *passThruTokenHandler) Scheme() string { | |
| return "bearer" | |
| } | |
| func (th *passThruTokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error { | |
| req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", th.token)) | |
| return nil | |
| } |