zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/sync/sync.go (about)

     1  //go:build sync
     2  // +build sync
     3  
     4  package sync
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/containers/common/pkg/retry"
    13  	"github.com/containers/image/v5/types"
    14  	"github.com/opencontainers/go-digest"
    15  
    16  	"zotregistry.dev/zot/pkg/log"
    17  	"zotregistry.dev/zot/pkg/scheduler"
    18  )
    19  
    20  // below types are used by containers/image to copy images
    21  // types.ImageReference - describes a registry/repo:tag
    22  // types.SystemContext - describes a registry/oci layout config
    23  
    24  // Sync general functionalities, one service per registry config.
    25  type Service interface {
    26  	// Get next repo from remote /v2/_catalog, will return empty string when there is no repo left.
    27  	GetNextRepo(lastRepo string) (string, error) // used by task scheduler
    28  	// Sync a repo with all of its tags and references (signatures, artifacts, sboms) into ImageStore.
    29  	SyncRepo(ctx context.Context, repo string) error // used by periodically sync
    30  	// Sync an image (repo:tag || repo:digest) into ImageStore.
    31  	SyncImage(ctx context.Context, repo, reference string) error // used by sync on demand
    32  	// Sync a single reference for an image.
    33  	SyncReference(ctx context.Context, repo string, subjectDigestStr string,
    34  		referenceType string) error // used by sync on demand
    35  	// Remove all internal catalog entries.
    36  	ResetCatalog() // used by scheduler to empty out the catalog after a sync periodically roundtrip finishes
    37  	// Sync supports multiple urls per registry, before a sync repo/image/ref 'ping' each url.
    38  	SetNextAvailableURL() error // used by all sync methods
    39  	// Returns retry options from registry config.
    40  	GetRetryOptions() *retry.Options // used by sync on demand to retry in background
    41  }
    42  
    43  // Local and remote registries must implement this interface.
    44  type Registry interface {
    45  	// Get temporary ImageReference, is used by functions in containers/image package
    46  	GetImageReference(repo string, tag string) (types.ImageReference, error)
    47  	// Get local oci layout context, is used by functions in containers/image package
    48  	GetContext() *types.SystemContext
    49  }
    50  
    51  /*
    52  Temporary oci layout, sync first pulls an image to this oci layout (using oci:// transport)
    53  then moves them into ImageStore.
    54  */
    55  type OciLayoutStorage interface {
    56  	Registry
    57  }
    58  
    59  // Remote registry.
    60  type Remote interface {
    61  	Registry
    62  	// Get a list of repos (catalog)
    63  	GetRepositories(ctx context.Context) ([]string, error)
    64  	// Get a list of tags given a repo
    65  	GetRepoTags(repo string) ([]string, error)
    66  	// Get manifest content, mediaType, digest given an ImageReference
    67  	GetManifestContent(imageReference types.ImageReference) ([]byte, string, digest.Digest, error)
    68  	// In the case of public dockerhub images 'library' namespace is added to the repo names of images
    69  	// eg: alpine -> library/alpine
    70  	GetDockerRemoteRepo(repo string) string
    71  }
    72  
    73  // Local registry.
    74  type Destination interface {
    75  	Registry
    76  	// Check if an image is already synced
    77  	CanSkipImage(repo, tag string, imageDigest digest.Digest) (bool, error)
    78  	// CommitImage moves a synced repo/ref from temporary oci layout to ImageStore
    79  	CommitImage(imageReference types.ImageReference, repo, tag string) error
    80  	// Removes image reference, used when copy.Image() errors out
    81  	CleanupImage(imageReference types.ImageReference, repo, reference string) error
    82  }
    83  
    84  type TaskGenerator struct {
    85  	Service      Service
    86  	lastRepo     string
    87  	done         bool
    88  	waitTime     time.Duration
    89  	lastTaskTime time.Time
    90  	maxWaitTime  time.Duration
    91  	lock         *sync.Mutex
    92  	log          log.Logger
    93  }
    94  
    95  func NewTaskGenerator(service Service, maxWaitTime time.Duration, log log.Logger) *TaskGenerator {
    96  	return &TaskGenerator{
    97  		Service:      service,
    98  		done:         false,
    99  		waitTime:     0,
   100  		lastTaskTime: time.Now(),
   101  		lock:         &sync.Mutex{},
   102  		lastRepo:     "",
   103  		maxWaitTime:  maxWaitTime,
   104  		log:          log,
   105  	}
   106  }
   107  
   108  func (gen *TaskGenerator) Name() string {
   109  	return "SyncGenerator"
   110  }
   111  
   112  func (gen *TaskGenerator) Next() (scheduler.Task, error) {
   113  	gen.lock.Lock()
   114  	defer gen.lock.Unlock()
   115  
   116  	if time.Since(gen.lastTaskTime) <= gen.waitTime {
   117  		return nil, nil
   118  	}
   119  
   120  	if err := gen.Service.SetNextAvailableURL(); err != nil {
   121  		gen.increaseWaitTime()
   122  
   123  		return nil, err
   124  	}
   125  
   126  	repo, err := gen.Service.GetNextRepo(gen.lastRepo)
   127  	if err != nil {
   128  		gen.increaseWaitTime()
   129  
   130  		return nil, err
   131  	}
   132  
   133  	gen.resetWaitTime()
   134  
   135  	if repo == "" {
   136  		gen.log.Info().Str("component", "sync").Msg("finished syncing all repositories")
   137  		gen.done = true
   138  
   139  		return nil, nil
   140  	}
   141  
   142  	gen.lastRepo = repo
   143  
   144  	return newSyncRepoTask(gen.lastRepo, gen.Service), nil
   145  }
   146  
   147  func (gen *TaskGenerator) IsDone() bool {
   148  	return gen.done
   149  }
   150  
   151  func (gen *TaskGenerator) IsReady() bool {
   152  	return true
   153  }
   154  
   155  func (gen *TaskGenerator) Reset() {
   156  	gen.lock.Lock()
   157  	defer gen.lock.Unlock()
   158  
   159  	gen.lastRepo = ""
   160  	gen.Service.ResetCatalog()
   161  	gen.done = false
   162  	gen.waitTime = 0
   163  }
   164  
   165  func (gen *TaskGenerator) increaseWaitTime() {
   166  	if gen.waitTime == 0 {
   167  		gen.waitTime = time.Second
   168  	}
   169  
   170  	gen.waitTime *= 2
   171  
   172  	// max wait time should not exceed generator interval.
   173  	if gen.waitTime > gen.maxWaitTime {
   174  		gen.waitTime = gen.maxWaitTime
   175  	}
   176  
   177  	gen.lastTaskTime = time.Now()
   178  }
   179  
   180  // resets wait time.
   181  func (gen *TaskGenerator) resetWaitTime() {
   182  	gen.lastTaskTime = time.Now()
   183  	gen.waitTime = 0
   184  }
   185  
   186  type syncRepoTask struct {
   187  	repo    string
   188  	service Service
   189  }
   190  
   191  func newSyncRepoTask(repo string, service Service) *syncRepoTask {
   192  	return &syncRepoTask{repo, service}
   193  }
   194  
   195  func (srt *syncRepoTask) DoWork(ctx context.Context) error {
   196  	return srt.service.SyncRepo(ctx, srt.repo)
   197  }
   198  
   199  func (srt *syncRepoTask) String() string {
   200  	return fmt.Sprintf("{Name: \"%s\", repository: \"%s\"}",
   201  		srt.Name(), srt.repo)
   202  }
   203  
   204  func (srt *syncRepoTask) Name() string {
   205  	return "SyncTask"
   206  }