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