endpoint.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. package registry
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "net"
  7. "net/http"
  8. "net/url"
  9. "strings"
  10. log "github.com/Sirupsen/logrus"
  11. )
  12. // for mocking in unit tests
  13. var lookupIP = net.LookupIP
  14. // scans string for api version in the URL path. returns the trimmed hostname, if version found, string and API version.
  15. func scanForAPIVersion(hostname string) (string, APIVersion) {
  16. var (
  17. chunks []string
  18. apiVersionStr string
  19. )
  20. if strings.HasSuffix(hostname, "/") {
  21. chunks = strings.Split(hostname[:len(hostname)-1], "/")
  22. apiVersionStr = chunks[len(chunks)-1]
  23. } else {
  24. chunks = strings.Split(hostname, "/")
  25. apiVersionStr = chunks[len(chunks)-1]
  26. }
  27. for k, v := range apiVersions {
  28. if apiVersionStr == v {
  29. hostname = strings.Join(chunks[:len(chunks)-1], "/")
  30. return hostname, k
  31. }
  32. }
  33. return hostname, DefaultAPIVersion
  34. }
  35. func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
  36. // *TODO: Allow per-registry configuration of endpoints.
  37. endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure)
  38. if err != nil {
  39. return nil, err
  40. }
  41. // Try HTTPS ping to registry
  42. endpoint.URL.Scheme = "https"
  43. if _, err := endpoint.Ping(); err != nil {
  44. //TODO: triggering highland build can be done there without "failing"
  45. if index.Secure {
  46. // If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
  47. // in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP.
  48. return nil, fmt.Errorf("Invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host)
  49. }
  50. // If registry is insecure and HTTPS failed, fallback to HTTP.
  51. log.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err)
  52. endpoint.URL.Scheme = "http"
  53. _, err2 := endpoint.Ping()
  54. if err2 == nil {
  55. return endpoint, nil
  56. }
  57. return nil, fmt.Errorf("Invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
  58. }
  59. return endpoint, nil
  60. }
  61. func newEndpoint(hostname string, secure bool) (*Endpoint, error) {
  62. var (
  63. endpoint = Endpoint{}
  64. trimmedHostname string
  65. err error
  66. )
  67. if !strings.HasPrefix(hostname, "http") {
  68. hostname = "https://" + hostname
  69. }
  70. trimmedHostname, endpoint.Version = scanForAPIVersion(hostname)
  71. endpoint.URL, err = url.Parse(trimmedHostname)
  72. if err != nil {
  73. return nil, err
  74. }
  75. endpoint.secure = secure
  76. return &endpoint, nil
  77. }
  78. func (repoInfo *RepositoryInfo) GetEndpoint() (*Endpoint, error) {
  79. return NewEndpoint(repoInfo.Index)
  80. }
  81. type Endpoint struct {
  82. URL *url.URL
  83. Version APIVersion
  84. secure bool
  85. }
  86. // Get the formated URL for the root of this registry Endpoint
  87. func (e Endpoint) String() string {
  88. return fmt.Sprintf("%s/v%d/", e.URL.String(), e.Version)
  89. }
  90. func (e Endpoint) VersionString(version APIVersion) string {
  91. return fmt.Sprintf("%s/v%d/", e.URL.String(), version)
  92. }
  93. func (e Endpoint) Ping() (RegistryInfo, error) {
  94. if e.String() == IndexServerAddress() {
  95. // Skip the check, we now this one is valid
  96. // (and we never want to fallback to http in case of error)
  97. return RegistryInfo{Standalone: false}, nil
  98. }
  99. req, err := http.NewRequest("GET", e.String()+"_ping", nil)
  100. if err != nil {
  101. return RegistryInfo{Standalone: false}, err
  102. }
  103. resp, _, err := doRequest(req, nil, ConnectTimeout, e.secure)
  104. if err != nil {
  105. return RegistryInfo{Standalone: false}, err
  106. }
  107. defer resp.Body.Close()
  108. jsonString, err := ioutil.ReadAll(resp.Body)
  109. if err != nil {
  110. return RegistryInfo{Standalone: false}, fmt.Errorf("Error while reading the http response: %s", err)
  111. }
  112. // If the header is absent, we assume true for compatibility with earlier
  113. // versions of the registry. default to true
  114. info := RegistryInfo{
  115. Standalone: true,
  116. }
  117. if err := json.Unmarshal(jsonString, &info); err != nil {
  118. log.Debugf("Error unmarshalling the _ping RegistryInfo: %s", err)
  119. // don't stop here. Just assume sane defaults
  120. }
  121. if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" {
  122. log.Debugf("Registry version header: '%s'", hdr)
  123. info.Version = hdr
  124. }
  125. log.Debugf("RegistryInfo.Version: %q", info.Version)
  126. standalone := resp.Header.Get("X-Docker-Registry-Standalone")
  127. log.Debugf("Registry standalone header: '%s'", standalone)
  128. // Accepted values are "true" (case-insensitive) and "1".
  129. if strings.EqualFold(standalone, "true") || standalone == "1" {
  130. info.Standalone = true
  131. } else if len(standalone) > 0 {
  132. // there is a header set, and it is not "true" or "1", so assume fails
  133. info.Standalone = false
  134. }
  135. log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
  136. return info, nil
  137. }