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