From 52136ab008e9586786fa4d81bd964e0c055d6fe5 Mon Sep 17 00:00:00 2001 From: Aaron Lehmann Date: Tue, 21 Jul 2015 12:40:36 -0700 Subject: [PATCH] Improve documentation and golint compliance of registry package * Add godoc documentation where it was missing * Change identifier names that don't match Go style, such as INDEX_NAME * Rename RegistryInfo to PingResult, which more accurately describes what this structure is for. It also has the benefit of making the name not stutter if used outside the package. Updates #14756 Signed-off-by: Aaron Lehmann --- docs/auth.go | 4 +- docs/auth_test.go | 10 ++--- docs/config.go | 43 ++++++++++-------- docs/endpoint.go | 42 ++++++++++-------- docs/endpoint_test.go | 2 +- docs/registry.go | 25 ++++++----- docs/registry_mock_test.go | 10 ++--- docs/registry_test.go | 60 ++++++++++++------------- docs/service.go | 37 +++++++++------- docs/session.go | 51 +++++++++++++++------- docs/types.go | 89 +++++++++++++++++++++++++++++--------- 11 files changed, 231 insertions(+), 142 deletions(-) diff --git a/docs/auth.go b/docs/auth.go index 157d2140..57560935 100644 --- a/docs/auth.go +++ b/docs/auth.go @@ -36,7 +36,7 @@ func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (stri return "", fmt.Errorf("Server Error: Server Address not set.") } - loginAgainstOfficialIndex := serverAddress == INDEXSERVER + loginAgainstOfficialIndex := serverAddress == IndexServer // to avoid sending the server address to the server it should be removed before being marshalled authCopy := *authConfig @@ -220,7 +220,7 @@ func tryV2TokenAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]str return nil } -// this method matches a auth configuration to a server address or a url +// ResolveAuthConfig matches an auth configuration to a server address or a URL func ResolveAuthConfig(config *cliconfig.ConfigFile, index *IndexInfo) cliconfig.AuthConfig { configKey := index.GetAuthConfigKey() // First try the happy case diff --git a/docs/auth_test.go b/docs/auth_test.go index be520add..a8e3da01 100644 --- a/docs/auth_test.go +++ b/docs/auth_test.go @@ -37,7 +37,7 @@ func setupTempConfigFile() (*cliconfig.ConfigFile, error) { root = filepath.Join(root, cliconfig.ConfigFileName) configFile := cliconfig.NewConfigFile(root) - for _, registry := range []string{"testIndex", INDEXSERVER} { + for _, registry := range []string{"testIndex", IndexServer} { configFile.AuthConfigs[registry] = cliconfig.AuthConfig{ Username: "docker-user", Password: "docker-pass", @@ -82,7 +82,7 @@ func TestResolveAuthConfigIndexServer(t *testing.T) { } defer os.RemoveAll(configFile.Filename()) - indexConfig := configFile.AuthConfigs[INDEXSERVER] + indexConfig := configFile.AuthConfigs[IndexServer] officialIndex := &IndexInfo{ Official: true, @@ -92,10 +92,10 @@ func TestResolveAuthConfigIndexServer(t *testing.T) { } resolved := ResolveAuthConfig(configFile, officialIndex) - assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return INDEXSERVER") + assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServer") resolved = ResolveAuthConfig(configFile, privateIndex) - assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return INDEXSERVER") + assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServer") } func TestResolveAuthConfigFullURL(t *testing.T) { @@ -120,7 +120,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) { Password: "baz-pass", Email: "baz@example.com", } - configFile.AuthConfigs[INDEXSERVER] = officialAuth + configFile.AuthConfigs[IndexServer] = officialAuth expectedAuths := map[string]cliconfig.AuthConfig{ "registry.example.com": registryAuth, diff --git a/docs/config.go b/docs/config.go index a1dc3aba..d2108894 100644 --- a/docs/config.go +++ b/docs/config.go @@ -21,24 +21,33 @@ type Options struct { } const ( - DEFAULT_NAMESPACE = "docker.io" - DEFAULT_V2_REGISTRY = "https://registry-1.docker.io" - DEFAULT_REGISTRY_VERSION_HEADER = "Docker-Distribution-Api-Version" - DEFAULT_V1_REGISTRY = "https://index.docker.io" + // DefaultNamespace is the default namespace + DefaultNamespace = "docker.io" + // DefaultV2Registry is the URI of the default v2 registry + DefaultV2Registry = "https://registry-1.docker.io" + // DefaultRegistryVersionHeader is the name of the default HTTP header + // that carries Registry version info + DefaultRegistryVersionHeader = "Docker-Distribution-Api-Version" + // DefaultV1Registry is the URI of the default v1 registry + DefaultV1Registry = "https://index.docker.io" - CERTS_DIR = "/etc/docker/certs.d" + // CertsDir is the directory where certificates are stored + CertsDir = "/etc/docker/certs.d" - // Only used for user auth + account creation - REGISTRYSERVER = DEFAULT_V2_REGISTRY - INDEXSERVER = DEFAULT_V1_REGISTRY + "/v1/" - INDEXNAME = "docker.io" + // IndexServer is the v1 registry server used for user auth + account creation + IndexServer = DefaultV1Registry + "/v1/" + // IndexName is the name of the index + IndexName = "docker.io" - // INDEXSERVER = "https://registry-stage.hub.docker.com/v1/" + // IndexServer = "https://registry-stage.hub.docker.com/v1/" ) var ( + // ErrInvalidRepositoryName is an error returned if the repository name did + // not have the correct form ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") - emptyServiceConfig = NewServiceConfig(nil) + + emptyServiceConfig = NewServiceConfig(nil) ) // InstallFlags adds command-line options to the top-level flag parser for @@ -116,8 +125,8 @@ func NewServiceConfig(options *Options) *ServiceConfig { } // Configure public registry. - config.IndexConfigs[INDEXNAME] = &IndexInfo{ - Name: INDEXNAME, + config.IndexConfigs[IndexName] = &IndexInfo{ + Name: IndexName, Mirrors: config.Mirrors, Secure: true, Official: true, @@ -196,8 +205,8 @@ func ValidateMirror(val string) (string, error) { // ValidateIndexName validates an index name. func ValidateIndexName(val string) (string, error) { // 'index.docker.io' => 'docker.io' - if val == "index."+INDEXNAME { - val = INDEXNAME + if val == "index."+IndexName { + val = IndexName } if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") { return "", fmt.Errorf("Invalid index name (%s). Cannot begin or end with a hyphen.", val) @@ -267,7 +276,7 @@ func (config *ServiceConfig) NewIndexInfo(indexName string) (*IndexInfo, error) // index as the AuthConfig key, and uses the (host)name[:port] for private indexes. func (index *IndexInfo) GetAuthConfigKey() string { if index.Official { - return INDEXSERVER + return IndexServer } return index.Name } @@ -280,7 +289,7 @@ func splitReposName(reposName string) (string, string) { !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") { // This is a Docker Index repos (ex: samalba/hipache or ubuntu) // 'docker.io' - indexName = INDEXNAME + indexName = IndexName remoteName = reposName } else { indexName = nameParts[0] diff --git a/docs/endpoint.go b/docs/endpoint.go index 17443543..c6361346 100644 --- a/docs/endpoint.go +++ b/docs/endpoint.go @@ -111,6 +111,7 @@ func newEndpoint(address string, tlsConfig *tls.Config, metaHeaders http.Header) return endpoint, nil } +// GetEndpoint returns a new endpoint with the specified headers func (repoInfo *RepositoryInfo) GetEndpoint(metaHeaders http.Header) (*Endpoint, error) { return NewEndpoint(repoInfo.Index, metaHeaders) } @@ -142,7 +143,10 @@ func (e *Endpoint) Path(path string) string { return fmt.Sprintf("%s/v%d/%s", e.URL, e.Version, path) } -func (e *Endpoint) Ping() (RegistryInfo, error) { +// Ping pings the remote endpoint with v2 and v1 pings to determine the API +// version. It returns a PingResult containing the discovered version. The +// PingResult also indicates whether the registry is standalone or not. +func (e *Endpoint) Ping() (PingResult, error) { // The ping logic to use is determined by the registry endpoint version. switch e.Version { case APIVersion1: @@ -167,49 +171,49 @@ func (e *Endpoint) Ping() (RegistryInfo, error) { } e.Version = APIVersionUnknown - return RegistryInfo{}, fmt.Errorf("unable to ping registry endpoint %s\nv2 ping attempt failed with error: %s\n v1 ping attempt failed with error: %s", e, errV2, errV1) + return PingResult{}, fmt.Errorf("unable to ping registry endpoint %s\nv2 ping attempt failed with error: %s\n v1 ping attempt failed with error: %s", e, errV2, errV1) } -func (e *Endpoint) pingV1() (RegistryInfo, error) { +func (e *Endpoint) pingV1() (PingResult, error) { logrus.Debugf("attempting v1 ping for registry endpoint %s", e) - if e.String() == INDEXSERVER { + if e.String() == IndexServer { // Skip the check, we know this one is valid // (and we never want to fallback to http in case of error) - return RegistryInfo{Standalone: false}, nil + return PingResult{Standalone: false}, nil } req, err := http.NewRequest("GET", e.Path("_ping"), nil) if err != nil { - return RegistryInfo{Standalone: false}, err + return PingResult{Standalone: false}, err } resp, err := e.client.Do(req) if err != nil { - return RegistryInfo{Standalone: false}, err + return PingResult{Standalone: false}, err } defer resp.Body.Close() jsonString, err := ioutil.ReadAll(resp.Body) if err != nil { - return RegistryInfo{Standalone: false}, fmt.Errorf("error while reading the http response: %s", err) + return PingResult{Standalone: false}, fmt.Errorf("error while reading the http response: %s", err) } // If the header is absent, we assume true for compatibility with earlier // versions of the registry. default to true - info := RegistryInfo{ + info := PingResult{ Standalone: true, } if err := json.Unmarshal(jsonString, &info); err != nil { - logrus.Debugf("Error unmarshalling the _ping RegistryInfo: %s", err) + logrus.Debugf("Error unmarshalling the _ping PingResult: %s", err) // don't stop here. Just assume sane defaults } if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" { logrus.Debugf("Registry version header: '%s'", hdr) info.Version = hdr } - logrus.Debugf("RegistryInfo.Version: %q", info.Version) + logrus.Debugf("PingResult.Version: %q", info.Version) standalone := resp.Header.Get("X-Docker-Registry-Standalone") logrus.Debugf("Registry standalone header: '%s'", standalone) @@ -220,21 +224,21 @@ func (e *Endpoint) pingV1() (RegistryInfo, error) { // there is a header set, and it is not "true" or "1", so assume fails info.Standalone = false } - logrus.Debugf("RegistryInfo.Standalone: %t", info.Standalone) + logrus.Debugf("PingResult.Standalone: %t", info.Standalone) return info, nil } -func (e *Endpoint) pingV2() (RegistryInfo, error) { +func (e *Endpoint) pingV2() (PingResult, error) { logrus.Debugf("attempting v2 ping for registry endpoint %s", e) req, err := http.NewRequest("GET", e.Path(""), nil) if err != nil { - return RegistryInfo{}, err + return PingResult{}, err } resp, err := e.client.Do(req) if err != nil { - return RegistryInfo{}, err + return PingResult{}, err } defer resp.Body.Close() @@ -253,21 +257,21 @@ HeaderLoop: } if !supportsV2 { - return RegistryInfo{}, fmt.Errorf("%s does not appear to be a v2 registry endpoint", e) + return PingResult{}, fmt.Errorf("%s does not appear to be a v2 registry endpoint", e) } if resp.StatusCode == http.StatusOK { // It would seem that no authentication/authorization is required. // So we don't need to parse/add any authorization schemes. - return RegistryInfo{Standalone: true}, nil + return PingResult{Standalone: true}, nil } if resp.StatusCode == http.StatusUnauthorized { // Parse the WWW-Authenticate Header and store the challenges // on this endpoint object. e.AuthChallenges = parseAuthHeader(resp.Header) - return RegistryInfo{}, nil + return PingResult{}, nil } - return RegistryInfo{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode)) + return PingResult{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode)) } diff --git a/docs/endpoint_test.go b/docs/endpoint_test.go index a04f9a03..ee301dbd 100644 --- a/docs/endpoint_test.go +++ b/docs/endpoint_test.go @@ -12,7 +12,7 @@ func TestEndpointParse(t *testing.T) { str string expected string }{ - {INDEXSERVER, INDEXSERVER}, + {IndexServer, IndexServer}, {"http://0.0.0.0:5000/v1/", "http://0.0.0.0:5000/v1/"}, {"http://0.0.0.0:5000/v2/", "http://0.0.0.0:5000/v2/"}, {"http://0.0.0.0:5000", "http://0.0.0.0:5000/v0/"}, diff --git a/docs/registry.go b/docs/registry.go index 74a0ad5f..fd85c21c 100644 --- a/docs/registry.go +++ b/docs/registry.go @@ -21,19 +21,12 @@ import ( ) var ( + // ErrAlreadyExists is an error returned if an image being pushed + // already exists on the remote side ErrAlreadyExists = errors.New("Image already exists") - ErrDoesNotExist = errors.New("Image does not exist") errLoginRequired = errors.New("Authentication is required.") ) -type TimeoutType uint32 - -const ( - NoTimeout TimeoutType = iota - ReceiveTimeout - ConnectTimeout -) - // dockerUserAgent is the User-Agent the Docker client uses to identify itself. // It is populated on init(), comprising version information of different components. var dockerUserAgent string @@ -74,10 +67,12 @@ func DockerHeaders(metaHeaders http.Header) []transport.RequestModifier { return modifiers } +// HTTPClient returns a HTTP client structure which uses the given transport +// and contains the necessary headers for redirected requests func HTTPClient(transport http.RoundTripper) *http.Client { return &http.Client{ Transport: transport, - CheckRedirect: AddRequiredHeadersToRedirectedRequests, + CheckRedirect: addRequiredHeadersToRedirectedRequests, } } @@ -98,7 +93,9 @@ func trustedLocation(req *http.Request) bool { return false } -func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error { +// addRequiredHeadersToRedirectedRequests adds the necessary redirection headers +// for redirected requests +func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error { if via != nil && via[0] != nil { if trustedLocation(req) && trustedLocation(via[0]) { req.Header = via[0].Header @@ -124,6 +121,8 @@ func shouldV2Fallback(err errcode.Error) bool { return false } +// ErrNoSupport is an error type used for errors indicating that an operation +// is not supported. It encapsulates a more specific error. type ErrNoSupport struct{ Err error } func (e ErrNoSupport) Error() string { @@ -133,6 +132,8 @@ func (e ErrNoSupport) Error() string { return e.Err.Error() } +// ContinueOnError returns true if we should fallback to the next endpoint +// as a result of this error. func ContinueOnError(err error) bool { switch v := err.(type) { case errcode.Errors: @@ -145,6 +146,8 @@ func ContinueOnError(err error) bool { return false } +// NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the +// default TLS configuration. func NewTransport(tlsConfig *tls.Config) *http.Transport { if tlsConfig == nil { var cfg = tlsconfig.ServerDefault diff --git a/docs/registry_mock_test.go b/docs/registry_mock_test.go index 9217956c..fb19e577 100644 --- a/docs/registry_mock_test.go +++ b/docs/registry_mock_test.go @@ -145,7 +145,7 @@ func makeURL(req string) string { return testHTTPServer.URL + req } -func makeHttpsURL(req string) string { +func makeHTTPSURL(req string) string { return testHTTPSServer.URL + req } @@ -156,16 +156,16 @@ func makeIndex(req string) *IndexInfo { return index } -func makeHttpsIndex(req string) *IndexInfo { +func makeHTTPSIndex(req string) *IndexInfo { index := &IndexInfo{ - Name: makeHttpsURL(req), + Name: makeHTTPSURL(req), } return index } func makePublicIndex() *IndexInfo { index := &IndexInfo{ - Name: INDEXSERVER, + Name: IndexServer, Secure: true, Official: true, } @@ -468,7 +468,7 @@ func TestPing(t *testing.T) { * WARNING: Don't push on the repos uncommented, it'll block the tests * func TestWait(t *testing.T) { - logrus.Println("Test HTTP server ready and waiting:", testHttpServer.URL) + logrus.Println("Test HTTP server ready and waiting:", testHTTPServer.URL) c := make(chan int) <-c } diff --git a/docs/registry_test.go b/docs/registry_test.go index 4d17a62c..88b08dff 100644 --- a/docs/registry_test.go +++ b/docs/registry_test.go @@ -63,7 +63,7 @@ func TestPingRegistryEndpoint(t *testing.T) { } testPing(makeIndex("/v1/"), true, "Expected standalone to be true (default)") - testPing(makeHttpsIndex("/v1/"), true, "Expected standalone to be true (default)") + testPing(makeHTTPSIndex("/v1/"), true, "Expected standalone to be true (default)") testPing(makePublicIndex(), false, "Expected standalone to be false for public index") } @@ -119,7 +119,7 @@ func TestEndpoint(t *testing.T) { } assertInsecureIndex(index) - index.Name = makeHttpsURL("/v1/") + index.Name = makeHTTPSURL("/v1/") endpoint = expandEndpoint(index) assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name) if endpoint.Version != APIVersion1 { @@ -127,7 +127,7 @@ func TestEndpoint(t *testing.T) { } assertSecureIndex(index) - index.Name = makeHttpsURL("") + index.Name = makeHTTPSURL("") endpoint = expandEndpoint(index) assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/") if endpoint.Version != APIVersion1 { @@ -135,7 +135,7 @@ func TestEndpoint(t *testing.T) { } assertSecureIndex(index) - httpsURL := makeHttpsURL("") + httpsURL := makeHTTPSURL("") index.Name = strings.SplitN(httpsURL, "://", 2)[1] endpoint = expandEndpoint(index) assertEqual(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/") @@ -332,7 +332,7 @@ func TestParseRepositoryInfo(t *testing.T) { expectedRepoInfos := map[string]RepositoryInfo{ "fooo/bar": { Index: &IndexInfo{ - Name: INDEXNAME, + Name: IndexName, Official: true, }, RemoteName: "fooo/bar", @@ -342,7 +342,7 @@ func TestParseRepositoryInfo(t *testing.T) { }, "library/ubuntu": { Index: &IndexInfo{ - Name: INDEXNAME, + Name: IndexName, Official: true, }, RemoteName: "library/ubuntu", @@ -352,7 +352,7 @@ func TestParseRepositoryInfo(t *testing.T) { }, "nonlibrary/ubuntu": { Index: &IndexInfo{ - Name: INDEXNAME, + Name: IndexName, Official: true, }, RemoteName: "nonlibrary/ubuntu", @@ -362,7 +362,7 @@ func TestParseRepositoryInfo(t *testing.T) { }, "ubuntu": { Index: &IndexInfo{ - Name: INDEXNAME, + Name: IndexName, Official: true, }, RemoteName: "library/ubuntu", @@ -372,7 +372,7 @@ func TestParseRepositoryInfo(t *testing.T) { }, "other/library": { Index: &IndexInfo{ - Name: INDEXNAME, + Name: IndexName, Official: true, }, RemoteName: "other/library", @@ -480,9 +480,9 @@ func TestParseRepositoryInfo(t *testing.T) { CanonicalName: "localhost/privatebase", Official: false, }, - INDEXNAME + "/public/moonbase": { + IndexName + "/public/moonbase": { Index: &IndexInfo{ - Name: INDEXNAME, + Name: IndexName, Official: true, }, RemoteName: "public/moonbase", @@ -490,9 +490,9 @@ func TestParseRepositoryInfo(t *testing.T) { CanonicalName: "docker.io/public/moonbase", Official: false, }, - "index." + INDEXNAME + "/public/moonbase": { + "index." + IndexName + "/public/moonbase": { Index: &IndexInfo{ - Name: INDEXNAME, + Name: IndexName, Official: true, }, RemoteName: "public/moonbase", @@ -502,7 +502,7 @@ func TestParseRepositoryInfo(t *testing.T) { }, "ubuntu-12.04-base": { Index: &IndexInfo{ - Name: INDEXNAME, + Name: IndexName, Official: true, }, RemoteName: "library/ubuntu-12.04-base", @@ -510,9 +510,9 @@ func TestParseRepositoryInfo(t *testing.T) { CanonicalName: "docker.io/library/ubuntu-12.04-base", Official: true, }, - INDEXNAME + "/ubuntu-12.04-base": { + IndexName + "/ubuntu-12.04-base": { Index: &IndexInfo{ - Name: INDEXNAME, + Name: IndexName, Official: true, }, RemoteName: "library/ubuntu-12.04-base", @@ -520,9 +520,9 @@ func TestParseRepositoryInfo(t *testing.T) { CanonicalName: "docker.io/library/ubuntu-12.04-base", Official: true, }, - "index." + INDEXNAME + "/ubuntu-12.04-base": { + "index." + IndexName + "/ubuntu-12.04-base": { Index: &IndexInfo{ - Name: INDEXNAME, + Name: IndexName, Official: true, }, RemoteName: "library/ubuntu-12.04-base", @@ -563,16 +563,16 @@ func TestNewIndexInfo(t *testing.T) { } config := NewServiceConfig(nil) - noMirrors := make([]string, 0) + noMirrors := []string{} expectedIndexInfos := map[string]*IndexInfo{ - INDEXNAME: { - Name: INDEXNAME, + IndexName: { + Name: IndexName, Official: true, Secure: true, Mirrors: noMirrors, }, - "index." + INDEXNAME: { - Name: INDEXNAME, + "index." + IndexName: { + Name: IndexName, Official: true, Secure: true, Mirrors: noMirrors, @@ -596,14 +596,14 @@ func TestNewIndexInfo(t *testing.T) { config = makeServiceConfig(publicMirrors, []string{"example.com"}) expectedIndexInfos = map[string]*IndexInfo{ - INDEXNAME: { - Name: INDEXNAME, + IndexName: { + Name: IndexName, Official: true, Secure: true, Mirrors: publicMirrors, }, - "index." + INDEXNAME: { - Name: INDEXNAME, + "index." + IndexName: { + Name: IndexName, Official: true, Secure: true, Mirrors: publicMirrors, @@ -814,7 +814,7 @@ func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) { reqFrom.Header.Add("Authorization", "super_secret") reqTo, _ := http.NewRequest("GET", urls[1], nil) - AddRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom}) + addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom}) if len(reqTo.Header) != 1 { t.Fatalf("Expected 1 headers, got %d", len(reqTo.Header)) @@ -838,7 +838,7 @@ func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) { reqFrom.Header.Add("Authorization", "super_secret") reqTo, _ := http.NewRequest("GET", urls[1], nil) - AddRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom}) + addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom}) if len(reqTo.Header) != 2 { t.Fatalf("Expected 2 headers, got %d", len(reqTo.Header)) @@ -860,7 +860,7 @@ func TestIsSecureIndex(t *testing.T) { insecureRegistries []string expected bool }{ - {INDEXNAME, nil, true}, + {IndexName, nil, true}, {"example.com", []string{}, true}, {"example.com", []string{"example.com"}, false}, {"localhost", []string{"localhost:5000"}, false}, diff --git a/docs/service.go b/docs/service.go index 1be448e4..274dfeb2 100644 --- a/docs/service.go +++ b/docs/service.go @@ -17,12 +17,14 @@ import ( "github.com/docker/docker/pkg/tlsconfig" ) +// Service is a registry service. It tracks configuration data such as a list +// of mirrors. type Service struct { Config *ServiceConfig } // NewService returns a new instance of Service ready to be -// installed no an engine. +// installed into an engine. func NewService(options *Options) *Service { return &Service{ Config: NewServiceConfig(options), @@ -36,7 +38,7 @@ func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) { addr := authConfig.ServerAddress if addr == "" { // Use the official registry address if not specified. - addr = INDEXSERVER + addr = IndexServer } index, err := s.ResolveIndex(addr) if err != nil { @@ -81,6 +83,7 @@ func (s *Service) ResolveIndex(name string) (*IndexInfo, error) { return s.Config.NewIndexInfo(name) } +// APIEndpoint represents a remote API endpoint type APIEndpoint struct { Mirror bool URL string @@ -92,12 +95,13 @@ type APIEndpoint struct { Versions []auth.APIVersion } +// ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint func (e APIEndpoint) ToV1Endpoint(metaHeaders http.Header) (*Endpoint, error) { return newEndpoint(e.URL, e.TLSConfig, metaHeaders) } -func (s *Service) TlsConfig(hostname string) (*tls.Config, error) { - // we construct a client tls config from server defaults +// TLSConfig constructs a client TLS configuration based on server defaults +func (s *Service) TLSConfig(hostname string) (*tls.Config, error) { // PreferredServerCipherSuites should have no effect tlsConfig := tlsconfig.ServerDefault @@ -115,7 +119,7 @@ func (s *Service) TlsConfig(hostname string) (*tls.Config, error) { return false } - hostDir := filepath.Join(CERTS_DIR, hostname) + hostDir := filepath.Join(CertsDir, hostname) logrus.Debugf("hostDir: %s", hostDir) fs, err := ioutil.ReadDir(hostDir) if err != nil && !os.IsNotExist(err) { @@ -163,20 +167,23 @@ func (s *Service) TlsConfig(hostname string) (*tls.Config, error) { } func (s *Service) tlsConfigForMirror(mirror string) (*tls.Config, error) { - mirrorUrl, err := url.Parse(mirror) + mirrorURL, err := url.Parse(mirror) if err != nil { return nil, err } - return s.TlsConfig(mirrorUrl.Host) + return s.TLSConfig(mirrorURL.Host) } +// LookupEndpoints creates an list of endpoints to try, in order of preference. +// It gives preference to v2 endpoints over v1, mirrors over the actual +// registry, and HTTPS over plain HTTP. func (s *Service) LookupEndpoints(repoName string) (endpoints []APIEndpoint, err error) { var cfg = tlsconfig.ServerDefault tlsConfig := &cfg - if strings.HasPrefix(repoName, DEFAULT_NAMESPACE+"/") { + if strings.HasPrefix(repoName, DefaultNamespace+"/") { // v2 mirrors for _, mirror := range s.Config.Mirrors { - mirrorTlsConfig, err := s.tlsConfigForMirror(mirror) + mirrorTLSConfig, err := s.tlsConfigForMirror(mirror) if err != nil { return nil, err } @@ -186,12 +193,12 @@ func (s *Service) LookupEndpoints(repoName string) (endpoints []APIEndpoint, err Version: APIVersion2, Mirror: true, TrimHostname: true, - TLSConfig: mirrorTlsConfig, + TLSConfig: mirrorTLSConfig, }) } // v2 registry endpoints = append(endpoints, APIEndpoint{ - URL: DEFAULT_V2_REGISTRY, + URL: DefaultV2Registry, Version: APIVersion2, Official: true, TrimHostname: true, @@ -199,7 +206,7 @@ func (s *Service) LookupEndpoints(repoName string) (endpoints []APIEndpoint, err }) // v1 registry endpoints = append(endpoints, APIEndpoint{ - URL: DEFAULT_V1_REGISTRY, + URL: DefaultV1Registry, Version: APIVersion1, Official: true, TrimHostname: true, @@ -214,7 +221,7 @@ func (s *Service) LookupEndpoints(repoName string) (endpoints []APIEndpoint, err } hostname := repoName[:slashIndex] - tlsConfig, err = s.TlsConfig(hostname) + tlsConfig, err = s.TLSConfig(hostname) if err != nil { return nil, err } @@ -232,7 +239,7 @@ func (s *Service) LookupEndpoints(repoName string) (endpoints []APIEndpoint, err Version: APIVersion2, TrimHostname: true, TLSConfig: tlsConfig, - VersionHeader: DEFAULT_REGISTRY_VERSION_HEADER, + VersionHeader: DefaultRegistryVersionHeader, Versions: v2Versions, }, { @@ -250,7 +257,7 @@ func (s *Service) LookupEndpoints(repoName string) (endpoints []APIEndpoint, err TrimHostname: true, // used to check if supposed to be secure via InsecureSkipVerify TLSConfig: tlsConfig, - VersionHeader: DEFAULT_REGISTRY_VERSION_HEADER, + VersionHeader: DefaultRegistryVersionHeader, Versions: v2Versions, }, APIEndpoint{ URL: "http://" + hostname, diff --git a/docs/session.go b/docs/session.go index cb982353..9bec7c1b 100644 --- a/docs/session.go +++ b/docs/session.go @@ -28,9 +28,12 @@ import ( ) var ( + // ErrRepoNotFound is returned if the repository didn't exist on the + // remote side ErrRepoNotFound = errors.New("Repository not found") ) +// A Session is used to communicate with a V1 registry type Session struct { indexEndpoint *Endpoint client *http.Client @@ -90,9 +93,11 @@ func cloneRequest(r *http.Request) *http.Request { return r2 } +// RoundTrip changes a HTTP request's headers to add the necessary +// authentication-related headers func (tr *authTransport) RoundTrip(orig *http.Request) (*http.Response, error) { // Authorization should not be set on 302 redirect for untrusted locations. - // This logic mirrors the behavior in AddRequiredHeadersToRedirectedRequests. + // This logic mirrors the behavior in addRequiredHeadersToRedirectedRequests. // As the authorization logic is currently implemented in RoundTrip, // a 302 redirect is detected by looking at the Referer header as go http package adds said header. // This is safe as Docker doesn't set Referer in other scenarios. @@ -154,6 +159,7 @@ func (tr *authTransport) CancelRequest(req *http.Request) { } } +// NewSession creates a new session // TODO(tiborvass): remove authConfig param once registry client v2 is vendored func NewSession(client *http.Client, authConfig *cliconfig.AuthConfig, endpoint *Endpoint) (r *Session, err error) { r = &Session{ @@ -167,7 +173,7 @@ func NewSession(client *http.Client, authConfig *cliconfig.AuthConfig, endpoint // If we're working with a standalone private registry over HTTPS, send Basic Auth headers // alongside all our requests. - if endpoint.VersionString(1) != INDEXSERVER && endpoint.URL.Scheme == "https" { + if endpoint.VersionString(1) != IndexServer && endpoint.URL.Scheme == "https" { info, err := endpoint.Ping() if err != nil { return nil, err @@ -196,8 +202,8 @@ func (r *Session) ID() string { return r.id } -// Retrieve the history of a given image from the Registry. -// Return a list of the parent's json (requested image included) +// GetRemoteHistory retrieves the history of a given image from the registry. +// It returns a list of the parent's JSON files (including the requested image). func (r *Session) GetRemoteHistory(imgID, registry string) ([]string, error) { res, err := r.client.Get(registry + "images/" + imgID + "/ancestry") if err != nil { @@ -220,7 +226,7 @@ func (r *Session) GetRemoteHistory(imgID, registry string) ([]string, error) { return history, nil } -// Check if an image exists in the Registry +// LookupRemoteImage checks if an image exists in the registry func (r *Session) LookupRemoteImage(imgID, registry string) error { res, err := r.client.Get(registry + "images/" + imgID + "/json") if err != nil { @@ -233,7 +239,7 @@ func (r *Session) LookupRemoteImage(imgID, registry string) error { return nil } -// Retrieve an image from the Registry. +// GetRemoteImageJSON retrieves an image's JSON metadata from the registry. func (r *Session) GetRemoteImageJSON(imgID, registry string) ([]byte, int, error) { res, err := r.client.Get(registry + "images/" + imgID + "/json") if err != nil { @@ -259,6 +265,7 @@ func (r *Session) GetRemoteImageJSON(imgID, registry string) ([]byte, int, error return jsonString, imageSize, nil } +// GetRemoteImageLayer retrieves an image layer from the registry func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io.ReadCloser, error) { var ( retries = 5 @@ -308,9 +315,13 @@ func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io return res.Body, nil } +// GetRemoteTag retrieves the tag named in the askedTag argument from the given +// repository. It queries each of the registries supplied in the registries +// argument, and returns data from the first one that answers the query +// successfully. func (r *Session) GetRemoteTag(registries []string, repository string, askedTag string) (string, error) { if strings.Count(repository, "/") == 0 { - // This will be removed once the Registry supports auto-resolution on + // This will be removed once the registry supports auto-resolution on // the "library" namespace repository = "library/" + repository } @@ -331,18 +342,22 @@ func (r *Session) GetRemoteTag(registries []string, repository string, askedTag continue } - var tagId string - if err := json.NewDecoder(res.Body).Decode(&tagId); err != nil { + var tagID string + if err := json.NewDecoder(res.Body).Decode(&tagID); err != nil { return "", err } - return tagId, nil + return tagID, nil } return "", fmt.Errorf("Could not reach any registry endpoint") } +// GetRemoteTags retrieves all tags from the given repository. It queries each +// of the registries supplied in the registries argument, and returns data from +// the first one that answers the query successfully. It returns a map with +// tag names as the keys and image IDs as the values. func (r *Session) GetRemoteTags(registries []string, repository string) (map[string]string, error) { if strings.Count(repository, "/") == 0 { - // This will be removed once the Registry supports auto-resolution on + // This will be removed once the registry supports auto-resolution on // the "library" namespace repository = "library/" + repository } @@ -379,7 +394,7 @@ func buildEndpointsList(headers []string, indexEp string) ([]string, error) { return nil, err } var urlScheme = parsedURL.Scheme - // The Registry's URL scheme has to match the Index' + // The registry's URL scheme has to match the Index' for _, ep := range headers { epList := strings.Split(ep, ",") for _, epListElement := range epList { @@ -391,6 +406,7 @@ func buildEndpointsList(headers []string, indexEp string) ([]string, error) { return endpoints, nil } +// GetRepositoryData returns lists of images and endpoints for the repository func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) { repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.VersionString(1), remote) @@ -457,8 +473,8 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) { }, nil } +// PushImageChecksumRegistry uploads checksums for an image func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string) error { - u := registry + "images/" + imgData.ID + "/checksum" logrus.Debugf("[registry] Calling PUT %s", u) @@ -494,7 +510,7 @@ func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string) e return nil } -// Push a local image to the registry +// PushImageJSONRegistry pushes JSON metadata for a local image to the registry func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string) error { u := registry + "images/" + imgData.ID + "/json" @@ -531,8 +547,8 @@ func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regist return nil } +// PushImageLayerRegistry sends the checksum of an image layer to the registry func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, jsonRaw []byte) (checksum string, checksumPayload string, err error) { - u := registry + "images/" + imgID + "/layer" logrus.Debugf("[registry] Calling PUT %s", u) @@ -576,7 +592,7 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry return tarsumLayer.Sum(jsonRaw), checksumPayload, nil } -// push a tag on the registry. +// PushRegistryTag pushes a tag on the registry. // Remote has the format '/ func (r *Session) PushRegistryTag(remote, revision, tag, registry string) error { // "jsonify" the string @@ -600,6 +616,7 @@ func (r *Session) PushRegistryTag(remote, revision, tag, registry string) error return nil } +// PushImageJSONIndex uploads an image list to the repository func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) { cleanImgList := []*ImgData{} if validate { @@ -705,6 +722,7 @@ func shouldRedirect(response *http.Response) bool { return response.StatusCode >= 300 && response.StatusCode < 400 } +// SearchRepositories performs a search against the remote repository func (r *Session) SearchRepositories(term string) (*SearchResults, error) { logrus.Debugf("Index server: %s", r.indexEndpoint) u := r.indexEndpoint.VersionString(1) + "search?q=" + url.QueryEscape(term) @@ -727,6 +745,7 @@ func (r *Session) SearchRepositories(term string) (*SearchResults, error) { return result, json.NewDecoder(res.Body).Decode(result) } +// GetAuthConfig returns the authentication settings for a session // TODO(tiborvass): remove this once registry client v2 is vendored func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig { password := "" diff --git a/docs/types.go b/docs/types.go index d02ae4fc..09b9d571 100644 --- a/docs/types.go +++ b/docs/types.go @@ -1,38 +1,66 @@ package registry +// SearchResult describes a search result returned from a registry type SearchResult struct { - StarCount int `json:"star_count"` - IsOfficial bool `json:"is_official"` - Name string `json:"name"` - IsTrusted bool `json:"is_trusted"` - IsAutomated bool `json:"is_automated"` + // StarCount indicates the number of stars this repository has + StarCount int `json:"star_count"` + // IsOfficial indicates whether the result is an official repository or not + IsOfficial bool `json:"is_official"` + // Name is the name of the repository + Name string `json:"name"` + // IsOfficial indicates whether the result is trusted + IsTrusted bool `json:"is_trusted"` + // IsAutomated indicates whether the result is automated + IsAutomated bool `json:"is_automated"` + // Description is a textual description of the repository Description string `json:"description"` } +// SearchResults lists a collection search results returned from a registry type SearchResults struct { - Query string `json:"query"` - NumResults int `json:"num_results"` - Results []SearchResult `json:"results"` + // Query contains the query string that generated the search results + Query string `json:"query"` + // NumResults indicates the number of results the query returned + NumResults int `json:"num_results"` + // Results is a slice containing the acutal results for the search + Results []SearchResult `json:"results"` } +// RepositoryData tracks the image list, list of endpoints, and list of tokens +// for a repository type RepositoryData struct { - ImgList map[string]*ImgData + // ImgList is a list of images in the repository + ImgList map[string]*ImgData + // Endpoints is a list of endpoints returned in X-Docker-Endpoints Endpoints []string - Tokens []string + // Tokens is currently unused (remove it?) + Tokens []string } +// ImgData is used to transfer image checksums to and from the registry type ImgData struct { + // ID is an opaque string that identifies the image ID string `json:"id"` Checksum string `json:"checksum,omitempty"` ChecksumPayload string `json:"-"` Tag string `json:",omitempty"` } -type RegistryInfo struct { - Version string `json:"version"` - Standalone bool `json:"standalone"` +// PingResult contains the information returned when pinging a registry. It +// indicates the registry's version and whether the registry claims to be a +// standalone registry. +type PingResult struct { + // Version is the registry version supplied by the registry in a HTTP + // header + Version string `json:"version"` + // Standalone is set to true if the registry indicates it is a + // standalone registry in the X-Docker-Registry-Standalone + // header + Standalone bool `json:"standalone"` } +// APIVersion is an integral representation of an API version (presently +// either 1 or 2) type APIVersion int func (av APIVersion) String() string { @@ -51,6 +79,8 @@ const ( APIVersion2 ) +// IndexInfo contains information about a registry +// // RepositoryInfo Examples: // { // "Index" : { @@ -64,7 +94,7 @@ const ( // "CanonicalName" : "docker.io/debian" // "Official" : true, // } - +// // { // "Index" : { // "Name" : "127.0.0.1:5000", @@ -78,16 +108,33 @@ const ( // "Official" : false, // } type IndexInfo struct { - Name string - Mirrors []string - Secure bool + // Name is the name of the registry, such as "docker.io" + Name string + // Mirrors is a list of mirrors, expressed as URIs + Mirrors []string + // Secure is set to false if the registry is part of the list of + // insecure registries. Insecure registries accept HTTP and/or accept + // HTTPS with certificates from unknown CAs. + Secure bool + // Official indicates whether this is an official registry Official bool } +// RepositoryInfo describes a repository type RepositoryInfo struct { - Index *IndexInfo - RemoteName string - LocalName string + // Index points to registry information + Index *IndexInfo + // RemoteName is the remote name of the repository, such as + // "library/ubuntu-12.04-base" + RemoteName string + // LocalName is the local name of the repository, such as + // "ubuntu-12.04-base" + LocalName string + // CanonicalName is the canonical name of the repository, such as + // "docker.io/library/ubuntu-12.04-base" CanonicalName string - Official bool + // Official indicates whether the repository is considered official. + // If the registry is official, and the normalized name does not + // contain a '/' (e.g. "foo"), then it is considered an official repo. + Official bool }