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 }