diff --git a/docs/Dockerfile b/docs/Dockerfile index fcc63422..a8a01d74 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,3 +1,7 @@ +--- +{} +--- + FROM docs/base:oss MAINTAINER Docker Docs diff --git a/docs/Makefile b/docs/Makefile index 585bc871..309f5846 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,3 +1,7 @@ +--- +{} +--- + .PHONY: all default docs docs-build docs-shell shell test # to allow `make DOCSDIR=docs docs-shell` (to create a bind mount in docs) diff --git a/docs/architecture.md b/docs/architecture.md index 39251760..91b704f8 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,8 +1,6 @@ - +--- +draft: true +--- # Architecture diff --git a/docs/auth.go b/docs/auth.go deleted file mode 100644 index 0b525718..00000000 --- a/docs/auth.go +++ /dev/null @@ -1,300 +0,0 @@ -package registry - -import ( - "fmt" - "io/ioutil" - "net/http" - "net/url" - "strings" - "time" - - "github.com/Sirupsen/logrus" - "github.com/docker/distribution/registry/client/auth" - "github.com/docker/distribution/registry/client/transport" - "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" -) - -const ( - // AuthClientID is used the ClientID used for the token server - AuthClientID = "docker" -) - -// loginV1 tries to register/login to the v1 registry server. -func loginV1(authConfig *types.AuthConfig, apiEndpoint APIEndpoint, userAgent string) (string, string, error) { - registryEndpoint, err := apiEndpoint.ToV1Endpoint(userAgent, nil) - if err != nil { - return "", "", err - } - - serverAddress := registryEndpoint.String() - - logrus.Debugf("attempting v1 login to registry endpoint %s", serverAddress) - - if serverAddress == "" { - return "", "", fmt.Errorf("Server Error: Server Address not set.") - } - - loginAgainstOfficialIndex := serverAddress == IndexServer - - req, err := http.NewRequest("GET", serverAddress+"users/", nil) - if err != nil { - return "", "", err - } - req.SetBasicAuth(authConfig.Username, authConfig.Password) - resp, err := registryEndpoint.client.Do(req) - if err != nil { - // fallback when request could not be completed - return "", "", fallbackError{ - err: err, - } - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "", "", err - } - if resp.StatusCode == http.StatusOK { - return "Login Succeeded", "", nil - } else if resp.StatusCode == http.StatusUnauthorized { - if loginAgainstOfficialIndex { - return "", "", fmt.Errorf("Wrong login/password, please try again. Haven't got a Docker ID? Create one at https://hub.docker.com") - } - return "", "", fmt.Errorf("Wrong login/password, please try again") - } else if resp.StatusCode == http.StatusForbidden { - if loginAgainstOfficialIndex { - return "", "", fmt.Errorf("Login: Account is not active. Please check your e-mail for a confirmation link.") - } - // *TODO: Use registry configuration to determine what this says, if anything? - return "", "", fmt.Errorf("Login: Account is not active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress) - } else if resp.StatusCode == http.StatusInternalServerError { // Issue #14326 - logrus.Errorf("%s returned status code %d. Response Body :\n%s", req.URL.String(), resp.StatusCode, body) - return "", "", fmt.Errorf("Internal Server Error") - } - return "", "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, - resp.StatusCode, resp.Header) -} - -type loginCredentialStore struct { - authConfig *types.AuthConfig -} - -func (lcs loginCredentialStore) Basic(*url.URL) (string, string) { - return lcs.authConfig.Username, lcs.authConfig.Password -} - -func (lcs loginCredentialStore) RefreshToken(*url.URL, string) string { - return lcs.authConfig.IdentityToken -} - -func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token string) { - lcs.authConfig.IdentityToken = token -} - -type staticCredentialStore struct { - auth *types.AuthConfig -} - -// NewStaticCredentialStore returns a credential store -// which always returns the same credential values. -func NewStaticCredentialStore(auth *types.AuthConfig) auth.CredentialStore { - return staticCredentialStore{ - auth: auth, - } -} - -func (scs staticCredentialStore) Basic(*url.URL) (string, string) { - if scs.auth == nil { - return "", "" - } - return scs.auth.Username, scs.auth.Password -} - -func (scs staticCredentialStore) RefreshToken(*url.URL, string) string { - if scs.auth == nil { - return "" - } - return scs.auth.IdentityToken -} - -func (scs staticCredentialStore) SetRefreshToken(*url.URL, string, string) { -} - -type fallbackError struct { - err error -} - -func (err fallbackError) Error() string { - return err.err.Error() -} - -// loginV2 tries to login to the v2 registry server. The given registry -// endpoint will be pinged to get authorization challenges. These challenges -// will be used to authenticate against the registry to validate credentials. -func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent string) (string, string, error) { - logrus.Debugf("attempting v2 login to registry endpoint %s", strings.TrimRight(endpoint.URL.String(), "/")+"/v2/") - - modifiers := DockerHeaders(userAgent, nil) - authTransport := transport.NewTransport(NewTransport(endpoint.TLSConfig), modifiers...) - - credentialAuthConfig := *authConfig - creds := loginCredentialStore{ - authConfig: &credentialAuthConfig, - } - - loginClient, foundV2, err := v2AuthHTTPClient(endpoint.URL, authTransport, modifiers, creds, nil) - if err != nil { - return "", "", err - } - - endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/" - req, err := http.NewRequest("GET", endpointStr, nil) - if err != nil { - if !foundV2 { - err = fallbackError{err: err} - } - return "", "", err - } - - resp, err := loginClient.Do(req) - if err != nil { - if !foundV2 { - err = fallbackError{err: err} - } - return "", "", err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - // TODO(dmcgowan): Attempt to further interpret result, status code and error code string - err := fmt.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode)) - if !foundV2 { - err = fallbackError{err: err} - } - return "", "", err - } - - return "Login Succeeded", credentialAuthConfig.IdentityToken, nil - -} - -func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, bool, error) { - challengeManager, foundV2, err := PingV2Registry(endpoint, authTransport) - if err != nil { - if !foundV2 { - err = fallbackError{err: err} - } - return nil, foundV2, err - } - - tokenHandlerOptions := auth.TokenHandlerOptions{ - Transport: authTransport, - Credentials: creds, - OfflineAccess: true, - ClientID: AuthClientID, - Scopes: scopes, - } - tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions) - basicHandler := auth.NewBasicHandler(creds) - modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)) - tr := transport.NewTransport(authTransport, modifiers...) - - return &http.Client{ - Transport: tr, - Timeout: 15 * time.Second, - }, foundV2, nil - -} - -// ResolveAuthConfig matches an auth configuration to a server address or a URL -func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig { - configKey := GetAuthConfigKey(index) - // First try the happy case - if c, found := authConfigs[configKey]; found || index.Official { - return c - } - - convertToHostname := func(url string) string { - stripped := url - if strings.HasPrefix(url, "http://") { - stripped = strings.Replace(url, "http://", "", 1) - } else if strings.HasPrefix(url, "https://") { - stripped = strings.Replace(url, "https://", "", 1) - } - - nameParts := strings.SplitN(stripped, "/", 2) - - return nameParts[0] - } - - // Maybe they have a legacy config file, we will iterate the keys converting - // them to the new format and testing - for registry, ac := range authConfigs { - if configKey == convertToHostname(registry) { - return ac - } - } - - // When all else fails, return an empty auth config - return types.AuthConfig{} -} - -// PingResponseError is used when the response from a ping -// was received but invalid. -type PingResponseError struct { - Err error -} - -func (err PingResponseError) Error() string { - return err.Error() -} - -// PingV2Registry attempts to ping a v2 registry and on success return a -// challenge manager for the supported authentication types and -// whether v2 was confirmed by the response. If a response is received but -// cannot be interpreted a PingResponseError will be returned. -func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (auth.ChallengeManager, bool, error) { - var ( - foundV2 = false - v2Version = auth.APIVersion{ - Type: "registry", - Version: "2.0", - } - ) - - pingClient := &http.Client{ - Transport: transport, - Timeout: 15 * time.Second, - } - endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/" - req, err := http.NewRequest("GET", endpointStr, nil) - if err != nil { - return nil, false, err - } - resp, err := pingClient.Do(req) - if err != nil { - return nil, false, err - } - defer resp.Body.Close() - - versions := auth.APIVersions(resp, DefaultRegistryVersionHeader) - for _, pingVersion := range versions { - if pingVersion == v2Version { - // The version header indicates we're definitely - // talking to a v2 registry. So don't allow future - // fallbacks to the v1 protocol. - - foundV2 = true - break - } - } - - challengeManager := auth.NewSimpleChallengeManager() - if err := challengeManager.AddResponse(resp); err != nil { - return nil, foundV2, PingResponseError{ - Err: err, - } - } - - return challengeManager, foundV2, nil -} diff --git a/docs/auth_test.go b/docs/auth_test.go deleted file mode 100644 index eedee44e..00000000 --- a/docs/auth_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package registry - -import ( - "testing" - - "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" -) - -func buildAuthConfigs() map[string]types.AuthConfig { - authConfigs := map[string]types.AuthConfig{} - - for _, registry := range []string{"testIndex", IndexServer} { - authConfigs[registry] = types.AuthConfig{ - Username: "docker-user", - Password: "docker-pass", - } - } - - return authConfigs -} - -func TestSameAuthDataPostSave(t *testing.T) { - authConfigs := buildAuthConfigs() - authConfig := authConfigs["testIndex"] - if authConfig.Username != "docker-user" { - t.Fail() - } - if authConfig.Password != "docker-pass" { - t.Fail() - } - if authConfig.Auth != "" { - t.Fail() - } -} - -func TestResolveAuthConfigIndexServer(t *testing.T) { - authConfigs := buildAuthConfigs() - indexConfig := authConfigs[IndexServer] - - officialIndex := ®istrytypes.IndexInfo{ - Official: true, - } - privateIndex := ®istrytypes.IndexInfo{ - Official: false, - } - - resolved := ResolveAuthConfig(authConfigs, officialIndex) - assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServer") - - resolved = ResolveAuthConfig(authConfigs, privateIndex) - assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServer") -} - -func TestResolveAuthConfigFullURL(t *testing.T) { - authConfigs := buildAuthConfigs() - - registryAuth := types.AuthConfig{ - Username: "foo-user", - Password: "foo-pass", - } - localAuth := types.AuthConfig{ - Username: "bar-user", - Password: "bar-pass", - } - officialAuth := types.AuthConfig{ - Username: "baz-user", - Password: "baz-pass", - } - authConfigs[IndexServer] = officialAuth - - expectedAuths := map[string]types.AuthConfig{ - "registry.example.com": registryAuth, - "localhost:8000": localAuth, - "registry.com": localAuth, - } - - validRegistries := map[string][]string{ - "registry.example.com": { - "https://registry.example.com/v1/", - "http://registry.example.com/v1/", - "registry.example.com", - "registry.example.com/v1/", - }, - "localhost:8000": { - "https://localhost:8000/v1/", - "http://localhost:8000/v1/", - "localhost:8000", - "localhost:8000/v1/", - }, - "registry.com": { - "https://registry.com/v1/", - "http://registry.com/v1/", - "registry.com", - "registry.com/v1/", - }, - } - - for configKey, registries := range validRegistries { - configured, ok := expectedAuths[configKey] - if !ok { - t.Fail() - } - index := ®istrytypes.IndexInfo{ - Name: configKey, - } - for _, registry := range registries { - authConfigs[registry] = configured - resolved := ResolveAuthConfig(authConfigs, index) - if resolved.Username != configured.Username || resolved.Password != configured.Password { - t.Errorf("%s -> %v != %v\n", registry, resolved, configured) - } - delete(authConfigs, registry) - resolved = ResolveAuthConfig(authConfigs, index) - if resolved.Username == configured.Username || resolved.Password == configured.Password { - t.Errorf("%s -> %v == %v\n", registry, resolved, configured) - } - } - } -} diff --git a/docs/compatibility.md b/docs/compatibility.md index cba7e378..6d18ffc3 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -1,13 +1,13 @@ - +--- +description: describes get by digest pitfall +keywords: +- registry, manifest, images, tags, repository, distribution, digest +menu: + main: + parent: smn_registry_ref + weight: 9 +title: Compatibility +--- # Registry Compatibility diff --git a/docs/config.go b/docs/config.go deleted file mode 100644 index e349660e..00000000 --- a/docs/config.go +++ /dev/null @@ -1,274 +0,0 @@ -package registry - -import ( - "errors" - "fmt" - "net" - "net/url" - "strings" - - "github.com/docker/docker/opts" - flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/reference" - registrytypes "github.com/docker/engine-api/types/registry" -) - -// ServiceOptions holds command line options. -type ServiceOptions struct { - Mirrors []string `json:"registry-mirrors,omitempty"` - InsecureRegistries []string `json:"insecure-registries,omitempty"` - - // V2Only controls access to legacy registries. If it is set to true via the - // command line flag the daemon will not attempt to contact v1 legacy registries - V2Only bool `json:"disable-legacy-registry,omitempty"` -} - -// serviceConfig holds daemon configuration for the registry service. -type serviceConfig struct { - registrytypes.ServiceConfig - V2Only bool -} - -var ( - // DefaultNamespace is the default namespace - DefaultNamespace = "docker.io" - // DefaultRegistryVersionHeader is the name of the default HTTP header - // that carries Registry version info - DefaultRegistryVersionHeader = "Docker-Distribution-Api-Version" - - // IndexServer is the v1 registry server used for user auth + account creation - IndexServer = DefaultV1Registry.String() + "/v1/" - // IndexName is the name of the index - IndexName = "docker.io" - - // NotaryServer is the endpoint serving the Notary trust server - NotaryServer = "https://notary.docker.io" - - // DefaultV1Registry is the URI of the default v1 registry - DefaultV1Registry = &url.URL{ - Scheme: "https", - Host: "index.docker.io", - } - - // DefaultV2Registry is the URI of the default v2 registry - DefaultV2Registry = &url.URL{ - Scheme: "https", - Host: "registry-1.docker.io", - } -) - -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(ServiceOptions{}) -) - -// for mocking in unit tests -var lookupIP = net.LookupIP - -// InstallCliFlags adds command-line options to the top-level flag parser for -// the current process. -func (options *ServiceOptions) InstallCliFlags(cmd *flag.FlagSet, usageFn func(string) string) { - mirrors := opts.NewNamedListOptsRef("registry-mirrors", &options.Mirrors, ValidateMirror) - cmd.Var(mirrors, []string{"-registry-mirror"}, usageFn("Preferred Docker registry mirror")) - - insecureRegistries := opts.NewNamedListOptsRef("insecure-registries", &options.InsecureRegistries, ValidateIndexName) - cmd.Var(insecureRegistries, []string{"-insecure-registry"}, usageFn("Enable insecure registry communication")) - - cmd.BoolVar(&options.V2Only, []string{"-disable-legacy-registry"}, false, usageFn("Disable contacting legacy registries")) -} - -// newServiceConfig returns a new instance of ServiceConfig -func newServiceConfig(options ServiceOptions) *serviceConfig { - // Localhost is by default considered as an insecure registry - // This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker). - // - // TODO: should we deprecate this once it is easier for people to set up a TLS registry or change - // daemon flags on boot2docker? - options.InsecureRegistries = append(options.InsecureRegistries, "127.0.0.0/8") - - config := &serviceConfig{ - ServiceConfig: registrytypes.ServiceConfig{ - InsecureRegistryCIDRs: make([]*registrytypes.NetIPNet, 0), - IndexConfigs: make(map[string]*registrytypes.IndexInfo, 0), - // Hack: Bypass setting the mirrors to IndexConfigs since they are going away - // and Mirrors are only for the official registry anyways. - Mirrors: options.Mirrors, - }, - V2Only: options.V2Only, - } - // Split --insecure-registry into CIDR and registry-specific settings. - for _, r := range options.InsecureRegistries { - // Check if CIDR was passed to --insecure-registry - _, ipnet, err := net.ParseCIDR(r) - if err == nil { - // Valid CIDR. - config.InsecureRegistryCIDRs = append(config.InsecureRegistryCIDRs, (*registrytypes.NetIPNet)(ipnet)) - } else { - // Assume `host:port` if not CIDR. - config.IndexConfigs[r] = ®istrytypes.IndexInfo{ - Name: r, - Mirrors: make([]string, 0), - Secure: false, - Official: false, - } - } - } - - // Configure public registry. - config.IndexConfigs[IndexName] = ®istrytypes.IndexInfo{ - Name: IndexName, - Mirrors: config.Mirrors, - Secure: true, - Official: true, - } - - return config -} - -// isSecureIndex returns false if the provided indexName is part of the list of insecure registries -// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs. -// -// The list of insecure registries can contain an element with CIDR notation to specify a whole subnet. -// If the subnet contains one of the IPs of the registry specified by indexName, the latter is considered -// insecure. -// -// indexName should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name -// or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained -// in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element -// of insecureRegistries. -func isSecureIndex(config *serviceConfig, indexName string) bool { - // Check for configured index, first. This is needed in case isSecureIndex - // is called from anything besides newIndexInfo, in order to honor per-index configurations. - if index, ok := config.IndexConfigs[indexName]; ok { - return index.Secure - } - - host, _, err := net.SplitHostPort(indexName) - if err != nil { - // assume indexName is of the form `host` without the port and go on. - host = indexName - } - - addrs, err := lookupIP(host) - if err != nil { - ip := net.ParseIP(host) - if ip != nil { - addrs = []net.IP{ip} - } - - // if ip == nil, then `host` is neither an IP nor it could be looked up, - // either because the index is unreachable, or because the index is behind an HTTP proxy. - // So, len(addrs) == 0 and we're not aborting. - } - - // Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined. - for _, addr := range addrs { - for _, ipnet := range config.InsecureRegistryCIDRs { - // check if the addr falls in the subnet - if (*net.IPNet)(ipnet).Contains(addr) { - return false - } - } - } - - return true -} - -// ValidateMirror validates an HTTP(S) registry mirror -func ValidateMirror(val string) (string, error) { - uri, err := url.Parse(val) - if err != nil { - return "", fmt.Errorf("%s is not a valid URI", val) - } - - if uri.Scheme != "http" && uri.Scheme != "https" { - return "", fmt.Errorf("Unsupported scheme %s", uri.Scheme) - } - - if uri.Path != "" || uri.RawQuery != "" || uri.Fragment != "" { - return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI") - } - - return fmt.Sprintf("%s://%s/", uri.Scheme, uri.Host), nil -} - -// ValidateIndexName validates an index name. -func ValidateIndexName(val string) (string, error) { - if val == reference.LegacyDefaultHostname { - val = reference.DefaultHostname - } - if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") { - return "", fmt.Errorf("Invalid index name (%s). Cannot begin or end with a hyphen.", val) - } - return val, nil -} - -func validateNoScheme(reposName string) error { - if strings.Contains(reposName, "://") { - // It cannot contain a scheme! - return ErrInvalidRepositoryName - } - return nil -} - -// newIndexInfo returns IndexInfo configuration from indexName -func newIndexInfo(config *serviceConfig, indexName string) (*registrytypes.IndexInfo, error) { - var err error - indexName, err = ValidateIndexName(indexName) - if err != nil { - return nil, err - } - - // Return any configured index info, first. - if index, ok := config.IndexConfigs[indexName]; ok { - return index, nil - } - - // Construct a non-configured index info. - index := ®istrytypes.IndexInfo{ - Name: indexName, - Mirrors: make([]string, 0), - Official: false, - } - index.Secure = isSecureIndex(config, indexName) - return index, nil -} - -// GetAuthConfigKey special-cases using the full index address of the official -// index as the AuthConfig key, and uses the (host)name[:port] for private indexes. -func GetAuthConfigKey(index *registrytypes.IndexInfo) string { - if index.Official { - return IndexServer - } - return index.Name -} - -// newRepositoryInfo validates and breaks down a repository name into a RepositoryInfo -func newRepositoryInfo(config *serviceConfig, name reference.Named) (*RepositoryInfo, error) { - index, err := newIndexInfo(config, name.Hostname()) - if err != nil { - return nil, err - } - official := !strings.ContainsRune(name.Name(), '/') - return &RepositoryInfo{name, index, official}, nil -} - -// ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but -// lacks registry configuration. -func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) { - return newRepositoryInfo(emptyServiceConfig, reposName) -} - -// ParseSearchIndexInfo will use repository name to get back an indexInfo. -func ParseSearchIndexInfo(reposName string) (*registrytypes.IndexInfo, error) { - indexName, _ := splitReposSearchTerm(reposName) - - indexInfo, err := newIndexInfo(emptyServiceConfig, indexName) - if err != nil { - return nil, err - } - return indexInfo, nil -} diff --git a/docs/config_test.go b/docs/config_test.go deleted file mode 100644 index 25578a7f..00000000 --- a/docs/config_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package registry - -import ( - "testing" -) - -func TestValidateMirror(t *testing.T) { - valid := []string{ - "http://mirror-1.com", - "https://mirror-1.com", - "http://localhost", - "https://localhost", - "http://localhost:5000", - "https://localhost:5000", - "http://127.0.0.1", - "https://127.0.0.1", - "http://127.0.0.1:5000", - "https://127.0.0.1:5000", - } - - invalid := []string{ - "!invalid!://%as%", - "ftp://mirror-1.com", - "http://mirror-1.com/", - "http://mirror-1.com/?q=foo", - "http://mirror-1.com/v1/", - "http://mirror-1.com/v1/?q=foo", - "http://mirror-1.com/v1/?q=foo#frag", - "http://mirror-1.com?q=foo", - "https://mirror-1.com#frag", - "https://mirror-1.com/", - "https://mirror-1.com/#frag", - "https://mirror-1.com/v1/", - "https://mirror-1.com/v1/#", - "https://mirror-1.com?q", - } - - for _, address := range valid { - if ret, err := ValidateMirror(address); err != nil || ret == "" { - t.Errorf("ValidateMirror(`"+address+"`) got %s %s", ret, err) - } - } - - for _, address := range invalid { - if ret, err := ValidateMirror(address); err == nil || ret != "" { - t.Errorf("ValidateMirror(`"+address+"`) got %s %s", ret, err) - } - } -} diff --git a/docs/config_unix.go b/docs/config_unix.go deleted file mode 100644 index b81d2493..00000000 --- a/docs/config_unix.go +++ /dev/null @@ -1,16 +0,0 @@ -// +build !windows - -package registry - -var ( - // CertsDir is the directory where certificates are stored - CertsDir = "/etc/docker/certs.d" -) - -// cleanPath is used to ensure that a directory name is valid on the target -// platform. It will be passed in something *similar* to a URL such as -// https:/index.docker.io/v1. Not all platforms support directory names -// which contain those characters (such as : on Windows) -func cleanPath(s string) string { - return s -} diff --git a/docs/config_windows.go b/docs/config_windows.go deleted file mode 100644 index 82bc4afe..00000000 --- a/docs/config_windows.go +++ /dev/null @@ -1,18 +0,0 @@ -package registry - -import ( - "os" - "path/filepath" - "strings" -) - -// CertsDir is the directory where certificates are stored -var CertsDir = os.Getenv("programdata") + `\docker\certs.d` - -// cleanPath is used to ensure that a directory name is valid on the target -// platform. It will be passed in something *similar* to a URL such as -// https:\index.docker.io\v1. Not all platforms support directory names -// which contain those characters (such as : on Windows) -func cleanPath(s string) string { - return filepath.FromSlash(strings.Replace(s, ":", "", -1)) -} diff --git a/docs/configuration.md b/docs/configuration.md index 1ef680f5..b900e0fb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,13 +1,13 @@ - +--- +description: Explains how to configure a registry +keywords: +- registry, on-prem, images, tags, repository, distribution, configuration +menu: + main: + parent: smn_registry + weight: 4 +title: Configuring a registry +--- # Registry Configuration Reference diff --git a/docs/deploying.md b/docs/deploying.md index 2e8ce69e..1ac25093 100644 --- a/docs/deploying.md +++ b/docs/deploying.md @@ -1,13 +1,13 @@ - +--- +description: Explains how to deploy a registry +keywords: +- registry, on-prem, images, tags, repository, distribution, deployment +menu: + main: + parent: smn_registry + weight: 3 +title: Deploying a registry server +--- # Deploying a registry server diff --git a/docs/deprecated.md b/docs/deprecated.md index 73bde497..d30ff425 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -1,13 +1,13 @@ - +--- +description: describes deprecated functionality +keywords: +- registry, manifest, images, signatures, repository, distribution, digest +menu: + main: + parent: smn_registry_ref + weight: 8 +title: Deprecated Features +--- # Docker Registry Deprecation diff --git a/docs/endpoint_test.go b/docs/endpoint_test.go deleted file mode 100644 index 8451d3f6..00000000 --- a/docs/endpoint_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package registry - -import ( - "net/http" - "net/http/httptest" - "net/url" - "testing" -) - -func TestEndpointParse(t *testing.T) { - testData := []struct { - str string - expected string - }{ - {IndexServer, IndexServer}, - {"http://0.0.0.0:5000/v1/", "http://0.0.0.0:5000/v1/"}, - {"http://0.0.0.0:5000", "http://0.0.0.0:5000/v1/"}, - {"0.0.0.0:5000", "https://0.0.0.0:5000/v1/"}, - {"http://0.0.0.0:5000/nonversion/", "http://0.0.0.0:5000/nonversion/v1/"}, - {"http://0.0.0.0:5000/v0/", "http://0.0.0.0:5000/v0/v1/"}, - } - for _, td := range testData { - e, err := newV1EndpointFromStr(td.str, nil, "", nil) - if err != nil { - t.Errorf("%q: %s", td.str, err) - } - if e == nil { - t.Logf("something's fishy, endpoint for %q is nil", td.str) - continue - } - if e.String() != td.expected { - t.Errorf("expected %q, got %q", td.expected, e.String()) - } - } -} - -func TestEndpointParseInvalid(t *testing.T) { - testData := []string{ - "http://0.0.0.0:5000/v2/", - } - for _, td := range testData { - e, err := newV1EndpointFromStr(td, nil, "", nil) - if err == nil { - t.Errorf("expected error parsing %q: parsed as %q", td, e) - } - } -} - -// Ensure that a registry endpoint that responds with a 401 only is determined -// to be a valid v1 registry endpoint -func TestValidateEndpoint(t *testing.T) { - requireBasicAuthHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("WWW-Authenticate", `Basic realm="localhost"`) - w.WriteHeader(http.StatusUnauthorized) - }) - - // Make a test server which should validate as a v1 server. - testServer := httptest.NewServer(requireBasicAuthHandler) - defer testServer.Close() - - testServerURL, err := url.Parse(testServer.URL) - if err != nil { - t.Fatal(err) - } - - testEndpoint := V1Endpoint{ - URL: testServerURL, - client: HTTPClient(NewTransport(nil)), - } - - if err = validateEndpoint(&testEndpoint); err != nil { - t.Fatal(err) - } - - if testEndpoint.URL.Scheme != "http" { - t.Fatalf("expecting to validate endpoint as http, got url %s", testEndpoint.String()) - } -} diff --git a/docs/endpoint_v1.go b/docs/endpoint_v1.go deleted file mode 100644 index fd81972c..00000000 --- a/docs/endpoint_v1.go +++ /dev/null @@ -1,198 +0,0 @@ -package registry - -import ( - "crypto/tls" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "strings" - - "github.com/Sirupsen/logrus" - "github.com/docker/distribution/registry/client/transport" - registrytypes "github.com/docker/engine-api/types/registry" -) - -// V1Endpoint stores basic information about a V1 registry endpoint. -type V1Endpoint struct { - client *http.Client - URL *url.URL - IsSecure bool -} - -// NewV1Endpoint parses the given address to return a registry endpoint. -func NewV1Endpoint(index *registrytypes.IndexInfo, userAgent string, metaHeaders http.Header) (*V1Endpoint, error) { - tlsConfig, err := newTLSConfig(index.Name, index.Secure) - if err != nil { - return nil, err - } - - endpoint, err := newV1EndpointFromStr(GetAuthConfigKey(index), tlsConfig, userAgent, metaHeaders) - if err != nil { - return nil, err - } - - if err := validateEndpoint(endpoint); err != nil { - return nil, err - } - - return endpoint, nil -} - -func validateEndpoint(endpoint *V1Endpoint) error { - logrus.Debugf("pinging registry endpoint %s", endpoint) - - // Try HTTPS ping to registry - endpoint.URL.Scheme = "https" - if _, err := endpoint.Ping(); err != nil { - if endpoint.IsSecure { - // If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry` - // in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP. - return fmt.Errorf("invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host) - } - - // If registry is insecure and HTTPS failed, fallback to HTTP. - logrus.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err) - endpoint.URL.Scheme = "http" - - var err2 error - if _, err2 = endpoint.Ping(); err2 == nil { - return nil - } - - return fmt.Errorf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2) - } - - return nil -} - -func newV1Endpoint(address url.URL, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) (*V1Endpoint, error) { - endpoint := &V1Endpoint{ - IsSecure: (tlsConfig == nil || !tlsConfig.InsecureSkipVerify), - URL: new(url.URL), - } - - *endpoint.URL = address - - // TODO(tiborvass): make sure a ConnectTimeout transport is used - tr := NewTransport(tlsConfig) - endpoint.client = HTTPClient(transport.NewTransport(tr, DockerHeaders(userAgent, metaHeaders)...)) - return endpoint, nil -} - -// trimV1Address trims the version off the address and returns the -// trimmed address or an error if there is a non-V1 version. -func trimV1Address(address string) (string, error) { - var ( - chunks []string - apiVersionStr string - ) - - if strings.HasSuffix(address, "/") { - address = address[:len(address)-1] - } - - chunks = strings.Split(address, "/") - apiVersionStr = chunks[len(chunks)-1] - if apiVersionStr == "v1" { - return strings.Join(chunks[:len(chunks)-1], "/"), nil - } - - for k, v := range apiVersions { - if k != APIVersion1 && apiVersionStr == v { - return "", fmt.Errorf("unsupported V1 version path %s", apiVersionStr) - } - } - - return address, nil -} - -func newV1EndpointFromStr(address string, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) (*V1Endpoint, error) { - if !strings.HasPrefix(address, "http://") && !strings.HasPrefix(address, "https://") { - address = "https://" + address - } - - address, err := trimV1Address(address) - if err != nil { - return nil, err - } - - uri, err := url.Parse(address) - if err != nil { - return nil, err - } - - endpoint, err := newV1Endpoint(*uri, tlsConfig, userAgent, metaHeaders) - if err != nil { - return nil, err - } - - return endpoint, nil -} - -// Get the formatted URL for the root of this registry Endpoint -func (e *V1Endpoint) String() string { - return e.URL.String() + "/v1/" -} - -// Path returns a formatted string for the URL -// of this endpoint with the given path appended. -func (e *V1Endpoint) Path(path string) string { - return e.URL.String() + "/v1/" + path -} - -// Ping returns a PingResult which indicates whether the registry is standalone or not. -func (e *V1Endpoint) Ping() (PingResult, error) { - logrus.Debugf("attempting v1 ping for registry endpoint %s", e) - - 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 PingResult{Standalone: false}, nil - } - - req, err := http.NewRequest("GET", e.Path("_ping"), nil) - if err != nil { - return PingResult{Standalone: false}, err - } - - resp, err := e.client.Do(req) - if err != nil { - return PingResult{Standalone: false}, err - } - - defer resp.Body.Close() - - jsonString, err := ioutil.ReadAll(resp.Body) - if err != nil { - 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 := PingResult{ - Standalone: true, - } - if err := json.Unmarshal(jsonString, &info); err != nil { - 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("PingResult.Version: %q", info.Version) - - standalone := resp.Header.Get("X-Docker-Registry-Standalone") - logrus.Debugf("Registry standalone header: '%s'", standalone) - // Accepted values are "true" (case-insensitive) and "1". - if strings.EqualFold(standalone, "true") || standalone == "1" { - info.Standalone = true - } else if len(standalone) > 0 { - // there is a header set, and it is not "true" or "1", so assume fails - info.Standalone = false - } - logrus.Debugf("PingResult.Standalone: %t", info.Standalone) - return info, nil -} diff --git a/docs/garbage-collection.md b/docs/garbage-collection.md index 2d03e787..a5b1a655 100644 --- a/docs/garbage-collection.md +++ b/docs/garbage-collection.md @@ -1,13 +1,13 @@ - +--- +description: High level discussion of garbage collection +keywords: +- registry, garbage, images, tags, repository, distribution +menu: + main: + parent: smn_registry_ref + weight: 4 +title: Garbage Collection +--- # Garbage Collection diff --git a/docs/glossary.md b/docs/glossary.md index 8159b520..00be147f 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -1,8 +1,6 @@ - +--- +draft: true +--- # Glossary diff --git a/docs/help.md b/docs/help.md index 77ec378f..8728924c 100644 --- a/docs/help.md +++ b/docs/help.md @@ -1,13 +1,13 @@ - +--- +description: Getting help with the Registry +keywords: +- registry, on-prem, images, tags, repository, distribution, help, 101, TL;DR +menu: + main: + parent: smn_registry + weight: 9 +title: Getting help +--- # Getting help diff --git a/docs/index.md b/docs/index.md index 21ec7a9a..3e7bde8e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,14 +1,15 @@ - +--- +aliases: +- /registry/overview/ +description: High-level overview of the Registry +keywords: +- registry, on-prem, images, tags, repository, distribution +menu: + main: + parent: smn_registry + weight: 1 +title: Registry Overview +--- # Docker Registry diff --git a/docs/insecure.md b/docs/insecure.md index 38b3a355..0bb21458 100644 --- a/docs/insecure.md +++ b/docs/insecure.md @@ -1,13 +1,13 @@ - +--- +description: Deploying a Registry in an insecure fashion +keywords: +- registry, on-prem, images, tags, repository, distribution, insecure +menu: + main: + parent: smn_registry_ref + weight: 5 +title: Testing an insecure registry +--- # Insecure Registry diff --git a/docs/introduction.md b/docs/introduction.md index eceb5ffc..f95be819 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -1,13 +1,13 @@ - +--- +description: Explains what the Registry is, basic use cases and requirements +keywords: +- registry, on-prem, images, tags, repository, distribution, use cases, requirements +menu: + main: + parent: smn_registry + weight: 2 +title: Understanding the Registry +--- # Understanding the Registry diff --git a/docs/menu.md b/docs/menu.md index 7e24a690..def2cd5c 100644 --- a/docs/menu.md +++ b/docs/menu.md @@ -1,14 +1,14 @@ - +--- +description: High-level overview of the Registry +keywords: +- registry, on-prem, images, tags, repository, distribution +menu: + main: + identifier: smn_registry + parent: mn_components +title: Docker Registry +type: menu +--- # Overview of Docker Registry Documentation diff --git a/docs/migration.md b/docs/migration.md index da0aba91..167c5a68 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -1,8 +1,6 @@ - +--- +draft: true +--- # Migrating a 1.0 registry to 2.0 diff --git a/docs/notifications.md b/docs/notifications.md index c511eb59..db858bc0 100644 --- a/docs/notifications.md +++ b/docs/notifications.md @@ -1,13 +1,13 @@ - +--- +description: Explains how to work with registry notifications +keywords: +- registry, on-prem, images, tags, repository, distribution, notifications, advanced +menu: + main: + parent: smn_registry + weight: 5 +title: Working with notifications +--- # Notifications diff --git a/docs/recipes/apache.md b/docs/recipes/apache.md index ac24113b..1b503584 100644 --- a/docs/recipes/apache.md +++ b/docs/recipes/apache.md @@ -1,12 +1,13 @@ - +--- +description: Restricting access to your registry using an apache proxy +keywords: +- registry, on-prem, images, tags, repository, distribution, authentication, proxy, + apache, httpd, TLS, recipe, advanced +menu: + main: + parent: smn_recipes +title: Authenticating proxy with apache +--- # Authenticating proxy with apache diff --git a/docs/recipes/index.md b/docs/recipes/index.md index b4dd6367..49537079 100644 --- a/docs/recipes/index.md +++ b/docs/recipes/index.md @@ -1,13 +1,13 @@ - +--- +description: Fun stuff to do with your registry +keywords: +- registry, on-prem, images, tags, repository, distribution, recipes, advanced +menu: + main: + parent: smn_recipes + weight: -10 +title: Recipes Overview +--- # Recipes diff --git a/docs/recipes/menu.md b/docs/recipes/menu.md index b79c1b30..1755009e 100644 --- a/docs/recipes/menu.md +++ b/docs/recipes/menu.md @@ -1,15 +1,15 @@ - +--- +description: Registry Recipes +keywords: +- registry, on-prem, images, tags, repository, distribution +menu: + main: + identifier: smn_recipes + parent: smn_registry + weight: 6 +title: Recipes +type: menu +--- # Recipes diff --git a/docs/recipes/mirror.md b/docs/recipes/mirror.md index 241e41bd..75ea964f 100644 --- a/docs/recipes/mirror.md +++ b/docs/recipes/mirror.md @@ -1,12 +1,13 @@ - +--- +description: Setting-up a local mirror for Docker Hub images +keywords: +- registry, on-prem, images, tags, repository, distribution, mirror, Hub, recipe, + advanced +menu: + main: + parent: smn_recipes +title: Mirroring Docker Hub +--- # Registry as a pull through cache diff --git a/docs/recipes/nginx.md b/docs/recipes/nginx.md index f4a67679..94fca625 100644 --- a/docs/recipes/nginx.md +++ b/docs/recipes/nginx.md @@ -1,12 +1,13 @@ - +--- +description: Restricting access to your registry using a nginx proxy +keywords: +- registry, on-prem, images, tags, repository, distribution, nginx, proxy, authentication, + TLS, recipe, advanced +menu: + main: + parent: smn_recipes +title: Authenticating proxy with nginx +--- # Authenticating proxy with nginx diff --git a/docs/recipes/osx-setup-guide.md b/docs/recipes/osx-setup-guide.md index d47d31c1..0d0c443d 100644 --- a/docs/recipes/osx-setup-guide.md +++ b/docs/recipes/osx-setup-guide.md @@ -1,12 +1,12 @@ - +--- +description: Explains how to run a registry on OS X +keywords: +- registry, on-prem, images, tags, repository, distribution, OS X, recipe, advanced +menu: + main: + parent: smn_recipes +title: Running on OS X +--- # OS X Setup Guide diff --git a/docs/recipes/osx/com.docker.registry.plist b/docs/recipes/osx/com.docker.registry.plist index 0982349f..c367bb98 100644 --- a/docs/recipes/osx/com.docker.registry.plist +++ b/docs/recipes/osx/com.docker.registry.plist @@ -1,3 +1,7 @@ +--- +{} +--- + diff --git a/docs/recipes/osx/config.yml b/docs/recipes/osx/config.yml index 63b8f713..b05bacb3 100644 --- a/docs/recipes/osx/config.yml +++ b/docs/recipes/osx/config.yml @@ -1,3 +1,7 @@ +--- +{} +--- + version: 0.1 log: level: info diff --git a/docs/reference.go b/docs/reference.go deleted file mode 100644 index e15f83ee..00000000 --- a/docs/reference.go +++ /dev/null @@ -1,68 +0,0 @@ -package registry - -import ( - "strings" - - "github.com/docker/distribution/digest" -) - -// Reference represents a tag or digest within a repository -type Reference interface { - // HasDigest returns whether the reference has a verifiable - // content addressable reference which may be considered secure. - HasDigest() bool - - // ImageName returns an image name for the given repository - ImageName(string) string - - // Returns a string representation of the reference - String() string -} - -type tagReference struct { - tag string -} - -func (tr tagReference) HasDigest() bool { - return false -} - -func (tr tagReference) ImageName(repo string) string { - return repo + ":" + tr.tag -} - -func (tr tagReference) String() string { - return tr.tag -} - -type digestReference struct { - digest digest.Digest -} - -func (dr digestReference) HasDigest() bool { - return true -} - -func (dr digestReference) ImageName(repo string) string { - return repo + "@" + dr.String() -} - -func (dr digestReference) String() string { - return dr.digest.String() -} - -// ParseReference parses a reference into either a digest or tag reference -func ParseReference(ref string) Reference { - if strings.Contains(ref, ":") { - dgst, err := digest.ParseDigest(ref) - if err == nil { - return digestReference{digest: dgst} - } - } - return tagReference{tag: ref} -} - -// DigestReference creates a digest reference using a digest -func DigestReference(dgst digest.Digest) Reference { - return digestReference{digest: dgst} -} diff --git a/docs/registry.go b/docs/registry.go deleted file mode 100644 index 973bff9f..00000000 --- a/docs/registry.go +++ /dev/null @@ -1,190 +0,0 @@ -// Package registry contains client primitives to interact with a remote Docker registry. -package registry - -import ( - "crypto/tls" - "crypto/x509" - "errors" - "fmt" - "io/ioutil" - "net" - "net/http" - "os" - "path/filepath" - "strings" - "time" - - "github.com/Sirupsen/logrus" - "github.com/docker/distribution/registry/client/transport" - "github.com/docker/go-connections/sockets" - "github.com/docker/go-connections/tlsconfig" -) - -var ( - // ErrAlreadyExists is an error returned if an image being pushed - // already exists on the remote side - ErrAlreadyExists = errors.New("Image already exists") -) - -func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) { - // PreferredServerCipherSuites should have no effect - tlsConfig := tlsconfig.ServerDefault - - tlsConfig.InsecureSkipVerify = !isSecure - - if isSecure && CertsDir != "" { - hostDir := filepath.Join(CertsDir, cleanPath(hostname)) - logrus.Debugf("hostDir: %s", hostDir) - if err := ReadCertsDirectory(&tlsConfig, hostDir); err != nil { - return nil, err - } - } - - return &tlsConfig, nil -} - -func hasFile(files []os.FileInfo, name string) bool { - for _, f := range files { - if f.Name() == name { - return true - } - } - return false -} - -// ReadCertsDirectory reads the directory for TLS certificates -// including roots and certificate pairs and updates the -// provided TLS configuration. -func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error { - fs, err := ioutil.ReadDir(directory) - if err != nil && !os.IsNotExist(err) { - return err - } - - for _, f := range fs { - if strings.HasSuffix(f.Name(), ".crt") { - if tlsConfig.RootCAs == nil { - // TODO(dmcgowan): Copy system pool - tlsConfig.RootCAs = x509.NewCertPool() - } - logrus.Debugf("crt: %s", filepath.Join(directory, f.Name())) - data, err := ioutil.ReadFile(filepath.Join(directory, f.Name())) - if err != nil { - return err - } - tlsConfig.RootCAs.AppendCertsFromPEM(data) - } - if strings.HasSuffix(f.Name(), ".cert") { - certName := f.Name() - keyName := certName[:len(certName)-5] + ".key" - logrus.Debugf("cert: %s", filepath.Join(directory, f.Name())) - if !hasFile(fs, keyName) { - return fmt.Errorf("Missing key %s for client certificate %s. Note that CA certificates should use the extension .crt.", keyName, certName) - } - cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName)) - if err != nil { - return err - } - tlsConfig.Certificates = append(tlsConfig.Certificates, cert) - } - if strings.HasSuffix(f.Name(), ".key") { - keyName := f.Name() - certName := keyName[:len(keyName)-4] + ".cert" - logrus.Debugf("key: %s", filepath.Join(directory, f.Name())) - if !hasFile(fs, certName) { - return fmt.Errorf("Missing client certificate %s for key %s", certName, keyName) - } - } - } - - return nil -} - -// DockerHeaders returns request modifiers with a User-Agent and metaHeaders -func DockerHeaders(userAgent string, metaHeaders http.Header) []transport.RequestModifier { - modifiers := []transport.RequestModifier{} - if userAgent != "" { - modifiers = append(modifiers, transport.NewHeaderRequestModifier(http.Header{ - "User-Agent": []string{userAgent}, - })) - } - if metaHeaders != nil { - modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders)) - } - return modifiers -} - -// HTTPClient returns an 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, - } -} - -func trustedLocation(req *http.Request) bool { - var ( - trusteds = []string{"docker.com", "docker.io"} - hostname = strings.SplitN(req.Host, ":", 2)[0] - ) - if req.URL.Scheme != "https" { - return false - } - - for _, trusted := range trusteds { - if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) { - return true - } - } - return false -} - -// 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 - return nil - } - for k, v := range via[0].Header { - if k != "Authorization" { - for _, vv := range v { - req.Header.Add(k, vv) - } - } - } - } - return nil -} - -// 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 - tlsConfig = &cfg - } - - direct := &net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - DualStack: true, - } - - base := &http.Transport{ - Proxy: http.ProxyFromEnvironment, - Dial: direct.Dial, - TLSHandshakeTimeout: 10 * time.Second, - TLSClientConfig: tlsConfig, - // TODO(dmcgowan): Call close idle connections when complete and use keep alive - DisableKeepAlives: true, - } - - proxyDialer, err := sockets.DialerFromEnvironment(direct) - if err == nil { - base.Dial = proxyDialer.Dial - } - return base -} diff --git a/docs/registry_mock_test.go b/docs/registry_mock_test.go deleted file mode 100644 index 828f48fc..00000000 --- a/docs/registry_mock_test.go +++ /dev/null @@ -1,476 +0,0 @@ -package registry - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "net" - "net/http" - "net/http/httptest" - "net/url" - "strconv" - "strings" - "testing" - "time" - - "github.com/docker/docker/reference" - registrytypes "github.com/docker/engine-api/types/registry" - "github.com/gorilla/mux" - - "github.com/Sirupsen/logrus" -) - -var ( - testHTTPServer *httptest.Server - testHTTPSServer *httptest.Server - testLayers = map[string]map[string]string{ - "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20": { - "json": `{"id":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", - "comment":"test base image","created":"2013-03-23T12:53:11.10432-07:00", - "container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0, - "CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false, - "Tty":false,"OpenStdin":false,"StdinOnce":false, - "Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null, - "VolumesFrom":"","Entrypoint":null},"Size":424242}`, - "checksum_simple": "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37", - "checksum_tarsum": "tarsum+sha256:4409a0685741ca86d38df878ed6f8cbba4c99de5dc73cd71aef04be3bb70be7c", - "ancestry": `["77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`, - "layer": string([]byte{ - 0x1f, 0x8b, 0x08, 0x08, 0x0e, 0xb0, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65, - 0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd2, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05, - 0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0xed, 0x38, 0x4e, 0xce, 0x13, 0x44, 0x2b, 0x66, - 0x62, 0x24, 0x8e, 0x4f, 0xa0, 0x15, 0x63, 0xb6, 0x20, 0x21, 0xfc, 0x96, 0xbf, 0x78, - 0xb0, 0xf5, 0x1d, 0x16, 0x98, 0x8e, 0x88, 0x8a, 0x2a, 0xbe, 0x33, 0xef, 0x49, 0x31, - 0xed, 0x79, 0x40, 0x8e, 0x5c, 0x44, 0x85, 0x88, 0x33, 0x12, 0x73, 0x2c, 0x02, 0xa8, - 0xf0, 0x05, 0xf7, 0x66, 0xf5, 0xd6, 0x57, 0x69, 0xd7, 0x7a, 0x19, 0xcd, 0xf5, 0xb1, - 0x6d, 0x1b, 0x1f, 0xf9, 0xba, 0xe3, 0x93, 0x3f, 0x22, 0x2c, 0xb6, 0x36, 0x0b, 0xf6, - 0xb0, 0xa9, 0xfd, 0xe7, 0x94, 0x46, 0xfd, 0xeb, 0xd1, 0x7f, 0x2c, 0xc4, 0xd2, 0xfb, - 0x97, 0xfe, 0x02, 0x80, 0xe4, 0xfd, 0x4f, 0x77, 0xae, 0x6d, 0x3d, 0x81, 0x73, 0xce, - 0xb9, 0x7f, 0xf3, 0x04, 0x41, 0xc1, 0xab, 0xc6, 0x00, 0x0a, 0x00, 0x00, - }), - }, - "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d": { - "json": `{"id":"42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", - "parent":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", - "comment":"test base image","created":"2013-03-23T12:55:11.10432-07:00", - "container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0, - "CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false, - "Tty":false,"OpenStdin":false,"StdinOnce":false, - "Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null, - "VolumesFrom":"","Entrypoint":null},"Size":424242}`, - "checksum_simple": "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2", - "checksum_tarsum": "tarsum+sha256:68fdb56fb364f074eec2c9b3f85ca175329c4dcabc4a6a452b7272aa613a07a2", - "ancestry": `["42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", - "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`, - "layer": string([]byte{ - 0x1f, 0x8b, 0x08, 0x08, 0xbd, 0xb3, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65, - 0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd1, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05, - 0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0x9d, 0x38, 0x8e, 0xcf, 0x53, 0x51, 0xaa, 0x56, - 0xea, 0x44, 0x82, 0xc4, 0xf1, 0x09, 0xb4, 0xea, 0x98, 0x2d, 0x48, 0x08, 0xbf, 0xe5, - 0x2f, 0x1e, 0xfc, 0xf5, 0xdd, 0x00, 0xdd, 0x11, 0x91, 0x8a, 0xe0, 0x27, 0xd3, 0x9e, - 0x14, 0xe2, 0x9e, 0x07, 0xf4, 0xc1, 0x2b, 0x0b, 0xfb, 0xa4, 0x82, 0xe4, 0x3d, 0x93, - 0x02, 0x0a, 0x7c, 0xc1, 0x23, 0x97, 0xf1, 0x5e, 0x5f, 0xc9, 0xcb, 0x38, 0xb5, 0xee, - 0xea, 0xd9, 0x3c, 0xb7, 0x4b, 0xbe, 0x7b, 0x9c, 0xf9, 0x23, 0xdc, 0x50, 0x6e, 0xb9, - 0xb8, 0xf2, 0x2c, 0x5d, 0xf7, 0x4f, 0x31, 0xb6, 0xf6, 0x4f, 0xc7, 0xfe, 0x41, 0x55, - 0x63, 0xdd, 0x9f, 0x89, 0x09, 0x90, 0x6c, 0xff, 0xee, 0xae, 0xcb, 0xba, 0x4d, 0x17, - 0x30, 0xc6, 0x18, 0xf3, 0x67, 0x5e, 0xc1, 0xed, 0x21, 0x5d, 0x00, 0x0a, 0x00, 0x00, - }), - }, - } - testRepositories = map[string]map[string]string{ - "foo42/bar": { - "latest": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", - "test": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", - }, - } - mockHosts = map[string][]net.IP{ - "": {net.ParseIP("0.0.0.0")}, - "localhost": {net.ParseIP("127.0.0.1"), net.ParseIP("::1")}, - "example.com": {net.ParseIP("42.42.42.42")}, - "other.com": {net.ParseIP("43.43.43.43")}, - } -) - -func init() { - r := mux.NewRouter() - - // /v1/ - r.HandleFunc("/v1/_ping", handlerGetPing).Methods("GET") - r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|ancestry}", handlerGetImage).Methods("GET") - r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|checksum}", handlerPutImage).Methods("PUT") - r.HandleFunc("/v1/repositories/{repository:.+}/tags", handlerGetDeleteTags).Methods("GET", "DELETE") - r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerGetTag).Methods("GET") - r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerPutTag).Methods("PUT") - r.HandleFunc("/v1/users{null:.*}", handlerUsers).Methods("GET", "POST", "PUT") - r.HandleFunc("/v1/repositories/{repository:.+}{action:/images|/}", handlerImages).Methods("GET", "PUT", "DELETE") - r.HandleFunc("/v1/repositories/{repository:.+}/auth", handlerAuth).Methods("PUT") - r.HandleFunc("/v1/search", handlerSearch).Methods("GET") - - // /v2/ - r.HandleFunc("/v2/version", handlerGetPing).Methods("GET") - - testHTTPServer = httptest.NewServer(handlerAccessLog(r)) - testHTTPSServer = httptest.NewTLSServer(handlerAccessLog(r)) - - // override net.LookupIP - lookupIP = func(host string) ([]net.IP, error) { - if host == "127.0.0.1" { - // I believe in future Go versions this will fail, so let's fix it later - return net.LookupIP(host) - } - for h, addrs := range mockHosts { - if host == h { - return addrs, nil - } - for _, addr := range addrs { - if addr.String() == host { - return []net.IP{addr}, nil - } - } - } - return nil, errors.New("lookup: no such host") - } -} - -func handlerAccessLog(handler http.Handler) http.Handler { - logHandler := func(w http.ResponseWriter, r *http.Request) { - logrus.Debugf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL) - handler.ServeHTTP(w, r) - } - return http.HandlerFunc(logHandler) -} - -func makeURL(req string) string { - return testHTTPServer.URL + req -} - -func makeHTTPSURL(req string) string { - return testHTTPSServer.URL + req -} - -func makeIndex(req string) *registrytypes.IndexInfo { - index := ®istrytypes.IndexInfo{ - Name: makeURL(req), - } - return index -} - -func makeHTTPSIndex(req string) *registrytypes.IndexInfo { - index := ®istrytypes.IndexInfo{ - Name: makeHTTPSURL(req), - } - return index -} - -func makePublicIndex() *registrytypes.IndexInfo { - index := ®istrytypes.IndexInfo{ - Name: IndexServer, - Secure: true, - Official: true, - } - return index -} - -func makeServiceConfig(mirrors []string, insecureRegistries []string) *serviceConfig { - options := ServiceOptions{ - Mirrors: mirrors, - InsecureRegistries: insecureRegistries, - } - - return newServiceConfig(options) -} - -func writeHeaders(w http.ResponseWriter) { - h := w.Header() - h.Add("Server", "docker-tests/mock") - h.Add("Expires", "-1") - h.Add("Content-Type", "application/json") - h.Add("Pragma", "no-cache") - h.Add("Cache-Control", "no-cache") - h.Add("X-Docker-Registry-Version", "0.0.0") - h.Add("X-Docker-Registry-Config", "mock") -} - -func writeResponse(w http.ResponseWriter, message interface{}, code int) { - writeHeaders(w) - w.WriteHeader(code) - body, err := json.Marshal(message) - if err != nil { - io.WriteString(w, err.Error()) - return - } - w.Write(body) -} - -func readJSON(r *http.Request, dest interface{}) error { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - return err - } - return json.Unmarshal(body, dest) -} - -func apiError(w http.ResponseWriter, message string, code int) { - body := map[string]string{ - "error": message, - } - writeResponse(w, body, code) -} - -func assertEqual(t *testing.T, a interface{}, b interface{}, message string) { - if a == b { - return - } - if len(message) == 0 { - message = fmt.Sprintf("%v != %v", a, b) - } - t.Fatal(message) -} - -func assertNotEqual(t *testing.T, a interface{}, b interface{}, message string) { - if a != b { - return - } - if len(message) == 0 { - message = fmt.Sprintf("%v == %v", a, b) - } - t.Fatal(message) -} - -// Similar to assertEqual, but does not stop test -func checkEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) { - if a == b { - return - } - message := fmt.Sprintf("%v != %v", a, b) - if len(messagePrefix) != 0 { - message = messagePrefix + ": " + message - } - t.Error(message) -} - -// Similar to assertNotEqual, but does not stop test -func checkNotEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) { - if a != b { - return - } - message := fmt.Sprintf("%v == %v", a, b) - if len(messagePrefix) != 0 { - message = messagePrefix + ": " + message - } - t.Error(message) -} - -func requiresAuth(w http.ResponseWriter, r *http.Request) bool { - writeCookie := func() { - value := fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano()) - cookie := &http.Cookie{Name: "session", Value: value, MaxAge: 3600} - http.SetCookie(w, cookie) - //FIXME(sam): this should be sent only on Index routes - value = fmt.Sprintf("FAKE-TOKEN-%d", time.Now().UnixNano()) - w.Header().Add("X-Docker-Token", value) - } - if len(r.Cookies()) > 0 { - writeCookie() - return true - } - if len(r.Header.Get("Authorization")) > 0 { - writeCookie() - return true - } - w.Header().Add("WWW-Authenticate", "token") - apiError(w, "Wrong auth", 401) - return false -} - -func handlerGetPing(w http.ResponseWriter, r *http.Request) { - writeResponse(w, true, 200) -} - -func handlerGetImage(w http.ResponseWriter, r *http.Request) { - if !requiresAuth(w, r) { - return - } - vars := mux.Vars(r) - layer, exists := testLayers[vars["image_id"]] - if !exists { - http.NotFound(w, r) - return - } - writeHeaders(w) - layerSize := len(layer["layer"]) - w.Header().Add("X-Docker-Size", strconv.Itoa(layerSize)) - io.WriteString(w, layer[vars["action"]]) -} - -func handlerPutImage(w http.ResponseWriter, r *http.Request) { - if !requiresAuth(w, r) { - return - } - vars := mux.Vars(r) - imageID := vars["image_id"] - action := vars["action"] - layer, exists := testLayers[imageID] - if !exists { - if action != "json" { - http.NotFound(w, r) - return - } - layer = make(map[string]string) - testLayers[imageID] = layer - } - if checksum := r.Header.Get("X-Docker-Checksum"); checksum != "" { - if checksum != layer["checksum_simple"] && checksum != layer["checksum_tarsum"] { - apiError(w, "Wrong checksum", 400) - return - } - } - body, err := ioutil.ReadAll(r.Body) - if err != nil { - apiError(w, fmt.Sprintf("Error: %s", err), 500) - return - } - layer[action] = string(body) - writeResponse(w, true, 200) -} - -func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) { - if !requiresAuth(w, r) { - return - } - repositoryName, err := reference.WithName(mux.Vars(r)["repository"]) - if err != nil { - apiError(w, "Could not parse repository", 400) - return - } - tags, exists := testRepositories[repositoryName.String()] - if !exists { - apiError(w, "Repository not found", 404) - return - } - if r.Method == "DELETE" { - delete(testRepositories, repositoryName.String()) - writeResponse(w, true, 200) - return - } - writeResponse(w, tags, 200) -} - -func handlerGetTag(w http.ResponseWriter, r *http.Request) { - if !requiresAuth(w, r) { - return - } - vars := mux.Vars(r) - repositoryName, err := reference.WithName(vars["repository"]) - if err != nil { - apiError(w, "Could not parse repository", 400) - return - } - tagName := vars["tag"] - tags, exists := testRepositories[repositoryName.String()] - if !exists { - apiError(w, "Repository not found", 404) - return - } - tag, exists := tags[tagName] - if !exists { - apiError(w, "Tag not found", 404) - return - } - writeResponse(w, tag, 200) -} - -func handlerPutTag(w http.ResponseWriter, r *http.Request) { - if !requiresAuth(w, r) { - return - } - vars := mux.Vars(r) - repositoryName, err := reference.WithName(vars["repository"]) - if err != nil { - apiError(w, "Could not parse repository", 400) - return - } - tagName := vars["tag"] - tags, exists := testRepositories[repositoryName.String()] - if !exists { - tags = make(map[string]string) - testRepositories[repositoryName.String()] = tags - } - tagValue := "" - readJSON(r, tagValue) - tags[tagName] = tagValue - writeResponse(w, true, 200) -} - -func handlerUsers(w http.ResponseWriter, r *http.Request) { - code := 200 - if r.Method == "POST" { - code = 201 - } else if r.Method == "PUT" { - code = 204 - } - writeResponse(w, "", code) -} - -func handlerImages(w http.ResponseWriter, r *http.Request) { - u, _ := url.Parse(testHTTPServer.URL) - w.Header().Add("X-Docker-Endpoints", fmt.Sprintf("%s , %s ", u.Host, "test.example.com")) - w.Header().Add("X-Docker-Token", fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano())) - if r.Method == "PUT" { - if strings.HasSuffix(r.URL.Path, "images") { - writeResponse(w, "", 204) - return - } - writeResponse(w, "", 200) - return - } - if r.Method == "DELETE" { - writeResponse(w, "", 204) - return - } - images := []map[string]string{} - for imageID, layer := range testLayers { - image := make(map[string]string) - image["id"] = imageID - image["checksum"] = layer["checksum_tarsum"] - image["Tag"] = "latest" - images = append(images, image) - } - writeResponse(w, images, 200) -} - -func handlerAuth(w http.ResponseWriter, r *http.Request) { - writeResponse(w, "OK", 200) -} - -func handlerSearch(w http.ResponseWriter, r *http.Request) { - result := ®istrytypes.SearchResults{ - Query: "fakequery", - NumResults: 1, - Results: []registrytypes.SearchResult{{Name: "fakeimage", StarCount: 42}}, - } - writeResponse(w, result, 200) -} - -func TestPing(t *testing.T) { - res, err := http.Get(makeURL("/v1/_ping")) - if err != nil { - t.Fatal(err) - } - assertEqual(t, res.StatusCode, 200, "") - assertEqual(t, res.Header.Get("X-Docker-Registry-Config"), "mock", - "This is not a Mocked Registry") -} - -/* Uncomment this to test Mocked Registry locally with curl - * 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) - c := make(chan int) - <-c -} - -//*/ diff --git a/docs/registry_test.go b/docs/registry_test.go deleted file mode 100644 index 9927af32..00000000 --- a/docs/registry_test.go +++ /dev/null @@ -1,873 +0,0 @@ -package registry - -import ( - "fmt" - "net/http" - "net/http/httputil" - "net/url" - "strings" - "testing" - - "github.com/docker/distribution/registry/client/transport" - "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" -) - -var ( - token = []string{"fake-token"} -) - -const ( - imageID = "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d" - REPO = "foo42/bar" -) - -func spawnTestRegistrySession(t *testing.T) *Session { - authConfig := &types.AuthConfig{} - endpoint, err := NewV1Endpoint(makeIndex("/v1/"), "", nil) - if err != nil { - t.Fatal(err) - } - userAgent := "docker test client" - var tr http.RoundTripper = debugTransport{NewTransport(nil), t.Log} - tr = transport.NewTransport(AuthTransport(tr, authConfig, false), DockerHeaders(userAgent, nil)...) - client := HTTPClient(tr) - r, err := NewSession(client, authConfig, endpoint) - if err != nil { - t.Fatal(err) - } - // In a normal scenario for the v1 registry, the client should send a `X-Docker-Token: true` - // header while authenticating, in order to retrieve a token that can be later used to - // perform authenticated actions. - // - // The mock v1 registry does not support that, (TODO(tiborvass): support it), instead, - // it will consider authenticated any request with the header `X-Docker-Token: fake-token`. - // - // Because we know that the client's transport is an `*authTransport` we simply cast it, - // in order to set the internal cached token to the fake token, and thus send that fake token - // upon every subsequent requests. - r.client.Transport.(*authTransport).token = token - return r -} - -func TestPingRegistryEndpoint(t *testing.T) { - testPing := func(index *registrytypes.IndexInfo, expectedStandalone bool, assertMessage string) { - ep, err := NewV1Endpoint(index, "", nil) - if err != nil { - t.Fatal(err) - } - regInfo, err := ep.Ping() - if err != nil { - t.Fatal(err) - } - - assertEqual(t, regInfo.Standalone, expectedStandalone, assertMessage) - } - - testPing(makeIndex("/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") -} - -func TestEndpoint(t *testing.T) { - // Simple wrapper to fail test if err != nil - expandEndpoint := func(index *registrytypes.IndexInfo) *V1Endpoint { - endpoint, err := NewV1Endpoint(index, "", nil) - if err != nil { - t.Fatal(err) - } - return endpoint - } - - assertInsecureIndex := func(index *registrytypes.IndexInfo) { - index.Secure = true - _, err := NewV1Endpoint(index, "", nil) - assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index") - assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry error for insecure index") - index.Secure = false - } - - assertSecureIndex := func(index *registrytypes.IndexInfo) { - index.Secure = true - _, err := NewV1Endpoint(index, "", nil) - assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index") - assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index") - index.Secure = false - } - - index := ®istrytypes.IndexInfo{} - index.Name = makeURL("/v1/") - endpoint := expandEndpoint(index) - assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name) - assertInsecureIndex(index) - - index.Name = makeURL("") - endpoint = expandEndpoint(index) - assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/") - assertInsecureIndex(index) - - httpURL := makeURL("") - index.Name = strings.SplitN(httpURL, "://", 2)[1] - endpoint = expandEndpoint(index) - assertEqual(t, endpoint.String(), httpURL+"/v1/", index.Name+": Expected endpoint to be "+httpURL+"/v1/") - assertInsecureIndex(index) - - index.Name = makeHTTPSURL("/v1/") - endpoint = expandEndpoint(index) - assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name) - assertSecureIndex(index) - - index.Name = makeHTTPSURL("") - endpoint = expandEndpoint(index) - assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/") - assertSecureIndex(index) - - 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/") - assertSecureIndex(index) - - badEndpoints := []string{ - "http://127.0.0.1/v1/", - "https://127.0.0.1/v1/", - "http://127.0.0.1", - "https://127.0.0.1", - "127.0.0.1", - } - for _, address := range badEndpoints { - index.Name = address - _, err := NewV1Endpoint(index, "", nil) - checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint") - } -} - -func TestGetRemoteHistory(t *testing.T) { - r := spawnTestRegistrySession(t) - hist, err := r.GetRemoteHistory(imageID, makeURL("/v1/")) - if err != nil { - t.Fatal(err) - } - assertEqual(t, len(hist), 2, "Expected 2 images in history") - assertEqual(t, hist[0], imageID, "Expected "+imageID+"as first ancestry") - assertEqual(t, hist[1], "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", - "Unexpected second ancestry") -} - -func TestLookupRemoteImage(t *testing.T) { - r := spawnTestRegistrySession(t) - err := r.LookupRemoteImage(imageID, makeURL("/v1/")) - assertEqual(t, err, nil, "Expected error of remote lookup to nil") - if err := r.LookupRemoteImage("abcdef", makeURL("/v1/")); err == nil { - t.Fatal("Expected error of remote lookup to not nil") - } -} - -func TestGetRemoteImageJSON(t *testing.T) { - r := spawnTestRegistrySession(t) - json, size, err := r.GetRemoteImageJSON(imageID, makeURL("/v1/")) - if err != nil { - t.Fatal(err) - } - assertEqual(t, size, int64(154), "Expected size 154") - if len(json) == 0 { - t.Fatal("Expected non-empty json") - } - - _, _, err = r.GetRemoteImageJSON("abcdef", makeURL("/v1/")) - if err == nil { - t.Fatal("Expected image not found error") - } -} - -func TestGetRemoteImageLayer(t *testing.T) { - r := spawnTestRegistrySession(t) - data, err := r.GetRemoteImageLayer(imageID, makeURL("/v1/"), 0) - if err != nil { - t.Fatal(err) - } - if data == nil { - t.Fatal("Expected non-nil data result") - } - - _, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), 0) - if err == nil { - t.Fatal("Expected image not found error") - } -} - -func TestGetRemoteTag(t *testing.T) { - r := spawnTestRegistrySession(t) - repoRef, err := reference.ParseNamed(REPO) - if err != nil { - t.Fatal(err) - } - tag, err := r.GetRemoteTag([]string{makeURL("/v1/")}, repoRef, "test") - if err != nil { - t.Fatal(err) - } - assertEqual(t, tag, imageID, "Expected tag test to map to "+imageID) - - bazRef, err := reference.ParseNamed("foo42/baz") - if err != nil { - t.Fatal(err) - } - _, err = r.GetRemoteTag([]string{makeURL("/v1/")}, bazRef, "foo") - if err != ErrRepoNotFound { - t.Fatal("Expected ErrRepoNotFound error when fetching tag for bogus repo") - } -} - -func TestGetRemoteTags(t *testing.T) { - r := spawnTestRegistrySession(t) - repoRef, err := reference.ParseNamed(REPO) - if err != nil { - t.Fatal(err) - } - tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, repoRef) - if err != nil { - t.Fatal(err) - } - assertEqual(t, len(tags), 2, "Expected two tags") - assertEqual(t, tags["latest"], imageID, "Expected tag latest to map to "+imageID) - assertEqual(t, tags["test"], imageID, "Expected tag test to map to "+imageID) - - bazRef, err := reference.ParseNamed("foo42/baz") - if err != nil { - t.Fatal(err) - } - _, err = r.GetRemoteTags([]string{makeURL("/v1/")}, bazRef) - if err != ErrRepoNotFound { - t.Fatal("Expected ErrRepoNotFound error when fetching tags for bogus repo") - } -} - -func TestGetRepositoryData(t *testing.T) { - r := spawnTestRegistrySession(t) - parsedURL, err := url.Parse(makeURL("/v1/")) - if err != nil { - t.Fatal(err) - } - host := "http://" + parsedURL.Host + "/v1/" - repoRef, err := reference.ParseNamed(REPO) - if err != nil { - t.Fatal(err) - } - data, err := r.GetRepositoryData(repoRef) - if err != nil { - t.Fatal(err) - } - assertEqual(t, len(data.ImgList), 2, "Expected 2 images in ImgList") - assertEqual(t, len(data.Endpoints), 2, - fmt.Sprintf("Expected 2 endpoints in Endpoints, found %d instead", len(data.Endpoints))) - assertEqual(t, data.Endpoints[0], host, - fmt.Sprintf("Expected first endpoint to be %s but found %s instead", host, data.Endpoints[0])) - assertEqual(t, data.Endpoints[1], "http://test.example.com/v1/", - fmt.Sprintf("Expected first endpoint to be http://test.example.com/v1/ but found %s instead", data.Endpoints[1])) - -} - -func TestPushImageJSONRegistry(t *testing.T) { - r := spawnTestRegistrySession(t) - imgData := &ImgData{ - ID: "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", - Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37", - } - - err := r.PushImageJSONRegistry(imgData, []byte{0x42, 0xdf, 0x0}, makeURL("/v1/")) - if err != nil { - t.Fatal(err) - } -} - -func TestPushImageLayerRegistry(t *testing.T) { - r := spawnTestRegistrySession(t) - layer := strings.NewReader("") - _, _, err := r.PushImageLayerRegistry(imageID, layer, makeURL("/v1/"), []byte{}) - if err != nil { - t.Fatal(err) - } -} - -func TestParseRepositoryInfo(t *testing.T) { - type staticRepositoryInfo struct { - Index *registrytypes.IndexInfo - RemoteName string - CanonicalName string - LocalName string - Official bool - } - - expectedRepoInfos := map[string]staticRepositoryInfo{ - "fooo/bar": { - Index: ®istrytypes.IndexInfo{ - Name: IndexName, - Official: true, - }, - RemoteName: "fooo/bar", - LocalName: "fooo/bar", - CanonicalName: "docker.io/fooo/bar", - Official: false, - }, - "library/ubuntu": { - Index: ®istrytypes.IndexInfo{ - Name: IndexName, - Official: true, - }, - RemoteName: "library/ubuntu", - LocalName: "ubuntu", - CanonicalName: "docker.io/library/ubuntu", - Official: true, - }, - "nonlibrary/ubuntu": { - Index: ®istrytypes.IndexInfo{ - Name: IndexName, - Official: true, - }, - RemoteName: "nonlibrary/ubuntu", - LocalName: "nonlibrary/ubuntu", - CanonicalName: "docker.io/nonlibrary/ubuntu", - Official: false, - }, - "ubuntu": { - Index: ®istrytypes.IndexInfo{ - Name: IndexName, - Official: true, - }, - RemoteName: "library/ubuntu", - LocalName: "ubuntu", - CanonicalName: "docker.io/library/ubuntu", - Official: true, - }, - "other/library": { - Index: ®istrytypes.IndexInfo{ - Name: IndexName, - Official: true, - }, - RemoteName: "other/library", - LocalName: "other/library", - CanonicalName: "docker.io/other/library", - Official: false, - }, - "127.0.0.1:8000/private/moonbase": { - Index: ®istrytypes.IndexInfo{ - Name: "127.0.0.1:8000", - Official: false, - }, - RemoteName: "private/moonbase", - LocalName: "127.0.0.1:8000/private/moonbase", - CanonicalName: "127.0.0.1:8000/private/moonbase", - Official: false, - }, - "127.0.0.1:8000/privatebase": { - Index: ®istrytypes.IndexInfo{ - Name: "127.0.0.1:8000", - Official: false, - }, - RemoteName: "privatebase", - LocalName: "127.0.0.1:8000/privatebase", - CanonicalName: "127.0.0.1:8000/privatebase", - Official: false, - }, - "localhost:8000/private/moonbase": { - Index: ®istrytypes.IndexInfo{ - Name: "localhost:8000", - Official: false, - }, - RemoteName: "private/moonbase", - LocalName: "localhost:8000/private/moonbase", - CanonicalName: "localhost:8000/private/moonbase", - Official: false, - }, - "localhost:8000/privatebase": { - Index: ®istrytypes.IndexInfo{ - Name: "localhost:8000", - Official: false, - }, - RemoteName: "privatebase", - LocalName: "localhost:8000/privatebase", - CanonicalName: "localhost:8000/privatebase", - Official: false, - }, - "example.com/private/moonbase": { - Index: ®istrytypes.IndexInfo{ - Name: "example.com", - Official: false, - }, - RemoteName: "private/moonbase", - LocalName: "example.com/private/moonbase", - CanonicalName: "example.com/private/moonbase", - Official: false, - }, - "example.com/privatebase": { - Index: ®istrytypes.IndexInfo{ - Name: "example.com", - Official: false, - }, - RemoteName: "privatebase", - LocalName: "example.com/privatebase", - CanonicalName: "example.com/privatebase", - Official: false, - }, - "example.com:8000/private/moonbase": { - Index: ®istrytypes.IndexInfo{ - Name: "example.com:8000", - Official: false, - }, - RemoteName: "private/moonbase", - LocalName: "example.com:8000/private/moonbase", - CanonicalName: "example.com:8000/private/moonbase", - Official: false, - }, - "example.com:8000/privatebase": { - Index: ®istrytypes.IndexInfo{ - Name: "example.com:8000", - Official: false, - }, - RemoteName: "privatebase", - LocalName: "example.com:8000/privatebase", - CanonicalName: "example.com:8000/privatebase", - Official: false, - }, - "localhost/private/moonbase": { - Index: ®istrytypes.IndexInfo{ - Name: "localhost", - Official: false, - }, - RemoteName: "private/moonbase", - LocalName: "localhost/private/moonbase", - CanonicalName: "localhost/private/moonbase", - Official: false, - }, - "localhost/privatebase": { - Index: ®istrytypes.IndexInfo{ - Name: "localhost", - Official: false, - }, - RemoteName: "privatebase", - LocalName: "localhost/privatebase", - CanonicalName: "localhost/privatebase", - Official: false, - }, - IndexName + "/public/moonbase": { - Index: ®istrytypes.IndexInfo{ - Name: IndexName, - Official: true, - }, - RemoteName: "public/moonbase", - LocalName: "public/moonbase", - CanonicalName: "docker.io/public/moonbase", - Official: false, - }, - "index." + IndexName + "/public/moonbase": { - Index: ®istrytypes.IndexInfo{ - Name: IndexName, - Official: true, - }, - RemoteName: "public/moonbase", - LocalName: "public/moonbase", - CanonicalName: "docker.io/public/moonbase", - Official: false, - }, - "ubuntu-12.04-base": { - Index: ®istrytypes.IndexInfo{ - Name: IndexName, - Official: true, - }, - RemoteName: "library/ubuntu-12.04-base", - LocalName: "ubuntu-12.04-base", - CanonicalName: "docker.io/library/ubuntu-12.04-base", - Official: true, - }, - IndexName + "/ubuntu-12.04-base": { - Index: ®istrytypes.IndexInfo{ - Name: IndexName, - Official: true, - }, - RemoteName: "library/ubuntu-12.04-base", - LocalName: "ubuntu-12.04-base", - CanonicalName: "docker.io/library/ubuntu-12.04-base", - Official: true, - }, - "index." + IndexName + "/ubuntu-12.04-base": { - Index: ®istrytypes.IndexInfo{ - Name: IndexName, - Official: true, - }, - RemoteName: "library/ubuntu-12.04-base", - LocalName: "ubuntu-12.04-base", - CanonicalName: "docker.io/library/ubuntu-12.04-base", - Official: true, - }, - } - - for reposName, expectedRepoInfo := range expectedRepoInfos { - named, err := reference.WithName(reposName) - if err != nil { - t.Error(err) - } - - repoInfo, err := ParseRepositoryInfo(named) - if err != nil { - t.Error(err) - } else { - checkEqual(t, repoInfo.Index.Name, expectedRepoInfo.Index.Name, reposName) - checkEqual(t, repoInfo.RemoteName(), expectedRepoInfo.RemoteName, reposName) - checkEqual(t, repoInfo.Name(), expectedRepoInfo.LocalName, reposName) - checkEqual(t, repoInfo.FullName(), expectedRepoInfo.CanonicalName, reposName) - checkEqual(t, repoInfo.Index.Official, expectedRepoInfo.Index.Official, reposName) - checkEqual(t, repoInfo.Official, expectedRepoInfo.Official, reposName) - } - } -} - -func TestNewIndexInfo(t *testing.T) { - testIndexInfo := func(config *serviceConfig, expectedIndexInfos map[string]*registrytypes.IndexInfo) { - for indexName, expectedIndexInfo := range expectedIndexInfos { - index, err := newIndexInfo(config, indexName) - if err != nil { - t.Fatal(err) - } else { - checkEqual(t, index.Name, expectedIndexInfo.Name, indexName+" name") - checkEqual(t, index.Official, expectedIndexInfo.Official, indexName+" is official") - checkEqual(t, index.Secure, expectedIndexInfo.Secure, indexName+" is secure") - checkEqual(t, len(index.Mirrors), len(expectedIndexInfo.Mirrors), indexName+" mirrors") - } - } - } - - config := newServiceConfig(ServiceOptions{}) - noMirrors := []string{} - expectedIndexInfos := map[string]*registrytypes.IndexInfo{ - IndexName: { - Name: IndexName, - Official: true, - Secure: true, - Mirrors: noMirrors, - }, - "index." + IndexName: { - Name: IndexName, - Official: true, - Secure: true, - Mirrors: noMirrors, - }, - "example.com": { - Name: "example.com", - Official: false, - Secure: true, - Mirrors: noMirrors, - }, - "127.0.0.1:5000": { - Name: "127.0.0.1:5000", - Official: false, - Secure: false, - Mirrors: noMirrors, - }, - } - testIndexInfo(config, expectedIndexInfos) - - publicMirrors := []string{"http://mirror1.local", "http://mirror2.local"} - config = makeServiceConfig(publicMirrors, []string{"example.com"}) - - expectedIndexInfos = map[string]*registrytypes.IndexInfo{ - IndexName: { - Name: IndexName, - Official: true, - Secure: true, - Mirrors: publicMirrors, - }, - "index." + IndexName: { - Name: IndexName, - Official: true, - Secure: true, - Mirrors: publicMirrors, - }, - "example.com": { - Name: "example.com", - Official: false, - Secure: false, - Mirrors: noMirrors, - }, - "example.com:5000": { - Name: "example.com:5000", - Official: false, - Secure: true, - Mirrors: noMirrors, - }, - "127.0.0.1": { - Name: "127.0.0.1", - Official: false, - Secure: false, - Mirrors: noMirrors, - }, - "127.0.0.1:5000": { - Name: "127.0.0.1:5000", - Official: false, - Secure: false, - Mirrors: noMirrors, - }, - "other.com": { - Name: "other.com", - Official: false, - Secure: true, - Mirrors: noMirrors, - }, - } - testIndexInfo(config, expectedIndexInfos) - - config = makeServiceConfig(nil, []string{"42.42.0.0/16"}) - expectedIndexInfos = map[string]*registrytypes.IndexInfo{ - "example.com": { - Name: "example.com", - Official: false, - Secure: false, - Mirrors: noMirrors, - }, - "example.com:5000": { - Name: "example.com:5000", - Official: false, - Secure: false, - Mirrors: noMirrors, - }, - "127.0.0.1": { - Name: "127.0.0.1", - Official: false, - Secure: false, - Mirrors: noMirrors, - }, - "127.0.0.1:5000": { - Name: "127.0.0.1:5000", - Official: false, - Secure: false, - Mirrors: noMirrors, - }, - "other.com": { - Name: "other.com", - Official: false, - Secure: true, - Mirrors: noMirrors, - }, - } - testIndexInfo(config, expectedIndexInfos) -} - -func TestMirrorEndpointLookup(t *testing.T) { - containsMirror := func(endpoints []APIEndpoint) bool { - for _, pe := range endpoints { - if pe.URL.Host == "my.mirror" { - return true - } - } - return false - } - s := DefaultService{config: makeServiceConfig([]string{"my.mirror"}, nil)} - - imageName, err := reference.WithName(IndexName + "/test/image") - if err != nil { - t.Error(err) - } - pushAPIEndpoints, err := s.LookupPushEndpoints(imageName.Hostname()) - if err != nil { - t.Fatal(err) - } - if containsMirror(pushAPIEndpoints) { - t.Fatal("Push endpoint should not contain mirror") - } - - pullAPIEndpoints, err := s.LookupPullEndpoints(imageName.Hostname()) - if err != nil { - t.Fatal(err) - } - if !containsMirror(pullAPIEndpoints) { - t.Fatal("Pull endpoint should contain mirror") - } -} - -func TestPushRegistryTag(t *testing.T) { - r := spawnTestRegistrySession(t) - repoRef, err := reference.ParseNamed(REPO) - if err != nil { - t.Fatal(err) - } - err = r.PushRegistryTag(repoRef, imageID, "stable", makeURL("/v1/")) - if err != nil { - t.Fatal(err) - } -} - -func TestPushImageJSONIndex(t *testing.T) { - r := spawnTestRegistrySession(t) - imgData := []*ImgData{ - { - ID: "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", - Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37", - }, - { - ID: "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", - Checksum: "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2", - }, - } - repoRef, err := reference.ParseNamed(REPO) - if err != nil { - t.Fatal(err) - } - repoData, err := r.PushImageJSONIndex(repoRef, imgData, false, nil) - if err != nil { - t.Fatal(err) - } - if repoData == nil { - t.Fatal("Expected RepositoryData object") - } - repoData, err = r.PushImageJSONIndex(repoRef, imgData, true, []string{r.indexEndpoint.String()}) - if err != nil { - t.Fatal(err) - } - if repoData == nil { - t.Fatal("Expected RepositoryData object") - } -} - -func TestSearchRepositories(t *testing.T) { - r := spawnTestRegistrySession(t) - results, err := r.SearchRepositories("fakequery", 25) - if err != nil { - t.Fatal(err) - } - if results == nil { - t.Fatal("Expected non-nil SearchResults object") - } - assertEqual(t, results.NumResults, 1, "Expected 1 search results") - assertEqual(t, results.Query, "fakequery", "Expected 'fakequery' as query") - assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars") -} - -func TestTrustedLocation(t *testing.T) { - for _, url := range []string{"http://example.com", "https://example.com:7777", "http://docker.io", "http://test.docker.com", "https://fakedocker.com"} { - req, _ := http.NewRequest("GET", url, nil) - if trustedLocation(req) == true { - t.Fatalf("'%s' shouldn't be detected as a trusted location", url) - } - } - - for _, url := range []string{"https://docker.io", "https://test.docker.com:80"} { - req, _ := http.NewRequest("GET", url, nil) - if trustedLocation(req) == false { - t.Fatalf("'%s' should be detected as a trusted location", url) - } - } -} - -func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) { - for _, urls := range [][]string{ - {"http://docker.io", "https://docker.com"}, - {"https://foo.docker.io:7777", "http://bar.docker.com"}, - {"https://foo.docker.io", "https://example.com"}, - } { - reqFrom, _ := http.NewRequest("GET", urls[0], nil) - reqFrom.Header.Add("Content-Type", "application/json") - reqFrom.Header.Add("Authorization", "super_secret") - reqTo, _ := http.NewRequest("GET", urls[1], nil) - - addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom}) - - if len(reqTo.Header) != 1 { - t.Fatalf("Expected 1 headers, got %d", len(reqTo.Header)) - } - - if reqTo.Header.Get("Content-Type") != "application/json" { - t.Fatal("'Content-Type' should be 'application/json'") - } - - if reqTo.Header.Get("Authorization") != "" { - t.Fatal("'Authorization' should be empty") - } - } - - for _, urls := range [][]string{ - {"https://docker.io", "https://docker.com"}, - {"https://foo.docker.io:7777", "https://bar.docker.com"}, - } { - reqFrom, _ := http.NewRequest("GET", urls[0], nil) - reqFrom.Header.Add("Content-Type", "application/json") - reqFrom.Header.Add("Authorization", "super_secret") - reqTo, _ := http.NewRequest("GET", urls[1], nil) - - addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom}) - - if len(reqTo.Header) != 2 { - t.Fatalf("Expected 2 headers, got %d", len(reqTo.Header)) - } - - if reqTo.Header.Get("Content-Type") != "application/json" { - t.Fatal("'Content-Type' should be 'application/json'") - } - - if reqTo.Header.Get("Authorization") != "super_secret" { - t.Fatal("'Authorization' should be 'super_secret'") - } - } -} - -func TestIsSecureIndex(t *testing.T) { - tests := []struct { - addr string - insecureRegistries []string - expected bool - }{ - {IndexName, nil, true}, - {"example.com", []string{}, true}, - {"example.com", []string{"example.com"}, false}, - {"localhost", []string{"localhost:5000"}, false}, - {"localhost:5000", []string{"localhost:5000"}, false}, - {"localhost", []string{"example.com"}, false}, - {"127.0.0.1:5000", []string{"127.0.0.1:5000"}, false}, - {"localhost", nil, false}, - {"localhost:5000", nil, false}, - {"127.0.0.1", nil, false}, - {"localhost", []string{"example.com"}, false}, - {"127.0.0.1", []string{"example.com"}, false}, - {"example.com", nil, true}, - {"example.com", []string{"example.com"}, false}, - {"127.0.0.1", []string{"example.com"}, false}, - {"127.0.0.1:5000", []string{"example.com"}, false}, - {"example.com:5000", []string{"42.42.0.0/16"}, false}, - {"example.com", []string{"42.42.0.0/16"}, false}, - {"example.com:5000", []string{"42.42.42.42/8"}, false}, - {"127.0.0.1:5000", []string{"127.0.0.0/8"}, false}, - {"42.42.42.42:5000", []string{"42.1.1.1/8"}, false}, - {"invalid.domain.com", []string{"42.42.0.0/16"}, true}, - {"invalid.domain.com", []string{"invalid.domain.com"}, false}, - {"invalid.domain.com:5000", []string{"invalid.domain.com"}, true}, - {"invalid.domain.com:5000", []string{"invalid.domain.com:5000"}, false}, - } - for _, tt := range tests { - config := makeServiceConfig(nil, tt.insecureRegistries) - if sec := isSecureIndex(config, tt.addr); sec != tt.expected { - t.Errorf("isSecureIndex failed for %q %v, expected %v got %v", tt.addr, tt.insecureRegistries, tt.expected, sec) - } - } -} - -type debugTransport struct { - http.RoundTripper - log func(...interface{}) -} - -func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) { - dump, err := httputil.DumpRequestOut(req, false) - if err != nil { - tr.log("could not dump request") - } - tr.log(string(dump)) - resp, err := tr.RoundTripper.RoundTrip(req) - if err != nil { - return nil, err - } - dump, err = httputil.DumpResponse(resp, false) - if err != nil { - tr.log("could not dump response") - } - tr.log(string(dump)) - return resp, err -} diff --git a/docs/service.go b/docs/service.go deleted file mode 100644 index dbc16284..00000000 --- a/docs/service.go +++ /dev/null @@ -1,260 +0,0 @@ -package registry - -import ( - "crypto/tls" - "fmt" - "net/http" - "net/url" - "strings" - - "golang.org/x/net/context" - - "github.com/Sirupsen/logrus" - "github.com/docker/distribution/registry/client/auth" - "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" -) - -const ( - // DefaultSearchLimit is the default value for maximum number of returned search results. - DefaultSearchLimit = 25 -) - -// Service is the interface defining what a registry service should implement. -type Service interface { - Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) - LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) - LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) - ResolveRepository(name reference.Named) (*RepositoryInfo, error) - ResolveIndex(name string) (*registrytypes.IndexInfo, error) - Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) - ServiceConfig() *registrytypes.ServiceConfig - TLSConfig(hostname string) (*tls.Config, error) -} - -// DefaultService is a registry service. It tracks configuration data such as a list -// of mirrors. -type DefaultService struct { - config *serviceConfig -} - -// NewService returns a new instance of DefaultService ready to be -// installed into an engine. -func NewService(options ServiceOptions) *DefaultService { - return &DefaultService{ - config: newServiceConfig(options), - } -} - -// ServiceConfig returns the public registry service configuration. -func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig { - return &s.config.ServiceConfig -} - -// Auth contacts the public registry with the provided credentials, -// and returns OK if authentication was successful. -// It can be used to verify the validity of a client's credentials. -func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) { - // TODO Use ctx when searching for repositories - serverAddress := authConfig.ServerAddress - if serverAddress == "" { - serverAddress = IndexServer - } - if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") { - serverAddress = "https://" + serverAddress - } - u, err := url.Parse(serverAddress) - if err != nil { - return "", "", fmt.Errorf("unable to parse server address: %v", err) - } - - endpoints, err := s.LookupPushEndpoints(u.Host) - if err != nil { - return "", "", err - } - - for _, endpoint := range endpoints { - login := loginV2 - if endpoint.Version == APIVersion1 { - login = loginV1 - } - - status, token, err = login(authConfig, endpoint, userAgent) - if err == nil { - return - } - if fErr, ok := err.(fallbackError); ok { - err = fErr.err - logrus.Infof("Error logging in to %s endpoint, trying next endpoint: %v", endpoint.Version, err) - continue - } - return "", "", err - } - - return "", "", err -} - -// splitReposSearchTerm breaks a search term into an index name and remote name -func splitReposSearchTerm(reposName string) (string, string) { - nameParts := strings.SplitN(reposName, "/", 2) - var indexName, remoteName string - if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && - !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") { - // This is a Docker Index repos (ex: samalba/hipache or ubuntu) - // 'docker.io' - indexName = IndexName - remoteName = reposName - } else { - indexName = nameParts[0] - remoteName = nameParts[1] - } - return indexName, remoteName -} - -// Search queries the public registry for images matching the specified -// search terms, and returns the results. -func (s *DefaultService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) { - // TODO Use ctx when searching for repositories - if err := validateNoScheme(term); err != nil { - return nil, err - } - - indexName, remoteName := splitReposSearchTerm(term) - - index, err := newIndexInfo(s.config, indexName) - if err != nil { - return nil, err - } - - // *TODO: Search multiple indexes. - endpoint, err := NewV1Endpoint(index, userAgent, http.Header(headers)) - if err != nil { - return nil, err - } - - var client *http.Client - if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" { - creds := NewStaticCredentialStore(authConfig) - scopes := []auth.Scope{ - auth.RegistryScope{ - Name: "catalog", - Actions: []string{"search"}, - }, - } - - modifiers := DockerHeaders(userAgent, nil) - v2Client, foundV2, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes) - if err != nil { - if fErr, ok := err.(fallbackError); ok { - logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err) - } else { - return nil, err - } - } else if foundV2 { - // Copy non transport http client features - v2Client.Timeout = endpoint.client.Timeout - v2Client.CheckRedirect = endpoint.client.CheckRedirect - v2Client.Jar = endpoint.client.Jar - - logrus.Debugf("using v2 client for search to %s", endpoint.URL) - client = v2Client - } - } - - if client == nil { - client = endpoint.client - if err := authorizeClient(client, authConfig, endpoint); err != nil { - return nil, err - } - } - - r := newSession(client, authConfig, endpoint) - - if index.Official { - localName := remoteName - if strings.HasPrefix(localName, "library/") { - // If pull "library/foo", it's stored locally under "foo" - localName = strings.SplitN(localName, "/", 2)[1] - } - - return r.SearchRepositories(localName, limit) - } - return r.SearchRepositories(remoteName, limit) -} - -// ResolveRepository splits a repository name into its components -// and configuration of the associated registry. -func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) { - return newRepositoryInfo(s.config, name) -} - -// ResolveIndex takes indexName and returns index info -func (s *DefaultService) ResolveIndex(name string) (*registrytypes.IndexInfo, error) { - return newIndexInfo(s.config, name) -} - -// APIEndpoint represents a remote API endpoint -type APIEndpoint struct { - Mirror bool - URL *url.URL - Version APIVersion - Official bool - TrimHostname bool - TLSConfig *tls.Config -} - -// ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint -func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) (*V1Endpoint, error) { - return newV1Endpoint(*e.URL, e.TLSConfig, userAgent, metaHeaders) -} - -// TLSConfig constructs a client TLS configuration based on server defaults -func (s *DefaultService) TLSConfig(hostname string) (*tls.Config, error) { - return newTLSConfig(hostname, isSecureIndex(s.config, hostname)) -} - -func (s *DefaultService) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) { - return s.TLSConfig(mirrorURL.Host) -} - -// LookupPullEndpoints creates a list of endpoints to try to pull from, in order of preference. -// It gives preference to v2 endpoints over v1, mirrors over the actual -// registry, and HTTPS over plain HTTP. -func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) { - return s.lookupEndpoints(hostname) -} - -// LookupPushEndpoints creates a list of endpoints to try to push to, in order of preference. -// It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP. -// Mirrors are not included. -func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) { - allEndpoints, err := s.lookupEndpoints(hostname) - if err == nil { - for _, endpoint := range allEndpoints { - if !endpoint.Mirror { - endpoints = append(endpoints, endpoint) - } - } - } - return endpoints, err -} - -func (s *DefaultService) lookupEndpoints(hostname string) (endpoints []APIEndpoint, err error) { - endpoints, err = s.lookupV2Endpoints(hostname) - if err != nil { - return nil, err - } - - if s.config.V2Only { - return endpoints, nil - } - - legacyEndpoints, err := s.lookupV1Endpoints(hostname) - if err != nil { - return nil, err - } - endpoints = append(endpoints, legacyEndpoints...) - - return endpoints, nil -} diff --git a/docs/service_v1.go b/docs/service_v1.go deleted file mode 100644 index 5d7e8989..00000000 --- a/docs/service_v1.go +++ /dev/null @@ -1,53 +0,0 @@ -package registry - -import ( - "net/url" - - "github.com/docker/go-connections/tlsconfig" -) - -func (s *DefaultService) lookupV1Endpoints(hostname string) (endpoints []APIEndpoint, err error) { - var cfg = tlsconfig.ServerDefault - tlsConfig := &cfg - if hostname == DefaultNamespace { - endpoints = append(endpoints, APIEndpoint{ - URL: DefaultV1Registry, - Version: APIVersion1, - Official: true, - TrimHostname: true, - TLSConfig: tlsConfig, - }) - return endpoints, nil - } - - tlsConfig, err = s.TLSConfig(hostname) - if err != nil { - return nil, err - } - - endpoints = []APIEndpoint{ - { - URL: &url.URL{ - Scheme: "https", - Host: hostname, - }, - Version: APIVersion1, - TrimHostname: true, - TLSConfig: tlsConfig, - }, - } - - if tlsConfig.InsecureSkipVerify { - endpoints = append(endpoints, APIEndpoint{ // or this - URL: &url.URL{ - Scheme: "http", - Host: hostname, - }, - Version: APIVersion1, - TrimHostname: true, - // used to check if supposed to be secure via InsecureSkipVerify - TLSConfig: tlsConfig, - }) - } - return endpoints, nil -} diff --git a/docs/service_v2.go b/docs/service_v2.go deleted file mode 100644 index 5e62f8ff..00000000 --- a/docs/service_v2.go +++ /dev/null @@ -1,79 +0,0 @@ -package registry - -import ( - "net/url" - "strings" - - "github.com/docker/go-connections/tlsconfig" -) - -func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) { - var cfg = tlsconfig.ServerDefault - tlsConfig := &cfg - if hostname == DefaultNamespace || hostname == DefaultV1Registry.Host { - // v2 mirrors - for _, mirror := range s.config.Mirrors { - if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") { - mirror = "https://" + mirror - } - mirrorURL, err := url.Parse(mirror) - if err != nil { - return nil, err - } - mirrorTLSConfig, err := s.tlsConfigForMirror(mirrorURL) - if err != nil { - return nil, err - } - endpoints = append(endpoints, APIEndpoint{ - URL: mirrorURL, - // guess mirrors are v2 - Version: APIVersion2, - Mirror: true, - TrimHostname: true, - TLSConfig: mirrorTLSConfig, - }) - } - // v2 registry - endpoints = append(endpoints, APIEndpoint{ - URL: DefaultV2Registry, - Version: APIVersion2, - Official: true, - TrimHostname: true, - TLSConfig: tlsConfig, - }) - - return endpoints, nil - } - - tlsConfig, err = s.TLSConfig(hostname) - if err != nil { - return nil, err - } - - endpoints = []APIEndpoint{ - { - URL: &url.URL{ - Scheme: "https", - Host: hostname, - }, - Version: APIVersion2, - TrimHostname: true, - TLSConfig: tlsConfig, - }, - } - - if tlsConfig.InsecureSkipVerify { - endpoints = append(endpoints, APIEndpoint{ - URL: &url.URL{ - Scheme: "http", - Host: hostname, - }, - Version: APIVersion2, - TrimHostname: true, - // used to check if supposed to be secure via InsecureSkipVerify - TLSConfig: tlsConfig, - }) - } - - return endpoints, nil -} diff --git a/docs/session.go b/docs/session.go deleted file mode 100644 index d48b9e8d..00000000 --- a/docs/session.go +++ /dev/null @@ -1,783 +0,0 @@ -package registry - -import ( - "bytes" - "crypto/sha256" - "errors" - "sync" - // this is required for some certificates - _ "crypto/sha512" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/http/cookiejar" - "net/url" - "strconv" - "strings" - - "github.com/Sirupsen/logrus" - "github.com/docker/distribution/registry/api/errcode" - "github.com/docker/docker/pkg/httputils" - "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/pkg/tarsum" - "github.com/docker/docker/reference" - "github.com/docker/engine-api/types" - registrytypes "github.com/docker/engine-api/types/registry" -) - -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 *V1Endpoint - client *http.Client - // TODO(tiborvass): remove authConfig - authConfig *types.AuthConfig - id string -} - -type authTransport struct { - http.RoundTripper - *types.AuthConfig - - alwaysSetBasicAuth bool - token []string - - mu sync.Mutex // guards modReq - modReq map[*http.Request]*http.Request // original -> modified -} - -// AuthTransport handles the auth layer when communicating with a v1 registry (private or official) -// -// For private v1 registries, set alwaysSetBasicAuth to true. -// -// For the official v1 registry, if there isn't already an Authorization header in the request, -// but there is an X-Docker-Token header set to true, then Basic Auth will be used to set the Authorization header. -// After sending the request with the provided base http.RoundTripper, if an X-Docker-Token header, representing -// a token, is present in the response, then it gets cached and sent in the Authorization header of all subsequent -// requests. -// -// If the server sends a token without the client having requested it, it is ignored. -// -// This RoundTripper also has a CancelRequest method important for correct timeout handling. -func AuthTransport(base http.RoundTripper, authConfig *types.AuthConfig, alwaysSetBasicAuth bool) http.RoundTripper { - if base == nil { - base = http.DefaultTransport - } - return &authTransport{ - RoundTripper: base, - AuthConfig: authConfig, - alwaysSetBasicAuth: alwaysSetBasicAuth, - modReq: make(map[*http.Request]*http.Request), - } -} - -// cloneRequest returns a clone of the provided *http.Request. -// The clone is a shallow copy of the struct and its Header map. -func cloneRequest(r *http.Request) *http.Request { - // shallow copy of the struct - r2 := new(http.Request) - *r2 = *r - // deep copy of the Header - r2.Header = make(http.Header, len(r.Header)) - for k, s := range r.Header { - r2.Header[k] = append([]string(nil), s...) - } - - return r2 -} - -// RoundTrip changes an 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. - // As the authorization logic is currently implemented in RoundTrip, - // a 302 redirect is detected by looking at the Referrer header as go http package adds said header. - // This is safe as Docker doesn't set Referrer in other scenarios. - if orig.Header.Get("Referer") != "" && !trustedLocation(orig) { - return tr.RoundTripper.RoundTrip(orig) - } - - req := cloneRequest(orig) - tr.mu.Lock() - tr.modReq[orig] = req - tr.mu.Unlock() - - if tr.alwaysSetBasicAuth { - if tr.AuthConfig == nil { - return nil, errors.New("unexpected error: empty auth config") - } - req.SetBasicAuth(tr.Username, tr.Password) - return tr.RoundTripper.RoundTrip(req) - } - - // Don't override - if req.Header.Get("Authorization") == "" { - if req.Header.Get("X-Docker-Token") == "true" && tr.AuthConfig != nil && len(tr.Username) > 0 { - req.SetBasicAuth(tr.Username, tr.Password) - } else if len(tr.token) > 0 { - req.Header.Set("Authorization", "Token "+strings.Join(tr.token, ",")) - } - } - resp, err := tr.RoundTripper.RoundTrip(req) - if err != nil { - delete(tr.modReq, orig) - return nil, err - } - if len(resp.Header["X-Docker-Token"]) > 0 { - tr.token = resp.Header["X-Docker-Token"] - } - resp.Body = &ioutils.OnEOFReader{ - Rc: resp.Body, - Fn: func() { - tr.mu.Lock() - delete(tr.modReq, orig) - tr.mu.Unlock() - }, - } - return resp, nil -} - -// CancelRequest cancels an in-flight request by closing its connection. -func (tr *authTransport) CancelRequest(req *http.Request) { - type canceler interface { - CancelRequest(*http.Request) - } - if cr, ok := tr.RoundTripper.(canceler); ok { - tr.mu.Lock() - modReq := tr.modReq[req] - delete(tr.modReq, req) - tr.mu.Unlock() - cr.CancelRequest(modReq) - } -} - -func authorizeClient(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) error { - var alwaysSetBasicAuth bool - - // If we're working with a standalone private registry over HTTPS, send Basic Auth headers - // alongside all our requests. - if endpoint.String() != IndexServer && endpoint.URL.Scheme == "https" { - info, err := endpoint.Ping() - if err != nil { - return err - } - if info.Standalone && authConfig != nil { - logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", endpoint.String()) - alwaysSetBasicAuth = true - } - } - - // Annotate the transport unconditionally so that v2 can - // properly fallback on v1 when an image is not found. - client.Transport = AuthTransport(client.Transport, authConfig, alwaysSetBasicAuth) - - jar, err := cookiejar.New(nil) - if err != nil { - return errors.New("cookiejar.New is not supposed to return an error") - } - client.Jar = jar - - return nil -} - -func newSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) *Session { - return &Session{ - authConfig: authConfig, - client: client, - indexEndpoint: endpoint, - id: stringid.GenerateRandomID(), - } -} - -// NewSession creates a new session -// TODO(tiborvass): remove authConfig param once registry client v2 is vendored -func NewSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) (*Session, error) { - if err := authorizeClient(client, authConfig, endpoint); err != nil { - return nil, err - } - - return newSession(client, authConfig, endpoint), nil -} - -// ID returns this registry session's ID. -func (r *Session) ID() string { - return r.id -} - -// 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 { - return nil, err - } - defer res.Body.Close() - if res.StatusCode != 200 { - if res.StatusCode == 401 { - return nil, errcode.ErrorCodeUnauthorized.WithArgs() - } - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res) - } - - var history []string - if err := json.NewDecoder(res.Body).Decode(&history); err != nil { - return nil, fmt.Errorf("Error while reading the http response: %v", err) - } - - logrus.Debugf("Ancestry: %v", history) - return history, nil -} - -// 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 { - return err - } - res.Body.Close() - if res.StatusCode != 200 { - return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) - } - return nil -} - -// GetRemoteImageJSON retrieves an image's JSON metadata from the registry. -func (r *Session) GetRemoteImageJSON(imgID, registry string) ([]byte, int64, error) { - res, err := r.client.Get(registry + "images/" + imgID + "/json") - if err != nil { - return nil, -1, fmt.Errorf("Failed to download json: %s", err) - } - defer res.Body.Close() - if res.StatusCode != 200 { - return nil, -1, httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) - } - // if the size header is not present, then set it to '-1' - imageSize := int64(-1) - if hdr := res.Header.Get("X-Docker-Size"); hdr != "" { - imageSize, err = strconv.ParseInt(hdr, 10, 64) - if err != nil { - return nil, -1, err - } - } - - jsonString, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, -1, fmt.Errorf("Failed to parse downloaded json: %v (%s)", err, jsonString) - } - 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 ( - statusCode = 0 - res *http.Response - err error - imageURL = fmt.Sprintf("%simages/%s/layer", registry, imgID) - ) - - req, err := http.NewRequest("GET", imageURL, nil) - if err != nil { - return nil, fmt.Errorf("Error while getting from the server: %v", err) - } - statusCode = 0 - res, err = r.client.Do(req) - if err != nil { - logrus.Debugf("Error contacting registry %s: %v", registry, err) - // the only case err != nil && res != nil is https://golang.org/src/net/http/client.go#L515 - if res != nil { - if res.Body != nil { - res.Body.Close() - } - statusCode = res.StatusCode - } - return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)", - statusCode, imgID) - } - - if res.StatusCode != 200 { - res.Body.Close() - return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)", - res.StatusCode, imgID) - } - - if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 { - logrus.Debug("server supports resume") - return httputils.ResumableRequestReaderWithInitialResponse(r.client, req, 5, imgSize, res), nil - } - logrus.Debug("server doesn't support resume") - 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, repositoryRef reference.Named, askedTag string) (string, error) { - repository := repositoryRef.RemoteName() - - if strings.Count(repository, "/") == 0 { - // This will be removed once the registry supports auto-resolution on - // the "library" namespace - repository = "library/" + repository - } - for _, host := range registries { - endpoint := fmt.Sprintf("%srepositories/%s/tags/%s", host, repository, askedTag) - res, err := r.client.Get(endpoint) - if err != nil { - return "", err - } - - logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint) - defer res.Body.Close() - - if res.StatusCode == 404 { - return "", ErrRepoNotFound - } - if res.StatusCode != 200 { - continue - } - - var tagID string - if err := json.NewDecoder(res.Body).Decode(&tagID); err != nil { - return "", err - } - 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, repositoryRef reference.Named) (map[string]string, error) { - repository := repositoryRef.RemoteName() - - if strings.Count(repository, "/") == 0 { - // This will be removed once the registry supports auto-resolution on - // the "library" namespace - repository = "library/" + repository - } - for _, host := range registries { - endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository) - res, err := r.client.Get(endpoint) - if err != nil { - return nil, err - } - - logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint) - defer res.Body.Close() - - if res.StatusCode == 404 { - return nil, ErrRepoNotFound - } - if res.StatusCode != 200 { - continue - } - - result := make(map[string]string) - if err := json.NewDecoder(res.Body).Decode(&result); err != nil { - return nil, err - } - return result, nil - } - return nil, fmt.Errorf("Could not reach any registry endpoint") -} - -func buildEndpointsList(headers []string, indexEp string) ([]string, error) { - var endpoints []string - parsedURL, err := url.Parse(indexEp) - if err != nil { - return nil, err - } - var urlScheme = parsedURL.Scheme - // The registry's URL scheme has to match the Index' - for _, ep := range headers { - epList := strings.Split(ep, ",") - for _, epListElement := range epList { - endpoints = append( - endpoints, - fmt.Sprintf("%s://%s/v1/", urlScheme, strings.TrimSpace(epListElement))) - } - } - return endpoints, nil -} - -// GetRepositoryData returns lists of images and endpoints for the repository -func (r *Session) GetRepositoryData(name reference.Named) (*RepositoryData, error) { - repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.String(), name.RemoteName()) - - logrus.Debugf("[registry] Calling GET %s", repositoryTarget) - - req, err := http.NewRequest("GET", repositoryTarget, nil) - if err != nil { - return nil, err - } - // this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests - req.Header.Set("X-Docker-Token", "true") - res, err := r.client.Do(req) - if err != nil { - // check if the error is because of i/o timeout - // and return a non-obtuse error message for users - // "Get https://index.docker.io/v1/repositories/library/busybox/images: i/o timeout" - // was a top search on the docker user forum - if isTimeout(err) { - return nil, fmt.Errorf("Network timed out while trying to connect to %s. You may want to check your internet connection or if you are behind a proxy.", repositoryTarget) - } - return nil, fmt.Errorf("Error while pulling image: %v", err) - } - defer res.Body.Close() - if res.StatusCode == 401 { - return nil, errcode.ErrorCodeUnauthorized.WithArgs() - } - // TODO: Right now we're ignoring checksums in the response body. - // In the future, we need to use them to check image validity. - if res.StatusCode == 404 { - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res) - } else if res.StatusCode != 200 { - errBody, err := ioutil.ReadAll(res.Body) - if err != nil { - logrus.Debugf("Error reading response body: %s", err) - } - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, name.RemoteName(), errBody), res) - } - - var endpoints []string - if res.Header.Get("X-Docker-Endpoints") != "" { - endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.String()) - if err != nil { - return nil, err - } - } else { - // Assume the endpoint is on the same host - endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", r.indexEndpoint.URL.Scheme, req.URL.Host)) - } - - remoteChecksums := []*ImgData{} - if err := json.NewDecoder(res.Body).Decode(&remoteChecksums); err != nil { - return nil, err - } - - // Forge a better object from the retrieved data - imgsData := make(map[string]*ImgData, len(remoteChecksums)) - for _, elem := range remoteChecksums { - imgsData[elem.ID] = elem - } - - return &RepositoryData{ - ImgList: imgsData, - Endpoints: endpoints, - }, 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) - - req, err := http.NewRequest("PUT", u, nil) - if err != nil { - return err - } - req.Header.Set("X-Docker-Checksum", imgData.Checksum) - req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload) - - res, err := r.client.Do(req) - if err != nil { - return fmt.Errorf("Failed to upload metadata: %v", err) - } - defer res.Body.Close() - if len(res.Cookies()) > 0 { - r.client.Jar.SetCookies(req.URL, res.Cookies()) - } - if res.StatusCode != 200 { - errBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err) - } - var jsonBody map[string]string - if err := json.Unmarshal(errBody, &jsonBody); err != nil { - errBody = []byte(err.Error()) - } else if jsonBody["error"] == "Image already exists" { - return ErrAlreadyExists - } - return fmt.Errorf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody) - } - return nil -} - -// 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" - - logrus.Debugf("[registry] Calling PUT %s", u) - - req, err := http.NewRequest("PUT", u, bytes.NewReader(jsonRaw)) - if err != nil { - return err - } - req.Header.Add("Content-type", "application/json") - - res, err := r.client.Do(req) - if err != nil { - return fmt.Errorf("Failed to upload metadata: %s", err) - } - defer res.Body.Close() - if res.StatusCode == 401 && strings.HasPrefix(registry, "http://") { - return httputils.NewHTTPRequestError("HTTP code 401, Docker will not send auth headers over HTTP.", res) - } - if res.StatusCode != 200 { - errBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) - } - var jsonBody map[string]string - if err := json.Unmarshal(errBody, &jsonBody); err != nil { - errBody = []byte(err.Error()) - } else if jsonBody["error"] == "Image already exists" { - return ErrAlreadyExists - } - return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody), res) - } - 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) - - tarsumLayer, err := tarsum.NewTarSum(layer, false, tarsum.Version0) - if err != nil { - return "", "", err - } - h := sha256.New() - h.Write(jsonRaw) - h.Write([]byte{'\n'}) - checksumLayer := io.TeeReader(tarsumLayer, h) - - req, err := http.NewRequest("PUT", u, checksumLayer) - if err != nil { - return "", "", err - } - req.Header.Add("Content-Type", "application/octet-stream") - req.ContentLength = -1 - req.TransferEncoding = []string{"chunked"} - res, err := r.client.Do(req) - if err != nil { - return "", "", fmt.Errorf("Failed to upload layer: %v", err) - } - if rc, ok := layer.(io.Closer); ok { - if err := rc.Close(); err != nil { - return "", "", err - } - } - defer res.Body.Close() - - if res.StatusCode != 200 { - errBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return "", "", httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) - } - return "", "", httputils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %q", res.StatusCode, errBody), res) - } - - checksumPayload = "sha256:" + hex.EncodeToString(h.Sum(nil)) - return tarsumLayer.Sum(jsonRaw), checksumPayload, nil -} - -// PushRegistryTag pushes a tag on the registry. -// Remote has the format '/ -func (r *Session) PushRegistryTag(remote reference.Named, revision, tag, registry string) error { - // "jsonify" the string - revision = "\"" + revision + "\"" - path := fmt.Sprintf("repositories/%s/tags/%s", remote.RemoteName(), tag) - - req, err := http.NewRequest("PUT", registry+path, strings.NewReader(revision)) - if err != nil { - return err - } - req.Header.Add("Content-type", "application/json") - req.ContentLength = int64(len(revision)) - res, err := r.client.Do(req) - if err != nil { - return err - } - res.Body.Close() - if res.StatusCode != 200 && res.StatusCode != 201 { - return httputils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote.RemoteName()), res) - } - return nil -} - -// PushImageJSONIndex uploads an image list to the repository -func (r *Session) PushImageJSONIndex(remote reference.Named, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) { - cleanImgList := []*ImgData{} - if validate { - for _, elem := range imgList { - if elem.Checksum != "" { - cleanImgList = append(cleanImgList, elem) - } - } - } else { - cleanImgList = imgList - } - - imgListJSON, err := json.Marshal(cleanImgList) - if err != nil { - return nil, err - } - var suffix string - if validate { - suffix = "images" - } - u := fmt.Sprintf("%srepositories/%s/%s", r.indexEndpoint.String(), remote.RemoteName(), suffix) - logrus.Debugf("[registry] PUT %s", u) - logrus.Debugf("Image list pushed to index:\n%s", imgListJSON) - headers := map[string][]string{ - "Content-type": {"application/json"}, - // this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests - "X-Docker-Token": {"true"}, - } - if validate { - headers["X-Docker-Endpoints"] = regs - } - - // Redirect if necessary - var res *http.Response - for { - if res, err = r.putImageRequest(u, headers, imgListJSON); err != nil { - return nil, err - } - if !shouldRedirect(res) { - break - } - res.Body.Close() - u = res.Header.Get("Location") - logrus.Debugf("Redirected to %s", u) - } - defer res.Body.Close() - - if res.StatusCode == 401 { - return nil, errcode.ErrorCodeUnauthorized.WithArgs() - } - - var tokens, endpoints []string - if !validate { - if res.StatusCode != 200 && res.StatusCode != 201 { - errBody, err := ioutil.ReadAll(res.Body) - if err != nil { - logrus.Debugf("Error reading response body: %s", err) - } - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote.RemoteName(), errBody), res) - } - tokens = res.Header["X-Docker-Token"] - logrus.Debugf("Auth token: %v", tokens) - - if res.Header.Get("X-Docker-Endpoints") == "" { - return nil, fmt.Errorf("Index response didn't contain any endpoints") - } - endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.String()) - if err != nil { - return nil, err - } - } else { - if res.StatusCode != 204 { - errBody, err := ioutil.ReadAll(res.Body) - if err != nil { - logrus.Debugf("Error reading response body: %s", err) - } - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote.RemoteName(), errBody), res) - } - } - - return &RepositoryData{ - Endpoints: endpoints, - }, nil -} - -func (r *Session) putImageRequest(u string, headers map[string][]string, body []byte) (*http.Response, error) { - req, err := http.NewRequest("PUT", u, bytes.NewReader(body)) - if err != nil { - return nil, err - } - req.ContentLength = int64(len(body)) - for k, v := range headers { - req.Header[k] = v - } - response, err := r.client.Do(req) - if err != nil { - return nil, err - } - return response, nil -} - -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, limit int) (*registrytypes.SearchResults, error) { - if limit < 1 || limit > 100 { - return nil, fmt.Errorf("Limit %d is outside the range of [1, 100]", limit) - } - logrus.Debugf("Index server: %s", r.indexEndpoint) - u := r.indexEndpoint.String() + "search?q=" + url.QueryEscape(term) + "&n=" + url.QueryEscape(fmt.Sprintf("%d", limit)) - - req, err := http.NewRequest("GET", u, nil) - if err != nil { - return nil, fmt.Errorf("Error while getting from the server: %v", err) - } - // Have the AuthTransport send authentication, when logged in. - req.Header.Set("X-Docker-Token", "true") - res, err := r.client.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - if res.StatusCode != 200 { - return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res) - } - result := new(registrytypes.SearchResults) - 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) *types.AuthConfig { - password := "" - if withPasswd { - password = r.authConfig.Password - } - return &types.AuthConfig{ - Username: r.authConfig.Username, - Password: password, - } -} - -func isTimeout(err error) bool { - type timeout interface { - Timeout() bool - } - e := err - switch urlErr := err.(type) { - case *url.Error: - e = urlErr.Err - } - t, ok := e.(timeout) - return ok && t.Timeout() -} diff --git a/docs/spec/api.md b/docs/spec/api.md index c4517c0b..fd745a5b 100644 --- a/docs/spec/api.md +++ b/docs/spec/api.md @@ -1,12 +1,12 @@ - +--- +description: Specification for the Registry API. +keywords: +- registry, on-prem, images, tags, repository, distribution, api, advanced +menu: + main: + parent: smn_registry_ref +title: HTTP API V2 +--- # Docker Registry HTTP API V2 diff --git a/docs/spec/api.md.tmpl b/docs/spec/api.md.tmpl index eeafec1e..c44418f4 100644 --- a/docs/spec/api.md.tmpl +++ b/docs/spec/api.md.tmpl @@ -1,12 +1,12 @@ - +--- +description: Specification for the Registry API. +keywords: +- registry, on-prem, images, tags, repository, distribution, api, advanced +menu: + main: + parent: smn_registry_ref +title: HTTP API V2 +--- # Docker Registry HTTP API V2 diff --git a/docs/spec/auth/index.md b/docs/spec/auth/index.md index f6ee8e1f..6b539f0e 100644 --- a/docs/spec/auth/index.md +++ b/docs/spec/auth/index.md @@ -1,13 +1,13 @@ - +--- +description: Docker Registry v2 authentication schema +keywords: +- registry, on-prem, images, tags, repository, distribution, authentication, advanced +menu: + main: + parent: smn_registry_ref + weight: 100 +title: Docker Registry Token Authentication +--- # Docker Registry v2 authentication diff --git a/docs/spec/auth/jwt.md b/docs/spec/auth/jwt.md index c90bd6e8..e0a2e641 100644 --- a/docs/spec/auth/jwt.md +++ b/docs/spec/auth/jwt.md @@ -1,13 +1,14 @@ - +--- +description: Describe the reference implementation of the Docker Registry v2 authentication + schema +keywords: +- registry, on-prem, images, tags, repository, distribution, JWT authentication, advanced +menu: + main: + parent: smn_registry_ref + weight: 101 +title: Token Authentication Implementation +--- # Docker Registry v2 Bearer token specification diff --git a/docs/spec/auth/oauth.md b/docs/spec/auth/oauth.md index 3d1ae0aa..ce0bcc49 100644 --- a/docs/spec/auth/oauth.md +++ b/docs/spec/auth/oauth.md @@ -1,13 +1,13 @@ - +--- +description: Specifies the Docker Registry v2 authentication +keywords: +- registry, on-prem, images, tags, repository, distribution, oauth2, advanced +menu: + main: + parent: smn_registry_ref + weight: 102 +title: Oauth2 Token Authentication +--- # Docker Registry v2 authentication using OAuth2 diff --git a/docs/spec/auth/scope.md b/docs/spec/auth/scope.md index a8f6c062..8cd8699e 100644 --- a/docs/spec/auth/scope.md +++ b/docs/spec/auth/scope.md @@ -1,13 +1,14 @@ - +--- +description: Describes the scope and access fields used for registry authorization + tokens +keywords: +- registry, on-prem, images, tags, repository, distribution, advanced, access, scope +menu: + main: + parent: smn_registry_ref + weight: 103 +title: Token Scope Documentation +--- # Docker Registry Token Scope and Access diff --git a/docs/spec/auth/token.md b/docs/spec/auth/token.md index 81af53b2..fa49357e 100644 --- a/docs/spec/auth/token.md +++ b/docs/spec/auth/token.md @@ -1,13 +1,14 @@ - +--- +description: Specifies the Docker Registry v2 authentication +keywords: +- registry, on-prem, images, tags, repository, distribution, Bearer authentication, + advanced +menu: + main: + parent: smn_registry_ref + weight: 104 +title: Token Authentication Specification +--- # Docker Registry v2 authentication via central service diff --git a/docs/spec/implementations.md b/docs/spec/implementations.md index ec937b64..a365db6c 100644 --- a/docs/spec/implementations.md +++ b/docs/spec/implementations.md @@ -1,8 +1,6 @@ - +--- +draft: true +--- # Distribution API Implementations diff --git a/docs/spec/index.md b/docs/spec/index.md index 474bd455..7ad0aaea 100644 --- a/docs/spec/index.md +++ b/docs/spec/index.md @@ -1,13 +1,13 @@ - +--- +description: Explains registry JSON objects +keywords: +- registry, service, images, repository, json +menu: + main: + parent: smn_registry_ref + weight: -1 +title: Reference Overview +--- # Docker Registry Reference diff --git a/docs/spec/json.md b/docs/spec/json.md index a8916dcc..8e149a34 100644 --- a/docs/spec/json.md +++ b/docs/spec/json.md @@ -1,15 +1,13 @@ - - - +--- +description: Explains registry JSON objects +draft: true +keywords: +- registry, service, images, repository, json +menu: + main: + parent: smn_registry_ref +title: Docker Distribution JSON Canonicalization +--- # Docker Distribution JSON Canonicalization diff --git a/docs/spec/manifest-v2-1.md b/docs/spec/manifest-v2-1.md index 056f4bc6..3162f3f8 100644 --- a/docs/spec/manifest-v2-1.md +++ b/docs/spec/manifest-v2-1.md @@ -1,12 +1,12 @@ - +--- +description: image manifest for the Registry. +keywords: +- registry, on-prem, images, tags, repository, distribution, api, advanced, manifest +menu: + main: + parent: smn_registry_ref +title: 'Image Manifest V 2, Schema 1 ' +--- # Image Manifest Version 2, Schema 1 diff --git a/docs/spec/manifest-v2-2.md b/docs/spec/manifest-v2-2.md index fc705639..469e7017 100644 --- a/docs/spec/manifest-v2-2.md +++ b/docs/spec/manifest-v2-2.md @@ -1,12 +1,12 @@ - +--- +description: image manifest for the Registry. +keywords: +- registry, on-prem, images, tags, repository, distribution, api, advanced, manifest +menu: + main: + parent: smn_registry_ref +title: 'Image Manifest V 2, Schema 2 ' +--- # Image Manifest Version 2, Schema 2 diff --git a/docs/spec/menu.md b/docs/spec/menu.md index ebc52327..0e39f6b7 100644 --- a/docs/spec/menu.md +++ b/docs/spec/menu.md @@ -1,13 +1,15 @@ - diff --git a/docs/storage-drivers/azure.md b/docs/storage-drivers/azure.md index a84888de..64e476e4 100644 --- a/docs/storage-drivers/azure.md +++ b/docs/storage-drivers/azure.md @@ -1,13 +1,12 @@ - - +--- +description: Explains how to use the Azure storage drivers +keywords: +- registry, service, driver, images, storage, azure +menu: + main: + parent: smn_storagedrivers +title: Microsoft Azure storage driver +--- # Microsoft Azure storage driver diff --git a/docs/storage-drivers/filesystem.md b/docs/storage-drivers/filesystem.md index 8e269cdb..2c7f6628 100644 --- a/docs/storage-drivers/filesystem.md +++ b/docs/storage-drivers/filesystem.md @@ -1,13 +1,12 @@ - - +--- +description: Explains how to use the filesystem storage drivers +keywords: +- registry, service, driver, images, storage, filesystem +menu: + main: + parent: smn_storagedrivers +title: Filesystem storage driver +--- # Filesystem storage driver diff --git a/docs/storage-drivers/gcs.md b/docs/storage-drivers/gcs.md index 1bc67f9e..4c8a7c88 100644 --- a/docs/storage-drivers/gcs.md +++ b/docs/storage-drivers/gcs.md @@ -1,13 +1,12 @@ - - +--- +description: Explains how to use the Google Cloud Storage drivers +keywords: +- registry, service, driver, images, storage, gcs, google, cloud +menu: + main: + parent: smn_storagedrivers +title: GCS storage driver +--- # Google Cloud Storage driver diff --git a/docs/storage-drivers/index.md b/docs/storage-drivers/index.md index 89635bd3..1c9fbe9d 100644 --- a/docs/storage-drivers/index.md +++ b/docs/storage-drivers/index.md @@ -1,16 +1,16 @@ - - +--- +aliases: +- /registry/storagedrivers/ +description: Explains how to use storage drivers +keywords: +- registry, on-prem, images, tags, repository, distribution, storage drivers, advanced +menu: + main: + identifier: storage_index + parent: smn_storagedrivers + weight: -1 +title: Storage Driver overview +--- # Docker Registry Storage Driver diff --git a/docs/storage-drivers/inmemory.md b/docs/storage-drivers/inmemory.md index 1a14e77a..6fbed6aa 100644 --- a/docs/storage-drivers/inmemory.md +++ b/docs/storage-drivers/inmemory.md @@ -1,13 +1,12 @@ - - +--- +description: Explains how to use the in-memory storage drivers +keywords: +- registry, service, driver, images, storage, in-memory +menu: + main: + parent: smn_storagedrivers +title: In-memory storage driver +--- # In-memory storage driver (Testing Only) diff --git a/docs/storage-drivers/menu.md b/docs/storage-drivers/menu.md index 3638649f..c58f57de 100644 --- a/docs/storage-drivers/menu.md +++ b/docs/storage-drivers/menu.md @@ -1,13 +1,15 @@ - diff --git a/docs/storage-drivers/oss.md b/docs/storage-drivers/oss.md index a85e315e..44109003 100644 --- a/docs/storage-drivers/oss.md +++ b/docs/storage-drivers/oss.md @@ -1,12 +1,12 @@ - +--- +description: Explains how to use the Aliyun OSS storage driver +keywords: +- registry, service, driver, images, storage, OSS, aliyun +menu: + main: + parent: smn_storagedrivers +title: Aliyun OSS storage driver +--- # Aliyun OSS storage driver diff --git a/docs/storage-drivers/s3.md b/docs/storage-drivers/s3.md index 97cfbfc1..7eef2ee0 100644 --- a/docs/storage-drivers/s3.md +++ b/docs/storage-drivers/s3.md @@ -1,13 +1,12 @@ - - +--- +description: Explains how to use the S3 storage drivers +keywords: +- registry, service, driver, images, storage, S3 +menu: + main: + parent: smn_storagedrivers +title: S3 storage driver +--- # S3 storage driver diff --git a/docs/storage-drivers/swift.md b/docs/storage-drivers/swift.md index b1a0c932..eaa80511 100644 --- a/docs/storage-drivers/swift.md +++ b/docs/storage-drivers/swift.md @@ -1,13 +1,12 @@ - - +--- +description: Explains how to use the OpenStack swift storage driver +keywords: +- registry, service, driver, images, storage, swift +menu: + main: + parent: smn_storagedrivers +title: Swift storage driver +--- # OpenStack Swift storage driver diff --git a/docs/types.go b/docs/types.go deleted file mode 100644 index 601fa09e..00000000 --- a/docs/types.go +++ /dev/null @@ -1,70 +0,0 @@ -package registry - -import ( - "github.com/docker/docker/reference" - registrytypes "github.com/docker/engine-api/types/registry" -) - -// RepositoryData tracks the image list, list of endpoints, and list of tokens -// for a repository -type RepositoryData struct { - // 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 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"` -} - -// 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 an 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 { - return apiVersions[av] -} - -// API Version identifiers. -const ( - _ = iota - APIVersion1 APIVersion = iota - APIVersion2 -) - -var apiVersions = map[APIVersion]string{ - APIVersion1: "v1", - APIVersion2: "v2", -} - -// RepositoryInfo describes a repository -type RepositoryInfo struct { - reference.Named - // Index points to registry information - Index *registrytypes.IndexInfo - // 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 -}