| 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/dockerversion" |
| "github.com/docker/docker/registry" |
| "github.com/docker/engine-api/types" |
| "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 |
| } |