This field was added in 19515a7ad8,
but looks to be always set for endpoints used, so we can trim remote names
unconditionally.
This option was added for possible future expansion, allowing registry-
mirrors to get the full reference of the image (including domain-name),
for them to host a mirror for multiple upstreams on the same registry.
That approach will unlikely be implemented, and containerd has a different
approach for this, where the reference to the original registry is passed
through a query parameter instead.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
129 lines
3.8 KiB
Go
129 lines
3.8 KiB
Go
package client
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/distribution/reference"
|
|
"github.com/docker/cli/cli/trust"
|
|
"github.com/docker/distribution/registry/client/auth"
|
|
"github.com/docker/distribution/registry/client/transport"
|
|
registrytypes "github.com/docker/docker/api/types/registry"
|
|
"github.com/docker/docker/registry"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type repositoryEndpoint struct {
|
|
info *registry.RepositoryInfo
|
|
endpoint registry.APIEndpoint
|
|
actions []string
|
|
}
|
|
|
|
// Name returns the repository name
|
|
func (r repositoryEndpoint) Name() string {
|
|
return reference.Path(r.info.Name)
|
|
}
|
|
|
|
// BaseURL returns the endpoint url
|
|
func (r repositoryEndpoint) BaseURL() string {
|
|
return r.endpoint.URL.String()
|
|
}
|
|
|
|
func newDefaultRepositoryEndpoint(ref reference.Named, insecure bool) (repositoryEndpoint, error) {
|
|
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
|
if err != nil {
|
|
return repositoryEndpoint{}, err
|
|
}
|
|
endpoint, err := getDefaultEndpointFromRepoInfo(repoInfo)
|
|
if err != nil {
|
|
return repositoryEndpoint{}, err
|
|
}
|
|
if insecure {
|
|
endpoint.TLSConfig.InsecureSkipVerify = true
|
|
}
|
|
return repositoryEndpoint{info: repoInfo, endpoint: endpoint}, nil
|
|
}
|
|
|
|
func getDefaultEndpointFromRepoInfo(repoInfo *registry.RepositoryInfo) (registry.APIEndpoint, error) {
|
|
var err error
|
|
|
|
options := registry.ServiceOptions{}
|
|
registryService, err := registry.NewService(options)
|
|
if err != nil {
|
|
return registry.APIEndpoint{}, err
|
|
}
|
|
endpoints, err := registryService.LookupPushEndpoints(reference.Domain(repoInfo.Name))
|
|
if err != nil {
|
|
return registry.APIEndpoint{}, err
|
|
}
|
|
// Default to the highest priority endpoint to return
|
|
endpoint := endpoints[0]
|
|
if !repoInfo.Index.Secure {
|
|
for _, ep := range endpoints {
|
|
if ep.URL.Scheme == "http" {
|
|
endpoint = ep
|
|
}
|
|
}
|
|
}
|
|
return endpoint, nil
|
|
}
|
|
|
|
// getHTTPTransport builds a transport for use in communicating with a registry
|
|
func getHTTPTransport(authConfig registrytypes.AuthConfig, endpoint registry.APIEndpoint, repoName, userAgent string, actions []string) (http.RoundTripper, error) {
|
|
// get the http transport, this will be used in a client to upload manifest
|
|
base := &http.Transport{
|
|
Proxy: http.ProxyFromEnvironment,
|
|
Dial: (&net.Dialer{
|
|
Timeout: 30 * time.Second,
|
|
KeepAlive: 30 * time.Second,
|
|
}).Dial,
|
|
TLSHandshakeTimeout: 10 * time.Second,
|
|
TLSClientConfig: endpoint.TLSConfig,
|
|
DisableKeepAlives: true,
|
|
}
|
|
|
|
modifiers := registry.Headers(userAgent, http.Header{})
|
|
authTransport := transport.NewTransport(base, modifiers...)
|
|
challengeManager, err := registry.PingV2Registry(endpoint.URL, authTransport)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "error pinging v2 registry")
|
|
}
|
|
if authConfig.RegistryToken != "" {
|
|
passThruTokenHandler := &existingTokenHandler{token: authConfig.RegistryToken}
|
|
modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler))
|
|
} else {
|
|
if len(actions) == 0 {
|
|
actions = trust.ActionsPullOnly
|
|
}
|
|
creds := registry.NewStaticCredentialStore(&authConfig)
|
|
tokenHandler := auth.NewTokenHandler(authTransport, creds, repoName, actions...)
|
|
basicHandler := auth.NewBasicHandler(creds)
|
|
modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
|
|
}
|
|
return transport.NewTransport(base, modifiers...), nil
|
|
}
|
|
|
|
// RepoNameForReference returns the repository name from a reference
|
|
func RepoNameForReference(ref reference.Named) (string, error) {
|
|
// insecure is fine since this only returns the name
|
|
repo, err := newDefaultRepositoryEndpoint(ref, false)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return repo.Name(), nil
|
|
}
|
|
|
|
type existingTokenHandler struct {
|
|
token string
|
|
}
|
|
|
|
func (th *existingTokenHandler) AuthorizeRequest(req *http.Request, _ map[string]string) error {
|
|
req.Header.Set("Authorization", "Bearer "+th.token)
|
|
return nil
|
|
}
|
|
|
|
func (th *existingTokenHandler) Scheme() string {
|
|
return "bearer"
|
|
}
|