func dbTagManifest()

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
}