summaryrefslogtreecommitdiff
path: root/pkg/storage
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/storage')
-rw-r--r--pkg/storage/doc.go5
-rw-r--r--pkg/storage/image.go551
-rw-r--r--pkg/storage/image_regexp.go125
-rw-r--r--pkg/storage/runtime.go452
4 files changed, 1133 insertions, 0 deletions
diff --git a/pkg/storage/doc.go b/pkg/storage/doc.go
new file mode 100644
index 000000000..6366b22a4
--- /dev/null
+++ b/pkg/storage/doc.go
@@ -0,0 +1,5 @@
+// Package storage provides helper functions for creating and managing CRI pod
+// sandboxes and containers and metadata associated with them in the format
+// that crio understands. The API it provides should be considered to be
+// unstable.
+package storage
diff --git a/pkg/storage/image.go b/pkg/storage/image.go
new file mode 100644
index 000000000..ddff82c1c
--- /dev/null
+++ b/pkg/storage/image.go
@@ -0,0 +1,551 @@
+package storage
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "path"
+ "regexp"
+ "strings"
+
+ "github.com/containers/image/copy"
+ "github.com/containers/image/docker/reference"
+ "github.com/containers/image/image"
+ "github.com/containers/image/signature"
+ istorage "github.com/containers/image/storage"
+ "github.com/containers/image/transports/alltransports"
+ "github.com/containers/image/types"
+ "github.com/containers/storage"
+ distreference "github.com/docker/distribution/reference"
+)
+
+// ImageResult wraps a subset of information about an image: its ID, its names,
+// and the size, if known, or nil if it isn't.
+type ImageResult struct {
+ ID string
+ Names []string
+ Digests []string
+ Size *uint64
+ ImageRef string
+}
+
+type indexInfo struct {
+ name string
+ secure bool
+}
+
+type imageService struct {
+ store storage.Store
+ defaultTransport string
+ insecureRegistryCIDRs []*net.IPNet
+ indexConfigs map[string]*indexInfo
+ registries []string
+}
+
+// ImageServer wraps up various CRI-related activities into a reusable
+// implementation.
+type ImageServer interface {
+ // ListImages returns list of all images which match the filter.
+ ListImages(systemContext *types.SystemContext, filter string) ([]ImageResult, error)
+ // ImageStatus returns status of an image which matches the filter.
+ ImageStatus(systemContext *types.SystemContext, filter string) (*ImageResult, error)
+ // PullImage imports an image from the specified location.
+ PullImage(systemContext *types.SystemContext, imageName string, options *copy.Options) (types.ImageReference, error)
+ // UntagImage removes a name from the specified image, and if it was
+ // the only name the image had, removes the image.
+ UntagImage(systemContext *types.SystemContext, imageName string) error
+ // RemoveImage deletes the specified image.
+ RemoveImage(systemContext *types.SystemContext, imageName string) error
+ // GetStore returns the reference to the storage library Store which
+ // the image server uses to hold images, and is the destination used
+ // when it's asked to pull an image.
+ 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) getRef(name string) (types.ImageReference, error) {
+ ref, err := alltransports.ParseImageName(name)
+ if err != nil {
+ ref2, err2 := istorage.Transport.ParseStoreReference(svc.store, "@"+name)
+ if err2 != nil {
+ ref3, err3 := istorage.Transport.ParseStoreReference(svc.store, name)
+ if err3 != nil {
+ return nil, err
+ }
+ ref2 = ref3
+ }
+ ref = ref2
+ }
+ return ref, nil
+}
+
+func (svc *imageService) ListImages(systemContext *types.SystemContext, filter string) ([]ImageResult, error) {
+ results := []ImageResult{}
+ if filter != "" {
+ ref, err := svc.getRef(filter)
+ if err != nil {
+ return nil, err
+ }
+ if image, err := istorage.Transport.GetStoreImage(svc.store, ref); err == nil {
+ img, err := ref.NewImage(systemContext)
+ if err != nil {
+ return nil, err
+ }
+ size := imageSize(img)
+ img.Close()
+ results = append(results, ImageResult{
+ ID: image.ID,
+ Names: image.Names,
+ Size: size,
+ })
+ }
+ } else {
+ images, err := svc.store.Images()
+ if err != nil {
+ return nil, err
+ }
+ for _, image := range images {
+ ref, err := istorage.Transport.ParseStoreReference(svc.store, "@"+image.ID)
+ if err != nil {
+ return nil, err
+ }
+ img, err := ref.NewImage(systemContext)
+ if err != nil {
+ return nil, err
+ }
+ size := imageSize(img)
+ img.Close()
+ results = append(results, ImageResult{
+ ID: image.ID,
+ Names: image.Names,
+ Size: size,
+ })
+ }
+ }
+ return results, nil
+}
+
+func (svc *imageService) ImageStatus(systemContext *types.SystemContext, nameOrID string) (*ImageResult, error) {
+ ref, err := alltransports.ParseImageName(nameOrID)
+ if err != nil {
+ ref2, err2 := istorage.Transport.ParseStoreReference(svc.store, "@"+nameOrID)
+ if err2 != nil {
+ ref3, err3 := istorage.Transport.ParseStoreReference(svc.store, nameOrID)
+ if err3 != nil {
+ return nil, err
+ }
+ ref2 = ref3
+ }
+ ref = ref2
+ }
+ image, err := istorage.Transport.GetStoreImage(svc.store, ref)
+ if err != nil {
+ return nil, err
+ }
+
+ img, err := ref.NewImage(systemContext)
+ if err != nil {
+ return nil, err
+ }
+ size := imageSize(img)
+ img.Close()
+
+ result := ImageResult{
+ ID: image.ID,
+ Names: image.Names,
+ Size: size,
+ }
+ if len(image.Names) > 0 {
+ result.ImageRef = image.Names[0]
+ if ref2, err2 := istorage.Transport.ParseStoreReference(svc.store, image.Names[0]); err2 == nil {
+ if dref := ref2.DockerReference(); dref != nil {
+ result.ImageRef = reference.FamiliarString(dref)
+ }
+ }
+ }
+
+ return &result, nil
+}
+
+func imageSize(img types.Image) *uint64 {
+ if sum, err := img.Size(); err == nil {
+ usum := uint64(sum)
+ return &usum
+ }
+ return nil
+}
+
+func (svc *imageService) CanPull(imageName string, options *copy.Options) (bool, error) {
+ srcRef, err := svc.prepareImage(imageName, options)
+ if err != nil {
+ return false, err
+ }
+ rawSource, err := srcRef.NewImageSource(options.SourceCtx)
+ if err != nil {
+ return false, err
+ }
+ src, err := image.FromSource(rawSource)
+ if err != nil {
+ rawSource.Close()
+ return false, err
+ }
+ src.Close()
+ return true, nil
+}
+
+// prepareImage creates an image reference from an image string and set options
+// for the source context
+func (svc *imageService) prepareImage(imageName string, options *copy.Options) (types.ImageReference, error) {
+ if imageName == "" {
+ return nil, storage.ErrNotAnImage
+ }
+
+ srcRef, err := alltransports.ParseImageName(imageName)
+ if err != nil {
+ if svc.defaultTransport == "" {
+ return nil, err
+ }
+ srcRef2, err2 := alltransports.ParseImageName(svc.defaultTransport + imageName)
+ if err2 != nil {
+ return nil, err
+ }
+ srcRef = srcRef2
+ }
+
+ if options.SourceCtx == nil {
+ options.SourceCtx = &types.SystemContext{}
+ }
+
+ hostname := reference.Domain(srcRef.DockerReference())
+ if secure := svc.isSecureIndex(hostname); !secure {
+ options.SourceCtx.DockerInsecureSkipTLSVerify = !secure
+ }
+ return srcRef, nil
+}
+
+func (svc *imageService) PullImage(systemContext *types.SystemContext, imageName string, options *copy.Options) (types.ImageReference, error) {
+ policy, err := signature.DefaultPolicy(systemContext)
+ if err != nil {
+ return nil, err
+ }
+ policyContext, err := signature.NewPolicyContext(policy)
+ if err != nil {
+ return nil, err
+ }
+ if options == nil {
+ options = &copy.Options{}
+ }
+
+ srcRef, err := svc.prepareImage(imageName, options)
+ if err != nil {
+ return nil, err
+ }
+
+ dest := imageName
+ if srcRef.DockerReference() != nil {
+ dest = srcRef.DockerReference().Name()
+ if tagged, ok := srcRef.DockerReference().(reference.NamedTagged); ok {
+ dest = dest + ":" + tagged.Tag()
+ }
+ if canonical, ok := srcRef.DockerReference().(reference.Canonical); ok {
+ dest = dest + "@" + canonical.Digest().String()
+ }
+ }
+ destRef, err := istorage.Transport.ParseStoreReference(svc.store, dest)
+ if err != nil {
+ return nil, err
+ }
+ err = copy.Image(policyContext, destRef, srcRef, options)
+ if err != nil {
+ return nil, err
+ }
+ return destRef, nil
+}
+
+func (svc *imageService) UntagImage(systemContext *types.SystemContext, nameOrID string) error {
+ ref, err := alltransports.ParseImageName(nameOrID)
+ if err != nil {
+ ref2, err2 := istorage.Transport.ParseStoreReference(svc.store, "@"+nameOrID)
+ if err2 != nil {
+ ref3, err3 := istorage.Transport.ParseStoreReference(svc.store, nameOrID)
+ if err3 != nil {
+ return err
+ }
+ ref2 = ref3
+ }
+ ref = ref2
+ }
+
+ img, err := istorage.Transport.GetStoreImage(svc.store, ref)
+ if err != nil {
+ return err
+ }
+
+ if nameOrID != img.ID {
+ namedRef, err := svc.prepareImage(nameOrID, &copy.Options{})
+ if err != nil {
+ return err
+ }
+
+ name := nameOrID
+ if namedRef.DockerReference() != nil {
+ name = namedRef.DockerReference().Name()
+ if tagged, ok := namedRef.DockerReference().(reference.NamedTagged); ok {
+ name = name + ":" + tagged.Tag()
+ }
+ if canonical, ok := namedRef.DockerReference().(reference.Canonical); ok {
+ name = name + "@" + canonical.Digest().String()
+ }
+ }
+
+ prunedNames := make([]string, 0, len(img.Names))
+ for _, imgName := range img.Names {
+ if imgName != name && imgName != nameOrID {
+ prunedNames = append(prunedNames, imgName)
+ }
+ }
+
+ if len(prunedNames) > 0 {
+ return svc.store.SetNames(img.ID, prunedNames)
+ }
+ }
+
+ return ref.DeleteImage(systemContext)
+}
+
+func (svc *imageService) RemoveImage(systemContext *types.SystemContext, nameOrID string) error {
+ ref, err := alltransports.ParseImageName(nameOrID)
+ if err != nil {
+ ref2, err2 := istorage.Transport.ParseStoreReference(svc.store, "@"+nameOrID)
+ if err2 != nil {
+ ref3, err3 := istorage.Transport.ParseStoreReference(svc.store, nameOrID)
+ if err3 != nil {
+ return err
+ }
+ ref2 = ref3
+ }
+ ref = ref2
+ }
+ return ref.DeleteImage(systemContext)
+}
+
+func (svc *imageService) GetStore() storage.Store {
+ return svc.store
+}
+
+func (svc *imageService) isSecureIndex(indexName string) bool {
+ if index, ok := svc.indexConfigs[indexName]; ok {
+ return index.secure
+ }
+
+ host, _, err := net.SplitHostPort(indexName)
+ if err != nil {
+ // assume indexName is of the form `host` without the port and go on.
+ host = indexName
+ }
+
+ addrs, err := net.LookupIP(host)
+ if err != nil {
+ ip := net.ParseIP(host)
+ if ip != nil {
+ addrs = []net.IP{ip}
+ }
+
+ // if ip == nil, then `host` is neither an IP nor it could be looked up,
+ // either because the index is unreachable, or because the index is behind an HTTP proxy.
+ // So, len(addrs) == 0 and we're not aborting.
+ }
+
+ // Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
+ for _, addr := range addrs {
+ for _, ipnet := range svc.insecureRegistryCIDRs {
+ // check if the addr falls in the subnet
+ if ipnet.Contains(addr) {
+ return false
+ }
+ }
+ }
+
+ return true
+}
+
+func isValidHostname(hostname string) bool {
+ return hostname != "" && !strings.Contains(hostname, "/") &&
+ (strings.Contains(hostname, ".") ||
+ strings.Contains(hostname, ":") || hostname == "localhost")
+}
+
+func isReferenceFullyQualified(reposName reference.Named) bool {
+ indexName, _, _ := splitReposName(reposName)
+ return indexName != ""
+}
+
+const (
+ // defaultHostname is the default built-in hostname
+ defaultHostname = "docker.io"
+ // legacyDefaultHostname is automatically converted to DefaultHostname
+ legacyDefaultHostname = "index.docker.io"
+ // defaultRepoPrefix is the prefix used for default repositories in default host
+ defaultRepoPrefix = "library/"
+)
+
+// splitReposName breaks a reposName into an index name and remote name
+func splitReposName(reposName reference.Named) (indexName string, remoteName reference.Named, err error) {
+ var remoteNameStr string
+ indexName, remoteNameStr = distreference.SplitHostname(reposName)
+ if !isValidHostname(indexName) {
+ // This is a Docker Index repos (ex: samalba/hipache or ubuntu)
+ // 'docker.io'
+ indexName = ""
+ remoteName = reposName
+ } else {
+ remoteName, err = withName(remoteNameStr)
+ }
+ return
+}
+
+func validateName(name string) error {
+ if err := validateID(strings.TrimPrefix(name, defaultHostname+"/")); err == nil {
+ return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
+ }
+ return nil
+}
+
+var validHex = regexp.MustCompile(`^([a-f0-9]{64})$`)
+
+// validateID checks whether an ID string is a valid image ID.
+func validateID(id string) error {
+ if ok := validHex.MatchString(id); !ok {
+ return fmt.Errorf("image ID %q is invalid", id)
+ }
+ return nil
+}
+
+// withName returns a named object representing the given string. If the input
+// is invalid ErrReferenceInvalidFormat will be returned.
+func withName(name string) (reference.Named, error) {
+ name, err := normalize(name)
+ if err != nil {
+ return nil, err
+ }
+ if err := validateName(name); err != nil {
+ return nil, err
+ }
+ r, err := distreference.WithName(name)
+ return r, err
+}
+
+// splitHostname splits a repository name to hostname and remotename string.
+// If no valid hostname is found, empty string will be returned as a resulting
+// hostname. Repository name needs to be already validated before.
+func splitHostname(name string) (hostname, remoteName string) {
+ i := strings.IndexRune(name, '/')
+ if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
+ hostname, remoteName = "", name
+ } else {
+ hostname, remoteName = name[:i], name[i+1:]
+ }
+ if hostname == legacyDefaultHostname {
+ hostname = defaultHostname
+ }
+ if hostname == defaultHostname && !strings.ContainsRune(remoteName, '/') {
+ remoteName = defaultRepoPrefix + remoteName
+ }
+ return
+}
+
+// normalize returns a repository name in its normalized form, meaning it
+// will contain library/ prefix for official images.
+func normalize(name string) (string, error) {
+ host, remoteName := splitHostname(name)
+ if strings.ToLower(remoteName) != remoteName {
+ return "", errors.New("invalid reference format: repository name must be lowercase")
+ }
+ if host == defaultHostname {
+ if strings.HasPrefix(remoteName, defaultRepoPrefix) {
+ remoteName = strings.TrimPrefix(remoteName, defaultRepoPrefix)
+ }
+ return host + "/" + remoteName, nil
+ }
+ return name, nil
+}
+
+func (svc *imageService) ResolveNames(imageName string) ([]string, error) {
+ r, err := reference.ParseNormalizedNamed(imageName)
+ if err != nil {
+ return nil, err
+ }
+ if isReferenceFullyQualified(r) {
+ // 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...
+ _, rest := splitDomain(r.Name())
+ images := []string{}
+ for _, r := range svc.registries {
+ images = append(images, path.Join(r, rest))
+ }
+ 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, registries []string) (ImageServer, error) {
+ if store == nil {
+ var err error
+ store, err = storage.GetStore(storage.DefaultStoreOptions)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ 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")
+ // Split --insecure-registry into CIDR and registry-specific settings.
+ for _, r := range insecureRegistries {
+ // Check if CIDR was passed to --insecure-registry
+ _, ipnet, err := net.ParseCIDR(r)
+ if err == nil {
+ // Valid CIDR.
+ is.insecureRegistryCIDRs = append(is.insecureRegistryCIDRs, ipnet)
+ } else {
+ // Assume `host:port` if not CIDR.
+ is.indexConfigs[r] = &indexInfo{
+ name: r,
+ secure: false,
+ }
+ }
+ }
+
+ return is, nil
+}
diff --git a/pkg/storage/image_regexp.go b/pkg/storage/image_regexp.go
new file mode 100644
index 000000000..96de64884
--- /dev/null
+++ b/pkg/storage/image_regexp.go
@@ -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() + `$`)
+}
diff --git a/pkg/storage/runtime.go b/pkg/storage/runtime.go
new file mode 100644
index 000000000..bc402f413
--- /dev/null
+++ b/pkg/storage/runtime.go
@@ -0,0 +1,452 @@
+package storage
+
+import (
+ "encoding/json"
+ "fmt"
+ "time"
+
+ "github.com/containers/image/copy"
+ istorage "github.com/containers/image/storage"
+ "github.com/containers/image/transports/alltransports"
+ "github.com/containers/image/types"
+ "github.com/containers/storage"
+ "github.com/opencontainers/image-spec/specs-go/v1"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+var (
+ // ErrInvalidPodName is returned when a pod name specified to a
+ // function call is found to be invalid (most often, because it's
+ // empty).
+ ErrInvalidPodName = errors.New("invalid pod name")
+ // ErrInvalidImageName is returned when an image name specified to a
+ // function call is found to be invalid (most often, because it's
+ // empty).
+ ErrInvalidImageName = errors.New("invalid image name")
+ // ErrInvalidContainerName is returned when a container name specified
+ // to a function call is found to be invalid (most often, because it's
+ // empty).
+ ErrInvalidContainerName = errors.New("invalid container name")
+ // ErrInvalidSandboxID is returned when a sandbox ID specified to a
+ // function call is found to be invalid (because it's either
+ // empty or doesn't match a valid sandbox).
+ ErrInvalidSandboxID = errors.New("invalid sandbox ID")
+ // ErrInvalidContainerID is returned when a container ID specified to a
+ // function call is found to be invalid (because it's either
+ // empty or doesn't match a valid container).
+ ErrInvalidContainerID = errors.New("invalid container ID")
+)
+
+type runtimeService struct {
+ storageImageServer ImageServer
+ pauseImage string
+}
+
+// ContainerInfo wraps a subset of information about a container: its ID and
+// the locations of its nonvolatile and volatile per-container directories,
+// along with a copy of the configuration blob from the image that was used to
+// create the container, if the image had a configuration.
+type ContainerInfo struct {
+ ID string
+ Dir string
+ RunDir string
+ Config *v1.Image
+}
+
+// RuntimeServer wraps up various CRI-related activities into a reusable
+// implementation.
+type RuntimeServer interface {
+ // CreatePodSandbox creates a pod infrastructure container, using the
+ // specified PodID for the infrastructure container's ID. In the CRI
+ // view of things, a sandbox is distinct from its containers, including
+ // its infrastructure container, but at this level the sandbox is
+ // essentially the same as its infrastructure container, with a
+ // container's membership in a pod being signified by it listing the
+ // same pod ID in its metadata that the pod's other members do, and
+ // with the pod's infrastructure container having the same value for
+ // both its pod's ID and its container ID.
+ // Pointer arguments can be nil. Either the image name or ID can be
+ // omitted, but not both. All other arguments are required.
+ CreatePodSandbox(systemContext *types.SystemContext, podName, podID, imageName, imageID, containerName, metadataName, uid, namespace string, attempt uint32, copyOptions *copy.Options) (ContainerInfo, error)
+ // RemovePodSandbox deletes a pod sandbox's infrastructure container.
+ // The CRI expects that a sandbox can't be removed unless its only
+ // container is its infrastructure container, but we don't enforce that
+ // here, since we're just keeping track of it for higher level APIs.
+ RemovePodSandbox(idOrName string) error
+
+ // GetContainerMetadata returns the metadata we've stored for a container.
+ GetContainerMetadata(idOrName string) (RuntimeContainerMetadata, error)
+ // SetContainerMetadata updates the metadata we've stored for a container.
+ SetContainerMetadata(idOrName string, metadata RuntimeContainerMetadata) error
+
+ // CreateContainer creates a container with the specified ID.
+ // Pointer arguments can be nil. Either the image name or ID can be
+ // omitted, but not both. All other arguments are required.
+ CreateContainer(systemContext *types.SystemContext, podName, podID, imageName, imageID, containerName, containerID, metadataName string, attempt uint32, mountLabel string, copyOptions *copy.Options) (ContainerInfo, error)
+ // DeleteContainer deletes a container, unmounting it first if need be.
+ DeleteContainer(idOrName string) error
+
+ // StartContainer makes sure a container's filesystem is mounted, and
+ // returns the location of its root filesystem, which is not guaranteed
+ // by lower-level drivers to never change.
+ StartContainer(idOrName string) (string, error)
+ // StopContainer attempts to unmount a container's root filesystem,
+ // freeing up any kernel resources which may be limited.
+ StopContainer(idOrName string) error
+
+ // GetWorkDir returns the path of a nonvolatile directory on the
+ // filesystem (somewhere under the Store's Root directory) which can be
+ // used to store arbitrary data that is specific to the container. It
+ // will be removed automatically when the container is deleted.
+ GetWorkDir(id string) (string, error)
+ // GetRunDir returns the path of a volatile directory (does not survive
+ // the host rebooting, somewhere under the Store's RunRoot directory)
+ // on the filesystem which can be used to store arbitrary data that is
+ // specific to the container. It will be removed automatically when
+ // the container is deleted.
+ GetRunDir(id string) (string, error)
+}
+
+// RuntimeContainerMetadata is the structure that we encode as JSON and store
+// in the metadata field of storage.Container objects. It is used for
+// specifying attributes of pod sandboxes and containers when they are being
+// created, and allows a container's MountLabel, and possibly other values, to
+// be modified in one read/write cycle via calls to
+// RuntimeServer.ContainerMetadata, RuntimeContainerMetadata.SetMountLabel,
+// and RuntimeServer.SetContainerMetadata.
+type RuntimeContainerMetadata struct {
+ // Pod is true if this is the pod's infrastructure container.
+ Pod bool `json:"pod,omitempty"` // Applicable to both PodSandboxes and Containers
+ // The pod's name and ID, kept for use by upper layers in determining
+ // which containers belong to which pods.
+ PodName string `json:"pod-name"` // Applicable to both PodSandboxes and Containers, mandatory
+ PodID string `json:"pod-id"` // Applicable to both PodSandboxes and Containers, mandatory
+ // The provided name and the ID of the image that was used to
+ // instantiate the container.
+ ImageName string `json:"image-name"` // Applicable to both PodSandboxes and Containers
+ ImageID string `json:"image-id"` // Applicable to both PodSandboxes and Containers
+ // The container's name, which for an infrastructure container is usually PodName + "-infra".
+ ContainerName string `json:"name"` // Applicable to both PodSandboxes and Containers, mandatory
+ // The name as originally specified in PodSandbox or Container CRI metadata.
+ MetadataName string `json:"metadata-name"` // Applicable to both PodSandboxes and Containers, mandatory
+ UID string `json:"uid,omitempty"` // Only applicable to pods
+ Namespace string `json:"namespace,omitempty"` // Only applicable to pods
+ Attempt uint32 `json:"attempt,omitempty"` // Applicable to both PodSandboxes and Containers
+ CreatedAt int64 `json:"created-at"` // Applicable to both PodSandboxes and Containers
+ MountLabel string `json:"mountlabel,omitempty"` // Applicable to both PodSandboxes and Containers
+}
+
+// SetMountLabel updates the mount label held by a RuntimeContainerMetadata
+// object.
+func (metadata *RuntimeContainerMetadata) SetMountLabel(mountLabel string) {
+ metadata.MountLabel = mountLabel
+}
+
+func (r *runtimeService) createContainerOrPodSandbox(systemContext *types.SystemContext, podName, podID, imageName, imageID, containerName, containerID, metadataName, uid, namespace string, attempt uint32, mountLabel string, options *copy.Options) (ContainerInfo, error) {
+ var ref types.ImageReference
+ if podName == "" || podID == "" {
+ return ContainerInfo{}, ErrInvalidPodName
+ }
+ if imageName == "" && imageID == "" {
+ return ContainerInfo{}, ErrInvalidImageName
+ }
+ if containerName == "" {
+ return ContainerInfo{}, ErrInvalidContainerName
+ }
+ if metadataName == "" {
+ metadataName = containerName
+ }
+
+ // Check if we have the specified image.
+ ref, err := istorage.Transport.ParseStoreReference(r.storageImageServer.GetStore(), imageName)
+ if err != nil {
+ // Maybe it's some other transport's copy of the image?
+ otherRef, err2 := alltransports.ParseImageName(imageName)
+ if err2 == nil && otherRef.DockerReference() != nil {
+ ref, err = istorage.Transport.ParseStoreReference(r.storageImageServer.GetStore(), otherRef.DockerReference().Name())
+ }
+ if err != nil {
+ // Maybe the image ID is sufficient?
+ ref, err = istorage.Transport.ParseStoreReference(r.storageImageServer.GetStore(), "@"+imageID)
+ if err != nil {
+ return ContainerInfo{}, err
+ }
+ }
+ }
+ img, err := istorage.Transport.GetStoreImage(r.storageImageServer.GetStore(), ref)
+ if img == nil && errors.Cause(err) == storage.ErrImageUnknown && imageName == r.pauseImage {
+ image := imageID
+ if imageName != "" {
+ image = imageName
+ }
+ if image == "" {
+ return ContainerInfo{}, ErrInvalidImageName
+ }
+ logrus.Debugf("couldn't find image %q, retrieving it", image)
+ ref, err = r.storageImageServer.PullImage(systemContext, image, options)
+ if err != nil {
+ return ContainerInfo{}, err
+ }
+ img, err = istorage.Transport.GetStoreImage(r.storageImageServer.GetStore(), ref)
+ if err != nil {
+ return ContainerInfo{}, err
+ }
+ logrus.Debugf("successfully pulled image %q", image)
+ }
+ if img == nil && errors.Cause(err) == storage.ErrImageUnknown {
+ if imageID == "" {
+ return ContainerInfo{}, fmt.Errorf("image %q not present in image store", imageName)
+ }
+ if imageName == "" {
+ return ContainerInfo{}, fmt.Errorf("image with ID %q not present in image store", imageID)
+ }
+ return ContainerInfo{}, fmt.Errorf("image %q with ID %q not present in image store", imageName, imageID)
+ }
+
+ // Pull out a copy of the image's configuration.
+ image, err := ref.NewImage(systemContext)
+ if err != nil {
+ return ContainerInfo{}, err
+ }
+ defer image.Close()
+
+ imageConfig, err := image.OCIConfig()
+ if err != nil {
+ return ContainerInfo{}, err
+ }
+
+ // Update the image name and ID.
+ if imageName == "" && len(img.Names) > 0 {
+ imageName = img.Names[0]
+ }
+ imageID = img.ID
+
+ // Build metadata to store with the container.
+ metadata := RuntimeContainerMetadata{
+ Pod: containerID == podID,
+ PodName: podName,
+ PodID: podID,
+ ImageName: imageName,
+ ImageID: imageID,
+ ContainerName: containerName,
+ MetadataName: metadataName,
+ UID: uid,
+ Namespace: namespace,
+ Attempt: attempt,
+ CreatedAt: time.Now().Unix(),
+ MountLabel: mountLabel,
+ }
+ mdata, err := json.Marshal(&metadata)
+ if err != nil {
+ return ContainerInfo{}, err
+ }
+
+ // Build the container.
+ names := []string{metadata.ContainerName}
+ if metadata.Pod {
+ names = append(names, metadata.PodName)
+ }
+ container, err := r.storageImageServer.GetStore().CreateContainer(containerID, names, img.ID, "", string(mdata), nil)
+ if err != nil {
+ if metadata.Pod {
+ logrus.Debugf("failed to create pod sandbox %s(%s): %v", metadata.PodName, metadata.PodID, err)
+ } else {
+ logrus.Debugf("failed to create container %s(%s): %v", metadata.ContainerName, containerID, err)
+ }
+ return ContainerInfo{}, err
+ }
+ if metadata.Pod {
+ logrus.Debugf("created pod sandbox %q", container.ID)
+ } else {
+ logrus.Debugf("created container %q", container.ID)
+ }
+
+ // If anything fails after this point, we need to delete the incomplete
+ // container before returning.
+ defer func() {
+ if err != nil {
+ if err2 := r.storageImageServer.GetStore().DeleteContainer(container.ID); err2 != nil {
+ if metadata.Pod {
+ logrus.Infof("%v deleting partially-created pod sandbox %q", err2, container.ID)
+ } else {
+ logrus.Infof("%v deleting partially-created container %q", err2, container.ID)
+ }
+ return
+ }
+ logrus.Infof("deleted partially-created container %q", container.ID)
+ }
+ }()
+
+ // Add a name to the container's layer so that it's easier to follow
+ // what's going on if we're just looking at the storage-eye view of things.
+ layerName := metadata.ContainerName + "-layer"
+ names, err = r.storageImageServer.GetStore().Names(container.LayerID)
+ if err != nil {
+ return ContainerInfo{}, err
+ }
+ names = append(names, layerName)
+ err = r.storageImageServer.GetStore().SetNames(container.LayerID, names)
+ if err != nil {
+ return ContainerInfo{}, err
+ }
+
+ // Find out where the container work directories are, so that we can return them.
+ containerDir, err := r.storageImageServer.GetStore().ContainerDirectory(container.ID)
+ if err != nil {
+ return ContainerInfo{}, err
+ }
+ if metadata.Pod {
+ logrus.Debugf("pod sandbox %q has work directory %q", container.ID, containerDir)
+ } else {
+ logrus.Debugf("container %q has work directory %q", container.ID, containerDir)
+ }
+
+ containerRunDir, err := r.storageImageServer.GetStore().ContainerRunDirectory(container.ID)
+ if err != nil {
+ return ContainerInfo{}, err
+ }
+ if metadata.Pod {
+ logrus.Debugf("pod sandbox %q has run directory %q", container.ID, containerRunDir)
+ } else {
+ logrus.Debugf("container %q has run directory %q", container.ID, containerRunDir)
+ }
+
+ return ContainerInfo{
+ ID: container.ID,
+ Dir: containerDir,
+ RunDir: containerRunDir,
+ Config: imageConfig,
+ }, nil
+}
+
+func (r *runtimeService) CreatePodSandbox(systemContext *types.SystemContext, podName, podID, imageName, imageID, containerName, metadataName, uid, namespace string, attempt uint32, copyOptions *copy.Options) (ContainerInfo, error) {
+ return r.createContainerOrPodSandbox(systemContext, podName, podID, imageName, imageID, containerName, podID, metadataName, uid, namespace, attempt, "", copyOptions)
+}
+
+func (r *runtimeService) CreateContainer(systemContext *types.SystemContext, podName, podID, imageName, imageID, containerName, containerID, metadataName string, attempt uint32, mountLabel string, copyOptions *copy.Options) (ContainerInfo, error) {
+ return r.createContainerOrPodSandbox(systemContext, podName, podID, imageName, imageID, containerName, containerID, metadataName, "", "", attempt, mountLabel, copyOptions)
+}
+
+func (r *runtimeService) RemovePodSandbox(idOrName string) error {
+ container, err := r.storageImageServer.GetStore().Container(idOrName)
+ if err != nil {
+ if errors.Cause(err) == storage.ErrContainerUnknown {
+ return ErrInvalidSandboxID
+ }
+ return err
+ }
+ err = r.storageImageServer.GetStore().DeleteContainer(container.ID)
+ if err != nil {
+ logrus.Debugf("failed to delete pod sandbox %q: %v", container.ID, err)
+ return err
+ }
+ return nil
+}
+
+func (r *runtimeService) DeleteContainer(idOrName string) error {
+ if idOrName == "" {
+ return ErrInvalidContainerID
+ }
+ container, err := r.storageImageServer.GetStore().Container(idOrName)
+ if err != nil {
+ return err
+ }
+ err = r.storageImageServer.GetStore().DeleteContainer(container.ID)
+ if err != nil {
+ logrus.Debugf("failed to delete container %q: %v", container.ID, err)
+ return err
+ }
+ return nil
+}
+
+func (r *runtimeService) SetContainerMetadata(idOrName string, metadata RuntimeContainerMetadata) error {
+ mdata, err := json.Marshal(&metadata)
+ if err != nil {
+ logrus.Debugf("failed to encode metadata for %q: %v", idOrName, err)
+ return err
+ }
+ return r.storageImageServer.GetStore().SetMetadata(idOrName, string(mdata))
+}
+
+func (r *runtimeService) GetContainerMetadata(idOrName string) (RuntimeContainerMetadata, error) {
+ metadata := RuntimeContainerMetadata{}
+ mdata, err := r.storageImageServer.GetStore().Metadata(idOrName)
+ if err != nil {
+ return metadata, err
+ }
+ if err = json.Unmarshal([]byte(mdata), &metadata); err != nil {
+ return metadata, err
+ }
+ return metadata, nil
+}
+
+func (r *runtimeService) StartContainer(idOrName string) (string, error) {
+ container, err := r.storageImageServer.GetStore().Container(idOrName)
+ if err != nil {
+ if errors.Cause(err) == storage.ErrContainerUnknown {
+ return "", ErrInvalidContainerID
+ }
+ return "", err
+ }
+ metadata := RuntimeContainerMetadata{}
+ if err = json.Unmarshal([]byte(container.Metadata), &metadata); err != nil {
+ return "", err
+ }
+ mountPoint, err := r.storageImageServer.GetStore().Mount(container.ID, metadata.MountLabel)
+ if err != nil {
+ logrus.Debugf("failed to mount container %q: %v", container.ID, err)
+ return "", err
+ }
+ logrus.Debugf("mounted container %q at %q", container.ID, mountPoint)
+ return mountPoint, nil
+}
+
+func (r *runtimeService) StopContainer(idOrName string) error {
+ if idOrName == "" {
+ return ErrInvalidContainerID
+ }
+ container, err := r.storageImageServer.GetStore().Container(idOrName)
+ if err != nil {
+ return err
+ }
+ err = r.storageImageServer.GetStore().Unmount(container.ID)
+ if err != nil {
+ logrus.Debugf("failed to unmount container %q: %v", container.ID, err)
+ return err
+ }
+ logrus.Debugf("unmounted container %q", container.ID)
+ return nil
+}
+
+func (r *runtimeService) GetWorkDir(id string) (string, error) {
+ container, err := r.storageImageServer.GetStore().Container(id)
+ if err != nil {
+ if errors.Cause(err) == storage.ErrContainerUnknown {
+ return "", ErrInvalidContainerID
+ }
+ return "", err
+ }
+ return r.storageImageServer.GetStore().ContainerDirectory(container.ID)
+}
+
+func (r *runtimeService) GetRunDir(id string) (string, error) {
+ container, err := r.storageImageServer.GetStore().Container(id)
+ if err != nil {
+ if errors.Cause(err) == storage.ErrContainerUnknown {
+ return "", ErrInvalidContainerID
+ }
+ return "", err
+ }
+ return r.storageImageServer.GetStore().ContainerRunDirectory(container.ID)
+}
+
+// GetRuntimeService returns a RuntimeServer that uses the passed-in image
+// service to pull and manage images, and its store to manage containers based
+// on those images.
+func GetRuntimeService(storageImageServer ImageServer, pauseImage string) RuntimeServer {
+ return &runtimeService{
+ storageImageServer: storageImageServer,
+ pauseImage: pauseImage,
+ }
+}