package distribution import ( "fmt" "net" "net/http" "time" "github.com/docker/distribution" distreference "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" "github.com/docker/go-connections/sockets" "golang.org/x/net/context" ) // NewV2Repository returns a repository (v2 only). It creates an HTTP transport // providing timeout settings and authentication support, and also verifies the // remote API version. func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string) (repo distribution.Repository, foundVersion bool, err error) { repoName := repoInfo.FullName() // If endpoint does not support CanonicalName, use the RemoteName instead if endpoint.TrimHostname { repoName = repoInfo.RemoteName() } direct := &net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, DualStack: true, } // TODO(dmcgowan): Call close idle connections when complete, use keep alive base := &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: direct.Dial, TLSHandshakeTimeout: 10 * time.Second, TLSClientConfig: endpoint.TLSConfig, // TODO(dmcgowan): Call close idle connections when complete and use keep alive DisableKeepAlives: true, } proxyDialer, err := sockets.DialerFromEnvironment(direct) if err == nil { base.Dial = proxyDialer.Dial } modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), metaHeaders) authTransport := transport.NewTransport(base, modifiers...) challengeManager, foundVersion, 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, foundVersion, fallbackError{ err: err, confirmedV2: foundVersion, transportOK: transportOK, } } if authConfig.RegistryToken != "" { passThruTokenHandler := &existingTokenHandler{token: authConfig.RegistryToken} modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler)) } else { creds := registry.NewStaticCredentialStore(authConfig) tokenHandlerOptions := auth.TokenHandlerOptions{ Transport: authTransport, Credentials: creds, Scopes: []auth.Scope{ auth.RepositoryScope{ Repository: repoName, Actions: actions, }, }, 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 := distreference.ParseNamed(repoName) if err != nil { return nil, foundVersion, fallbackError{ err: err, confirmedV2: foundVersion, transportOK: true, } } repo, err = client.NewRepository(ctx, repoNameRef, endpoint.URL.String(), tr) if err != nil { err = fallbackError{ err: err, confirmedV2: foundVersion, 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 }