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  }