Merge pull request #1337 from nalind/fix-runasuser-cache
imageService: cache information about images
This commit is contained in:
commit
ea90be40c4
4 changed files with 193 additions and 50 deletions
|
@ -5,6 +5,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/containers/image/copy"
|
"github.com/containers/image/copy"
|
||||||
"github.com/containers/image/docker/reference"
|
"github.com/containers/image/docker/reference"
|
||||||
|
@ -39,6 +40,7 @@ type ImageResult struct {
|
||||||
Size *uint64
|
Size *uint64
|
||||||
Digest digest.Digest
|
Digest digest.Digest
|
||||||
ConfigDigest digest.Digest
|
ConfigDigest digest.Digest
|
||||||
|
User string
|
||||||
}
|
}
|
||||||
|
|
||||||
type indexInfo struct {
|
type indexInfo struct {
|
||||||
|
@ -46,12 +48,23 @@ type indexInfo struct {
|
||||||
secure bool
|
secure bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A set of information that we prefer to cache about images, so that we can
|
||||||
|
// avoid having to reread them every time we need to return information about
|
||||||
|
// images.
|
||||||
|
type imageCacheItem struct {
|
||||||
|
user string
|
||||||
|
size *uint64
|
||||||
|
configDigest digest.Digest
|
||||||
|
}
|
||||||
|
|
||||||
type imageService struct {
|
type imageService struct {
|
||||||
store storage.Store
|
store storage.Store
|
||||||
defaultTransport string
|
defaultTransport string
|
||||||
insecureRegistryCIDRs []*net.IPNet
|
insecureRegistryCIDRs []*net.IPNet
|
||||||
indexConfigs map[string]*indexInfo
|
indexConfigs map[string]*indexInfo
|
||||||
registries []string
|
registries []string
|
||||||
|
imageCache map[string]imageCacheItem
|
||||||
|
imageCacheLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// sizer knows its size.
|
// sizer knows its size.
|
||||||
|
@ -171,16 +184,39 @@ func (svc *imageService) ListImages(systemContext *types.SystemContext, filter s
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if image, err := istorage.Transport.GetStoreImage(svc.store, ref); err == nil {
|
if image, err := istorage.Transport.GetStoreImage(svc.store, ref); err == nil {
|
||||||
|
var user string
|
||||||
|
var size *uint64
|
||||||
|
var configDigest digest.Digest
|
||||||
|
if cacheItem, ok := svc.imageCache[image.ID]; ok {
|
||||||
|
user, size, configDigest = cacheItem.user, cacheItem.size, cacheItem.configDigest
|
||||||
|
} else {
|
||||||
img, err := ref.NewImageSource(systemContext)
|
img, err := ref.NewImageSource(systemContext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
size := imageSize(img)
|
size = imageSize(img)
|
||||||
configDigest, err := imageConfigDigest(img, nil)
|
configDigest, err = imageConfigDigest(img, nil)
|
||||||
img.Close()
|
img.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
imageFull, err := ref.NewImage(systemContext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer imageFull.Close()
|
||||||
|
imageConfig, err := imageFull.OCIConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
user = imageConfig.Config.User
|
||||||
|
cacheItem := imageCacheItem{
|
||||||
|
user: user,
|
||||||
|
size: size,
|
||||||
|
configDigest: configDigest,
|
||||||
|
}
|
||||||
|
svc.imageCache[image.ID] = cacheItem
|
||||||
|
}
|
||||||
name, tags, digests := sortNamesByType(image.Names)
|
name, tags, digests := sortNamesByType(image.Names)
|
||||||
imageDigest, repoDigests := svc.makeRepoDigests(digests, tags, image.ID)
|
imageDigest, repoDigests := svc.makeRepoDigests(digests, tags, image.ID)
|
||||||
results = append(results, ImageResult{
|
results = append(results, ImageResult{
|
||||||
|
@ -191,6 +227,7 @@ func (svc *imageService) ListImages(systemContext *types.SystemContext, filter s
|
||||||
Size: size,
|
Size: size,
|
||||||
Digest: imageDigest,
|
Digest: imageDigest,
|
||||||
ConfigDigest: configDigest,
|
ConfigDigest: configDigest,
|
||||||
|
User: user,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -198,7 +235,39 @@ func (svc *imageService) ListImages(systemContext *types.SystemContext, filter s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
visited := make(map[string]struct{})
|
||||||
|
defer func() {
|
||||||
|
// We built a map using IDs of images that we looked
|
||||||
|
// at, so remove any items from the cache that don't
|
||||||
|
// correspond to any of those IDs.
|
||||||
|
removedIDs := make([]string, 0, len(svc.imageCache))
|
||||||
|
for imageID := range svc.imageCache {
|
||||||
|
if _, keep := visited[imageID]; !keep {
|
||||||
|
// We have cached data for an image
|
||||||
|
// with this ID, but it's not in the
|
||||||
|
// list of images now, so the image has
|
||||||
|
// been removed.
|
||||||
|
removedIDs = append(removedIDs, imageID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle the removals.
|
||||||
|
svc.imageCacheLock.Lock()
|
||||||
|
for _, removedID := range removedIDs {
|
||||||
|
delete(svc.imageCache, removedID)
|
||||||
|
}
|
||||||
|
svc.imageCacheLock.Unlock()
|
||||||
|
}()
|
||||||
for _, image := range images {
|
for _, image := range images {
|
||||||
|
visited[image.ID] = struct{}{}
|
||||||
|
var user string
|
||||||
|
var size *uint64
|
||||||
|
var configDigest digest.Digest
|
||||||
|
svc.imageCacheLock.Lock()
|
||||||
|
cacheItem, ok := svc.imageCache[image.ID]
|
||||||
|
svc.imageCacheLock.Unlock()
|
||||||
|
if ok {
|
||||||
|
user, size, configDigest = cacheItem.user, cacheItem.size, cacheItem.configDigest
|
||||||
|
} else {
|
||||||
ref, err := istorage.Transport.ParseStoreReference(svc.store, "@"+image.ID)
|
ref, err := istorage.Transport.ParseStoreReference(svc.store, "@"+image.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -207,12 +276,32 @@ func (svc *imageService) ListImages(systemContext *types.SystemContext, filter s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
size := imageSize(img)
|
size = imageSize(img)
|
||||||
configDigest, err := imageConfigDigest(img, nil)
|
configDigest, err = imageConfigDigest(img, nil)
|
||||||
img.Close()
|
img.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
imageFull, err := ref.NewImage(systemContext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer imageFull.Close()
|
||||||
|
|
||||||
|
imageConfig, err := imageFull.OCIConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
user = imageConfig.Config.User
|
||||||
|
cacheItem := imageCacheItem{
|
||||||
|
user: user,
|
||||||
|
size: size,
|
||||||
|
configDigest: configDigest,
|
||||||
|
}
|
||||||
|
svc.imageCacheLock.Lock()
|
||||||
|
svc.imageCache[image.ID] = cacheItem
|
||||||
|
svc.imageCacheLock.Unlock()
|
||||||
|
}
|
||||||
name, tags, digests := sortNamesByType(image.Names)
|
name, tags, digests := sortNamesByType(image.Names)
|
||||||
imageDigest, repoDigests := svc.makeRepoDigests(digests, tags, image.ID)
|
imageDigest, repoDigests := svc.makeRepoDigests(digests, tags, image.ID)
|
||||||
results = append(results, ImageResult{
|
results = append(results, ImageResult{
|
||||||
|
@ -223,6 +312,7 @@ func (svc *imageService) ListImages(systemContext *types.SystemContext, filter s
|
||||||
Size: size,
|
Size: size,
|
||||||
Digest: imageDigest,
|
Digest: imageDigest,
|
||||||
ConfigDigest: configDigest,
|
ConfigDigest: configDigest,
|
||||||
|
User: user,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,6 +336,16 @@ func (svc *imageService) ImageStatus(systemContext *types.SystemContext, nameOrI
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
imageFull, err := ref.NewImage(systemContext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer imageFull.Close()
|
||||||
|
|
||||||
|
imageConfig, err := imageFull.OCIConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
img, err := ref.NewImageSource(systemContext)
|
img, err := ref.NewImageSource(systemContext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -268,6 +368,7 @@ func (svc *imageService) ImageStatus(systemContext *types.SystemContext, nameOrI
|
||||||
Size: size,
|
Size: size,
|
||||||
Digest: imageDigest,
|
Digest: imageDigest,
|
||||||
ConfigDigest: configDigest,
|
ConfigDigest: configDigest,
|
||||||
|
User: imageConfig.Config.User,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &result, nil
|
return &result, nil
|
||||||
|
@ -585,6 +686,7 @@ func GetImageService(store storage.Store, defaultTransport string, insecureRegis
|
||||||
indexConfigs: make(map[string]*indexInfo, 0),
|
indexConfigs: make(map[string]*indexInfo, 0),
|
||||||
insecureRegistryCIDRs: make([]*net.IPNet, 0),
|
insecureRegistryCIDRs: make([]*net.IPNet, 0),
|
||||||
registries: cleanRegistries,
|
registries: cleanRegistries,
|
||||||
|
imageCache: make(map[string]imageCacheItem),
|
||||||
}
|
}
|
||||||
|
|
||||||
insecureRegistries = append(insecureRegistries, "127.0.0.0/8")
|
insecureRegistries = append(insecureRegistries, "127.0.0.0/8")
|
||||||
|
|
|
@ -449,7 +449,7 @@ func setupContainerUser(specgen *generate.Generator, rootfs string, sc *pb.Linux
|
||||||
containerUser := ""
|
containerUser := ""
|
||||||
// Case 1: run as user is set by kubelet
|
// Case 1: run as user is set by kubelet
|
||||||
if sc.GetRunAsUser() != nil {
|
if sc.GetRunAsUser() != nil {
|
||||||
containerUser = strconv.FormatInt(sc.GetRunAsUser().Value, 10)
|
containerUser = strconv.FormatInt(sc.GetRunAsUser().GetValue(), 10)
|
||||||
} else {
|
} else {
|
||||||
// Case 2: run as username is set by kubelet
|
// Case 2: run as username is set by kubelet
|
||||||
userName := sc.GetRunAsUsername()
|
userName := sc.GetRunAsUsername()
|
||||||
|
|
|
@ -31,20 +31,20 @@ func (s *Server) ListImages(ctx context.Context, req *pb.ListImagesRequest) (res
|
||||||
}
|
}
|
||||||
resp = &pb.ListImagesResponse{}
|
resp = &pb.ListImagesResponse{}
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
if result.Size != nil {
|
resImg := &pb.Image{
|
||||||
resp.Images = append(resp.Images, &pb.Image{
|
|
||||||
Id: result.ID,
|
Id: result.ID,
|
||||||
RepoTags: result.RepoTags,
|
RepoTags: result.RepoTags,
|
||||||
RepoDigests: result.RepoDigests,
|
RepoDigests: result.RepoDigests,
|
||||||
Size_: *result.Size,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
resp.Images = append(resp.Images, &pb.Image{
|
|
||||||
Id: result.ID,
|
|
||||||
RepoTags: result.RepoTags,
|
|
||||||
RepoDigests: result.RepoDigests,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
uid, username := getUserFromImage(result.User)
|
||||||
|
if uid != nil {
|
||||||
|
resImg.Uid = &pb.Int64Value{Value: *uid}
|
||||||
|
}
|
||||||
|
resImg.Username = username
|
||||||
|
if result.Size != nil {
|
||||||
|
resImg.Size_ = *result.Size
|
||||||
|
}
|
||||||
|
resp.Images = append(resp.Images, resImg)
|
||||||
}
|
}
|
||||||
logrus.Debugf("ListImagesResponse: %+v", resp)
|
logrus.Debugf("ListImagesResponse: %+v", resp)
|
||||||
return resp, nil
|
return resp, nil
|
||||||
|
|
|
@ -2,6 +2,8 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
|
@ -37,14 +39,21 @@ func (s *Server) ImageStatus(ctx context.Context, req *pb.ImageStatusRequest) (r
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// match just the first registry as that's what kube meant
|
var (
|
||||||
image = images[0]
|
notfound bool
|
||||||
|
lastErr error
|
||||||
|
)
|
||||||
|
for _, image := range images {
|
||||||
status, err := s.StorageImageServer().ImageStatus(s.ImageContext(), image)
|
status, err := s.StorageImageServer().ImageStatus(s.ImageContext(), image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Cause(err) == storage.ErrImageUnknown {
|
if errors.Cause(err) == storage.ErrImageUnknown {
|
||||||
return &pb.ImageStatusResponse{}, nil
|
logrus.Warnf("imageStatus: can't find %s", image)
|
||||||
|
notfound = true
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
return nil, err
|
logrus.Warnf("imageStatus: error getting status from %s: %v", image, err)
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
resp = &pb.ImageStatusResponse{
|
resp = &pb.ImageStatusResponse{
|
||||||
Image: &pb.Image{
|
Image: &pb.Image{
|
||||||
|
@ -54,6 +63,38 @@ func (s *Server) ImageStatus(ctx context.Context, req *pb.ImageStatusRequest) (r
|
||||||
Size_: *status.Size,
|
Size_: *status.Size,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
uid, username := getUserFromImage(status.User)
|
||||||
|
if uid != nil {
|
||||||
|
resp.Image.Uid = &pb.Int64Value{Value: *uid}
|
||||||
|
}
|
||||||
|
resp.Image.Username = username
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if lastErr != nil && resp == nil {
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
||||||
|
if notfound && resp == nil {
|
||||||
|
return &pb.ImageStatusResponse{}, nil
|
||||||
|
}
|
||||||
logrus.Debugf("ImageStatusResponse: %+v", resp)
|
logrus.Debugf("ImageStatusResponse: %+v", resp)
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getUserFromImage gets uid or user name of the image user.
|
||||||
|
// If user is numeric, it will be treated as uid; or else, it is treated as user name.
|
||||||
|
func getUserFromImage(user string) (*int64, string) {
|
||||||
|
// return both empty if user is not specified in the image.
|
||||||
|
if user == "" {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
// split instances where the id may contain user:group
|
||||||
|
user = strings.Split(user, ":")[0]
|
||||||
|
// user could be either uid or user name. Try to interpret as numeric uid.
|
||||||
|
uid, err := strconv.ParseInt(user, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
// If user is non numeric, assume it's user name.
|
||||||
|
return nil, user
|
||||||
|
}
|
||||||
|
// If user is a numeric uid.
|
||||||
|
return &uid, ""
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue