mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	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:
		| @@ -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 New Issue
	
	Block a user