mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
160 lines
4.6 KiB
Go
160 lines
4.6 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package task
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
admin_model "github.com/hanzoai/git/models/admin"
|
|
repo_model "github.com/hanzoai/git/models/repo"
|
|
user_model "github.com/hanzoai/git/models/user"
|
|
"github.com/hanzoai/git/modules/graceful"
|
|
"github.com/hanzoai/git/modules/json"
|
|
"github.com/hanzoai/git/modules/log"
|
|
base "github.com/hanzoai/git/modules/migration"
|
|
"github.com/hanzoai/git/modules/queue"
|
|
"github.com/hanzoai/git/modules/secret"
|
|
"github.com/hanzoai/git/modules/setting"
|
|
"github.com/hanzoai/git/modules/structs"
|
|
"github.com/hanzoai/git/modules/timeutil"
|
|
"github.com/hanzoai/git/modules/util"
|
|
repo_service "github.com/hanzoai/git/services/repository"
|
|
)
|
|
|
|
// taskQueue is a global queue of tasks
|
|
var taskQueue *queue.WorkerPoolQueue[*admin_model.Task]
|
|
|
|
// Run a task
|
|
func Run(ctx context.Context, t *admin_model.Task) error {
|
|
switch t.Type {
|
|
case structs.TaskTypeMigrateRepo:
|
|
return runMigrateTask(ctx, t)
|
|
default:
|
|
return fmt.Errorf("Unknown task type: %d", t.Type)
|
|
}
|
|
}
|
|
|
|
// Init will start the service to get all unfinished tasks and run them
|
|
func Init() error {
|
|
taskQueue = queue.CreateSimpleQueue(graceful.GetManager().ShutdownContext(), "task", handler)
|
|
if taskQueue == nil {
|
|
return errors.New("unable to create task queue")
|
|
}
|
|
go graceful.GetManager().RunWithCancel(taskQueue)
|
|
return nil
|
|
}
|
|
|
|
func handler(items ...*admin_model.Task) []*admin_model.Task {
|
|
for _, task := range items {
|
|
if err := Run(graceful.GetManager().ShutdownContext(), task); err != nil {
|
|
log.Error("Run task failed: %v", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MigrateRepository add migration repository to task
|
|
func MigrateRepository(ctx context.Context, doer, u *user_model.User, opts base.MigrateOptions) error {
|
|
task, err := CreateMigrateTask(ctx, doer, u, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return taskQueue.Push(task)
|
|
}
|
|
|
|
// CreateMigrateTask creates a migrate task
|
|
func CreateMigrateTask(ctx context.Context, doer, u *user_model.User, opts base.MigrateOptions) (*admin_model.Task, error) {
|
|
// encrypt credentials for persistence
|
|
var err error
|
|
opts.CloneAddrEncrypted, err = secret.EncryptSecret(setting.SecretKey, opts.CloneAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts.CloneAddr = util.SanitizeCredentialURLs(opts.CloneAddr)
|
|
opts.AuthPasswordEncrypted, err = secret.EncryptSecret(setting.SecretKey, opts.AuthPassword)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts.AuthPassword = ""
|
|
opts.AuthTokenEncrypted, err = secret.EncryptSecret(setting.SecretKey, opts.AuthToken)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts.AuthToken = ""
|
|
opts.AWSSecretAccessKeyEncrypted, err = secret.EncryptSecret(setting.SecretKey, opts.AWSSecretAccessKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts.AWSSecretAccessKey = ""
|
|
bs, err := json.Marshal(&opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
task := &admin_model.Task{
|
|
DoerID: doer.ID,
|
|
OwnerID: u.ID,
|
|
Type: structs.TaskTypeMigrateRepo,
|
|
Status: structs.TaskStatusQueued,
|
|
PayloadContent: string(bs),
|
|
}
|
|
|
|
if err := admin_model.CreateTask(ctx, task); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
repo, err := repo_service.CreateRepositoryDirectly(ctx, doer, u, repo_service.CreateRepoOptions{
|
|
Name: opts.RepoName,
|
|
Description: opts.Description,
|
|
OriginalURL: opts.OriginalURL,
|
|
GitServiceType: opts.GitServiceType,
|
|
IsPrivate: opts.Private || setting.Repository.ForcePrivate,
|
|
IsMirror: opts.Mirror,
|
|
Status: repo_model.RepositoryBeingMigrated,
|
|
}, false)
|
|
if err != nil {
|
|
task.EndTime = timeutil.TimeStampNow()
|
|
task.Status = structs.TaskStatusFailed
|
|
err2 := task.UpdateCols(ctx, "end_time", "status")
|
|
if err2 != nil {
|
|
log.Error("UpdateCols Failed: %v", err2.Error())
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
task.RepoID = repo.ID
|
|
if err = task.UpdateCols(ctx, "repo_id"); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return task, nil
|
|
}
|
|
|
|
// RetryMigrateTask retry a migrate task
|
|
func RetryMigrateTask(ctx context.Context, repoID int64) error {
|
|
migratingTask, err := admin_model.GetMigratingTask(ctx, repoID)
|
|
if err != nil {
|
|
log.Error("GetMigratingTask: %v", err)
|
|
return err
|
|
}
|
|
if migratingTask.Status == structs.TaskStatusQueued || migratingTask.Status == structs.TaskStatusRunning {
|
|
return nil
|
|
}
|
|
|
|
// TODO Need to removing the storage/database garbage brought by the failed task
|
|
|
|
// Reset task status and messages
|
|
migratingTask.Status = structs.TaskStatusQueued
|
|
migratingTask.Message = ""
|
|
if err = migratingTask.UpdateCols(ctx, "status", "message"); err != nil {
|
|
log.Error("task.UpdateCols failed: %v", err)
|
|
return err
|
|
}
|
|
|
|
return taskQueue.Push(migratingTask)
|
|
}
|