Skip to content
This repository has been archived by the owner on Mar 16, 2024. It is now read-only.

Commit

Permalink
Merge pull request #2173 from ibuildthecloud/main
Browse files Browse the repository at this point in the history
Cache image metadata to speed image details call
  • Loading branch information
ibuildthecloud committed Sep 22, 2023
2 parents 4254917 + 481c17a commit 83dd102
Show file tree
Hide file tree
Showing 12 changed files with 304 additions and 25 deletions.
16 changes: 16 additions & 0 deletions pkg/apis/internal.acorn.io/v1/imageinstance.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,19 @@ func (in *ImageInstance) ShortID() string {
}
return string(in.UID)
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type ImageMetadataCacheList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ImageMetadataCache `json:"items"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type ImageMetadataCache struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Data []byte `json:"data,omitempty"`
}
2 changes: 2 additions & 0 deletions pkg/apis/internal.acorn.io/v1/scheme.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ func AddToScheme(scheme *runtime.Scheme) error {
&DevSessionInstanceList{},
&ProjectInstance{},
&ProjectInstanceList{},
&ImageMetadataCache{},
&ImageMetadataCacheList{},
)

// Add common types
Expand Down
62 changes: 62 additions & 0 deletions pkg/apis/internal.acorn.io/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 7 additions & 6 deletions pkg/autoupgrade/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import (

"github.com/acorn-io/runtime/pkg/imagepattern"
imagename "github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/sirupsen/logrus"
"k8s.io/utils/strings/slices"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
)

const invalidTag = "+notfound+"

func getTagsForImagePattern(ctx context.Context, c daemonClient, namespace, image string) (imagename.Reference, []string, error) {
func getTagsForImagePattern(ctx context.Context, c daemonClient, namespace, image string, remoteOpts ...remote.Option) (imagename.Reference, []string, error) {
current, err := imagename.ParseReference(image, imagename.WithDefaultRegistry(defaultNoReg))
if err != nil {
return nil, nil, fmt.Errorf("problem parsing image referece %v: %v", image, err)
Expand All @@ -25,7 +26,7 @@ func getTagsForImagePattern(ctx context.Context, c daemonClient, namespace, imag
var tags []string
var pullErr error
if hasValidRegistry {
tags, pullErr = c.listTags(ctx, namespace, image)
tags, pullErr = c.listTags(ctx, namespace, image, remoteOpts...)
}
localTags, err := c.getTagsMatchingRepo(ctx, current, namespace, defaultNoReg)
if err != nil {
Expand All @@ -37,8 +38,8 @@ func getTagsForImagePattern(ctx context.Context, c daemonClient, namespace, imag
return current, append(tags, localTags...), nil
}

func findLatestTagForImageWithPattern(ctx context.Context, c daemonClient, current, namespace, image, pattern string) (string, bool, error) {
ref, tags, err := getTagsForImagePattern(ctx, c, namespace, strings.TrimSuffix(image, ":"+pattern))
func findLatestTagForImageWithPattern(ctx context.Context, c daemonClient, current, namespace, image, pattern string, remoteOpts ...remote.Option) (string, bool, error) {
ref, tags, err := getTagsForImagePattern(ctx, c, namespace, strings.TrimSuffix(image, ":"+pattern), remoteOpts...)
if err != nil {
return "", false, err
}
Expand Down Expand Up @@ -82,8 +83,8 @@ func findLatestTagForImageWithPattern(ctx context.Context, c daemonClient, curre
}

// FindLatestTagForImageWithPattern will return the latest tag for image corresponding to the pattern.
func FindLatestTagForImageWithPattern(ctx context.Context, c kclient.Client, current, namespace, image, pattern string) (string, bool, error) {
return findLatestTagForImageWithPattern(ctx, &client{c}, current, namespace, image, pattern)
func FindLatestTagForImageWithPattern(ctx context.Context, c kclient.Client, current, namespace, image, pattern string, remoteOpts ...remote.Option) (string, bool, error) {
return findLatestTagForImageWithPattern(ctx, &client{c}, current, namespace, image, pattern, remoteOpts...)
}

// FindLatest returns the tag from the tags slice that sorts as the "latest" according to the supplied pattern.
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/appdefinition/pullappimage.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func PullAppImage(transport http.RoundTripper, recorder event.Recorder) router.H
})
}

type pullImageFunc func(ctx context.Context, c kclient.Reader, namespace, image, nestedDigest string, opts ...remote.Option) (*v1.AppImage, error)
type pullImageFunc func(ctx context.Context, c kclient.Client, namespace, image, nestedDigest string, opts ...remote.Option) (*v1.AppImage, error)

type pullClient struct {
pull pullImageFunc
Expand Down
2 changes: 1 addition & 1 deletion pkg/controller/appdefinition/pullappimage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func TestAutoUpgradeImageResolution(t *testing.T) {

func testPullAppImage(transport http.RoundTripper) router.HandlerFunc {
return pullAppImage(transport, pullClient{
pull: func(_ context.Context, _ kclient.Reader, _ string, _ string, _ string, _ ...remote.Option) (*v1.AppImage, error) {
pull: func(_ context.Context, _ kclient.Client, _ string, _ string, _ string, _ ...remote.Option) (*v1.AppImage, error) {
return &v1.AppImage{
Name: "myimage:latest",
Digest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
Expand Down
3 changes: 3 additions & 0 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/acorn-io/runtime/pkg/crds"
"github.com/acorn-io/runtime/pkg/dns"
"github.com/acorn-io/runtime/pkg/event"
"github.com/acorn-io/runtime/pkg/imagemetadatacache"
"github.com/acorn-io/runtime/pkg/imagesystem"
"github.com/acorn-io/runtime/pkg/k8sclient"
"github.com/acorn-io/runtime/pkg/logserver"
Expand Down Expand Up @@ -113,5 +114,7 @@ func (c *Controller) Start(ctx context.Context) error {
// Note: the cache will only be populated for EventInstances if a handler for EventInstances has been registered.
go event.Truncate(ctx, c.Router.Backend(), 5*time.Minute, 1000)

go imagemetadatacache.Purge(ctx, c.client)

return c.Router.Start(ctx)
}
22 changes: 18 additions & 4 deletions pkg/imagedetails/imagedetails.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,25 @@ type GetImageDetailsOptions struct {
}

func GetImageDetails(ctx context.Context, c kclient.Client, namespace, imageName string, opts GetImageDetailsOptions) (*apiv1.ImageDetails, error) {
remoteOpts, err := images.GetAuthenticationRemoteOptions(ctx, c, namespace, opts.RemoteOpts...)
if err != nil {
return nil, err
}

puller, err := remote.NewPuller(remoteOpts...)
if err != nil {
return nil, err
}
opts.RemoteOpts = append(opts.RemoteOpts, remote.Reuse(puller))
return getImageDetails(ctx, c, namespace, imageName, opts)
}

func getImageDetails(ctx context.Context, c kclient.Client, namespace, imageName string, opts GetImageDetailsOptions) (*apiv1.ImageDetails, error) {
imageName = strings.ReplaceAll(imageName, "+", "/")
name := strings.ReplaceAll(imageName, "/", "+")

if tagPattern, isPattern := autoupgrade.AutoUpgradePattern(imageName); isPattern {
if latestImage, found, err := autoupgrade.FindLatestTagForImageWithPattern(ctx, c, "", namespace, imageName, tagPattern); err != nil {
if latestImage, found, err := autoupgrade.FindLatestTagForImageWithPattern(ctx, c, "", namespace, imageName, tagPattern, opts.RemoteOpts...); err != nil {
return nil, err
} else if !found {
// Check and see if no registry was specified on the image.
Expand Down Expand Up @@ -120,7 +134,7 @@ func GetImageDetails(ctx context.Context, c kclient.Client, namespace, imageName

var nestedImages []apiv1.NestedImage
if opts.IncludeNested {
nestedImages, err = getNested(ctx, c, namespace, imageName, details.AppSpec, appImageWithData.AppImage.ImageData, opts.RemoteOpts)
nestedImages, err = getNested(ctx, c, namespace, imageName, details.AppSpec, appImageWithData.AppImage.ImageData, remoteOpts)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -214,7 +228,7 @@ func getNestedAcorns(ctx context.Context, c kclient.Client, namespace, image str
acornImage = image
}

details, err := GetImageDetails(ctx, c, namespace, acornImage, GetImageDetailsOptions{
details, err := getImageDetails(ctx, c, namespace, acornImage, GetImageDetailsOptions{
Profiles: acorn.Profiles,
DeployArgs: acorn.DeployArgs.GetData(),
Nested: nestedImage,
Expand Down Expand Up @@ -247,7 +261,7 @@ func getNestedServices(ctx context.Context, c kclient.Client, namespace, image s
serviceImage = image
}

details, err := GetImageDetails(ctx, c, namespace, serviceImage, GetImageDetailsOptions{
details, err := getImageDetails(ctx, c, namespace, serviceImage, GetImageDetailsOptions{
// Services don't have profiles
Profiles: nil,
DeployArgs: service.ServiceArgs.GetData(),
Expand Down
46 changes: 46 additions & 0 deletions pkg/imagemetadatacache/imagemetadatacache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package imagemetadatacache

import (
"context"
"time"

internalv1 "github.com/acorn-io/runtime/pkg/apis/internal.acorn.io/v1"
"github.com/sirupsen/logrus"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
)

var interval = 24 * time.Hour

func Purge(ctx context.Context, c kclient.Client) {
for {
err := doPurge(ctx, c)
if err != nil {
logrus.Errorf("Failed to purge imagemetadatacache: %v", err)
}
time.Sleep(interval)
}
}

func doPurge(ctx context.Context, c kclient.Client) error {
// We don't want to cache any of this data, so do live API calls
list := &v1.PartialObjectMetadataList{
TypeMeta: v1.TypeMeta{
Kind: "ImageMetadataCacheList",
APIVersion: internalv1.SchemeGroupVersion.String(),
},
}

if err := c.List(ctx, list); err != nil {
return err
}

purge := time.Now().Add(-interval)
for _, item := range list.Items {
if item.CreationTimestamp.Time.Before(purge) {
_ = c.Delete(ctx, &item)
}
}

return nil
}

0 comments on commit 83dd102

Please sign in to comment.