Correctly handle failed migrations (#17575)

* Correctly handle failed migrations

There is a bug in handling failed migrations whereby the migration task gets decoupled
from the migration repository. This leads to a failure of the task to get deleted with
the repository and also leads to the migration failed page resulting in a ISE.

This PR removes the zeroing out of the task id from the migration but also makes
the migration handler tolerate missing tasks much nicer.

Fix #17571

Signed-off-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
zeripath 2021-11-13 11:28:50 +00:00 committed by GitHub
parent 47448083a1
commit bab95c3a86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 26 additions and 4 deletions

View file

@ -58,6 +58,9 @@ func runMigrateTask(t *models.Task) (err error) {
t.EndTime = timeutil.TimeStampNow() t.EndTime = timeutil.TimeStampNow()
t.Status = structs.TaskStatusFailed t.Status = structs.TaskStatusFailed
t.Message = err.Error() t.Message = err.Error()
// Ensure that the repo loaded before we zero out the repo ID from the task - thus ensuring that we can delete it
_ = t.LoadRepo()
t.RepoID = 0 t.RepoID = 0
if err := t.UpdateCols("status", "errors", "repo_id", "end_time"); err != nil { if err := t.UpdateCols("status", "errors", "repo_id", "end_time"); err != nil {
log.Error("Task UpdateCols failed: %v", err) log.Error("Task UpdateCols failed: %v", err)

View file

@ -90,7 +90,7 @@ func CreateMigrateTask(doer, u *models.User, opts base.MigrateOptions) (*models.
return nil, err return nil, err
} }
var task = models.Task{ var task = &models.Task{
DoerID: doer.ID, DoerID: doer.ID,
OwnerID: u.ID, OwnerID: u.ID,
Type: structs.TaskTypeMigrateRepo, Type: structs.TaskTypeMigrateRepo,
@ -98,7 +98,7 @@ func CreateMigrateTask(doer, u *models.User, opts base.MigrateOptions) (*models.
PayloadContent: string(bs), PayloadContent: string(bs),
} }
if err := models.CreateTask(&task); err != nil { if err := models.CreateTask(task); err != nil {
return nil, err return nil, err
} }
@ -126,5 +126,5 @@ func CreateMigrateTask(doer, u *models.User, opts base.MigrateOptions) (*models.
return nil, err return nil, err
} }
return &task, nil return task, nil
} }

View file

@ -904,6 +904,7 @@ migrate.migrate = Migrate From %s
migrate.migrating = Migrating from <b>%s</b> ... migrate.migrating = Migrating from <b>%s</b> ...
migrate.migrating_failed = Migrating from <b>%s</b> failed. migrate.migrating_failed = Migrating from <b>%s</b> failed.
migrate.migrating_failed.error = Error: %s migrate.migrating_failed.error = Error: %s
migrate.migrating_failed_no_addr = Migration failed.
migrate.github.description = Migrate data from github.com or other Github instances. migrate.github.description = Migrate data from github.com or other Github instances.
migrate.git.description = Migrate a repository only from any Git service. migrate.git.description = Migrate a repository only from any Git service.
migrate.gitlab.description = Migrate data from gitlab.com or other GitLab instances. migrate.gitlab.description = Migrate data from gitlab.com or other GitLab instances.

View file

@ -583,6 +583,13 @@ func checkHomeCodeViewable(ctx *context.Context) {
if ctx.Repo.Repository.IsBeingCreated() { if ctx.Repo.Repository.IsBeingCreated() {
task, err := models.GetMigratingTask(ctx.Repo.Repository.ID) task, err := models.GetMigratingTask(ctx.Repo.Repository.ID)
if err != nil { if err != nil {
if models.IsErrTaskDoesNotExist(err) {
ctx.Data["Repo"] = ctx.Repo
ctx.Data["CloneAddr"] = ""
ctx.Data["Failed"] = true
ctx.HTML(http.StatusOK, tplMigrating)
return
}
ctx.ServerError("models.GetMigratingTask", err) ctx.ServerError("models.GetMigratingTask", err)
return return
} }

View file

@ -6,6 +6,7 @@ package user
import ( import (
"net/http" "net/http"
"strconv"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
@ -16,6 +17,12 @@ import (
func TaskStatus(ctx *context.Context) { func TaskStatus(ctx *context.Context) {
task, opts, err := models.GetMigratingTaskByID(ctx.ParamsInt64("task"), ctx.User.ID) task, opts, err := models.GetMigratingTaskByID(ctx.ParamsInt64("task"), ctx.User.ID)
if err != nil { if err != nil {
if models.IsErrTaskDoesNotExist(err) {
ctx.JSON(http.StatusNotFound, map[string]interface{}{
"error": "task `" + strconv.FormatInt(ctx.ParamsInt64("task"), 10) + "` does not exist",
})
return
}
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": err, "err": err,
}) })

View file

@ -25,7 +25,11 @@
<p id="repo_migrating_progress_message"></p> <p id="repo_migrating_progress_message"></p>
</div> </div>
<div id="repo_migrating_failed" hidden> <div id="repo_migrating_failed" hidden>
{{if .CloneAddr}}
<p>{{.i18n.Tr "repo.migrate.migrating_failed" .CloneAddr | Safe}}</p> <p>{{.i18n.Tr "repo.migrate.migrating_failed" .CloneAddr | Safe}}</p>
{{else}}
<p>{{.i18n.Tr "repo.migrate.migrating_failed_no_addr" | Safe}}</p>
{{end}}
<p id="repo_migrating_failed_error"></p> <p id="repo_migrating_failed_error"></p>
</div> </div>
{{if and .Failed .Permission.IsAdmin}} {{if and .Failed .Permission.IsAdmin}}