github.com/vincentwoo/docker@v0.7.3-0.20160116130405-82401a4b13c0/distribution/registry.go (about) 1 package distribution 2 3 import ( 4 "fmt" 5 "net" 6 "net/http" 7 "net/url" 8 "strings" 9 "time" 10 11 "github.com/docker/distribution" 12 "github.com/docker/distribution/registry/api/errcode" 13 "github.com/docker/distribution/registry/client" 14 "github.com/docker/distribution/registry/client/auth" 15 "github.com/docker/distribution/registry/client/transport" 16 "github.com/docker/docker/distribution/xfer" 17 "github.com/docker/docker/registry" 18 "github.com/docker/engine-api/types" 19 "golang.org/x/net/context" 20 ) 21 22 // fallbackError wraps an error that can possibly allow fallback to a different 23 // endpoint. 24 type fallbackError struct { 25 // err is the error being wrapped. 26 err error 27 // confirmedV2 is set to true if it was confirmed that the registry 28 // supports the v2 protocol. This is used to limit fallbacks to the v1 29 // protocol. 30 confirmedV2 bool 31 } 32 33 // Error renders the FallbackError as a string. 34 func (f fallbackError) Error() string { 35 return f.err.Error() 36 } 37 38 type dumbCredentialStore struct { 39 auth *types.AuthConfig 40 } 41 42 func (dcs dumbCredentialStore) Basic(*url.URL) (string, string) { 43 return dcs.auth.Username, dcs.auth.Password 44 } 45 46 // NewV2Repository returns a repository (v2 only). It creates a HTTP transport 47 // providing timeout settings and authentication support, and also verifies the 48 // remote API version. 49 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) { 50 repoName := repoInfo.FullName() 51 // If endpoint does not support CanonicalName, use the RemoteName instead 52 if endpoint.TrimHostname { 53 repoName = repoInfo.RemoteName() 54 } 55 56 // TODO(dmcgowan): Call close idle connections when complete, use keep alive 57 base := &http.Transport{ 58 Proxy: http.ProxyFromEnvironment, 59 Dial: (&net.Dialer{ 60 Timeout: 30 * time.Second, 61 KeepAlive: 30 * time.Second, 62 DualStack: true, 63 }).Dial, 64 TLSHandshakeTimeout: 10 * time.Second, 65 TLSClientConfig: endpoint.TLSConfig, 66 // TODO(dmcgowan): Call close idle connections when complete and use keep alive 67 DisableKeepAlives: true, 68 } 69 70 modifiers := registry.DockerHeaders(metaHeaders) 71 authTransport := transport.NewTransport(base, modifiers...) 72 pingClient := &http.Client{ 73 Transport: authTransport, 74 Timeout: 15 * time.Second, 75 } 76 endpointStr := strings.TrimRight(endpoint.URL, "/") + "/v2/" 77 req, err := http.NewRequest("GET", endpointStr, nil) 78 if err != nil { 79 return nil, false, err 80 } 81 resp, err := pingClient.Do(req) 82 if err != nil { 83 return nil, false, err 84 } 85 defer resp.Body.Close() 86 87 v2Version := auth.APIVersion{ 88 Type: "registry", 89 Version: "2.0", 90 } 91 92 versions := auth.APIVersions(resp, registry.DefaultRegistryVersionHeader) 93 for _, pingVersion := range versions { 94 if pingVersion == v2Version { 95 // The version header indicates we're definitely 96 // talking to a v2 registry. So don't allow future 97 // fallbacks to the v1 protocol. 98 99 foundVersion = true 100 break 101 } 102 } 103 104 challengeManager := auth.NewSimpleChallengeManager() 105 if err := challengeManager.AddResponse(resp); err != nil { 106 return nil, foundVersion, err 107 } 108 109 if authConfig.RegistryToken != "" { 110 passThruTokenHandler := &existingTokenHandler{token: authConfig.RegistryToken} 111 modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler)) 112 } else { 113 creds := dumbCredentialStore{auth: authConfig} 114 tokenHandler := auth.NewTokenHandler(authTransport, creds, repoName, actions...) 115 basicHandler := auth.NewBasicHandler(creds) 116 modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) 117 } 118 tr := transport.NewTransport(base, modifiers...) 119 120 repo, err = client.NewRepository(ctx, repoName, endpoint.URL, tr) 121 return repo, foundVersion, err 122 } 123 124 type existingTokenHandler struct { 125 token string 126 } 127 128 func (th *existingTokenHandler) Scheme() string { 129 return "bearer" 130 } 131 132 func (th *existingTokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error { 133 req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", th.token)) 134 return nil 135 } 136 137 // retryOnError wraps the error in xfer.DoNotRetry if we should not retry the 138 // operation after this error. 139 func retryOnError(err error) error { 140 switch v := err.(type) { 141 case errcode.Errors: 142 return retryOnError(v[0]) 143 case errcode.Error: 144 switch v.Code { 145 case errcode.ErrorCodeUnauthorized, errcode.ErrorCodeUnsupported, errcode.ErrorCodeDenied: 146 return xfer.DoNotRetry{Err: err} 147 } 148 case *client.UnexpectedHTTPResponseError: 149 return xfer.DoNotRetry{Err: err} 150 } 151 // let's be nice and fallback if the error is a completely 152 // unexpected one. 153 // If new errors have to be handled in some way, please 154 // add them to the switch above. 155 return err 156 }