Path: blob/main/components/registry-facade/pkg/registry/imagecfg.go
2499 views
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package registry56import (7"context"8"sync"910"github.com/containerd/containerd/errdefs"11lru "github.com/hashicorp/golang-lru"12ociv1 "github.com/opencontainers/image-spec/specs-go/v1"13"golang.org/x/xerrors"14"google.golang.org/grpc"15"google.golang.org/grpc/connectivity"1617"github.com/gitpod-io/gitpod/registry-facade/api"18)1920// ErrRefInvalid is returned by spec provider who cannot interpret the ref21var ErrRefInvalid = xerrors.Errorf("invalid ref")2223// ImageSpecProvider provide the image spec for an image pull24// based on the ref25type ImageSpecProvider interface {26// GetSpec returns the spec for the image or a wrapped ErrRefInvalid27GetSpec(ctx context.Context, ref string) (*api.ImageSpec, error)28}2930// FixedImageSpecProvider provides a single spec31type FixedImageSpecProvider map[string]*api.ImageSpec3233func (p FixedImageSpecProvider) GetSpec(ctx context.Context, ref string) (*api.ImageSpec, error) {34res, ok := p[ref]35if !ok {36return nil, xerrors.Errorf("%w: %s", ErrRefInvalid, errdefs.ErrNotFound)37}38return res, nil39}4041// NewCompositeSpecProvider aggregates multiple image spec providers42type CompositeSpecProvider struct {43providers []ImageSpecProvider44}4546// NewCompositeSpecProvider produces a new composite image spec provider47func NewCompositeSpecProvider(providers ...ImageSpecProvider) *CompositeSpecProvider {48return &CompositeSpecProvider{49providers: providers,50}51}5253// GetSpec returns the spec for the image or the error of the last image spec provider54func (csp *CompositeSpecProvider) GetSpec(ctx context.Context, ref string) (spec *api.ImageSpec, err error) {55if len(csp.providers) == 0 {56return nil, xerrors.Errorf("no image spec providers configured")57}5859for _, p := range csp.providers {60spec, err = p.GetSpec(ctx, ref)61if err == nil {62return spec, nil63}64}65return66}6768// RemoteSpecProvider queries a remote spec provider using gRPC69type RemoteSpecProvider struct {70addr string71opts []grpc.DialOption72conn *grpc.ClientConn73mu sync.RWMutex74}7576// NewRemoteSpecProvider produces a new remote spec provider77func NewRemoteSpecProvider(addr string, opts []grpc.DialOption) *RemoteSpecProvider {78return &RemoteSpecProvider{79addr: addr,80opts: opts,81}82}8384// GetSpec returns the spec for the image or a wrapped ErrRefInvalid85func (p *RemoteSpecProvider) GetSpec(ctx context.Context, ref string) (*api.ImageSpec, error) {86client, err := p.getClient(ctx)87if err != nil {88return nil, xerrors.Errorf("%w: %s", ErrRefInvalid, err.Error())89}9091resp, err := client.GetImageSpec(ctx, &api.GetImageSpecRequest{Id: ref})92if err != nil {93return nil, xerrors.Errorf("%w: %s", ErrRefInvalid, err.Error())94}95return resp.Spec, nil96}9798func (p *RemoteSpecProvider) getClient(ctx context.Context) (client api.SpecProviderClient, err error) {99isValidConn := func() bool {100return p.conn != nil && p.conn.GetState() != connectivity.TransientFailure101}102103p.mu.RLock()104if isValidConn() {105defer p.mu.RUnlock()106return api.NewSpecProviderClient(p.conn), nil107}108p.mu.RUnlock()109110p.mu.Lock()111defer p.mu.Unlock()112if isValidConn() {113return api.NewSpecProviderClient(p.conn), nil114}115116p.conn, err = grpc.DialContext(ctx, p.addr, p.opts...)117if err != nil {118return nil, err119}120return api.NewSpecProviderClient(p.conn), nil121}122123// NewCachingSpecProvider creates a new LRU caching spec provider with a max number of specs it can cache.124func NewCachingSpecProvider(space int, delegate ImageSpecProvider) (*CachingSpecProvider, error) {125cache, err := lru.New(space)126if err != nil {127return nil, err128}129return &CachingSpecProvider{130Cache: cache,131Delegate: delegate,132}, nil133}134135// CachingSpecProvider caches an image spec in an LRU cache136type CachingSpecProvider struct {137Cache *lru.Cache138Delegate ImageSpecProvider139}140141// GetSpec returns the spec for the image or a wrapped ErrRefInvalid142func (p *CachingSpecProvider) GetSpec(ctx context.Context, ref string) (*api.ImageSpec, error) {143res, ok := p.Cache.Get(ref)144if ok {145return res.(*api.ImageSpec), nil146}147spec, err := p.Delegate.GetSpec(ctx, ref)148if err != nil {149return nil, err150}151p.Cache.Add(ref, spec)152return spec, nil153}154155// ConfigModifier modifies an image's configuration156type ConfigModifier func(ctx context.Context, spec *api.ImageSpec, cfg *ociv1.Image) (layer []ociv1.Descriptor, err error)157158// NewConfigModifierFromLayerSource produces a config modifier from a layer source159func NewConfigModifierFromLayerSource(src LayerSource) ConfigModifier {160return func(ctx context.Context, spec *api.ImageSpec, cfg *ociv1.Image) (layer []ociv1.Descriptor, err error) {161addons, err := src.GetLayer(ctx, spec)162if err != nil {163return164}165envs, err := src.Envs(ctx, spec)166if err != nil {167return168}169170for _, l := range addons {171layer = append(layer, l.Descriptor)172cfg.RootFS.DiffIDs = append(cfg.RootFS.DiffIDs, l.DiffID)173}174175if len(envs) > 0 {176parsed := parseEnvs(cfg.Config.Env)177for _, modifyEnv := range envs {178modifyEnv(parsed)179}180cfg.Config.Env = parsed.serialize()181}182183return184}185}186187188