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();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue