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  }