f3b7065bd8
The image's canonical reference is a name with a digest of the image's manifest, so in imageService.ImageStatus() and imageService.ListImages(), divide the image's name list into tagged and digested values, and if we have names, add canonical versions. In Server.ContainerStatus(), return the image name as it was given to us as the image, and the image digested reference as the image reference. In Server.ListImages(), be sure to only return tagged names in the RepoTags field. In Server.ImageStatus(), also return canonical references in the RepoDigests field. In Server.PullImage(), be sure that we consistently return the same image reference for an image, whether we ended up pulling it or not. Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
135 lines
3.7 KiB
Go
135 lines
3.7 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containers/image/copy"
|
|
"github.com/containers/image/types"
|
|
"github.com/kubernetes-incubator/cri-o/pkg/storage"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/net/context"
|
|
pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
|
)
|
|
|
|
// PullImage pulls a image with authentication config.
|
|
func (s *Server) PullImage(ctx context.Context, req *pb.PullImageRequest) (resp *pb.PullImageResponse, err error) {
|
|
const operation = "pull_image"
|
|
defer func() {
|
|
recordOperation(operation, time.Now())
|
|
recordError(operation, err)
|
|
}()
|
|
|
|
logrus.Debugf("PullImageRequest: %+v", req)
|
|
// TODO: what else do we need here? (Signatures when the story isn't just pulling from docker://)
|
|
image := ""
|
|
img := req.GetImage()
|
|
if img != nil {
|
|
image = img.Image
|
|
}
|
|
|
|
var (
|
|
images []string
|
|
pulled string
|
|
)
|
|
images, err = s.StorageImageServer().ResolveNames(image)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, img := range images {
|
|
var (
|
|
username string
|
|
password string
|
|
)
|
|
if req.GetAuth() != nil {
|
|
username = req.GetAuth().Username
|
|
password = req.GetAuth().Password
|
|
if req.GetAuth().Auth != "" {
|
|
username, password, err = decodeDockerAuth(req.GetAuth().Auth)
|
|
if err != nil {
|
|
logrus.Debugf("error decoding authentication for image %s: %v", img, err)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
options := ©.Options{
|
|
SourceCtx: &types.SystemContext{},
|
|
}
|
|
// Specifying a username indicates the user intends to send authentication to the registry.
|
|
if username != "" {
|
|
options.SourceCtx = &types.SystemContext{
|
|
DockerAuthConfig: &types.DockerAuthConfig{
|
|
Username: username,
|
|
Password: password,
|
|
},
|
|
}
|
|
}
|
|
|
|
var canPull bool
|
|
canPull, err = s.StorageImageServer().CanPull(img, options)
|
|
if err != nil && !canPull {
|
|
logrus.Debugf("error checking image %s: %v", img, err)
|
|
continue
|
|
}
|
|
|
|
// let's be smart, docker doesn't repull if image already exists.
|
|
var storedImage *storage.ImageResult
|
|
storedImage, err = s.StorageImageServer().ImageStatus(s.ImageContext(), img)
|
|
if err == nil {
|
|
tmpImg, err := s.StorageImageServer().PrepareImage(s.ImageContext(), img, options)
|
|
if err == nil {
|
|
tmpImgConfigDigest := tmpImg.ConfigInfo().Digest
|
|
if tmpImgConfigDigest.String() == "" {
|
|
// this means we are playing with a schema1 image, in which
|
|
// case, we're going to repull the image in any case
|
|
logrus.Debugf("image config digest is empty, re-pulling image")
|
|
} else if tmpImgConfigDigest.String() == storedImage.ConfigDigest.String() {
|
|
logrus.Debugf("image %s already in store, skipping pull", img)
|
|
pulled = img
|
|
break
|
|
}
|
|
}
|
|
logrus.Debugf("image in store has different ID, re-pulling %s", img)
|
|
}
|
|
|
|
_, err = s.StorageImageServer().PullImage(s.ImageContext(), img, options)
|
|
if err != nil {
|
|
logrus.Debugf("error pulling image %s: %v", img, err)
|
|
continue
|
|
}
|
|
pulled = img
|
|
break
|
|
}
|
|
if pulled == "" && err != nil {
|
|
return nil, err
|
|
}
|
|
status, err := s.StorageImageServer().ImageStatus(s.ImageContext(), pulled)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
imageRef := status.ID
|
|
if len(status.RepoDigests) > 0 {
|
|
imageRef = status.RepoDigests[0]
|
|
}
|
|
resp = &pb.PullImageResponse{
|
|
ImageRef: imageRef,
|
|
}
|
|
logrus.Debugf("PullImageResponse: %+v", resp)
|
|
return resp, nil
|
|
}
|
|
|
|
func decodeDockerAuth(s string) (string, string, error) {
|
|
decoded, err := base64.StdEncoding.DecodeString(s)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
parts := strings.SplitN(string(decoded), ":", 2)
|
|
if len(parts) != 2 {
|
|
// if it's invalid just skip, as docker does
|
|
return "", "", nil
|
|
}
|
|
user := parts[0]
|
|
password := strings.Trim(parts[1], "\x00")
|
|
return user, password, nil
|
|
}
|