Store task errors following migrations and display them (#13246)
* 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>
This commit is contained in:
parent
9b11c3e320
commit
f40a2a4404
8 changed files with 95 additions and 41 deletions
|
@ -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
|
||||||
|
|
|
@ -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
BIN
public/img/failed.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
|
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
@ -490,6 +490,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 *****
|
||||||
|
|
||||||
|
@ -997,8 +998,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
30
routers/user/task.go
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -192,25 +192,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);
|
||||||
|
@ -218,7 +225,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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue