endpoint.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. package registry
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "io/ioutil"
  7. "net/http"
  8. "net/url"
  9. "strings"
  10. "github.com/docker/docker/pkg/log"
  11. )
  12. // scans string for api version in the URL path. returns the trimmed hostname, if version found, string and API version.
  13. func scanForAPIVersion(hostname string) (string, APIVersion) {
  14. var (
  15. chunks []string
  16. apiVersionStr string
  17. )
  18. if strings.HasSuffix(hostname, "/") {
  19. chunks = strings.Split(hostname[:len(hostname)-1], "/")
  20. apiVersionStr = chunks[len(chunks)-1]
  21. } else {
  22. chunks = strings.Split(hostname, "/")
  23. apiVersionStr = chunks[len(chunks)-1]
  24. }
  25. for k, v := range apiVersions {
  26. if apiVersionStr == v {
  27. hostname = strings.Join(chunks[:len(chunks)-1], "/")
  28. return hostname, k
  29. }
  30. }
  31. return hostname, DefaultAPIVersion
  32. }
  33. func NewEndpoint(hostname string) (*Endpoint, error) {
  34. var (
  35. endpoint Endpoint
  36. trimmedHostname string
  37. err error
  38. )
  39. if !strings.HasPrefix(hostname, "http") {
  40. hostname = "https://" + hostname
  41. }
  42. trimmedHostname, endpoint.Version = scanForAPIVersion(hostname)
  43. endpoint.URL, err = url.Parse(trimmedHostname)
  44. if err != nil {
  45. return nil, err
  46. }
  47. endpoint.URL.Scheme = "https"
  48. if _, err := endpoint.Ping(); err != nil {
  49. log.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err)
  50. // TODO: Check if http fallback is enabled
  51. endpoint.URL.Scheme = "http"
  52. if _, err = endpoint.Ping(); err != nil {
  53. return nil, errors.New("Invalid Registry endpoint: " + err.Error())
  54. }
  55. }
  56. return &endpoint, nil
  57. }
  58. type Endpoint struct {
  59. URL *url.URL
  60. Version APIVersion
  61. }
  62. // Get the formated URL for the root of this registry Endpoint
  63. func (e Endpoint) String() string {
  64. return fmt.Sprintf("%s/v%d/", e.URL.String(), e.Version)
  65. }
  66. func (e Endpoint) VersionString(version APIVersion) string {
  67. return fmt.Sprintf("%s/v%d/", e.URL.String(), version)
  68. }
  69. func (e Endpoint) Ping() (RegistryInfo, error) {
  70. if e.String() == IndexServerAddress() {
  71. // Skip the check, we now this one is valid
  72. // (and we never want to fallback to http in case of error)
  73. return RegistryInfo{Standalone: false}, nil
  74. }
  75. req, err := http.NewRequest("GET", e.String()+"_ping", nil)
  76. if err != nil {
  77. return RegistryInfo{Standalone: false}, err
  78. }
  79. resp, _, err := doRequest(req, nil, ConnectTimeout)
  80. if err != nil {
  81. return RegistryInfo{Standalone: false}, err
  82. }
  83. defer resp.Body.Close()
  84. jsonString, err := ioutil.ReadAll(resp.Body)
  85. if err != nil {
  86. return RegistryInfo{Standalone: false}, fmt.Errorf("Error while reading the http response: %s", err)
  87. }
  88. // If the header is absent, we assume true for compatibility with earlier
  89. // versions of the registry. default to true
  90. info := RegistryInfo{
  91. Standalone: true,
  92. }
  93. if err := json.Unmarshal(jsonString, &info); err != nil {
  94. log.Debugf("Error unmarshalling the _ping RegistryInfo: %s", err)
  95. // don't stop here. Just assume sane defaults
  96. }
  97. if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" {
  98. log.Debugf("Registry version header: '%s'", hdr)
  99. info.Version = hdr
  100. }
  101. log.Debugf("RegistryInfo.Version: %q", info.Version)
  102. standalone := resp.Header.Get("X-Docker-Registry-Standalone")
  103. log.Debugf("Registry standalone header: '%s'", standalone)
  104. // Accepted values are "true" (case-insensitive) and "1".
  105. if strings.EqualFold(standalone, "true") || standalone == "1" {
  106. info.Standalone = true
  107. } else if len(standalone) > 0 {
  108. // there is a header set, and it is not "true" or "1", so assume fails
  109. info.Standalone = false
  110. }
  111. log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
  112. return info, nil
  113. }