github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/distribution/registry.go (about)

     1  package distribution // import "github.com/docker/docker/distribution"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"net/http"
     8  	"time"
     9  
    10  	"github.com/docker/distribution"
    11  	"github.com/docker/distribution/manifest/schema2"
    12  	"github.com/docker/distribution/reference"
    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/api/types"
    17  	"github.com/docker/docker/dockerversion"
    18  	"github.com/docker/docker/registry"
    19  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    20  )
    21  
    22  // ImageTypes represents the schema2 config types for images
    23  var ImageTypes = []string{
    24  	schema2.MediaTypeImageConfig,
    25  	ocispec.MediaTypeImageConfig,
    26  	// Handle unexpected values from https://github.com/docker/distribution/issues/1621
    27  	// (see also https://github.com/docker/docker/issues/22378,
    28  	// https://github.com/docker/docker/issues/30083)
    29  	"application/octet-stream",
    30  	"application/json",
    31  	"text/html",
    32  	// Treat defaulted values as images, newer types cannot be implied
    33  	"",
    34  }
    35  
    36  // PluginTypes represents the schema2 config types for plugins
    37  var PluginTypes = []string{
    38  	schema2.MediaTypePluginConfig,
    39  }
    40  
    41  var mediaTypeClasses map[string]string
    42  
    43  func init() {
    44  	// initialize media type classes with all know types for
    45  	// plugin
    46  	mediaTypeClasses = map[string]string{}
    47  	for _, t := range ImageTypes {
    48  		mediaTypeClasses[t] = "image"
    49  	}
    50  	for _, t := range PluginTypes {
    51  		mediaTypeClasses[t] = "plugin"
    52  	}
    53  }
    54  
    55  // NewV2Repository returns a repository (v2 only). It creates an HTTP transport
    56  // providing timeout settings and authentication support, and also verifies the
    57  // remote API version.
    58  func NewV2Repository(
    59  	ctx context.Context, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint,
    60  	metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string,
    61  ) (repo distribution.Repository, foundVersion bool, err error) {
    62  	repoName := repoInfo.Name.Name()
    63  	// If endpoint does not support CanonicalName, use the RemoteName instead
    64  	if endpoint.TrimHostname {
    65  		repoName = reference.Path(repoInfo.Name)
    66  	}
    67  
    68  	direct := &net.Dialer{
    69  		Timeout:   30 * time.Second,
    70  		KeepAlive: 30 * time.Second,
    71  		DualStack: true,
    72  	}
    73  
    74  	// TODO(dmcgowan): Call close idle connections when complete, use keep alive
    75  	base := &http.Transport{
    76  		Proxy:               http.ProxyFromEnvironment,
    77  		DialContext:         direct.DialContext,
    78  		TLSHandshakeTimeout: 10 * time.Second,
    79  		TLSClientConfig:     endpoint.TLSConfig,
    80  		// TODO(dmcgowan): Call close idle connections when complete and use keep alive
    81  		DisableKeepAlives: true,
    82  	}
    83  
    84  	modifiers := registry.Headers(dockerversion.DockerUserAgent(ctx), metaHeaders)
    85  	authTransport := transport.NewTransport(base, modifiers...)
    86  
    87  	challengeManager, foundVersion, err := registry.PingV2Registry(endpoint.URL, authTransport)
    88  	if err != nil {
    89  		transportOK := false
    90  		if responseErr, ok := err.(registry.PingResponseError); ok {
    91  			transportOK = true
    92  			err = responseErr.Err
    93  		}
    94  		return nil, foundVersion, fallbackError{
    95  			err:         err,
    96  			confirmedV2: foundVersion,
    97  			transportOK: transportOK,
    98  		}
    99  	}
   100  
   101  	if authConfig.RegistryToken != "" {
   102  		passThruTokenHandler := &existingTokenHandler{token: authConfig.RegistryToken}
   103  		modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler))
   104  	} else {
   105  		scope := auth.RepositoryScope{
   106  			Repository: repoName,
   107  			Actions:    actions,
   108  			Class:      repoInfo.Class,
   109  		}
   110  
   111  		creds := registry.NewStaticCredentialStore(authConfig)
   112  		tokenHandlerOptions := auth.TokenHandlerOptions{
   113  			Transport:   authTransport,
   114  			Credentials: creds,
   115  			Scopes:      []auth.Scope{scope},
   116  			ClientID:    registry.AuthClientID,
   117  		}
   118  		tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
   119  		basicHandler := auth.NewBasicHandler(creds)
   120  		modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
   121  	}
   122  	tr := transport.NewTransport(base, modifiers...)
   123  
   124  	repoNameRef, err := reference.WithName(repoName)
   125  	if err != nil {
   126  		return nil, foundVersion, fallbackError{
   127  			err:         err,
   128  			confirmedV2: foundVersion,
   129  			transportOK: true,
   130  		}
   131  	}
   132  
   133  	repo, err = client.NewRepository(repoNameRef, endpoint.URL.String(), tr)
   134  	if err != nil {
   135  		err = fallbackError{
   136  			err:         err,
   137  			confirmedV2: foundVersion,
   138  			transportOK: true,
   139  		}
   140  	}
   141  	return
   142  }
   143  
   144  type existingTokenHandler struct {
   145  	token string
   146  }
   147  
   148  func (th *existingTokenHandler) Scheme() string {
   149  	return "bearer"
   150  }
   151  
   152  func (th *existingTokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
   153  	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", th.token))
   154  	return nil
   155  }