* Add godoc documentation where it was missing
* Change identifier names that don't match Go style, such as INDEX_NAME
* Rename RegistryInfo to PingResult, which more accurately describes
what this structure is for. It also has the benefit of making the name
not stutter if used outside the package.
Updates #14756
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
| ... | ... |
@@ -22,7 +22,7 @@ import ( |
| 22 | 22 |
// |
| 23 | 23 |
// Usage: docker login SERVER |
| 24 | 24 |
func (cli *DockerCli) CmdLogin(args ...string) error {
|
| 25 |
- cmd := Cli.Subcmd("login", []string{"[SERVER]"}, "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.INDEXSERVER+"\" is the default.", true)
|
|
| 25 |
+ cmd := Cli.Subcmd("login", []string{"[SERVER]"}, "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.IndexServer+"\" is the default.", true)
|
|
| 26 | 26 |
cmd.Require(flag.Max, 1) |
| 27 | 27 |
|
| 28 | 28 |
var username, password, email string |
| ... | ... |
@@ -33,7 +33,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
| 33 | 33 |
|
| 34 | 34 |
cmd.ParseFlags(args, true) |
| 35 | 35 |
|
| 36 |
- serverAddress := registry.INDEXSERVER |
|
| 36 |
+ serverAddress := registry.IndexServer |
|
| 37 | 37 |
if len(cmd.Args()) > 0 {
|
| 38 | 38 |
serverAddress = cmd.Arg(0) |
| 39 | 39 |
} |
| ... | ... |
@@ -14,12 +14,12 @@ import ( |
| 14 | 14 |
// |
| 15 | 15 |
// Usage: docker logout [SERVER] |
| 16 | 16 |
func (cli *DockerCli) CmdLogout(args ...string) error {
|
| 17 |
- cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, "Log out from a Docker registry, if no server is\nspecified \""+registry.INDEXSERVER+"\" is the default.", true)
|
|
| 17 |
+ cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServer+"\" is the default.", true)
|
|
| 18 | 18 |
cmd.Require(flag.Max, 1) |
| 19 | 19 |
|
| 20 | 20 |
cmd.ParseFlags(args, true) |
| 21 | 21 |
|
| 22 |
- serverAddress := registry.INDEXSERVER |
|
| 22 |
+ serverAddress := registry.IndexServer |
|
| 23 | 23 |
if len(cmd.Args()) > 0 {
|
| 24 | 24 |
serverAddress = cmd.Arg(0) |
| 25 | 25 |
} |
| ... | ... |
@@ -74,7 +74,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
|
| 74 | 74 |
NEventsListener: daemon.EventsService.SubscribersCount(), |
| 75 | 75 |
KernelVersion: kernelVersion, |
| 76 | 76 |
OperatingSystem: operatingSystem, |
| 77 |
- IndexServerAddress: registry.INDEXSERVER, |
|
| 77 |
+ IndexServerAddress: registry.IndexServer, |
|
| 78 | 78 |
RegistryConfig: daemon.RegistryService.Config, |
| 79 | 79 |
InitSha1: dockerversion.INITSHA1, |
| 80 | 80 |
InitPath: initPath, |
| ... | ... |
@@ -33,7 +33,7 @@ func (p *v1Puller) Pull(tag string) (fallback bool, err error) {
|
| 33 | 33 |
return true, registry.ErrNoSupport{errors.New("Cannot pull by digest with v1 registry")}
|
| 34 | 34 |
} |
| 35 | 35 |
|
| 36 |
- tlsConfig, err := p.registryService.TlsConfig(p.repoInfo.Index.Name) |
|
| 36 |
+ tlsConfig, err := p.registryService.TLSConfig(p.repoInfo.Index.Name) |
|
| 37 | 37 |
if err != nil {
|
| 38 | 38 |
return false, err |
| 39 | 39 |
} |
| ... | ... |
@@ -29,7 +29,7 @@ type v1Pusher struct {
|
| 29 | 29 |
} |
| 30 | 30 |
|
| 31 | 31 |
func (p *v1Pusher) Push() (fallback bool, err error) {
|
| 32 |
- tlsConfig, err := p.registryService.TlsConfig(p.repoInfo.Index.Name) |
|
| 32 |
+ tlsConfig, err := p.registryService.TLSConfig(p.repoInfo.Index.Name) |
|
| 33 | 33 |
if err != nil {
|
| 34 | 34 |
return false, err |
| 35 | 35 |
} |
| ... | ... |
@@ -36,7 +36,7 @@ func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (stri |
| 36 | 36 |
return "", fmt.Errorf("Server Error: Server Address not set.")
|
| 37 | 37 |
} |
| 38 | 38 |
|
| 39 |
- loginAgainstOfficialIndex := serverAddress == INDEXSERVER |
|
| 39 |
+ loginAgainstOfficialIndex := serverAddress == IndexServer |
|
| 40 | 40 |
|
| 41 | 41 |
// to avoid sending the server address to the server it should be removed before being marshalled |
| 42 | 42 |
authCopy := *authConfig |
| ... | ... |
@@ -220,7 +220,7 @@ func tryV2TokenAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]str |
| 220 | 220 |
return nil |
| 221 | 221 |
} |
| 222 | 222 |
|
| 223 |
-// this method matches a auth configuration to a server address or a url |
|
| 223 |
+// ResolveAuthConfig matches an auth configuration to a server address or a URL |
|
| 224 | 224 |
func ResolveAuthConfig(config *cliconfig.ConfigFile, index *IndexInfo) cliconfig.AuthConfig {
|
| 225 | 225 |
configKey := index.GetAuthConfigKey() |
| 226 | 226 |
// First try the happy case |
| ... | ... |
@@ -37,7 +37,7 @@ func setupTempConfigFile() (*cliconfig.ConfigFile, error) {
|
| 37 | 37 |
root = filepath.Join(root, cliconfig.ConfigFileName) |
| 38 | 38 |
configFile := cliconfig.NewConfigFile(root) |
| 39 | 39 |
|
| 40 |
- for _, registry := range []string{"testIndex", INDEXSERVER} {
|
|
| 40 |
+ for _, registry := range []string{"testIndex", IndexServer} {
|
|
| 41 | 41 |
configFile.AuthConfigs[registry] = cliconfig.AuthConfig{
|
| 42 | 42 |
Username: "docker-user", |
| 43 | 43 |
Password: "docker-pass", |
| ... | ... |
@@ -82,7 +82,7 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
|
| 82 | 82 |
} |
| 83 | 83 |
defer os.RemoveAll(configFile.Filename()) |
| 84 | 84 |
|
| 85 |
- indexConfig := configFile.AuthConfigs[INDEXSERVER] |
|
| 85 |
+ indexConfig := configFile.AuthConfigs[IndexServer] |
|
| 86 | 86 |
|
| 87 | 87 |
officialIndex := &IndexInfo{
|
| 88 | 88 |
Official: true, |
| ... | ... |
@@ -92,10 +92,10 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
|
| 92 | 92 |
} |
| 93 | 93 |
|
| 94 | 94 |
resolved := ResolveAuthConfig(configFile, officialIndex) |
| 95 |
- assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return INDEXSERVER") |
|
| 95 |
+ assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServer") |
|
| 96 | 96 |
|
| 97 | 97 |
resolved = ResolveAuthConfig(configFile, privateIndex) |
| 98 |
- assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return INDEXSERVER") |
|
| 98 |
+ assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServer") |
|
| 99 | 99 |
} |
| 100 | 100 |
|
| 101 | 101 |
func TestResolveAuthConfigFullURL(t *testing.T) {
|
| ... | ... |
@@ -120,7 +120,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
|
| 120 | 120 |
Password: "baz-pass", |
| 121 | 121 |
Email: "baz@example.com", |
| 122 | 122 |
} |
| 123 |
- configFile.AuthConfigs[INDEXSERVER] = officialAuth |
|
| 123 |
+ configFile.AuthConfigs[IndexServer] = officialAuth |
|
| 124 | 124 |
|
| 125 | 125 |
expectedAuths := map[string]cliconfig.AuthConfig{
|
| 126 | 126 |
"registry.example.com": registryAuth, |
| ... | ... |
@@ -21,24 +21,33 @@ type Options struct {
|
| 21 | 21 |
} |
| 22 | 22 |
|
| 23 | 23 |
const ( |
| 24 |
- DEFAULT_NAMESPACE = "docker.io" |
|
| 25 |
- DEFAULT_V2_REGISTRY = "https://registry-1.docker.io" |
|
| 26 |
- DEFAULT_REGISTRY_VERSION_HEADER = "Docker-Distribution-Api-Version" |
|
| 27 |
- DEFAULT_V1_REGISTRY = "https://index.docker.io" |
|
| 28 |
- |
|
| 29 |
- CERTS_DIR = "/etc/docker/certs.d" |
|
| 30 |
- |
|
| 31 |
- // Only used for user auth + account creation |
|
| 32 |
- REGISTRYSERVER = DEFAULT_V2_REGISTRY |
|
| 33 |
- INDEXSERVER = DEFAULT_V1_REGISTRY + "/v1/" |
|
| 34 |
- INDEXNAME = "docker.io" |
|
| 35 |
- |
|
| 36 |
- // INDEXSERVER = "https://registry-stage.hub.docker.com/v1/" |
|
| 24 |
+ // DefaultNamespace is the default namespace |
|
| 25 |
+ DefaultNamespace = "docker.io" |
|
| 26 |
+ // DefaultV2Registry is the URI of the default v2 registry |
|
| 27 |
+ DefaultV2Registry = "https://registry-1.docker.io" |
|
| 28 |
+ // DefaultRegistryVersionHeader is the name of the default HTTP header |
|
| 29 |
+ // that carries Registry version info |
|
| 30 |
+ DefaultRegistryVersionHeader = "Docker-Distribution-Api-Version" |
|
| 31 |
+ // DefaultV1Registry is the URI of the default v1 registry |
|
| 32 |
+ DefaultV1Registry = "https://index.docker.io" |
|
| 33 |
+ |
|
| 34 |
+ // CertsDir is the directory where certificates are stored |
|
| 35 |
+ CertsDir = "/etc/docker/certs.d" |
|
| 36 |
+ |
|
| 37 |
+ // IndexServer is the v1 registry server used for user auth + account creation |
|
| 38 |
+ IndexServer = DefaultV1Registry + "/v1/" |
|
| 39 |
+ // IndexName is the name of the index |
|
| 40 |
+ IndexName = "docker.io" |
|
| 41 |
+ |
|
| 42 |
+ // IndexServer = "https://registry-stage.hub.docker.com/v1/" |
|
| 37 | 43 |
) |
| 38 | 44 |
|
| 39 | 45 |
var ( |
| 46 |
+ // ErrInvalidRepositoryName is an error returned if the repository name did |
|
| 47 |
+ // not have the correct form |
|
| 40 | 48 |
ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
|
| 41 |
- emptyServiceConfig = NewServiceConfig(nil) |
|
| 49 |
+ |
|
| 50 |
+ emptyServiceConfig = NewServiceConfig(nil) |
|
| 42 | 51 |
) |
| 43 | 52 |
|
| 44 | 53 |
// InstallFlags adds command-line options to the top-level flag parser for |
| ... | ... |
@@ -116,8 +125,8 @@ func NewServiceConfig(options *Options) *ServiceConfig {
|
| 116 | 116 |
} |
| 117 | 117 |
|
| 118 | 118 |
// Configure public registry. |
| 119 |
- config.IndexConfigs[INDEXNAME] = &IndexInfo{
|
|
| 120 |
- Name: INDEXNAME, |
|
| 119 |
+ config.IndexConfigs[IndexName] = &IndexInfo{
|
|
| 120 |
+ Name: IndexName, |
|
| 121 | 121 |
Mirrors: config.Mirrors, |
| 122 | 122 |
Secure: true, |
| 123 | 123 |
Official: true, |
| ... | ... |
@@ -196,8 +205,8 @@ func ValidateMirror(val string) (string, error) {
|
| 196 | 196 |
// ValidateIndexName validates an index name. |
| 197 | 197 |
func ValidateIndexName(val string) (string, error) {
|
| 198 | 198 |
// 'index.docker.io' => 'docker.io' |
| 199 |
- if val == "index."+INDEXNAME {
|
|
| 200 |
- val = INDEXNAME |
|
| 199 |
+ if val == "index."+IndexName {
|
|
| 200 |
+ val = IndexName |
|
| 201 | 201 |
} |
| 202 | 202 |
if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
|
| 203 | 203 |
return "", fmt.Errorf("Invalid index name (%s). Cannot begin or end with a hyphen.", val)
|
| ... | ... |
@@ -267,7 +276,7 @@ func (config *ServiceConfig) NewIndexInfo(indexName string) (*IndexInfo, error) |
| 267 | 267 |
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes. |
| 268 | 268 |
func (index *IndexInfo) GetAuthConfigKey() string {
|
| 269 | 269 |
if index.Official {
|
| 270 |
- return INDEXSERVER |
|
| 270 |
+ return IndexServer |
|
| 271 | 271 |
} |
| 272 | 272 |
return index.Name |
| 273 | 273 |
} |
| ... | ... |
@@ -280,7 +289,7 @@ func splitReposName(reposName string) (string, string) {
|
| 280 | 280 |
!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
|
| 281 | 281 |
// This is a Docker Index repos (ex: samalba/hipache or ubuntu) |
| 282 | 282 |
// 'docker.io' |
| 283 |
- indexName = INDEXNAME |
|
| 283 |
+ indexName = IndexName |
|
| 284 | 284 |
remoteName = reposName |
| 285 | 285 |
} else {
|
| 286 | 286 |
indexName = nameParts[0] |
| ... | ... |
@@ -111,6 +111,7 @@ func newEndpoint(address string, tlsConfig *tls.Config, metaHeaders http.Header) |
| 111 | 111 |
return endpoint, nil |
| 112 | 112 |
} |
| 113 | 113 |
|
| 114 |
+// GetEndpoint returns a new endpoint with the specified headers |
|
| 114 | 115 |
func (repoInfo *RepositoryInfo) GetEndpoint(metaHeaders http.Header) (*Endpoint, error) {
|
| 115 | 116 |
return NewEndpoint(repoInfo.Index, metaHeaders) |
| 116 | 117 |
} |
| ... | ... |
@@ -142,7 +143,10 @@ func (e *Endpoint) Path(path string) string {
|
| 142 | 142 |
return fmt.Sprintf("%s/v%d/%s", e.URL, e.Version, path)
|
| 143 | 143 |
} |
| 144 | 144 |
|
| 145 |
-func (e *Endpoint) Ping() (RegistryInfo, error) {
|
|
| 145 |
+// Ping pings the remote endpoint with v2 and v1 pings to determine the API |
|
| 146 |
+// version. It returns a PingResult containing the discovered version. The |
|
| 147 |
+// PingResult also indicates whether the registry is standalone or not. |
|
| 148 |
+func (e *Endpoint) Ping() (PingResult, error) {
|
|
| 146 | 149 |
// The ping logic to use is determined by the registry endpoint version. |
| 147 | 150 |
switch e.Version {
|
| 148 | 151 |
case APIVersion1: |
| ... | ... |
@@ -167,49 +171,49 @@ func (e *Endpoint) Ping() (RegistryInfo, error) {
|
| 167 | 167 |
} |
| 168 | 168 |
|
| 169 | 169 |
e.Version = APIVersionUnknown |
| 170 |
- return RegistryInfo{}, fmt.Errorf("unable to ping registry endpoint %s\nv2 ping attempt failed with error: %s\n v1 ping attempt failed with error: %s", e, errV2, errV1)
|
|
| 170 |
+ return PingResult{}, fmt.Errorf("unable to ping registry endpoint %s\nv2 ping attempt failed with error: %s\n v1 ping attempt failed with error: %s", e, errV2, errV1)
|
|
| 171 | 171 |
} |
| 172 | 172 |
|
| 173 |
-func (e *Endpoint) pingV1() (RegistryInfo, error) {
|
|
| 173 |
+func (e *Endpoint) pingV1() (PingResult, error) {
|
|
| 174 | 174 |
logrus.Debugf("attempting v1 ping for registry endpoint %s", e)
|
| 175 | 175 |
|
| 176 |
- if e.String() == INDEXSERVER {
|
|
| 176 |
+ if e.String() == IndexServer {
|
|
| 177 | 177 |
// Skip the check, we know this one is valid |
| 178 | 178 |
// (and we never want to fallback to http in case of error) |
| 179 |
- return RegistryInfo{Standalone: false}, nil
|
|
| 179 |
+ return PingResult{Standalone: false}, nil
|
|
| 180 | 180 |
} |
| 181 | 181 |
|
| 182 | 182 |
req, err := http.NewRequest("GET", e.Path("_ping"), nil)
|
| 183 | 183 |
if err != nil {
|
| 184 |
- return RegistryInfo{Standalone: false}, err
|
|
| 184 |
+ return PingResult{Standalone: false}, err
|
|
| 185 | 185 |
} |
| 186 | 186 |
|
| 187 | 187 |
resp, err := e.client.Do(req) |
| 188 | 188 |
if err != nil {
|
| 189 |
- return RegistryInfo{Standalone: false}, err
|
|
| 189 |
+ return PingResult{Standalone: false}, err
|
|
| 190 | 190 |
} |
| 191 | 191 |
|
| 192 | 192 |
defer resp.Body.Close() |
| 193 | 193 |
|
| 194 | 194 |
jsonString, err := ioutil.ReadAll(resp.Body) |
| 195 | 195 |
if err != nil {
|
| 196 |
- return RegistryInfo{Standalone: false}, fmt.Errorf("error while reading the http response: %s", err)
|
|
| 196 |
+ return PingResult{Standalone: false}, fmt.Errorf("error while reading the http response: %s", err)
|
|
| 197 | 197 |
} |
| 198 | 198 |
|
| 199 | 199 |
// If the header is absent, we assume true for compatibility with earlier |
| 200 | 200 |
// versions of the registry. default to true |
| 201 |
- info := RegistryInfo{
|
|
| 201 |
+ info := PingResult{
|
|
| 202 | 202 |
Standalone: true, |
| 203 | 203 |
} |
| 204 | 204 |
if err := json.Unmarshal(jsonString, &info); err != nil {
|
| 205 |
- logrus.Debugf("Error unmarshalling the _ping RegistryInfo: %s", err)
|
|
| 205 |
+ logrus.Debugf("Error unmarshalling the _ping PingResult: %s", err)
|
|
| 206 | 206 |
// don't stop here. Just assume sane defaults |
| 207 | 207 |
} |
| 208 | 208 |
if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" {
|
| 209 | 209 |
logrus.Debugf("Registry version header: '%s'", hdr)
|
| 210 | 210 |
info.Version = hdr |
| 211 | 211 |
} |
| 212 |
- logrus.Debugf("RegistryInfo.Version: %q", info.Version)
|
|
| 212 |
+ logrus.Debugf("PingResult.Version: %q", info.Version)
|
|
| 213 | 213 |
|
| 214 | 214 |
standalone := resp.Header.Get("X-Docker-Registry-Standalone")
|
| 215 | 215 |
logrus.Debugf("Registry standalone header: '%s'", standalone)
|
| ... | ... |
@@ -220,21 +224,21 @@ func (e *Endpoint) pingV1() (RegistryInfo, error) {
|
| 220 | 220 |
// there is a header set, and it is not "true" or "1", so assume fails |
| 221 | 221 |
info.Standalone = false |
| 222 | 222 |
} |
| 223 |
- logrus.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
|
|
| 223 |
+ logrus.Debugf("PingResult.Standalone: %t", info.Standalone)
|
|
| 224 | 224 |
return info, nil |
| 225 | 225 |
} |
| 226 | 226 |
|
| 227 |
-func (e *Endpoint) pingV2() (RegistryInfo, error) {
|
|
| 227 |
+func (e *Endpoint) pingV2() (PingResult, error) {
|
|
| 228 | 228 |
logrus.Debugf("attempting v2 ping for registry endpoint %s", e)
|
| 229 | 229 |
|
| 230 | 230 |
req, err := http.NewRequest("GET", e.Path(""), nil)
|
| 231 | 231 |
if err != nil {
|
| 232 |
- return RegistryInfo{}, err
|
|
| 232 |
+ return PingResult{}, err
|
|
| 233 | 233 |
} |
| 234 | 234 |
|
| 235 | 235 |
resp, err := e.client.Do(req) |
| 236 | 236 |
if err != nil {
|
| 237 |
- return RegistryInfo{}, err
|
|
| 237 |
+ return PingResult{}, err
|
|
| 238 | 238 |
} |
| 239 | 239 |
defer resp.Body.Close() |
| 240 | 240 |
|
| ... | ... |
@@ -253,21 +257,21 @@ HeaderLoop: |
| 253 | 253 |
} |
| 254 | 254 |
|
| 255 | 255 |
if !supportsV2 {
|
| 256 |
- return RegistryInfo{}, fmt.Errorf("%s does not appear to be a v2 registry endpoint", e)
|
|
| 256 |
+ return PingResult{}, fmt.Errorf("%s does not appear to be a v2 registry endpoint", e)
|
|
| 257 | 257 |
} |
| 258 | 258 |
|
| 259 | 259 |
if resp.StatusCode == http.StatusOK {
|
| 260 | 260 |
// It would seem that no authentication/authorization is required. |
| 261 | 261 |
// So we don't need to parse/add any authorization schemes. |
| 262 |
- return RegistryInfo{Standalone: true}, nil
|
|
| 262 |
+ return PingResult{Standalone: true}, nil
|
|
| 263 | 263 |
} |
| 264 | 264 |
|
| 265 | 265 |
if resp.StatusCode == http.StatusUnauthorized {
|
| 266 | 266 |
// Parse the WWW-Authenticate Header and store the challenges |
| 267 | 267 |
// on this endpoint object. |
| 268 | 268 |
e.AuthChallenges = parseAuthHeader(resp.Header) |
| 269 |
- return RegistryInfo{}, nil
|
|
| 269 |
+ return PingResult{}, nil
|
|
| 270 | 270 |
} |
| 271 | 271 |
|
| 272 |
- return RegistryInfo{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode))
|
|
| 272 |
+ return PingResult{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode))
|
|
| 273 | 273 |
} |
| ... | ... |
@@ -12,7 +12,7 @@ func TestEndpointParse(t *testing.T) {
|
| 12 | 12 |
str string |
| 13 | 13 |
expected string |
| 14 | 14 |
}{
|
| 15 |
- {INDEXSERVER, INDEXSERVER},
|
|
| 15 |
+ {IndexServer, IndexServer},
|
|
| 16 | 16 |
{"http://0.0.0.0:5000/v1/", "http://0.0.0.0:5000/v1/"},
|
| 17 | 17 |
{"http://0.0.0.0:5000/v2/", "http://0.0.0.0:5000/v2/"},
|
| 18 | 18 |
{"http://0.0.0.0:5000", "http://0.0.0.0:5000/v0/"},
|
| ... | ... |
@@ -21,19 +21,12 @@ import ( |
| 21 | 21 |
) |
| 22 | 22 |
|
| 23 | 23 |
var ( |
| 24 |
+ // ErrAlreadyExists is an error returned if an image being pushed |
|
| 25 |
+ // already exists on the remote side |
|
| 24 | 26 |
ErrAlreadyExists = errors.New("Image already exists")
|
| 25 |
- ErrDoesNotExist = errors.New("Image does not exist")
|
|
| 26 | 27 |
errLoginRequired = errors.New("Authentication is required.")
|
| 27 | 28 |
) |
| 28 | 29 |
|
| 29 |
-type TimeoutType uint32 |
|
| 30 |
- |
|
| 31 |
-const ( |
|
| 32 |
- NoTimeout TimeoutType = iota |
|
| 33 |
- ReceiveTimeout |
|
| 34 |
- ConnectTimeout |
|
| 35 |
-) |
|
| 36 |
- |
|
| 37 | 30 |
// dockerUserAgent is the User-Agent the Docker client uses to identify itself. |
| 38 | 31 |
// It is populated on init(), comprising version information of different components. |
| 39 | 32 |
var dockerUserAgent string |
| ... | ... |
@@ -74,10 +67,12 @@ func DockerHeaders(metaHeaders http.Header) []transport.RequestModifier {
|
| 74 | 74 |
return modifiers |
| 75 | 75 |
} |
| 76 | 76 |
|
| 77 |
+// HTTPClient returns a HTTP client structure which uses the given transport |
|
| 78 |
+// and contains the necessary headers for redirected requests |
|
| 77 | 79 |
func HTTPClient(transport http.RoundTripper) *http.Client {
|
| 78 | 80 |
return &http.Client{
|
| 79 | 81 |
Transport: transport, |
| 80 |
- CheckRedirect: AddRequiredHeadersToRedirectedRequests, |
|
| 82 |
+ CheckRedirect: addRequiredHeadersToRedirectedRequests, |
|
| 81 | 83 |
} |
| 82 | 84 |
} |
| 83 | 85 |
|
| ... | ... |
@@ -98,7 +93,9 @@ func trustedLocation(req *http.Request) bool {
|
| 98 | 98 |
return false |
| 99 | 99 |
} |
| 100 | 100 |
|
| 101 |
-func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
|
|
| 101 |
+// addRequiredHeadersToRedirectedRequests adds the necessary redirection headers |
|
| 102 |
+// for redirected requests |
|
| 103 |
+func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error {
|
|
| 102 | 104 |
if via != nil && via[0] != nil {
|
| 103 | 105 |
if trustedLocation(req) && trustedLocation(via[0]) {
|
| 104 | 106 |
req.Header = via[0].Header |
| ... | ... |
@@ -124,6 +121,8 @@ func shouldV2Fallback(err errcode.Error) bool {
|
| 124 | 124 |
return false |
| 125 | 125 |
} |
| 126 | 126 |
|
| 127 |
+// ErrNoSupport is an error type used for errors indicating that an operation |
|
| 128 |
+// is not supported. It encapsulates a more specific error. |
|
| 127 | 129 |
type ErrNoSupport struct{ Err error }
|
| 128 | 130 |
|
| 129 | 131 |
func (e ErrNoSupport) Error() string {
|
| ... | ... |
@@ -133,6 +132,8 @@ func (e ErrNoSupport) Error() string {
|
| 133 | 133 |
return e.Err.Error() |
| 134 | 134 |
} |
| 135 | 135 |
|
| 136 |
+// ContinueOnError returns true if we should fallback to the next endpoint |
|
| 137 |
+// as a result of this error. |
|
| 136 | 138 |
func ContinueOnError(err error) bool {
|
| 137 | 139 |
switch v := err.(type) {
|
| 138 | 140 |
case errcode.Errors: |
| ... | ... |
@@ -145,6 +146,8 @@ func ContinueOnError(err error) bool {
|
| 145 | 145 |
return false |
| 146 | 146 |
} |
| 147 | 147 |
|
| 148 |
+// NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the |
|
| 149 |
+// default TLS configuration. |
|
| 148 | 150 |
func NewTransport(tlsConfig *tls.Config) *http.Transport {
|
| 149 | 151 |
if tlsConfig == nil {
|
| 150 | 152 |
var cfg = tlsconfig.ServerDefault |
| ... | ... |
@@ -145,7 +145,7 @@ func makeURL(req string) string {
|
| 145 | 145 |
return testHTTPServer.URL + req |
| 146 | 146 |
} |
| 147 | 147 |
|
| 148 |
-func makeHttpsURL(req string) string {
|
|
| 148 |
+func makeHTTPSURL(req string) string {
|
|
| 149 | 149 |
return testHTTPSServer.URL + req |
| 150 | 150 |
} |
| 151 | 151 |
|
| ... | ... |
@@ -156,16 +156,16 @@ func makeIndex(req string) *IndexInfo {
|
| 156 | 156 |
return index |
| 157 | 157 |
} |
| 158 | 158 |
|
| 159 |
-func makeHttpsIndex(req string) *IndexInfo {
|
|
| 159 |
+func makeHTTPSIndex(req string) *IndexInfo {
|
|
| 160 | 160 |
index := &IndexInfo{
|
| 161 |
- Name: makeHttpsURL(req), |
|
| 161 |
+ Name: makeHTTPSURL(req), |
|
| 162 | 162 |
} |
| 163 | 163 |
return index |
| 164 | 164 |
} |
| 165 | 165 |
|
| 166 | 166 |
func makePublicIndex() *IndexInfo {
|
| 167 | 167 |
index := &IndexInfo{
|
| 168 |
- Name: INDEXSERVER, |
|
| 168 |
+ Name: IndexServer, |
|
| 169 | 169 |
Secure: true, |
| 170 | 170 |
Official: true, |
| 171 | 171 |
} |
| ... | ... |
@@ -468,7 +468,7 @@ func TestPing(t *testing.T) {
|
| 468 | 468 |
* WARNING: Don't push on the repos uncommented, it'll block the tests |
| 469 | 469 |
* |
| 470 | 470 |
func TestWait(t *testing.T) {
|
| 471 |
- logrus.Println("Test HTTP server ready and waiting:", testHttpServer.URL)
|
|
| 471 |
+ logrus.Println("Test HTTP server ready and waiting:", testHTTPServer.URL)
|
|
| 472 | 472 |
c := make(chan int) |
| 473 | 473 |
<-c |
| 474 | 474 |
} |
| ... | ... |
@@ -63,7 +63,7 @@ func TestPingRegistryEndpoint(t *testing.T) {
|
| 63 | 63 |
} |
| 64 | 64 |
|
| 65 | 65 |
testPing(makeIndex("/v1/"), true, "Expected standalone to be true (default)")
|
| 66 |
- testPing(makeHttpsIndex("/v1/"), true, "Expected standalone to be true (default)")
|
|
| 66 |
+ testPing(makeHTTPSIndex("/v1/"), true, "Expected standalone to be true (default)")
|
|
| 67 | 67 |
testPing(makePublicIndex(), false, "Expected standalone to be false for public index") |
| 68 | 68 |
} |
| 69 | 69 |
|
| ... | ... |
@@ -119,7 +119,7 @@ func TestEndpoint(t *testing.T) {
|
| 119 | 119 |
} |
| 120 | 120 |
assertInsecureIndex(index) |
| 121 | 121 |
|
| 122 |
- index.Name = makeHttpsURL("/v1/")
|
|
| 122 |
+ index.Name = makeHTTPSURL("/v1/")
|
|
| 123 | 123 |
endpoint = expandEndpoint(index) |
| 124 | 124 |
assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name) |
| 125 | 125 |
if endpoint.Version != APIVersion1 {
|
| ... | ... |
@@ -127,7 +127,7 @@ func TestEndpoint(t *testing.T) {
|
| 127 | 127 |
} |
| 128 | 128 |
assertSecureIndex(index) |
| 129 | 129 |
|
| 130 |
- index.Name = makeHttpsURL("")
|
|
| 130 |
+ index.Name = makeHTTPSURL("")
|
|
| 131 | 131 |
endpoint = expandEndpoint(index) |
| 132 | 132 |
assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/") |
| 133 | 133 |
if endpoint.Version != APIVersion1 {
|
| ... | ... |
@@ -135,7 +135,7 @@ func TestEndpoint(t *testing.T) {
|
| 135 | 135 |
} |
| 136 | 136 |
assertSecureIndex(index) |
| 137 | 137 |
|
| 138 |
- httpsURL := makeHttpsURL("")
|
|
| 138 |
+ httpsURL := makeHTTPSURL("")
|
|
| 139 | 139 |
index.Name = strings.SplitN(httpsURL, "://", 2)[1] |
| 140 | 140 |
endpoint = expandEndpoint(index) |
| 141 | 141 |
assertEqual(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/") |
| ... | ... |
@@ -332,7 +332,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 332 | 332 |
expectedRepoInfos := map[string]RepositoryInfo{
|
| 333 | 333 |
"fooo/bar": {
|
| 334 | 334 |
Index: &IndexInfo{
|
| 335 |
- Name: INDEXNAME, |
|
| 335 |
+ Name: IndexName, |
|
| 336 | 336 |
Official: true, |
| 337 | 337 |
}, |
| 338 | 338 |
RemoteName: "fooo/bar", |
| ... | ... |
@@ -342,7 +342,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 342 | 342 |
}, |
| 343 | 343 |
"library/ubuntu": {
|
| 344 | 344 |
Index: &IndexInfo{
|
| 345 |
- Name: INDEXNAME, |
|
| 345 |
+ Name: IndexName, |
|
| 346 | 346 |
Official: true, |
| 347 | 347 |
}, |
| 348 | 348 |
RemoteName: "library/ubuntu", |
| ... | ... |
@@ -352,7 +352,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 352 | 352 |
}, |
| 353 | 353 |
"nonlibrary/ubuntu": {
|
| 354 | 354 |
Index: &IndexInfo{
|
| 355 |
- Name: INDEXNAME, |
|
| 355 |
+ Name: IndexName, |
|
| 356 | 356 |
Official: true, |
| 357 | 357 |
}, |
| 358 | 358 |
RemoteName: "nonlibrary/ubuntu", |
| ... | ... |
@@ -362,7 +362,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 362 | 362 |
}, |
| 363 | 363 |
"ubuntu": {
|
| 364 | 364 |
Index: &IndexInfo{
|
| 365 |
- Name: INDEXNAME, |
|
| 365 |
+ Name: IndexName, |
|
| 366 | 366 |
Official: true, |
| 367 | 367 |
}, |
| 368 | 368 |
RemoteName: "library/ubuntu", |
| ... | ... |
@@ -372,7 +372,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 372 | 372 |
}, |
| 373 | 373 |
"other/library": {
|
| 374 | 374 |
Index: &IndexInfo{
|
| 375 |
- Name: INDEXNAME, |
|
| 375 |
+ Name: IndexName, |
|
| 376 | 376 |
Official: true, |
| 377 | 377 |
}, |
| 378 | 378 |
RemoteName: "other/library", |
| ... | ... |
@@ -480,9 +480,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 480 | 480 |
CanonicalName: "localhost/privatebase", |
| 481 | 481 |
Official: false, |
| 482 | 482 |
}, |
| 483 |
- INDEXNAME + "/public/moonbase": {
|
|
| 483 |
+ IndexName + "/public/moonbase": {
|
|
| 484 | 484 |
Index: &IndexInfo{
|
| 485 |
- Name: INDEXNAME, |
|
| 485 |
+ Name: IndexName, |
|
| 486 | 486 |
Official: true, |
| 487 | 487 |
}, |
| 488 | 488 |
RemoteName: "public/moonbase", |
| ... | ... |
@@ -490,9 +490,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 490 | 490 |
CanonicalName: "docker.io/public/moonbase", |
| 491 | 491 |
Official: false, |
| 492 | 492 |
}, |
| 493 |
- "index." + INDEXNAME + "/public/moonbase": {
|
|
| 493 |
+ "index." + IndexName + "/public/moonbase": {
|
|
| 494 | 494 |
Index: &IndexInfo{
|
| 495 |
- Name: INDEXNAME, |
|
| 495 |
+ Name: IndexName, |
|
| 496 | 496 |
Official: true, |
| 497 | 497 |
}, |
| 498 | 498 |
RemoteName: "public/moonbase", |
| ... | ... |
@@ -502,7 +502,7 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 502 | 502 |
}, |
| 503 | 503 |
"ubuntu-12.04-base": {
|
| 504 | 504 |
Index: &IndexInfo{
|
| 505 |
- Name: INDEXNAME, |
|
| 505 |
+ Name: IndexName, |
|
| 506 | 506 |
Official: true, |
| 507 | 507 |
}, |
| 508 | 508 |
RemoteName: "library/ubuntu-12.04-base", |
| ... | ... |
@@ -510,9 +510,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 510 | 510 |
CanonicalName: "docker.io/library/ubuntu-12.04-base", |
| 511 | 511 |
Official: true, |
| 512 | 512 |
}, |
| 513 |
- INDEXNAME + "/ubuntu-12.04-base": {
|
|
| 513 |
+ IndexName + "/ubuntu-12.04-base": {
|
|
| 514 | 514 |
Index: &IndexInfo{
|
| 515 |
- Name: INDEXNAME, |
|
| 515 |
+ Name: IndexName, |
|
| 516 | 516 |
Official: true, |
| 517 | 517 |
}, |
| 518 | 518 |
RemoteName: "library/ubuntu-12.04-base", |
| ... | ... |
@@ -520,9 +520,9 @@ func TestParseRepositoryInfo(t *testing.T) {
|
| 520 | 520 |
CanonicalName: "docker.io/library/ubuntu-12.04-base", |
| 521 | 521 |
Official: true, |
| 522 | 522 |
}, |
| 523 |
- "index." + INDEXNAME + "/ubuntu-12.04-base": {
|
|
| 523 |
+ "index." + IndexName + "/ubuntu-12.04-base": {
|
|
| 524 | 524 |
Index: &IndexInfo{
|
| 525 |
- Name: INDEXNAME, |
|
| 525 |
+ Name: IndexName, |
|
| 526 | 526 |
Official: true, |
| 527 | 527 |
}, |
| 528 | 528 |
RemoteName: "library/ubuntu-12.04-base", |
| ... | ... |
@@ -563,16 +563,16 @@ func TestNewIndexInfo(t *testing.T) {
|
| 563 | 563 |
} |
| 564 | 564 |
|
| 565 | 565 |
config := NewServiceConfig(nil) |
| 566 |
- noMirrors := make([]string, 0) |
|
| 566 |
+ noMirrors := []string{}
|
|
| 567 | 567 |
expectedIndexInfos := map[string]*IndexInfo{
|
| 568 |
- INDEXNAME: {
|
|
| 569 |
- Name: INDEXNAME, |
|
| 568 |
+ IndexName: {
|
|
| 569 |
+ Name: IndexName, |
|
| 570 | 570 |
Official: true, |
| 571 | 571 |
Secure: true, |
| 572 | 572 |
Mirrors: noMirrors, |
| 573 | 573 |
}, |
| 574 |
- "index." + INDEXNAME: {
|
|
| 575 |
- Name: INDEXNAME, |
|
| 574 |
+ "index." + IndexName: {
|
|
| 575 |
+ Name: IndexName, |
|
| 576 | 576 |
Official: true, |
| 577 | 577 |
Secure: true, |
| 578 | 578 |
Mirrors: noMirrors, |
| ... | ... |
@@ -596,14 +596,14 @@ func TestNewIndexInfo(t *testing.T) {
|
| 596 | 596 |
config = makeServiceConfig(publicMirrors, []string{"example.com"})
|
| 597 | 597 |
|
| 598 | 598 |
expectedIndexInfos = map[string]*IndexInfo{
|
| 599 |
- INDEXNAME: {
|
|
| 600 |
- Name: INDEXNAME, |
|
| 599 |
+ IndexName: {
|
|
| 600 |
+ Name: IndexName, |
|
| 601 | 601 |
Official: true, |
| 602 | 602 |
Secure: true, |
| 603 | 603 |
Mirrors: publicMirrors, |
| 604 | 604 |
}, |
| 605 |
- "index." + INDEXNAME: {
|
|
| 606 |
- Name: INDEXNAME, |
|
| 605 |
+ "index." + IndexName: {
|
|
| 606 |
+ Name: IndexName, |
|
| 607 | 607 |
Official: true, |
| 608 | 608 |
Secure: true, |
| 609 | 609 |
Mirrors: publicMirrors, |
| ... | ... |
@@ -814,7 +814,7 @@ func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
|
| 814 | 814 |
reqFrom.Header.Add("Authorization", "super_secret")
|
| 815 | 815 |
reqTo, _ := http.NewRequest("GET", urls[1], nil)
|
| 816 | 816 |
|
| 817 |
- AddRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
|
|
| 817 |
+ addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
|
|
| 818 | 818 |
|
| 819 | 819 |
if len(reqTo.Header) != 1 {
|
| 820 | 820 |
t.Fatalf("Expected 1 headers, got %d", len(reqTo.Header))
|
| ... | ... |
@@ -838,7 +838,7 @@ func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
|
| 838 | 838 |
reqFrom.Header.Add("Authorization", "super_secret")
|
| 839 | 839 |
reqTo, _ := http.NewRequest("GET", urls[1], nil)
|
| 840 | 840 |
|
| 841 |
- AddRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
|
|
| 841 |
+ addRequiredHeadersToRedirectedRequests(reqTo, []*http.Request{reqFrom})
|
|
| 842 | 842 |
|
| 843 | 843 |
if len(reqTo.Header) != 2 {
|
| 844 | 844 |
t.Fatalf("Expected 2 headers, got %d", len(reqTo.Header))
|
| ... | ... |
@@ -860,7 +860,7 @@ func TestIsSecureIndex(t *testing.T) {
|
| 860 | 860 |
insecureRegistries []string |
| 861 | 861 |
expected bool |
| 862 | 862 |
}{
|
| 863 |
- {INDEXNAME, nil, true},
|
|
| 863 |
+ {IndexName, nil, true},
|
|
| 864 | 864 |
{"example.com", []string{}, true},
|
| 865 | 865 |
{"example.com", []string{"example.com"}, false},
|
| 866 | 866 |
{"localhost", []string{"localhost:5000"}, false},
|
| ... | ... |
@@ -17,12 +17,14 @@ import ( |
| 17 | 17 |
"github.com/docker/docker/pkg/tlsconfig" |
| 18 | 18 |
) |
| 19 | 19 |
|
| 20 |
+// Service is a registry service. It tracks configuration data such as a list |
|
| 21 |
+// of mirrors. |
|
| 20 | 22 |
type Service struct {
|
| 21 | 23 |
Config *ServiceConfig |
| 22 | 24 |
} |
| 23 | 25 |
|
| 24 | 26 |
// NewService returns a new instance of Service ready to be |
| 25 |
-// installed no an engine. |
|
| 27 |
+// installed into an engine. |
|
| 26 | 28 |
func NewService(options *Options) *Service {
|
| 27 | 29 |
return &Service{
|
| 28 | 30 |
Config: NewServiceConfig(options), |
| ... | ... |
@@ -36,7 +38,7 @@ func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) {
|
| 36 | 36 |
addr := authConfig.ServerAddress |
| 37 | 37 |
if addr == "" {
|
| 38 | 38 |
// Use the official registry address if not specified. |
| 39 |
- addr = INDEXSERVER |
|
| 39 |
+ addr = IndexServer |
|
| 40 | 40 |
} |
| 41 | 41 |
index, err := s.ResolveIndex(addr) |
| 42 | 42 |
if err != nil {
|
| ... | ... |
@@ -81,6 +83,7 @@ func (s *Service) ResolveIndex(name string) (*IndexInfo, error) {
|
| 81 | 81 |
return s.Config.NewIndexInfo(name) |
| 82 | 82 |
} |
| 83 | 83 |
|
| 84 |
+// APIEndpoint represents a remote API endpoint |
|
| 84 | 85 |
type APIEndpoint struct {
|
| 85 | 86 |
Mirror bool |
| 86 | 87 |
URL string |
| ... | ... |
@@ -92,12 +95,13 @@ type APIEndpoint struct {
|
| 92 | 92 |
Versions []auth.APIVersion |
| 93 | 93 |
} |
| 94 | 94 |
|
| 95 |
+// ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint |
|
| 95 | 96 |
func (e APIEndpoint) ToV1Endpoint(metaHeaders http.Header) (*Endpoint, error) {
|
| 96 | 97 |
return newEndpoint(e.URL, e.TLSConfig, metaHeaders) |
| 97 | 98 |
} |
| 98 | 99 |
|
| 99 |
-func (s *Service) TlsConfig(hostname string) (*tls.Config, error) {
|
|
| 100 |
- // we construct a client tls config from server defaults |
|
| 100 |
+// TLSConfig constructs a client TLS configuration based on server defaults |
|
| 101 |
+func (s *Service) TLSConfig(hostname string) (*tls.Config, error) {
|
|
| 101 | 102 |
// PreferredServerCipherSuites should have no effect |
| 102 | 103 |
tlsConfig := tlsconfig.ServerDefault |
| 103 | 104 |
|
| ... | ... |
@@ -115,7 +119,7 @@ func (s *Service) TlsConfig(hostname string) (*tls.Config, error) {
|
| 115 | 115 |
return false |
| 116 | 116 |
} |
| 117 | 117 |
|
| 118 |
- hostDir := filepath.Join(CERTS_DIR, hostname) |
|
| 118 |
+ hostDir := filepath.Join(CertsDir, hostname) |
|
| 119 | 119 |
logrus.Debugf("hostDir: %s", hostDir)
|
| 120 | 120 |
fs, err := ioutil.ReadDir(hostDir) |
| 121 | 121 |
if err != nil && !os.IsNotExist(err) {
|
| ... | ... |
@@ -163,20 +167,23 @@ func (s *Service) TlsConfig(hostname string) (*tls.Config, error) {
|
| 163 | 163 |
} |
| 164 | 164 |
|
| 165 | 165 |
func (s *Service) tlsConfigForMirror(mirror string) (*tls.Config, error) {
|
| 166 |
- mirrorUrl, err := url.Parse(mirror) |
|
| 166 |
+ mirrorURL, err := url.Parse(mirror) |
|
| 167 | 167 |
if err != nil {
|
| 168 | 168 |
return nil, err |
| 169 | 169 |
} |
| 170 |
- return s.TlsConfig(mirrorUrl.Host) |
|
| 170 |
+ return s.TLSConfig(mirrorURL.Host) |
|
| 171 | 171 |
} |
| 172 | 172 |
|
| 173 |
+// LookupEndpoints creates an list of endpoints to try, in order of preference. |
|
| 174 |
+// It gives preference to v2 endpoints over v1, mirrors over the actual |
|
| 175 |
+// registry, and HTTPS over plain HTTP. |
|
| 173 | 176 |
func (s *Service) LookupEndpoints(repoName string) (endpoints []APIEndpoint, err error) {
|
| 174 | 177 |
var cfg = tlsconfig.ServerDefault |
| 175 | 178 |
tlsConfig := &cfg |
| 176 |
- if strings.HasPrefix(repoName, DEFAULT_NAMESPACE+"/") {
|
|
| 179 |
+ if strings.HasPrefix(repoName, DefaultNamespace+"/") {
|
|
| 177 | 180 |
// v2 mirrors |
| 178 | 181 |
for _, mirror := range s.Config.Mirrors {
|
| 179 |
- mirrorTlsConfig, err := s.tlsConfigForMirror(mirror) |
|
| 182 |
+ mirrorTLSConfig, err := s.tlsConfigForMirror(mirror) |
|
| 180 | 183 |
if err != nil {
|
| 181 | 184 |
return nil, err |
| 182 | 185 |
} |
| ... | ... |
@@ -186,12 +193,12 @@ func (s *Service) LookupEndpoints(repoName string) (endpoints []APIEndpoint, err |
| 186 | 186 |
Version: APIVersion2, |
| 187 | 187 |
Mirror: true, |
| 188 | 188 |
TrimHostname: true, |
| 189 |
- TLSConfig: mirrorTlsConfig, |
|
| 189 |
+ TLSConfig: mirrorTLSConfig, |
|
| 190 | 190 |
}) |
| 191 | 191 |
} |
| 192 | 192 |
// v2 registry |
| 193 | 193 |
endpoints = append(endpoints, APIEndpoint{
|
| 194 |
- URL: DEFAULT_V2_REGISTRY, |
|
| 194 |
+ URL: DefaultV2Registry, |
|
| 195 | 195 |
Version: APIVersion2, |
| 196 | 196 |
Official: true, |
| 197 | 197 |
TrimHostname: true, |
| ... | ... |
@@ -199,7 +206,7 @@ func (s *Service) LookupEndpoints(repoName string) (endpoints []APIEndpoint, err |
| 199 | 199 |
}) |
| 200 | 200 |
// v1 registry |
| 201 | 201 |
endpoints = append(endpoints, APIEndpoint{
|
| 202 |
- URL: DEFAULT_V1_REGISTRY, |
|
| 202 |
+ URL: DefaultV1Registry, |
|
| 203 | 203 |
Version: APIVersion1, |
| 204 | 204 |
Official: true, |
| 205 | 205 |
TrimHostname: true, |
| ... | ... |
@@ -214,7 +221,7 @@ func (s *Service) LookupEndpoints(repoName string) (endpoints []APIEndpoint, err |
| 214 | 214 |
} |
| 215 | 215 |
hostname := repoName[:slashIndex] |
| 216 | 216 |
|
| 217 |
- tlsConfig, err = s.TlsConfig(hostname) |
|
| 217 |
+ tlsConfig, err = s.TLSConfig(hostname) |
|
| 218 | 218 |
if err != nil {
|
| 219 | 219 |
return nil, err |
| 220 | 220 |
} |
| ... | ... |
@@ -232,7 +239,7 @@ func (s *Service) LookupEndpoints(repoName string) (endpoints []APIEndpoint, err |
| 232 | 232 |
Version: APIVersion2, |
| 233 | 233 |
TrimHostname: true, |
| 234 | 234 |
TLSConfig: tlsConfig, |
| 235 |
- VersionHeader: DEFAULT_REGISTRY_VERSION_HEADER, |
|
| 235 |
+ VersionHeader: DefaultRegistryVersionHeader, |
|
| 236 | 236 |
Versions: v2Versions, |
| 237 | 237 |
}, |
| 238 | 238 |
{
|
| ... | ... |
@@ -250,7 +257,7 @@ func (s *Service) LookupEndpoints(repoName string) (endpoints []APIEndpoint, err |
| 250 | 250 |
TrimHostname: true, |
| 251 | 251 |
// used to check if supposed to be secure via InsecureSkipVerify |
| 252 | 252 |
TLSConfig: tlsConfig, |
| 253 |
- VersionHeader: DEFAULT_REGISTRY_VERSION_HEADER, |
|
| 253 |
+ VersionHeader: DefaultRegistryVersionHeader, |
|
| 254 | 254 |
Versions: v2Versions, |
| 255 | 255 |
}, APIEndpoint{
|
| 256 | 256 |
URL: "http://" + hostname, |
| ... | ... |
@@ -28,9 +28,12 @@ import ( |
| 28 | 28 |
) |
| 29 | 29 |
|
| 30 | 30 |
var ( |
| 31 |
+ // ErrRepoNotFound is returned if the repository didn't exist on the |
|
| 32 |
+ // remote side |
|
| 31 | 33 |
ErrRepoNotFound = errors.New("Repository not found")
|
| 32 | 34 |
) |
| 33 | 35 |
|
| 36 |
+// A Session is used to communicate with a V1 registry |
|
| 34 | 37 |
type Session struct {
|
| 35 | 38 |
indexEndpoint *Endpoint |
| 36 | 39 |
client *http.Client |
| ... | ... |
@@ -90,9 +93,11 @@ func cloneRequest(r *http.Request) *http.Request {
|
| 90 | 90 |
return r2 |
| 91 | 91 |
} |
| 92 | 92 |
|
| 93 |
+// RoundTrip changes a HTTP request's headers to add the necessary |
|
| 94 |
+// authentication-related headers |
|
| 93 | 95 |
func (tr *authTransport) RoundTrip(orig *http.Request) (*http.Response, error) {
|
| 94 | 96 |
// Authorization should not be set on 302 redirect for untrusted locations. |
| 95 |
- // This logic mirrors the behavior in AddRequiredHeadersToRedirectedRequests. |
|
| 97 |
+ // This logic mirrors the behavior in addRequiredHeadersToRedirectedRequests. |
|
| 96 | 98 |
// As the authorization logic is currently implemented in RoundTrip, |
| 97 | 99 |
// a 302 redirect is detected by looking at the Referer header as go http package adds said header. |
| 98 | 100 |
// This is safe as Docker doesn't set Referer in other scenarios. |
| ... | ... |
@@ -154,6 +159,7 @@ func (tr *authTransport) CancelRequest(req *http.Request) {
|
| 154 | 154 |
} |
| 155 | 155 |
} |
| 156 | 156 |
|
| 157 |
+// NewSession creates a new session |
|
| 157 | 158 |
// TODO(tiborvass): remove authConfig param once registry client v2 is vendored |
| 158 | 159 |
func NewSession(client *http.Client, authConfig *cliconfig.AuthConfig, endpoint *Endpoint) (r *Session, err error) {
|
| 159 | 160 |
r = &Session{
|
| ... | ... |
@@ -167,7 +173,7 @@ func NewSession(client *http.Client, authConfig *cliconfig.AuthConfig, endpoint |
| 167 | 167 |
|
| 168 | 168 |
// If we're working with a standalone private registry over HTTPS, send Basic Auth headers |
| 169 | 169 |
// alongside all our requests. |
| 170 |
- if endpoint.VersionString(1) != INDEXSERVER && endpoint.URL.Scheme == "https" {
|
|
| 170 |
+ if endpoint.VersionString(1) != IndexServer && endpoint.URL.Scheme == "https" {
|
|
| 171 | 171 |
info, err := endpoint.Ping() |
| 172 | 172 |
if err != nil {
|
| 173 | 173 |
return nil, err |
| ... | ... |
@@ -196,8 +202,8 @@ func (r *Session) ID() string {
|
| 196 | 196 |
return r.id |
| 197 | 197 |
} |
| 198 | 198 |
|
| 199 |
-// Retrieve the history of a given image from the Registry. |
|
| 200 |
-// Return a list of the parent's json (requested image included) |
|
| 199 |
+// GetRemoteHistory retrieves the history of a given image from the registry. |
|
| 200 |
+// It returns a list of the parent's JSON files (including the requested image). |
|
| 201 | 201 |
func (r *Session) GetRemoteHistory(imgID, registry string) ([]string, error) {
|
| 202 | 202 |
res, err := r.client.Get(registry + "images/" + imgID + "/ancestry") |
| 203 | 203 |
if err != nil {
|
| ... | ... |
@@ -220,7 +226,7 @@ func (r *Session) GetRemoteHistory(imgID, registry string) ([]string, error) {
|
| 220 | 220 |
return history, nil |
| 221 | 221 |
} |
| 222 | 222 |
|
| 223 |
-// Check if an image exists in the Registry |
|
| 223 |
+// LookupRemoteImage checks if an image exists in the registry |
|
| 224 | 224 |
func (r *Session) LookupRemoteImage(imgID, registry string) error {
|
| 225 | 225 |
res, err := r.client.Get(registry + "images/" + imgID + "/json") |
| 226 | 226 |
if err != nil {
|
| ... | ... |
@@ -233,7 +239,7 @@ func (r *Session) LookupRemoteImage(imgID, registry string) error {
|
| 233 | 233 |
return nil |
| 234 | 234 |
} |
| 235 | 235 |
|
| 236 |
-// Retrieve an image from the Registry. |
|
| 236 |
+// GetRemoteImageJSON retrieves an image's JSON metadata from the registry. |
|
| 237 | 237 |
func (r *Session) GetRemoteImageJSON(imgID, registry string) ([]byte, int, error) {
|
| 238 | 238 |
res, err := r.client.Get(registry + "images/" + imgID + "/json") |
| 239 | 239 |
if err != nil {
|
| ... | ... |
@@ -259,6 +265,7 @@ func (r *Session) GetRemoteImageJSON(imgID, registry string) ([]byte, int, error |
| 259 | 259 |
return jsonString, imageSize, nil |
| 260 | 260 |
} |
| 261 | 261 |
|
| 262 |
+// GetRemoteImageLayer retrieves an image layer from the registry |
|
| 262 | 263 |
func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io.ReadCloser, error) {
|
| 263 | 264 |
var ( |
| 264 | 265 |
retries = 5 |
| ... | ... |
@@ -308,9 +315,13 @@ func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io |
| 308 | 308 |
return res.Body, nil |
| 309 | 309 |
} |
| 310 | 310 |
|
| 311 |
+// GetRemoteTag retrieves the tag named in the askedTag argument from the given |
|
| 312 |
+// repository. It queries each of the registries supplied in the registries |
|
| 313 |
+// argument, and returns data from the first one that answers the query |
|
| 314 |
+// successfully. |
|
| 311 | 315 |
func (r *Session) GetRemoteTag(registries []string, repository string, askedTag string) (string, error) {
|
| 312 | 316 |
if strings.Count(repository, "/") == 0 {
|
| 313 |
- // This will be removed once the Registry supports auto-resolution on |
|
| 317 |
+ // This will be removed once the registry supports auto-resolution on |
|
| 314 | 318 |
// the "library" namespace |
| 315 | 319 |
repository = "library/" + repository |
| 316 | 320 |
} |
| ... | ... |
@@ -331,18 +342,22 @@ func (r *Session) GetRemoteTag(registries []string, repository string, askedTag |
| 331 | 331 |
continue |
| 332 | 332 |
} |
| 333 | 333 |
|
| 334 |
- var tagId string |
|
| 335 |
- if err := json.NewDecoder(res.Body).Decode(&tagId); err != nil {
|
|
| 334 |
+ var tagID string |
|
| 335 |
+ if err := json.NewDecoder(res.Body).Decode(&tagID); err != nil {
|
|
| 336 | 336 |
return "", err |
| 337 | 337 |
} |
| 338 |
- return tagId, nil |
|
| 338 |
+ return tagID, nil |
|
| 339 | 339 |
} |
| 340 | 340 |
return "", fmt.Errorf("Could not reach any registry endpoint")
|
| 341 | 341 |
} |
| 342 | 342 |
|
| 343 |
+// GetRemoteTags retrieves all tags from the given repository. It queries each |
|
| 344 |
+// of the registries supplied in the registries argument, and returns data from |
|
| 345 |
+// the first one that answers the query successfully. It returns a map with |
|
| 346 |
+// tag names as the keys and image IDs as the values. |
|
| 343 | 347 |
func (r *Session) GetRemoteTags(registries []string, repository string) (map[string]string, error) {
|
| 344 | 348 |
if strings.Count(repository, "/") == 0 {
|
| 345 |
- // This will be removed once the Registry supports auto-resolution on |
|
| 349 |
+ // This will be removed once the registry supports auto-resolution on |
|
| 346 | 350 |
// the "library" namespace |
| 347 | 351 |
repository = "library/" + repository |
| 348 | 352 |
} |
| ... | ... |
@@ -379,7 +394,7 @@ func buildEndpointsList(headers []string, indexEp string) ([]string, error) {
|
| 379 | 379 |
return nil, err |
| 380 | 380 |
} |
| 381 | 381 |
var urlScheme = parsedURL.Scheme |
| 382 |
- // The Registry's URL scheme has to match the Index' |
|
| 382 |
+ // The registry's URL scheme has to match the Index' |
|
| 383 | 383 |
for _, ep := range headers {
|
| 384 | 384 |
epList := strings.Split(ep, ",") |
| 385 | 385 |
for _, epListElement := range epList {
|
| ... | ... |
@@ -391,6 +406,7 @@ func buildEndpointsList(headers []string, indexEp string) ([]string, error) {
|
| 391 | 391 |
return endpoints, nil |
| 392 | 392 |
} |
| 393 | 393 |
|
| 394 |
+// GetRepositoryData returns lists of images and endpoints for the repository |
|
| 394 | 395 |
func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
|
| 395 | 396 |
repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.VersionString(1), remote)
|
| 396 | 397 |
|
| ... | ... |
@@ -457,8 +473,8 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
|
| 457 | 457 |
}, nil |
| 458 | 458 |
} |
| 459 | 459 |
|
| 460 |
+// PushImageChecksumRegistry uploads checksums for an image |
|
| 460 | 461 |
func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string) error {
|
| 461 |
- |
|
| 462 | 462 |
u := registry + "images/" + imgData.ID + "/checksum" |
| 463 | 463 |
|
| 464 | 464 |
logrus.Debugf("[registry] Calling PUT %s", u)
|
| ... | ... |
@@ -494,7 +510,7 @@ func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string) e |
| 494 | 494 |
return nil |
| 495 | 495 |
} |
| 496 | 496 |
|
| 497 |
-// Push a local image to the registry |
|
| 497 |
+// PushImageJSONRegistry pushes JSON metadata for a local image to the registry |
|
| 498 | 498 |
func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string) error {
|
| 499 | 499 |
|
| 500 | 500 |
u := registry + "images/" + imgData.ID + "/json" |
| ... | ... |
@@ -531,8 +547,8 @@ func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regist |
| 531 | 531 |
return nil |
| 532 | 532 |
} |
| 533 | 533 |
|
| 534 |
+// PushImageLayerRegistry sends the checksum of an image layer to the registry |
|
| 534 | 535 |
func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, jsonRaw []byte) (checksum string, checksumPayload string, err error) {
|
| 535 |
- |
|
| 536 | 536 |
u := registry + "images/" + imgID + "/layer" |
| 537 | 537 |
|
| 538 | 538 |
logrus.Debugf("[registry] Calling PUT %s", u)
|
| ... | ... |
@@ -576,7 +592,7 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry |
| 576 | 576 |
return tarsumLayer.Sum(jsonRaw), checksumPayload, nil |
| 577 | 577 |
} |
| 578 | 578 |
|
| 579 |
-// push a tag on the registry. |
|
| 579 |
+// PushRegistryTag pushes a tag on the registry. |
|
| 580 | 580 |
// Remote has the format '<user>/<repo> |
| 581 | 581 |
func (r *Session) PushRegistryTag(remote, revision, tag, registry string) error {
|
| 582 | 582 |
// "jsonify" the string |
| ... | ... |
@@ -600,6 +616,7 @@ func (r *Session) PushRegistryTag(remote, revision, tag, registry string) error |
| 600 | 600 |
return nil |
| 601 | 601 |
} |
| 602 | 602 |
|
| 603 |
+// PushImageJSONIndex uploads an image list to the repository |
|
| 603 | 604 |
func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) {
|
| 604 | 605 |
cleanImgList := []*ImgData{}
|
| 605 | 606 |
if validate {
|
| ... | ... |
@@ -705,6 +722,7 @@ func shouldRedirect(response *http.Response) bool {
|
| 705 | 705 |
return response.StatusCode >= 300 && response.StatusCode < 400 |
| 706 | 706 |
} |
| 707 | 707 |
|
| 708 |
+// SearchRepositories performs a search against the remote repository |
|
| 708 | 709 |
func (r *Session) SearchRepositories(term string) (*SearchResults, error) {
|
| 709 | 710 |
logrus.Debugf("Index server: %s", r.indexEndpoint)
|
| 710 | 711 |
u := r.indexEndpoint.VersionString(1) + "search?q=" + url.QueryEscape(term) |
| ... | ... |
@@ -727,6 +745,7 @@ func (r *Session) SearchRepositories(term string) (*SearchResults, error) {
|
| 727 | 727 |
return result, json.NewDecoder(res.Body).Decode(result) |
| 728 | 728 |
} |
| 729 | 729 |
|
| 730 |
+// GetAuthConfig returns the authentication settings for a session |
|
| 730 | 731 |
// TODO(tiborvass): remove this once registry client v2 is vendored |
| 731 | 732 |
func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig {
|
| 732 | 733 |
password := "" |
| ... | ... |
@@ -1,38 +1,66 @@ |
| 1 | 1 |
package registry |
| 2 | 2 |
|
| 3 |
+// SearchResult describes a search result returned from a registry |
|
| 3 | 4 |
type SearchResult struct {
|
| 4 |
- StarCount int `json:"star_count"` |
|
| 5 |
- IsOfficial bool `json:"is_official"` |
|
| 6 |
- Name string `json:"name"` |
|
| 7 |
- IsTrusted bool `json:"is_trusted"` |
|
| 8 |
- IsAutomated bool `json:"is_automated"` |
|
| 5 |
+ // StarCount indicates the number of stars this repository has |
|
| 6 |
+ StarCount int `json:"star_count"` |
|
| 7 |
+ // IsOfficial indicates whether the result is an official repository or not |
|
| 8 |
+ IsOfficial bool `json:"is_official"` |
|
| 9 |
+ // Name is the name of the repository |
|
| 10 |
+ Name string `json:"name"` |
|
| 11 |
+ // IsOfficial indicates whether the result is trusted |
|
| 12 |
+ IsTrusted bool `json:"is_trusted"` |
|
| 13 |
+ // IsAutomated indicates whether the result is automated |
|
| 14 |
+ IsAutomated bool `json:"is_automated"` |
|
| 15 |
+ // Description is a textual description of the repository |
|
| 9 | 16 |
Description string `json:"description"` |
| 10 | 17 |
} |
| 11 | 18 |
|
| 19 |
+// SearchResults lists a collection search results returned from a registry |
|
| 12 | 20 |
type SearchResults struct {
|
| 13 |
- Query string `json:"query"` |
|
| 14 |
- NumResults int `json:"num_results"` |
|
| 15 |
- Results []SearchResult `json:"results"` |
|
| 21 |
+ // Query contains the query string that generated the search results |
|
| 22 |
+ Query string `json:"query"` |
|
| 23 |
+ // NumResults indicates the number of results the query returned |
|
| 24 |
+ NumResults int `json:"num_results"` |
|
| 25 |
+ // Results is a slice containing the acutal results for the search |
|
| 26 |
+ Results []SearchResult `json:"results"` |
|
| 16 | 27 |
} |
| 17 | 28 |
|
| 29 |
+// RepositoryData tracks the image list, list of endpoints, and list of tokens |
|
| 30 |
+// for a repository |
|
| 18 | 31 |
type RepositoryData struct {
|
| 19 |
- ImgList map[string]*ImgData |
|
| 32 |
+ // ImgList is a list of images in the repository |
|
| 33 |
+ ImgList map[string]*ImgData |
|
| 34 |
+ // Endpoints is a list of endpoints returned in X-Docker-Endpoints |
|
| 20 | 35 |
Endpoints []string |
| 21 |
- Tokens []string |
|
| 36 |
+ // Tokens is currently unused (remove it?) |
|
| 37 |
+ Tokens []string |
|
| 22 | 38 |
} |
| 23 | 39 |
|
| 40 |
+// ImgData is used to transfer image checksums to and from the registry |
|
| 24 | 41 |
type ImgData struct {
|
| 42 |
+ // ID is an opaque string that identifies the image |
|
| 25 | 43 |
ID string `json:"id"` |
| 26 | 44 |
Checksum string `json:"checksum,omitempty"` |
| 27 | 45 |
ChecksumPayload string `json:"-"` |
| 28 | 46 |
Tag string `json:",omitempty"` |
| 29 | 47 |
} |
| 30 | 48 |
|
| 31 |
-type RegistryInfo struct {
|
|
| 32 |
- Version string `json:"version"` |
|
| 33 |
- Standalone bool `json:"standalone"` |
|
| 49 |
+// PingResult contains the information returned when pinging a registry. It |
|
| 50 |
+// indicates the registry's version and whether the registry claims to be a |
|
| 51 |
+// standalone registry. |
|
| 52 |
+type PingResult struct {
|
|
| 53 |
+ // Version is the registry version supplied by the registry in a HTTP |
|
| 54 |
+ // header |
|
| 55 |
+ Version string `json:"version"` |
|
| 56 |
+ // Standalone is set to true if the registry indicates it is a |
|
| 57 |
+ // standalone registry in the X-Docker-Registry-Standalone |
|
| 58 |
+ // header |
|
| 59 |
+ Standalone bool `json:"standalone"` |
|
| 34 | 60 |
} |
| 35 | 61 |
|
| 62 |
+// APIVersion is an integral representation of an API version (presently |
|
| 63 |
+// either 1 or 2) |
|
| 36 | 64 |
type APIVersion int |
| 37 | 65 |
|
| 38 | 66 |
func (av APIVersion) String() string {
|
| ... | ... |
@@ -51,6 +79,8 @@ const ( |
| 51 | 51 |
APIVersion2 |
| 52 | 52 |
) |
| 53 | 53 |
|
| 54 |
+// IndexInfo contains information about a registry |
|
| 55 |
+// |
|
| 54 | 56 |
// RepositoryInfo Examples: |
| 55 | 57 |
// {
|
| 56 | 58 |
// "Index" : {
|
| ... | ... |
@@ -64,7 +94,7 @@ const ( |
| 64 | 64 |
// "CanonicalName" : "docker.io/debian" |
| 65 | 65 |
// "Official" : true, |
| 66 | 66 |
// } |
| 67 |
- |
|
| 67 |
+// |
|
| 68 | 68 |
// {
|
| 69 | 69 |
// "Index" : {
|
| 70 | 70 |
// "Name" : "127.0.0.1:5000", |
| ... | ... |
@@ -78,16 +108,33 @@ const ( |
| 78 | 78 |
// "Official" : false, |
| 79 | 79 |
// } |
| 80 | 80 |
type IndexInfo struct {
|
| 81 |
- Name string |
|
| 82 |
- Mirrors []string |
|
| 83 |
- Secure bool |
|
| 81 |
+ // Name is the name of the registry, such as "docker.io" |
|
| 82 |
+ Name string |
|
| 83 |
+ // Mirrors is a list of mirrors, expressed as URIs |
|
| 84 |
+ Mirrors []string |
|
| 85 |
+ // Secure is set to false if the registry is part of the list of |
|
| 86 |
+ // insecure registries. Insecure registries accept HTTP and/or accept |
|
| 87 |
+ // HTTPS with certificates from unknown CAs. |
|
| 88 |
+ Secure bool |
|
| 89 |
+ // Official indicates whether this is an official registry |
|
| 84 | 90 |
Official bool |
| 85 | 91 |
} |
| 86 | 92 |
|
| 93 |
+// RepositoryInfo describes a repository |
|
| 87 | 94 |
type RepositoryInfo struct {
|
| 88 |
- Index *IndexInfo |
|
| 89 |
- RemoteName string |
|
| 90 |
- LocalName string |
|
| 95 |
+ // Index points to registry information |
|
| 96 |
+ Index *IndexInfo |
|
| 97 |
+ // RemoteName is the remote name of the repository, such as |
|
| 98 |
+ // "library/ubuntu-12.04-base" |
|
| 99 |
+ RemoteName string |
|
| 100 |
+ // LocalName is the local name of the repository, such as |
|
| 101 |
+ // "ubuntu-12.04-base" |
|
| 102 |
+ LocalName string |
|
| 103 |
+ // CanonicalName is the canonical name of the repository, such as |
|
| 104 |
+ // "docker.io/library/ubuntu-12.04-base" |
|
| 91 | 105 |
CanonicalName string |
| 92 |
- Official bool |
|
| 106 |
+ // Official indicates whether the repository is considered official. |
|
| 107 |
+ // If the registry is official, and the normalized name does not |
|
| 108 |
+ // contain a '/' (e.g. "foo"), then it is considered an official repo. |
|
| 109 |
+ Official bool |
|
| 93 | 110 |
} |