code.gitea.io/gitea@v1.21.7/services/task/task.go (about)

     1  // Copyright 2019 Gitea. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package task
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	admin_model "code.gitea.io/gitea/models/admin"
    11  	"code.gitea.io/gitea/models/db"
    12  	repo_model "code.gitea.io/gitea/models/repo"
    13  	user_model "code.gitea.io/gitea/models/user"
    14  	"code.gitea.io/gitea/modules/graceful"
    15  	"code.gitea.io/gitea/modules/json"
    16  	"code.gitea.io/gitea/modules/log"
    17  	base "code.gitea.io/gitea/modules/migration"
    18  	"code.gitea.io/gitea/modules/queue"
    19  	"code.gitea.io/gitea/modules/secret"
    20  	"code.gitea.io/gitea/modules/setting"
    21  	"code.gitea.io/gitea/modules/structs"
    22  	"code.gitea.io/gitea/modules/timeutil"
    23  	"code.gitea.io/gitea/modules/util"
    24  	repo_service "code.gitea.io/gitea/services/repository"
    25  )
    26  
    27  // taskQueue is a global queue of tasks
    28  var taskQueue *queue.WorkerPoolQueue[*admin_model.Task]
    29  
    30  // Run a task
    31  func Run(ctx context.Context, t *admin_model.Task) error {
    32  	switch t.Type {
    33  	case structs.TaskTypeMigrateRepo:
    34  		return runMigrateTask(ctx, t)
    35  	default:
    36  		return fmt.Errorf("Unknown task type: %d", t.Type)
    37  	}
    38  }
    39  
    40  // Init will start the service to get all unfinished tasks and run them
    41  func Init() error {
    42  	taskQueue = queue.CreateSimpleQueue(graceful.GetManager().ShutdownContext(), "task", handler)
    43  	if taskQueue == nil {
    44  		return fmt.Errorf("unable to create task queue")
    45  	}
    46  	go graceful.GetManager().RunWithCancel(taskQueue)
    47  	return nil
    48  }
    49  
    50  func handler(items ...*admin_model.Task) []*admin_model.Task {
    51  	for _, task := range items {
    52  		if err := Run(db.DefaultContext, task); err != nil {
    53  			log.Error("Run task failed: %v", err)
    54  		}
    55  	}
    56  	return nil
    57  }
    58  
    59  // MigrateRepository add migration repository to task
    60  func MigrateRepository(ctx context.Context, doer, u *user_model.User, opts base.MigrateOptions) error {
    61  	task, err := CreateMigrateTask(ctx, doer, u, opts)
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	return taskQueue.Push(task)
    67  }
    68  
    69  // CreateMigrateTask creates a migrate task
    70  func CreateMigrateTask(ctx context.Context, doer, u *user_model.User, opts base.MigrateOptions) (*admin_model.Task, error) {
    71  	// encrypt credentials for persistence
    72  	var err error
    73  	opts.CloneAddrEncrypted, err = secret.EncryptSecret(setting.SecretKey, opts.CloneAddr)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  	opts.CloneAddr = util.SanitizeCredentialURLs(opts.CloneAddr)
    78  	opts.AuthPasswordEncrypted, err = secret.EncryptSecret(setting.SecretKey, opts.AuthPassword)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	opts.AuthPassword = ""
    83  	opts.AuthTokenEncrypted, err = secret.EncryptSecret(setting.SecretKey, opts.AuthToken)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	opts.AuthToken = ""
    88  	bs, err := json.Marshal(&opts)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	task := &admin_model.Task{
    94  		DoerID:         doer.ID,
    95  		OwnerID:        u.ID,
    96  		Type:           structs.TaskTypeMigrateRepo,
    97  		Status:         structs.TaskStatusQueued,
    98  		PayloadContent: string(bs),
    99  	}
   100  
   101  	if err := admin_model.CreateTask(ctx, task); err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	repo, err := repo_service.CreateRepositoryDirectly(ctx, doer, u, repo_service.CreateRepoOptions{
   106  		Name:           opts.RepoName,
   107  		Description:    opts.Description,
   108  		OriginalURL:    opts.OriginalURL,
   109  		GitServiceType: opts.GitServiceType,
   110  		IsPrivate:      opts.Private,
   111  		IsMirror:       opts.Mirror,
   112  		Status:         repo_model.RepositoryBeingMigrated,
   113  	})
   114  	if err != nil {
   115  		task.EndTime = timeutil.TimeStampNow()
   116  		task.Status = structs.TaskStatusFailed
   117  		err2 := task.UpdateCols(ctx, "end_time", "status")
   118  		if err2 != nil {
   119  			log.Error("UpdateCols Failed: %v", err2.Error())
   120  		}
   121  		return nil, err
   122  	}
   123  
   124  	task.RepoID = repo.ID
   125  	if err = task.UpdateCols(ctx, "repo_id"); err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	return task, nil
   130  }
   131  
   132  // RetryMigrateTask retry a migrate task
   133  func RetryMigrateTask(ctx context.Context, repoID int64) error {
   134  	migratingTask, err := admin_model.GetMigratingTask(ctx, repoID)
   135  	if err != nil {
   136  		log.Error("GetMigratingTask: %v", err)
   137  		return err
   138  	}
   139  	if migratingTask.Status == structs.TaskStatusQueued || migratingTask.Status == structs.TaskStatusRunning {
   140  		return nil
   141  	}
   142  
   143  	// TODO Need to removing the storage/database garbage brought by the failed task
   144  
   145  	// Reset task status and messages
   146  	migratingTask.Status = structs.TaskStatusQueued
   147  	migratingTask.Message = ""
   148  	if err = migratingTask.UpdateCols(ctx, "status", "message"); err != nil {
   149  		log.Error("task.UpdateCols failed: %v", err)
   150  		return err
   151  	}
   152  
   153  	return taskQueue.Push(migratingTask)
   154  }