Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
379 lines
11 KiB
Go
379 lines
11 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package commands
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"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 TeamCmd = &cobra.Command{
|
|
Use: "team",
|
|
Short: "Management of teams",
|
|
}
|
|
|
|
var TeamCreateCmd = &cobra.Command{
|
|
Use: "create",
|
|
Short: "Create a team",
|
|
Long: `Create a team.`,
|
|
Example: ` team create --name mynewteam --display-name "My New Team"
|
|
team create --name private --display-name "My New Private Team" --private`,
|
|
RunE: withClient(createTeamCmdF),
|
|
}
|
|
|
|
var DeleteTeamsCmd = &cobra.Command{
|
|
Use: "delete [teams]",
|
|
Short: "Delete teams",
|
|
Long: `Permanently delete some teams.
|
|
Permanently deletes a team along with all related information including posts from the database.`,
|
|
Example: " team delete myteam",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: withClient(deleteTeamsCmdF),
|
|
}
|
|
|
|
var ArchiveTeamsCmd = &cobra.Command{
|
|
Use: "archive [teams]",
|
|
Short: "Archive teams",
|
|
Long: `Archive some teams.
|
|
Archives a team along with all related information including posts from the database.`,
|
|
Example: " team archive myteam",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: withClient(archiveTeamsCmdF),
|
|
}
|
|
|
|
var RestoreTeamsCmd = &cobra.Command{
|
|
Use: "restore [teams]",
|
|
Short: "Restore teams",
|
|
Long: "Restores archived teams.",
|
|
Example: " team restore myteam",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: withClient(restoreTeamsCmdF),
|
|
}
|
|
|
|
var ListTeamsCmd = &cobra.Command{
|
|
Use: "list",
|
|
Short: "List all teams",
|
|
Long: `List all teams on the server.`,
|
|
Example: " team list",
|
|
RunE: withClient(listTeamsCmdF),
|
|
}
|
|
|
|
var SearchTeamCmd = &cobra.Command{
|
|
Use: "search [teams]",
|
|
Short: "Search for teams",
|
|
Long: "Search for teams based on name",
|
|
Example: " team search team1",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: withClient(searchTeamCmdF),
|
|
}
|
|
|
|
// RenameTeamCmd is the command to rename team along with its display name
|
|
var RenameTeamCmd = &cobra.Command{
|
|
Use: "rename [team]",
|
|
Short: "Rename team",
|
|
Long: "Rename an existing team",
|
|
Example: " team rename old-team --display-name 'New Display Name'",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: withClient(renameTeamCmdF),
|
|
}
|
|
|
|
var ModifyTeamsCmd = &cobra.Command{
|
|
Use: "modify [teams] [flag]",
|
|
Short: "Modify teams",
|
|
Long: "Modify teams' privacy setting to public or private",
|
|
Example: " team modify myteam --private",
|
|
Args: cobra.MinimumNArgs(1),
|
|
RunE: withClient(modifyTeamsCmdF),
|
|
}
|
|
|
|
func init() {
|
|
TeamCreateCmd.Flags().String("name", "", "Team Name")
|
|
TeamCreateCmd.Flags().String("display-name", "", "Team Display Name")
|
|
TeamCreateCmd.Flags().Bool("private", false, "Create a private team.")
|
|
TeamCreateCmd.Flags().String("email", "", "Administrator Email (anyone with this email is automatically a team admin)")
|
|
|
|
DeleteTeamsCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the team and a DB backup has been performed.")
|
|
ArchiveTeamsCmd.Flags().Bool("confirm", false, "Confirm you really want to archive the team and a DB backup has been performed.")
|
|
|
|
ModifyTeamsCmd.Flags().Bool("private", false, "Modify team to be private.")
|
|
ModifyTeamsCmd.Flags().Bool("public", false, "Modify team to be public.")
|
|
|
|
// Add flag declaration for RenameTeam
|
|
RenameTeamCmd.Flags().String("display-name", "", "Team Display Name")
|
|
_ = RenameTeamCmd.MarkFlagRequired("display-name")
|
|
|
|
TeamCmd.AddCommand(
|
|
TeamCreateCmd,
|
|
DeleteTeamsCmd,
|
|
ArchiveTeamsCmd,
|
|
RestoreTeamsCmd,
|
|
ListTeamsCmd,
|
|
SearchTeamCmd,
|
|
RenameTeamCmd,
|
|
ModifyTeamsCmd,
|
|
)
|
|
|
|
RootCmd.AddCommand(TeamCmd)
|
|
}
|
|
|
|
func createTeamCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
printer.SetSingle(true)
|
|
|
|
name, errn := cmd.Flags().GetString("name")
|
|
if errn != nil || name == "" {
|
|
return errors.New("name is required")
|
|
}
|
|
displayname, errdn := cmd.Flags().GetString("display-name")
|
|
if errdn != nil || displayname == "" {
|
|
return errors.New("display-name is required")
|
|
}
|
|
email, _ := cmd.Flags().GetString("email")
|
|
useprivate, _ := cmd.Flags().GetBool("private")
|
|
|
|
teamType := model.TeamOpen
|
|
allowOpenInvite := true
|
|
if useprivate {
|
|
teamType = model.TeamInvite
|
|
allowOpenInvite = false
|
|
}
|
|
|
|
team := &model.Team{
|
|
Name: name,
|
|
DisplayName: displayname,
|
|
Email: email,
|
|
Type: teamType,
|
|
AllowOpenInvite: allowOpenInvite,
|
|
}
|
|
|
|
newTeam, _, err := c.CreateTeam(context.TODO(), team)
|
|
if err != nil {
|
|
return errors.New("Team creation failed: " + err.Error())
|
|
}
|
|
|
|
printer.PrintT("New team {{.Name}} successfully created", newTeam)
|
|
|
|
return nil
|
|
}
|
|
|
|
func deleteTeam(c client.Client, team *model.Team) (*model.Response, error) {
|
|
return c.PermanentDeleteTeam(context.TODO(), team.Id)
|
|
}
|
|
|
|
func archiveTeamsCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
var result *multierror.Error
|
|
confirmFlag, _ := cmd.Flags().GetBool("confirm")
|
|
if !confirmFlag {
|
|
if err := getConfirmation("Are you sure you want to archive the specified teams?", true); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
teams := getTeamsFromTeamArgs(c, args)
|
|
for i, team := range teams {
|
|
if team == nil {
|
|
printer.PrintError("Unable to find team '" + args[i] + "'")
|
|
result = multierror.Append(result, errors.New("Unable to find team '"+args[i]+"'"))
|
|
continue
|
|
}
|
|
if _, err := c.SoftDeleteTeam(context.TODO(), team.Id); err != nil {
|
|
printer.PrintError("Unable to archive team '" + team.Name + "' error: " + err.Error())
|
|
result = multierror.Append(result, errors.New("Unable to archive team '"+team.Name+"' error: "+err.Error()))
|
|
} else {
|
|
printer.PrintT("Archived team '{{.Name}}'", team)
|
|
}
|
|
}
|
|
|
|
return result.ErrorOrNil()
|
|
}
|
|
|
|
func listTeamsCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
page := 0
|
|
for {
|
|
teams, _, err := c.GetAllTeams(context.TODO(), "", page, DefaultPageSize)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, team := range teams {
|
|
if team.DeleteAt > 0 {
|
|
printer.PrintT("{{.Name}} (archived)", team)
|
|
} else {
|
|
printer.PrintT("{{.Name}}", team)
|
|
}
|
|
}
|
|
|
|
if len(teams) < DefaultPageSize {
|
|
break
|
|
}
|
|
|
|
page++
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func searchTeamCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
var teams []*model.Team
|
|
|
|
for _, searchTerm := range args {
|
|
foundTeams, _, err := c.SearchTeams(context.TODO(), &model.TeamSearch{Term: searchTerm})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(foundTeams) == 0 {
|
|
printer.PrintError("Unable to find team '" + searchTerm + "'")
|
|
continue
|
|
}
|
|
|
|
teams = append(teams, foundTeams...)
|
|
}
|
|
|
|
sortedTeams := removeDuplicatesAndSortTeams(teams)
|
|
|
|
for _, team := range sortedTeams {
|
|
printer.PrintT("{{.Name}}: {{.DisplayName}} ({{.Id}})", team)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Removes duplicates and sorts teams by name
|
|
func removeDuplicatesAndSortTeams(teams []*model.Team) []*model.Team {
|
|
keys := make(map[string]bool)
|
|
result := []*model.Team{}
|
|
for _, team := range teams {
|
|
if _, value := keys[team.Name]; !value {
|
|
keys[team.Name] = true
|
|
result = append(result, team)
|
|
}
|
|
}
|
|
sort.Slice(result, func(i, j int) bool {
|
|
return result[i].Name < result[j].Name
|
|
})
|
|
return result
|
|
}
|
|
|
|
func renameTeamCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
oldTeamName := args[0]
|
|
|
|
newDisplayName, _ := cmd.Flags().GetString("display-name")
|
|
if newDisplayName == "" {
|
|
return errors.New("display-name is required")
|
|
}
|
|
|
|
team := getTeamFromTeamArg(c, oldTeamName)
|
|
if team == nil {
|
|
return errors.New("Unable to find team '" + oldTeamName + "', to see the all teams try 'team list' command")
|
|
}
|
|
|
|
team.DisplayName = newDisplayName
|
|
|
|
// Using UpdateTeam API Method to rename team
|
|
_, _, err := c.UpdateTeam(context.TODO(), team)
|
|
if err != nil {
|
|
return errors.New("Cannot rename team '" + oldTeamName + "', error : " + err.Error())
|
|
}
|
|
|
|
printer.Print("'" + oldTeamName + "' team renamed")
|
|
return nil
|
|
}
|
|
|
|
func deleteTeamsCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
confirmFlag, _ := cmd.Flags().GetBool("confirm")
|
|
if !confirmFlag {
|
|
if err := getConfirmation("Are you sure you want to delete the teams specified? All data will be permanently deleted?", true); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var result *multierror.Error
|
|
|
|
teams := getTeamsFromTeamArgs(c, args)
|
|
for i, team := range teams {
|
|
if team == nil {
|
|
printer.PrintError("Unable to find team '" + args[i] + "'")
|
|
result = multierror.Append(result, fmt.Errorf("unable to find team %s", args[i]))
|
|
continue
|
|
}
|
|
if _, err := deleteTeam(c, team); err != nil {
|
|
printer.PrintError("Unable to delete team '" + team.Name + "' error: " + err.Error())
|
|
result = multierror.Append(result, fmt.Errorf("unable to delete team %s error: %w", team.Name, err))
|
|
} else {
|
|
printer.PrintT("Deleted team '{{.Name}}'", team)
|
|
}
|
|
}
|
|
|
|
return result.ErrorOrNil()
|
|
}
|
|
|
|
func modifyTeamsCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
private, _ := cmd.Flags().GetBool("private")
|
|
public, _ := cmd.Flags().GetBool("public")
|
|
|
|
if (!private && !public) || (private && public) {
|
|
return errors.New("must specify one of --private or --public")
|
|
}
|
|
|
|
var errs *multierror.Error
|
|
|
|
// I = invite only (private)
|
|
// O = open (public)
|
|
privacy := model.TeamInvite
|
|
if public {
|
|
privacy = model.TeamOpen
|
|
}
|
|
|
|
teams := getTeamsFromTeamArgs(c, args)
|
|
for i, team := range teams {
|
|
if team == nil {
|
|
errs = multierror.Append(errs, errors.New("Unable to find team '"+args[i]+"'"))
|
|
continue
|
|
}
|
|
|
|
updatedTeam, _, err := c.UpdateTeamPrivacy(context.TODO(), team.Id, privacy)
|
|
if err != nil {
|
|
errs = multierror.Append(errs, errors.New("Unable to modify team '"+team.Name+"' error: "+err.Error()))
|
|
continue
|
|
}
|
|
|
|
printer.PrintT("Modified team '{{.Name}}'", updatedTeam)
|
|
}
|
|
|
|
for _, err := range errs.WrappedErrors() {
|
|
printer.PrintError(err.Error())
|
|
}
|
|
|
|
return errs.ErrorOrNil()
|
|
}
|
|
|
|
func restoreTeamsCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
teams := getTeamsFromTeamArgs(c, args)
|
|
var result *multierror.Error
|
|
for i, team := range teams {
|
|
if team == nil {
|
|
result = multierror.Append(result, fmt.Errorf("unable to find team '%s'", args[i]))
|
|
printer.PrintError("Unable to find team '" + args[i] + "'")
|
|
continue
|
|
}
|
|
if rteam, _, err := c.RestoreTeam(context.TODO(), team.Id); err != nil {
|
|
result = multierror.Append(result, fmt.Errorf("unable to restore team '%s' error: %w", team.Name, err))
|
|
printer.PrintError("Unable to restore team '" + team.Name + "' error: " + err.Error())
|
|
} else {
|
|
printer.PrintT("Restored team '{{.Name}}'", rteam)
|
|
}
|
|
}
|
|
return result.ErrorOrNil()
|
|
}
|