func handleRenameStoreOperation()

in registry/handlers/repositories.go [1140:1227]


func handleRenameStoreOperation(ctx context.Context, w http.ResponseWriter, repo renameStoreParams, cache *iredis.Cache, db *datastore.DB) (bool, error) {
	// create a repository lease store
	var (
		opts      []datastore.RepositoryLeaseStoreOption
		isRenamed bool
	)
	opts = append(opts, datastore.WithRepositoryLeaseCache(datastore.NewCentralRepositoryLeaseCache(cache)))
	rlstore := datastore.NewRepositoryLeaseStore(opts...)

	// verify a valid rename lease exists or create one if one can be created
	lease, err := enforceRenameLease(ctx, rlstore, repo.newPath, repo.source.Path)
	if err != nil {
		return isRenamed, errcode.FromUnknownError(err)
	}

	// set a time limit for the rename operation query (both dry-run and real run)
	var repositoryRenameOperationTTL time.Duration
	if repo.isDryRun {
		repositoryRenameOperationTTL = defaultDryRunRenameOperationTimeout
	} else {
		// selects the lower of `defaultDryRunRenameOperationTimeout` and the TTL of the lease
		repositoryRenameOperationTTL, err = getDynamicRenameOperationTTL(ctx, rlstore, lease)
		if err != nil {
			return isRenamed, errcode.FromUnknownError(err)
		}
	}

	if !repo.isDryRun {
		// enact a lease on the source project path which will be used to block all
		// write operations to the existing repositories in the given GitLab project.
		plStore, err := datastore.NewProjectLeaseStore(datastore.NewCentralProjectLeaseCache(cache))
		if err != nil {
			return isRenamed, errcode.FromUnknownError(err)
		}
		// this lease expires in less than  repositoryRenameOperationTTL + 1 second.
		// where repositoryRenameOperationTTL is at most 5 seconds.
		if err := plStore.Set(ctx, repo.source.Path, (repositoryRenameOperationTTL + 1*time.Second)); err != nil {
			return isRenamed, errcode.FromUnknownError(err)
		}
		defer func() {
			if err := plStore.Invalidate(ctx, repo.source.Path); err != nil {
				errortracking.Capture(err, errortracking.WithContext(ctx), errortracking.WithStackTrace())
			}
		}()
	}

	// start a transaction to rename the repository (and sub-repository attributes)
	// and specify a timeout limit to prevent long running repository rename operations
	txCtx, cancel := context.WithTimeout(ctx, repositoryRenameOperationTTL)
	defer cancel()
	tx, err := db.BeginTx(ctx, nil)
	if err != nil {
		return isRenamed, errcode.FromUnknownError(fmt.Errorf("failed to create database transaction: %w", err))
	}
	defer tx.Rollback()

	// run the rename operation in a transaction
	if err = executeRenameOperation(txCtx, tx, repo.source, repo.isPathOriginRepo, repo.newPath, repo.newName); err != nil {
		return isRenamed, errcode.FromUnknownError(err)
	}

	// only commit the transaction if the request was not a dry-run
	if !repo.isDryRun {
		if err := tx.Commit(); err != nil {
			return isRenamed, errcode.FromUnknownError(fmt.Errorf("failed to commit database transaction: %w", err))
		}
		w.WriteHeader(http.StatusNoContent)
		isRenamed = true

		// When a lease fails to be destroyed after it is no longer needed it should not impact the response to the caller.
		// The lease will eventually expire regardless, but we still need to record these failed cases.
		if err := rlstore.Destroy(ctx, lease); err != nil {
			errortracking.Capture(err, errortracking.WithContext(ctx), errortracking.WithStackTrace())
		}
	} else {
		w.WriteHeader(http.StatusAccepted)
		w.Header().Set("Content-Type", "application/json")

		repositoryRenameOperationTTL, err = rlstore.GetTTL(ctx, lease)
		if err != nil {
			return isRenamed, errcode.FromUnknownError(err)
		}
		if err := json.NewEncoder(w).Encode(&RenameRepositoryAPIResponse{TTL: time.Now().Add(repositoryRenameOperationTTL).UTC()}); err != nil {
			return isRenamed, errcode.FromUnknownError(err)
		}
	}
	return isRenamed, nil
}