You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
portainer/api/docker/images/registry.go

140 lines
3.5 KiB

package images
import (
"cmp"
"strings"
"time"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/internal/registryutils"
"github.com/patrickmn/go-cache"
"github.com/pkg/errors"
)
var registriesCache = cache.New(5*time.Minute, 5*time.Minute)
type RegistryClient struct {
dataStore dataservices.DataStore
}
func NewRegistryClient(dataStore dataservices.DataStore) *RegistryClient {
return &RegistryClient{dataStore: dataStore}
}
func (c *RegistryClient) RegistryAuth(image Image) (string, string, error) {
registries, err := c.dataStore.Registry().ReadAll()
if err != nil {
return "", "", err
}
registry, err := findBestMatchRegistry(image.opts.Name, registries)
if err != nil {
return "", "", err
}
if !registry.Authentication {
return "", "", errors.New("authentication is disabled")
}
return c.CertainRegistryAuth(registry)
}
func (c *RegistryClient) CertainRegistryAuth(registry *portainer.Registry) (string, string, error) {
if err := registryutils.EnsureRegTokenValid(c.dataStore, registry); err != nil {
return "", "", err
}
if !registry.Authentication {
return "", "", errors.New("authentication is disabled")
}
return registryutils.GetRegEffectiveCredential(registry)
}
func (c *RegistryClient) EncodedRegistryAuth(image Image) (string, error) {
registries, err := c.dataStore.Registry().ReadAll()
if err != nil {
return "", err
}
registry, err := findBestMatchRegistry(image.opts.Name, registries)
if err != nil {
return "", err
}
if !registry.Authentication {
return "", errors.New("authentication is disabled")
}
return c.EncodedCertainRegistryAuth(registry)
}
func (c *RegistryClient) EncodedCertainRegistryAuth(registry *portainer.Registry) (string, error) {
if err := registryutils.EnsureRegTokenValid(c.dataStore, registry); err != nil {
return "", err
}
return registryutils.GetRegistryAuthHeader(registry)
}
// findBestMatchRegistry finds out the best match registry for repository @Meng
// matching precedence:
// 1. both domain name and username matched (for dockerhub only)
// 2. only URL matched
// 3. pick up the first dockerhub registry
func findBestMatchRegistry(repository string, registries []portainer.Registry) (*portainer.Registry, error) {
cachedRegistry, err := cachedRegistry(repository)
if err == nil {
return cachedRegistry, nil
}
var match1, match2, match3 *portainer.Registry
for _, registry := range registries {
if strings.Contains(repository, registry.URL) {
match2 = &registry
}
if registry.Type != portainer.DockerHubRegistry {
continue
}
// try to match repository examples:
// <USERNAME>/nginx:latest
// docker.io/<USERNAME>/nginx:latest
if strings.HasPrefix(repository, registry.Username+"/") || strings.HasPrefix(repository, registry.URL+"/"+registry.Username+"/") {
match1 = &registry
}
// try to match repository examples:
// portainer/portainer-ee:latest
// <NON-USERNAME>/portainer-ee:latest
if match3 == nil {
match3 = &registry
}
}
match := cmp.Or(match1, match2, match3)
if match == nil {
return nil, errors.New("no registries matched")
}
registriesCache.Set(repository, match, 0)
return match, nil
}
func cachedRegistry(cacheKey string) (*portainer.Registry, error) {
r, ok := registriesCache.Get(cacheKey)
if ok {
registry, ok := r.(portainer.Registry)
if ok {
return &registry, nil
}
}
return nil, errors.Errorf("no registry found in cache: %s", cacheKey)
}