* add job list and update  job status command to mmctl
Этот коммит содержится в:
Ben Cooke
2024-06-17 12:07:05 -04:00
коммит произвёл GitHub
родитель 5894abc36e
Коммит 9187c772b6
38 изменённых файлов: 1423 добавлений и 101 удалений

Просмотреть файл

@@ -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 Обычный файл
Просмотреть файл

@@ -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 Обычный файл
Просмотреть файл

@@ -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 Обычный файл
Просмотреть файл

@@ -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 Обычный файл
Просмотреть файл

@@ -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 Обычный файл
Просмотреть файл

@@ -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,