*: implement additional pull registries
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
parent
7351786411
commit
a35727c80b
13 changed files with 337 additions and 43 deletions
|
@ -1,7 +1,10 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/copy"
|
||||
"github.com/containers/image/docker/reference"
|
||||
|
@ -31,6 +34,7 @@ type imageService struct {
|
|||
defaultTransport string
|
||||
insecureRegistryCIDRs []*net.IPNet
|
||||
indexConfigs map[string]*indexInfo
|
||||
registries []string
|
||||
}
|
||||
|
||||
// ImageServer wraps up various CRI-related activities into a reusable
|
||||
|
@ -50,6 +54,9 @@ type ImageServer interface {
|
|||
GetStore() storage.Store
|
||||
// CanPull preliminary checks whether we're allowed to pull an image
|
||||
CanPull(imageName string, options *copy.Options) (bool, error)
|
||||
// ResolveNames takes an image reference and if it's unqualified (w/o hostname),
|
||||
// it uses crio's default registries to qualify it.
|
||||
ResolveNames(imageName string) ([]string, error)
|
||||
}
|
||||
|
||||
func (svc *imageService) ListImages(filter string) ([]ImageResult, error) {
|
||||
|
@ -271,11 +278,47 @@ func (svc *imageService) isSecureIndex(indexName string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func isValidHostname(hostname string) bool {
|
||||
return hostname != "" && !strings.Contains(hostname, "/") &&
|
||||
(strings.Contains(hostname, ".") ||
|
||||
strings.Contains(hostname, ":") || hostname == "localhost")
|
||||
}
|
||||
|
||||
func (svc *imageService) ResolveNames(imageName string) ([]string, error) {
|
||||
r, err := reference.ParseNormalizedNamed(imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domain, rest := splitDomain(r.Name())
|
||||
if len(domain) != 0 && isValidHostname(domain) {
|
||||
// this means the image is already fully qualified
|
||||
return []string{imageName}, nil
|
||||
}
|
||||
// we got an unqualified image here, we can't go ahead w/o registries configured
|
||||
// properly.
|
||||
if len(svc.registries) == 0 {
|
||||
return nil, errors.New("no registries configured while trying to pull an unqualified image")
|
||||
}
|
||||
// this means we got an image in the form of "busybox"
|
||||
// we need to use additional registries...
|
||||
// normalize the unqualified image to be domain/repo/image...
|
||||
images := []string{}
|
||||
for _, r := range svc.registries {
|
||||
path := rest
|
||||
if !isValidHostname(domain) {
|
||||
// This is the case where we have an image like "runcom/busybox"
|
||||
path = imageName
|
||||
}
|
||||
images = append(images, filepath.Join(r, path))
|
||||
}
|
||||
return images, nil
|
||||
}
|
||||
|
||||
// GetImageService returns an ImageServer that uses the passed-in store, and
|
||||
// which will prepend the passed-in defaultTransport value to an image name if
|
||||
// a name that's passed to its PullImage() method can't be resolved to an image
|
||||
// in the store and can't be resolved to a source on its own.
|
||||
func GetImageService(store storage.Store, defaultTransport string, insecureRegistries []string) (ImageServer, error) {
|
||||
func GetImageService(store storage.Store, defaultTransport string, insecureRegistries []string, registries []string) (ImageServer, error) {
|
||||
if store == nil {
|
||||
var err error
|
||||
store, err = storage.GetStore(storage.DefaultStoreOptions)
|
||||
|
@ -284,11 +327,22 @@ func GetImageService(store storage.Store, defaultTransport string, insecureRegis
|
|||
}
|
||||
}
|
||||
|
||||
seenRegistries := make(map[string]bool, len(registries))
|
||||
cleanRegistries := []string{}
|
||||
for _, r := range registries {
|
||||
if seenRegistries[r] {
|
||||
continue
|
||||
}
|
||||
cleanRegistries = append(cleanRegistries, r)
|
||||
seenRegistries[r] = true
|
||||
}
|
||||
|
||||
is := &imageService{
|
||||
store: store,
|
||||
defaultTransport: defaultTransport,
|
||||
indexConfigs: make(map[string]*indexInfo, 0),
|
||||
insecureRegistryCIDRs: make([]*net.IPNet, 0),
|
||||
registries: cleanRegistries,
|
||||
}
|
||||
|
||||
insecureRegistries = append(insecureRegistries, "127.0.0.0/8")
|
||||
|
|
125
pkg/storage/image_regexp.go
Normal file
125
pkg/storage/image_regexp.go
Normal file
|
@ -0,0 +1,125 @@
|
|||
package storage
|
||||
|
||||
// This is a fork of docker/distribution code to be used when manipulating image
|
||||
// references.
|
||||
// DO NOT EDIT THIS FILE.
|
||||
|
||||
import "regexp"
|
||||
|
||||
var (
|
||||
// alphaNumericRegexp defines the alpha numeric atom, typically a
|
||||
// component of names. This only allows lower case characters and digits.
|
||||
alphaNumericRegexp = match(`[a-z0-9]+`)
|
||||
|
||||
// separatorRegexp defines the separators allowed to be embedded in name
|
||||
// components. This allow one period, one or two underscore and multiple
|
||||
// dashes.
|
||||
separatorRegexp = match(`(?:[._]|__|[-]*)`)
|
||||
|
||||
// nameComponentRegexp restricts registry path component names to start
|
||||
// with at least one letter or number, with following parts able to be
|
||||
// separated by one period, one or two underscore and multiple dashes.
|
||||
nameComponentRegexp = expression(
|
||||
alphaNumericRegexp,
|
||||
optional(repeated(separatorRegexp, alphaNumericRegexp)))
|
||||
|
||||
// domainComponentRegexp restricts the registry domain component of a
|
||||
// repository name to start with a component as defined by domainRegexp
|
||||
// and followed by an optional port.
|
||||
domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
|
||||
|
||||
// domainRegexp defines the structure of potential domain components
|
||||
// that may be part of image names. This is purposely a subset of what is
|
||||
// allowed by DNS to ensure backwards compatibility with Docker image
|
||||
// names.
|
||||
domainRegexp = expression(
|
||||
domainComponentRegexp,
|
||||
optional(repeated(literal(`.`), domainComponentRegexp)),
|
||||
optional(literal(`:`), match(`[0-9]+`)))
|
||||
|
||||
// NameRegexp is the format for the name component of references. The
|
||||
// regexp has capturing groups for the domain and name part omitting
|
||||
// the separating forward slash from either.
|
||||
NameRegexp = expression(
|
||||
optional(domainRegexp, literal(`/`)),
|
||||
nameComponentRegexp,
|
||||
optional(repeated(literal(`/`), nameComponentRegexp)))
|
||||
|
||||
// anchoredNameRegexp is used to parse a name value, capturing the
|
||||
// domain and trailing components.
|
||||
anchoredNameRegexp = anchored(
|
||||
optional(capture(domainRegexp), literal(`/`)),
|
||||
capture(nameComponentRegexp,
|
||||
optional(repeated(literal(`/`), nameComponentRegexp))))
|
||||
|
||||
// IdentifierRegexp is the format for string identifier used as a
|
||||
// content addressable identifier using sha256. These identifiers
|
||||
// are like digests without the algorithm, since sha256 is used.
|
||||
IdentifierRegexp = match(`([a-f0-9]{64})`)
|
||||
|
||||
// ShortIdentifierRegexp is the format used to represent a prefix
|
||||
// of an identifier. A prefix may be used to match a sha256 identifier
|
||||
// within a list of trusted identifiers.
|
||||
ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`)
|
||||
)
|
||||
|
||||
// match compiles the string to a regular expression.
|
||||
var match = regexp.MustCompile
|
||||
|
||||
// literal compiles s into a literal regular expression, escaping any regexp
|
||||
// reserved characters.
|
||||
func literal(s string) *regexp.Regexp {
|
||||
re := match(regexp.QuoteMeta(s))
|
||||
|
||||
if _, complete := re.LiteralPrefix(); !complete {
|
||||
panic("must be a literal")
|
||||
}
|
||||
|
||||
return re
|
||||
}
|
||||
|
||||
func splitDomain(name string) (string, string) {
|
||||
match := anchoredNameRegexp.FindStringSubmatch(name)
|
||||
if len(match) != 3 {
|
||||
return "", name
|
||||
}
|
||||
return match[1], match[2]
|
||||
}
|
||||
|
||||
// expression defines a full expression, where each regular expression must
|
||||
// follow the previous.
|
||||
func expression(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
var s string
|
||||
for _, re := range res {
|
||||
s += re.String()
|
||||
}
|
||||
|
||||
return match(s)
|
||||
}
|
||||
|
||||
// optional wraps the expression in a non-capturing group and makes the
|
||||
// production optional.
|
||||
func optional(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(group(expression(res...)).String() + `?`)
|
||||
}
|
||||
|
||||
// repeated wraps the regexp in a non-capturing group to get one or more
|
||||
// matches.
|
||||
func repeated(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(group(expression(res...)).String() + `+`)
|
||||
}
|
||||
|
||||
// group wraps the regexp in a non-capturing group.
|
||||
func group(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(`(?:` + expression(res...).String() + `)`)
|
||||
}
|
||||
|
||||
// capture wraps the expression in a capturing group.
|
||||
func capture(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(`(` + expression(res...).String() + `)`)
|
||||
}
|
||||
|
||||
// anchored anchors the regular expression by adding start and end delimiters.
|
||||
func anchored(res ...*regexp.Regexp) *regexp.Regexp {
|
||||
return match(`^` + expression(res...).String() + `$`)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue