diff --git a/src/basecontext/api_context.go b/src/basecontext/api_context.go index a94777b9..6bee1efd 100644 --- a/src/basecontext/api_context.go +++ b/src/basecontext/api_context.go @@ -4,6 +4,7 @@ import ( "context" "github.com/Parallels/prl-devops-service/models" + "github.com/Parallels/prl-devops-service/errors" log "github.com/cjlapao/common-go-logger" ) @@ -11,7 +12,7 @@ type ApiContext interface { Context() context.Context GetAuthorizationContext() *AuthorizationContext GetRequestId() string - GetUser() *models.ApiUser + GetUser(diag *errors.Diagnostics) *models.ApiUser Verbose() bool EnableLog() DisableLog() diff --git a/src/basecontext/main.go b/src/basecontext/main.go index c2f09953..420de3f2 100644 --- a/src/basecontext/main.go +++ b/src/basecontext/main.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/Parallels/prl-devops-service/constants" + "github.com/Parallels/prl-devops-service/errors" "github.com/Parallels/prl-devops-service/models" log "github.com/cjlapao/common-go-logger" ) @@ -98,11 +99,15 @@ func (c *BaseContext) GetRequestId() string { return id.(string) } -func (c *BaseContext) GetUser() *models.ApiUser { - if c.authContext != nil { +func (c *BaseContext) GetUser(diag *errors.Diagnostics) *models.ApiUser { + if c.authContext != nil && c.authContext.User != nil { return c.authContext.User } + if diag != nil { + diag.AddError("401", "User not found or session expired", "GetUser") + } + return nil } diff --git a/src/basecontext/test/mock_base_context.go b/src/basecontext/test/mock_base_context.go index 35dc9c7e..6f8bea55 100644 --- a/src/basecontext/test/mock_base_context.go +++ b/src/basecontext/test/mock_base_context.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/Parallels/prl-devops-service/basecontext" + "github.com/Parallels/prl-devops-service/errors" "github.com/Parallels/prl-devops-service/models" log "github.com/cjlapao/common-go-logger" ) @@ -69,7 +70,7 @@ func (m *MockBaseContext) GetRequestId() string { return m.MockRequestID } -func (m *MockBaseContext) GetUser() *models.ApiUser { +func (m *MockBaseContext) GetUser(diag *errors.Diagnostics) *models.ApiUser { if m.callbackFunctions["GetUser"] != nil { m.callbackFunctions["GetUser"]() } diff --git a/src/controllers/catalog.go b/src/controllers/catalog.go index 72628eb1..f94ab150 100644 --- a/src/controllers/catalog.go +++ b/src/controllers/catalog.go @@ -15,6 +15,7 @@ import ( "github.com/Parallels/prl-devops-service/config" "github.com/Parallels/prl-devops-service/constants" data_models "github.com/Parallels/prl-devops-service/data/models" + prlerrors "github.com/Parallels/prl-devops-service/errors" "github.com/Parallels/prl-devops-service/helpers" "github.com/Parallels/prl-devops-service/jobs" "github.com/Parallels/prl-devops-service/mappers" @@ -1883,6 +1884,7 @@ func PushCatalogManifestHandler() restapi.ControllerHandler { defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) + pushCatalogManifestDiag := prlerrors.NewDiagnostics("/v1/catalog/push") var request catalog_models.PushCatalogManifestRequest if err := http_helper.MapRequestBody(r, &request); err != nil { ReturnApiError(ctx, w, models.ApiErrorResponse{ @@ -1911,9 +1913,9 @@ func PushCatalogManifestHandler() restapi.ControllerHandler { return } - userContext := ctx.GetUser() - if userContext == nil { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + userContext := ctx.GetUser(pushCatalogManifestDiag) + if pushCatalogManifestDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(pushCatalogManifestDiag, http.StatusUnauthorized)) return } @@ -1923,9 +1925,9 @@ func PushCatalogManifestHandler() restapi.ControllerHandler { return } - job, err := jobManager.CreateNewJob(userContext.ID, "catalog", "push", "Initializing catalog push") - if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + job := jobManager.CreateNewJob(userContext.ID, "catalog", "push", "Initializing catalog push", pushCatalogManifestDiag) + if pushCatalogManifestDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(pushCatalogManifestDiag, http.StatusInternalServerError)) return } @@ -1994,10 +1996,10 @@ func AsyncPushCatalogManifestHandler() restapi.ControllerHandler { defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) - - userContext := ctx.GetUser() - if userContext == nil { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + asyncPushCatalogManifestDiag := prlerrors.NewDiagnostics("/v1/catalog/push/async") + userContext := ctx.GetUser(asyncPushCatalogManifestDiag) + if asyncPushCatalogManifestDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(asyncPushCatalogManifestDiag, http.StatusUnauthorized)) return } @@ -2035,9 +2037,9 @@ func AsyncPushCatalogManifestHandler() restapi.ControllerHandler { return } - job, err := jobManager.CreateNewJob(userContext.ID, "catalog", "push", "Initializing catalog push") - if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + job := jobManager.CreateNewJob(userContext.ID, "catalog", "push", "Initializing catalog push", asyncPushCatalogManifestDiag) + if asyncPushCatalogManifestDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(asyncPushCatalogManifestDiag, http.StatusInternalServerError)) return } @@ -2069,6 +2071,7 @@ func PullCatalogManifestHandler() restapi.ControllerHandler { defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) + pullCatalogManifestDiag := prlerrors.NewDiagnostics("/v1/catalog/pull") var request catalog_models.PullCatalogManifestRequest if err := http_helper.MapRequestBody(r, &request); err != nil { ReturnApiError(ctx, w, models.ApiErrorResponse{ @@ -2136,9 +2139,9 @@ func PullCatalogManifestHandler() restapi.ControllerHandler { } } - userContext := ctx.GetUser() - if userContext == nil { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + userContext := ctx.GetUser(pullCatalogManifestDiag) + if pullCatalogManifestDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(pullCatalogManifestDiag, http.StatusUnauthorized)) return } @@ -2148,9 +2151,9 @@ func PullCatalogManifestHandler() restapi.ControllerHandler { return } - job, err := jobManager.CreateNewJob(userContext.ID, "catalog", "pull", "Initializing repository pull") - if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + job := jobManager.CreateNewJob(userContext.ID, "catalog", "pull", "Initializing repository pull", pullCatalogManifestDiag) + if pullCatalogManifestDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(pullCatalogManifestDiag, http.StatusInternalServerError)) return } @@ -2207,10 +2210,10 @@ func AsyncPullCatalogManifestHandler() restapi.ControllerHandler { defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) - - userContext := ctx.GetUser() - if userContext == nil { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + asyncPullCatalogManifestDiag := prlerrors.NewDiagnostics("/v1/catalog/pull/async") + userContext := ctx.GetUser(asyncPullCatalogManifestDiag) + if asyncPullCatalogManifestDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(asyncPullCatalogManifestDiag, http.StatusUnauthorized)) return } @@ -2258,9 +2261,9 @@ func AsyncPullCatalogManifestHandler() restapi.ControllerHandler { return } - job, err := jobManager.CreateNewJob(userContext.ID, "catalog", "pull", "Initializing repository pull") - if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + job := jobManager.CreateNewJob(userContext.ID, "catalog", "pull", "Initializing repository pull", asyncPullCatalogManifestDiag) + if asyncPullCatalogManifestDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(asyncPullCatalogManifestDiag, http.StatusInternalServerError)) return } diff --git a/src/controllers/catalog_managers.go b/src/controllers/catalog_managers.go index a618639d..ed9cf558 100644 --- a/src/controllers/catalog_managers.go +++ b/src/controllers/catalog_managers.go @@ -22,6 +22,7 @@ import ( "github.com/Parallels/prl-devops-service/config" "github.com/Parallels/prl-devops-service/constants" data_models "github.com/Parallels/prl-devops-service/data/models" + prlerrors "github.com/Parallels/prl-devops-service/errors" "github.com/Parallels/prl-devops-service/helpers" "github.com/Parallels/prl-devops-service/jobs" "github.com/Parallels/prl-devops-service/mappers" @@ -378,7 +379,7 @@ func GetCatalogManagersHandler() restapi.ControllerHandler { return } - user := ctx.GetUser() + user := ctx.GetUser(nil) if user == nil { ReturnApiError(ctx, w, models.NewFromErrorWithCode(errors.New("User not contextually found"), http.StatusUnauthorized)) return @@ -475,7 +476,7 @@ func GetCatalogManagerByIdHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := vars["id"] - user := ctx.GetUser() + user := ctx.GetUser(nil) if user == nil { ReturnApiError(ctx, w, models.NewFromErrorWithCode(errors.New("User not contextually found"), http.StatusUnauthorized)) return @@ -556,7 +557,7 @@ func CreateCatalogManagerHandler() restapi.ControllerHandler { } defer r.Body.Close() - user := ctx.GetUser() + user := ctx.GetUser(nil) newMgr := mappers.FromCatalogManagerRequest(&req) newMgr.OwnerID = user.ID @@ -642,7 +643,7 @@ func UpdateCatalogManagerHandler() restapi.ControllerHandler { return } - user := ctx.GetUser() + user := ctx.GetUser(nil) authCtxUpdate := ctx.GetAuthorizationContext() effectiveClaimsUpdate := authCtxUpdate.GetEffectiveClaims() effectiveRolesUpdate := authCtxUpdate.GetEffectiveRoles() @@ -721,7 +722,7 @@ func DeleteCatalogManagerHandler() restapi.ControllerHandler { return } - user := ctx.GetUser() + user := ctx.GetUser(nil) authCtxDelete := ctx.GetAuthorizationContext() effectiveClaimsDelete := authCtxDelete.GetEffectiveClaims() effectiveRolesDelete := authCtxDelete.GetEffectiveRoles() @@ -766,10 +767,10 @@ func AsyncPushCatalogManifestToCatalogManagerHandler() restapi.ControllerHandler defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) - - userContext := ctx.GetUser() - if userContext == nil { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + asyncPushCatalogManifestDiag := prlerrors.NewDiagnostics("AsyncPushCatalogManifestToCatalogManagerHandler") + userContext := ctx.GetUser(asyncPushCatalogManifestDiag) + if asyncPushCatalogManifestDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(asyncPushCatalogManifestDiag, http.StatusUnauthorized)) return } @@ -833,9 +834,9 @@ func AsyncPushCatalogManifestToCatalogManagerHandler() restapi.ControllerHandler return } - localJob, err := jobManager.CreateNewJob(userContext.ID, "catalog", "push", "Initializing catalog push to manager "+mgr.Name) - if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + localJob := jobManager.CreateNewJob(userContext.ID, "catalog", "push", "Initializing catalog push to manager "+mgr.Name, asyncPushCatalogManifestDiag) + if asyncPushCatalogManifestDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(asyncPushCatalogManifestDiag, http.StatusInternalServerError)) return } @@ -936,7 +937,7 @@ func getAuthorizedCatalogManagerForUse(ctx basecontext.ApiContext, managerID str return nil, &apiErr } - user := ctx.GetUser() + user := ctx.GetUser(nil) if user == nil { apiErr := models.NewFromErrorWithCode(errors.New("User not contextually found"), http.StatusUnauthorized) return nil, &apiErr @@ -1298,7 +1299,7 @@ func forwardCatalogManagerRequest(ctx basecontext.ApiContext, w http.ResponseWri // Inject the current user's effective claims and roles so the downstream // catalog service can filter results using the calling user's permissions // rather than the stored catalog-manager credentials. - if fwdUser := ctx.GetUser(); fwdUser != nil { + if fwdUser := ctx.GetUser(nil); fwdUser != nil { fwdAuthCtx := ctx.GetAuthorizationContext() claims := fwdAuthCtx.GetEffectiveClaims() roles := fwdAuthCtx.GetEffectiveRoles() diff --git a/src/controllers/common.go b/src/controllers/common.go index f4c4e5cf..55861575 100644 --- a/src/controllers/common.go +++ b/src/controllers/common.go @@ -18,8 +18,8 @@ import ( // orchestrator-to-host calls, there is no user account on the target, so // "system" is used as a fallback owner. Returns ("", false) when the request // is not authenticated at all. -func getEffectiveCallerID(ctx *basecontext.BaseContext) (string, bool) { - if user := ctx.GetUser(); user != nil { +func getEffectiveCallerID(ctx *basecontext.BaseContext, diag *errors.Diagnostics) (string, bool) { + if user := ctx.GetUser(nil); user != nil { return user.ID, true } authCtx := ctx.GetAuthorizationContext() @@ -35,6 +35,9 @@ func getEffectiveCallerID(ctx *basecontext.BaseContext) (string, bool) { return currentUser, true } + if diag != nil { + diag.AddError("401", "Caller identity not found", "getEffectiveCallerID") + } return "", false } diff --git a/src/controllers/jobs.go b/src/controllers/jobs.go index 707fe7bd..a8077429 100644 --- a/src/controllers/jobs.go +++ b/src/controllers/jobs.go @@ -3,6 +3,7 @@ package controllers import ( "encoding/json" "net/http" + "strconv" "strings" "sync" "time" @@ -80,15 +81,18 @@ func GetJobsHandler() restapi.ControllerHandler { defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) + getJobsDiag := errors.NewDiagnostics("/jobs") dbService, err := serviceprovider.GetDatabaseService(ctx) if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + rsp := models.NewFromError(err) + getJobsDiag.AddError(strconv.Itoa(rsp.Code), rsp.Message, "GetDatabaseService") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(getJobsDiag, rsp.Code)) return } - userContext := ctx.GetUser() - if userContext == nil { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + userContext := ctx.GetUser(getJobsDiag) + if getJobsDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(getJobsDiag, http.StatusUnauthorized)) return } @@ -99,7 +103,9 @@ func GetJobsHandler() restapi.ControllerHandler { if canListAll { dbJobs, err := dbService.GetJobs(ctx) if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + rsp := models.NewFromError(err) + getJobsDiag.AddError(strconv.Itoa(rsp.Code), rsp.Message, "GetJobs") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(getJobsDiag, rsp.Code)) return } for _, dbJob := range dbJobs { @@ -108,7 +114,9 @@ func GetJobsHandler() restapi.ControllerHandler { } else { dbJobs, err := dbService.GetJobsByOwner(ctx, userContext.ID) if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + rsp := models.NewFromError(err) + getJobsDiag.AddError(strconv.Itoa(rsp.Code), rsp.Message, "GetJobsByOwner") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(getJobsDiag, rsp.Code)) return } for _, dbJob := range dbJobs { @@ -131,31 +139,36 @@ func GetJobHandler() restapi.ControllerHandler { defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) + vars := mux.Vars(r) + jobId := vars["id"] + getJobDiag := errors.NewDiagnostics("/jobs/" + jobId) dbService, err := serviceprovider.GetDatabaseService(ctx) if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + rsp := models.NewFromError(err) + getJobDiag.AddError(strconv.Itoa(rsp.Code), rsp.Message, "GetDatabaseService") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(getJobDiag, rsp.Code)) return } - userContext := ctx.GetUser() - if userContext == nil { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + userContext := ctx.GetUser(getJobDiag) + if getJobDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(getJobDiag, http.StatusUnauthorized)) return } - vars := mux.Vars(r) - jobId := vars["id"] - dbJob, err := dbService.GetJob(ctx, jobId) if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusNotFound)) + rsp := models.NewFromError(err) + getJobDiag.AddError(strconv.Itoa(rsp.Code), rsp.Message, "GetJob") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(getJobDiag, rsp.Code)) return } authCtx := ctx.GetAuthorizationContext() canListAll := authCtx != nil && authCtx.UserHasClaim("job_manager_list") if !canListAll && !strings.EqualFold(dbJob.Owner, userContext.ID) { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusForbidden, Message: "Forbidden to view this job"}) + getJobDiag.AddError(strconv.Itoa(http.StatusForbidden), "Forbidden to view this job", "CheckAuthorization") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(getJobDiag, http.StatusForbidden)) return } @@ -172,10 +185,10 @@ func GetJobHandler() restapi.ControllerHandler { // @Tags Jobs // @Param id path string true "Job ID" // @Success 204 -// @Failure 400 {object} models.ApiErrorResponse +// @Failure 400 {object} models.ApiErrorDiagnosticsResponse // @Failure 401 {object} models.OAuthErrorResponse -// @Failure 403 {object} models.ApiErrorResponse -// @Failure 404 {object} models.ApiErrorResponse +// @Failure 403 {object} models.ApiErrorDiagnosticsResponse +// @Failure 404 {object} models.ApiErrorDiagnosticsResponse // @Security ApiKeyAuth // @Security BearerAuth // @Router /v1/jobs/{id} [delete] @@ -184,43 +197,49 @@ func DeleteJobHandler() restapi.ControllerHandler { defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) - - userContext := ctx.GetUser() - if userContext == nil { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) - return - } - vars := mux.Vars(r) jobId := vars["id"] + deleteJobDiag := errors.NewDiagnostics("/jobs/" + jobId) + userContext := ctx.GetUser(deleteJobDiag) + if deleteJobDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(deleteJobDiag, http.StatusUnauthorized)) + return + } dbService, err := serviceprovider.GetDatabaseService(ctx) if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + rsp := models.NewFromError(err) + deleteJobDiag.AddError(strconv.Itoa(rsp.Code), rsp.Message, "GetDatabaseService") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(deleteJobDiag, rsp.Code)) return } dbJob, err := dbService.GetJob(ctx, jobId) if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusNotFound)) + rsp := models.NewFromError(err) + deleteJobDiag.AddError(strconv.Itoa(rsp.Code), rsp.Message, "GetJob") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(deleteJobDiag, rsp.Code)) return } authCtx := ctx.GetAuthorizationContext() canDeleteAll := authCtx != nil && authCtx.UserHasClaim("job_manager_delete") if !canDeleteAll && !strings.EqualFold(dbJob.Owner, userContext.ID) { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusForbidden, Message: "Forbidden to delete this job"}) + deleteJobDiag.AddError(strconv.Itoa(http.StatusForbidden), "Forbidden to delete this job", "CheckAuthorization") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(deleteJobDiag, http.StatusForbidden)) return } jobManager := jobs.Get(ctx) if jobManager == nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(errors.New("Job Manager is not available"), http.StatusInternalServerError)) + deleteJobDiag.AddError(strconv.Itoa(http.StatusInternalServerError), "Job Manager is not available", "GetJobManager") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(deleteJobDiag, http.StatusInternalServerError)) return } - if err := jobManager.DeleteJob(jobId); err != nil { - ReturnApiError(ctx, w, models.NewFromError(err)) + jobManager.DeleteJob(jobId, deleteJobDiag) + if deleteJobDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(deleteJobDiag, http.StatusInternalServerError)) return } @@ -234,15 +253,18 @@ func CleanupJobsHandler() restapi.ControllerHandler { defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) + cleanupJobsDiag := errors.NewDiagnostics("/jobs/cleanup") dbService, err := serviceprovider.GetDatabaseService(ctx) if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + rsp := models.NewFromError(err) + cleanupJobsDiag.AddError(strconv.Itoa(rsp.Code), rsp.Message, "GetDatabaseService") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(cleanupJobsDiag, rsp.Code)) return } - userContext := ctx.GetUser() - if userContext == nil { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + userContext := ctx.GetUser(cleanupJobsDiag) + if cleanupJobsDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(cleanupJobsDiag, http.StatusUnauthorized)) return } @@ -252,13 +274,17 @@ func CleanupJobsHandler() restapi.ControllerHandler { if canListAll { err = dbService.DeleteJobsByState(ctx, constants.JobStateCompleted, constants.JobStateFailed) if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + rsp := models.NewFromError(err) + cleanupJobsDiag.AddError(strconv.Itoa(rsp.Code), rsp.Message, "DeleteJobsByState") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(cleanupJobsDiag, rsp.Code)) return } } else { dbJobs, err := dbService.GetJobsByOwner(ctx, userContext.ID) if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + rsp := models.NewFromError(err) + cleanupJobsDiag.AddError(strconv.Itoa(rsp.Code), rsp.Message, "GetJobsByOwner") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(cleanupJobsDiag, rsp.Code)) return } @@ -286,22 +312,24 @@ func DebugJobHandler() restapi.ControllerHandler { defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) - - userContext := ctx.GetUser() - if userContext == nil { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + debugJobDiag := errors.NewDiagnostics("/jobs/debug") + userContext := ctx.GetUser(debugJobDiag) + if debugJobDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(debugJobDiag, http.StatusUnauthorized)) return } var request models.JobCreateRequest if err := http_helper.MapRequestBody(r, &request); err != nil { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusBadRequest, Message: "Invalid request body"}) + debugJobDiag.AddError(strconv.Itoa(http.StatusBadRequest), "Invalid request body", "MapRequestBody") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(debugJobDiag, http.StatusBadRequest)) return } jobManager := jobs.Get(ctx) if jobManager == nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(errors.New("Job Manager is not available"), http.StatusInternalServerError)) + debugJobDiag.AddError(strconv.Itoa(http.StatusInternalServerError), "Job Manager is not available", "GetJobManager") + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(debugJobDiag, http.StatusInternalServerError)) return } @@ -310,9 +338,9 @@ func DebugJobHandler() restapi.ControllerHandler { action = "Debug Task" } - job, err := jobManager.CreateNewJob(userContext.ID, request.JobType, request.JobOperation, action) - if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + job := jobManager.CreateNewJob(userContext.ID, request.JobType, request.JobOperation, action, debugJobDiag) + if debugJobDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(debugJobDiag, http.StatusInternalServerError)) return } @@ -342,7 +370,11 @@ func runDebugProfileSimple(jobId string, jobManager *jobs.JobManagerService) { ns := tracker.GetProgressService() bCtx := basecontext.NewRootBaseContext() bCtx.LogInfof("[Debug/simple] Starting job %s", jobId) - + defer func() { + if r := recover(); r != nil { + bCtx.LogErrorf("Panic in debug profile: %v", r) + } + }() if ns != nil { ns.NotifyJobMessage(jobId, "Initializing debug background environment...") time.Sleep(1 * time.Second) @@ -373,6 +405,11 @@ func runDebugProfilePullRemote(jobId string, jobManager *jobs.JobManagerService) } bCtx := basecontext.NewRootBaseContext() bCtx.LogInfof("[Debug/pull_remote] Starting job %s", jobId) + defer func() { + if r := recover(); r != nil { + bCtx.LogErrorf("Panic in debug profile: %v", r) + } + }() // Register the same step configuration as catalog/pull.go (no caching) ns.RegisterJobWorkflow(jobId, []tracker.JobStep{ @@ -480,7 +517,11 @@ func runDebugProfilePullCache(jobId string, jobManager *jobs.JobManagerService) } bCtx := basecontext.NewRootBaseContext() bCtx.LogInfof("[Debug/pull_cache] Starting job %s", jobId) - + defer func() { + if r := recover(); r != nil { + bCtx.LogErrorf("Panic in debug profile: %v", r) + } + }() ns.RegisterJobWorkflow(jobId, []tracker.JobStep{ {Name: constants.ActionValidatingRequest, Weight: 3, Parallel: false, HasPercentage: false}, {Name: constants.ActionCheckingLocalCatalog, Weight: 3, Parallel: false, HasPercentage: false}, @@ -555,6 +596,11 @@ func runDebugProfileSkippedSteps(jobId string, jobManager *jobs.JobManagerServic } bCtx := basecontext.NewRootBaseContext() bCtx.LogInfof("[Debug/skipped_steps] Starting job %s", jobId) + defer func() { + if r := recover(); r != nil { + bCtx.LogErrorf("Panic in debug profile: %v", r) + } + }() ns.RegisterJobWorkflow(jobId, []tracker.JobStep{ {Name: constants.ActionValidatingRequest, Weight: 2, Parallel: false, HasPercentage: false}, diff --git a/src/controllers/machines.go b/src/controllers/machines.go index ce319ad7..e8add1b1 100644 --- a/src/controllers/machines.go +++ b/src/controllers/machines.go @@ -14,6 +14,7 @@ import ( "github.com/Parallels/prl-devops-service/config" "github.com/Parallels/prl-devops-service/constants" "github.com/Parallels/prl-devops-service/errors" + prlerrors "github.com/Parallels/prl-devops-service/errors" "github.com/Parallels/prl-devops-service/jobs" "github.com/Parallels/prl-devops-service/mappers" "github.com/Parallels/prl-devops-service/models" @@ -1155,7 +1156,7 @@ func CreateVirtualMachineHandler() restapi.ControllerHandler { defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) - + createMachineDiag := prlerrors.NewDiagnostics("/v1/machines") var request models.CreateVirtualMachineRequest if err := http_helper.MapRequestBody(r, &request); err != nil { ReturnApiError(ctx, w, models.ApiErrorResponse{ @@ -1225,9 +1226,9 @@ func CreateVirtualMachineHandler() restapi.ControllerHandler { return } - callerID, ok := getEffectiveCallerID(ctx) + callerID, ok := getEffectiveCallerID(ctx, createMachineDiag) if !ok { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(createMachineDiag, http.StatusUnauthorized)) return } @@ -1237,9 +1238,9 @@ func CreateVirtualMachineHandler() restapi.ControllerHandler { return } - job, err := jobManager.CreateNewJob(callerID, "machines", "create", "Initializing catalog machine creation") - if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + job := jobManager.CreateNewJob(callerID, "machines", "create", "Initializing catalog machine creation", createMachineDiag) + if createMachineDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(createMachineDiag, http.StatusInternalServerError)) return } @@ -1285,10 +1286,10 @@ func AsyncCreateVirtualMachineHandler() restapi.ControllerHandler { defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) - - callerID, ok := getEffectiveCallerID(ctx) + asyncCreateVirtualMachineDiag := prlerrors.NewDiagnostics("/v1/machines/async") + callerID, ok := getEffectiveCallerID(ctx, asyncCreateVirtualMachineDiag) if !ok { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(asyncCreateVirtualMachineDiag, http.StatusUnauthorized)) return } @@ -1377,9 +1378,9 @@ func AsyncCreateVirtualMachineHandler() restapi.ControllerHandler { return } - job, err := jobManager.CreateNewJob(callerID, "machines", "create", "Initializing catalog machine creation") - if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + job := jobManager.CreateNewJob(callerID, "machines", "create", "Initializing catalog machine creation", asyncCreateVirtualMachineDiag) + if asyncCreateVirtualMachineDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(asyncCreateVirtualMachineDiag, http.StatusInternalServerError)) return } diff --git a/src/controllers/orchestrator.go b/src/controllers/orchestrator.go index 0e7da828..702f6ae6 100644 --- a/src/controllers/orchestrator.go +++ b/src/controllers/orchestrator.go @@ -10,6 +10,7 @@ import ( "github.com/Parallels/prl-devops-service/basecontext" "github.com/Parallels/prl-devops-service/config" "github.com/Parallels/prl-devops-service/constants" + prlerrors "github.com/Parallels/prl-devops-service/errors" "github.com/Parallels/prl-devops-service/jobs" "github.com/Parallels/prl-devops-service/mappers" "github.com/Parallels/prl-devops-service/models" @@ -2964,10 +2965,9 @@ func CreateOrchestratorHostVirtualMachineHandler() restapi.ControllerHandler { ctx := GetBaseContext(r) defer Recover(ctx, r, w) var request models.CreateVirtualMachineRequest - vars := mux.Vars(r) id := vars["id"] - + createOrchestratorHostMachineDiag := prlerrors.NewDiagnostics("/v1/orchestrator/hosts/" + id + "/machines") if err := http_helper.MapRequestBody(r, &request); err != nil { ReturnApiError(ctx, w, models.ApiErrorResponse{ Message: "Invalid request body: " + err.Error(), @@ -3000,9 +3000,9 @@ func CreateOrchestratorHostVirtualMachineHandler() restapi.ControllerHandler { request.CatalogManifest.CatalogManagerId = "" } - callerID, ok := getEffectiveCallerID(ctx) + callerID, ok := getEffectiveCallerID(ctx, createOrchestratorHostMachineDiag) if !ok { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(createOrchestratorHostMachineDiag, http.StatusUnauthorized)) return } @@ -3012,9 +3012,9 @@ func CreateOrchestratorHostVirtualMachineHandler() restapi.ControllerHandler { return } - job, jobErr := jobManager.CreateNewJob(callerID, "orchestrator", "create", fmt.Sprintf("Initializing virtual machine creation on host %s", id)) - if jobErr != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(jobErr, http.StatusInternalServerError)) + job := jobManager.CreateNewJob(callerID, "orchestrator", "create", fmt.Sprintf("Initializing virtual machine creation on host %s", id), createOrchestratorHostMachineDiag) + if createOrchestratorHostMachineDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(createOrchestratorHostMachineDiag, http.StatusInternalServerError)) return } @@ -3060,7 +3060,7 @@ func CreateOrchestratorVirtualMachineHandler() restapi.ControllerHandler { ctx := GetBaseContext(r) defer Recover(ctx, r, w) var request models.CreateVirtualMachineRequest - + createOrchestratorVMDiag := prlerrors.NewDiagnostics("/v1/orchestrator/machines") if err := http_helper.MapRequestBody(r, &request); err != nil { ReturnApiError(ctx, w, models.ApiErrorResponse{ Message: "Invalid request body: " + err.Error(), @@ -3094,9 +3094,9 @@ func CreateOrchestratorVirtualMachineHandler() restapi.ControllerHandler { request.CatalogManifest.CatalogManagerId = "" } - callerID, ok := getEffectiveCallerID(ctx) + callerID, ok := getEffectiveCallerID(ctx, createOrchestratorVMDiag) if !ok { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(createOrchestratorVMDiag, http.StatusUnauthorized)) return } @@ -3106,9 +3106,9 @@ func CreateOrchestratorVirtualMachineHandler() restapi.ControllerHandler { return } - job, jobErr := jobManager.CreateNewJob(callerID, "orchestrator", "create", "Initializing orchestrator virtual machine creation") - if jobErr != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(jobErr, http.StatusInternalServerError)) + job := jobManager.CreateNewJob(callerID, "orchestrator", "create", "Initializing orchestrator virtual machine creation", createOrchestratorVMDiag) + if createOrchestratorVMDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(createOrchestratorVMDiag, http.StatusInternalServerError)) return } @@ -3992,7 +3992,7 @@ func DeployOrchestratorHostHandler() restapi.ControllerHandler { defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) - + deployOrchestratorHostDiag := prlerrors.NewDiagnostics("/v1/orchestrator/hosts/deploy") var req models.DeployOrchestratorHostRequest if err := http_helper.MapRequestBody(r, &req); err != nil { ReturnApiError(ctx, w, models.ApiErrorResponse{ @@ -4017,9 +4017,9 @@ func DeployOrchestratorHostHandler() restapi.ControllerHandler { return } - callerID, ok := getEffectiveCallerID(ctx) + callerID, ok := getEffectiveCallerID(ctx, deployOrchestratorHostDiag) if !ok { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(deployOrchestratorHostDiag, http.StatusUnauthorized)) return } @@ -4029,9 +4029,9 @@ func DeployOrchestratorHostHandler() restapi.ControllerHandler { return } - job, jobErr := jobManager.CreateNewJob(callerID, "orchestrator", "deploy", "Deploying agent "+req.HostName) - if jobErr != nil { - ReturnApiError(ctx, w, models.NewFromError(jobErr)) + job := jobManager.CreateNewJob(callerID, "orchestrator", "deploy", "Deploying agent "+req.HostName, deployOrchestratorHostDiag) + if deployOrchestratorHostDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(deployOrchestratorHostDiag, http.StatusInternalServerError)) return } @@ -4081,7 +4081,7 @@ func AsyncDeployOrchestratorHostHandler() restapi.ControllerHandler { defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) - + asyncDeployOrchestratorHostDiag := prlerrors.NewDiagnostics("/v1/orchestrator/hosts/deploy/async") var req models.DeployOrchestratorHostRequest if err := http_helper.MapRequestBody(r, &req); err != nil { ReturnApiError(ctx, w, models.ApiErrorResponse{ @@ -4098,9 +4098,9 @@ func AsyncDeployOrchestratorHostHandler() restapi.ControllerHandler { return } - callerID, ok := getEffectiveCallerID(ctx) + callerID, ok := getEffectiveCallerID(ctx, asyncDeployOrchestratorHostDiag) if !ok { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(asyncDeployOrchestratorHostDiag, http.StatusUnauthorized)) return } @@ -4118,9 +4118,9 @@ func AsyncDeployOrchestratorHostHandler() restapi.ControllerHandler { return } - localJob, err := jobManager.CreateNewJob(callerID, "orchestrator", "deploy", "Deploying agent "+req.HostName) - if err != nil { - ReturnApiError(ctx, w, models.NewFromError(err)) + localJob := jobManager.CreateNewJob(callerID, "orchestrator", "deploy", "Deploying agent "+req.HostName, asyncDeployOrchestratorHostDiag) + if asyncDeployOrchestratorHostDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(asyncDeployOrchestratorHostDiag, http.StatusInternalServerError)) return } @@ -4176,10 +4176,10 @@ func AsyncCreateOrchestratorVirtualMachineHandler() restapi.ControllerHandler { defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) - - callerID, ok := getEffectiveCallerID(ctx) + asyncCreateOrchestratorVMDiag := prlerrors.NewDiagnostics("/v1/orchestrator/machines/async") + callerID, ok := getEffectiveCallerID(ctx, asyncCreateOrchestratorVMDiag) if !ok { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(asyncCreateOrchestratorVMDiag, http.StatusUnauthorized)) return } @@ -4223,9 +4223,9 @@ func AsyncCreateOrchestratorVirtualMachineHandler() restapi.ControllerHandler { return } - job, err := jobManager.CreateNewJob(callerID, "orchestrator", "create", "Initializing orchestrator virtual machine creation") - if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + job := jobManager.CreateNewJob(callerID, "orchestrator", "create", "Initializing orchestrator virtual machine creation", asyncCreateOrchestratorVMDiag) + if asyncCreateOrchestratorVMDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(asyncCreateOrchestratorVMDiag, http.StatusInternalServerError)) return } @@ -4275,16 +4275,15 @@ func AsyncCreateOrchestratorHostVirtualMachineHandler() restapi.ControllerHandle defer r.Body.Close() ctx := GetBaseContext(r) defer Recover(ctx, r, w) - - callerID, ok := getEffectiveCallerID(ctx) + vars := mux.Vars(r) + id := vars["id"] + asyncCreateOrchestratorHostVMDiag := prlerrors.NewDiagnostics("/v1/orchestrator/hosts/" + id + "/machines/async") + callerID, ok := getEffectiveCallerID(ctx, asyncCreateOrchestratorHostVMDiag) if !ok { - ReturnApiError(ctx, w, models.ApiErrorResponse{Code: http.StatusUnauthorized, Message: "User not found"}) + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(asyncCreateOrchestratorHostVMDiag, http.StatusUnauthorized)) return } - vars := mux.Vars(r) - id := vars["id"] - var request models.CreateVirtualMachineRequest if err := http_helper.MapRequestBody(r, &request); err != nil { ReturnApiError(ctx, w, models.ApiErrorResponse{ @@ -4325,9 +4324,9 @@ func AsyncCreateOrchestratorHostVirtualMachineHandler() restapi.ControllerHandle return } - job, err := jobManager.CreateNewJob(callerID, "orchestrator", "create", fmt.Sprintf("Initializing virtual machine creation on host %s", id)) - if err != nil { - ReturnApiError(ctx, w, models.NewFromErrorWithCode(err, http.StatusInternalServerError)) + job := jobManager.CreateNewJob(callerID, "orchestrator", "create", fmt.Sprintf("Initializing virtual machine creation on host %s", id), asyncCreateOrchestratorHostVMDiag) + if asyncCreateOrchestratorHostVMDiag.HasErrors() { + ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(asyncCreateOrchestratorHostVMDiag, http.StatusInternalServerError)) return } diff --git a/src/controllers/user_configs.go b/src/controllers/user_configs.go index dfd61b1a..96a496b4 100644 --- a/src/controllers/user_configs.go +++ b/src/controllers/user_configs.go @@ -78,9 +78,8 @@ func GetUserConfigsHandler() restapi.ControllerHandler { ctx := GetBaseContext(r) defer Recover(ctx, r, w) getUserConfigsDiag := errors.NewDiagnostics("/user/configs") - userContext := ctx.GetUser() - if userContext == nil { - getUserConfigsDiag.AddError(strconv.Itoa(http.StatusUnauthorized), "user not found", "GetUser") + userContext := ctx.GetUser(getUserConfigsDiag) + if getUserConfigsDiag.HasErrors() { ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(getUserConfigsDiag, http.StatusUnauthorized)) return } @@ -129,9 +128,8 @@ func GetUserConfigHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := html.UnescapeString(vars["id"]) getUserConfigDiag := errors.NewDiagnostics("/user/configs/" + id) - userContext := ctx.GetUser() - if userContext == nil { - getUserConfigDiag.AddError(strconv.Itoa(http.StatusUnauthorized), "user not found", "GetUser") + userContext := ctx.GetUser(getUserConfigDiag) + if getUserConfigDiag.HasErrors() { ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(getUserConfigDiag, http.StatusUnauthorized)) return } @@ -177,9 +175,8 @@ func CreateUserConfigHandler() restapi.ControllerHandler { ctx := GetBaseContext(r) defer Recover(ctx, r, w) createUserConfigDiag := errors.NewDiagnostics("/user/configs") - userContext := ctx.GetUser() - if userContext == nil { - createUserConfigDiag.AddError(strconv.Itoa(http.StatusUnauthorized), "user not found", "GetUser") + userContext := ctx.GetUser(createUserConfigDiag) + if createUserConfigDiag.HasErrors() { ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(createUserConfigDiag, http.StatusUnauthorized)) return } @@ -251,9 +248,8 @@ func UpdateUserConfigHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := html.UnescapeString(vars["id"]) updateUserConfigDiag := errors.NewDiagnostics("/user/configs/" + id) - userContext := ctx.GetUser() - if userContext == nil { - updateUserConfigDiag.AddError(strconv.Itoa(http.StatusUnauthorized), "user not found", "GetUser") + userContext := ctx.GetUser(updateUserConfigDiag) + if updateUserConfigDiag.HasErrors() { ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(updateUserConfigDiag, http.StatusUnauthorized)) return } @@ -357,9 +353,8 @@ func DeleteUserConfigHandler() restapi.ControllerHandler { vars := mux.Vars(r) id := html.UnescapeString(vars["id"]) deleteUserConfigDiag := errors.NewDiagnostics("/user/configs/" + id) - userContext := ctx.GetUser() - if userContext == nil { - deleteUserConfigDiag.AddError(strconv.Itoa(http.StatusUnauthorized), "user not found", "GetUser") + userContext := ctx.GetUser(deleteUserConfigDiag) + if deleteUserConfigDiag.HasErrors() { ReturnApiErrorWithDiagnostics(ctx, w, models.NewDiagnosticsWithCode(deleteUserConfigDiag, http.StatusUnauthorized)) return } diff --git a/src/controllers/users.go b/src/controllers/users.go index f111631e..2d7ec1ed 100644 --- a/src/controllers/users.go +++ b/src/controllers/users.go @@ -261,8 +261,8 @@ func CreateUserHandler() restapi.ControllerHandler { } isSuperUser := false - if ctx.GetUser() != nil { - isSuperUser = ctx.User.IsSuperUser + if u := ctx.GetUser(nil); u != nil { + isSuperUser = u.IsSuperUser } if request.IsSuperUser && !isSuperUser { diff --git a/src/data/main.go b/src/data/main.go index 8e943057..d1b1e75f 100644 --- a/src/data/main.go +++ b/src/data/main.go @@ -434,7 +434,7 @@ func LockRecord(ctx basecontext.ApiContext, dbRecord *models.DbRecord) { } dbRecord.IsLocked = true dbRecord.LockedAt = helpers.GetUtcCurrentDateTime() - if user := ctx.GetUser(); user != nil { + if user := ctx.GetUser(nil); user != nil { dbRecord.LockedBy = user.Email } mutexLock.Unlock() diff --git a/src/jobs/main.go b/src/jobs/main.go index 078fea54..638fabf9 100644 --- a/src/jobs/main.go +++ b/src/jobs/main.go @@ -2,13 +2,16 @@ package jobs import ( "context" + "strconv" "time" "github.com/Parallels/prl-devops-service/basecontext" "github.com/Parallels/prl-devops-service/constants" "github.com/Parallels/prl-devops-service/data" data_models "github.com/Parallels/prl-devops-service/data/models" + "github.com/Parallels/prl-devops-service/errors" "github.com/Parallels/prl-devops-service/mappers" + "github.com/Parallels/prl-devops-service/models" global_models "github.com/Parallels/prl-devops-service/models" "github.com/Parallels/prl-devops-service/serviceprovider" ) @@ -71,7 +74,7 @@ func (jms *JobManagerService) Stop() error { return nil } -func (jms *JobManagerService) CreateNewJob(owner string, jobType string, jobOperation string, action string) (*data_models.Job, error) { +func (jms *JobManagerService) CreateNewJob(owner string, jobType string, jobOperation string, action string, diag *errors.Diagnostics) *data_models.Job { job := data_models.Job{ Owner: owner, State: constants.JobStatePending, @@ -83,11 +86,13 @@ func (jms *JobManagerService) CreateNewJob(owner string, jobType string, jobOper createdJob, err := jms.db.CreateJob(jms.apiCtx, job) if err != nil { - return nil, err + rsp := models.NewFromError(err) + diag.AddError(strconv.Itoa(rsp.Code), rsp.Message, "CreateNewJob") + return nil } jms.emitEvent("JOB_CREATED", createdJob) - return createdJob, nil + return createdJob } func (jms *JobManagerService) InitJob(jobId string) (*data_models.Job, error) { @@ -329,18 +334,21 @@ func (jms *JobManagerService) MarkJobError(jobId string, jobErr error) error { return nil } -func (jms *JobManagerService) DeleteJob(jobId string) error { +func (jms *JobManagerService) DeleteJob(jobId string, diag *errors.Diagnostics) { job, err := jms.db.GetJob(jms.apiCtx, jobId) if err != nil { - return err + rsp := models.NewFromError(err) + diag.AddError(strconv.Itoa(rsp.Code), rsp.Message, "GetJob") + return } if err := jms.db.DeleteJob(jms.apiCtx, jobId); err != nil { - return err + rsp := models.NewFromError(err) + diag.AddError(strconv.Itoa(rsp.Code), rsp.Message, "DeleteJob") + return } jms.emitEvent("JOB_DELETED", job) - return nil } func (jms *JobManagerService) emitEvent(message string, job *data_models.Job) { diff --git a/src/serviceprovider/eventEmitter/websocket_handler.go b/src/serviceprovider/eventEmitter/websocket_handler.go index a5e35825..3bad6cf5 100644 --- a/src/serviceprovider/eventEmitter/websocket_handler.go +++ b/src/serviceprovider/eventEmitter/websocket_handler.go @@ -40,7 +40,7 @@ func HandleWebSocketConnection(w http.ResponseWriter, r *http.Request, ctx basec // connection. Checking here (before Upgrade) lets us return a proper HTTP // 401 response instead of a WebSocket close frame, which some clients // misinterpret as a generic error. - usr := ctx.GetUser() + usr := ctx.GetUser(wsHandleDiag) if usr == nil { // API key auth (IsMicroService=true) does not populate the user. // Allow the connection with a synthetic identity so microservices can @@ -152,7 +152,7 @@ func HandleUnsubscribe(w http.ResponseWriter, r *http.Request, ctx basecontext.A } username := "" - if u := ctx.GetUser(); u != nil { + if u := ctx.GetUser(unsubscribeHandleDiag); u != nil { username = u.Username } else if authCtx := ctx.GetAuthorizationContext(); authCtx != nil && authCtx.IsMicroService { username = authCtx.ApiKeyName