Files
zeekay 8feb24ea1f One name for this fork, and it is ours
The server module named github.com/hanzoai/team/server/v8 — a path that
resolves to the retired workspace's repository, not this one — while
server/public still published github.com/mattermost/mattermost/server/public.
Both are github.com/hanzoteam/server/... now, which is where this code
actually lives.

That second half is what made the binary unbootable. Upstream's public path
stayed claimed by upstream, mattermost-plugin-ai imported it, and the build
linked public/model twice; public/plugin's gob.Register then ran twice under
one name and panicked at init, before main. Reverting the rename traded the
brand for a working binary. Rebranding the plugin alongside it needs no such
trade: the agents fork is github.com/hanzoteam/agents/v2 against our public
module, so no module named mattermost/mattermost is in the graph at all.

Measured: `go list -deps ./cmd/mattermost` names exactly one public/model, and
it is ours.
2026-08-28 23:35:36 -07:00

304 lines
7.9 KiB
Go

// 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/hanzoteam/server/server/public/model"
"github.com/hanzoteam/server/server/v8/cmd/mmctl/client"
"github.com/hanzoteam/server/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 createJobCmd = &cobra.Command{
Use: "create [type]",
Short: "Create a new job",
Long: "Create a new job of the given type. Use --data to pass type-specific options as key=value pairs.",
Example: ` job create ldap_sync
job create data_retention
job create message_export --data batch_size=1000`,
Args: cobra.ExactArgs(1),
ValidArgsFunction: jobTypeCompletionF,
RunE: withClient(createJobCmdF),
}
var showJobCmd = &cobra.Command{
Use: "show [job]",
Short: "Show a job",
Example: " job show jobID",
Args: cobra.ExactArgs(1),
ValidArgsFunction: validateArgsWithClient(jobCompletionF),
RunE: withClient(showJobCmdF),
}
var cancelJobCmd = &cobra.Command{
Use: "cancel [job]",
Short: "Cancel a job",
Example: " job cancel jobID",
Args: cobra.ExactArgs(1),
ValidArgsFunction: validateArgsWithClient(jobCompletionF),
RunE: withClient(cancelJobCmdF),
}
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 jobs")
listJobsCmd.Flags().Int("per-page", 5, "Number of jobs to be fetched")
listJobsCmd.Flags().Bool("all", false, "Fetch all 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")
createJobCmd.Flags().StringToString("data", nil, "Comma-separated list of key=value pairs passed to the job as type-specific options")
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,
createJobCmd,
showJobCmd,
cancelJobCmd,
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 createJobCmdF(c client.Client, cmd *cobra.Command, args []string) error {
jobType := args[0]
data, err := cmd.Flags().GetStringToString("data")
if err != nil {
return err
}
job, _, err := c.CreateJob(context.TODO(), &model.Job{
Type: jobType,
Data: data,
})
if err != nil {
return fmt.Errorf("failed to create job: %w", err)
}
printer.PrintT("Job successfully created, ID: {{.Id}}", job)
return nil
}
func showJobCmdF(c client.Client, cmd *cobra.Command, args []string) error {
jobId := args[0]
if !model.IsValidId(jobId) {
return fmt.Errorf("invalid job ID: %s", jobId)
}
job, _, err := c.GetJob(context.TODO(), jobId)
if err != nil {
return fmt.Errorf("failed to get job: %w", err)
}
printJob(job)
return nil
}
func cancelJobCmdF(c client.Client, cmd *cobra.Command, args []string) error {
jobId := args[0]
if !model.IsValidId(jobId) {
return fmt.Errorf("invalid job ID: %s", jobId)
}
if _, err := c.CancelJob(context.TODO(), jobId); err != nil {
return fmt.Errorf("failed to cancel job: %w", err)
}
return nil
}
func jobTypeCompletionF(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
if len(args) > 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return model.AllJobTypes[:], cobra.ShellCompDirectiveNoFileComp
}
func jobCompletionF(ctx context.Context, c client.Client, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return fetchAndComplete(
func(ctx context.Context, c client.Client, page, perPage int) ([]*model.Job, *model.Response, error) {
return c.GetJobs(ctx, "", "", page, perPage)
},
func(j *model.Job) []string { return []string{j.Id} },
)(ctx, c, cmd, args, toComplete)
}
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)
}
}