Store task errors following migrations and display them (#13246) (#13287)

* Store task errors following migrations and display them

When migrate tasks fail store the error in the task table
and ensure that they show on the status page.

Fix #13242

Signed-off-by: Andrew Thornton <art27@cantab.net>

* Update web_src/js/index.js

* Hide the failed first

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: techknowlogick <techknowlogick@gitea.io>

Co-authored-by: zeripath <art27@cantab.net>
This commit is contained in:
techknowlogick 2020-10-24 01:02:36 -04:00 committed by GitHub
parent 074f7abd95
commit e177728a82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 95 additions and 41 deletions

View file

@ -147,6 +147,27 @@ func GetMigratingTask(repoID int64) (*Task, error) {
return &task, nil return &task, nil
} }
// GetMigratingTaskByID returns the migrating task by repo's id
func GetMigratingTaskByID(id, doerID int64) (*Task, *migration.MigrateOptions, error) {
var task = Task{
ID: id,
DoerID: doerID,
Type: structs.TaskTypeMigrateRepo,
}
has, err := x.Get(&task)
if err != nil {
return nil, nil, err
} else if !has {
return nil, nil, ErrTaskDoesNotExist{id, 0, task.Type}
}
var opts migration.MigrateOptions
if err := json.Unmarshal([]byte(task.PayloadContent), &opts); err != nil {
return nil, nil, err
}
return &task, &opts, nil
}
// FindTaskOptions find all tasks // FindTaskOptions find all tasks
type FindTaskOptions struct { type FindTaskOptions struct {
Status int Status int

View file

@ -20,7 +20,7 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
func handleCreateError(owner *models.User, err error, name string) error { func handleCreateError(owner *models.User, err error) error {
switch { switch {
case models.IsErrReachLimitOfRepo(err): case models.IsErrReachLimitOfRepo(err):
return fmt.Errorf("You have already reached your limit of %d repositories", owner.MaxCreationLimit()) return fmt.Errorf("You have already reached your limit of %d repositories", owner.MaxCreationLimit())
@ -38,8 +38,8 @@ func handleCreateError(owner *models.User, err error, name string) error {
func runMigrateTask(t *models.Task) (err error) { func runMigrateTask(t *models.Task) (err error) {
defer func() { defer func() {
if e := recover(); e != nil { if e := recover(); e != nil {
err = fmt.Errorf("PANIC whilst trying to do migrate task: %v\nStacktrace: %v", err, log.Stack(2)) err = fmt.Errorf("PANIC whilst trying to do migrate task: %v", e)
log.Critical("PANIC during runMigrateTask[%d] by DoerID[%d] to RepoID[%d] for OwnerID[%d]: %v", t.ID, t.DoerID, t.RepoID, t.OwnerID, err) log.Critical("PANIC during runMigrateTask[%d] by DoerID[%d] to RepoID[%d] for OwnerID[%d]: %v\nStacktrace: %v", t.ID, t.DoerID, t.RepoID, t.OwnerID, e, log.Stack(2))
} }
if err == nil { if err == nil {
@ -55,7 +55,8 @@ func runMigrateTask(t *models.Task) (err error) {
t.EndTime = timeutil.TimeStampNow() t.EndTime = timeutil.TimeStampNow()
t.Status = structs.TaskStatusFailed t.Status = structs.TaskStatusFailed
t.Errors = err.Error() t.Errors = err.Error()
if err := t.UpdateCols("status", "errors", "end_time"); err != nil { t.RepoID = 0
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)
} }
@ -66,8 +67,8 @@ func runMigrateTask(t *models.Task) (err error) {
} }
}() }()
if err := t.LoadRepo(); err != nil { if err = t.LoadRepo(); err != nil {
return err return
} }
// if repository is ready, then just finsih the task // if repository is ready, then just finsih the task
@ -75,33 +76,35 @@ func runMigrateTask(t *models.Task) (err error) {
return nil return nil
} }
if err := t.LoadDoer(); err != nil { if err = t.LoadDoer(); err != nil {
return err return
} }
if err := t.LoadOwner(); err != nil { if err = t.LoadOwner(); err != nil {
return err return
} }
t.StartTime = timeutil.TimeStampNow() t.StartTime = timeutil.TimeStampNow()
t.Status = structs.TaskStatusRunning t.Status = structs.TaskStatusRunning
if err := t.UpdateCols("start_time", "status"); err != nil { if err = t.UpdateCols("start_time", "status"); err != nil {
return err return
} }
var opts *migration.MigrateOptions var opts *migration.MigrateOptions
opts, err = t.MigrateConfig() opts, err = t.MigrateConfig()
if err != nil { if err != nil {
return err return
} }
opts.MigrateToRepoID = t.RepoID opts.MigrateToRepoID = t.RepoID
repo, err := migrations.MigrateRepository(graceful.GetManager().HammerContext(), t.Doer, t.Owner.Name, *opts) var repo *models.Repository
repo, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), t.Doer, t.Owner.Name, *opts)
if err == nil { if err == nil {
log.Trace("Repository migrated [%d]: %s/%s", repo.ID, t.Owner.Name, repo.Name) log.Trace("Repository migrated [%d]: %s/%s", repo.ID, t.Owner.Name, repo.Name)
return nil return
} }
if models.IsErrRepoAlreadyExist(err) { if models.IsErrRepoAlreadyExist(err) {
return errors.New("The repository name is already used") err = errors.New("The repository name is already used")
return
} }
// remoteAddr may contain credentials, so we sanitize it // remoteAddr may contain credentials, so we sanitize it
@ -113,5 +116,7 @@ func runMigrateTask(t *models.Task) (err error) {
return fmt.Errorf("Migration failed: %v", err.Error()) return fmt.Errorf("Migration failed: %v", err.Error())
} }
return handleCreateError(t.Owner, err, "MigratePost") // do not be tempted to coalesce this line with the return
err = handleCreateError(t.Owner, err)
return
} }

BIN
public/img/failed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -402,19 +402,3 @@ func Download(ctx *context.Context) {
ctx.ServeFile(archivePath, ctx.Repo.Repository.Name+"-"+refName+ext) ctx.ServeFile(archivePath, ctx.Repo.Repository.Name+"-"+refName+ext)
} }
// Status returns repository's status
func Status(ctx *context.Context) {
task, err := models.GetMigratingTask(ctx.Repo.Repository.ID)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err,
})
return
}
ctx.JSON(200, map[string]interface{}{
"status": ctx.Repo.Repository.Status,
"err": task.Errors,
})
}

View file

@ -479,6 +479,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/forgot_password", user.ForgotPasswd) m.Get("/forgot_password", user.ForgotPasswd)
m.Post("/forgot_password", user.ForgotPasswdPost) m.Post("/forgot_password", user.ForgotPasswdPost)
m.Post("/logout", user.SignOut) m.Post("/logout", user.SignOut)
m.Get("/task/:task", user.TaskStatus)
}) })
// ***** END: User ***** // ***** END: User *****
@ -986,8 +987,6 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/archive/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.Download) m.Get("/archive/*", repo.MustBeNotEmpty, reqRepoCodeReader, repo.Download)
m.Get("/status", reqRepoCodeReader, repo.Status)
m.Group("/branches", func() { m.Group("/branches", func() {
m.Get("", repo.Branches) m.Get("", repo.Branches)
}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader) }, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)

30
routers/user/task.go Normal file
View file

@ -0,0 +1,30 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package user
import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
)
// TaskStatus returns task's status
func TaskStatus(ctx *context.Context) {
task, opts, err := models.GetMigratingTaskByID(ctx.ParamsInt64("task"), ctx.User.ID)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err,
})
return
}
ctx.JSON(200, map[string]interface{}{
"status": task.Status,
"err": task.Errors,
"repo-id": task.RepoID,
"repo-name": opts.RepoName,
"start": task.StartTime,
"end": task.EndTime,
})
}

View file

@ -7,11 +7,16 @@
{{template "base/alert" .}} {{template "base/alert" .}}
<div class="home"> <div class="home">
<div class="ui stackable middle very relaxed page grid"> <div class="ui stackable middle very relaxed page grid">
<div id="repo_migrating" class="sixteen wide center aligned centered column" repo="{{.Repo.Repository.FullName}}"> <div id="repo_migrating" class="sixteen wide center aligned centered column" task="{{.MigrateTask.ID}}">
<div> <div>
<img src="{{StaticUrlPrefix}}/img/loading.png"/> <img src="{{StaticUrlPrefix}}/img/loading.png"/>
</div> </div>
</div> </div>
<div id="repo_migrating_failed_image" class="sixteen wide center aligned centered column" style="display: none;">
<div>
<img src="{{StaticUrlPrefix}}/img/failed.png"/>
</div>
</div>
</div> </div>
<div class="ui stackable middle very relaxed page grid"> <div class="ui stackable middle very relaxed page grid">
<div class="sixteen wide center aligned centered column"> <div class="sixteen wide center aligned centered column">
@ -20,6 +25,7 @@
</div> </div>
<div id="repo_migrating_failed"> <div id="repo_migrating_failed">
<p>{{.i18n.Tr "repo.migrate.migrating_failed" .CloneAddr | Safe}}</p> <p>{{.i18n.Tr "repo.migrate.migrating_failed" .CloneAddr | Safe}}</p>
<p id="repo_migrating_failed_error"></p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -191,25 +191,32 @@ function updateIssuesMeta(url, action, issueIds, elementId) {
function initRepoStatusChecker() { function initRepoStatusChecker() {
const migrating = $('#repo_migrating'); const migrating = $('#repo_migrating');
$('#repo_migrating_failed').hide(); $('#repo_migrating_failed').hide();
$('#repo_migrating_failed_image').hide();
if (migrating) { if (migrating) {
const repo_name = migrating.attr('repo'); const task = migrating.attr('task');
if (typeof repo_name === 'undefined') { if (typeof task === 'undefined') {
return; return;
} }
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
url: `${AppSubUrl}/${repo_name}/status`, url: `${AppSubUrl}/user/task/${task}`,
data: { data: {
_csrf: csrf, _csrf: csrf,
}, },
complete(xhr) { complete(xhr) {
if (xhr.status === 200) { if (xhr.status === 200) {
if (xhr.responseJSON) { if (xhr.responseJSON) {
if (xhr.responseJSON.status === 0) { if (xhr.responseJSON.status === 4) {
window.location.reload(); window.location.reload();
return; return;
} else if (xhr.responseJSON.status === 3) {
$('#repo_migrating_progress').hide();
$('#repo_migrating').hide();
$('#repo_migrating_failed').show();
$('#repo_migrating_failed_image').show();
$('#repo_migrating_failed_error').text(xhr.responseJSON.err);
return;
} }
setTimeout(() => { setTimeout(() => {
initRepoStatusChecker(); initRepoStatusChecker();
}, 2000); }, 2000);
@ -217,7 +224,9 @@ function initRepoStatusChecker() {
} }
} }
$('#repo_migrating_progress').hide(); $('#repo_migrating_progress').hide();
$('#repo_migrating').hide();
$('#repo_migrating_failed').show(); $('#repo_migrating_failed').show();
$('#repo_migrating_failed_image').show();
} }
}); });
} }