in registry/handlers/manifests.go [804:896]
func dbTagManifest(ctx context.Context, db datastore.Handler, cache datastore.RepositoryCache, dgst digest.Digest, tagName, path string) error {
l := log.GetLogger(log.WithContext(ctx)).WithFields(log.Fields{"repository": path, "manifest_digest": dgst, "tag_name": tagName})
l.Debug("tagging manifest")
var opts []datastore.RepositoryStoreOption
if cache != nil {
opts = append(opts, datastore.WithRepositoryCache(cache))
}
repositoryStore := datastore.NewRepositoryStore(db, opts...)
dbRepo, err := repositoryStore.FindByPath(ctx, path)
if err != nil {
return err
}
// TODO: If we return the manifest ID from the putDatabase methods, we can
// avoid looking up the manifest by digest.
dbManifest, err := repositoryStore.FindManifestByDigest(ctx, dbRepo, dgst)
if err != nil {
return err
}
if dbManifest == nil {
return fmt.Errorf("manifest %s not found in database", dgst)
}
l.Debug("creating tag")
// We need to find and lock a GC manifest task that is related with the manifest that we're about to tag. This
// is needed to ensure we lock any related online GC tasks to prevent race conditions around the tag creation. See:
// https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/spec/gitlab/online-garbage-collection.md#creating-a-tag-for-an-untagged-manifest
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("failed to create database transaction: %w", err)
}
defer tx.Rollback()
// Prevent long running transactions by setting an upper limit of manifestTagGCLockTimeout. If the GC is holding
// the lock of a related review record, the processing there should be fast enough to avoid this. Regardless, we
// should not let transactions open (and clients waiting) for too long. If this sensible timeout is exceeded, abort
// the tag creation and let the client retry. This will bubble up and lead to a 503 Service Unavailable response.
ctx, cancel := context.WithTimeout(ctx, manifestTagGCLockTimeout)
defer cancel()
mts := datastore.NewGCManifestTaskStore(tx)
if _, err := mts.FindAndLockBefore(ctx, dbRepo.NamespaceID, dbRepo.ID, dbManifest.ID, time.Now().Add(manifestTagGCReviewWindow)); err != nil {
return err
}
// identify and log a tag override event for audit purposes
repositoryStore = datastore.NewRepositoryStore(tx, opts...)
currentManifest, err := repositoryStore.FindManifestByTagName(ctx, dbRepo, tagName)
if err != nil {
return fmt.Errorf("failed to find if tag is already being used: %w", err)
}
// Check immutability patterns only for existing tags (we perform a manifest lookup by tag name, so if such tag
// exists, there will be a manifest here - no need for a separate tag lookup query).
if currentManifest != nil {
if patterns, found := dcontext.TagImmutablePatterns(ctx, path); found {
if err := validateTagImmutability(ctx, tagName, patterns); err != nil {
return err
}
}
}
if currentManifest != nil && (currentManifest.ID != dbManifest.ID) {
l.WithFields(log.Fields{
"root_repo": dbRepo.TopLevelPathSegment(),
"current_manifest_digest": currentManifest.Digest,
}).Info("tag override")
}
tagStore := datastore.NewTagStore(tx)
tag := &models.Tag{
Name: tagName,
NamespaceID: dbRepo.NamespaceID,
RepositoryID: dbRepo.ID,
ManifestID: dbManifest.ID,
}
if err := tagStore.CreateOrUpdate(ctx, tag); err != nil {
return err
}
repoStore := datastore.NewRepositoryStore(tx, opts...)
if err := repoStore.UpdateLastPublishedAt(ctx, dbRepo, tag); err != nil {
return err
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("committing database transaction: %w", err)
}
cache.InvalidateSize(ctx, dbRepo)
return nil
}