[MM-56074] mmctl job commands (#26855)
* add job list and update job status command to mmctl
Этот коммит содержится в:
@@ -25,7 +25,17 @@
|
||||
description: The number of jobs per page.
|
||||
schema:
|
||||
type: integer
|
||||
default: 60
|
||||
default: 5
|
||||
- name: job_type
|
||||
in: query
|
||||
description: The type of jobs to fetch.
|
||||
schema:
|
||||
type: string
|
||||
- name: status
|
||||
in: query
|
||||
description: The status of jobs to fetch.
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Job list retrieval successful
|
||||
@@ -223,3 +233,51 @@
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"/api/v4/jobs/{job_id}/status":
|
||||
patch:
|
||||
tags:
|
||||
- jobs
|
||||
summary: Update the status of a job
|
||||
description: >
|
||||
Update the status of a job. Valid status updates:
|
||||
- 'in_progress' -> 'pending'
|
||||
- 'in_progress' | 'pending' -> 'cancel_requested'
|
||||
- 'cancel_requested' -> 'canceled'
|
||||
|
||||
Add force to the body of the PATCH request to bypass the given rules, the only statuses you can go to are: pending, cancel_requested and canceled. This can have unexpected consequences and should be used with caution.
|
||||
operationId: UpdateJobStatus
|
||||
parameters:
|
||||
- name: job_id
|
||||
in: path
|
||||
description: Job GUID
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
description: The status you want to set
|
||||
force:
|
||||
type: boolean
|
||||
description: Set this to true to bypass status restrictions
|
||||
responses:
|
||||
"200":
|
||||
description: Status successfully set.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/StatusOK"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
|
||||
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
@@ -23,6 +23,7 @@ func (api *API) InitJob() {
|
||||
api.BaseRoutes.Jobs.Handle("/{job_id:[A-Za-z0-9]+}/download", api.APISessionRequiredTrustRequester(downloadJob)).Methods("GET")
|
||||
api.BaseRoutes.Jobs.Handle("/{job_id:[A-Za-z0-9]+}/cancel", api.APISessionRequired(cancelJob)).Methods("POST")
|
||||
api.BaseRoutes.Jobs.Handle("/type/{job_type:[A-Za-z0-9_-]+}", api.APISessionRequired(getJobsByType)).Methods("GET")
|
||||
api.BaseRoutes.Jobs.Handle("/{job_id:[A-Za-z0-9]+}/status", api.APISessionRequired(updateJobStatus)).Methods("PATCH")
|
||||
}
|
||||
|
||||
func getJob(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -147,23 +148,58 @@ func getJobs(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
jobType := r.URL.Query().Get("job_type")
|
||||
var validJobTypes []string
|
||||
for _, jobType := range model.AllJobTypes {
|
||||
|
||||
if jobType != "" {
|
||||
isValidJobType := model.IsValidJobType(jobType)
|
||||
if !isValidJobType {
|
||||
c.SetInvalidURLParam("job_type")
|
||||
return
|
||||
}
|
||||
hasPermission, permissionRequired := c.App.SessionHasPermissionToReadJob(*c.AppContext.Session(), jobType)
|
||||
if permissionRequired == nil {
|
||||
c.Logger.Warn("The job types of a job you are trying to retrieve does not contain permissions", mlog.String("jobType", jobType))
|
||||
continue
|
||||
c.Err = model.NewAppError("getJobsByType", "api.job.retrieve.nopermissions", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if hasPermission {
|
||||
validJobTypes = append(validJobTypes, jobType)
|
||||
if !hasPermission {
|
||||
c.SetPermissionError(permissionRequired)
|
||||
return
|
||||
}
|
||||
validJobTypes = append(validJobTypes, jobType)
|
||||
} else {
|
||||
for _, jType := range model.AllJobTypes {
|
||||
hasPermission, permissionRequired := c.App.SessionHasPermissionToReadJob(*c.AppContext.Session(), jType)
|
||||
if permissionRequired == nil {
|
||||
c.Logger.Warn("The job types of a job you are trying to retrieve does not contain permissions", mlog.String("jobType", jType))
|
||||
continue
|
||||
}
|
||||
if hasPermission {
|
||||
validJobTypes = append(validJobTypes, jType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(validJobTypes) == 0 {
|
||||
c.SetPermissionError()
|
||||
return
|
||||
}
|
||||
|
||||
jobs, appErr := c.App.GetJobsByTypesPage(c.AppContext, validJobTypes, c.Params.Page, c.Params.PerPage)
|
||||
status := r.URL.Query().Get("status")
|
||||
isValidStatus := model.IsValidJobStatus(status)
|
||||
if status != "" && !isValidStatus {
|
||||
c.Err = model.NewAppError("getJobs", "api.job.status.invalid", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
var jobs []*model.Job
|
||||
var appErr *model.AppError
|
||||
|
||||
if status == "" {
|
||||
jobs, appErr = c.App.GetJobsByTypesPage(c.AppContext, validJobTypes, c.Params.Page, c.Params.PerPage)
|
||||
} else {
|
||||
jobs, appErr = c.App.GetJobsByTypeAndStatus(c.AppContext, validJobTypes, status, c.Params.Page, c.Params.PerPage)
|
||||
}
|
||||
|
||||
if appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
@@ -248,3 +284,60 @@ func cancelJob(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
func updateJobStatus(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireJobId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("updateJobStatus", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
audit.AddEventParameter(auditRec, "job_id", c.Params.JobId)
|
||||
|
||||
props := model.StringInterfaceFromJSON(r.Body)
|
||||
status, ok := props["status"].(string)
|
||||
if !ok {
|
||||
c.SetInvalidParam("status")
|
||||
return
|
||||
}
|
||||
|
||||
force, ok := props["force"].(bool)
|
||||
if !ok {
|
||||
force = false
|
||||
}
|
||||
|
||||
job, err := c.App.GetJob(c.AppContext, c.Params.JobId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.AddEventPriorState(job)
|
||||
auditRec.AddEventObjectType("job")
|
||||
|
||||
hasPermission, permissionRequired := c.App.SessionHasPermissionToManageJob(*c.AppContext.Session(), job)
|
||||
if permissionRequired == nil {
|
||||
c.Err = model.NewAppError("updateJobStatus", "api.job.unable_to_manage_job.incorrect_job_type", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if !hasPermission {
|
||||
c.SetPermissionError(permissionRequired)
|
||||
return
|
||||
}
|
||||
|
||||
if !force && !job.IsValidStatusChange(status) {
|
||||
c.Err = model.NewAppError("updateJobStatus", "api.job.status.invalid", nil, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.App.UpdateJobStatus(c.AppContext, job, status); err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
|
||||
ReturnStatusOK(w)
|
||||
}
|
||||
|
||||
@@ -9,4 +9,5 @@ func (api *API) InitJobLocal() {
|
||||
api.BaseRoutes.Jobs.Handle("/{job_id:[A-Za-z0-9]+}", api.APILocal(getJob)).Methods("GET")
|
||||
api.BaseRoutes.Jobs.Handle("/{job_id:[A-Za-z0-9]+}/cancel", api.APILocal(cancelJob)).Methods("POST")
|
||||
api.BaseRoutes.Jobs.Handle("/type/{job_type:[A-Za-z0-9_-]+}", api.APILocal(getJobsByType)).Methods("GET")
|
||||
api.BaseRoutes.Jobs.Handle("/{job_id:[A-Za-z0-9]+}/status", api.APILocal(updateJobStatus)).Methods("PATCH")
|
||||
}
|
||||
|
||||
@@ -102,6 +102,12 @@ func TestGetJobs(t *testing.T) {
|
||||
Type: jobType,
|
||||
CreateAt: t0 + 2,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: model.JobTypeLdapSync,
|
||||
CreateAt: t0 + 3,
|
||||
Status: model.JobStatusPending,
|
||||
},
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
@@ -110,21 +116,47 @@ func TestGetJobs(t *testing.T) {
|
||||
defer th.App.Srv().Store().Job().Delete(job.Id)
|
||||
}
|
||||
|
||||
received, _, err := th.SystemAdminClient.GetJobs(context.Background(), 0, 2)
|
||||
require.NoError(t, err)
|
||||
t.Run("Get 2 jobs", func(t *testing.T) {
|
||||
received, _, err := th.SystemAdminClient.GetJobs(context.Background(), "", "", 0, 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, received, 2, "received wrong number of jobs")
|
||||
require.Equal(t, jobs[2].Id, received[0].Id, "should've received newest job first")
|
||||
require.Equal(t, jobs[0].Id, received[1].Id, "should've received second newest job second")
|
||||
require.Len(t, received, 2, "received wrong number of jobs")
|
||||
require.Equal(t, jobs[3].Id, received[0].Id, "should've received newest job first")
|
||||
require.Equal(t, jobs[2].Id, received[1].Id, "should've received second newest job second")
|
||||
})
|
||||
|
||||
received, _, err = th.SystemAdminClient.GetJobs(context.Background(), 1, 2)
|
||||
require.NoError(t, err)
|
||||
t.Run("Get oldest job using paging", func(t *testing.T) {
|
||||
received, _, err := th.SystemAdminClient.GetJobs(context.Background(), "", "", 1, 3)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, jobs[1].Id, received[0].Id, "should've received oldest job last")
|
||||
})
|
||||
|
||||
require.Equal(t, jobs[1].Id, received[0].Id, "should've received oldest job last")
|
||||
t.Run("Return error fetching job without permissions", func(t *testing.T) {
|
||||
_, resp, err := th.Client.GetJobs(context.Background(), "", "", 0, 60)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
_, resp, err := th.Client.GetJobs(context.Background(), 0, 60)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
t.Run("Get job by type", func(t *testing.T) {
|
||||
received, _, err := th.SystemAdminClient.GetJobs(context.Background(), model.JobTypeLdapSync, "", 0, 3)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, received, 1, "received wrong number of jobs")
|
||||
require.Equal(t, jobs[3].Id, received[0].Id, "should've received the ldap sync job")
|
||||
})
|
||||
|
||||
t.Run("Get job by status", func(t *testing.T) {
|
||||
received, _, err := th.SystemAdminClient.GetJobs(context.Background(), "", model.JobStatusPending, 0, 3)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, received, 1, "received wrong number of jobs")
|
||||
require.Equal(t, jobs[3].Id, received[0].Id, "should've received the ldap sync job")
|
||||
})
|
||||
|
||||
t.Run("Get job by type and status", func(t *testing.T) {
|
||||
received, _, err := th.SystemAdminClient.GetJobs(context.Background(), model.JobTypeLdapSync, model.JobStatusPending, 0, 3)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, received, 1, "received wrong number of jobs")
|
||||
require.Equal(t, jobs[3].Id, received[0].Id, "should've received the ldap sync job")
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetJobsByType(t *testing.T) {
|
||||
@@ -336,3 +368,69 @@ func TestCancelJob(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
CheckNotFoundStatus(t, resp)
|
||||
}
|
||||
|
||||
func TestUpdateJobStatus(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
jobType := model.JobTypeDataRetention
|
||||
jobs := []*model.Job{
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
Status: model.JobStatusPending,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
Status: model.JobStatusInProgress,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
Status: model.JobStatusSuccess,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
Status: model.JobStatusPending,
|
||||
},
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
_, err := th.App.Srv().Store().Job().Save(job)
|
||||
require.NoError(t, err)
|
||||
defer th.App.Srv().Store().Job().Delete(job.Id)
|
||||
}
|
||||
|
||||
t.Run("Fail to update job status without permission", func(t *testing.T) {
|
||||
resp, err := th.Client.UpdateJobStatus(context.Background(), jobs[0].Id, model.JobStatusCancelRequested, false)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("Change a pending job to cancel requested without force with sysadmin client", func(t *testing.T) {
|
||||
_, err := th.SystemAdminClient.UpdateJobStatus(context.Background(), jobs[0].Id, model.JobStatusCancelRequested, false)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Change a pending job to cancel requested without force with local client", func(t *testing.T) {
|
||||
_, err := th.LocalClient.UpdateJobStatus(context.Background(), jobs[3].Id, model.JobStatusCancelRequested, false)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Fail to change a pending job to canceled without force", func(t *testing.T) {
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
resp, err := client.UpdateJobStatus(context.Background(), jobs[0].Id, model.JobStatusCanceled, false)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Change a pending job to canceled with force", func(t *testing.T) {
|
||||
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
||||
_, err := client.UpdateJobStatus(context.Background(), jobs[0].Id, model.JobStatusCanceled, true)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -720,6 +720,7 @@ type AppIface interface {
|
||||
GetIncomingWebhooksPageByUser(userID string, page, perPage int) ([]*model.IncomingWebhook, *model.AppError)
|
||||
GetJob(c request.CTX, id string) (*model.Job, *model.AppError)
|
||||
GetJobsByType(c request.CTX, jobType string, offset int, limit int) ([]*model.Job, *model.AppError)
|
||||
GetJobsByTypeAndStatus(c request.CTX, jobTypes []string, status string, page int, perPage int) ([]*model.Job, *model.AppError)
|
||||
GetJobsByTypePage(c request.CTX, jobType string, page int, perPage int) ([]*model.Job, *model.AppError)
|
||||
GetJobsByTypes(c request.CTX, jobTypes []string, offset int, limit int) ([]*model.Job, *model.AppError)
|
||||
GetJobsByTypesPage(c request.CTX, jobType []string, page int, perPage int) ([]*model.Job, *model.AppError)
|
||||
@@ -1099,6 +1100,7 @@ type AppIface interface {
|
||||
SessionHasPermissionToChannelByPost(session model.Session, postID string, permission *model.Permission) bool
|
||||
SessionHasPermissionToCreateJob(session model.Session, job *model.Job) (bool, *model.Permission)
|
||||
SessionHasPermissionToGroup(session model.Session, groupID string, permission *model.Permission) bool
|
||||
SessionHasPermissionToManageJob(session model.Session, job *model.Job) (bool, *model.Permission)
|
||||
SessionHasPermissionToReadJob(session model.Session, jobType string) (bool, *model.Permission)
|
||||
SessionHasPermissionToTeam(session model.Session, teamID string, permission *model.Permission) bool
|
||||
SessionHasPermissionToUser(session model.Session, userID string) bool
|
||||
@@ -1173,6 +1175,7 @@ type AppIface interface {
|
||||
UpdateHashedPassword(user *model.User, newHashedPassword string) *model.AppError
|
||||
UpdateHashedPasswordByUserId(userID, newHashedPassword string) *model.AppError
|
||||
UpdateIncomingWebhook(oldHook, updatedHook *model.IncomingWebhook) (*model.IncomingWebhook, *model.AppError)
|
||||
UpdateJobStatus(c request.CTX, job *model.Job, newStatus string) *model.AppError
|
||||
UpdateMfa(c request.CTX, activate bool, userID, token string) *model.AppError
|
||||
UpdateMobileAppBadge(userID string)
|
||||
UpdateOAuthApp(oldApp, updatedApp *model.OAuthApp) (*model.OAuthApp, *model.AppError)
|
||||
|
||||
@@ -52,6 +52,14 @@ func (a *App) GetJobsByTypes(c request.CTX, jobTypes []string, offset int, limit
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
func (a *App) GetJobsByTypeAndStatus(c request.CTX, jobTypes []string, status string, page int, perPage int) ([]*model.Job, *model.AppError) {
|
||||
jobs, err := a.Srv().Store().Job().GetAllByTypeAndStatusPage(c, jobTypes, status, page*perPage, perPage)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetAllByTypeAndStatusPage", "app.job.get_all.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
func (a *App) CreateJob(c request.CTX, job *model.Job) (*model.Job, *model.AppError) {
|
||||
return a.Srv().Jobs.CreateJob(c, job.Type, job.Data)
|
||||
}
|
||||
@@ -60,6 +68,19 @@ func (a *App) CancelJob(c request.CTX, jobId string) *model.AppError {
|
||||
return a.Srv().Jobs.RequestCancellation(c, jobId)
|
||||
}
|
||||
|
||||
func (a *App) UpdateJobStatus(c request.CTX, job *model.Job, newStatus string) *model.AppError {
|
||||
switch newStatus {
|
||||
case model.JobStatusPending:
|
||||
return a.Srv().Jobs.SetJobPending(job)
|
||||
case model.JobStatusCancelRequested:
|
||||
return a.Srv().Jobs.RequestCancellation(c, job.Id)
|
||||
case model.JobStatusCanceled:
|
||||
return a.Srv().Jobs.SetJobCanceled(job)
|
||||
default:
|
||||
return model.NewAppError("UpdateJobStatus", "app.job.update_status.app_error", nil, "", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) SessionHasPermissionToCreateJob(session model.Session, job *model.Job) (bool, *model.Permission) {
|
||||
switch job.Type {
|
||||
case model.JobTypeBlevePostIndexing:
|
||||
@@ -92,6 +113,44 @@ func (a *App) SessionHasPermissionToCreateJob(session model.Session, job *model.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (a *App) SessionHasPermissionToManageJob(session model.Session, job *model.Job) (bool, *model.Permission) {
|
||||
var permission *model.Permission
|
||||
|
||||
switch job.Type {
|
||||
case model.JobTypeBlevePostIndexing:
|
||||
permission = model.PermissionManagePostBleveIndexesJob
|
||||
case model.JobTypeDataRetention:
|
||||
permission = model.PermissionManageDataRetentionJob
|
||||
case model.JobTypeMessageExport:
|
||||
permission = model.PermissionManageComplianceExportJob
|
||||
case model.JobTypeElasticsearchPostIndexing:
|
||||
permission = model.PermissionManageElasticsearchPostIndexingJob
|
||||
case model.JobTypeElasticsearchPostAggregation:
|
||||
permission = model.PermissionManageElasticsearchPostAggregationJob
|
||||
case model.JobTypeLdapSync:
|
||||
permission = model.PermissionManageLdapSyncJob
|
||||
case
|
||||
model.JobTypeMigrations,
|
||||
model.JobTypePlugins,
|
||||
model.JobTypeProductNotices,
|
||||
model.JobTypeExpiryNotify,
|
||||
model.JobTypeActiveUsers,
|
||||
model.JobTypeImportProcess,
|
||||
model.JobTypeImportDelete,
|
||||
model.JobTypeExportProcess,
|
||||
model.JobTypeExportDelete,
|
||||
model.JobTypeCloud,
|
||||
model.JobTypeExtractContent:
|
||||
permission = model.PermissionManageJobs
|
||||
}
|
||||
|
||||
if permission == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return a.SessionHasPermissionTo(session, permission), permission
|
||||
}
|
||||
|
||||
func (a *App) SessionHasPermissionToReadJob(session model.Session, jobType string) (bool, *model.Permission) {
|
||||
switch jobType {
|
||||
case model.JobTypeDataRetention:
|
||||
|
||||
@@ -7308,6 +7308,28 @@ func (a *OpenTracingAppLayer) GetJobsByType(c request.CTX, jobType string, offse
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetJobsByTypeAndStatus(c request.CTX, jobTypes []string, status string, page int, perPage int) ([]*model.Job, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetJobsByTypeAndStatus")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetJobsByTypeAndStatus(c, jobTypes, status, page, perPage)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetJobsByTypePage(c request.CTX, jobType string, page int, perPage int) ([]*model.Job, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetJobsByTypePage")
|
||||
@@ -16316,6 +16338,23 @@ func (a *OpenTracingAppLayer) SessionHasPermissionToManageBot(rctx request.CTX,
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SessionHasPermissionToManageJob(session model.Session, job *model.Job) (bool, *model.Permission) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SessionHasPermissionToManageJob")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.SessionHasPermissionToManageJob(session, job)
|
||||
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SessionHasPermissionToReadJob(session model.Session, jobType string) (bool, *model.Permission) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SessionHasPermissionToReadJob")
|
||||
@@ -18083,6 +18122,28 @@ func (a *OpenTracingAppLayer) UpdateIncomingWebhook(oldHook *model.IncomingWebho
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) UpdateJobStatus(c request.CTX, job *model.Job, newStatus string) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateJobStatus")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store().SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store().SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.UpdateJobStatus(c, job, newStatus)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) UpdateMfa(c request.CTX, activate bool, userID string, token string) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateMfa")
|
||||
|
||||
@@ -1181,6 +1181,40 @@ func (a *App) getAddChannelBookmarksPermissionsMigration() (permissionsMap, erro
|
||||
return transformations, nil
|
||||
}
|
||||
|
||||
func (a *App) getAddManageJobAncillaryPermissionsMigration() (permissionsMap, error) {
|
||||
transformations := []permissionTransformation{}
|
||||
|
||||
transformations = append(transformations, permissionTransformation{
|
||||
On: permissionExists(model.PermissionSysconsoleWriteAuthenticationLdap.Id),
|
||||
Add: []string{model.PermissionManageLdapSyncJob.Id},
|
||||
})
|
||||
|
||||
transformations = append(transformations, permissionTransformation{
|
||||
On: permissionExists(model.PermissionSysconsoleWriteComplianceDataRetentionPolicy.Id),
|
||||
Add: []string{model.PermissionManageDataRetentionJob.Id},
|
||||
})
|
||||
|
||||
transformations = append(transformations, permissionTransformation{
|
||||
On: permissionExists(model.PermissionSysconsoleWriteExperimentalBleve.Id),
|
||||
Add: []string{model.PermissionManagePostBleveIndexesJob.Id},
|
||||
})
|
||||
|
||||
transformations = append(transformations, permissionTransformation{
|
||||
On: permissionExists(model.PermissionSysconsoleWriteComplianceComplianceExport.Id),
|
||||
Add: []string{model.PermissionManageComplianceExportJob.Id},
|
||||
})
|
||||
|
||||
transformations = append(transformations, permissionTransformation{
|
||||
On: permissionExists(model.PermissionSysconsoleWriteEnvironmentElasticsearch.Id),
|
||||
Add: []string{
|
||||
model.PermissionManageElasticsearchPostIndexingJob.Id,
|
||||
model.PermissionManageElasticsearchPostAggregationJob.Id,
|
||||
},
|
||||
})
|
||||
|
||||
return transformations, nil
|
||||
}
|
||||
|
||||
// DoPermissionsMigrations execute all the permissions migrations need by the current version.
|
||||
func (a *App) DoPermissionsMigrations() error {
|
||||
return a.Srv().doPermissionsMigrations()
|
||||
@@ -1228,6 +1262,7 @@ func (s *Server) doPermissionsMigrations() error {
|
||||
{Key: model.MigrationKeyAddIPFilteringPermissions, Migration: a.getAddIPFilterPermissionsMigration},
|
||||
{Key: model.MigrationKeyAddOutgoingOAuthConnectionsPermissions, Migration: a.getAddOutgoingOAuthConnectionsPermissions},
|
||||
{Key: model.MigrationKeyAddChannelBookmarksPermissions, Migration: a.getAddChannelBookmarksPermissionsMigration},
|
||||
{Key: model.MigrationKeyAddManageJobAncillaryPermissions, Migration: a.getAddManageJobAncillaryPermissionsMigration},
|
||||
}
|
||||
|
||||
roles, err := s.Store().Role().GetAll()
|
||||
|
||||
@@ -5212,6 +5212,24 @@ func (s *OpenTracingLayerJobStore) GetAllByTypeAndStatus(c request.CTX, jobType
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerJobStore) GetAllByTypeAndStatusPage(c request.CTX, jobType []string, status string, offset int, limit int) ([]*model.Job, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "JobStore.GetAllByTypeAndStatusPage")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.JobStore.GetAllByTypeAndStatusPage(c, jobType, status, offset, limit)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerJobStore) GetAllByTypePage(c request.CTX, jobType string, offset int, limit int) ([]*model.Job, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "JobStore.GetAllByTypePage")
|
||||
|
||||
@@ -5903,6 +5903,27 @@ func (s *RetryLayerJobStore) GetAllByTypeAndStatus(c request.CTX, jobType string
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerJobStore) GetAllByTypeAndStatusPage(c request.CTX, jobType []string, status string, offset int, limit int) ([]*model.Job, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.JobStore.GetAllByTypeAndStatusPage(c, jobType, status, offset, limit)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
timepkg.Sleep(100 * timepkg.Millisecond)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerJobStore) GetAllByTypePage(c request.CTX, jobType string, offset int, limit int) ([]*model.Job, error) {
|
||||
|
||||
tries := 0
|
||||
|
||||
@@ -316,6 +316,26 @@ func (jss SqlJobStore) GetAllByStatus(c request.CTX, status string) ([]*model.Jo
|
||||
return statuses, nil
|
||||
}
|
||||
|
||||
func (jss SqlJobStore) GetAllByTypeAndStatusPage(c request.CTX, jobType []string, status string, offset int, limit int) ([]*model.Job, error) {
|
||||
query, args, err := jss.getQueryBuilder().
|
||||
Select("*").
|
||||
From("Jobs").
|
||||
Where(sq.Eq{"Type": jobType, "Status": status}).
|
||||
OrderBy("CreateAt DESC").
|
||||
Limit(uint64(limit)).
|
||||
Offset(uint64(offset)).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "job_tosql")
|
||||
}
|
||||
|
||||
jobs := []*model.Job{}
|
||||
if err = jss.GetReplicaX().Select(&jobs, query, args...); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to find Jobs with type=%s and status=%s", strings.Join(jobType, ","), status)
|
||||
}
|
||||
|
||||
return jobs, nil
|
||||
}
|
||||
|
||||
func (jss SqlJobStore) GetNewestJobByStatusAndType(status string, jobType string) (*model.Job, error) {
|
||||
return jss.GetNewestJobByStatusesAndType([]string{status}, jobType)
|
||||
}
|
||||
|
||||
@@ -761,6 +761,7 @@ type JobStore interface {
|
||||
GetAllByTypePage(c request.CTX, jobType string, offset int, limit int) ([]*model.Job, error)
|
||||
GetAllByTypesPage(c request.CTX, jobTypes []string, offset int, limit int) ([]*model.Job, error)
|
||||
GetAllByStatus(c request.CTX, status string) ([]*model.Job, error)
|
||||
GetAllByTypeAndStatusPage(c request.CTX, jobType []string, status string, offset int, limit int) ([]*model.Job, error)
|
||||
GetNewestJobByStatusAndType(status string, jobType string) (*model.Job, error)
|
||||
GetNewestJobByStatusesAndType(statuses []string, jobType string) (*model.Job, error)
|
||||
GetCountByStatusAndType(status string, jobType string) (int64, error)
|
||||
|
||||
@@ -24,6 +24,7 @@ func TestJobStore(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
t.Run("JobGetAllByTypeAndStatus", func(t *testing.T) { testJobGetAllByTypeAndStatus(t, rctx, ss) })
|
||||
t.Run("JobGetAllByTypePage", func(t *testing.T) { testJobGetAllByTypePage(t, rctx, ss) })
|
||||
t.Run("JobGetAllByTypesPage", func(t *testing.T) { testJobGetAllByTypesPage(t, rctx, ss) })
|
||||
t.Run("JobGetAllByTypeAndStatusPage", func(t *testing.T) { testJobGetAllByTypeAndStatusPage(t, rctx, ss) })
|
||||
t.Run("JobGetAllByStatus", func(t *testing.T) { testJobGetAllByStatus(t, rctx, ss) })
|
||||
t.Run("GetNewestJobByStatusAndType", func(t *testing.T) { testJobStoreGetNewestJobByStatusAndType(t, rctx, ss) })
|
||||
t.Run("GetNewestJobByStatusesAndType", func(t *testing.T) { testJobStoreGetNewestJobByStatusesAndType(t, rctx, ss) })
|
||||
@@ -259,6 +260,62 @@ func testJobGetAllByTypesPage(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
require.Equal(t, received[0].Id, jobs[1].Id, "should've received oldest job last")
|
||||
}
|
||||
|
||||
func testJobGetAllByTypeAndStatusPage(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
jobType := model.NewId()
|
||||
jobType2 := model.NewId()
|
||||
t0 := model.GetMillis()
|
||||
|
||||
jobs := []*model.Job{
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
Status: model.JobStatusPending,
|
||||
CreateAt: t0,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType,
|
||||
Status: model.JobStatusPending,
|
||||
CreateAt: t0 + 1,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType2,
|
||||
Status: model.JobStatusCanceled,
|
||||
CreateAt: t0 + 2,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: jobType2,
|
||||
Status: model.JobStatusCanceled,
|
||||
CreateAt: t0 + 3,
|
||||
},
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
_, err := ss.Job().Save(job)
|
||||
require.NoError(t, err)
|
||||
defer ss.Job().Delete(job.Id)
|
||||
}
|
||||
|
||||
jobTypes := []string{jobType, jobType2}
|
||||
received, err := ss.Job().GetAllByTypeAndStatusPage(rctx, jobTypes, model.JobStatusPending, 0, 4)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, received, 2)
|
||||
require.Equal(t, received[0].Id, jobs[1].Id, "should've received newest job first")
|
||||
require.Equal(t, received[1].Id, jobs[0].Id, "should've received oldest job last")
|
||||
|
||||
received, err = ss.Job().GetAllByTypeAndStatusPage(rctx, jobTypes, model.JobStatusPending, 1, 1)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, received, 1)
|
||||
require.Equal(t, received[0].Id, jobs[0].Id, "should've received the oldest pending job")
|
||||
|
||||
received, err = ss.Job().GetAllByTypeAndStatusPage(rctx, []string{jobType2}, model.JobStatusCanceled, 1, 1)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, received, 1)
|
||||
require.Equal(t, received[0].Id, jobs[2].Id, "should've received the oldest canceled job")
|
||||
}
|
||||
|
||||
func testJobGetAllByStatus(t *testing.T, rctx request.CTX, ss store.Store) {
|
||||
jobType := model.NewId()
|
||||
status := model.NewId()
|
||||
|
||||
@@ -181,6 +181,36 @@ func (_m *JobStore) GetAllByTypeAndStatus(c request.CTX, jobType string, status
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetAllByTypeAndStatusPage provides a mock function with given fields: c, jobType, status, offset, limit
|
||||
func (_m *JobStore) GetAllByTypeAndStatusPage(c request.CTX, jobType []string, status string, offset int, limit int) ([]*model.Job, error) {
|
||||
ret := _m.Called(c, jobType, status, offset, limit)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetAllByTypeAndStatusPage")
|
||||
}
|
||||
|
||||
var r0 []*model.Job
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, []string, string, int, int) ([]*model.Job, error)); ok {
|
||||
return rf(c, jobType, status, offset, limit)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(request.CTX, []string, string, int, int) []*model.Job); ok {
|
||||
r0 = rf(c, jobType, status, offset, limit)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Job)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(request.CTX, []string, string, int, int) error); ok {
|
||||
r1 = rf(c, jobType, status, offset, limit)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetAllByTypePage provides a mock function with given fields: c, jobType, offset, limit
|
||||
func (_m *JobStore) GetAllByTypePage(c request.CTX, jobType string, offset int, limit int) ([]*model.Job, error) {
|
||||
ret := _m.Called(c, jobType, offset, limit)
|
||||
|
||||
@@ -4731,6 +4731,22 @@ func (s *TimerLayerJobStore) GetAllByTypeAndStatus(c request.CTX, jobType string
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerJobStore) GetAllByTypeAndStatusPage(c request.CTX, jobType []string, status string, offset int, limit int) ([]*model.Job, error) {
|
||||
start := time.Now()
|
||||
|
||||
result, err := s.JobStore.GetAllByTypeAndStatusPage(c, jobType, status, offset, limit)
|
||||
|
||||
elapsed := float64(time.Since(start)) / float64(time.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("JobStore.GetAllByTypeAndStatusPage", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerJobStore) GetAllByTypePage(c request.CTX, jobType string, offset int, limit int) ([]*model.Job, error) {
|
||||
start := time.Now()
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
|
||||
systemStore.On("GetByName", model.MigrationKeyAddIPFilteringPermissions).Return(&model.System{Name: model.MigrationKeyAddIPFilteringPermissions, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", model.MigrationKeyAddOutgoingOAuthConnectionsPermissions).Return(&model.System{Name: model.MigrationKeyAddOutgoingOAuthConnectionsPermissions, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", model.MigrationKeyAddChannelBookmarksPermissions).Return(&model.System{Name: model.MigrationKeyAddChannelBookmarksPermissions, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", model.MigrationKeyAddManageJobAncillaryPermissions).Return(&model.System{Name: model.MigrationKeyAddManageJobAncillaryPermissions, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", "CustomGroupAdminRoleCreationMigrationComplete").Return(&model.System{Name: model.MigrationKeyAddPlayboosksManageRolesPermissions, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", "products_boards").Return(&model.System{Name: "products_boards", Value: "true"}, nil)
|
||||
systemStore.On("GetByName", "elasticsearch_fix_channel_index_migration").Return(&model.System{Name: "elasticsearch_fix_channel_index_migration", Value: "true"}, nil)
|
||||
|
||||
@@ -128,10 +128,11 @@ type Client interface {
|
||||
UploadData(ctx context.Context, uploadID string, data io.Reader) (*model.FileInfo, *model.Response, error)
|
||||
ListImports(ctx context.Context) ([]string, *model.Response, error)
|
||||
GetJob(ctx context.Context, id string) (*model.Job, *model.Response, error)
|
||||
GetJobs(ctx context.Context, page int, perPage int) ([]*model.Job, *model.Response, error)
|
||||
GetJobs(ctx context.Context, jobType string, status string, page int, perPage int) ([]*model.Job, *model.Response, error)
|
||||
GetJobsByType(ctx context.Context, jobType string, page int, perPage int) ([]*model.Job, *model.Response, error)
|
||||
CreateJob(ctx context.Context, job *model.Job) (*model.Job, *model.Response, error)
|
||||
CancelJob(ctx context.Context, jobID string) (*model.Response, error)
|
||||
UpdateJobStatus(ctx context.Context, jobId string, status string, force bool) (*model.Response, error)
|
||||
CreateIncomingWebhook(ctx context.Context, hook *model.IncomingWebhook) (*model.IncomingWebhook, *model.Response, error)
|
||||
UpdateIncomingWebhook(ctx context.Context, hook *model.IncomingWebhook) (*model.IncomingWebhook, *model.Response, error)
|
||||
GetIncomingWebhooks(ctx context.Context, page int, perPage int, etag string) ([]*model.IncomingWebhook, *model.Response, error)
|
||||
|
||||
@@ -270,7 +270,7 @@ func exportDownloadCmdF(c client.Client, command *cobra.Command, args []string)
|
||||
}
|
||||
|
||||
func exportJobListCmdF(c client.Client, command *cobra.Command, args []string) error {
|
||||
return jobListCmdF(c, command, model.JobTypeExportProcess)
|
||||
return jobListCmdF(c, command, model.JobTypeExportProcess, "")
|
||||
}
|
||||
|
||||
func exportJobShowCmdF(c client.Client, command *cobra.Command, args []string) error {
|
||||
|
||||
@@ -107,7 +107,7 @@ func extractJobShowCmdF(c client.Client, command *cobra.Command, args []string)
|
||||
}
|
||||
|
||||
func extractJobListCmdF(c client.Client, command *cobra.Command, args []string) error {
|
||||
return jobListCmdF(c, command, model.JobTypeExtractContent)
|
||||
return jobListCmdF(c, command, model.JobTypeExtractContent, "")
|
||||
}
|
||||
|
||||
func printExtractContentJob(job *model.Job) {
|
||||
|
||||
@@ -308,24 +308,6 @@ func importProcessCmdF(c client.Client, command *cobra.Command, args []string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func printJob(job *model.Job) {
|
||||
if job.StartAt > 0 {
|
||||
printer.PrintT(fmt.Sprintf(` ID: {{.Id}}
|
||||
Status: {{.Status}}
|
||||
Created: %s
|
||||
Started: %s
|
||||
Data: {{.Data}}
|
||||
`,
|
||||
time.Unix(job.CreateAt/1000, 0), time.Unix(job.StartAt/1000, 0)), job)
|
||||
} else {
|
||||
printer.PrintT(fmt.Sprintf(` ID: {{.Id}}
|
||||
Status: {{.Status}}
|
||||
Created: %s
|
||||
`,
|
||||
time.Unix(job.CreateAt/1000, 0)), job)
|
||||
}
|
||||
}
|
||||
|
||||
func importJobShowCmdF(c client.Client, command *cobra.Command, args []string) error {
|
||||
job, _, err := c.GetJob(context.TODO(), args[0])
|
||||
if err != nil {
|
||||
@@ -337,53 +319,8 @@ func importJobShowCmdF(c client.Client, command *cobra.Command, args []string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func jobListCmdF(c client.Client, command *cobra.Command, jobType string) error {
|
||||
page, err := command.Flags().GetInt("page")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
perPage, err := command.Flags().GetInt("per-page")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
showAll, err := command.Flags().GetBool("all")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if showAll {
|
||||
page = 0
|
||||
}
|
||||
|
||||
for {
|
||||
jobs, _, err := c.GetJobsByType(context.TODO(), jobType, page, perPage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get jobs: %w", err)
|
||||
}
|
||||
|
||||
if len(jobs) == 0 {
|
||||
if !showAll || page == 0 {
|
||||
printer.Print("No jobs found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
printJob(job)
|
||||
}
|
||||
|
||||
if !showAll {
|
||||
break
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func importJobListCmdF(c client.Client, command *cobra.Command, args []string) error {
|
||||
return jobListCmdF(c, command, model.JobTypeImportProcess)
|
||||
return jobListCmdF(c, command, model.JobTypeImportProcess, "")
|
||||
}
|
||||
|
||||
type Statistics struct {
|
||||
|
||||
@@ -163,7 +163,7 @@ func (s *MmctlUnitTestSuite) TestImportJobListCmdF() {
|
||||
|
||||
s.client.
|
||||
EXPECT().
|
||||
GetJobsByType(context.TODO(), model.JobTypeImportProcess, 0, perPage).
|
||||
GetJobs(context.TODO(), model.JobTypeImportProcess, "", 0, perPage).
|
||||
Return(mockJobs, &model.Response{}, nil).
|
||||
Times(1)
|
||||
|
||||
@@ -196,7 +196,7 @@ func (s *MmctlUnitTestSuite) TestImportJobListCmdF() {
|
||||
|
||||
s.client.
|
||||
EXPECT().
|
||||
GetJobsByType(context.TODO(), model.JobTypeImportProcess, 0, perPage).
|
||||
GetJobs(context.TODO(), model.JobTypeImportProcess, "", 0, perPage).
|
||||
Return(mockJobs, &model.Response{}, nil).
|
||||
Times(1)
|
||||
|
||||
|
||||
202
server/cmd/mmctl/commands/job.go
Обычный файл
202
server/cmd/mmctl/commands/job.go
Обычный файл
@@ -0,0 +1,202 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/client"
|
||||
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var JobCmd = &cobra.Command{
|
||||
Use: "job",
|
||||
Short: "Management of jobs",
|
||||
}
|
||||
|
||||
var listJobsCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List the latest jobs",
|
||||
Example: ` job list
|
||||
job list --ids jobID1,jobID2
|
||||
job list --type ldap_sync --status success
|
||||
job list --type ldap_sync --status success --page 0 --per-page 10`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: withClient(listJobsCmdF),
|
||||
}
|
||||
|
||||
var updateJobCmd = &cobra.Command{
|
||||
Use: "update [job] [status]",
|
||||
Short: "Update the status of a job",
|
||||
Long: `Update the status of a job. The following restrictions are in place:
|
||||
- in_progress -> pending
|
||||
- in_progress | pending -> cancel_requested
|
||||
- cancel_requested -> canceled
|
||||
|
||||
Those restriction can be bypassed with --force=true but the only statuses you can go to are: pending, cancel_requested and canceled. This can have unexpected consequences and should be used with caution.`,
|
||||
Example: ` job update myJobID pending
|
||||
job update myJobID pending --force true
|
||||
job update myJobID canceled --force true`,
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: withClient(updateJobCmdF),
|
||||
}
|
||||
|
||||
func init() {
|
||||
listJobsCmd.Flags().Int("page", 0, "Page number to fetch for the list of import jobs")
|
||||
listJobsCmd.Flags().Int("per-page", 5, "Number of import jobs to be fetched")
|
||||
listJobsCmd.Flags().Bool("all", false, "Fetch all import jobs. --page flag will be ignored if provided")
|
||||
listJobsCmd.Flags().StringSlice("ids", nil, "Comma-separated list of job IDs to which the operation will be applied. All other flags are ignored")
|
||||
listJobsCmd.Flags().String("status", "", "Filter by job status")
|
||||
listJobsCmd.Flags().String("type", "", "Filter by job type")
|
||||
|
||||
updateJobCmd.Flags().Bool("force", false, "Setting a job status is restricted to certain statuses. You can overwrite these restrictions by using --force. This might cause unexpected behaviour on your Mattermost Server. Use this option with caution.")
|
||||
|
||||
JobCmd.AddCommand(
|
||||
listJobsCmd,
|
||||
updateJobCmd,
|
||||
)
|
||||
|
||||
RootCmd.AddCommand(JobCmd)
|
||||
}
|
||||
|
||||
func listJobsCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
||||
ids, err := cmd.Flags().GetStringSlice("ids")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jobType, err := cmd.Flags().GetString("type")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status, err := cmd.Flags().GetString("status")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(ids) > 0 {
|
||||
jobs := make([]*model.Job, 0, len(ids))
|
||||
var result *multierror.Error
|
||||
for _, id := range ids {
|
||||
isValidId := model.IsValidId(id)
|
||||
if !isValidId {
|
||||
result = multierror.Append(result, fmt.Errorf("invalid job ID: %s", id))
|
||||
continue
|
||||
}
|
||||
|
||||
job, _, err := c.GetJob(context.TODO(), id)
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
continue
|
||||
}
|
||||
jobs = append(jobs, job)
|
||||
}
|
||||
for _, job := range jobs {
|
||||
printJob(job)
|
||||
}
|
||||
return result.ErrorOrNil()
|
||||
}
|
||||
|
||||
return jobListCmdF(c, cmd, jobType, status)
|
||||
}
|
||||
|
||||
func updateJobCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
||||
force, err := cmd.Flags().GetBool("force")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jobId := args[0]
|
||||
if !model.IsValidId(jobId) {
|
||||
return fmt.Errorf("invalid job ID: %s", jobId)
|
||||
}
|
||||
status := args[1]
|
||||
if !model.IsValidJobStatus(status) {
|
||||
return fmt.Errorf("invalid job status: %s", status)
|
||||
}
|
||||
|
||||
_, err = c.UpdateJobStatus(context.TODO(), jobId, status, force)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func jobListCmdF(c client.Client, command *cobra.Command, jobType string, status string) error {
|
||||
page, err := command.Flags().GetInt("page")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
perPage, err := command.Flags().GetInt("per-page")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
showAll, err := command.Flags().GetBool("all")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if showAll {
|
||||
page = 0
|
||||
}
|
||||
|
||||
if jobType != "" && !model.IsValidJobType(jobType) {
|
||||
return fmt.Errorf("invalid job type: %s", jobType)
|
||||
}
|
||||
|
||||
if status != "" && !model.IsValidJobStatus(status) {
|
||||
return fmt.Errorf("invalid job status: %s", status)
|
||||
}
|
||||
|
||||
for {
|
||||
jobs, _, err := c.GetJobs(context.TODO(), jobType, status, page, perPage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get jobs: %w", err)
|
||||
}
|
||||
|
||||
if len(jobs) == 0 {
|
||||
if !showAll || page == 0 {
|
||||
printer.Print("No jobs found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
printJob(job)
|
||||
}
|
||||
|
||||
if !showAll {
|
||||
break
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printJob(job *model.Job) {
|
||||
if job.StartAt > 0 {
|
||||
printer.PrintT(fmt.Sprintf(` ID: {{.Id}}
|
||||
Type: {{.Type}}
|
||||
Status: {{.Status}}
|
||||
Created: %s
|
||||
Started: %s
|
||||
Data: {{.Data}}
|
||||
`,
|
||||
time.Unix(job.CreateAt/1000, 0), time.Unix(job.StartAt/1000, 0)), job)
|
||||
} else {
|
||||
printer.PrintT(fmt.Sprintf(` ID: {{.Id}}
|
||||
Status: {{.Status}}
|
||||
Created: %s
|
||||
`,
|
||||
time.Unix(job.CreateAt/1000, 0)), job)
|
||||
}
|
||||
}
|
||||
204
server/cmd/mmctl/commands/job_test.go
Обычный файл
204
server/cmd/mmctl/commands/job_test.go
Обычный файл
@@ -0,0 +1,204 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
|
||||
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func (s *MmctlUnitTestSuite) TestListJobsCmdF() {
|
||||
s.Run("no jobs found", func() {
|
||||
printer.Clean()
|
||||
var mockJobs []*model.Job
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
perPage := 10
|
||||
cmd.Flags().Int("page", 0, "")
|
||||
cmd.Flags().Int("per-page", perPage, "")
|
||||
cmd.Flags().Bool("all", false, "")
|
||||
cmd.Flags().StringSlice("ids", []string{}, "")
|
||||
cmd.Flags().String("status", "", "")
|
||||
cmd.Flags().String("type", "", "")
|
||||
|
||||
s.client.
|
||||
EXPECT().
|
||||
GetJobs(context.TODO(), "", "", 0, perPage).
|
||||
Return(mockJobs, &model.Response{}, nil).
|
||||
Times(1)
|
||||
|
||||
err := listJobsCmdF(s.client, cmd, nil)
|
||||
s.Require().Nil(err)
|
||||
s.Len(printer.GetLines(), 1)
|
||||
s.Empty(printer.GetErrorLines())
|
||||
s.Equal("No jobs found", printer.GetLines()[0])
|
||||
})
|
||||
|
||||
s.Run("3 jobs found", func() {
|
||||
printer.Clean()
|
||||
mockJobs := []*model.Job{
|
||||
{
|
||||
Id: model.NewId(),
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
},
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
perPage := 3
|
||||
cmd.Flags().Int("page", 0, "")
|
||||
cmd.Flags().Int("per-page", perPage, "")
|
||||
cmd.Flags().Bool("all", false, "")
|
||||
cmd.Flags().StringSlice("ids", []string{}, "")
|
||||
cmd.Flags().String("status", "", "")
|
||||
cmd.Flags().String("type", "", "")
|
||||
|
||||
s.client.
|
||||
EXPECT().
|
||||
GetJobs(context.TODO(), "", "", 0, perPage).
|
||||
Return(mockJobs, &model.Response{}, nil).
|
||||
Times(1)
|
||||
|
||||
err := listJobsCmdF(s.client, cmd, nil)
|
||||
s.Require().Nil(err)
|
||||
s.Len(printer.GetLines(), len(mockJobs))
|
||||
s.Empty(printer.GetErrorLines())
|
||||
for i, line := range printer.GetLines() {
|
||||
s.Equal(mockJobs[i], line.(*model.Job))
|
||||
}
|
||||
})
|
||||
|
||||
s.Run("return 1 job using ids flag", func() {
|
||||
printer.Clean()
|
||||
id := model.NewId()
|
||||
mockJob := &model.Job{
|
||||
Id: id,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
perPage := 3
|
||||
cmd.Flags().Int("page", 0, "")
|
||||
cmd.Flags().Int("per-page", perPage, "")
|
||||
cmd.Flags().Bool("all", false, "")
|
||||
cmd.Flags().StringSlice("ids", []string{id}, "")
|
||||
cmd.Flags().String("status", "", "")
|
||||
cmd.Flags().String("type", "", "")
|
||||
|
||||
s.client.
|
||||
EXPECT().
|
||||
GetJob(context.TODO(), id).
|
||||
Return(mockJob, &model.Response{}, nil).
|
||||
Times(1)
|
||||
|
||||
err := listJobsCmdF(s.client, cmd, nil)
|
||||
s.Require().Nil(err)
|
||||
s.Len(printer.GetLines(), 1)
|
||||
s.Empty(printer.GetErrorLines())
|
||||
for _, line := range printer.GetLines() {
|
||||
s.Equal(mockJob, line.(*model.Job))
|
||||
}
|
||||
})
|
||||
|
||||
s.Run("return 2 jobs by status", func() {
|
||||
printer.Clean()
|
||||
mockJobs := []*model.Job{
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Status: model.JobStatusSuccess,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Status: model.JobStatusSuccess,
|
||||
},
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
perPage := 2
|
||||
cmd.Flags().Int("page", 0, "")
|
||||
cmd.Flags().Int("per-page", perPage, "")
|
||||
cmd.Flags().Bool("all", false, "")
|
||||
cmd.Flags().String("status", model.JobStatusSuccess, "")
|
||||
cmd.Flags().StringSlice("ids", []string{}, "")
|
||||
cmd.Flags().String("type", "", "")
|
||||
|
||||
s.client.
|
||||
EXPECT().
|
||||
GetJobs(context.TODO(), "", model.JobStatusSuccess, 0, perPage).
|
||||
Return(mockJobs, &model.Response{}, nil).
|
||||
Times(1)
|
||||
|
||||
err := listJobsCmdF(s.client, cmd, nil)
|
||||
s.Require().Nil(err)
|
||||
s.Len(printer.GetLines(), len(mockJobs))
|
||||
s.Empty(printer.GetErrorLines())
|
||||
for i, line := range printer.GetLines() {
|
||||
s.Equal(mockJobs[i], line.(*model.Job))
|
||||
}
|
||||
})
|
||||
|
||||
s.Run("return 2 jobs by type", func() {
|
||||
printer.Clean()
|
||||
mockJobs := []*model.Job{
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: model.JobTypeDataRetention,
|
||||
},
|
||||
{
|
||||
Id: model.NewId(),
|
||||
Type: model.JobTypeDataRetention,
|
||||
},
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
perPage := 2
|
||||
cmd.Flags().Int("page", 0, "")
|
||||
cmd.Flags().Int("per-page", perPage, "")
|
||||
cmd.Flags().Bool("all", false, "")
|
||||
cmd.Flags().String("type", model.JobTypeDataRetention, "")
|
||||
cmd.Flags().StringSlice("ids", []string{}, "")
|
||||
cmd.Flags().String("status", "", "")
|
||||
|
||||
s.client.
|
||||
EXPECT().
|
||||
GetJobs(context.TODO(), model.JobTypeDataRetention, "", 0, perPage).
|
||||
Return(mockJobs, &model.Response{}, nil).
|
||||
Times(1)
|
||||
|
||||
err := listJobsCmdF(s.client, cmd, nil)
|
||||
s.Require().Nil(err)
|
||||
s.Len(printer.GetLines(), len(mockJobs))
|
||||
s.Empty(printer.GetErrorLines())
|
||||
for i, line := range printer.GetLines() {
|
||||
s.Equal(mockJobs[i], line.(*model.Job))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *MmctlUnitTestSuite) TestUpdateJobCmdF() {
|
||||
s.Run("update job status", func() {
|
||||
printer.Clean()
|
||||
id := model.NewId()
|
||||
|
||||
cmd := &cobra.Command{}
|
||||
cmd.Flags().Bool("force", true, "")
|
||||
|
||||
s.client.
|
||||
EXPECT().
|
||||
UpdateJobStatus(context.TODO(), id, model.JobStatusPending, true).
|
||||
Return(&model.Response{}, nil).
|
||||
Times(1)
|
||||
|
||||
err := updateJobCmdF(s.client, cmd, []string{id, model.JobStatusPending})
|
||||
s.Require().Nil(err)
|
||||
})
|
||||
}
|
||||
@@ -121,7 +121,7 @@ func ldapIDMigrateCmdF(c client.Client, cmd *cobra.Command, args []string) error
|
||||
}
|
||||
|
||||
func ldapJobListCmdF(c client.Client, command *cobra.Command, args []string) error {
|
||||
return jobListCmdF(c, command, model.JobTypeLdapSync)
|
||||
return jobListCmdF(c, command, model.JobTypeLdapSync, "")
|
||||
}
|
||||
|
||||
func ldapJobShowCmdF(c client.Client, command *cobra.Command, args []string) error {
|
||||
|
||||
@@ -128,7 +128,7 @@ func (s *MmctlUnitTestSuite) TestLdapJobListCmdF() {
|
||||
|
||||
s.client.
|
||||
EXPECT().
|
||||
GetJobsByType(context.TODO(), model.JobTypeLdapSync, 0, perPage).
|
||||
GetJobs(context.TODO(), model.JobTypeLdapSync, "", 0, perPage).
|
||||
Return(mockJobs, &model.Response{}, nil).
|
||||
Times(1)
|
||||
|
||||
@@ -161,7 +161,7 @@ func (s *MmctlUnitTestSuite) TestLdapJobListCmdF() {
|
||||
|
||||
s.client.
|
||||
EXPECT().
|
||||
GetJobsByType(context.TODO(), model.JobTypeLdapSync, 0, perPage).
|
||||
GetJobs(context.TODO(), model.JobTypeLdapSync, "", 0, perPage).
|
||||
Return(mockJobs, &model.Response{}, nil).
|
||||
Times(1)
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ SEE ALSO
|
||||
* `mmctl group <mmctl_group.rst>`_ - Management of groups
|
||||
* `mmctl import <mmctl_import.rst>`_ - Management of imports
|
||||
* `mmctl integrity <mmctl_integrity.rst>`_ - Check database records integrity.
|
||||
* `mmctl job <mmctl_job.rst>`_ - Management of jobs
|
||||
* `mmctl ldap <mmctl_ldap.rst>`_ - LDAP related utilities
|
||||
* `mmctl license <mmctl_license.rst>`_ - Licensing commands
|
||||
* `mmctl logs <mmctl_logs.rst>`_ - Display logs in a human-readable format
|
||||
|
||||
42
server/cmd/mmctl/docs/mmctl_job.rst
Обычный файл
42
server/cmd/mmctl/docs/mmctl_job.rst
Обычный файл
@@ -0,0 +1,42 @@
|
||||
.. _mmctl_job:
|
||||
|
||||
mmctl job
|
||||
---------
|
||||
|
||||
Management of jobs
|
||||
|
||||
Synopsis
|
||||
~~~~~~~~
|
||||
|
||||
|
||||
Management of jobs
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
-h, --help help for job
|
||||
|
||||
Options inherited from parent commands
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
--config string path to the configuration file (default "$XDG_CONFIG_HOME/mmctl/config")
|
||||
--disable-pager disables paged output
|
||||
--insecure-sha1-intermediate allows to use insecure TLS protocols, such as SHA-1
|
||||
--insecure-tls-version allows to use TLS versions 1.0 and 1.1
|
||||
--json the output format will be in json format
|
||||
--local allows communicating with the server through a unix socket
|
||||
--quiet prevent mmctl to generate output for the commands
|
||||
--strict will only run commands if the mmctl version matches the server one
|
||||
--suppress-warnings disables printing warning messages
|
||||
|
||||
SEE ALSO
|
||||
~~~~~~~~
|
||||
|
||||
* `mmctl <mmctl.rst>`_ - Remote client for the Open Source, self-hosted Slack-alternative
|
||||
* `mmctl job list <mmctl_job_list.rst>`_ - List the latest jobs
|
||||
* `mmctl job update <mmctl_job_update.rst>`_ - Update the status of a job
|
||||
|
||||
60
server/cmd/mmctl/docs/mmctl_job_list.rst
Обычный файл
60
server/cmd/mmctl/docs/mmctl_job_list.rst
Обычный файл
@@ -0,0 +1,60 @@
|
||||
.. _mmctl_job_list:
|
||||
|
||||
mmctl job list
|
||||
--------------
|
||||
|
||||
List the latest jobs
|
||||
|
||||
Synopsis
|
||||
~~~~~~~~
|
||||
|
||||
|
||||
List the latest jobs
|
||||
|
||||
::
|
||||
|
||||
mmctl job list [flags]
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
job list
|
||||
job list --ids jobID1,jobID2
|
||||
job list --type ldap_sync --status success
|
||||
job list --type ldap_sync --status success --page 0 --per-page 10
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
--all Fetch all import jobs. --page flag will be ignored if provided
|
||||
-h, --help help for list
|
||||
--ids strings Comma-separated list of job IDs to which the operation will be applied. All other flags are ignored
|
||||
--page int Page number to fetch for the list of import jobs
|
||||
--per-page int Number of import jobs to be fetched (default 5)
|
||||
--status string Filter by job status
|
||||
--type string Filter by job type
|
||||
|
||||
Options inherited from parent commands
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
--config string path to the configuration file (default "$XDG_CONFIG_HOME/mmctl/config")
|
||||
--disable-pager disables paged output
|
||||
--insecure-sha1-intermediate allows to use insecure TLS protocols, such as SHA-1
|
||||
--insecure-tls-version allows to use TLS versions 1.0 and 1.1
|
||||
--json the output format will be in json format
|
||||
--local allows communicating with the server through a unix socket
|
||||
--quiet prevent mmctl to generate output for the commands
|
||||
--strict will only run commands if the mmctl version matches the server one
|
||||
--suppress-warnings disables printing warning messages
|
||||
|
||||
SEE ALSO
|
||||
~~~~~~~~
|
||||
|
||||
* `mmctl job <mmctl_job.rst>`_ - Management of jobs
|
||||
|
||||
59
server/cmd/mmctl/docs/mmctl_job_update.rst
Обычный файл
59
server/cmd/mmctl/docs/mmctl_job_update.rst
Обычный файл
@@ -0,0 +1,59 @@
|
||||
.. _mmctl_job_update:
|
||||
|
||||
mmctl job update
|
||||
----------------
|
||||
|
||||
Update the status of a job
|
||||
|
||||
Synopsis
|
||||
~~~~~~~~
|
||||
|
||||
|
||||
Update the status of a job. The following restrictions are in place:
|
||||
- in_progress -> pending
|
||||
- in_progress | pending -> cancel_requested
|
||||
- cancel_requested -> canceled
|
||||
|
||||
Those restriction can be bypassed with --force=true but the only statuses you can go to are: pending, cancel_requested and canceled. This can have unexpected consequences and should be used with caution.
|
||||
|
||||
::
|
||||
|
||||
mmctl job update [job] [status] [flags]
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
job update myJobID pending
|
||||
job update myJobID pending --force true
|
||||
job update myJobID canceled --force true
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
--force Setting a job status is restricted to certain statuses. You can overwrite these restrictions by using --force. This might cause unexpected behaviour on your Mattermost Server. Use this option with caution.
|
||||
-h, --help help for update
|
||||
|
||||
Options inherited from parent commands
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
--config string path to the configuration file (default "$XDG_CONFIG_HOME/mmctl/config")
|
||||
--disable-pager disables paged output
|
||||
--insecure-sha1-intermediate allows to use insecure TLS protocols, such as SHA-1
|
||||
--insecure-tls-version allows to use TLS versions 1.0 and 1.1
|
||||
--json the output format will be in json format
|
||||
--local allows communicating with the server through a unix socket
|
||||
--quiet prevent mmctl to generate output for the commands
|
||||
--strict will only run commands if the mmctl version matches the server one
|
||||
--suppress-warnings disables printing warning messages
|
||||
|
||||
SEE ALSO
|
||||
~~~~~~~~
|
||||
|
||||
* `mmctl job <mmctl_job.rst>`_ - Management of jobs
|
||||
|
||||
@@ -863,9 +863,9 @@ func (mr *MockClientMockRecorder) GetJob(arg0, arg1 interface{}) *gomock.Call {
|
||||
}
|
||||
|
||||
// GetJobs mocks base method.
|
||||
func (m *MockClient) GetJobs(arg0 context.Context, arg1, arg2 int) ([]*model.Job, *model.Response, error) {
|
||||
func (m *MockClient) GetJobs(arg0 context.Context, arg1, arg2 string, arg3, arg4 int) ([]*model.Job, *model.Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetJobs", arg0, arg1, arg2)
|
||||
ret := m.ctrl.Call(m, "GetJobs", arg0, arg1, arg2, arg3, arg4)
|
||||
ret0, _ := ret[0].([]*model.Job)
|
||||
ret1, _ := ret[1].(*model.Response)
|
||||
ret2, _ := ret[2].(error)
|
||||
@@ -873,9 +873,9 @@ func (m *MockClient) GetJobs(arg0 context.Context, arg1, arg2 int) ([]*model.Job
|
||||
}
|
||||
|
||||
// GetJobs indicates an expected call of GetJobs.
|
||||
func (mr *MockClientMockRecorder) GetJobs(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) GetJobs(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetJobs", reflect.TypeOf((*MockClient)(nil).GetJobs), arg0, arg1, arg2)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetJobs", reflect.TypeOf((*MockClient)(nil).GetJobs), arg0, arg1, arg2, arg3, arg4)
|
||||
}
|
||||
|
||||
// GetJobsByType mocks base method.
|
||||
@@ -2105,6 +2105,21 @@ func (mr *MockClientMockRecorder) UpdateIncomingWebhook(arg0, arg1 interface{})
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateIncomingWebhook", reflect.TypeOf((*MockClient)(nil).UpdateIncomingWebhook), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateJobStatus mocks base method.
|
||||
func (m *MockClient) UpdateJobStatus(arg0 context.Context, arg1, arg2 string, arg3 bool) (*model.Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateJobStatus", arg0, arg1, arg2, arg3)
|
||||
ret0, _ := ret[0].(*model.Response)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateJobStatus indicates an expected call of UpdateJobStatus.
|
||||
func (mr *MockClientMockRecorder) UpdateJobStatus(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateJobStatus", reflect.TypeOf((*MockClient)(nil).UpdateJobStatus), arg0, arg1, arg2, arg3)
|
||||
}
|
||||
|
||||
// UpdateOutgoingWebhook mocks base method.
|
||||
func (m *MockClient) UpdateOutgoingWebhook(arg0 context.Context, arg1 *model.OutgoingWebhook) (*model.OutgoingWebhook, *model.Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -2176,6 +2176,10 @@
|
||||
"id": "api.job.retrieve.nopermissions",
|
||||
"translation": "The job types of a job you are trying to retrieve does not contain permissions"
|
||||
},
|
||||
{
|
||||
"id": "api.job.status.invalid",
|
||||
"translation": "Invalid status set"
|
||||
},
|
||||
{
|
||||
"id": "api.job.unable_to_create_job.incorrect_job_type",
|
||||
"translation": "The job type of the job you are trying to create is invalid"
|
||||
@@ -2188,6 +2192,10 @@
|
||||
"id": "api.job.unable_to_download_job.incorrect_job_type",
|
||||
"translation": "The job type you are trying to download is not supported at the moment"
|
||||
},
|
||||
{
|
||||
"id": "api.job.unable_to_manage_job.incorrect_job_type",
|
||||
"translation": "You do not have permission to manage this job type"
|
||||
},
|
||||
{
|
||||
"id": "api.ldap_group.not_found",
|
||||
"translation": "ldap group not found"
|
||||
@@ -5670,6 +5678,10 @@
|
||||
"id": "app.job.update.app_error",
|
||||
"translation": "Unable to update the job."
|
||||
},
|
||||
{
|
||||
"id": "app.job.update_status.app_error",
|
||||
"translation": "Unable to update job status. Invalid status set"
|
||||
},
|
||||
{
|
||||
"id": "app.last_accessible_file.app_error",
|
||||
"translation": "Error fetching last accessible file"
|
||||
|
||||
@@ -7018,8 +7018,8 @@ func (c *Client4) GetJob(ctx context.Context, id string) (*Job, *Response, error
|
||||
}
|
||||
|
||||
// GetJobs gets all jobs, sorted with the job that was created most recently first.
|
||||
func (c *Client4) GetJobs(ctx context.Context, page int, perPage int) ([]*Job, *Response, error) {
|
||||
r, err := c.DoAPIGet(ctx, c.jobsRoute()+fmt.Sprintf("?page=%v&per_page=%v", page, perPage), "")
|
||||
func (c *Client4) GetJobs(ctx context.Context, jobType string, status string, page int, perPage int) ([]*Job, *Response, error) {
|
||||
r, err := c.DoAPIGet(ctx, c.jobsRoute()+fmt.Sprintf("?page=%v&per_page=%v&job_type=%v&status=%v", page, perPage, jobType, status), "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
@@ -7088,6 +7088,23 @@ func (c *Client4) DownloadJob(ctx context.Context, jobId string) ([]byte, *Respo
|
||||
return data, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// UpdateJobStatus updates the status of a job
|
||||
func (c *Client4) UpdateJobStatus(ctx context.Context, jobId string, status string, force bool) (*Response, error) {
|
||||
buf, err := json.Marshal(map[string]any{
|
||||
"status": status,
|
||||
"force": force,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, NewAppError("UpdateJobStatus", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
r, err := c.DoAPIPatchBytes(ctx, c.jobsRoute()+fmt.Sprintf("/%v/status", jobId), buf)
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
// Roles Section
|
||||
|
||||
// GetAllRoles returns a list of all the roles.
|
||||
|
||||
@@ -108,7 +108,31 @@ func (j *Job) IsValid() *AppError {
|
||||
return NewAppError("Job.IsValid", "model.job.is_valid.create_at.app_error", nil, "id="+j.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
switch j.Status {
|
||||
validStatus := IsValidJobStatus(j.Status)
|
||||
if !validStatus {
|
||||
return NewAppError("Job.IsValid", "model.job.is_valid.status.app_error", nil, "id="+j.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j *Job) IsValidStatusChange(newStatus string) bool {
|
||||
currentStatus := j.Status
|
||||
|
||||
switch currentStatus {
|
||||
case JobStatusInProgress:
|
||||
return newStatus == JobStatusPending || newStatus == JobStatusCancelRequested
|
||||
case JobStatusPending:
|
||||
return newStatus == JobStatusCancelRequested
|
||||
case JobStatusCancelRequested:
|
||||
return newStatus == JobStatusCanceled
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func IsValidJobStatus(status string) bool {
|
||||
switch status {
|
||||
case JobStatusPending,
|
||||
JobStatusInProgress,
|
||||
JobStatusSuccess,
|
||||
@@ -117,10 +141,20 @@ func (j *Job) IsValid() *AppError {
|
||||
JobStatusCancelRequested,
|
||||
JobStatusCanceled:
|
||||
default:
|
||||
return NewAppError("Job.IsValid", "model.job.is_valid.status.app_error", nil, "id="+j.Id, http.StatusBadRequest)
|
||||
return false
|
||||
}
|
||||
|
||||
return nil
|
||||
return true
|
||||
}
|
||||
|
||||
func IsValidJobType(jobType string) bool {
|
||||
for _, t := range AllJobTypes {
|
||||
if t == jobType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (j *Job) LogClone() any {
|
||||
|
||||
@@ -121,3 +121,113 @@ func TestJobIsValid(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestJobIsValidStatusChange(t *testing.T) {
|
||||
t.Run("invalid status change", func(t *testing.T) {
|
||||
job := &Job{
|
||||
Id: "arandomstring0123456789012",
|
||||
Type: JobTypeExportProcess,
|
||||
Priority: 42,
|
||||
CreateAt: 1336,
|
||||
StartAt: 1337,
|
||||
LastActivityAt: 1666609360813,
|
||||
Status: JobStatusInProgress,
|
||||
Progress: 32,
|
||||
Data: StringMap{"Hello": "World"},
|
||||
}
|
||||
|
||||
require.False(t, job.IsValidStatusChange("invalid!"))
|
||||
})
|
||||
|
||||
t.Run("valid status change from in_progress", func(t *testing.T) {
|
||||
job := &Job{
|
||||
Id: "arandomstring0123456789012",
|
||||
Type: JobTypeExportProcess,
|
||||
Priority: 42,
|
||||
CreateAt: 1336,
|
||||
StartAt: 1337,
|
||||
LastActivityAt: 1666609360813,
|
||||
Status: JobStatusInProgress,
|
||||
Progress: 32,
|
||||
Data: StringMap{"Hello": "World"},
|
||||
}
|
||||
|
||||
require.True(t, job.IsValidStatusChange(JobStatusPending))
|
||||
require.True(t, job.IsValidStatusChange(JobStatusCancelRequested))
|
||||
require.False(t, job.IsValidStatusChange(JobStatusCanceled))
|
||||
})
|
||||
|
||||
t.Run("valid status change from pending", func(t *testing.T) {
|
||||
job := &Job{
|
||||
Id: "arandomstring0123456789012",
|
||||
Type: JobTypeExportProcess,
|
||||
Priority: 42,
|
||||
CreateAt: 1336,
|
||||
StartAt: 1337,
|
||||
LastActivityAt: 1666609360813,
|
||||
Status: JobStatusPending,
|
||||
Progress: 32,
|
||||
Data: StringMap{"Hello": "World"},
|
||||
}
|
||||
|
||||
require.True(t, job.IsValidStatusChange(JobStatusCancelRequested))
|
||||
require.False(t, job.IsValidStatusChange(JobStatusInProgress))
|
||||
})
|
||||
|
||||
t.Run("valid status change from cancel_requested", func(t *testing.T) {
|
||||
job := &Job{
|
||||
Id: "arandomstring0123456789012",
|
||||
Type: JobTypeExportProcess,
|
||||
Priority: 42,
|
||||
CreateAt: 1336,
|
||||
StartAt: 1337,
|
||||
LastActivityAt: 1666609360813,
|
||||
Status: JobStatusCancelRequested,
|
||||
Progress: 32,
|
||||
Data: StringMap{"Hello": "World"},
|
||||
}
|
||||
|
||||
require.True(t, job.IsValidStatusChange(JobStatusCanceled))
|
||||
require.False(t, job.IsValidStatusChange(JobStatusPending))
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsValidJobType(t *testing.T) {
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
validTypes := []string{JobTypeExportProcess, JobTypeImportProcess}
|
||||
for _, jobType := range validTypes {
|
||||
t.Run(jobType, func(t *testing.T) {
|
||||
require.True(t, IsValidJobType(jobType))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
invalidTypes := []string{"invalid!", ""}
|
||||
for _, jobType := range invalidTypes {
|
||||
t.Run(jobType, func(t *testing.T) {
|
||||
require.False(t, IsValidJobType(jobType))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsValidJobStatus(t *testing.T) {
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
validStatuses := []string{JobStatusCancelRequested, JobStatusCanceled, JobStatusError, JobStatusInProgress, JobStatusPending, JobStatusSuccess, JobStatusWarning}
|
||||
for _, status := range validStatuses {
|
||||
t.Run(status, func(t *testing.T) {
|
||||
require.True(t, IsValidJobStatus(status))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
invalidStatuses := []string{"invalid!", ""}
|
||||
for _, status := range invalidStatuses {
|
||||
t.Run(status, func(t *testing.T) {
|
||||
require.False(t, IsValidJobStatus(status))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -47,4 +47,5 @@ const (
|
||||
MigrationKeyAddIPFilteringPermissions = "add_ip_filtering_permissions"
|
||||
MigrationKeyAddOutgoingOAuthConnectionsPermissions = "add_outgoing_oauth_connections_permissions"
|
||||
MigrationKeyAddChannelBookmarksPermissions = "add_channel_bookmarks_permissions"
|
||||
MigrationKeyAddManageJobAncillaryPermissions = "add_manage_jobs_ancillary_permissions"
|
||||
)
|
||||
|
||||
@@ -123,8 +123,10 @@ var PermissionManageSharedChannels *Permission
|
||||
var PermissionManageSecureConnections *Permission
|
||||
var PermissionDownloadComplianceExportResult *Permission
|
||||
var PermissionCreateDataRetentionJob *Permission
|
||||
var PermissionManageDataRetentionJob *Permission
|
||||
var PermissionReadDataRetentionJob *Permission
|
||||
var PermissionCreateComplianceExportJob *Permission
|
||||
var PermissionManageComplianceExportJob *Permission
|
||||
var PermissionReadComplianceExportJob *Permission
|
||||
var PermissionReadAudits *Permission
|
||||
var PermissionTestElasticsearch *Permission
|
||||
@@ -136,12 +138,16 @@ var PermissionRecycleDatabaseConnections *Permission
|
||||
var PermissionPurgeElasticsearchIndexes *Permission
|
||||
var PermissionTestEmail *Permission
|
||||
var PermissionCreateElasticsearchPostIndexingJob *Permission
|
||||
var PermissionManageElasticsearchPostIndexingJob *Permission
|
||||
var PermissionCreateElasticsearchPostAggregationJob *Permission
|
||||
var PermissionManageElasticsearchPostAggregationJob *Permission
|
||||
var PermissionReadElasticsearchPostIndexingJob *Permission
|
||||
var PermissionReadElasticsearchPostAggregationJob *Permission
|
||||
var PermissionPurgeBleveIndexes *Permission
|
||||
var PermissionCreatePostBleveIndexesJob *Permission
|
||||
var PermissionManagePostBleveIndexesJob *Permission
|
||||
var PermissionCreateLdapSyncJob *Permission
|
||||
var PermissionManageLdapSyncJob *Permission
|
||||
var PermissionReadLdapSyncJob *Permission
|
||||
var PermissionTestLdap *Permission
|
||||
var PermissionInvalidateEmailInvite *Permission
|
||||
@@ -790,6 +796,12 @@ func initializePermissions() {
|
||||
"",
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
PermissionManageDataRetentionJob = &Permission{
|
||||
"manage_data_retention_job",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
PermissionReadDataRetentionJob = &Permission{
|
||||
"read_data_retention_job",
|
||||
"",
|
||||
@@ -803,6 +815,12 @@ func initializePermissions() {
|
||||
"",
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
PermissionManageComplianceExportJob = &Permission{
|
||||
"manage_compliance_export_job",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
PermissionReadComplianceExportJob = &Permission{
|
||||
"read_compliance_export_job",
|
||||
"",
|
||||
@@ -831,12 +849,25 @@ func initializePermissions() {
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
|
||||
PermissionManagePostBleveIndexesJob = &Permission{
|
||||
"manage_post_bleve_indexes_job",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
|
||||
PermissionCreateLdapSyncJob = &Permission{
|
||||
"create_ldap_sync_job",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
PermissionManageLdapSyncJob = &Permission{
|
||||
"manage_ldap_sync_job",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
PermissionReadLdapSyncJob = &Permission{
|
||||
"read_ldap_sync_job",
|
||||
"",
|
||||
@@ -1029,12 +1060,24 @@ func initializePermissions() {
|
||||
"",
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
PermissionManageElasticsearchPostIndexingJob = &Permission{
|
||||
"manage_elasticsearch_post_indexing_job",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
PermissionCreateElasticsearchPostAggregationJob = &Permission{
|
||||
"create_elasticsearch_post_aggregation_job",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
PermissionManageElasticsearchPostAggregationJob = &Permission{
|
||||
"manage_elasticsearch_post_aggregation_job",
|
||||
"",
|
||||
"",
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
PermissionReadElasticsearchPostIndexingJob = &Permission{
|
||||
"read_elasticsearch_post_indexing_job",
|
||||
"",
|
||||
@@ -2347,8 +2390,10 @@ func initializePermissions() {
|
||||
PermissionManageSecureConnections,
|
||||
PermissionDownloadComplianceExportResult,
|
||||
PermissionCreateDataRetentionJob,
|
||||
PermissionManageDataRetentionJob,
|
||||
PermissionReadDataRetentionJob,
|
||||
PermissionCreateComplianceExportJob,
|
||||
PermissionManageComplianceExportJob,
|
||||
PermissionReadComplianceExportJob,
|
||||
PermissionReadAudits,
|
||||
PermissionTestSiteURL,
|
||||
@@ -2360,12 +2405,16 @@ func initializePermissions() {
|
||||
PermissionPurgeElasticsearchIndexes,
|
||||
PermissionTestEmail,
|
||||
PermissionCreateElasticsearchPostIndexingJob,
|
||||
PermissionManageElasticsearchPostIndexingJob,
|
||||
PermissionCreateElasticsearchPostAggregationJob,
|
||||
PermissionManageElasticsearchPostAggregationJob,
|
||||
PermissionReadElasticsearchPostIndexingJob,
|
||||
PermissionReadElasticsearchPostAggregationJob,
|
||||
PermissionPurgeBleveIndexes,
|
||||
PermissionCreatePostBleveIndexesJob,
|
||||
PermissionManagePostBleveIndexesJob,
|
||||
PermissionCreateLdapSyncJob,
|
||||
PermissionManageLdapSyncJob,
|
||||
PermissionReadLdapSyncJob,
|
||||
PermissionTestLdap,
|
||||
PermissionInvalidateEmailInvite,
|
||||
|
||||
@@ -90,7 +90,9 @@ func init() {
|
||||
PermissionSysconsoleWriteEnvironmentElasticsearch.Id: {
|
||||
PermissionTestElasticsearch,
|
||||
PermissionCreateElasticsearchPostIndexingJob,
|
||||
PermissionManageElasticsearchPostIndexingJob,
|
||||
PermissionCreateElasticsearchPostAggregationJob,
|
||||
PermissionManageElasticsearchPostAggregationJob,
|
||||
PermissionPurgeElasticsearchIndexes,
|
||||
},
|
||||
PermissionSysconsoleWriteEnvironmentFileStorage.Id: {
|
||||
@@ -145,12 +147,14 @@ func init() {
|
||||
},
|
||||
PermissionSysconsoleWriteComplianceDataRetentionPolicy.Id: {
|
||||
PermissionCreateDataRetentionJob,
|
||||
PermissionManageDataRetentionJob,
|
||||
},
|
||||
PermissionSysconsoleReadComplianceDataRetentionPolicy.Id: {
|
||||
PermissionReadDataRetentionJob,
|
||||
},
|
||||
PermissionSysconsoleWriteComplianceComplianceExport.Id: {
|
||||
PermissionCreateComplianceExportJob,
|
||||
PermissionManageComplianceExportJob,
|
||||
PermissionDownloadComplianceExportResult,
|
||||
},
|
||||
PermissionSysconsoleReadComplianceComplianceExport.Id: {
|
||||
@@ -163,9 +167,11 @@ func init() {
|
||||
PermissionSysconsoleWriteExperimentalBleve.Id: {
|
||||
PermissionCreatePostBleveIndexesJob,
|
||||
PermissionPurgeBleveIndexes,
|
||||
PermissionManagePostBleveIndexesJob,
|
||||
},
|
||||
PermissionSysconsoleWriteAuthenticationLdap.Id: {
|
||||
PermissionCreateLdapSyncJob,
|
||||
PermissionManageLdapSyncJob,
|
||||
PermissionAddLdapPublicCert,
|
||||
PermissionRemoveLdapPublicCert,
|
||||
PermissionAddLdapPrivateCert,
|
||||
|
||||
Ссылка в новой задаче
Block a user