reg/registry/registry.go
Jess Frazelle 067711f90e
fix getting official registry manifest
closes #164

Signed-off-by: Jess Frazelle <acidburn@github.com>
2019-01-02 11:19:56 -05:00

172 lines
3.8 KiB
Go

package registry
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"log"
"net/http"
"regexp"
"strings"
"time"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema2"
"github.com/docker/docker/api/types"
)
// Registry defines the client for retrieving information from the registry API.
type Registry struct {
URL string
Domain string
Username string
Password string
Client *http.Client
Logf LogfCallback
Opt Opt
}
var reProtocol = regexp.MustCompile("^https?://")
// LogfCallback is the callback for formatting logs.
type LogfCallback func(format string, args ...interface{})
// Quiet discards logs silently.
func Quiet(format string, args ...interface{}) {}
// Log passes log messages to the logging package.
func Log(format string, args ...interface{}) {
log.Printf(format, args...)
}
// Opt holds the options for a new registry.
type Opt struct {
Domain string
Insecure bool
Debug bool
SkipPing bool
NonSSL bool
Timeout time.Duration
Headers map[string]string
}
// New creates a new Registry struct with the given URL and credentials.
func New(ctx context.Context, auth types.AuthConfig, opt Opt) (*Registry, error) {
transport := http.DefaultTransport
if opt.Insecure {
transport = &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
}
return newFromTransport(ctx, auth, transport, opt)
}
func newFromTransport(ctx context.Context, auth types.AuthConfig, transport http.RoundTripper, opt Opt) (*Registry, error) {
if len(opt.Domain) < 1 || opt.Domain == "docker.io" {
opt.Domain = auth.ServerAddress
}
url := strings.TrimSuffix(opt.Domain, "/")
authURL := strings.TrimSuffix(auth.ServerAddress, "/")
if !reProtocol.MatchString(url) {
if !opt.NonSSL {
url = "https://" + url
} else {
url = "http://" + url
}
}
if !reProtocol.MatchString(authURL) {
if !opt.NonSSL {
authURL = "https://" + authURL
} else {
authURL = "http://" + authURL
}
}
tokenTransport := &TokenTransport{
Transport: transport,
Username: auth.Username,
Password: auth.Password,
}
basicAuthTransport := &BasicTransport{
Transport: tokenTransport,
URL: authURL,
Username: auth.Username,
Password: auth.Password,
}
errorTransport := &ErrorTransport{
Transport: basicAuthTransport,
}
customTransport := &CustomTransport{
Transport: errorTransport,
Headers: opt.Headers,
}
// set the logging
logf := Quiet
if opt.Debug {
logf = Log
}
registry := &Registry{
URL: url,
Domain: reProtocol.ReplaceAllString(url, ""),
Client: &http.Client{
Timeout: opt.Timeout,
Transport: customTransport,
},
Username: auth.Username,
Password: auth.Password,
Logf: logf,
Opt: opt,
}
if !opt.SkipPing {
if err := registry.Ping(ctx); err != nil {
return nil, err
}
}
return registry, nil
}
// url returns a registry URL with the passed arguements concatenated.
func (r *Registry) url(pathTemplate string, args ...interface{}) string {
pathSuffix := fmt.Sprintf(pathTemplate, args...)
url := fmt.Sprintf("%s%s", r.URL, pathSuffix)
return url
}
func (r *Registry) getJSON(ctx context.Context, url string, response interface{}) (http.Header, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
switch response.(type) {
case *schema2.Manifest:
req.Header.Add("Accept", fmt.Sprintf("%s;q=0.9", schema2.MediaTypeManifest))
case *manifestlist.ManifestList:
req.Header.Add("Accept", fmt.Sprintf("%s;q=0.9", manifestlist.MediaTypeManifestList))
}
resp, err := r.Client.Do(req.WithContext(ctx))
if err != nil {
return nil, err
}
defer resp.Body.Close()
r.Logf("registry.registry resp.Status=%s", resp.Status)
if err := json.NewDecoder(resp.Body).Decode(response); err != nil {
return nil, err
}
return resp.Header, nil
}