MM-36448: Removes legacy CLI commands. (#17995)
* MM-36448: Removes legacy CLI commands. * MM-26448: Update translations. * MM-36448: Fixes some lint errors. * MM-36448: Conflict resolution error fix. Lint fixes. * MM-36448: Removes some more commands. * MM-36448: Removes unused functions. * MM-36448: Re-adds config command. * MM-36448: Re-adds func for use by config. * MM-36448: Moved structs back. * MM-36488: Re-adds version. * MM-36448: Re-added some commands. * MM-36448: Fix tests. * MM-36448: Removed unused func. * MM-36448: Removes test. * MM-36448: Removes uses of 'config set'. * MM-36448: Moves some test structs. * MM-36448: Removes the logs command. * MM-36448: Re-deleted file after bad merge. * MM-36448: Deleted test files again. * MM-36448: Re-delete files. Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
2560469bc7
Коммит
8f01a1b5a1
@@ -1,666 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app"
|
||||
"github.com/mattermost/mattermost-server/v6/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
var ChannelCmd = &cobra.Command{
|
||||
Use: "channel",
|
||||
Short: "Management of channels",
|
||||
}
|
||||
|
||||
var ChannelCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a channel",
|
||||
Long: `Create a channel.`,
|
||||
Example: ` channel create --team myteam --name mynewchannel --display_name "My New Channel"
|
||||
channel create --team myteam --name mynewprivatechannel --display_name "My New Private Channel" --private`,
|
||||
RunE: createChannelCmdF,
|
||||
}
|
||||
|
||||
var ChannelRenameCmd = &cobra.Command{
|
||||
Use: "rename",
|
||||
Short: "Rename a channel",
|
||||
Long: `Rename a channel.`,
|
||||
Example: `" channel rename myteam:mychannel newchannelname --display_name "New Display Name"`,
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: renameChannelCmdF,
|
||||
}
|
||||
|
||||
var RemoveChannelUsersCmd = &cobra.Command{
|
||||
Use: "remove [channel] [users]",
|
||||
Short: "Remove users from channel",
|
||||
Long: "Remove some users from channel",
|
||||
Example: ` channel remove myteam:mychannel user@example.com username
|
||||
channel remove myteam:mychannel --all-users`,
|
||||
RunE: removeChannelUsersCmdF,
|
||||
}
|
||||
|
||||
var AddChannelUsersCmd = &cobra.Command{
|
||||
Use: "add [channel] [users]",
|
||||
Short: "Add users to channel",
|
||||
Long: "Add some users to channel",
|
||||
Example: " channel add myteam:mychannel user@example.com username",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: addChannelUsersCmdF,
|
||||
}
|
||||
|
||||
var ArchiveChannelsCmd = &cobra.Command{
|
||||
Use: "archive [channels]",
|
||||
Short: "Archive channels",
|
||||
Long: `Archive some channels.
|
||||
Archive a channel along with all related information including posts from the database.
|
||||
Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
|
||||
Example: " channel archive myteam:mychannel",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: archiveChannelsCmdF,
|
||||
}
|
||||
|
||||
var DeleteChannelsCmd = &cobra.Command{
|
||||
Use: "delete [channels]",
|
||||
Short: "Delete channels",
|
||||
Long: `Permanently delete some channels.
|
||||
Permanently deletes a channel along with all related information including posts from the database.
|
||||
Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
|
||||
Example: " channel delete myteam:mychannel",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: deleteChannelsCmdF,
|
||||
}
|
||||
|
||||
var ListChannelsCmd = &cobra.Command{
|
||||
Use: "list [teams]",
|
||||
Short: "List all channels on specified teams.",
|
||||
Long: `List all channels on specified teams.
|
||||
Archived channels are appended with ' (archived)'.
|
||||
Private channels are appended with ' (private)'.`,
|
||||
Example: " channel list myteam",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: listChannelsCmdF,
|
||||
}
|
||||
|
||||
var MoveChannelsCmd = &cobra.Command{
|
||||
Use: "move [team] [channels] --username [user]",
|
||||
Short: "Moves channels to the specified team",
|
||||
Long: `Moves the provided channels to the specified team.
|
||||
Validates that all users in the channel belong to the target team. Incoming/Outgoing webhooks are moved along with the channel.
|
||||
Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
|
||||
Example: " channel move newteam oldteam:mychannel --username myusername",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: moveChannelsCmdF,
|
||||
}
|
||||
|
||||
var RestoreChannelsCmd = &cobra.Command{
|
||||
Use: "restore [channels]",
|
||||
Short: "Restore some channels",
|
||||
Long: `Restore a previously deleted channel
|
||||
Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
|
||||
Example: " channel restore myteam:mychannel",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: restoreChannelsCmdF,
|
||||
}
|
||||
|
||||
var ModifyChannelCmd = &cobra.Command{
|
||||
Use: "modify [channel] [flags] --username [user]",
|
||||
Short: "Modify a channel's public/private type",
|
||||
Long: `Change the public/private type of a channel.
|
||||
Channel can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
|
||||
Example: " channel modify myteam:mychannel --private --username myusername",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: modifyChannelCmdF,
|
||||
}
|
||||
|
||||
var SearchChannelCmd = &cobra.Command{
|
||||
Use: "search [channel]\n mattermost search --team [team] [channel]",
|
||||
Short: "Search a channel",
|
||||
Long: `Search a channel by channel name.
|
||||
Channel can be specified by team. ie. --team myTeam myChannel or by team ID.`,
|
||||
Example: ` channel search myChannel
|
||||
channel search --team myTeam myChannel`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: searchChannelCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
ChannelCreateCmd.Flags().String("name", "", "Channel Name")
|
||||
ChannelCreateCmd.Flags().String("display_name", "", "Channel Display Name")
|
||||
ChannelCreateCmd.Flags().String("team", "", "Team name or ID")
|
||||
ChannelCreateCmd.Flags().String("header", "", "Channel header")
|
||||
ChannelCreateCmd.Flags().String("purpose", "", "Channel purpose")
|
||||
ChannelCreateCmd.Flags().Bool("private", false, "Create a private channel.")
|
||||
|
||||
MoveChannelsCmd.Flags().String("username", "", "Required. Username who is moving the channel.")
|
||||
|
||||
DeleteChannelsCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the channels.")
|
||||
|
||||
ModifyChannelCmd.Flags().Bool("private", false, "Convert the channel to a private channel")
|
||||
ModifyChannelCmd.Flags().Bool("public", false, "Convert the channel to a public channel")
|
||||
ModifyChannelCmd.Flags().String("username", "", "Required. Username who changes the channel privacy.")
|
||||
|
||||
ChannelRenameCmd.Flags().String("display_name", "", "Channel Display Name")
|
||||
SearchChannelCmd.Flags().String("team", "", "Team name or ID")
|
||||
|
||||
RemoveChannelUsersCmd.Flags().Bool("all-users", false, "Remove all users from the indicated channel.")
|
||||
|
||||
ChannelCmd.AddCommand(
|
||||
ChannelCreateCmd,
|
||||
RemoveChannelUsersCmd,
|
||||
AddChannelUsersCmd,
|
||||
ArchiveChannelsCmd,
|
||||
DeleteChannelsCmd,
|
||||
ListChannelsCmd,
|
||||
MoveChannelsCmd,
|
||||
RestoreChannelsCmd,
|
||||
ModifyChannelCmd,
|
||||
ChannelRenameCmd,
|
||||
SearchChannelCmd,
|
||||
)
|
||||
|
||||
RootCmd.AddCommand(ChannelCmd)
|
||||
}
|
||||
|
||||
func createChannelCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
name, errn := command.Flags().GetString("name")
|
||||
if errn != nil || name == "" {
|
||||
return errors.New("Name is required")
|
||||
}
|
||||
displayname, errdn := command.Flags().GetString("display_name")
|
||||
if errdn != nil || displayname == "" {
|
||||
return errors.New("Display Name is required")
|
||||
}
|
||||
teamArg, errteam := command.Flags().GetString("team")
|
||||
if errteam != nil || teamArg == "" {
|
||||
return errors.New("Team is required")
|
||||
}
|
||||
header, _ := command.Flags().GetString("header")
|
||||
purpose, _ := command.Flags().GetString("purpose")
|
||||
useprivate, _ := command.Flags().GetBool("private")
|
||||
|
||||
channelType := model.ChannelTypeOpen
|
||||
if useprivate {
|
||||
channelType = model.ChannelTypePrivate
|
||||
}
|
||||
|
||||
team := getTeamFromTeamArg(a, teamArg)
|
||||
if team == nil {
|
||||
return errors.New("Unable to find team: " + teamArg)
|
||||
}
|
||||
|
||||
channel := &model.Channel{
|
||||
TeamId: team.Id,
|
||||
Name: name,
|
||||
DisplayName: displayname,
|
||||
Header: header,
|
||||
Purpose: purpose,
|
||||
Type: channelType,
|
||||
CreatorId: "",
|
||||
}
|
||||
|
||||
createdChannel, errCreatedChannel := a.CreateChannel(&request.Context{}, channel, false)
|
||||
if errCreatedChannel != nil {
|
||||
return errCreatedChannel
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("createChannel", audit.Success)
|
||||
auditRec.AddMeta("channel", createdChannel)
|
||||
auditRec.AddMeta("team", team)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
CommandPrettyPrintln("Id: " + createdChannel.Id)
|
||||
CommandPrettyPrintln("Name: " + createdChannel.Name)
|
||||
CommandPrettyPrintln("Display Name: " + createdChannel.DisplayName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeChannelUsersCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
allUsers, _ := command.Flags().GetBool("all-users")
|
||||
|
||||
if allUsers && len(args) != 1 {
|
||||
return errors.New("individual users must not be specified in conjunction with the --all-users flag")
|
||||
}
|
||||
|
||||
if !allUsers && len(args) < 2 {
|
||||
return errors.New("you must specify some users to remove from the channel, or use the --all-users flag to remove them all")
|
||||
}
|
||||
|
||||
channel := getChannelFromChannelArg(a, args[0])
|
||||
if channel == nil {
|
||||
return errors.New("Unable to find channel '" + args[0] + "'")
|
||||
}
|
||||
|
||||
if allUsers {
|
||||
removeAllUsersFromChannel(a, channel)
|
||||
} else {
|
||||
users := getUsersFromUserArgs(a, args[1:])
|
||||
for i, user := range users {
|
||||
removeUserFromChannel(a, channel, user, args[i+1])
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeUserFromChannel(a *app.App, channel *model.Channel, user *model.User, userArg string) {
|
||||
if user == nil {
|
||||
CommandPrintErrorln("Can't find user '" + userArg + "'")
|
||||
return
|
||||
}
|
||||
if err := a.RemoveUserFromChannel(&request.Context{}, user.Id, "", channel); err != nil {
|
||||
CommandPrintErrorln("Unable to remove '" + userArg + "' from " + channel.Name + ". Error: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("removeUserFromChannel", audit.Success)
|
||||
auditRec.AddMeta("channel", channel)
|
||||
auditRec.AddMeta("user", user)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
|
||||
func removeAllUsersFromChannel(a *app.App, channel *model.Channel) {
|
||||
if err := a.Srv().Store.Channel().PermanentDeleteMembersByChannel(channel.Id); err != nil {
|
||||
CommandPrintErrorln("Unable to remove all users from " + channel.Name + ". Error: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("removeAllUsersFromChannel", audit.Success)
|
||||
auditRec.AddMeta("channel", channel)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
|
||||
func addChannelUsersCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
channel := getChannelFromChannelArg(a, args[0])
|
||||
if channel == nil {
|
||||
return errors.New("Unable to find channel '" + args[0] + "'")
|
||||
}
|
||||
|
||||
users := getUsersFromUserArgs(a, args[1:])
|
||||
for i, user := range users {
|
||||
addUserToChannel(a, channel, user, args[i+1])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addUserToChannel(a *app.App, channel *model.Channel, user *model.User, userArg string) {
|
||||
if user == nil {
|
||||
CommandPrintErrorln("Can't find user '" + userArg + "'")
|
||||
return
|
||||
}
|
||||
if _, err := a.AddUserToChannel(user, channel, false); err != nil {
|
||||
CommandPrintErrorln("Unable to add '" + userArg + "' from " + channel.Name + ". Error: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("addUserToChannel", audit.Success)
|
||||
auditRec.AddMeta("channel", channel)
|
||||
auditRec.AddMeta("user", user)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
|
||||
func archiveChannelsCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
channels := getChannelsFromChannelArgs(a, args)
|
||||
for i, channel := range channels {
|
||||
if channel == nil {
|
||||
CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
|
||||
continue
|
||||
}
|
||||
if err := a.Srv().Store.Channel().Delete(channel.Id, model.GetMillis()); err != nil {
|
||||
CommandPrintErrorln("Unable to archive channel '" + channel.Name + "' error: " + err.Error())
|
||||
continue
|
||||
}
|
||||
auditRec := a.MakeAuditRecord("archiveChannel", audit.Success)
|
||||
auditRec.AddMeta("channel", channel)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteChannelsCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
confirmFlag, _ := command.Flags().GetBool("confirm")
|
||||
if !confirmFlag {
|
||||
var confirm string
|
||||
CommandPrettyPrintln("Are you sure you want to delete the channels specified? All data will be permanently deleted? (YES/NO): ")
|
||||
fmt.Scanln(&confirm)
|
||||
if confirm != "YES" {
|
||||
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
|
||||
}
|
||||
}
|
||||
|
||||
channels := getChannelsFromChannelArgs(a, args)
|
||||
for i, channel := range channels {
|
||||
if channel == nil {
|
||||
CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
|
||||
continue
|
||||
}
|
||||
if err := deleteChannel(a, channel); err != nil {
|
||||
CommandPrintErrorln("Unable to delete channel '" + channel.Name + "' error: " + err.Error())
|
||||
} else {
|
||||
CommandPrettyPrintln("Deleted channel '" + channel.Name + "'")
|
||||
|
||||
auditRec := a.MakeAuditRecord("deleteChannel", audit.Success)
|
||||
auditRec.AddMeta("channel", channel)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteChannel(a *app.App, channel *model.Channel) *model.AppError {
|
||||
return a.PermanentDeleteChannel(channel)
|
||||
}
|
||||
|
||||
func moveChannelsCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
team := getTeamFromTeamArg(a, args[0])
|
||||
if team == nil {
|
||||
return errors.New("Unable to find destination team '" + args[0] + "'")
|
||||
}
|
||||
|
||||
username, erru := command.Flags().GetString("username")
|
||||
if erru != nil || username == "" {
|
||||
return errors.New("Username is required.")
|
||||
}
|
||||
user := getUserFromUserArg(a, username)
|
||||
|
||||
channels := getChannelsFromChannelArgs(a, args[1:])
|
||||
for i, channel := range channels {
|
||||
if channel == nil {
|
||||
CommandPrintErrorln("Unable to find channel '" + args[i+1] + "'")
|
||||
continue
|
||||
}
|
||||
originTeamID := channel.TeamId
|
||||
if err := moveChannel(a, team, channel, user); err != nil {
|
||||
CommandPrintErrorln("Unable to move channel '" + channel.Name + "' error: " + err.Error())
|
||||
} else {
|
||||
CommandPrettyPrintln("Moved channel '" + channel.Name + "' to " + team.Name + "(" + team.Id + ") from " + originTeamID + ".")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveChannel(a *app.App, team *model.Team, channel *model.Channel, user *model.User) *model.AppError {
|
||||
oldTeamId := channel.TeamId
|
||||
|
||||
if err := a.RemoveAllDeactivatedMembersFromChannel(channel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := a.MoveChannel(&request.Context{}, team, channel, user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("moveChannel", audit.Success)
|
||||
auditRec.AddMeta("channel", channel)
|
||||
auditRec.AddMeta("team", team)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
incomingWebhooks, err := a.GetIncomingWebhooksForTeamPage(oldTeamId, 0, 10000000)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, webhook := range incomingWebhooks {
|
||||
if webhook.ChannelId == channel.Id {
|
||||
webhook.TeamId = team.Id
|
||||
if _, err := a.Srv().Store.Webhook().UpdateIncoming(webhook); err != nil {
|
||||
CommandPrintErrorln("Failed to move incoming webhook '" + webhook.Id + "' to new team.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outgoingWebhooks, err := a.GetOutgoingWebhooksForTeamPage(oldTeamId, 0, 10000000)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, webhook := range outgoingWebhooks {
|
||||
if webhook.ChannelId == channel.Id {
|
||||
webhook.TeamId = team.Id
|
||||
if _, err := a.Srv().Store.Webhook().UpdateOutgoing(webhook); err != nil {
|
||||
CommandPrintErrorln("Failed to move outgoing webhook '" + webhook.Id + "' to new team.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func listChannelsCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
teams := getTeamsFromTeamArgs(a, args)
|
||||
for i, team := range teams {
|
||||
if team == nil {
|
||||
CommandPrintErrorln("Unable to find team '" + args[i] + "'")
|
||||
continue
|
||||
}
|
||||
if channels, chanErr := a.Srv().Store.Channel().GetAll(team.Id); chanErr != nil {
|
||||
CommandPrintErrorln("Unable to list channels for '" + args[i] + "'")
|
||||
} else {
|
||||
for _, channel := range channels {
|
||||
output := channel.Name
|
||||
if channel.DeleteAt > 0 {
|
||||
output += " (archived)"
|
||||
}
|
||||
if channel.Type == model.ChannelTypePrivate {
|
||||
output += " (private)"
|
||||
}
|
||||
CommandPrettyPrintln(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func restoreChannelsCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
channels := getChannelsFromChannelArgs(a, args)
|
||||
for i, channel := range channels {
|
||||
if channel == nil {
|
||||
CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
|
||||
continue
|
||||
}
|
||||
if err := a.Srv().Store.Channel().SetDeleteAt(channel.Id, 0, model.GetMillis()); err != nil {
|
||||
CommandPrintErrorln("Unable to restore channel '" + args[i] + "'")
|
||||
continue
|
||||
}
|
||||
auditRec := a.MakeAuditRecord("restoreChannel", audit.Success)
|
||||
auditRec.AddMeta("channel", channel)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
|
||||
suffix := ""
|
||||
if len(channels) > 1 {
|
||||
suffix = "s"
|
||||
}
|
||||
CommandPrintln("Successfully restored channel" + suffix)
|
||||
return nil
|
||||
}
|
||||
|
||||
func modifyChannelCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
username, erru := command.Flags().GetString("username")
|
||||
if erru != nil || username == "" {
|
||||
return errors.New("Username is required.")
|
||||
}
|
||||
|
||||
public, _ := command.Flags().GetBool("public")
|
||||
private, _ := command.Flags().GetBool("private")
|
||||
|
||||
if public == private {
|
||||
return errors.New("You must specify only one of --public or --private")
|
||||
}
|
||||
|
||||
channel := getChannelFromChannelArg(a, args[0])
|
||||
if channel == nil {
|
||||
return errors.New("Unable to find channel '" + args[0] + "'")
|
||||
}
|
||||
|
||||
if !(channel.Type == model.ChannelTypeOpen || channel.Type == model.ChannelTypePrivate) {
|
||||
return errors.New("You can only change the type of public/private channels.")
|
||||
}
|
||||
|
||||
channel.Type = model.ChannelTypeOpen
|
||||
if private {
|
||||
channel.Type = model.ChannelTypePrivate
|
||||
}
|
||||
|
||||
user := getUserFromUserArg(a, username)
|
||||
if user == nil {
|
||||
return fmt.Errorf("Unable to find user: '%v'", username)
|
||||
}
|
||||
|
||||
updatedChannel, errUpdate := a.UpdateChannelPrivacy(&request.Context{}, channel, user)
|
||||
if errUpdate != nil {
|
||||
return errors.Wrapf(err, "Failed to update channel ('%s') privacy", args[0])
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("modifyChannel", audit.Success)
|
||||
auditRec.AddMeta("channel", channel)
|
||||
auditRec.AddMeta("user", user)
|
||||
auditRec.AddMeta("update", updatedChannel)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func renameChannelCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
var newDisplayName, newChannelName string
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
channel := getChannelFromChannelArg(a, args[0])
|
||||
if channel == nil {
|
||||
return errors.New("Unable to find channel '" + args[0] + "'")
|
||||
}
|
||||
|
||||
newChannelName = args[1]
|
||||
newDisplayName, errdn := command.Flags().GetString("display_name")
|
||||
if errdn != nil {
|
||||
return errdn
|
||||
}
|
||||
|
||||
updatedChannel, errch := a.RenameChannel(channel, newChannelName, newDisplayName)
|
||||
if errch != nil {
|
||||
return errors.Wrapf(errch, "Error in updating channel from %s to %s", channel.Name, newChannelName)
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("renameChannel", audit.Success)
|
||||
auditRec.AddMeta("channel", channel)
|
||||
auditRec.AddMeta("update", updatedChannel)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func searchChannelCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to InitDBCommandContextCobra")
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
var channel *model.Channel
|
||||
|
||||
if teamArg, _ := command.Flags().GetString("team"); teamArg != "" {
|
||||
team := getTeamFromTeamArg(a, teamArg)
|
||||
if team == nil {
|
||||
CommandPrettyPrintln(fmt.Sprintf("Team %s is not found", teamArg))
|
||||
return nil
|
||||
}
|
||||
|
||||
var aErr *model.AppError
|
||||
channel, aErr = a.GetChannelByName(args[0], team.Id, true)
|
||||
if aErr != nil || channel == nil {
|
||||
CommandPrettyPrintln(fmt.Sprintf("Channel %s is not found in team %s", args[0], teamArg))
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
teams, aErr := a.GetAllTeams()
|
||||
if aErr != nil {
|
||||
return errors.Wrap(err, "failed to GetAllTeams")
|
||||
}
|
||||
|
||||
for _, team := range teams {
|
||||
channel, _ = a.GetChannelByName(args[0], team.Id, true)
|
||||
if channel != nil && channel.Name == args[0] {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if channel == nil {
|
||||
CommandPrettyPrintln(fmt.Sprintf("Channel %s is not found in any team", args[0]))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
output := fmt.Sprintf(`Channel Name: %s, Display Name: %s, Channel ID: %s`, channel.Name, channel.DisplayName, channel.Id)
|
||||
if channel.DeleteAt > 0 {
|
||||
output += " (archived)"
|
||||
}
|
||||
if channel.Type == model.ChannelTypePrivate {
|
||||
output += " (private)"
|
||||
}
|
||||
CommandPrettyPrintln(output)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,342 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
func TestJoinChannel(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
channel := th.CreatePublicChannel()
|
||||
|
||||
th.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
|
||||
|
||||
// Joining twice should succeed
|
||||
th.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
|
||||
|
||||
// should fail because channel does not exist
|
||||
require.Error(t, th.RunCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name+"asdf", th.BasicUser2.Email))
|
||||
}
|
||||
|
||||
func TestRemoveChannel(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
channel := th.CreatePublicChannel()
|
||||
|
||||
t.Run("should fail because channel does not exist", func(t *testing.T) {
|
||||
require.Error(t, th.RunCommand(t, "channel", "remove", th.BasicTeam.Name+":doesnotexist", th.BasicUser2.Email))
|
||||
})
|
||||
|
||||
t.Run("should remove user from channel", func(t *testing.T) {
|
||||
th.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
|
||||
isMember, _ := th.App.Srv().Store.Channel().UserBelongsToChannels(th.BasicUser2.Id, []string{channel.Id})
|
||||
assert.True(t, isMember)
|
||||
|
||||
th.CheckCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
|
||||
isMember, _ = th.App.Srv().Store.Channel().UserBelongsToChannels(th.BasicUser2.Id, []string{channel.Id})
|
||||
assert.False(t, isMember)
|
||||
})
|
||||
|
||||
t.Run("should not fail removing non member user from channel", func(t *testing.T) {
|
||||
isMember, _ := th.App.Srv().Store.Channel().UserBelongsToChannels(th.BasicUser2.Id, []string{channel.Id})
|
||||
assert.False(t, isMember)
|
||||
th.CheckCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
|
||||
})
|
||||
|
||||
t.Run("should throw error if both --all-users flag and user email are passed", func(t *testing.T) {
|
||||
require.Error(t, th.RunCommand(t, "channel", "remove", "--all-users", th.BasicUser.Email))
|
||||
})
|
||||
|
||||
t.Run("should remove all users from channel", func(t *testing.T) {
|
||||
th.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser.Email)
|
||||
th.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
|
||||
count, _ := th.App.Srv().Store.Channel().GetMemberCount(channel.Id, false)
|
||||
assert.Equal(t, count, int64(2))
|
||||
|
||||
th.CheckCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, "--all-users")
|
||||
count, _ = th.App.Srv().Store.Channel().GetMemberCount(channel.Id, false)
|
||||
assert.Equal(t, count, int64(0))
|
||||
})
|
||||
|
||||
t.Run("should remove multiple users from channel", func(t *testing.T) {
|
||||
th.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser.Email)
|
||||
th.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
|
||||
count, _ := th.App.Srv().Store.Channel().GetMemberCount(channel.Id, false)
|
||||
assert.Equal(t, count, int64(2))
|
||||
|
||||
th.CheckCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser.Email, th.BasicUser2.Email)
|
||||
count, _ = th.App.Srv().Store.Channel().GetMemberCount(channel.Id, false)
|
||||
assert.Equal(t, count, int64(0))
|
||||
})
|
||||
}
|
||||
|
||||
func TestMoveChannel(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
team1 := th.BasicTeam
|
||||
team2 := th.CreateTeam()
|
||||
user1 := th.BasicUser
|
||||
th.LinkUserToTeam(user1, team2)
|
||||
channel := th.BasicChannel
|
||||
|
||||
th.LinkUserToTeam(user1, team1)
|
||||
th.LinkUserToTeam(user1, team2)
|
||||
|
||||
adminEmail := user1.Email
|
||||
adminUsername := user1.Username
|
||||
origin := team1.Name + ":" + channel.Name
|
||||
dest := team2.Name
|
||||
|
||||
th.CheckCommand(t, "channel", "add", origin, adminEmail)
|
||||
|
||||
// should fail with nil because errors are logged instead of returned when a channel does not exist
|
||||
th.CheckCommand(t, "channel", "move", dest, team1.Name+":doesnotexist", "--username", adminUsername)
|
||||
|
||||
th.CheckCommand(t, "channel", "move", dest, origin, "--username", adminUsername)
|
||||
}
|
||||
|
||||
func TestListChannels(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
channel := th.CreatePublicChannel()
|
||||
_, err := th.Client.DeleteChannel(channel.Id)
|
||||
require.NoError(t, err)
|
||||
privateChannel := th.CreatePrivateChannel()
|
||||
|
||||
output := th.CheckCommand(t, "channel", "list", th.BasicTeam.Name)
|
||||
|
||||
require.True(t, strings.Contains(output, "town-square"), "should have channels")
|
||||
|
||||
require.True(t, strings.Contains(output, channel.Name+" (archived)"), "should have archived channel")
|
||||
|
||||
require.True(t, strings.Contains(output, privateChannel.Name+" (private)"), "should have private channel")
|
||||
|
||||
_, err = th.Client.DeleteChannel(privateChannel.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
output = th.CheckCommand(t, "channel", "list", th.BasicTeam.Name)
|
||||
|
||||
require.True(t, strings.Contains(output, privateChannel.Name+" (archived) (private)"), "should have a channel both archived and private")
|
||||
}
|
||||
|
||||
func TestRestoreChannel(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
channel := th.CreatePublicChannel()
|
||||
_, err := th.Client.DeleteChannel(channel.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
th.CheckCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name)
|
||||
|
||||
// restoring twice should succeed
|
||||
th.CheckCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name)
|
||||
}
|
||||
|
||||
func TestCreateChannel(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
commonName := "name" + id
|
||||
team, _ := th.App.Srv().Store.Team().GetByName(th.BasicTeam.Name)
|
||||
|
||||
t.Run("should create public channel", func(t *testing.T) {
|
||||
th.CheckCommand(t, "channel", "create", "--display_name", commonName, "--team", th.BasicTeam.Name, "--name", commonName)
|
||||
channel, _ := th.App.Srv().Store.Channel().GetByName(team.Id, commonName, false)
|
||||
assert.Equal(t, commonName, channel.Name)
|
||||
assert.Equal(t, model.ChannelTypeOpen, channel.Type)
|
||||
})
|
||||
|
||||
t.Run("should create private channel", func(t *testing.T) {
|
||||
name := commonName + "-private"
|
||||
th.CheckCommand(t, "channel", "create", "--display_name", name, "--team", th.BasicTeam.Name, "--name", name, "--private")
|
||||
channel, _ := th.App.Srv().Store.Channel().GetByName(team.Id, name, false)
|
||||
assert.Equal(t, name, channel.Name)
|
||||
assert.Equal(t, model.ChannelTypePrivate, channel.Type)
|
||||
})
|
||||
|
||||
t.Run("should create channel with header and purpose", func(t *testing.T) {
|
||||
name := commonName + "-withhp"
|
||||
th.CheckCommand(t, "channel", "create", "--display_name", name, "--team", th.BasicTeam.Name, "--name", name, "--header", "this is a header", "--purpose", "this is the purpose")
|
||||
channel, _ := th.App.Srv().Store.Channel().GetByName(team.Id, name, false)
|
||||
assert.Equal(t, name, channel.Name)
|
||||
assert.Equal(t, model.ChannelTypeOpen, channel.Type)
|
||||
assert.Equal(t, "this is a header", channel.Header)
|
||||
assert.Equal(t, "this is the purpose", channel.Purpose)
|
||||
})
|
||||
|
||||
t.Run("should not create channel if name already exists on the same team", func(t *testing.T) {
|
||||
output, err := th.RunCommandWithOutput(t, "channel", "create", "--display_name", commonName, "--team", th.BasicTeam.Name, "--name", commonName)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, output, "A channel with that name already exists on the same team.")
|
||||
})
|
||||
|
||||
t.Run("should not create channel without display name", func(t *testing.T) {
|
||||
output, err := th.RunCommandWithOutput(t, "channel", "create", "--display_name", "", "--team", th.BasicTeam.Name, "--name", commonName)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, output, "Display Name is required")
|
||||
})
|
||||
|
||||
t.Run("should not create channel without name", func(t *testing.T) {
|
||||
output, err := th.RunCommandWithOutput(t, "channel", "create", "--display_name", commonName, "--team", th.BasicTeam.Name, "--name", "")
|
||||
require.Error(t, err)
|
||||
require.Contains(t, output, "Name is required")
|
||||
})
|
||||
|
||||
t.Run("should not create channel without team", func(t *testing.T) {
|
||||
output, err := th.RunCommandWithOutput(t, "channel", "create", "--display_name", commonName, "--team", "", "--name", commonName)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, output, "Team is required")
|
||||
})
|
||||
|
||||
t.Run("should not create channel with unexisting team", func(t *testing.T) {
|
||||
output, err := th.RunCommandWithOutput(t, "channel", "create", "--display_name", commonName, "--team", th.BasicTeam.Name+"-unexisting", "--name", commonName)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, output, "Unable to find team:")
|
||||
})
|
||||
}
|
||||
|
||||
func TestRenameChannel(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
channel := th.CreatePublicChannel()
|
||||
th.CheckCommand(t, "channel", "rename", th.BasicTeam.Name+":"+channel.Name, "newchannelname10", "--display_name", "New Display Name")
|
||||
|
||||
// Get the channel from the DB
|
||||
updatedChannel, _ := th.App.GetChannel(channel.Id)
|
||||
assert.Equal(t, "newchannelname10", updatedChannel.Name)
|
||||
assert.Equal(t, "New Display Name", updatedChannel.DisplayName)
|
||||
}
|
||||
|
||||
func Test_searchChannelCmdF(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
channel := th.CreatePublicChannel()
|
||||
channel2 := th.CreatePublicChannel()
|
||||
channel3 := th.CreatePrivateChannel()
|
||||
channel4 := th.CreatePrivateChannel()
|
||||
th.Client.DeleteChannel(channel2.Id)
|
||||
th.Client.DeleteChannel(channel4.Id)
|
||||
|
||||
tests := []struct {
|
||||
Name string
|
||||
Args []string
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
"Success find Channel in any team",
|
||||
[]string{"channel", "search", channel.Name},
|
||||
fmt.Sprintf("Channel Name: %s, Display Name: %s, Channel ID: %s", channel.Name, channel.DisplayName, channel.Id),
|
||||
},
|
||||
{
|
||||
"Failed find Channel in any team",
|
||||
[]string{"channel", "search", channel.Name + "404"},
|
||||
fmt.Sprintf("Channel %s is not found in any team", channel.Name+"404"),
|
||||
},
|
||||
{
|
||||
"Success find Channel with param team ID",
|
||||
[]string{"channel", "search", "--team", channel.TeamId, channel.Name},
|
||||
fmt.Sprintf("Channel Name: %s, Display Name: %s, Channel ID: %s", channel.Name, channel.DisplayName, channel.Id),
|
||||
},
|
||||
{
|
||||
"Failed find Channel with param team ID",
|
||||
[]string{"channel", "search", "--team", channel.TeamId, channel.Name + "404"},
|
||||
fmt.Sprintf("Channel %s is not found in team %s", channel.Name+"404", channel.TeamId),
|
||||
},
|
||||
{
|
||||
"Success find archived Channel in any team",
|
||||
[]string{"channel", "search", channel2.Name},
|
||||
fmt.Sprintf("Channel Name: %s, Display Name: %s, Channel ID: %s (archived)", channel2.Name, channel2.DisplayName, channel2.Id),
|
||||
},
|
||||
{
|
||||
"Success find archived Channel with param team ID",
|
||||
[]string{"channel", "search", "--team", channel2.TeamId, channel2.Name},
|
||||
fmt.Sprintf("Channel Name: %s, Display Name: %s, Channel ID: %s (archived)", channel2.Name, channel2.DisplayName, channel2.Id),
|
||||
},
|
||||
{
|
||||
"Success find private Channel in any team",
|
||||
[]string{"channel", "search", channel3.Name},
|
||||
fmt.Sprintf("Channel Name: %s, Display Name: %s, Channel ID: %s (private)", channel3.Name, channel3.DisplayName, channel3.Id),
|
||||
},
|
||||
{
|
||||
"Success find private Channel with param team ID",
|
||||
[]string{"channel", "search", "--team", channel3.TeamId, channel3.Name},
|
||||
fmt.Sprintf("Channel Name: %s, Display Name: %s, Channel ID: %s (private)", channel3.Name, channel3.DisplayName, channel3.Id),
|
||||
},
|
||||
{
|
||||
"Success find both archived and private Channel in any team",
|
||||
[]string{"channel", "search", channel4.Name},
|
||||
fmt.Sprintf("Channel Name: %s, Display Name: %s, Channel ID: %s (archived) (private)", channel4.Name, channel4.DisplayName, channel4.Id),
|
||||
},
|
||||
{
|
||||
"Success find both archived and private Channel with param team ID",
|
||||
[]string{"channel", "search", "--team", channel4.TeamId, channel4.Name},
|
||||
fmt.Sprintf("Channel Name: %s, Display Name: %s, Channel ID: %s (archived) (private)", channel4.Name, channel4.DisplayName, channel4.Id),
|
||||
},
|
||||
{
|
||||
"Failed find team",
|
||||
[]string{"channel", "search", "--team", channel.TeamId + "404", channel.Name},
|
||||
fmt.Sprintf("Team %s is not found", channel.TeamId+"404"),
|
||||
},
|
||||
{
|
||||
"Success find Channel with param team ID",
|
||||
[]string{"channel", "search", channel.Name, "--team", channel.TeamId},
|
||||
fmt.Sprintf("Channel Name: %s, Display Name: %s, Channel ID: %s", channel.Name, channel.DisplayName, channel.Id),
|
||||
},
|
||||
{
|
||||
"Success find Channel with param team ID",
|
||||
[]string{"channel", "search", channel.Name, "--team=" + channel.TeamId},
|
||||
fmt.Sprintf("Channel Name: %s, Display Name: %s, Channel ID: %s", channel.Name, channel.DisplayName, channel.Id),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
assert.Contains(t, th.CheckCommand(t, test.Args...), test.Expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestModifyChannel(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
channel1 := th.CreatePrivateChannel()
|
||||
channel2 := th.CreatePrivateChannel()
|
||||
|
||||
th.CheckCommand(t, "channel", "modify", "--public", th.BasicTeam.Name+":"+channel1.Name, "--username", th.BasicUser2.Email)
|
||||
res, err := th.App.Srv().Store.Channel().Get(channel1.Id, false)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, model.ChannelTypeOpen, res.Type)
|
||||
|
||||
// should fail because user doesn't exist
|
||||
require.Error(t, th.RunCommand(t, "channel", "modify", "--public", th.BasicTeam.Name+":"+channel2.Name, "--username", "idonotexist"))
|
||||
|
||||
pchannel1 := th.CreatePublicChannel()
|
||||
pchannel2 := th.CreatePublicChannel()
|
||||
|
||||
th.CheckCommand(t, "channel", "modify", "--private", th.BasicTeam.Name+":"+pchannel1.Name, "--username", th.BasicUser2.Email)
|
||||
res, err = th.App.Srv().Store.Channel().Get(pchannel1.Id, false)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, model.ChannelTypePrivate, res.Type)
|
||||
|
||||
// should fail because user doesn't exist
|
||||
require.Error(t, th.RunCommand(t, "channel", "modify", "--private", th.BasicTeam.Name+":"+pchannel2.Name, "--username", "idonotexist"))
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
const ChannelArgSeparator = ":"
|
||||
|
||||
func getChannelsFromChannelArgs(a *app.App, channelArgs []string) []*model.Channel {
|
||||
channels := make([]*model.Channel, 0, len(channelArgs))
|
||||
for _, channelArg := range channelArgs {
|
||||
channel := getChannelFromChannelArg(a, channelArg)
|
||||
channels = append(channels, channel)
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
||||
func parseChannelArg(channelArg string) (string, string) {
|
||||
result := strings.SplitN(channelArg, ChannelArgSeparator, 2)
|
||||
if len(result) == 1 {
|
||||
return "", channelArg
|
||||
}
|
||||
return result[0], result[1]
|
||||
}
|
||||
|
||||
func getChannelFromChannelArg(a *app.App, channelArg string) *model.Channel {
|
||||
teamArg, channelPart := parseChannelArg(channelArg)
|
||||
if teamArg == "" && channelPart == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var channel *model.Channel
|
||||
if teamArg != "" {
|
||||
team := getTeamFromTeamArg(a, teamArg)
|
||||
if team == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if result, err := a.Srv().Store.Channel().GetByNameIncludeDeleted(team.Id, channelPart, true); err == nil {
|
||||
channel = result
|
||||
} else {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if channel == nil {
|
||||
if ch, errCh := a.Srv().Store.Channel().Get(channelPart, true); errCh == nil {
|
||||
channel = ch
|
||||
}
|
||||
}
|
||||
|
||||
return channel
|
||||
}
|
||||
@@ -1,408 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app"
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
var CommandCmd = &cobra.Command{
|
||||
Use: "command",
|
||||
Short: "Management of slash commands",
|
||||
}
|
||||
|
||||
var CommandCreateCmd = &cobra.Command{
|
||||
Use: "create [team]",
|
||||
Short: "Create a custom slash command",
|
||||
Long: `Create a custom slash command for the specified team.`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Example: ` command create myteam --title MyCommand --description "My Command Description" --trigger-word mycommand --url http://localhost:8000/my-slash-handler --creator myusername --response-username my-bot-username --icon http://localhost:8000/my-slash-handler-bot-icon.png --autocomplete --post`,
|
||||
RunE: createCommandCmdF,
|
||||
}
|
||||
|
||||
var CommandShowCmd = &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Show a custom slash command",
|
||||
Long: `Show a custom slash command. Commands can be specified by command ID.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Example: ` command show commandID`,
|
||||
RunE: showCommandCmdF,
|
||||
}
|
||||
|
||||
var CommandMoveCmd = &cobra.Command{
|
||||
Use: "move",
|
||||
Short: "Move a slash command to a different team",
|
||||
Long: `Move a slash command to a different team. Commands can be specified by [team]:[command-trigger-word]. ie. myteam:trigger or by command ID.`,
|
||||
Example: ` command move newteam oldteam:command`,
|
||||
RunE: moveCommandCmdF,
|
||||
}
|
||||
|
||||
var CommandListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all commands on specified teams.",
|
||||
Long: `List all commands on specified teams.`,
|
||||
Example: ` command list myteam`,
|
||||
RunE: listCommandCmdF,
|
||||
}
|
||||
|
||||
var CommandDeleteCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete a slash command",
|
||||
Long: `Delete a slash command. Commands can be specified by command ID.`,
|
||||
Example: ` command delete commandID`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: deleteCommandCmdF,
|
||||
}
|
||||
|
||||
var CommandModifyCmd = &cobra.Command{
|
||||
Use: "modify",
|
||||
Short: "Modify a slash command",
|
||||
Long: `Modify a slash command. Commands can be specified by command ID.`,
|
||||
Example: ` command modify commandID`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: modifyCommandCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
CommandCreateCmd.Flags().String("title", "", "Command Title")
|
||||
CommandCreateCmd.Flags().String("description", "", "Command Description")
|
||||
CommandCreateCmd.Flags().String("trigger-word", "", "Command Trigger Word (required)")
|
||||
CommandCreateCmd.MarkFlagRequired("trigger-word")
|
||||
CommandCreateCmd.Flags().String("url", "", "Command Callback URL (required)")
|
||||
CommandCreateCmd.MarkFlagRequired("url")
|
||||
CommandCreateCmd.Flags().String("creator", "", "Command Creator's Username (required)")
|
||||
CommandCreateCmd.MarkFlagRequired("creator")
|
||||
CommandCreateCmd.Flags().String("response-username", "", "Command Response Username")
|
||||
CommandCreateCmd.Flags().String("icon", "", "Command Icon URL")
|
||||
CommandCreateCmd.Flags().Bool("autocomplete", false, "Show Command in autocomplete list")
|
||||
CommandCreateCmd.Flags().String("autocompleteDesc", "", "Short Command Description for autocomplete list")
|
||||
CommandCreateCmd.Flags().String("autocompleteHint", "", "Command Arguments displayed as help in autocomplete list")
|
||||
CommandCreateCmd.Flags().Bool("post", false, "Use POST method for Callback URL")
|
||||
|
||||
CommandModifyCmd.Flags().String("title", "", "Command Title")
|
||||
CommandModifyCmd.Flags().String("description", "", "Command Description")
|
||||
CommandModifyCmd.Flags().String("trigger-word", "", "Command Trigger Word")
|
||||
CommandModifyCmd.Flags().String("url", "", "Command Callback URL")
|
||||
CommandModifyCmd.Flags().String("creator", "", "Command Creator's Username")
|
||||
CommandModifyCmd.Flags().String("response-username", "", "Command Response Username")
|
||||
CommandModifyCmd.Flags().String("icon", "", "Command Icon URL")
|
||||
CommandModifyCmd.Flags().Bool("autocomplete", false, "Show Command in autocomplete list")
|
||||
CommandModifyCmd.Flags().String("autocompleteDesc", "", "Short Command Description for autocomplete list")
|
||||
CommandModifyCmd.Flags().String("autocompleteHint", "", "Command Arguments displayed as help in autocomplete list")
|
||||
CommandModifyCmd.Flags().Bool("post", false, "Use POST method for Callback URL")
|
||||
|
||||
CommandCmd.AddCommand(
|
||||
CommandCreateCmd,
|
||||
CommandShowCmd,
|
||||
CommandMoveCmd,
|
||||
CommandListCmd,
|
||||
CommandDeleteCmd,
|
||||
CommandModifyCmd,
|
||||
)
|
||||
RootCmd.AddCommand(CommandCmd)
|
||||
}
|
||||
|
||||
func createCommandCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
team := getTeamFromTeamArg(a, args[0])
|
||||
if team == nil {
|
||||
return errors.New("unable to find team '" + args[0] + "'")
|
||||
}
|
||||
|
||||
// get the creator
|
||||
creator, _ := command.Flags().GetString("creator")
|
||||
user := getUserFromUserArg(a, creator)
|
||||
if user == nil {
|
||||
return errors.New("unable to find user '" + creator + "'")
|
||||
}
|
||||
|
||||
// check if creator has permission to create slash commands
|
||||
if !a.HasPermissionToTeam(user.Id, team.Id, model.PermissionManageSlashCommands) {
|
||||
return errors.New("the creator must be a user who has permissions to manage slash commands")
|
||||
}
|
||||
|
||||
title, _ := command.Flags().GetString("title")
|
||||
description, _ := command.Flags().GetString("description")
|
||||
trigger, _ := command.Flags().GetString("trigger-word")
|
||||
|
||||
if strings.HasPrefix(trigger, "/") {
|
||||
return errors.New("a trigger word cannot begin with a /")
|
||||
}
|
||||
if strings.Contains(trigger, " ") {
|
||||
return errors.New("a trigger word must not contain spaces")
|
||||
}
|
||||
|
||||
url, _ := command.Flags().GetString("url")
|
||||
responseUsername, _ := command.Flags().GetString("response-username")
|
||||
icon, _ := command.Flags().GetString("icon")
|
||||
autocomplete, _ := command.Flags().GetBool("autocomplete")
|
||||
autocompleteDesc, _ := command.Flags().GetString("autocompleteDesc")
|
||||
autocompleteHint, _ := command.Flags().GetString("autocompleteHint")
|
||||
post, errp := command.Flags().GetBool("post")
|
||||
method := "P"
|
||||
if errp != nil || !post {
|
||||
method = "G"
|
||||
}
|
||||
|
||||
newCommand := &model.Command{
|
||||
CreatorId: user.Id,
|
||||
TeamId: team.Id,
|
||||
Trigger: trigger,
|
||||
Method: method,
|
||||
Username: responseUsername,
|
||||
IconURL: icon,
|
||||
AutoComplete: autocomplete,
|
||||
AutoCompleteDesc: autocompleteDesc,
|
||||
AutoCompleteHint: autocompleteHint,
|
||||
DisplayName: title,
|
||||
Description: description,
|
||||
URL: url,
|
||||
}
|
||||
|
||||
createdCommand, errCreate := a.CreateCommand(newCommand)
|
||||
if errCreate != nil {
|
||||
return errors.New("unable to create command '" + newCommand.DisplayName + "'. " + errCreate.Error())
|
||||
}
|
||||
CommandPrettyPrintln("created command '" + newCommand.DisplayName + "'")
|
||||
|
||||
auditRec := a.MakeAuditRecord("createCommand", audit.Success)
|
||||
auditRec.AddMeta("user", user)
|
||||
auditRec.AddMeta("command", createdCommand)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func showCommandCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
slashCommand := getCommandFromCommandArg(a, args[0])
|
||||
if slashCommand == nil {
|
||||
command.SilenceUsage = true
|
||||
return errors.New("Unable to find command '" + args[0] + "'")
|
||||
}
|
||||
// pretty print
|
||||
fmt.Printf("%s", prettyPrintStruct(*slashCommand))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveCommandCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
if len(args) < 2 {
|
||||
return errors.New("Enter the destination team and at least one command to move.")
|
||||
}
|
||||
|
||||
team := getTeamFromTeamArg(a, args[0])
|
||||
if team == nil {
|
||||
return errors.New("Unable to find destination team '" + args[0] + "'")
|
||||
}
|
||||
|
||||
commands := getCommandsFromCommandArgs(a, args[1:])
|
||||
for i, command := range commands {
|
||||
if command == nil {
|
||||
CommandPrintErrorln("Unable to find command '" + args[i+1] + "'")
|
||||
continue
|
||||
}
|
||||
if err := moveCommand(a, team, command); err != nil {
|
||||
CommandPrintErrorln("Unable to move command '" + command.DisplayName + "' error: " + err.Error())
|
||||
} else {
|
||||
CommandPrettyPrintln("Moved command '" + command.DisplayName + "'")
|
||||
|
||||
auditRec := a.MakeAuditRecord("moveCommand", audit.Success)
|
||||
auditRec.AddMeta("team", team)
|
||||
auditRec.AddMeta("command", command)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveCommand(a *app.App, team *model.Team, command *model.Command) *model.AppError {
|
||||
return a.MoveCommand(team, command)
|
||||
}
|
||||
|
||||
func listCommandCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
var teams []*model.Team
|
||||
if len(args) < 1 {
|
||||
teamList, err := a.GetAllTeams()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
teams = teamList
|
||||
} else {
|
||||
teams = getTeamsFromTeamArgs(a, args)
|
||||
}
|
||||
|
||||
for i, team := range teams {
|
||||
if team == nil {
|
||||
CommandPrintErrorln("Unable to find team '" + args[i] + "'")
|
||||
continue
|
||||
}
|
||||
commands, err := a.Srv().Store.Command().GetByTeam(team.Id)
|
||||
if err != nil {
|
||||
CommandPrintErrorln("Unable to list commands for '" + args[i] + "'")
|
||||
continue
|
||||
}
|
||||
for _, command := range commands {
|
||||
commandListItem := fmt.Sprintf("%s: %s (team: %s)", command.Id, command.DisplayName, team.Name)
|
||||
CommandPrettyPrintln(commandListItem)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteCommandCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
slashCommand := getCommandFromCommandArg(a, args[0])
|
||||
if slashCommand == nil {
|
||||
command.SilenceUsage = true
|
||||
return errors.New("Unable to find command '" + args[0] + "'")
|
||||
}
|
||||
|
||||
if err := a.DeleteCommand(slashCommand.Id); err != nil {
|
||||
command.SilenceUsage = true
|
||||
return errors.New("Unable to delete command '" + slashCommand.Id + "' error: " + err.Error())
|
||||
}
|
||||
CommandPrettyPrintln("Deleted command '" + slashCommand.Id + "' (" + slashCommand.DisplayName + ")")
|
||||
|
||||
auditRec := a.MakeAuditRecord("deleteCommand", audit.Success)
|
||||
auditRec.AddMeta("command", slashCommand)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func modifyCommandCmdF(command *cobra.Command, args []string) (cmdError error) {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
oldCommand := getCommandFromCommandArg(a, args[0])
|
||||
if oldCommand == nil {
|
||||
command.SilenceUsage = true
|
||||
return errors.New("Unable to find command '" + args[0] + "'")
|
||||
}
|
||||
modifiedCommand := oldCommand
|
||||
|
||||
auditRec := a.MakeAuditRecord("modifyCommand", audit.Fail)
|
||||
defer func() { a.LogAuditRec(auditRec, cmdError) }()
|
||||
auditRec.AddMeta("command", oldCommand)
|
||||
|
||||
// get creator user
|
||||
creator, _ := command.Flags().GetString("creator")
|
||||
if creator != "" {
|
||||
user := getUserFromUserArg(a, creator)
|
||||
if user == nil {
|
||||
return errors.New("unable to find user '" + creator + "'")
|
||||
}
|
||||
|
||||
// check if creator has permission to create slash commands
|
||||
if !a.HasPermissionToTeam(user.Id, modifiedCommand.TeamId, model.PermissionManageSlashCommands) {
|
||||
return errors.New("the creator must be a user who has permissions to manage slash commands")
|
||||
}
|
||||
|
||||
modifiedCommand.CreatorId = user.Id
|
||||
}
|
||||
|
||||
title, _ := command.Flags().GetString("title")
|
||||
if title != "" {
|
||||
modifiedCommand.DisplayName = title
|
||||
}
|
||||
|
||||
description, _ := command.Flags().GetString("description")
|
||||
if description != "" {
|
||||
modifiedCommand.Description = description
|
||||
}
|
||||
|
||||
trigger, _ := command.Flags().GetString("trigger-word")
|
||||
if trigger != "" {
|
||||
if strings.HasPrefix(trigger, "/") {
|
||||
return errors.New("a trigger word cannot begin with a /")
|
||||
}
|
||||
if strings.Contains(trigger, " ") {
|
||||
return errors.New("a trigger word must not contain spaces")
|
||||
}
|
||||
modifiedCommand.Trigger = trigger
|
||||
}
|
||||
|
||||
url, _ := command.Flags().GetString("url")
|
||||
if url != "" {
|
||||
modifiedCommand.URL = url
|
||||
}
|
||||
|
||||
responseUsername, _ := command.Flags().GetString("response-username")
|
||||
if responseUsername != "" {
|
||||
modifiedCommand.Username = responseUsername
|
||||
}
|
||||
|
||||
icon, _ := command.Flags().GetString("icon")
|
||||
if icon != "" {
|
||||
modifiedCommand.IconURL = icon
|
||||
}
|
||||
|
||||
autocomplete, _ := command.Flags().GetBool("autocomplete")
|
||||
modifiedCommand.AutoComplete = autocomplete
|
||||
|
||||
autocompleteDesc, _ := command.Flags().GetString("autocompleteDesc")
|
||||
if autocompleteDesc != "" {
|
||||
modifiedCommand.AutoCompleteDesc = autocompleteDesc
|
||||
}
|
||||
|
||||
autocompleteHint, _ := command.Flags().GetString("autocompleteHint")
|
||||
if autocompleteHint != "" {
|
||||
modifiedCommand.AutoCompleteHint = autocompleteHint
|
||||
}
|
||||
|
||||
post, err := command.Flags().GetBool("post")
|
||||
method := "P"
|
||||
if err != nil || !post {
|
||||
method = "G"
|
||||
}
|
||||
modifiedCommand.Method = method
|
||||
|
||||
updatedCommand, errUpdated := a.UpdateCommand(oldCommand, modifiedCommand)
|
||||
if errUpdated != nil {
|
||||
return errors.New("unable to modify command '" + modifiedCommand.DisplayName + "'. " + errUpdated.Error())
|
||||
}
|
||||
CommandPrettyPrintln("modified command '" + modifiedCommand.DisplayName + "'")
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddMeta("update", updatedCommand)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,443 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
func TestCreateCommand(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
config := th.Config()
|
||||
*config.ServiceSettings.EnableCommands = true
|
||||
th.SetConfig(config)
|
||||
|
||||
team := th.BasicTeam
|
||||
adminUser := th.TeamAdminUser
|
||||
user := th.BasicUser
|
||||
|
||||
testCases := []struct {
|
||||
Description string
|
||||
Args []string
|
||||
ExpectedErr string
|
||||
}{
|
||||
{
|
||||
"nil error",
|
||||
[]string{"command", "create", team.Name, "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
|
||||
"",
|
||||
},
|
||||
{
|
||||
"Team not specified",
|
||||
[]string{"command", "create", "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
|
||||
"Error: requires at least 1 arg(s), only received 0",
|
||||
},
|
||||
{
|
||||
"Team not found",
|
||||
[]string{"command", "create", "fakeTeam", "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
|
||||
"Error: unable to find team",
|
||||
},
|
||||
{
|
||||
"Creator not specified",
|
||||
[]string{"command", "create", team.Name, "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler"},
|
||||
`Error: required flag(s) "creator" not set`,
|
||||
},
|
||||
{
|
||||
"Creator not found",
|
||||
[]string{"command", "create", team.Name, "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", "fakeuser"},
|
||||
"unable to find user",
|
||||
},
|
||||
{
|
||||
"Creator not team admin",
|
||||
[]string{"command", "create", team.Name, "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", user.Username},
|
||||
"the creator must be a user who has permissions to manage slash commands",
|
||||
},
|
||||
{
|
||||
"Command not specified",
|
||||
[]string{"command", "", team.Name, "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
|
||||
"Error: unknown flag: --trigger-word",
|
||||
},
|
||||
{
|
||||
"Trigger not specified",
|
||||
[]string{"command", "create", team.Name, "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
|
||||
`Error: required flag(s) "trigger-word" not set`,
|
||||
},
|
||||
{
|
||||
"Blank trigger",
|
||||
[]string{"command", "create", team.Name, "--trigger-word", "", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
|
||||
"Invalid trigger",
|
||||
},
|
||||
{
|
||||
"Trigger with space",
|
||||
[]string{"command", "create", team.Name, "--trigger-word", "test cmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
|
||||
"Error: a trigger word must not contain spaces",
|
||||
},
|
||||
{
|
||||
"Trigger starting with /",
|
||||
[]string{"command", "create", team.Name, "--trigger-word", "/testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
|
||||
"Error: a trigger word cannot begin with a /",
|
||||
},
|
||||
{
|
||||
"URL not specified",
|
||||
[]string{"command", "create", team.Name, "--trigger-word", "testcmd", "--creator", adminUser.Username},
|
||||
`Error: required flag(s) "url" not set`,
|
||||
},
|
||||
{
|
||||
"Blank URL",
|
||||
[]string{"command", "create", team.Name, "--trigger-word", "testcmd2", "--url", "", "--creator", adminUser.Username},
|
||||
"Invalid URL",
|
||||
},
|
||||
{
|
||||
"Invalid URL",
|
||||
[]string{"command", "create", team.Name, "--trigger-word", "testcmd2", "--url", "localhost:8000/my-slash-handler", "--creator", adminUser.Username},
|
||||
"Invalid URL",
|
||||
},
|
||||
{
|
||||
"Duplicate Command",
|
||||
[]string{"command", "create", team.Name, "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
|
||||
"This trigger word is already in use",
|
||||
},
|
||||
{
|
||||
"Misspelled flag",
|
||||
[]string{"command", "create", team.Name, "--trigger-wor", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
|
||||
"Error: unknown flag:",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.Description, func(t *testing.T) {
|
||||
actual, _ := th.RunCommandWithOutput(t, testCase.Args...)
|
||||
|
||||
cmds, _, err := th.SystemAdminClient.ListCommands(team.Id, true)
|
||||
require.NoError(t, err, "Failed to list commands")
|
||||
|
||||
if testCase.ExpectedErr == "" {
|
||||
assert.NotZero(t, len(cmds), "Failed to create command")
|
||||
assert.Equal(t, cmds[0].Trigger, "testcmd", "Failed to create command")
|
||||
assert.Contains(t, actual, "PASS")
|
||||
} else {
|
||||
assert.LessOrEqual(t, len(cmds), 1, "Created command that shouldn't have been created")
|
||||
assert.Contains(t, actual, testCase.ExpectedErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShowCommand(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
url := "http://localhost:8000/test-command"
|
||||
team := th.BasicTeam
|
||||
user := th.BasicUser
|
||||
th.LinkUserToTeam(user, team)
|
||||
trigger := "trigger_" + model.NewId()
|
||||
displayName := "dn_" + model.NewId()
|
||||
|
||||
c := &model.Command{
|
||||
DisplayName: displayName,
|
||||
Method: "G",
|
||||
TeamId: team.Id,
|
||||
Username: user.Username,
|
||||
CreatorId: user.Id,
|
||||
URL: url,
|
||||
Trigger: trigger,
|
||||
}
|
||||
|
||||
t.Run("existing command", func(t *testing.T) {
|
||||
command, err := th.App.CreateCommand(c)
|
||||
require.Nil(t, err)
|
||||
commands, err := th.App.ListTeamCommands(team.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, len(commands), 1)
|
||||
|
||||
output := th.CheckCommand(t, "command", "show", command.Id)
|
||||
assert.Contains(t, output, command.Id)
|
||||
assert.Contains(t, output, command.TeamId)
|
||||
assert.Contains(t, output, trigger)
|
||||
assert.Contains(t, output, displayName)
|
||||
assert.Contains(t, output, user.Username)
|
||||
})
|
||||
|
||||
t.Run("not existing command", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "command", "show", "invalid"))
|
||||
})
|
||||
|
||||
t.Run("no commandID", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "command", "show"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteCommand(t *testing.T) {
|
||||
// Skipped due to v5.6 RC build issues.
|
||||
t.Skip()
|
||||
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
url := "http://localhost:8000/test-command"
|
||||
team := th.BasicTeam
|
||||
user := th.BasicUser
|
||||
th.LinkUserToTeam(user, team)
|
||||
|
||||
c := &model.Command{
|
||||
DisplayName: "dn_" + model.NewId(),
|
||||
Method: "G",
|
||||
TeamId: team.Id,
|
||||
Username: user.Username,
|
||||
CreatorId: user.Id,
|
||||
URL: url,
|
||||
Trigger: "trigger_" + model.NewId(),
|
||||
}
|
||||
|
||||
t.Run("existing command", func(t *testing.T) {
|
||||
command, err := th.App.CreateCommand(c)
|
||||
require.Nil(t, err)
|
||||
commands, err := th.App.ListTeamCommands(team.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, len(commands), 1)
|
||||
|
||||
th.CheckCommand(t, "command", "delete", command.Id)
|
||||
commands, err = th.App.ListTeamCommands(team.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, len(commands), 0)
|
||||
})
|
||||
|
||||
t.Run("not existing command", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "command", "delete", "invalid"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestModifyCommand(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// set config
|
||||
config := th.Config()
|
||||
*config.ServiceSettings.EnableCommands = true
|
||||
th.SetConfig(config)
|
||||
|
||||
// set team and users
|
||||
team := th.BasicTeam
|
||||
adminUser := th.TeamAdminUser
|
||||
user := th.BasicUser
|
||||
|
||||
// create test command to modify
|
||||
url := "http://localhost:8000/test-command"
|
||||
th.LinkUserToTeam(user, team)
|
||||
trigger := "trigger_" + model.NewId()
|
||||
displayName := "dn_" + model.NewId()
|
||||
|
||||
c := &model.Command{
|
||||
DisplayName: displayName,
|
||||
Method: "G",
|
||||
TeamId: team.Id,
|
||||
Username: user.Username,
|
||||
CreatorId: user.Id,
|
||||
URL: url,
|
||||
Trigger: trigger,
|
||||
}
|
||||
|
||||
command, err := th.App.CreateCommand(c)
|
||||
require.Nil(t, err)
|
||||
commands, err := th.App.ListTeamCommands(team.Id)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, len(commands), 1)
|
||||
|
||||
t.Run("command not specified", func(t *testing.T) {
|
||||
args := []string{"command", "", command.Id, "--trigger-word", "sometrigger"}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
assert.Contains(t, output, "Error: unknown flag: --trigger-word")
|
||||
})
|
||||
|
||||
t.Run("modify command unchanged", func(t *testing.T) {
|
||||
args := []string{"command", "modify", command.Id}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
cmd, _ := th.App.GetCommand(command.Id)
|
||||
assert.Contains(t, output, "PASS")
|
||||
assert.Equal(t, cmd.DisplayName, command.DisplayName)
|
||||
assert.Equal(t, cmd.Method, command.Method)
|
||||
assert.Equal(t, cmd.TeamId, command.TeamId)
|
||||
assert.Equal(t, cmd.Username, command.Username)
|
||||
assert.Equal(t, cmd.CreatorId, command.CreatorId)
|
||||
assert.Equal(t, cmd.URL, command.URL)
|
||||
assert.Equal(t, cmd.Trigger, command.Trigger)
|
||||
assert.Equal(t, cmd.AutoComplete, command.AutoComplete)
|
||||
assert.Equal(t, cmd.AutoCompleteDesc, command.AutoCompleteDesc)
|
||||
assert.Equal(t, cmd.AutoCompleteHint, command.AutoCompleteHint)
|
||||
assert.Equal(t, cmd.IconURL, command.IconURL)
|
||||
})
|
||||
|
||||
t.Run("misspelled flag", func(t *testing.T) {
|
||||
args := []string{"command", "", command.Id, "--trigger-wor", "sometrigger"}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
assert.Contains(t, output, "Error: unknown flag:")
|
||||
})
|
||||
|
||||
t.Run("multiple flags nil error", func(t *testing.T) {
|
||||
testName := "multitrigger"
|
||||
testURL := "http://localhost:8000/test-modify"
|
||||
testDescription := "multiple field test"
|
||||
args := []string{"command", "modify", command.Id, "--trigger-word", testName, "--url", testURL, "--description", testDescription}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
cmd, _ := th.App.GetCommand(command.Id)
|
||||
assert.Contains(t, output, "PASS")
|
||||
assert.Equal(t, cmd.Trigger, testName)
|
||||
assert.Equal(t, cmd.URL, testURL)
|
||||
assert.Equal(t, cmd.Description, testDescription)
|
||||
})
|
||||
|
||||
t.Run("displayname nil error", func(t *testing.T) {
|
||||
testVal := "newName"
|
||||
args := []string{"command", "modify", command.Id, "--title", testVal}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
cmd, _ := th.App.GetCommand(command.Id)
|
||||
assert.Contains(t, output, "PASS")
|
||||
assert.Equal(t, cmd.DisplayName, testVal)
|
||||
})
|
||||
|
||||
t.Run("description nil error", func(t *testing.T) {
|
||||
testVal := "test description"
|
||||
args := []string{"command", "modify", command.Id, "--description", testVal}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
cmd, _ := th.App.GetCommand(command.Id)
|
||||
assert.Contains(t, output, "PASS")
|
||||
assert.Equal(t, cmd.Description, testVal)
|
||||
})
|
||||
|
||||
t.Run("trigger nil error", func(t *testing.T) {
|
||||
testVal := "testtrigger"
|
||||
args := []string{"command", "modify", command.Id, "--trigger-word", testVal}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
cmd, _ := th.App.GetCommand(command.Id)
|
||||
assert.Contains(t, output, "PASS")
|
||||
assert.Equal(t, cmd.Trigger, testVal)
|
||||
})
|
||||
|
||||
t.Run("trigger with space", func(t *testing.T) {
|
||||
testVal := "bad trigger"
|
||||
args := []string{"command", "modify", command.Id, "--trigger-word", testVal}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
assert.Contains(t, output, "Error: a trigger word must not contain spaces")
|
||||
})
|
||||
|
||||
t.Run("trigger with leading /", func(t *testing.T) {
|
||||
testVal := "/bad-trigger"
|
||||
args := []string{"command", "modify", command.Id, "--trigger-word", testVal}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
assert.Contains(t, output, "Error: a trigger word cannot begin with a /")
|
||||
})
|
||||
|
||||
t.Run("blank trigger", func(t *testing.T) {
|
||||
cmd_unmodified, _ := th.App.GetCommand(command.Id)
|
||||
args := []string{"command", "modify", command.Id, "--trigger-word", ""}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
cmd_modified, _ := th.App.GetCommand(command.Id)
|
||||
|
||||
// assert trigger remains unchanged
|
||||
assert.Contains(t, output, "PASS")
|
||||
assert.Equal(t, cmd_unmodified.Trigger, cmd_modified.Trigger)
|
||||
})
|
||||
|
||||
//url case
|
||||
t.Run("url nil error", func(t *testing.T) {
|
||||
testVal := "http://localhost:8000/modify-command"
|
||||
args := []string{"command", "modify", command.Id, "--url", testVal}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
cmd, _ := th.App.GetCommand(command.Id)
|
||||
assert.Contains(t, output, "PASS")
|
||||
assert.Equal(t, cmd.URL, testVal)
|
||||
})
|
||||
|
||||
t.Run("blank url", func(t *testing.T) {
|
||||
cmd_unmodified, _ := th.App.GetCommand(command.Id)
|
||||
args := []string{"command", "modify", command.Id, "--url", ""}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
cmd_modified, _ := th.App.GetCommand(command.Id)
|
||||
|
||||
//assert URL remains unchanged
|
||||
assert.Contains(t, output, "PASS")
|
||||
assert.Equal(t, cmd_unmodified.URL, cmd_modified.URL)
|
||||
})
|
||||
|
||||
t.Run("icon url nil error", func(t *testing.T) {
|
||||
testVal := "http://localhost:8000/testicon.png"
|
||||
args := []string{"command", "modify", command.Id, "--icon", testVal}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
cmd, _ := th.App.GetCommand(command.Id)
|
||||
assert.Contains(t, output, "PASS")
|
||||
assert.Equal(t, cmd.IconURL, testVal)
|
||||
})
|
||||
|
||||
t.Run("creator nil error", func(t *testing.T) {
|
||||
testVal := adminUser
|
||||
args := []string{"command", "modify", command.Id, "--creator", testVal.Username}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
cmd, _ := th.App.GetCommand(command.Id)
|
||||
assert.Contains(t, output, "PASS")
|
||||
assert.Equal(t, cmd.CreatorId, testVal.Id)
|
||||
})
|
||||
|
||||
t.Run("creator not found", func(t *testing.T) {
|
||||
testVal := "fakeuser"
|
||||
args := []string{"command", "modify", command.Id, "--creator", testVal}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
assert.Contains(t, output, "unable to find user")
|
||||
})
|
||||
|
||||
t.Run("creator not admin user", func(t *testing.T) {
|
||||
testVal := user.Username
|
||||
args := []string{"command", "modify", command.Id, "--creator", testVal}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
assert.Contains(t, output, "the creator must be a user who has permissions to manage slash commands")
|
||||
})
|
||||
|
||||
t.Run("response username nil error", func(t *testing.T) {
|
||||
testVal := "response-test"
|
||||
args := []string{"command", "modify", command.Id, "--response-username", testVal}
|
||||
output, _ := th.RunCommandWithOutput(t, args...)
|
||||
cmd, _ := th.App.GetCommand(command.Id)
|
||||
assert.Contains(t, output, "PASS")
|
||||
assert.Equal(t, cmd.Username, testVal)
|
||||
})
|
||||
|
||||
t.Run("post set and unset", func(t *testing.T) {
|
||||
args_set := []string{"command", "modify", command.Id, "--post", ""}
|
||||
args_unset := []string{"command", "modify", command.Id, "", ""}
|
||||
|
||||
// set post and check
|
||||
output_set, _ := th.RunCommandWithOutput(t, args_set...)
|
||||
cmd_set, _ := th.App.GetCommand(command.Id)
|
||||
assert.Contains(t, output_set, "PASS")
|
||||
assert.Equal(t, cmd_set.Method, "P")
|
||||
|
||||
// unset post and check
|
||||
output_unset, _ := th.RunCommandWithOutput(t, args_unset...)
|
||||
cmd_unset, _ := th.App.GetCommand(command.Id)
|
||||
assert.Contains(t, output_unset, "PASS")
|
||||
assert.Equal(t, cmd_unset.Method, "G")
|
||||
})
|
||||
|
||||
t.Run("autocomplete set and unset", func(t *testing.T) {
|
||||
args_set := []string{"command", "modify", command.Id, "--autocomplete", ""}
|
||||
args_unset := []string{"command", "modify", command.Id, "", ""}
|
||||
|
||||
// set autocomplete and check
|
||||
output_set, _ := th.RunCommandWithOutput(t, args_set...)
|
||||
cmd_set, _ := th.App.GetCommand(command.Id)
|
||||
assert.Contains(t, output_set, "PASS")
|
||||
assert.Equal(t, cmd_set.AutoComplete, true)
|
||||
|
||||
// unset autocomplete and check
|
||||
output_unset, _ := th.RunCommandWithOutput(t, args_unset...)
|
||||
cmd_unset, _ := th.App.GetCommand(command.Id)
|
||||
assert.Contains(t, output_unset, "PASS")
|
||||
assert.Equal(t, cmd_unset.AutoComplete, false)
|
||||
})
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
const CommandArgsSeparator = ":"
|
||||
|
||||
func getCommandsFromCommandArgs(a *app.App, commandArgs []string) []*model.Command {
|
||||
commands := make([]*model.Command, 0, len(commandArgs))
|
||||
|
||||
for _, commandArg := range commandArgs {
|
||||
command := getCommandFromCommandArg(a, commandArg)
|
||||
commands = append(commands, command)
|
||||
}
|
||||
|
||||
return commands
|
||||
}
|
||||
|
||||
func parseCommandArg(commandArg string) (string, string) {
|
||||
result := strings.SplitN(commandArg, CommandArgsSeparator, 2)
|
||||
|
||||
if len(result) == 1 {
|
||||
return "", commandArg
|
||||
}
|
||||
|
||||
return result[0], result[1]
|
||||
}
|
||||
|
||||
func getCommandFromCommandArg(a *app.App, commandArg string) *model.Command {
|
||||
teamArg, commandPart := parseCommandArg(commandArg)
|
||||
if teamArg == "" && commandPart == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var command *model.Command
|
||||
if teamArg != "" {
|
||||
team := getTeamFromTeamArg(a, teamArg)
|
||||
if team == nil {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
command, err = a.Srv().Store.Command().GetByTrigger(team.Id, commandPart)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if command == nil {
|
||||
command, _ = a.Srv().Store.Command().Get(commandPart)
|
||||
}
|
||||
|
||||
return command
|
||||
}
|
||||
@@ -1,541 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/config"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/i18n"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
"github.com/mattermost/mattermost-server/v6/utils"
|
||||
)
|
||||
|
||||
const noSettingsNamed = "unable to find a setting named: %s"
|
||||
|
||||
var ConfigCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Configuration",
|
||||
}
|
||||
|
||||
var ValidateConfigCmd = &cobra.Command{
|
||||
Use: "validate",
|
||||
Short: "Validate config file",
|
||||
Long: "If the config file is valid, this command will output a success message and have a zero exit code. If it is invalid, this command will output an error and have a non-zero exit code.",
|
||||
RunE: configValidateCmdF,
|
||||
}
|
||||
|
||||
var ConfigSubpathCmd = &cobra.Command{
|
||||
Use: "subpath",
|
||||
Short: "Update client asset loading to use the configured subpath",
|
||||
Long: "Update the hard-coded production client asset paths to take into account Mattermost running on a subpath.",
|
||||
Example: ` config subpath
|
||||
config subpath --path /mattermost
|
||||
config subpath --path /`,
|
||||
RunE: configSubpathCmdF,
|
||||
}
|
||||
|
||||
var ConfigGetCmd = &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Get config setting",
|
||||
Long: "Gets the value of a config setting by its name in dot notation.",
|
||||
Example: `config get SqlSettings.DriverName`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: configGetCmdF,
|
||||
}
|
||||
|
||||
var ConfigShowCmd = &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Writes the server configuration to STDOUT",
|
||||
Long: "Pretty-prints the server configuration and writes to STDOUT",
|
||||
Example: "config show",
|
||||
RunE: configShowCmdF,
|
||||
}
|
||||
|
||||
var ConfigSetCmd = &cobra.Command{
|
||||
Use: "set",
|
||||
Short: "Set config setting",
|
||||
Long: "Sets the value of a config setting by its name in dot notation. Accepts multiple values for array settings",
|
||||
Example: "config set SqlSettings.DriverName mysql",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: configSetCmdF,
|
||||
}
|
||||
|
||||
var MigrateConfigCmd = &cobra.Command{
|
||||
Use: "migrate [from_config] [to_config]",
|
||||
Short: "Migrate existing config between backends",
|
||||
Long: "Migrate a file-based configuration to (or from) a database-based configuration. Point the Mattermost server at the target configuration to start using it",
|
||||
Example: `config migrate path/to/config.json "postgres://mmuser:mostest@localhost:5432/mattermost_test?sslmode=disable&connect_timeout=10"`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: configMigrateCmdF,
|
||||
}
|
||||
|
||||
var ConfigResetCmd = &cobra.Command{
|
||||
Use: "reset",
|
||||
Short: "Reset config setting",
|
||||
Long: "Resets the value of a config setting by its name in dot notation or a setting section. Accepts multiple values for array settings.",
|
||||
Example: "config reset SqlSettings.DriverName LogSettings",
|
||||
RunE: configResetCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
ConfigSubpathCmd.Flags().String("path", "", "Optional subpath; defaults to value in SiteURL")
|
||||
ConfigResetCmd.Flags().Bool("confirm", false, "Confirm you really want to reset all configuration settings to its default value")
|
||||
ConfigShowCmd.Flags().Bool("json", false, "Output the configuration as JSON.")
|
||||
|
||||
ConfigCmd.AddCommand(
|
||||
ValidateConfigCmd,
|
||||
ConfigSubpathCmd,
|
||||
ConfigGetCmd,
|
||||
ConfigShowCmd,
|
||||
ConfigSetCmd,
|
||||
MigrateConfigCmd,
|
||||
ConfigResetCmd,
|
||||
)
|
||||
RootCmd.AddCommand(ConfigCmd)
|
||||
}
|
||||
|
||||
func configValidateCmdF(command *cobra.Command, args []string) error {
|
||||
utils.TranslationsPreInit()
|
||||
model.AppErrorInit(i18n.T)
|
||||
|
||||
_, err := getConfigStore(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
CommandPrettyPrintln("The document is valid")
|
||||
return nil
|
||||
}
|
||||
|
||||
func configSubpathCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
path, err := command.Flags().GetString("path")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed reading path")
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
return utils.UpdateAssetsSubpathFromConfig(a.Config())
|
||||
}
|
||||
|
||||
if err := utils.UpdateAssetsSubpath(path); err != nil {
|
||||
return errors.Wrap(err, "failed to update assets subpath")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getConfigStore(command *cobra.Command) (*config.Store, error) {
|
||||
if err := utils.TranslationsPreInit(); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to initialize i18n")
|
||||
}
|
||||
|
||||
configStore, err := config.NewStoreFromDSN(getConfigDSN(command, config.GetEnvironment()), false, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to initialize config store")
|
||||
}
|
||||
|
||||
return configStore, nil
|
||||
}
|
||||
|
||||
func configGetCmdF(command *cobra.Command, args []string) error {
|
||||
configStore, err := getConfigStore(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := printConfigValues(configToMap(*configStore.Get()), strings.Split(args[0], "."), args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("%s", out)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func configShowCmdF(command *cobra.Command, args []string) error {
|
||||
useJSON, err := command.Flags().GetBool("json")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed reading json parameter")
|
||||
}
|
||||
|
||||
err = cobra.NoArgs(command, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configStore, err := getConfigStore(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config := *configStore.Get()
|
||||
|
||||
if useJSON {
|
||||
configJSON, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal config as json")
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", configJSON)
|
||||
} else {
|
||||
fmt.Printf("%s", prettyPrintStruct(config))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// printConfigValues function prints out the value of the configSettings working recursively or
|
||||
// gives an error if config setting is not in the file.
|
||||
func printConfigValues(configMap map[string]interface{}, configSetting []string, name string) (string, error) {
|
||||
res, ok := configMap[configSetting[0]]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("%s configuration setting is not in the file", name)
|
||||
}
|
||||
value := reflect.ValueOf(res)
|
||||
switch value.Kind() {
|
||||
case reflect.Map:
|
||||
if len(configSetting) == 1 {
|
||||
return printStringMap(value, 0), nil
|
||||
}
|
||||
return printConfigValues(res.(map[string]interface{}), configSetting[1:], name)
|
||||
default:
|
||||
if len(configSetting) == 1 {
|
||||
return fmt.Sprintf("%s: \"%v\"\n", name, res), nil
|
||||
}
|
||||
return "", fmt.Errorf("%s configuration setting is not in the file", name)
|
||||
}
|
||||
}
|
||||
|
||||
func configSetCmdF(command *cobra.Command, args []string) error {
|
||||
configStore, err := getConfigStore(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// args[0] -> holds the config setting that we want to change
|
||||
// args[1:] -> the new value of the config setting
|
||||
configSetting := args[0]
|
||||
newVal := args[1:]
|
||||
|
||||
// create the function to update config
|
||||
oldConfig := configStore.Get().Clone()
|
||||
newConfig := configStore.Get().Clone()
|
||||
|
||||
f := updateConfigValue(configSetting, newVal, oldConfig, newConfig)
|
||||
f(newConfig)
|
||||
|
||||
// UpdateConfig above would have already fixed these invalid locales, but we check again
|
||||
// in the context of an explicit change to these parameters to avoid saving the fixed
|
||||
// settings in the first place.
|
||||
if changed := config.FixInvalidLocales(newConfig); changed {
|
||||
return errors.New("Invalid locale configuration")
|
||||
}
|
||||
|
||||
oldCfg, newCfg, errSet := configStore.Set(newConfig)
|
||||
if errSet != nil {
|
||||
return errors.Wrap(errSet, "failed to set config")
|
||||
}
|
||||
|
||||
a, errInit := InitDBCommandContextCobra(command)
|
||||
if errInit != nil {
|
||||
return errInit
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
auditRec := a.MakeAuditRecord("configSet", audit.Success)
|
||||
defer a.LogAuditRec(auditRec, nil)
|
||||
diffs, diffErr := config.Diff(oldCfg, newCfg)
|
||||
if diffErr != nil {
|
||||
return errors.Wrap(diffErr, "failed to diff configs")
|
||||
}
|
||||
auditRec.AddMeta("diff", diffs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func configMigrateCmdF(command *cobra.Command, args []string) error {
|
||||
if err := utils.TranslationsPreInit(); err != nil {
|
||||
return errors.Wrap(err, "failed to load translations while migrating config")
|
||||
}
|
||||
|
||||
from := args[0]
|
||||
to := args[1]
|
||||
|
||||
err := config.Migrate(from, to)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to migrate config")
|
||||
}
|
||||
|
||||
mlog.Info("Successfully migrated config.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateConfigValue(configSetting string, newVal []string, oldConfig, newConfig *model.Config) func(*model.Config) {
|
||||
return func(update *model.Config) {
|
||||
|
||||
// convert config to map[string]interface
|
||||
configMap := configToMap(*oldConfig)
|
||||
|
||||
// iterate through the map and update the value or print an error and exit
|
||||
err := UpdateMap(configMap, strings.Split(configSetting, "."), newVal)
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// convert map to json
|
||||
bs, err := json.Marshal(configMap)
|
||||
if err != nil {
|
||||
fmt.Printf("Error while marshalling map to json %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// convert json to struct
|
||||
err = json.Unmarshal(bs, newConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Error while unmarshalling json to struct %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
*update = *newConfig
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateMap(configMap map[string]interface{}, configSettings []string, newVal []string) error {
|
||||
res, ok := configMap[configSettings[0]]
|
||||
if !ok {
|
||||
return fmt.Errorf(noSettingsNamed, configSettings[0])
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(res)
|
||||
|
||||
switch value.Kind() {
|
||||
|
||||
case reflect.Map:
|
||||
// we can only change the value of a particular setting, not the whole map, return error
|
||||
if len(configSettings) == 1 {
|
||||
return errors.New("unable to set multiple settings at once")
|
||||
}
|
||||
simpleMap, ok := res.(map[string]interface{})
|
||||
if ok {
|
||||
return UpdateMap(simpleMap, configSettings[1:], newVal)
|
||||
}
|
||||
mapOfTheMap, ok := res.(map[string]map[string]interface{})
|
||||
if ok {
|
||||
convertedMap := make(map[string]interface{})
|
||||
for k, v := range mapOfTheMap {
|
||||
convertedMap[k] = v
|
||||
}
|
||||
return UpdateMap(convertedMap, configSettings[1:], newVal)
|
||||
}
|
||||
pluginStateMap, ok := res.(map[string]*model.PluginState)
|
||||
if ok {
|
||||
convertedMap := make(map[string]interface{})
|
||||
for k, v := range pluginStateMap {
|
||||
convertedMap[k] = v
|
||||
}
|
||||
return UpdateMap(convertedMap, configSettings[1:], newVal)
|
||||
}
|
||||
return fmt.Errorf(noSettingsNamed, configSettings[1])
|
||||
|
||||
case reflect.Int:
|
||||
if len(configSettings) == 1 {
|
||||
val, err := strconv.Atoi(newVal[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configMap[configSettings[0]] = val
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(noSettingsNamed, configSettings[0])
|
||||
|
||||
case reflect.Int64:
|
||||
if len(configSettings) == 1 {
|
||||
val, err := strconv.Atoi(newVal[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configMap[configSettings[0]] = int64(val)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(noSettingsNamed, configSettings[0])
|
||||
|
||||
case reflect.Bool:
|
||||
if len(configSettings) == 1 {
|
||||
val, err := strconv.ParseBool(newVal[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configMap[configSettings[0]] = val
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(noSettingsNamed, configSettings[0])
|
||||
|
||||
case reflect.String:
|
||||
if len(configSettings) == 1 {
|
||||
configMap[configSettings[0]] = newVal[0]
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(noSettingsNamed, configSettings[0])
|
||||
|
||||
case reflect.Slice:
|
||||
if len(configSettings) == 1 {
|
||||
configMap[configSettings[0]] = newVal
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(noSettingsNamed, configSettings[0])
|
||||
|
||||
case reflect.Ptr:
|
||||
state, ok := res.(*model.PluginState)
|
||||
if !ok || len(configSettings) != 2 {
|
||||
return errors.New("type not supported yet")
|
||||
}
|
||||
val, err := strconv.ParseBool(newVal[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state.Enable = val
|
||||
return nil
|
||||
|
||||
default:
|
||||
return errors.New("type not supported yet")
|
||||
}
|
||||
}
|
||||
|
||||
func configResetCmdF(command *cobra.Command, args []string) error {
|
||||
configStore, err := getConfigStore(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a, errInit := InitDBCommandContextCobra(command)
|
||||
if errInit != nil {
|
||||
return errInit
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
var oldCfg *model.Config
|
||||
var newCfg *model.Config
|
||||
|
||||
defer func() {
|
||||
auditRec := a.MakeAuditRecord("configReset", audit.Success)
|
||||
if oldCfg != nil && newCfg != nil {
|
||||
diffs, diffErr := config.Diff(oldCfg, newCfg)
|
||||
if diffErr != nil {
|
||||
mlog.Warn("Failed to diff configs", mlog.Err(diffErr))
|
||||
return
|
||||
}
|
||||
auditRec.AddMeta("diff", diffs)
|
||||
}
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}()
|
||||
|
||||
defaultConfig := &model.Config{}
|
||||
defaultConfig.SetDefaults()
|
||||
|
||||
confirmFlag, _ := command.Flags().GetBool("confirm")
|
||||
if confirmFlag {
|
||||
if oldCfg, newCfg, err = configStore.Set(defaultConfig); err != nil {
|
||||
return errors.Wrap(err, "failed to set config")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if !confirmFlag && len(args) == 0 {
|
||||
var confirmResetAll string
|
||||
CommandPrettyPrintln("Are you sure you want to reset all the configuration settings?(YES/NO): ")
|
||||
fmt.Scanln(&confirmResetAll)
|
||||
if confirmResetAll == "YES" {
|
||||
if oldCfg, newCfg, err = configStore.Set(defaultConfig); err != nil {
|
||||
return errors.Wrap(err, "failed to set config")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
tempConfig := configStore.Get().Clone()
|
||||
tempConfigMap := configToMap(*tempConfig)
|
||||
defaultConfigMap := configToMap(*defaultConfig)
|
||||
for _, arg := range args {
|
||||
err = changeMap(tempConfigMap, defaultConfigMap, strings.Split(arg, "."))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to reset config")
|
||||
}
|
||||
}
|
||||
bs, err := json.Marshal(tempConfigMap)
|
||||
if err != nil {
|
||||
fmt.Printf("Error while marshalling map to json %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = json.Unmarshal(bs, tempConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Error while unmarshalling json to struct %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if changed := config.FixInvalidLocales(tempConfig); changed {
|
||||
return errors.New("Invalid locale configuration")
|
||||
}
|
||||
|
||||
oldCfg, newCfg, err = configStore.Set(tempConfig)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to set config")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func changeMap(oldConfigMap, defaultConfigMap map[string]interface{}, configSettings []string) error {
|
||||
resOld, ok := oldConfigMap[configSettings[0]]
|
||||
if !ok {
|
||||
return fmt.Errorf("Unable to find a setting with that name %s", configSettings[0])
|
||||
}
|
||||
resDef := defaultConfigMap[configSettings[0]]
|
||||
valueOld := reflect.ValueOf(resOld)
|
||||
|
||||
if valueOld.Kind() == reflect.Map {
|
||||
if len(configSettings) == 1 {
|
||||
return changeSection(resOld.(map[string]interface{}), resDef.(map[string]interface{}))
|
||||
}
|
||||
return changeMap(resOld.(map[string]interface{}), resDef.(map[string]interface{}), configSettings[1:])
|
||||
}
|
||||
if len(configSettings) == 1 {
|
||||
oldConfigMap[configSettings[0]] = defaultConfigMap[configSettings[0]]
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Unable to find a setting with that name %s", configSettings[0])
|
||||
}
|
||||
|
||||
func changeSection(oldConfigMap, defaultConfigMap map[string]interface{}) error {
|
||||
valueOld := reflect.ValueOf(oldConfigMap)
|
||||
for _, key := range valueOld.MapKeys() {
|
||||
oldConfigMap[key.String()] = defaultConfigMap[key.String()]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// configToMap converts our config into a map
|
||||
func configToMap(s interface{}) map[string]interface{} {
|
||||
return structToMap(s)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConfigFlag(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
dir := th.TemporaryDirectory()
|
||||
|
||||
prevDir, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
defer os.Chdir(prevDir)
|
||||
os.Chdir(dir)
|
||||
|
||||
t.Run("version without a config file should fail", func(t *testing.T) {
|
||||
err := os.RemoveAll("config")
|
||||
require.NoError(t, err)
|
||||
th.SetAutoConfig(false)
|
||||
defer th.SetAutoConfig(true)
|
||||
require.Error(t, th.RunCommand(t, "version"))
|
||||
})
|
||||
|
||||
t.Run("version with varying paths to the config file", func(t *testing.T) {
|
||||
th.CheckCommand(t, "--config", filepath.Base(th.ConfigPath()), "version")
|
||||
th.CheckCommand(t, "--config", "./"+filepath.Base(th.ConfigPath()), "version")
|
||||
th.CheckCommand(t, "version")
|
||||
})
|
||||
}
|
||||
@@ -1,673 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/config"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
type TestConfig struct {
|
||||
TestServiceSettings TestServiceSettings
|
||||
TestTeamSettings TestTeamSettings
|
||||
TestClientRequirements TestClientRequirements
|
||||
TestMessageExportSettings TestMessageExportSettings
|
||||
}
|
||||
|
||||
type TestMessageExportSettings struct {
|
||||
Enableexport bool
|
||||
Exportformat string
|
||||
TestGlobalRelaySettings TestGlobalRelaySettings
|
||||
}
|
||||
|
||||
type TestGlobalRelaySettings struct {
|
||||
Customertype string
|
||||
SMTPUsername string
|
||||
SMTPPassword string
|
||||
}
|
||||
|
||||
type TestServiceSettings struct {
|
||||
Siteurl string
|
||||
Websocketurl string
|
||||
Licensedfieldlocation string
|
||||
}
|
||||
|
||||
type TestTeamSettings struct {
|
||||
Sitename string
|
||||
Maxuserperteam int
|
||||
}
|
||||
|
||||
type TestClientRequirements struct {
|
||||
Androidlatestversion string
|
||||
Androidminversion string
|
||||
Desktoplatestversion string
|
||||
}
|
||||
|
||||
type TestNewConfig struct {
|
||||
TestNewServiceSettings TestNewServiceSettings
|
||||
TestNewTeamSettings TestNewTeamSettings
|
||||
}
|
||||
|
||||
type TestNewServiceSettings struct {
|
||||
SiteURL *string
|
||||
UseLetsEncrypt *bool
|
||||
TLSStrictTransportMaxAge *int64
|
||||
AllowedThemes []string
|
||||
}
|
||||
|
||||
type TestNewTeamSettings struct {
|
||||
SiteName *string
|
||||
MaxUserPerTeam *int
|
||||
}
|
||||
|
||||
type TestPluginSettings struct {
|
||||
Enable *bool
|
||||
Directory *string `restricted:"true"`
|
||||
Plugins map[string]map[string]interface{}
|
||||
PluginStates map[string]*model.PluginState
|
||||
SignaturePublicKeyFiles []string
|
||||
}
|
||||
|
||||
func getDsn(driver string, source string) string {
|
||||
if driver == model.DatabaseDriverMysql {
|
||||
return driver + "://" + source
|
||||
}
|
||||
return source
|
||||
}
|
||||
|
||||
func TestConfigValidate(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
tempFile, err := ioutil.TempFile("", "TestConfigValidate")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tempFile.Name())
|
||||
tempFile.Write([]byte("{"))
|
||||
|
||||
assert.Error(t, th.RunCommand(t, "--config", tempFile.Name(), "config", "validate"))
|
||||
th.CheckCommand(t, "config", "validate")
|
||||
}
|
||||
|
||||
func TestConfigGet(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("Error when no arguments are given", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "config", "get"))
|
||||
})
|
||||
|
||||
t.Run("Error when more than one config settings are given", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "config", "get", "abc", "def"))
|
||||
})
|
||||
|
||||
t.Run("Error when a config setting which is not in the config.json is given", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "config", "get", "abc"))
|
||||
})
|
||||
|
||||
t.Run("No Error when a config setting which is in the config.json is given", func(t *testing.T) {
|
||||
th.CheckCommand(t, "config", "get", "MessageExportSettings")
|
||||
th.CheckCommand(t, "config", "get", "MessageExportSettings.GlobalRelaySettings")
|
||||
th.CheckCommand(t, "config", "get", "MessageExportSettings.GlobalRelaySettings.CustomerType")
|
||||
})
|
||||
|
||||
t.Run("check output", func(t *testing.T) {
|
||||
output := th.CheckCommand(t, "config", "get", "MessageExportSettings")
|
||||
|
||||
assert.Contains(t, output, "EnableExport")
|
||||
assert.Contains(t, output, "ExportFormat")
|
||||
assert.Contains(t, output, "DailyRunTime")
|
||||
assert.Contains(t, output, "ExportFromTimestamp")
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigSet(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("Error when no arguments are given", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "config", "set"))
|
||||
})
|
||||
|
||||
t.Run("Error when only one argument is given", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "config", "set", "test"))
|
||||
})
|
||||
|
||||
t.Run("Error when the wrong key is set", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "config", "set", "invalid-key", "value"))
|
||||
assert.Error(t, th.RunCommand(t, "config", "get", "invalid-key"))
|
||||
})
|
||||
|
||||
t.Run("Error when the wrong value is set", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "config", "set", "EmailSettings.ConnectionSecurity", "invalid-key"))
|
||||
output := th.CheckCommand(t, "config", "get", "EmailSettings.ConnectionSecurity")
|
||||
assert.NotContains(t, output, "invalid-key")
|
||||
})
|
||||
|
||||
t.Run("Error when the parameter of an unknown plugin is set", func(t *testing.T) {
|
||||
output, err := th.RunCommandWithOutput(t, "config", "set", "PluginSettings.Plugins.someplugin", "true")
|
||||
assert.Error(t, err)
|
||||
assert.NotContains(t, output, "panic")
|
||||
})
|
||||
|
||||
t.Run("Error when the wrong locale is set", func(t *testing.T) {
|
||||
th.CheckCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "es")
|
||||
assert.Error(t, th.RunCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "invalid-key"))
|
||||
output := th.CheckCommand(t, "config", "get", "LocalizationSettings.DefaultServerLocale")
|
||||
assert.NotContains(t, output, "invalid-key")
|
||||
assert.NotContains(t, output, "\"en\"")
|
||||
})
|
||||
|
||||
t.Run("Success when a valid value is set", func(t *testing.T) {
|
||||
assert.NoError(t, th.RunCommand(t, "config", "set", "EmailSettings.ConnectionSecurity", "TLS"))
|
||||
output := th.CheckCommand(t, "config", "get", "EmailSettings.ConnectionSecurity")
|
||||
assert.Contains(t, output, "TLS")
|
||||
})
|
||||
|
||||
t.Run("Success when a valid locale is set", func(t *testing.T) {
|
||||
assert.NoError(t, th.RunCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "es"))
|
||||
output := th.CheckCommand(t, "config", "get", "LocalizationSettings.DefaultServerLocale")
|
||||
assert.Contains(t, output, "\"es\"")
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigReset(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("No Error when no arguments are given (reset all the configurations)", func(t *testing.T) {
|
||||
assert.NoError(t, th.RunCommand(t, "config", "reset"))
|
||||
})
|
||||
|
||||
t.Run("No Error when a configuration section is given", func(t *testing.T) {
|
||||
assert.NoError(t, th.RunCommand(t, "config", "reset", "JobSettings"))
|
||||
})
|
||||
|
||||
t.Run("No Error when a configuration setting is given", func(t *testing.T) {
|
||||
assert.NoError(t, th.RunCommand(t, "config", "reset", "JobSettings.RunJobs"))
|
||||
})
|
||||
|
||||
t.Run("Error when the wrong configuration section is given", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "config", "reset", "InvalidSettings"))
|
||||
})
|
||||
|
||||
t.Run("Error when the wrong configuration setting is given", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "config", "reset", "JobSettings.InvalidConfiguration"))
|
||||
})
|
||||
|
||||
t.Run("Success when the confirm boolean flag is given", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
assert.NoError(t, th.RunCommand(t, "config", "set", "JobSettings.RunJobs", "false"))
|
||||
assert.NoError(t, th.RunCommand(t, "config", "set", "PrivacySettings.ShowFullName", "false"))
|
||||
assert.NoError(t, th.RunCommand(t, "config", "reset", "--confirm"))
|
||||
output1 := th.CheckCommand(t, "config", "get", "JobSettings.RunJobs")
|
||||
output2 := th.CheckCommand(t, "config", "get", "PrivacySettings.ShowFullName")
|
||||
assert.Contains(t, output1, "true")
|
||||
assert.Contains(t, output2, "true")
|
||||
})
|
||||
|
||||
t.Run("Success when a configuration section is given", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
output, err := th.RunCommandWithOutput(t, "config", "set", "JobSettings.RunJobs", "false")
|
||||
assert.NoErrorf(t, err, "output %s", output)
|
||||
|
||||
output, err = th.RunCommandWithOutput(t, "config", "set", "JobSettings.RunScheduler", "false")
|
||||
assert.NoErrorf(t, err, "output %s", output)
|
||||
|
||||
output, err = th.RunCommandWithOutput(t, "config", "set", "PrivacySettings.ShowFullName", "false")
|
||||
assert.NoErrorf(t, err, "output %s", output)
|
||||
|
||||
output, err = th.RunCommandWithOutput(t, "config", "reset", "JobSettings")
|
||||
assert.NoErrorf(t, err, "output %s", output)
|
||||
|
||||
output1 := th.CheckCommand(t, "config", "get", "JobSettings.RunJobs")
|
||||
output2 := th.CheckCommand(t, "config", "get", "JobSettings.RunScheduler")
|
||||
output3 := th.CheckCommand(t, "config", "get", "PrivacySettings.ShowFullName")
|
||||
assert.Contains(t, output1, "true")
|
||||
assert.Contains(t, output2, "true")
|
||||
assert.Contains(t, output3, "false")
|
||||
})
|
||||
|
||||
t.Run("Success when a configuration setting is given", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
output, err := th.RunCommandWithOutput(t, "config", "set", "JobSettings.RunJobs", "false")
|
||||
assert.NoErrorf(t, err, "output %s", output)
|
||||
|
||||
output, err = th.RunCommandWithOutput(t, "config", "set", "JobSettings.RunScheduler", "false")
|
||||
assert.NoErrorf(t, err, "output %s", output)
|
||||
|
||||
output, err = th.RunCommandWithOutput(t, "config", "reset", "JobSettings.RunJobs")
|
||||
assert.NoErrorf(t, err, "output %s", output)
|
||||
|
||||
output1 := th.CheckCommand(t, "config", "get", "JobSettings.RunJobs")
|
||||
output2 := th.CheckCommand(t, "config", "get", "JobSettings.RunScheduler")
|
||||
assert.Contains(t, output1, "true")
|
||||
assert.Contains(t, output2, "false")
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfigToMap(t *testing.T) {
|
||||
// This test is almost the same as TestStructToMap, but I have it here for the sake of completions
|
||||
cases := []struct {
|
||||
Name string
|
||||
Input interface{}
|
||||
Expected map[string]interface{}
|
||||
}{
|
||||
{
|
||||
Name: "Struct with one string field",
|
||||
Input: struct {
|
||||
Test string
|
||||
}{
|
||||
Test: "test",
|
||||
},
|
||||
Expected: map[string]interface{}{
|
||||
"Test": "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "String with multiple fields of different ",
|
||||
Input: struct {
|
||||
Test1 string
|
||||
Test2 int
|
||||
Test3 string
|
||||
Test4 bool
|
||||
}{
|
||||
Test1: "test1",
|
||||
Test2: 21,
|
||||
Test3: "test2",
|
||||
Test4: false,
|
||||
},
|
||||
Expected: map[string]interface{}{
|
||||
"Test1": "test1",
|
||||
"Test2": 21,
|
||||
"Test3": "test2",
|
||||
"Test4": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Nested fields",
|
||||
Input: TestConfig{
|
||||
TestServiceSettings{"abc", "def", "ghi"},
|
||||
TestTeamSettings{"abc", 1},
|
||||
TestClientRequirements{"abc", "def", "ghi"},
|
||||
TestMessageExportSettings{true, "abc", TestGlobalRelaySettings{"abc", "def", "ghi"}},
|
||||
},
|
||||
Expected: map[string]interface{}{
|
||||
"TestServiceSettings": map[string]interface{}{
|
||||
"Siteurl": "abc",
|
||||
"Websocketurl": "def",
|
||||
"Licensedfieldlocation": "ghi",
|
||||
},
|
||||
"TestTeamSettings": map[string]interface{}{
|
||||
"Sitename": "abc",
|
||||
"Maxuserperteam": 1,
|
||||
},
|
||||
"TestClientRequirements": map[string]interface{}{
|
||||
"Androidlatestversion": "abc",
|
||||
"Androidminversion": "def",
|
||||
"Desktoplatestversion": "ghi",
|
||||
},
|
||||
"TestMessageExportSettings": map[string]interface{}{
|
||||
"Enableexport": true,
|
||||
"Exportformat": "abc",
|
||||
"TestGlobalRelaySettings": map[string]interface{}{
|
||||
"Customertype": "abc",
|
||||
"SMTPUsername": "def",
|
||||
"SMTPPassword": "ghi",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
res := configToMap(test.Input)
|
||||
|
||||
if !reflect.DeepEqual(res, test.Expected) {
|
||||
t.Errorf("got %v want %v ", res, test.Expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintConfigValues(t *testing.T) {
|
||||
outputs := []string{
|
||||
"Siteurl: \"abc\"\nWebsocketurl: \"def\"\nLicensedfieldlocation: \"ghi\"\n",
|
||||
"Sitename: \"abc\"\nMaxuserperteam: \"1\"\n",
|
||||
"Androidlatestversion: \"abc\"\nAndroidminversion: \"def\"\nDesktoplatestversion: \"ghi\"\n",
|
||||
"Enableexport: \"true\"\nExportformat: \"abc\"\nTestGlobalRelaySettings:\n\tCustomertype: \"abc\"\n\tSMTPUsername: \"def\"\n\tSMTPPassword: \"ghi\"\n",
|
||||
"Customertype: \"abc\"\nSMTPUsername: \"def\"\nSMTPPassword: \"ghi\"\n",
|
||||
}
|
||||
|
||||
commands := []string{
|
||||
"TestServiceSettings",
|
||||
"TestTeamSettings",
|
||||
"TestClientRequirements",
|
||||
"TestMessageExportSettings",
|
||||
"TestMessageExportSettings.TestGlobalRelaySettings",
|
||||
}
|
||||
|
||||
input := TestConfig{
|
||||
TestServiceSettings{"abc", "def", "ghi"},
|
||||
TestTeamSettings{"abc", 1},
|
||||
TestClientRequirements{"abc", "def", "ghi"},
|
||||
TestMessageExportSettings{true, "abc", TestGlobalRelaySettings{"abc", "def", "ghi"}},
|
||||
}
|
||||
|
||||
configMap := structToMap(input)
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
Command string
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
Name: "First test",
|
||||
Command: commands[0],
|
||||
Expected: outputs[0],
|
||||
},
|
||||
{
|
||||
Name: "Second test",
|
||||
Command: commands[1],
|
||||
Expected: outputs[1],
|
||||
},
|
||||
{
|
||||
Name: "third test",
|
||||
Command: commands[2],
|
||||
Expected: outputs[2],
|
||||
},
|
||||
{
|
||||
Name: "fourth test",
|
||||
Command: commands[3],
|
||||
Expected: outputs[3],
|
||||
},
|
||||
{
|
||||
Name: "fifth test",
|
||||
Command: commands[4],
|
||||
Expected: outputs[4],
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
res, _ := printConfigValues(configMap, strings.Split(test.Command, "."), test.Command)
|
||||
|
||||
// create two slice of string formed by splitting our strings on \n
|
||||
slice1 := strings.Split(res, "\n")
|
||||
slice2 := strings.Split(test.Expected, "\n")
|
||||
|
||||
sort.Strings(slice1)
|
||||
sort.Strings(slice2)
|
||||
|
||||
if !reflect.DeepEqual(slice1, slice2) {
|
||||
t.Errorf("got '%#v' want '%#v", slice1, slice2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShow(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("error with unknown subcommand", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "config", "show", "abc"))
|
||||
})
|
||||
|
||||
t.Run("successfully dumping config", func(t *testing.T) {
|
||||
output := th.CheckCommand(t, "config", "show")
|
||||
assert.Contains(t, output, "SqlSettings")
|
||||
assert.Contains(t, output, "MessageExportSettings")
|
||||
assert.Contains(t, output, "AnnouncementSettings")
|
||||
})
|
||||
|
||||
t.Run("successfully dumping config as json", func(t *testing.T) {
|
||||
output, err := th.RunCommandWithOutput(t, "config", "show", "--json")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Filter out the test headers
|
||||
var filteredOutput []string
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
if strings.HasPrefix(line, "---") || strings.HasPrefix(line, "===") || strings.HasPrefix(line, "PASS") || strings.HasPrefix(line, "coverage:") {
|
||||
continue
|
||||
}
|
||||
|
||||
filteredOutput = append(filteredOutput, line)
|
||||
}
|
||||
|
||||
output = strings.Join(filteredOutput, "")
|
||||
|
||||
var config model.Config
|
||||
err = json.Unmarshal([]byte(output), &config)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetConfig(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
// Error when no argument is given
|
||||
assert.Error(t, th.RunCommand(t, "config", "set"))
|
||||
|
||||
// No Error when more than one argument is given
|
||||
th.CheckCommand(t, "config", "set", "ThemeSettings.AllowedThemes", "hello", "World")
|
||||
|
||||
// No Error when two arguments are given
|
||||
th.CheckCommand(t, "config", "set", "ThemeSettings.AllowedThemes", "hello")
|
||||
|
||||
// Error when only one argument is given
|
||||
assert.Error(t, th.RunCommand(t, "config", "set", "ThemeSettings.AllowedThemes"))
|
||||
|
||||
// Error when config settings not in the config file are given
|
||||
assert.Error(t, th.RunCommand(t, "config", "set", "Abc"))
|
||||
}
|
||||
|
||||
func TestUpdateMap(t *testing.T) {
|
||||
// create a config to make changes
|
||||
config := TestNewConfig{
|
||||
TestNewServiceSettings{
|
||||
SiteURL: model.NewString("abc.def"),
|
||||
UseLetsEncrypt: model.NewBool(false),
|
||||
TLSStrictTransportMaxAge: model.NewInt64(36),
|
||||
AllowedThemes: []string{"Hello", "World"},
|
||||
},
|
||||
TestNewTeamSettings{
|
||||
SiteName: model.NewString("def.ghi"),
|
||||
MaxUserPerTeam: model.NewInt(12),
|
||||
},
|
||||
}
|
||||
|
||||
// create a map of type map[string]interface
|
||||
configMap := configToMap(config)
|
||||
|
||||
cases := []struct {
|
||||
Name string
|
||||
configSettings []string
|
||||
newVal []string
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
Name: "check for Map and string",
|
||||
configSettings: []string{"TestNewServiceSettings", "SiteURL"},
|
||||
newVal: []string{"siteurl"},
|
||||
expected: "siteurl",
|
||||
},
|
||||
{
|
||||
Name: "check for Map and bool",
|
||||
configSettings: []string{"TestNewServiceSettings", "UseLetsEncrypt"},
|
||||
newVal: []string{"true"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
Name: "check for Map and int64",
|
||||
configSettings: []string{"TestNewServiceSettings", "TLSStrictTransportMaxAge"},
|
||||
newVal: []string{"56"},
|
||||
expected: int64(56),
|
||||
},
|
||||
{
|
||||
Name: "check for Map and string Slice",
|
||||
configSettings: []string{"TestNewServiceSettings", "AllowedThemes"},
|
||||
newVal: []string{"hello1", "world1"},
|
||||
expected: []string{"hello1", "world1"},
|
||||
},
|
||||
{
|
||||
Name: "Map and string",
|
||||
configSettings: []string{"TestNewTeamSettings", "SiteName"},
|
||||
newVal: []string{"jkl.mno"},
|
||||
expected: "jkl.mno",
|
||||
},
|
||||
{
|
||||
Name: "Map and int",
|
||||
configSettings: []string{"TestNewTeamSettings", "MaxUserPerTeam"},
|
||||
newVal: []string{"18"},
|
||||
expected: 18,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
err := UpdateMap(configMap, test.configSettings, test.newVal)
|
||||
|
||||
require.NoError(t, err, "Wasn't expecting an error")
|
||||
|
||||
if !contains(configMap, test.expected, test.configSettings) {
|
||||
t.Error("update didn't happen")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigMigrate(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
sqlSettings := mainHelper.GetSQLSettings()
|
||||
sqlDSN := getDsn(*sqlSettings.DriverName, *sqlSettings.DataSource)
|
||||
fileDSN := "config.json"
|
||||
|
||||
ds, err := config.NewStoreFromDSN(sqlDSN, false, nil)
|
||||
require.NoError(t, err)
|
||||
fs, err := config.NewStoreFromDSN(fileDSN, false, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer ds.Close()
|
||||
defer fs.Close()
|
||||
|
||||
t.Run("Should error with too few parameters", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "config", "migrate", fileDSN))
|
||||
})
|
||||
|
||||
t.Run("Should error with too many parameters", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "config", "migrate", fileDSN, sqlDSN, "reallyfast"))
|
||||
})
|
||||
|
||||
t.Run("Should work passing two parameters", func(t *testing.T) {
|
||||
assert.NoError(t, th.RunCommand(t, "config", "migrate", fileDSN, sqlDSN))
|
||||
})
|
||||
|
||||
t.Run("Should fail passing an invalid target", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "config", "migrate", fileDSN, "mysql://asd"))
|
||||
})
|
||||
|
||||
t.Run("Should fail passing an invalid source", func(t *testing.T) {
|
||||
assert.Error(t, th.RunCommand(t, "config", "migrate", "invalid/path", sqlDSN))
|
||||
})
|
||||
}
|
||||
|
||||
func contains(configMap map[string]interface{}, v interface{}, configSettings []string) bool {
|
||||
res := configMap[configSettings[0]]
|
||||
|
||||
value := reflect.ValueOf(res)
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Map:
|
||||
return contains(res.(map[string]interface{}), v, configSettings[1:])
|
||||
case reflect.Slice:
|
||||
return reflect.DeepEqual(value.Interface(), v)
|
||||
case reflect.Int64:
|
||||
return value.Interface() == v.(int64)
|
||||
default:
|
||||
return value.Interface() == v
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginConfigs(t *testing.T) {
|
||||
pluginConfig := TestPluginSettings{
|
||||
Enable: model.NewBool(true),
|
||||
Directory: model.NewString("dir"),
|
||||
Plugins: map[string]map[string]interface{}{
|
||||
"antivirus": {
|
||||
"clamavhostport": "localhost:3310",
|
||||
"scantimeoutseconds": 12,
|
||||
},
|
||||
"com.mattermost.demo-plugin": {
|
||||
"channelname": "demo_plugin",
|
||||
"customsetting": "7",
|
||||
"enablementionuser": false,
|
||||
"lastname": "Plugin User",
|
||||
"mentionuser": "demo_plugin",
|
||||
"randomsecret": "random secret",
|
||||
"secretmessage": "Changed value.",
|
||||
"textstyle": "",
|
||||
"username": "demo_plugin",
|
||||
},
|
||||
"com.mattermost.webex": {
|
||||
"sitehost": "praptishrestha.my.webex.com",
|
||||
},
|
||||
"jira": {
|
||||
"enablejiraui": true,
|
||||
"groupsallowedtoeditjirasubscriptions": "",
|
||||
"rolesallowedtoeditjirasubscriptions": "system_admin",
|
||||
"secret": "some secret",
|
||||
},
|
||||
"mattermost-autolink": {
|
||||
"enableadmincommand": false,
|
||||
},
|
||||
},
|
||||
PluginStates: map[string]*model.PluginState{
|
||||
"antivirus": {
|
||||
Enable: false,
|
||||
},
|
||||
"com.github.manland.mattermost-plugin-gitlab": {
|
||||
Enable: true,
|
||||
},
|
||||
},
|
||||
SignaturePublicKeyFiles: []string{"Hello", "World"},
|
||||
}
|
||||
|
||||
configMap := configToMap(pluginConfig)
|
||||
err := UpdateMap(configMap, []string{"Enable"}, []string{"false"})
|
||||
require.NoError(t, err, "Wasn't expecting an error")
|
||||
assert.Equal(t, false, configMap["Enable"].(bool))
|
||||
|
||||
err = UpdateMap(configMap, []string{"Plugins", "antivirus", "clamavhostport"}, []string{"some text"})
|
||||
require.NoError(t, err, "Wasn't expecting an error")
|
||||
assert.Equal(t, "some text", configMap["Plugins"].(map[string]map[string]interface{})["antivirus"]["clamavhostport"].(string))
|
||||
|
||||
err = UpdateMap(configMap, []string{"Plugins", "mattermost-autolink", "enableadmincommand"}, []string{"true"})
|
||||
require.NoError(t, err, "Wasn't expecting an error")
|
||||
assert.Equal(t, true, configMap["Plugins"].(map[string]map[string]interface{})["mattermost-autolink"]["enableadmincommand"].(bool))
|
||||
|
||||
err = UpdateMap(configMap, []string{"PluginStates", "antivirus", "Enable"}, []string{"true"})
|
||||
require.NoError(t, err, "Wasn't expecting an error")
|
||||
assert.Equal(t, true, configMap["PluginStates"].(map[string]*model.PluginState)["antivirus"].Enable)
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
|
||||
var ExtractContentCmd = &cobra.Command{
|
||||
Use: "extract-documents-content",
|
||||
Short: "Extracts the documents content",
|
||||
Long: "Extracts the documents content and stores it in the database for document search",
|
||||
Example: "extract-documents-content --from=12345",
|
||||
RunE: extractContentCmdF,
|
||||
}
|
||||
|
||||
var ignoredFiles map[string]bool
|
||||
|
||||
func init() {
|
||||
ignoredFiles = map[string]bool{
|
||||
"png": true, "jpg": true, "jpeg": true, "gif": true, "wmv": true,
|
||||
"mpg": true, "mpeg": true, "mp3": true, "mp4": true, "ogg": true,
|
||||
"ogv": true, "mov": true, "apk": true, "svg": true, "webm": true,
|
||||
"mkv": true,
|
||||
}
|
||||
ExtractContentCmd.Flags().Int64("from", 0, "The timestamp of the earliest file to extract, expressed in seconds since the unix epoch.")
|
||||
ExtractContentCmd.Flags().Int64("to", model.GetMillis()/1000, "The timestamp of the latest file to extract, expressed in seconds since the unix epoch.")
|
||||
RootCmd.AddCommand(ExtractContentCmd)
|
||||
}
|
||||
|
||||
func extractContentCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
if !*a.Config().FileSettings.ExtractContent {
|
||||
return errors.New("ERROR: Document extraction is not enabled")
|
||||
}
|
||||
|
||||
startTime, err := command.Flags().GetInt64("from")
|
||||
if err != nil {
|
||||
return errors.New("\"from\" flag error")
|
||||
}
|
||||
if startTime < 0 {
|
||||
return errors.New("\"from\" must be a positive integer")
|
||||
}
|
||||
|
||||
endTime, err := command.Flags().GetInt64("to")
|
||||
if err != nil {
|
||||
return errors.New("\"to\" flag error")
|
||||
}
|
||||
if endTime < startTime {
|
||||
return errors.New("\"to\" must be greater than from")
|
||||
}
|
||||
|
||||
since := startTime * 1000
|
||||
for {
|
||||
opts := model.GetFileInfosOptions{
|
||||
Since: since,
|
||||
SortBy: model.FileinfoSortByCreated,
|
||||
IncludeDeleted: false,
|
||||
}
|
||||
fileInfos, err := a.Srv().Store.FileInfo().GetWithOptions(0, 1000, &opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ERROR: Document extraction failed %v", err.Error())
|
||||
}
|
||||
if len(fileInfos) == 0 {
|
||||
break
|
||||
}
|
||||
for _, fileInfo := range fileInfos {
|
||||
if !ignoredFiles[fileInfo.Extension] {
|
||||
fmt.Println("extracting file", fileInfo.Name, fileInfo.Path)
|
||||
err = a.ExtractContentFromFileInfo(fileInfo)
|
||||
if err != nil {
|
||||
mlog.Error("Failed to extract file content", mlog.Err(err), mlog.String("fileInfoId", fileInfo.Id))
|
||||
}
|
||||
}
|
||||
}
|
||||
lastFileInfo := fileInfos[len(fileInfos)-1]
|
||||
if lastFileInfo.CreateAt > endTime*1000 {
|
||||
break
|
||||
}
|
||||
since = lastFileInfo.CreateAt + 1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,326 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
var GroupCmd = &cobra.Command{
|
||||
Use: "group",
|
||||
Short: "Management of groups",
|
||||
}
|
||||
|
||||
var ChannelGroupCmd = &cobra.Command{
|
||||
Use: "channel",
|
||||
Short: "Management of channel groups",
|
||||
}
|
||||
|
||||
var ChannelGroupEnableCmd = &cobra.Command{
|
||||
Use: "enable [team]:[channel]",
|
||||
Short: "Enables group constraint on the specified channel",
|
||||
Example: " group channel enable myteam:mychannel",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: channelGroupEnableCmdF,
|
||||
}
|
||||
|
||||
var ChannelGroupDisableCmd = &cobra.Command{
|
||||
Use: "disable [team]:[channel]",
|
||||
Short: "Disables group constraint on the specified channel",
|
||||
Example: " group channel disable myteam:mychannel",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: channelGroupDisableCmdF,
|
||||
}
|
||||
|
||||
var ChannelGroupStatusCmd = &cobra.Command{
|
||||
Use: "status [team]:[channel]",
|
||||
Short: "Shows the group constraint status of the specified channel",
|
||||
Example: " group channel status myteam:mychannel",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: channelGroupStatusCmdF,
|
||||
}
|
||||
|
||||
var ChannelGroupListCmd = &cobra.Command{
|
||||
Use: "list [team]:[channel]",
|
||||
Short: "List channel groups",
|
||||
Long: "Lists the groups associated with a channel",
|
||||
Example: " group channel list myteam:mychannel",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: channelGroupListCmdF,
|
||||
}
|
||||
|
||||
var TeamGroupCmd = &cobra.Command{
|
||||
Use: "team",
|
||||
Short: "Management of team groups",
|
||||
}
|
||||
|
||||
var TeamGroupEnableCmd = &cobra.Command{
|
||||
Use: "enable [team]",
|
||||
Short: "Enables group constraint on the specified team",
|
||||
Example: " group team enable myteam",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: teamGroupEnableCmdF,
|
||||
}
|
||||
|
||||
var TeamGroupDisableCmd = &cobra.Command{
|
||||
Use: "disable [team]",
|
||||
Short: "Disables group constraint on the specified team",
|
||||
Example: " group team disable myteam",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: teamGroupDisableCmdF,
|
||||
}
|
||||
|
||||
var TeamGroupStatusCmd = &cobra.Command{
|
||||
Use: "status [team]",
|
||||
Short: "Shows the group constraint status of the specified team",
|
||||
Example: " group team status myteam",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: teamGroupStatusCmdF,
|
||||
}
|
||||
|
||||
var TeamGroupListCmd = &cobra.Command{
|
||||
Use: "list [team]",
|
||||
Short: "List team groups",
|
||||
Long: "Lists the groups associated with a team",
|
||||
Example: " group team list myteam",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: teamGroupListCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
ChannelGroupCmd.AddCommand(
|
||||
ChannelGroupEnableCmd,
|
||||
ChannelGroupDisableCmd,
|
||||
ChannelGroupStatusCmd,
|
||||
ChannelGroupListCmd,
|
||||
)
|
||||
|
||||
TeamGroupCmd.AddCommand(
|
||||
TeamGroupEnableCmd,
|
||||
TeamGroupDisableCmd,
|
||||
TeamGroupStatusCmd,
|
||||
TeamGroupListCmd,
|
||||
)
|
||||
|
||||
GroupCmd.AddCommand(
|
||||
ChannelGroupCmd,
|
||||
TeamGroupCmd,
|
||||
)
|
||||
|
||||
RootCmd.AddCommand(GroupCmd)
|
||||
}
|
||||
|
||||
func channelGroupEnableCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
channel := getChannelFromChannelArg(a, args[0])
|
||||
if channel == nil {
|
||||
return errors.New("Unable to find channel '" + args[0] + "'")
|
||||
}
|
||||
|
||||
if channel.Type != model.ChannelTypePrivate {
|
||||
return errors.New("Channel '" + args[0] + "' is not private. It cannot be group-constrained")
|
||||
}
|
||||
|
||||
groups, _, appErr := a.GetGroupsByChannel(channel.Id, model.GroupSearchOpts{})
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
if len(groups) == 0 {
|
||||
return errors.New("Channel '" + args[0] + "' has no groups associated. It cannot be group-constrained")
|
||||
}
|
||||
|
||||
channel.GroupConstrained = model.NewBool(true)
|
||||
if _, appErr = a.UpdateChannel(channel); appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("channelGroupEnable", audit.Success)
|
||||
auditRec.AddMeta("channel", channel)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func channelGroupDisableCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
channel := getChannelFromChannelArg(a, args[0])
|
||||
if channel == nil {
|
||||
return errors.New("Unable to find channel '" + args[0] + "'")
|
||||
}
|
||||
|
||||
channel.GroupConstrained = model.NewBool(false)
|
||||
if _, appErr := a.UpdateChannel(channel); appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("channelGroupDisable", audit.Success)
|
||||
auditRec.AddMeta("channel", channel)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func channelGroupStatusCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
channel := getChannelFromChannelArg(a, args[0])
|
||||
if channel == nil {
|
||||
return errors.New("Unable to find channel '" + args[0] + "'")
|
||||
}
|
||||
|
||||
if channel.IsGroupConstrained() {
|
||||
fmt.Println("Enabled")
|
||||
} else {
|
||||
fmt.Println("Disabled")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func channelGroupListCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
channel := getChannelFromChannelArg(a, args[0])
|
||||
if channel == nil {
|
||||
return errors.New("Unable to find channel '" + args[0] + "'")
|
||||
}
|
||||
|
||||
groups, _, appErr := a.GetGroupsByChannel(channel.Id, model.GroupSearchOpts{})
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
fmt.Println(group.DisplayName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func teamGroupEnableCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
team := getTeamFromTeamArg(a, args[0])
|
||||
if team == nil {
|
||||
return errors.New("Unable to find team '" + args[0] + "'")
|
||||
}
|
||||
|
||||
groups, _, appErr := a.GetGroupsByTeam(team.Id, model.GroupSearchOpts{})
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
if len(groups) == 0 {
|
||||
return errors.New("Team '" + args[0] + "' has no groups associated. It cannot be group-constrained")
|
||||
}
|
||||
|
||||
team.GroupConstrained = model.NewBool(true)
|
||||
if _, appErr = a.UpdateTeam(team); appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("teamGroupEnable", audit.Success)
|
||||
auditRec.AddMeta("team", team)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func teamGroupDisableCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
team := getTeamFromTeamArg(a, args[0])
|
||||
if team == nil {
|
||||
return errors.New("Unable to find team '" + args[0] + "'")
|
||||
}
|
||||
|
||||
team.GroupConstrained = model.NewBool(false)
|
||||
if _, appErr := a.UpdateTeam(team); appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("teamGroupDisable", audit.Success)
|
||||
auditRec.AddMeta("team", team)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func teamGroupStatusCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
team := getTeamFromTeamArg(a, args[0])
|
||||
if team == nil {
|
||||
return errors.New("Unable to find team '" + args[0] + "'")
|
||||
}
|
||||
|
||||
if team.IsGroupConstrained() {
|
||||
fmt.Println("Enabled")
|
||||
} else {
|
||||
fmt.Println("Disabled")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func teamGroupListCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
team := getTeamFromTeamArg(a, args[0])
|
||||
if team == nil {
|
||||
return errors.New("Unable to find team '" + args[0] + "'")
|
||||
}
|
||||
|
||||
groups, _, appErr := a.GetGroupsByTeam(team.Id, model.GroupSearchOpts{})
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
fmt.Println(group.DisplayName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,397 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
func TestChannelGroupEnable(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// create public channel
|
||||
channel := th.CreatePublicChannel()
|
||||
|
||||
// try to enable, should fail it is private
|
||||
require.Error(t, th.RunCommand(t, "group", "channel", "enable", th.BasicTeam.Name+":"+channel.Name))
|
||||
|
||||
channel = th.CreatePrivateChannel()
|
||||
|
||||
// try to enable, should fail because channel has no groups
|
||||
require.Error(t, th.RunCommand(t, "group", "channel", "enable", th.BasicTeam.Name+":"+channel.Name))
|
||||
|
||||
// add group
|
||||
id := model.NewId()
|
||||
group, err := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn_" + id,
|
||||
Name: model.NewString("name" + id),
|
||||
Source: model.GroupSourceLdap,
|
||||
Description: "description_" + id,
|
||||
RemoteId: model.NewId(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
||||
AutoAdd: true,
|
||||
SyncableId: channel.Id,
|
||||
Type: model.GroupSyncableTypeChannel,
|
||||
GroupId: group.Id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
// enabling should succeed now
|
||||
th.CheckCommand(t, "group", "channel", "enable", th.BasicTeam.Name+":"+channel.Name)
|
||||
channel, appErr := th.App.GetChannelByName(channel.Name, th.BasicTeam.Id, false)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, channel.GroupConstrained)
|
||||
require.True(t, *channel.GroupConstrained)
|
||||
|
||||
// try to enable nonexistent channel, should fail
|
||||
require.Error(t, th.RunCommand(t, "group", "channel", "enable", th.BasicTeam.Name+":"+channel.Name+"asdf"))
|
||||
}
|
||||
|
||||
func TestChannelGroupDisable(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// create private channel
|
||||
channel := th.CreatePrivateChannel()
|
||||
|
||||
// try to disable, should work
|
||||
th.CheckCommand(t, "group", "channel", "disable", th.BasicTeam.Name+":"+channel.Name)
|
||||
channel, appErr := th.App.GetChannelByName(channel.Name, th.BasicTeam.Id, false)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, channel.GroupConstrained)
|
||||
require.False(t, *channel.GroupConstrained)
|
||||
|
||||
// add group and enable
|
||||
id := model.NewId()
|
||||
group, err := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn_" + id,
|
||||
Name: model.NewString("name" + id),
|
||||
Source: model.GroupSourceLdap,
|
||||
Description: "description_" + id,
|
||||
RemoteId: model.NewId(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
||||
AutoAdd: true,
|
||||
SyncableId: channel.Id,
|
||||
Type: model.GroupSyncableTypeChannel,
|
||||
GroupId: group.Id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
th.CheckCommand(t, "group", "channel", "enable", th.BasicTeam.Name+":"+channel.Name)
|
||||
channel, appErr = th.App.GetChannelByName(channel.Name, th.BasicTeam.Id, false)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, channel.GroupConstrained)
|
||||
require.True(t, *channel.GroupConstrained)
|
||||
|
||||
// try to disable, should work
|
||||
th.CheckCommand(t, "group", "channel", "disable", th.BasicTeam.Name+":"+channel.Name)
|
||||
channel, appErr = th.App.GetChannelByName(channel.Name, th.BasicTeam.Id, false)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, channel.GroupConstrained)
|
||||
require.False(t, *channel.GroupConstrained)
|
||||
|
||||
// try to disable nonexistent channel, should fail
|
||||
require.Error(t, th.RunCommand(t, "group", "channel", "disable", th.BasicTeam.Name+":"+channel.Name+"asdf"))
|
||||
}
|
||||
|
||||
func TestChannelGroupStatus(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// create private channel
|
||||
channel := th.CreatePrivateChannel()
|
||||
|
||||
// get status, should be Disabled
|
||||
output := th.CheckCommand(t, "group", "channel", "status", th.BasicTeam.Name+":"+channel.Name)
|
||||
require.Contains(t, output, "Disabled")
|
||||
|
||||
// add group and enable
|
||||
id := model.NewId()
|
||||
group, err := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn_" + id,
|
||||
Name: model.NewString("name" + id),
|
||||
Source: model.GroupSourceLdap,
|
||||
Description: "description_" + id,
|
||||
RemoteId: model.NewId(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
||||
AutoAdd: true,
|
||||
SyncableId: channel.Id,
|
||||
Type: model.GroupSyncableTypeChannel,
|
||||
GroupId: group.Id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
th.CheckCommand(t, "group", "channel", "enable", th.BasicTeam.Name+":"+channel.Name)
|
||||
channel, appErr := th.App.GetChannelByName(channel.Name, th.BasicTeam.Id, false)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, channel.GroupConstrained)
|
||||
require.True(t, *channel.GroupConstrained)
|
||||
|
||||
// get status, should be enabled
|
||||
output = th.CheckCommand(t, "group", "channel", "status", th.BasicTeam.Name+":"+channel.Name)
|
||||
require.Contains(t, output, "Enabled")
|
||||
|
||||
// try to get status of nonexistent channel, should fail
|
||||
require.Error(t, th.RunCommand(t, "group", "channel", "status", th.BasicTeam.Name+":"+channel.Name+"asdf"))
|
||||
}
|
||||
|
||||
func TestChannelGroupList(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// create private channel
|
||||
channel := th.CreatePrivateChannel()
|
||||
|
||||
// list groups for a channel with none, should work
|
||||
th.CheckCommand(t, "group", "channel", "list", th.BasicTeam.Name+":"+channel.Name)
|
||||
|
||||
// add groups and enable
|
||||
id1 := model.NewId()
|
||||
g1, err := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn_" + id1,
|
||||
Name: model.NewString("name" + id1),
|
||||
Source: model.GroupSourceLdap,
|
||||
Description: "description_" + id1,
|
||||
RemoteId: model.NewId(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
||||
AutoAdd: true,
|
||||
SyncableId: channel.Id,
|
||||
Type: model.GroupSyncableTypeChannel,
|
||||
GroupId: g1.Id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
id2 := model.NewId()
|
||||
g2, err := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn_" + id2,
|
||||
Name: model.NewString("name" + id2),
|
||||
Source: model.GroupSourceLdap,
|
||||
Description: "description_" + id2,
|
||||
RemoteId: model.NewId(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
||||
AutoAdd: true,
|
||||
SyncableId: channel.Id,
|
||||
Type: model.GroupSyncableTypeChannel,
|
||||
GroupId: g2.Id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
th.CheckCommand(t, "group", "channel", "enable", th.BasicTeam.Name+":"+channel.Name)
|
||||
channel, appErr := th.App.GetChannelByName(channel.Name, th.BasicTeam.Id, false)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, channel.GroupConstrained)
|
||||
require.True(t, *channel.GroupConstrained)
|
||||
|
||||
// list groups
|
||||
output := th.CheckCommand(t, "group", "channel", "list", th.BasicTeam.Name+":"+channel.Name)
|
||||
require.Contains(t, output, g1.DisplayName)
|
||||
require.Contains(t, output, g2.DisplayName)
|
||||
|
||||
// try to get list of nonexistent channel, should fail
|
||||
require.Error(t, th.RunCommand(t, "group", "channel", "list", th.BasicTeam.Name+":"+channel.Name+"asdf"))
|
||||
}
|
||||
|
||||
func TestTeamGroupEnable(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// try to enable, should fail because team has no groups
|
||||
require.Error(t, th.RunCommand(t, "group", "team", "enable", th.BasicTeam.Name))
|
||||
|
||||
// add group
|
||||
id := model.NewId()
|
||||
group, err := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn_" + id,
|
||||
Name: model.NewString("name" + id),
|
||||
Source: model.GroupSourceLdap,
|
||||
Description: "description_" + id,
|
||||
RemoteId: model.NewId(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
||||
AutoAdd: true,
|
||||
SyncableId: th.BasicTeam.Id,
|
||||
Type: model.GroupSyncableTypeTeam,
|
||||
GroupId: group.Id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
// enabling should succeed now
|
||||
th.CheckCommand(t, "group", "team", "enable", th.BasicTeam.Name)
|
||||
team, appErr := th.App.GetTeamByName(th.BasicTeam.Name)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, team.GroupConstrained)
|
||||
require.True(t, *team.GroupConstrained)
|
||||
|
||||
// try to enable nonexistent team, should fail
|
||||
require.Error(t, th.RunCommand(t, "group", "team", "enable", th.BasicTeam.Name+"asdf"))
|
||||
}
|
||||
|
||||
func TestTeamGroupDisable(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// try to disable, should work
|
||||
th.CheckCommand(t, "group", "team", "disable", th.BasicTeam.Name)
|
||||
team, appErr := th.App.GetTeamByName(th.BasicTeam.Name)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, team.GroupConstrained)
|
||||
require.False(t, *team.GroupConstrained)
|
||||
|
||||
// add group and enable
|
||||
id := model.NewId()
|
||||
group, err := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn_" + id,
|
||||
Name: model.NewString("name" + id),
|
||||
Source: model.GroupSourceLdap,
|
||||
Description: "description_" + id,
|
||||
RemoteId: model.NewId(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
||||
AutoAdd: true,
|
||||
SyncableId: team.Id,
|
||||
Type: model.GroupSyncableTypeTeam,
|
||||
GroupId: group.Id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
th.CheckCommand(t, "group", "team", "enable", th.BasicTeam.Name)
|
||||
team, appErr = th.App.GetTeamByName(th.BasicTeam.Name)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, team.GroupConstrained)
|
||||
require.True(t, *team.GroupConstrained)
|
||||
|
||||
// try to disable, should work
|
||||
th.CheckCommand(t, "group", "team", "disable", th.BasicTeam.Name)
|
||||
team, appErr = th.App.GetTeamByName(th.BasicTeam.Name)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, team.GroupConstrained)
|
||||
require.False(t, *team.GroupConstrained)
|
||||
|
||||
// try to disable nonexistent team, should fail
|
||||
require.Error(t, th.RunCommand(t, "group", "team", "disable", th.BasicTeam.Name+"asdf"))
|
||||
}
|
||||
|
||||
func TestTeamGroupStatus(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// get status, should be Disabled
|
||||
output := th.CheckCommand(t, "group", "team", "status", th.BasicTeam.Name)
|
||||
require.Contains(t, output, "Disabled")
|
||||
|
||||
// add group and enable
|
||||
id := model.NewId()
|
||||
group, err := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn_" + id,
|
||||
Name: model.NewString("name" + id),
|
||||
Source: model.GroupSourceLdap,
|
||||
Description: "description_" + id,
|
||||
RemoteId: model.NewId(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
||||
AutoAdd: true,
|
||||
SyncableId: th.BasicTeam.Id,
|
||||
Type: model.GroupSyncableTypeTeam,
|
||||
GroupId: group.Id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
th.CheckCommand(t, "group", "team", "enable", th.BasicTeam.Name)
|
||||
team, appErr := th.App.GetTeamByName(th.BasicTeam.Name)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, team.GroupConstrained)
|
||||
require.True(t, *team.GroupConstrained)
|
||||
|
||||
// get status, should be enabled
|
||||
output = th.CheckCommand(t, "group", "team", "status", th.BasicTeam.Name)
|
||||
require.Contains(t, output, "Enabled")
|
||||
|
||||
// try to get status of nonexistent channel, should fail
|
||||
require.Error(t, th.RunCommand(t, "group", "team", "status", th.BasicTeam.Name+"asdf"))
|
||||
}
|
||||
|
||||
func TestTeamGroupList(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// list groups for a team with none, should work
|
||||
th.CheckCommand(t, "group", "team", "list", th.BasicTeam.Name)
|
||||
|
||||
// add groups and enable
|
||||
id1 := model.NewId()
|
||||
g1, err := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn_" + id1,
|
||||
Name: model.NewString("name" + id1),
|
||||
Source: model.GroupSourceLdap,
|
||||
Description: "description_" + id1,
|
||||
RemoteId: model.NewId(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
||||
AutoAdd: true,
|
||||
SyncableId: th.BasicTeam.Id,
|
||||
Type: model.GroupSyncableTypeTeam,
|
||||
GroupId: g1.Id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
id2 := model.NewId()
|
||||
g2, err := th.App.CreateGroup(&model.Group{
|
||||
DisplayName: "dn_" + id2,
|
||||
Name: model.NewString("name" + id2),
|
||||
Source: model.GroupSourceLdap,
|
||||
Description: "description_" + id2,
|
||||
RemoteId: model.NewId(),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
_, err = th.App.UpsertGroupSyncable(&model.GroupSyncable{
|
||||
AutoAdd: true,
|
||||
SyncableId: th.BasicTeam.Id,
|
||||
Type: model.GroupSyncableTypeTeam,
|
||||
GroupId: g2.Id,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
th.CheckCommand(t, "group", "team", "enable", th.BasicTeam.Name)
|
||||
team, appErr := th.App.GetTeamByName(th.BasicTeam.Name)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, team.GroupConstrained)
|
||||
require.True(t, *team.GroupConstrained)
|
||||
|
||||
// list groups
|
||||
output := th.CheckCommand(t, "group", "team", "list", th.BasicTeam.Name)
|
||||
require.Contains(t, output, g1.DisplayName)
|
||||
require.Contains(t, output, g2.DisplayName)
|
||||
|
||||
// try to get list of nonexistent team, should fail
|
||||
require.Error(t, th.RunCommand(t, "group", "team", "list", th.BasicTeam.Name+"asdf"))
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
)
|
||||
|
||||
var ImportCmd = &cobra.Command{
|
||||
Use: "import",
|
||||
Short: "Import data.",
|
||||
}
|
||||
|
||||
var SlackImportCmd = &cobra.Command{
|
||||
Use: "slack [team] [file]",
|
||||
Short: "Import a team from Slack.",
|
||||
Long: "Import a team from a Slack export zip file.",
|
||||
Example: " import slack myteam slack_export.zip",
|
||||
RunE: slackImportCmdF,
|
||||
}
|
||||
|
||||
var BulkImportCmd = &cobra.Command{
|
||||
Use: "bulk [file]",
|
||||
Short: "Import bulk data.",
|
||||
Long: "Import data from a Mattermost Bulk Import File.",
|
||||
Example: " import bulk bulk_data.json",
|
||||
RunE: bulkImportCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
BulkImportCmd.Flags().Bool("apply", false, "Save the import data to the database. Use with caution - this cannot be reverted.")
|
||||
BulkImportCmd.Flags().Bool("validate", false, "Validate the import data without making any changes to the system.")
|
||||
BulkImportCmd.Flags().Int("workers", 2, "How many workers to run whilst doing the import.")
|
||||
BulkImportCmd.Flags().String("import-path", "", "A path to the data directory to import files from.")
|
||||
|
||||
ImportCmd.AddCommand(
|
||||
BulkImportCmd,
|
||||
SlackImportCmd,
|
||||
)
|
||||
RootCmd.AddCommand(ImportCmd)
|
||||
}
|
||||
|
||||
func slackImportCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
if len(args) != 2 {
|
||||
return errors.New("Incorrect number of arguments.")
|
||||
}
|
||||
|
||||
team := getTeamFromTeamArg(a, args[0])
|
||||
if team == nil {
|
||||
return errors.New("Unable to find team '" + args[0] + "'")
|
||||
}
|
||||
|
||||
fileReader, err := os.Open(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileReader.Close()
|
||||
|
||||
fileInfo, err := fileReader.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
CommandPrettyPrintln("Running Slack Import. This may take a long time for large teams or teams with many messages.")
|
||||
|
||||
importErr, log := a.SlackImport(&request.Context{}, fileReader, fileInfo.Size(), team.Id)
|
||||
|
||||
if importErr != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
CommandPrettyPrintln("")
|
||||
CommandPrintln(log.String())
|
||||
CommandPrettyPrintln("")
|
||||
|
||||
CommandPrettyPrintln("Finished Slack Import.")
|
||||
CommandPrettyPrintln("")
|
||||
|
||||
auditRec := a.MakeAuditRecord("slackImport", audit.Success)
|
||||
auditRec.AddMeta("team", team)
|
||||
auditRec.AddMeta("file", args[1])
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func bulkImportCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
apply, err := command.Flags().GetBool("apply")
|
||||
if err != nil {
|
||||
return errors.New("Apply flag error")
|
||||
}
|
||||
|
||||
validate, err := command.Flags().GetBool("validate")
|
||||
if err != nil {
|
||||
return errors.New("Validate flag error")
|
||||
}
|
||||
|
||||
workers, err := command.Flags().GetInt("workers")
|
||||
if err != nil {
|
||||
return errors.New("Workers flag error")
|
||||
}
|
||||
|
||||
importPath, err := command.Flags().GetString("import-path")
|
||||
if err != nil {
|
||||
return errors.New("import-path flag error")
|
||||
}
|
||||
|
||||
if len(args) != 1 {
|
||||
return errors.New("Incorrect number of arguments.")
|
||||
}
|
||||
|
||||
fileReader, err := os.Open(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileReader.Close()
|
||||
|
||||
if apply && validate {
|
||||
CommandPrettyPrintln("Use only one of --apply or --validate.")
|
||||
return nil
|
||||
}
|
||||
|
||||
if apply && !validate {
|
||||
CommandPrettyPrintln("Running Bulk Import. This may take a long time.")
|
||||
} else {
|
||||
CommandPrettyPrintln("Running Bulk Import Data Validation.")
|
||||
CommandPrettyPrintln("** This checks the validity of the entities in the data file, but does not persist any changes **")
|
||||
CommandPrettyPrintln("Use the --apply flag to perform the actual data import.")
|
||||
}
|
||||
|
||||
CommandPrettyPrintln("")
|
||||
|
||||
if err, lineNumber := a.BulkImportWithPath(&request.Context{}, fileReader, nil, !apply, workers, importPath); err != nil {
|
||||
CommandPrintErrorln(err.Error())
|
||||
if lineNumber != 0 {
|
||||
CommandPrintErrorln(fmt.Sprintf("Error occurred on data file line %v", lineNumber))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if apply {
|
||||
CommandPrettyPrintln("Finished Bulk Import.")
|
||||
auditRec := a.MakeAuditRecord("bulkImport", audit.Success)
|
||||
auditRec.AddMeta("file", args[0])
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
} else {
|
||||
CommandPrettyPrintln("Validation complete. You can now perform the import by rerunning this command with the --apply flag.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
var IntegrityCmd = &cobra.Command{
|
||||
Use: "integrity",
|
||||
Short: "Check database data integrity",
|
||||
RunE: integrityCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
IntegrityCmd.Flags().Bool("confirm", false, "Confirm you really want to run a complete integrity check that may temporarily harm system performance")
|
||||
IntegrityCmd.Flags().BoolP("verbose", "v", false, "Show detailed information on integrity check results")
|
||||
RootCmd.AddCommand(IntegrityCmd)
|
||||
}
|
||||
|
||||
func printRelationalIntegrityCheckResult(data model.RelationalIntegrityCheckData, verbose bool) {
|
||||
fmt.Printf("Found %d records in relation %s orphans of relation %s\n",
|
||||
len(data.Records), data.ChildName, data.ParentName)
|
||||
if !verbose {
|
||||
return
|
||||
}
|
||||
for _, record := range data.Records {
|
||||
var parentId string
|
||||
|
||||
if record.ParentId == nil {
|
||||
parentId = "NULL"
|
||||
} else if *record.ParentId == "" {
|
||||
parentId = "empty"
|
||||
} else {
|
||||
parentId = *record.ParentId
|
||||
}
|
||||
|
||||
if record.ChildId != nil {
|
||||
if parentId == "NULL" || parentId == "empty" {
|
||||
fmt.Printf(" Child %s (%s.%s) has %s ParentIdAttr (%s.%s)\n", *record.ChildId, data.ChildName, data.ChildIdAttr, parentId, data.ChildName, data.ParentIdAttr)
|
||||
} else {
|
||||
fmt.Printf(" Child %s (%s.%s) is missing Parent %s (%s.%s)\n", *record.ChildId, data.ChildName, data.ChildIdAttr, parentId, data.ChildName, data.ParentIdAttr)
|
||||
}
|
||||
} else {
|
||||
if parentId == "NULL" || parentId == "empty" {
|
||||
fmt.Printf(" Child has %s ParentIdAttr (%s.%s)\n", parentId, data.ChildName, data.ParentIdAttr)
|
||||
} else {
|
||||
fmt.Printf(" Child is missing Parent %s (%s.%s)\n", parentId, data.ChildName, data.ParentIdAttr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printIntegrityCheckResult(result model.IntegrityCheckResult, verbose bool) {
|
||||
switch data := result.Data.(type) {
|
||||
case model.RelationalIntegrityCheckData:
|
||||
printRelationalIntegrityCheckResult(data, verbose)
|
||||
}
|
||||
}
|
||||
|
||||
func integrityCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
confirmFlag, _ := command.Flags().GetBool("confirm")
|
||||
if !confirmFlag {
|
||||
var confirm string
|
||||
fmt.Fprintf(os.Stdout, "This check may harm performance on live systems. Are you sure you want to proceed? (y/N): ")
|
||||
fmt.Scanln(&confirm)
|
||||
if !strings.EqualFold(confirm, "y") && !strings.EqualFold(confirm, "yes") {
|
||||
fmt.Fprintf(os.Stderr, "Aborted.\n")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
verboseFlag, _ := command.Flags().GetBool("verbose")
|
||||
results := a.Srv().Store.CheckIntegrity()
|
||||
for result := range results {
|
||||
if result.Err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", result.Err.Error())
|
||||
break
|
||||
}
|
||||
printIntegrityCheckResult(result, verboseFlag)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/config"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
|
||||
var JobserverCmd = &cobra.Command{
|
||||
Use: "jobserver",
|
||||
Short: "Start the Mattermost job server",
|
||||
RunE: jobserverCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
JobserverCmd.Flags().Bool("nojobs", false, "Do not run jobs on this jobserver.")
|
||||
JobserverCmd.Flags().Bool("noschedule", false, "Do not schedule jobs from this jobserver.")
|
||||
|
||||
RootCmd.AddCommand(JobserverCmd)
|
||||
}
|
||||
|
||||
func jobserverCmdF(command *cobra.Command, args []string) error {
|
||||
// Options
|
||||
noJobs, _ := command.Flags().GetBool("nojobs")
|
||||
noSchedule, _ := command.Flags().GetBool("noschedule")
|
||||
|
||||
// Initialize
|
||||
a, err := initDBCommandContext(getConfigDSN(command, config.GetEnvironment()), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
a.Srv().LoadLicense()
|
||||
|
||||
// Run jobs
|
||||
mlog.Info("Starting Mattermost job server")
|
||||
defer mlog.Info("Stopped Mattermost job server")
|
||||
|
||||
if !noJobs {
|
||||
a.Srv().Jobs.StartWorkers()
|
||||
defer a.Srv().Jobs.StopWorkers()
|
||||
}
|
||||
if !noSchedule {
|
||||
a.Srv().Jobs.StartSchedulers()
|
||||
defer a.Srv().Jobs.StopSchedulers()
|
||||
}
|
||||
|
||||
if !noJobs || !noSchedule {
|
||||
auditRec := a.MakeAuditRecord("jobServer", audit.Success)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-signalChan
|
||||
|
||||
// Cleanup anything that isn't handled by a defer statement
|
||||
mlog.Info("Stopping Mattermost job server")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
var LdapCmd = &cobra.Command{
|
||||
Use: "ldap",
|
||||
Short: "LDAP related utilities",
|
||||
}
|
||||
|
||||
var LdapSyncCmd = &cobra.Command{
|
||||
Use: "sync",
|
||||
Short: "Synchronize now",
|
||||
Long: "Synchronize all LDAP users now.",
|
||||
Example: " ldap sync",
|
||||
RunE: ldapSyncCmdF,
|
||||
}
|
||||
|
||||
var LdapIdMigrate = &cobra.Command{
|
||||
Use: "idmigrate",
|
||||
Short: "Migrate LDAP IdAttribute to new value",
|
||||
Long: "Migrate LDAP IdAttribute to new value. Run this utility then change the IdAttribute to the new value.",
|
||||
Example: " ldap idmigrate objectGUID",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: ldapIdMigrateCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
LdapSyncCmd.Flags().Bool("include-removed-members", false, "Include members who left or were removed from a group-synced team/channel")
|
||||
LdapCmd.AddCommand(
|
||||
LdapSyncCmd,
|
||||
LdapIdMigrate,
|
||||
)
|
||||
RootCmd.AddCommand(LdapCmd)
|
||||
}
|
||||
|
||||
func ldapSyncCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
includeRemovedMembers, _ := command.Flags().GetBool("include-removed-members")
|
||||
|
||||
if ldapI := a.Ldap(); ldapI != nil {
|
||||
job, err := ldapI.StartSynchronizeJob(true, includeRemovedMembers)
|
||||
if err != nil || job.Status == model.JobStatusError || job.Status == model.JobStatusCanceled {
|
||||
CommandPrintErrorln("ERROR: AD/LDAP Synchronization please check the server logs")
|
||||
} else {
|
||||
CommandPrettyPrintln("SUCCESS: AD/LDAP Synchronization Complete")
|
||||
auditRec := a.MakeAuditRecord("ldapSync", audit.Success)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ldapIdMigrateCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
toAttribute := args[0]
|
||||
if ldapI := a.Ldap(); ldapI != nil {
|
||||
if err := ldapI.MigrateIDAttribute(toAttribute); err != nil {
|
||||
CommandPrintErrorln("ERROR: AD/LDAP IdAttribute migration failed! Error: " + err.Error())
|
||||
} else {
|
||||
CommandPrettyPrintln("SUCCESS: AD/LDAP IdAttribute migration complete. You can now change your IdAttribute to: " + toAttribute)
|
||||
auditRec := a.MakeAuditRecord("ldapMigrate", audit.Success)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
)
|
||||
|
||||
var LicenseCmd = &cobra.Command{
|
||||
Use: "license",
|
||||
Short: "Licensing commands",
|
||||
}
|
||||
|
||||
var UploadLicenseCmd = &cobra.Command{
|
||||
Use: "upload [license]",
|
||||
Short: "Upload a license.",
|
||||
Long: "Upload a license. Replaces current license.",
|
||||
Example: " license upload /path/to/license/mylicensefile.mattermost-license",
|
||||
RunE: uploadLicenseCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
LicenseCmd.AddCommand(UploadLicenseCmd)
|
||||
RootCmd.AddCommand(LicenseCmd)
|
||||
}
|
||||
|
||||
func uploadLicenseCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
if len(args) != 1 {
|
||||
return errors.New("Enter one license file to upload")
|
||||
}
|
||||
|
||||
var fileBytes []byte
|
||||
if fileBytes, err = ioutil.ReadFile(args[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := a.Srv().SaveLicense(fileBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
CommandPrettyPrintln("Uploaded license file")
|
||||
|
||||
auditRec := a.MakeAuditRecord("uploadLicense", audit.Success)
|
||||
auditRec.AddMeta("file", args[0])
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -9,9 +9,74 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/api4"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/testlib"
|
||||
)
|
||||
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
type TestConfig struct {
|
||||
TestServiceSettings TestServiceSettings
|
||||
TestTeamSettings TestTeamSettings
|
||||
TestClientRequirements TestClientRequirements
|
||||
TestMessageExportSettings TestMessageExportSettings
|
||||
}
|
||||
|
||||
type TestMessageExportSettings struct {
|
||||
Enableexport bool
|
||||
Exportformat string
|
||||
TestGlobalRelaySettings TestGlobalRelaySettings
|
||||
}
|
||||
|
||||
type TestGlobalRelaySettings struct {
|
||||
Customertype string
|
||||
Smtpusername string
|
||||
Smtppassword string
|
||||
}
|
||||
|
||||
type TestServiceSettings struct {
|
||||
Siteurl string
|
||||
Websocketurl string
|
||||
Licensedfieldlocation string
|
||||
}
|
||||
|
||||
type TestTeamSettings struct {
|
||||
Sitename string
|
||||
Maxuserperteam int
|
||||
}
|
||||
|
||||
type TestClientRequirements struct {
|
||||
Androidlatestversion string
|
||||
Androidminversion string
|
||||
Desktoplatestversion string
|
||||
}
|
||||
|
||||
type TestNewConfig struct {
|
||||
TestNewServiceSettings TestNewServiceSettings
|
||||
TestNewTeamSettings TestNewTeamSettings
|
||||
}
|
||||
|
||||
type TestNewServiceSettings struct {
|
||||
SiteUrl *string
|
||||
UseLetsEncrypt *bool
|
||||
TLSStrictTransportMaxAge *int64
|
||||
AllowedThemes []string
|
||||
}
|
||||
|
||||
type TestNewTeamSettings struct {
|
||||
SiteName *string
|
||||
MaxUserPerTeam *int
|
||||
}
|
||||
|
||||
type TestPluginSettings struct {
|
||||
Enable *bool
|
||||
Directory *string `restricted:"true"`
|
||||
Plugins map[string]map[string]interface{}
|
||||
PluginStates map[string]*model.PluginState
|
||||
SignaturePublicKeyFiles []string
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Command tests are run by re-invoking the test binary in question, so avoid creating
|
||||
// another container when we detect same.
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/i18n"
|
||||
)
|
||||
|
||||
var PermissionsCmd = &cobra.Command{
|
||||
Use: "permissions",
|
||||
Short: "Management of the Permissions system",
|
||||
}
|
||||
|
||||
var ResetPermissionsCmd = &cobra.Command{
|
||||
Use: "reset",
|
||||
Short: "Reset the permissions system to its default state",
|
||||
Long: "Reset the permissions system to its default state",
|
||||
Example: " permissions reset",
|
||||
RunE: resetPermissionsCmdF,
|
||||
}
|
||||
|
||||
var ExportPermissionsCmd = &cobra.Command{
|
||||
Use: "export",
|
||||
Short: "Export permissions data",
|
||||
Long: "Export Roles and Schemes to JSONL for use by Mattermost permissions import.",
|
||||
Example: " permissions export > export.jsonl",
|
||||
RunE: exportPermissionsCmdF,
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
os.Setenv("MM_LOGSETTINGS_CONSOLELEVEL", "error")
|
||||
},
|
||||
}
|
||||
|
||||
var ImportPermissionsCmd = &cobra.Command{
|
||||
Use: "import [file]",
|
||||
Short: "Import permissions data",
|
||||
Long: "Import Roles and Schemes JSONL data as created by the Mattermost permissions export.",
|
||||
Example: " permissions import export.jsonl",
|
||||
RunE: importPermissionsCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
ResetPermissionsCmd.Flags().Bool("confirm", false, "Confirm you really want to reset the permissions system and a database backup has been performed.")
|
||||
|
||||
PermissionsCmd.AddCommand(
|
||||
ResetPermissionsCmd,
|
||||
ExportPermissionsCmd,
|
||||
ImportPermissionsCmd,
|
||||
)
|
||||
RootCmd.AddCommand(PermissionsCmd)
|
||||
}
|
||||
|
||||
func resetPermissionsCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
confirmFlag, _ := command.Flags().GetBool("confirm")
|
||||
if !confirmFlag {
|
||||
var confirm string
|
||||
CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
|
||||
fmt.Scanln(&confirm)
|
||||
|
||||
if confirm != "YES" {
|
||||
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
|
||||
}
|
||||
CommandPrettyPrintln("Are you sure you want to reset the permissions system? All data related to the permissions system will be permanently deleted and all users will revert to having the default permissions. (YES/NO): ")
|
||||
fmt.Scanln(&confirm)
|
||||
if confirm != "YES" {
|
||||
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
|
||||
}
|
||||
}
|
||||
|
||||
if err := a.ResetPermissionsSystem(); err != nil {
|
||||
return errors.New(err.Error())
|
||||
}
|
||||
|
||||
CommandPrettyPrintln("Permissions system successfully reset.")
|
||||
CommandPrettyPrintln("Changes will take effect gradually as the server caches expire.")
|
||||
CommandPrettyPrintln("For the changes to take effect immediately, go to the Mattermost System Console > General > Configuration and click \"Purge All Caches\".")
|
||||
|
||||
auditRec := a.MakeAuditRecord("resetPermissions", audit.Success)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func exportPermissionsCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
if license := a.Srv().License(); license == nil {
|
||||
return errors.New(i18n.T("cli.license.critical"))
|
||||
}
|
||||
|
||||
if err = a.ExportPermissions(os.Stdout); err != nil {
|
||||
return errors.New(err.Error())
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("exportPermissions", audit.Success)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func importPermissionsCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
if license := a.Srv().License(); license == nil {
|
||||
return errors.New(i18n.T("cli.license.critical"))
|
||||
}
|
||||
|
||||
file, err := os.Open(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
auditRec := a.MakeAuditRecord("importPermissions", audit.Success)
|
||||
auditRec.AddMeta("file", args[0])
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return a.ImportPermissions(file)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/shared/i18n"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPermissionsExport_rejectsUnlicensed(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
actual, _ := th.RunCommandWithOutput(t, "permissions", "export")
|
||||
assert.Contains(t, actual, i18n.T("cli.license.critical"))
|
||||
}
|
||||
|
||||
func TestPermissionsImport_rejectsUnlicensed(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
actual, _ := th.RunCommandWithOutput(t, "permissions", "import")
|
||||
|
||||
assert.Contains(t, actual, i18n.T("cli.license.critical"))
|
||||
}
|
||||
@@ -1,320 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
var PluginCmd = &cobra.Command{
|
||||
Use: "plugin",
|
||||
Short: "Management of plugins",
|
||||
}
|
||||
|
||||
var PluginAddCmd = &cobra.Command{
|
||||
Use: "add [plugins]",
|
||||
Short: "Add plugins",
|
||||
Long: "Add plugins to your Mattermost server.",
|
||||
Example: ` plugin add hovercardexample.tar.gz pluginexample.tar.gz`,
|
||||
RunE: pluginAddCmdF,
|
||||
}
|
||||
|
||||
var PluginDeleteCmd = &cobra.Command{
|
||||
Use: "delete [plugins]",
|
||||
Short: "Delete plugins",
|
||||
Long: "Delete previously uploaded plugins from your Mattermost server.",
|
||||
Example: ` plugin delete hovercardexample pluginexample`,
|
||||
RunE: pluginDeleteCmdF,
|
||||
}
|
||||
|
||||
var PluginEnableCmd = &cobra.Command{
|
||||
Use: "enable [plugins]",
|
||||
Short: "Enable plugins",
|
||||
Long: "Enable plugins for use on your Mattermost server.",
|
||||
Example: ` plugin enable hovercardexample pluginexample`,
|
||||
RunE: pluginEnableCmdF,
|
||||
}
|
||||
|
||||
var PluginDisableCmd = &cobra.Command{
|
||||
Use: "disable [plugins]",
|
||||
Short: "Disable plugins",
|
||||
Long: "Disable plugins. Disabled plugins are immediately removed from the user interface and logged out of all sessions.",
|
||||
Example: ` plugin disable hovercardexample pluginexample`,
|
||||
RunE: pluginDisableCmdF,
|
||||
}
|
||||
|
||||
var PluginListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List plugins",
|
||||
Long: "List all enabled and disabled plugins installed on your Mattermost server.",
|
||||
Example: ` plugin list`,
|
||||
RunE: pluginListCmdF,
|
||||
}
|
||||
|
||||
var PluginPublicKeysCmd = &cobra.Command{
|
||||
Use: "keys",
|
||||
Short: "List public keys",
|
||||
Long: "List names of all public keys installed on your Mattermost server.",
|
||||
Example: ` plugin keys
|
||||
plugin keys --verbose`,
|
||||
RunE: pluginPublicKeysCmdF,
|
||||
}
|
||||
|
||||
var PluginAddPublicKeyCmd = &cobra.Command{
|
||||
Use: "add [keys]",
|
||||
Short: "Adds public key(s)",
|
||||
Long: "Adds public key(s) for plugins on your Mattermost server.",
|
||||
Example: ` plugin keys add my-pk-file1 my-pk-file2`,
|
||||
RunE: pluginAddPublicKeyCmdF,
|
||||
}
|
||||
|
||||
var PluginDeletePublicKeyCmd = &cobra.Command{
|
||||
Use: "delete [keys]",
|
||||
Short: "Deletes public key(s)",
|
||||
Long: "Deletes public key(s) for plugins on your Mattermost server.",
|
||||
Example: ` plugin keys delete my-pk-file1 my-pk-file2`,
|
||||
RunE: pluginDeletePublicKeyCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
PluginPublicKeysCmd.Flags().Bool("verbose", false, "List names and details of all public keys installed on your Mattermost server.")
|
||||
PluginPublicKeysCmd.AddCommand(
|
||||
PluginAddPublicKeyCmd,
|
||||
PluginDeletePublicKeyCmd,
|
||||
)
|
||||
PluginCmd.AddCommand(
|
||||
PluginAddCmd,
|
||||
PluginDeleteCmd,
|
||||
PluginEnableCmd,
|
||||
PluginDisableCmd,
|
||||
PluginListCmd,
|
||||
PluginPublicKeysCmd,
|
||||
)
|
||||
|
||||
RootCmd.AddCommand(PluginCmd)
|
||||
}
|
||||
|
||||
func pluginAddCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobraReadWrite(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.New("Expected at least one argument. See help text for details.")
|
||||
}
|
||||
|
||||
for i, plugin := range args {
|
||||
fileReader, err := os.Open(plugin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := a.InstallPlugin(fileReader, false); err != nil {
|
||||
CommandPrintErrorln("Unable to add plugin: " + args[i] + ". Error: " + err.Error())
|
||||
} else {
|
||||
CommandPrettyPrintln("Added plugin: " + plugin)
|
||||
auditRec := a.MakeAuditRecord("pluginAdd", audit.Success)
|
||||
auditRec.AddMeta("plugin", plugin)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
fileReader.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pluginDeleteCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobraReadWrite(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.New("Expected at least one argument. See help text for details.")
|
||||
}
|
||||
|
||||
for _, plugin := range args {
|
||||
if err := a.RemovePlugin(plugin); err != nil {
|
||||
CommandPrintErrorln("Unable to delete plugin: " + plugin + ". Error: " + err.Error())
|
||||
} else {
|
||||
CommandPrettyPrintln("Deleted plugin: " + plugin)
|
||||
auditRec := a.MakeAuditRecord("pluginDelete", audit.Success)
|
||||
auditRec.AddMeta("plugin", plugin)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pluginEnableCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobraReadWrite(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.New("Expected at least one argument. See help text for details.")
|
||||
}
|
||||
|
||||
for _, plugin := range args {
|
||||
if err := a.EnablePlugin(plugin); err != nil {
|
||||
CommandPrintErrorln("Unable to enable plugin: " + plugin + ". Error: " + err.Error())
|
||||
} else {
|
||||
CommandPrettyPrintln("Enabled plugin: " + plugin)
|
||||
auditRec := a.MakeAuditRecord("pluginEnable", audit.Success)
|
||||
auditRec.AddMeta("plugin", plugin)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pluginDisableCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobraReadWrite(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.New("Expected at least one argument. See help text for details.")
|
||||
}
|
||||
|
||||
for _, plugin := range args {
|
||||
if err := a.DisablePlugin(plugin); err != nil {
|
||||
CommandPrintErrorln("Unable to disable plugin: " + plugin + ". Error: " + err.Error())
|
||||
} else {
|
||||
CommandPrettyPrintln("Disabled plugin: " + plugin)
|
||||
auditRec := a.MakeAuditRecord("pluginDisable", audit.Success)
|
||||
auditRec.AddMeta("plugin", plugin)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pluginListCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
pluginsResp, appErr := a.GetPlugins()
|
||||
if appErr != nil {
|
||||
return errors.Wrap(appErr, "Unable to list plugins.")
|
||||
}
|
||||
|
||||
CommandPrettyPrintln("Listing enabled plugins")
|
||||
for _, plugin := range pluginsResp.Active {
|
||||
CommandPrettyPrintln(plugin.Manifest.Name + ", Version: " + plugin.Manifest.Version)
|
||||
}
|
||||
|
||||
CommandPrettyPrintln("Listing disabled plugins")
|
||||
for _, plugin := range pluginsResp.Inactive {
|
||||
CommandPrettyPrintln(plugin.Manifest.Name + ", Version: " + plugin.Manifest.Version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pluginPublicKeysCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
verbose, err := command.Flags().GetBool("verbose")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed reading verbose flag.")
|
||||
}
|
||||
|
||||
pluginPublicKeysResp, appErr := a.GetPluginPublicKeyFiles()
|
||||
if appErr != nil {
|
||||
return errors.Wrap(appErr, "Unable to list public keys.")
|
||||
}
|
||||
|
||||
if verbose {
|
||||
for _, publicKey := range pluginPublicKeysResp {
|
||||
key, err := a.GetPublicKey(publicKey)
|
||||
if err != nil {
|
||||
CommandPrintErrorln("Unable to get plugin public key: " + publicKey + ". Error: " + err.Error())
|
||||
}
|
||||
CommandPrettyPrintln("Plugin name: " + publicKey + ". \nPublic key: \n" + string(key) + "\n")
|
||||
}
|
||||
} else {
|
||||
for _, publicKey := range pluginPublicKeysResp {
|
||||
CommandPrettyPrintln(publicKey)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pluginAddPublicKeyCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobraReadWrite(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.New("Expected at least one argument. See help text for details.")
|
||||
}
|
||||
|
||||
for _, pkFile := range args {
|
||||
filename := filepath.Base(pkFile)
|
||||
fileReader, err := os.Open(pkFile)
|
||||
if err != nil {
|
||||
return model.NewAppError("AddPublicKey", "api.plugin.add_public_key.open.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
defer fileReader.Close()
|
||||
|
||||
if err := a.AddPublicKey(filename, fileReader); err != nil {
|
||||
CommandPrintErrorln("Unable to add public key: " + pkFile + ". Error: " + err.Error())
|
||||
} else {
|
||||
CommandPrettyPrintln("Added public key: " + pkFile)
|
||||
auditRec := a.MakeAuditRecord("pluginAddPublicKey", audit.Success)
|
||||
auditRec.AddMeta("file", pkFile)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pluginDeletePublicKeyCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobraReadWrite(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.New("Expected at least one argument. See help text for details.")
|
||||
}
|
||||
|
||||
for _, pkFile := range args {
|
||||
if err := a.DeletePublicKey(pkFile); err != nil {
|
||||
CommandPrintErrorln("Unable to delete public key: " + pkFile + ". Error: " + err.Error())
|
||||
} else {
|
||||
CommandPrettyPrintln("Deleted public key: " + pkFile)
|
||||
auditRec := a.MakeAuditRecord("pluginDeletePublicKey", audit.Success)
|
||||
auditRec.AddMeta("file", pkFile)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/config"
|
||||
"github.com/mattermost/mattermost-server/v6/utils/fileutils"
|
||||
)
|
||||
|
||||
func TestPlugin(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
cfg := th.Config()
|
||||
*cfg.PluginSettings.EnableUploads = true
|
||||
*cfg.PluginSettings.Directory = "./test-plugins"
|
||||
*cfg.PluginSettings.ClientDirectory = "./test-client-plugins"
|
||||
th.SetConfig(cfg)
|
||||
|
||||
err := os.MkdirAll("./test-plugins", os.ModePerm)
|
||||
require.NoError(t, err)
|
||||
err = os.MkdirAll("./test-client-plugins", os.ModePerm)
|
||||
require.NoError(t, err)
|
||||
|
||||
path, _ := fileutils.FindDir("tests")
|
||||
|
||||
output := th.CheckCommand(t, "plugin", "add", filepath.Join(path, "testplugin.tar.gz"))
|
||||
assert.Contains(t, output, "Added plugin:")
|
||||
output = th.CheckCommand(t, "plugin", "enable", "testplugin")
|
||||
assert.Contains(t, output, "Enabled plugin: testplugin")
|
||||
|
||||
fs, err := config.NewFileStore(th.ConfigPath())
|
||||
require.NoError(t, err)
|
||||
cfsStore, err := config.NewStoreFromBacking(fs, nil, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cfsStore.Get().PluginSettings.PluginStates["testplugin"])
|
||||
assert.True(t, cfsStore.Get().PluginSettings.PluginStates["testplugin"].Enable)
|
||||
cfsStore.Close()
|
||||
|
||||
output = th.CheckCommand(t, "plugin", "disable", "testplugin")
|
||||
assert.Contains(t, output, "Disabled plugin: testplugin")
|
||||
fs, err = config.NewFileStore(th.ConfigPath())
|
||||
require.NoError(t, err)
|
||||
cfsStore, err = config.NewStoreFromBacking(fs, nil, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cfsStore.Get().PluginSettings.PluginStates["testplugin"])
|
||||
assert.False(t, cfsStore.Get().PluginSettings.PluginStates["testplugin"].Enable)
|
||||
cfsStore.Close()
|
||||
|
||||
th.CheckCommand(t, "plugin", "list")
|
||||
|
||||
th.CheckCommand(t, "plugin", "delete", "testplugin")
|
||||
}
|
||||
|
||||
func TestPluginPublicKeys(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
cfg := th.Config()
|
||||
cfg.PluginSettings.SignaturePublicKeyFiles = []string{"public-key"}
|
||||
th.SetConfig(cfg)
|
||||
|
||||
output := th.CheckCommand(t, "plugin", "keys")
|
||||
assert.Contains(t, output, "public-key")
|
||||
assert.NotContains(t, output, "Plugin name:")
|
||||
}
|
||||
|
||||
func TestPluginPublicKeyDetails(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
cfg := th.Config()
|
||||
cfg.PluginSettings.SignaturePublicKeyFiles = []string{"public-key"}
|
||||
|
||||
th.SetConfig(cfg)
|
||||
|
||||
output := th.CheckCommand(t, "plugin", "keys", "--verbose", "true")
|
||||
assert.Contains(t, output, "Plugin name: public-key")
|
||||
output = th.CheckCommand(t, "plugin", "keys", "--verbose")
|
||||
assert.Contains(t, output, "Plugin name: public-key")
|
||||
}
|
||||
|
||||
func TestAddPluginPublicKeys(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
cfg := th.Config()
|
||||
cfg.PluginSettings.SignaturePublicKeyFiles = []string{"public-key"}
|
||||
th.SetConfig(cfg)
|
||||
|
||||
err := th.RunCommand(t, "plugin", "keys", "add", "pk1")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDeletePluginPublicKeys(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
cfg := th.Config()
|
||||
cfg.PluginSettings.SignaturePublicKeyFiles = []string{"pk1"}
|
||||
th.SetConfig(cfg)
|
||||
|
||||
output := th.CheckCommand(t, "plugin", "keys", "delete", "pk1")
|
||||
assert.Contains(t, output, "Deleted public key: pk1")
|
||||
}
|
||||
|
||||
func TestPluginPublicKeysFlow(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
path, _ := fileutils.FindDir("tests")
|
||||
name := "test-public-key.plugin.gpg"
|
||||
output := th.CheckCommand(t, "plugin", "keys", "add", filepath.Join(path, name))
|
||||
assert.Contains(t, output, "Added public key: "+filepath.Join(path, name))
|
||||
|
||||
output = th.CheckCommand(t, "plugin", "keys")
|
||||
assert.Contains(t, output, name)
|
||||
assert.NotContains(t, output, "Plugin name:")
|
||||
|
||||
output = th.CheckCommand(t, "plugin", "keys", "--verbose")
|
||||
assert.Contains(t, output, "Plugin name: "+name)
|
||||
|
||||
output = th.CheckCommand(t, "plugin", "keys", "delete", name)
|
||||
assert.Contains(t, output, "Deleted public key: "+name)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
)
|
||||
|
||||
var ResetCmd = &cobra.Command{
|
||||
Use: "reset",
|
||||
Short: "Reset the database to initial state",
|
||||
Long: "Completely erases the database causing the loss of all data. This will reset Mattermost to its initial state.",
|
||||
RunE: resetCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
ResetCmd.Flags().Bool("confirm", false, "Confirm you really want to delete everything and a DB backup has been performed.")
|
||||
|
||||
RootCmd.AddCommand(ResetCmd)
|
||||
}
|
||||
|
||||
func resetCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
confirmFlag, _ := command.Flags().GetBool("confirm")
|
||||
if !confirmFlag {
|
||||
var confirm string
|
||||
CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
|
||||
fmt.Scanln(&confirm)
|
||||
|
||||
if confirm != "YES" {
|
||||
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
|
||||
}
|
||||
CommandPrettyPrintln("Are you sure you want to delete everything? All data will be permanently deleted? (YES/NO): ")
|
||||
fmt.Scanln(&confirm)
|
||||
if confirm != "YES" {
|
||||
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
|
||||
}
|
||||
}
|
||||
|
||||
a.Srv().Store.DropAllTables()
|
||||
CommandPrettyPrintln("Database successfully reset")
|
||||
|
||||
auditRec := a.MakeAuditRecord("reset", audit.Success)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
var RolesCmd = &cobra.Command{
|
||||
Use: "roles",
|
||||
Short: "Management of user roles",
|
||||
}
|
||||
|
||||
var MakeSystemAdminCmd = &cobra.Command{
|
||||
Use: "system_admin [users]",
|
||||
Short: "Set a user as system admin",
|
||||
Long: "Make some users system admins",
|
||||
Example: " roles system_admin user1",
|
||||
RunE: makeSystemAdminCmdF,
|
||||
}
|
||||
|
||||
var MakeMemberCmd = &cobra.Command{
|
||||
Use: "member [users]",
|
||||
Short: "Remove system admin privileges",
|
||||
Long: "Remove system admin privileges from some users.",
|
||||
Example: " roles member user1",
|
||||
RunE: makeMemberCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
RolesCmd.AddCommand(
|
||||
MakeSystemAdminCmd,
|
||||
MakeMemberCmd,
|
||||
)
|
||||
RootCmd.AddCommand(RolesCmd)
|
||||
}
|
||||
|
||||
func makeSystemAdminCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.New("Enter at least one user.")
|
||||
}
|
||||
|
||||
users := getUsersFromUserArgs(a, args)
|
||||
for i, user := range users {
|
||||
if user == nil {
|
||||
return errors.New("Unable to find user '" + args[i] + "'")
|
||||
}
|
||||
|
||||
systemAdmin := false
|
||||
systemUser := false
|
||||
|
||||
roles := strings.Fields(user.Roles)
|
||||
for _, role := range roles {
|
||||
switch role {
|
||||
case model.SystemAdminRoleId:
|
||||
systemAdmin = true
|
||||
case model.SystemUserRoleId:
|
||||
systemUser = true
|
||||
}
|
||||
}
|
||||
|
||||
if !systemUser {
|
||||
roles = append(roles, model.SystemUserRoleId)
|
||||
}
|
||||
if !systemAdmin {
|
||||
roles = append(roles, model.SystemAdminRoleId)
|
||||
}
|
||||
|
||||
updatedUser, errUpdate := a.UpdateUserRoles(user.Id, strings.Join(roles, " "), true)
|
||||
if errUpdate != nil {
|
||||
return errUpdate
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("makeSystemAdmin", audit.Success)
|
||||
auditRec.AddMeta("user", user)
|
||||
auditRec.AddMeta("update", updatedUser)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeMemberCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.New("Enter at least one user.")
|
||||
}
|
||||
|
||||
users := getUsersFromUserArgs(a, args)
|
||||
for i, user := range users {
|
||||
if user == nil {
|
||||
return errors.New("Unable to find user '" + args[i] + "'")
|
||||
}
|
||||
|
||||
systemUser := false
|
||||
var newRoles []string
|
||||
|
||||
roles := strings.Fields(user.Roles)
|
||||
for _, role := range roles {
|
||||
switch role {
|
||||
case model.SystemAdminRoleId:
|
||||
default:
|
||||
if role == model.SystemUserRoleId {
|
||||
systemUser = true
|
||||
}
|
||||
newRoles = append(newRoles, role)
|
||||
}
|
||||
}
|
||||
|
||||
if !systemUser {
|
||||
newRoles = append(roles, model.SystemUserRoleId)
|
||||
}
|
||||
|
||||
updatedUser, errUpdate := a.UpdateUserRoles(user.Id, strings.Join(newRoles, " "), true)
|
||||
if errUpdate != nil {
|
||||
return errUpdate
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("makeMember", audit.Success)
|
||||
auditRec.AddMeta("user", user)
|
||||
auditRec.AddMeta("update", updatedUser)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAssignRole(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.CheckCommand(t, "roles", "system_admin", th.BasicUser.Email)
|
||||
|
||||
user, err := th.App.Srv().Store.User().GetByEmail(th.BasicUser.Email)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "system_user system_admin", user.Roles)
|
||||
|
||||
th.CheckCommand(t, "roles", "member", th.BasicUser.Email)
|
||||
|
||||
user, err = th.App.Srv().Store.User().GetByEmail(th.BasicUser.Email)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "system_user", user.Roles)
|
||||
|
||||
}
|
||||
@@ -1,743 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/icrowley/fake"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app"
|
||||
"github.com/mattermost/mattermost-server/v6/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
DeactivatedUser = "deactivated"
|
||||
GuestUser = "guest"
|
||||
)
|
||||
|
||||
var SampleDataCmd = &cobra.Command{
|
||||
Use: "sampledata",
|
||||
Short: "Generate sample data",
|
||||
RunE: sampleDataCmdF,
|
||||
}
|
||||
|
||||
func init() {
|
||||
SampleDataCmd.Flags().Int64P("seed", "s", 1, "Seed used for generating the random data (Different seeds generate different data).")
|
||||
SampleDataCmd.Flags().IntP("teams", "t", 2, "The number of sample teams.")
|
||||
SampleDataCmd.Flags().Int("channels-per-team", 10, "The number of sample channels per team.")
|
||||
SampleDataCmd.Flags().IntP("users", "u", 15, "The number of sample users.")
|
||||
SampleDataCmd.Flags().IntP("guests", "g", 1, "The number of sample guests.")
|
||||
SampleDataCmd.Flags().Int("deactivated-users", 0, "The number of deactivated users.")
|
||||
SampleDataCmd.Flags().Int("team-memberships", 2, "The number of sample team memberships per user.")
|
||||
SampleDataCmd.Flags().Int("channel-memberships", 5, "The number of sample channel memberships per user in a team.")
|
||||
SampleDataCmd.Flags().Int("posts-per-channel", 100, "The number of sample post per channel.")
|
||||
SampleDataCmd.Flags().Int("direct-channels", 30, "The number of sample direct message channels.")
|
||||
SampleDataCmd.Flags().Int("posts-per-direct-channel", 15, "The number of sample posts per direct message channel.")
|
||||
SampleDataCmd.Flags().Int("group-channels", 15, "The number of sample group message channels.")
|
||||
SampleDataCmd.Flags().Int("posts-per-group-channel", 30, "The number of sample posts per group message channel.")
|
||||
SampleDataCmd.Flags().IntP("workers", "w", 2, "How many workers to run during the import.")
|
||||
SampleDataCmd.Flags().String("profile-images", "", "Optional. Path to folder with images to randomly pick as user profile image.")
|
||||
SampleDataCmd.Flags().StringP("bulk", "b", "", "Optional. Path to write a JSONL bulk file instead of loading into the database.")
|
||||
RootCmd.AddCommand(SampleDataCmd)
|
||||
}
|
||||
|
||||
func randomPastTime(seconds int) int64 {
|
||||
now := time.Now()
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.FixedZone("UTC", 0))
|
||||
return (today.Unix() * 1000) - int64(rand.Intn(seconds*1000))
|
||||
}
|
||||
|
||||
func sortedRandomDates(size int) []int64 {
|
||||
dates := make([]int64, size)
|
||||
for i := 0; i < size; i++ {
|
||||
dates[i] = randomPastTime(50000)
|
||||
}
|
||||
sort.Slice(dates, func(a, b int) bool { return dates[a] < dates[b] })
|
||||
return dates
|
||||
}
|
||||
|
||||
func randomEmoji() string {
|
||||
emojis := []string{"+1", "-1", "heart", "blush"}
|
||||
return emojis[rand.Intn(len(emojis))]
|
||||
}
|
||||
|
||||
func randomReaction(users []string, parentCreateAt int64) app.ReactionImportData {
|
||||
user := users[rand.Intn(len(users))]
|
||||
emoji := randomEmoji()
|
||||
date := parentCreateAt + int64(rand.Intn(100000))
|
||||
return app.ReactionImportData{
|
||||
User: &user,
|
||||
EmojiName: &emoji,
|
||||
CreateAt: &date,
|
||||
}
|
||||
}
|
||||
|
||||
func randomReply(users []string, parentCreateAt int64) app.ReplyImportData {
|
||||
user := users[rand.Intn(len(users))]
|
||||
message := randomMessage(users)
|
||||
date := parentCreateAt + int64(rand.Intn(100000))
|
||||
return app.ReplyImportData{
|
||||
User: &user,
|
||||
Message: &message,
|
||||
CreateAt: &date,
|
||||
}
|
||||
}
|
||||
|
||||
func randomMessage(users []string) string {
|
||||
var message string
|
||||
switch rand.Intn(30) {
|
||||
case 0:
|
||||
mention := users[rand.Intn(len(users))]
|
||||
message = "@" + mention + " " + fake.Sentence()
|
||||
case 1:
|
||||
switch rand.Intn(2) {
|
||||
case 0:
|
||||
mattermostVideos := []string{"Q4MgnxbpZas", "BFo7E9-Kc_E", "LsMLR-BHsKg", "MRmGDhlMhNA", "mUOPxT7VgWc"}
|
||||
message = "https://www.youtube.com/watch?v=" + mattermostVideos[rand.Intn(len(mattermostVideos))]
|
||||
case 1:
|
||||
mattermostTweets := []string{"943119062334353408", "949370809528832005", "948539688171819009", "939122439115681792", "938061722027425797"}
|
||||
message = "https://twitter.com/mattermosthq/status/" + mattermostTweets[rand.Intn(len(mattermostTweets))]
|
||||
}
|
||||
case 2:
|
||||
message = ""
|
||||
if rand.Intn(2) == 0 {
|
||||
message += fake.Sentence()
|
||||
}
|
||||
for i := 0; i < rand.Intn(4)+1; i++ {
|
||||
message += "\n * " + fake.Word()
|
||||
}
|
||||
default:
|
||||
if rand.Intn(2) == 0 {
|
||||
message = fake.Sentence()
|
||||
} else {
|
||||
message = fake.Paragraph()
|
||||
}
|
||||
if rand.Intn(3) == 0 {
|
||||
message += "\n" + fake.Sentence()
|
||||
}
|
||||
if rand.Intn(3) == 0 {
|
||||
message += "\n" + fake.Sentence()
|
||||
}
|
||||
if rand.Intn(3) == 0 {
|
||||
message += "\n" + fake.Sentence()
|
||||
}
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
func sampleDataCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
seed, err := command.Flags().GetInt64("seed")
|
||||
if err != nil {
|
||||
return errors.New("Invalid seed parameter")
|
||||
}
|
||||
bulk, err := command.Flags().GetString("bulk")
|
||||
if err != nil {
|
||||
return errors.New("Invalid bulk parameter")
|
||||
}
|
||||
teams, err := command.Flags().GetInt("teams")
|
||||
if err != nil || teams < 0 {
|
||||
return errors.New("Invalid teams parameter")
|
||||
}
|
||||
channelsPerTeam, err := command.Flags().GetInt("channels-per-team")
|
||||
if err != nil || channelsPerTeam < 0 {
|
||||
return errors.New("Invalid channels-per-team parameter")
|
||||
}
|
||||
users, err := command.Flags().GetInt("users")
|
||||
if err != nil || users < 0 {
|
||||
return errors.New("Invalid users parameter")
|
||||
}
|
||||
deactivatedUsers, err := command.Flags().GetInt("deactivated-users")
|
||||
if err != nil || deactivatedUsers < 0 {
|
||||
return errors.New("Invalid deactivated-users parameter")
|
||||
}
|
||||
guests, err := command.Flags().GetInt("guests")
|
||||
if err != nil || guests < 0 {
|
||||
return errors.New("Invalid guests parameter")
|
||||
}
|
||||
teamMemberships, err := command.Flags().GetInt("team-memberships")
|
||||
if err != nil || teamMemberships < 0 {
|
||||
return errors.New("Invalid team-memberships parameter")
|
||||
}
|
||||
channelMemberships, err := command.Flags().GetInt("channel-memberships")
|
||||
if err != nil || channelMemberships < 0 {
|
||||
return errors.New("Invalid channel-memberships parameter")
|
||||
}
|
||||
postsPerChannel, err := command.Flags().GetInt("posts-per-channel")
|
||||
if err != nil || postsPerChannel < 0 {
|
||||
return errors.New("Invalid posts-per-channel parameter")
|
||||
}
|
||||
directChannels, err := command.Flags().GetInt("direct-channels")
|
||||
if err != nil || directChannels < 0 {
|
||||
return errors.New("Invalid direct-channels parameter")
|
||||
}
|
||||
postsPerDirectChannel, err := command.Flags().GetInt("posts-per-direct-channel")
|
||||
if err != nil || postsPerDirectChannel < 0 {
|
||||
return errors.New("Invalid posts-per-direct-channel parameter")
|
||||
}
|
||||
groupChannels, err := command.Flags().GetInt("group-channels")
|
||||
if err != nil || groupChannels < 0 {
|
||||
return errors.New("Invalid group-channels parameter")
|
||||
}
|
||||
postsPerGroupChannel, err := command.Flags().GetInt("posts-per-group-channel")
|
||||
if err != nil || postsPerGroupChannel < 0 {
|
||||
return errors.New("Invalid posts-per-group-channel parameter")
|
||||
}
|
||||
workers, err := command.Flags().GetInt("workers")
|
||||
if err != nil {
|
||||
return errors.New("Invalid workers parameter")
|
||||
}
|
||||
profileImagesPath, err := command.Flags().GetString("profile-images")
|
||||
if err != nil {
|
||||
return errors.New("Invalid profile-images parameter")
|
||||
}
|
||||
profileImages := []string{}
|
||||
if profileImagesPath != "" {
|
||||
var profileImagesStat os.FileInfo
|
||||
profileImagesStat, err = os.Stat(profileImagesPath)
|
||||
if os.IsNotExist(err) {
|
||||
return errors.New("Profile images folder doesn't exists.")
|
||||
}
|
||||
if !profileImagesStat.IsDir() {
|
||||
return errors.New("profile-images parameters must be a folder path.")
|
||||
}
|
||||
var profileImagesFiles []os.FileInfo
|
||||
profileImagesFiles, err = ioutil.ReadDir(profileImagesPath)
|
||||
if err != nil {
|
||||
return errors.New("Invalid profile-images parameter")
|
||||
}
|
||||
for _, profileImage := range profileImagesFiles {
|
||||
profileImages = append(profileImages, path.Join(profileImagesPath, profileImage.Name()))
|
||||
}
|
||||
sort.Strings(profileImages)
|
||||
}
|
||||
|
||||
if workers < 1 {
|
||||
return errors.New("You must have at least one worker.")
|
||||
}
|
||||
if teamMemberships > teams {
|
||||
return errors.New("You can't have more team memberships than teams.")
|
||||
}
|
||||
if channelMemberships > channelsPerTeam {
|
||||
return errors.New("You can't have more channel memberships than channels per team.")
|
||||
}
|
||||
|
||||
if users < 6 && groupChannels > 0 {
|
||||
return errors.New("You can't have group channels generation with less than 6 users. Use --group-channels 0 or increase the number of users.")
|
||||
}
|
||||
|
||||
var bulkFile *os.File
|
||||
switch bulk {
|
||||
case "":
|
||||
bulkFile, err = ioutil.TempFile("", ".mattermost-sample-data-")
|
||||
defer os.Remove(bulkFile.Name())
|
||||
if err != nil {
|
||||
return errors.New("Unable to open temporary file.")
|
||||
}
|
||||
case "-":
|
||||
bulkFile = os.Stdout
|
||||
default:
|
||||
bulkFile, err = os.OpenFile(bulk, os.O_RDWR|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return errors.New("Unable to write into the \"" + bulk + "\" file.")
|
||||
}
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(bulkFile)
|
||||
version := 1
|
||||
encoder.Encode(app.LineImportData{Type: "version", Version: &version})
|
||||
|
||||
fake.Seed(seed)
|
||||
rand.Seed(seed)
|
||||
|
||||
teamsAndChannels := make(map[string][]string)
|
||||
for i := 0; i < teams; i++ {
|
||||
teamLine := createTeam(i)
|
||||
teamsAndChannels[*teamLine.Team.Name] = []string{}
|
||||
encoder.Encode(teamLine)
|
||||
}
|
||||
|
||||
teamsList := []string{}
|
||||
for teamName := range teamsAndChannels {
|
||||
teamsList = append(teamsList, teamName)
|
||||
}
|
||||
sort.Strings(teamsList)
|
||||
|
||||
for _, teamName := range teamsList {
|
||||
for i := 0; i < channelsPerTeam; i++ {
|
||||
channelLine := createChannel(i, teamName)
|
||||
teamsAndChannels[teamName] = append(teamsAndChannels[teamName], *channelLine.Channel.Name)
|
||||
encoder.Encode(channelLine)
|
||||
}
|
||||
}
|
||||
|
||||
allUsers := []string{}
|
||||
for i := 0; i < users; i++ {
|
||||
userLine := createUser(i, teamMemberships, channelMemberships, teamsAndChannels, profileImages, "")
|
||||
encoder.Encode(userLine)
|
||||
allUsers = append(allUsers, *userLine.User.Username)
|
||||
}
|
||||
for i := 0; i < guests; i++ {
|
||||
userLine := createUser(i, teamMemberships, channelMemberships, teamsAndChannels, profileImages, GuestUser)
|
||||
encoder.Encode(userLine)
|
||||
allUsers = append(allUsers, *userLine.User.Username)
|
||||
}
|
||||
for i := 0; i < deactivatedUsers; i++ {
|
||||
userLine := createUser(i, teamMemberships, channelMemberships, teamsAndChannels, profileImages, DeactivatedUser)
|
||||
encoder.Encode(userLine)
|
||||
allUsers = append(allUsers, *userLine.User.Username)
|
||||
}
|
||||
|
||||
for team, channels := range teamsAndChannels {
|
||||
for _, channel := range channels {
|
||||
dates := sortedRandomDates(postsPerChannel)
|
||||
|
||||
for i := 0; i < postsPerChannel; i++ {
|
||||
postLine := createPost(team, channel, allUsers, dates[i])
|
||||
encoder.Encode(postLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < directChannels; i++ {
|
||||
user1 := allUsers[rand.Intn(len(allUsers))]
|
||||
user2 := allUsers[rand.Intn(len(allUsers))]
|
||||
channelLine := createDirectChannel([]string{user1, user2})
|
||||
encoder.Encode(channelLine)
|
||||
}
|
||||
|
||||
for i := 0; i < directChannels; i++ {
|
||||
user1 := allUsers[rand.Intn(len(allUsers))]
|
||||
user2 := allUsers[rand.Intn(len(allUsers))]
|
||||
|
||||
dates := sortedRandomDates(postsPerDirectChannel)
|
||||
for j := 0; j < postsPerDirectChannel; j++ {
|
||||
postLine := createDirectPost([]string{user1, user2}, dates[j])
|
||||
encoder.Encode(postLine)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < groupChannels; i++ {
|
||||
users := []string{}
|
||||
totalUsers := 3 + rand.Intn(3)
|
||||
for len(users) < totalUsers {
|
||||
user := allUsers[rand.Intn(len(allUsers))]
|
||||
if !utils.StringInSlice(user, users) {
|
||||
users = append(users, user)
|
||||
}
|
||||
}
|
||||
channelLine := createDirectChannel(users)
|
||||
encoder.Encode(channelLine)
|
||||
}
|
||||
|
||||
for i := 0; i < groupChannels; i++ {
|
||||
users := []string{}
|
||||
totalUsers := 3 + rand.Intn(3)
|
||||
for len(users) < totalUsers {
|
||||
user := allUsers[rand.Intn(len(allUsers))]
|
||||
if !utils.StringInSlice(user, users) {
|
||||
users = append(users, user)
|
||||
}
|
||||
}
|
||||
|
||||
dates := sortedRandomDates(postsPerGroupChannel)
|
||||
for j := 0; j < postsPerGroupChannel; j++ {
|
||||
postLine := createDirectPost(users, dates[j])
|
||||
encoder.Encode(postLine)
|
||||
}
|
||||
}
|
||||
|
||||
if bulk == "" {
|
||||
_, err := bulkFile.Seek(0, 0)
|
||||
if err != nil {
|
||||
return errors.New("Unable to read correctly the temporary file.")
|
||||
}
|
||||
|
||||
var importErr *model.AppError
|
||||
|
||||
importErr, lineNumber := a.BulkImport(&request.Context{}, bulkFile, nil, false, workers)
|
||||
if importErr != nil {
|
||||
return fmt.Errorf("%s: %s, %s (line: %d)", importErr.Where, importErr.Message, importErr.DetailedError, lineNumber)
|
||||
}
|
||||
auditRec := a.MakeAuditRecord("sampleData", audit.Success)
|
||||
auditRec.AddMeta("file", bulkFile.Name())
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
} else if bulk != "-" {
|
||||
err := bulkFile.Close()
|
||||
if err != nil {
|
||||
return errors.New("Unable to close correctly the output file")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createUser(idx int, teamMemberships int, channelMemberships int, teamsAndChannels map[string][]string, profileImages []string, userType string) app.LineImportData {
|
||||
firstName := fake.FirstName()
|
||||
lastName := fake.LastName()
|
||||
position := fake.JobTitle()
|
||||
|
||||
username := fmt.Sprintf("%s.%s", strings.ToLower(firstName), strings.ToLower(lastName))
|
||||
roles := "system_user"
|
||||
|
||||
var password string
|
||||
var email string
|
||||
|
||||
switch userType {
|
||||
case GuestUser:
|
||||
password = fmt.Sprintf("SampleGu@st-%d", idx)
|
||||
email = fmt.Sprintf("guest-%d@sample.mattermost.com", idx)
|
||||
roles = "system_guest"
|
||||
if idx == 0 {
|
||||
username = "guest"
|
||||
password = "SampleGu@st1"
|
||||
email = "guest@sample.mattermost.com"
|
||||
}
|
||||
case DeactivatedUser:
|
||||
password = fmt.Sprintf("SampleDe@ctivated-%d", idx)
|
||||
email = fmt.Sprintf("deactivated-%d@sample.mattermost.com", idx)
|
||||
default:
|
||||
password = fmt.Sprintf("SampleUs@r-%d", idx)
|
||||
email = fmt.Sprintf("user-%d@sample.mattermost.com", idx)
|
||||
if idx == 0 {
|
||||
username = "sysadmin"
|
||||
password = "Sys@dmin-sample1"
|
||||
email = "sysadmin@sample.mattermost.com"
|
||||
} else if idx == 1 {
|
||||
username = "user-1"
|
||||
}
|
||||
|
||||
if idx%5 == 0 {
|
||||
roles = "system_admin system_user"
|
||||
}
|
||||
}
|
||||
|
||||
// The 75% of the users have custom profile image
|
||||
var profileImage *string = nil
|
||||
if rand.Intn(4) != 0 {
|
||||
profileImageSelector := rand.Int()
|
||||
if len(profileImages) > 0 {
|
||||
profileImage = &profileImages[profileImageSelector%len(profileImages)]
|
||||
}
|
||||
}
|
||||
|
||||
useMilitaryTime := "false"
|
||||
if idx != 0 && rand.Intn(2) == 0 {
|
||||
useMilitaryTime = "true"
|
||||
}
|
||||
|
||||
collapsePreviews := "false"
|
||||
if idx != 0 && rand.Intn(2) == 0 {
|
||||
collapsePreviews = "true"
|
||||
}
|
||||
|
||||
messageDisplay := "clean"
|
||||
if idx != 0 && rand.Intn(2) == 0 {
|
||||
messageDisplay = "compact"
|
||||
}
|
||||
|
||||
channelDisplayMode := "full"
|
||||
if idx != 0 && rand.Intn(2) == 0 {
|
||||
channelDisplayMode = "centered"
|
||||
}
|
||||
|
||||
// Some users has nickname
|
||||
nickname := ""
|
||||
if rand.Intn(5) == 0 {
|
||||
nickname = fake.Company()
|
||||
}
|
||||
|
||||
// sysadmin, user-1 and user-2 users skip tutorial steps
|
||||
// Other half of users also skip tutorial steps
|
||||
tutorialStep := "999"
|
||||
if idx > 2 {
|
||||
switch rand.Intn(6) {
|
||||
case 1:
|
||||
tutorialStep = "1"
|
||||
case 2:
|
||||
tutorialStep = "2"
|
||||
case 3:
|
||||
tutorialStep = "3"
|
||||
}
|
||||
}
|
||||
|
||||
teams := []app.UserTeamImportData{}
|
||||
possibleTeams := []string{}
|
||||
for teamName := range teamsAndChannels {
|
||||
possibleTeams = append(possibleTeams, teamName)
|
||||
}
|
||||
sort.Strings(possibleTeams)
|
||||
for x := 0; x < teamMemberships; x++ {
|
||||
if len(possibleTeams) == 0 {
|
||||
break
|
||||
}
|
||||
position := rand.Intn(len(possibleTeams))
|
||||
team := possibleTeams[position]
|
||||
possibleTeams = append(possibleTeams[:position], possibleTeams[position+1:]...)
|
||||
if teamChannels, err := teamsAndChannels[team]; err {
|
||||
teams = append(teams, createTeamMembership(channelMemberships, teamChannels, &team, userType == GuestUser))
|
||||
}
|
||||
}
|
||||
|
||||
var deleteAt int64
|
||||
if userType == DeactivatedUser {
|
||||
deleteAt = model.GetMillis()
|
||||
}
|
||||
|
||||
user := app.UserImportData{
|
||||
ProfileImage: profileImage,
|
||||
Username: &username,
|
||||
Email: &email,
|
||||
Password: &password,
|
||||
Nickname: &nickname,
|
||||
FirstName: &firstName,
|
||||
LastName: &lastName,
|
||||
Position: &position,
|
||||
Roles: &roles,
|
||||
Teams: &teams,
|
||||
UseMilitaryTime: &useMilitaryTime,
|
||||
CollapsePreviews: &collapsePreviews,
|
||||
MessageDisplay: &messageDisplay,
|
||||
ChannelDisplayMode: &channelDisplayMode,
|
||||
TutorialStep: &tutorialStep,
|
||||
DeleteAt: &deleteAt,
|
||||
}
|
||||
return app.LineImportData{
|
||||
Type: "user",
|
||||
User: &user,
|
||||
}
|
||||
}
|
||||
|
||||
func createTeamMembership(numOfchannels int, teamChannels []string, teamName *string, guest bool) app.UserTeamImportData {
|
||||
roles := "team_user"
|
||||
if guest {
|
||||
roles = "team_guest"
|
||||
} else if rand.Intn(5) == 0 {
|
||||
roles = "team_user team_admin"
|
||||
}
|
||||
channels := []app.UserChannelImportData{}
|
||||
teamChannelsCopy := append([]string(nil), teamChannels...)
|
||||
for x := 0; x < numOfchannels; x++ {
|
||||
if len(teamChannelsCopy) == 0 {
|
||||
break
|
||||
}
|
||||
position := rand.Intn(len(teamChannelsCopy))
|
||||
channelName := teamChannelsCopy[position]
|
||||
teamChannelsCopy = append(teamChannelsCopy[:position], teamChannelsCopy[position+1:]...)
|
||||
channels = append(channels, createChannelMembership(channelName, guest))
|
||||
}
|
||||
|
||||
return app.UserTeamImportData{
|
||||
Name: teamName,
|
||||
Roles: &roles,
|
||||
Channels: &channels,
|
||||
}
|
||||
}
|
||||
|
||||
func createChannelMembership(channelName string, guest bool) app.UserChannelImportData {
|
||||
roles := "channel_user"
|
||||
if guest {
|
||||
roles = "channel_guest"
|
||||
} else if rand.Intn(5) == 0 {
|
||||
roles = "channel_user channel_admin"
|
||||
}
|
||||
favorite := rand.Intn(5) == 0
|
||||
|
||||
return app.UserChannelImportData{
|
||||
Name: &channelName,
|
||||
Roles: &roles,
|
||||
Favorite: &favorite,
|
||||
}
|
||||
}
|
||||
|
||||
func getSampleTeamName(idx int) string {
|
||||
for {
|
||||
name := fmt.Sprintf("%s-%d", fake.Word(), idx)
|
||||
if !model.IsReservedTeamName(name) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createTeam(idx int) app.LineImportData {
|
||||
displayName := fake.Word()
|
||||
name := getSampleTeamName(idx)
|
||||
allowOpenInvite := rand.Intn(2) == 0
|
||||
|
||||
description := fake.Paragraph()
|
||||
if len(description) > 255 {
|
||||
description = description[0:255]
|
||||
}
|
||||
|
||||
teamType := "O"
|
||||
if rand.Intn(2) == 0 {
|
||||
teamType = "I"
|
||||
}
|
||||
|
||||
team := app.TeamImportData{
|
||||
DisplayName: &displayName,
|
||||
Name: &name,
|
||||
AllowOpenInvite: &allowOpenInvite,
|
||||
Description: &description,
|
||||
Type: &teamType,
|
||||
}
|
||||
return app.LineImportData{
|
||||
Type: "team",
|
||||
Team: &team,
|
||||
}
|
||||
}
|
||||
|
||||
func createChannel(idx int, teamName string) app.LineImportData {
|
||||
displayName := fake.Word()
|
||||
name := fmt.Sprintf("%s-%d", fake.Word(), idx)
|
||||
header := fake.Paragraph()
|
||||
purpose := fake.Paragraph()
|
||||
|
||||
if len(purpose) > 250 {
|
||||
purpose = purpose[0:250]
|
||||
}
|
||||
|
||||
channelType := model.ChannelTypePrivate
|
||||
if rand.Intn(2) == 0 {
|
||||
channelType = model.ChannelTypeOpen
|
||||
}
|
||||
|
||||
channel := app.ChannelImportData{
|
||||
Team: &teamName,
|
||||
Name: &name,
|
||||
DisplayName: &displayName,
|
||||
Type: &channelType,
|
||||
Header: &header,
|
||||
Purpose: &purpose,
|
||||
}
|
||||
return app.LineImportData{
|
||||
Type: "channel",
|
||||
Channel: &channel,
|
||||
}
|
||||
}
|
||||
|
||||
func createPost(team string, channel string, allUsers []string, createAt int64) app.LineImportData {
|
||||
message := randomMessage(allUsers)
|
||||
create_at := createAt
|
||||
user := allUsers[rand.Intn(len(allUsers))]
|
||||
|
||||
// Some messages are flagged by an user
|
||||
flagged_by := []string{}
|
||||
if rand.Intn(10) == 0 {
|
||||
flagged_by = append(flagged_by, allUsers[rand.Intn(len(allUsers))])
|
||||
}
|
||||
|
||||
reactions := []app.ReactionImportData{}
|
||||
if rand.Intn(10) == 0 {
|
||||
for {
|
||||
reactions = append(reactions, randomReaction(allUsers, create_at))
|
||||
if rand.Intn(3) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
replies := []app.ReplyImportData{}
|
||||
if rand.Intn(10) == 0 {
|
||||
for {
|
||||
replies = append(replies, randomReply(allUsers, create_at))
|
||||
if rand.Intn(4) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post := app.PostImportData{
|
||||
Team: &team,
|
||||
Channel: &channel,
|
||||
User: &user,
|
||||
Message: &message,
|
||||
CreateAt: &create_at,
|
||||
FlaggedBy: &flagged_by,
|
||||
Reactions: &reactions,
|
||||
Replies: &replies,
|
||||
}
|
||||
return app.LineImportData{
|
||||
Type: "post",
|
||||
Post: &post,
|
||||
}
|
||||
}
|
||||
|
||||
func createDirectChannel(members []string) app.LineImportData {
|
||||
header := fake.Sentence()
|
||||
|
||||
channel := app.DirectChannelImportData{
|
||||
Members: &members,
|
||||
Header: &header,
|
||||
}
|
||||
return app.LineImportData{
|
||||
Type: "direct_channel",
|
||||
DirectChannel: &channel,
|
||||
}
|
||||
}
|
||||
|
||||
func createDirectPost(members []string, createAt int64) app.LineImportData {
|
||||
message := randomMessage(members)
|
||||
create_at := createAt
|
||||
user := members[rand.Intn(len(members))]
|
||||
|
||||
// Some messages are flagged by an user
|
||||
flagged_by := []string{}
|
||||
if rand.Intn(10) == 0 {
|
||||
flagged_by = append(flagged_by, members[rand.Intn(len(members))])
|
||||
}
|
||||
|
||||
reactions := []app.ReactionImportData{}
|
||||
if rand.Intn(10) == 0 {
|
||||
for {
|
||||
reactions = append(reactions, randomReaction(members, create_at))
|
||||
if rand.Intn(3) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
replies := []app.ReplyImportData{}
|
||||
if rand.Intn(10) == 0 {
|
||||
for {
|
||||
replies = append(replies, randomReply(members, create_at))
|
||||
if rand.Intn(4) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post := app.DirectPostImportData{
|
||||
ChannelMembers: &members,
|
||||
User: &user,
|
||||
Message: &message,
|
||||
CreateAt: &create_at,
|
||||
FlaggedBy: &flagged_by,
|
||||
Reactions: &reactions,
|
||||
Replies: &replies,
|
||||
}
|
||||
return app.LineImportData{
|
||||
Type: "direct_post",
|
||||
DirectPost: &post,
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSampledataBadParameters(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("should fail because you need at least 1 worker", func(t *testing.T) {
|
||||
require.Error(t, th.RunCommand(t, "sampledata", "--workers", "0"))
|
||||
})
|
||||
|
||||
t.Run("should fail because you have more team memberships than teams", func(t *testing.T) {
|
||||
require.Error(t, th.RunCommand(t, "sampledata", "--teams", "10", "--teams-memberships", "11"))
|
||||
})
|
||||
|
||||
t.Run("should fail because you have more channel memberships than channels per team", func(t *testing.T) {
|
||||
require.Error(t, th.RunCommand(t, "sampledata", "--channels-per-team", "10", "--channel-memberships", "11"))
|
||||
})
|
||||
|
||||
t.Run("should fail because you have group channels and don't have enough users (6 users)", func(t *testing.T) {
|
||||
require.Error(t, th.RunCommand(t, "sampledata", "--group-channels", "1", "--users", "5"))
|
||||
})
|
||||
|
||||
t.Run("should not fail with less than 6 users and no group channels", func(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "*")
|
||||
require.NoError(t, err)
|
||||
f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
require.NoError(t, th.RunCommand(t, "sampledata", "--group-channels", "0", "--users", "5", "--bulk", f.Name()))
|
||||
})
|
||||
|
||||
t.Run("should not fail with less than 6 users and no group channels", func(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "*")
|
||||
require.NoError(t, err)
|
||||
f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
require.NoError(t, th.RunCommand(t, "sampledata", "--group-channels", "0", "--users", "5", "--bulk", f.Name()))
|
||||
})
|
||||
}
|
||||
@@ -1,501 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app"
|
||||
"github.com/mattermost/mattermost-server/v6/app/request"
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
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: createTeamCmdF,
|
||||
}
|
||||
|
||||
var RemoveUsersCmd = &cobra.Command{
|
||||
Use: "remove [team] [users]",
|
||||
Short: "Remove users from team",
|
||||
Long: "Remove some users from team",
|
||||
Example: " team remove myteam user@example.com username",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: removeUsersCmdF,
|
||||
}
|
||||
|
||||
var AddUsersCmd = &cobra.Command{
|
||||
Use: "add [team] [users]",
|
||||
Short: "Add users to team",
|
||||
Long: "Add some users to team",
|
||||
Example: " team add myteam user@example.com username",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: addUsersCmdF,
|
||||
}
|
||||
|
||||
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: deleteTeamsCmdF,
|
||||
}
|
||||
|
||||
var ListTeamsCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all teams.",
|
||||
Long: `List all teams on the server.`,
|
||||
Example: " team list",
|
||||
RunE: 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: searchTeamCmdF,
|
||||
}
|
||||
|
||||
var ArchiveTeamCmd = &cobra.Command{
|
||||
Use: "archive [teams]",
|
||||
Short: "Archive teams",
|
||||
Long: "Archive teams based on name",
|
||||
Example: " team archive team1",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: archiveTeamCmdF,
|
||||
}
|
||||
|
||||
var RestoreTeamsCmd = &cobra.Command{
|
||||
Use: "restore [teams]",
|
||||
Short: "Restore some teams",
|
||||
Long: `Restore a previously deleted team`,
|
||||
Example: " team restore myteam",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: restoreTeamsCmdF,
|
||||
}
|
||||
|
||||
var TeamRenameCmd = &cobra.Command{
|
||||
Use: "rename",
|
||||
Short: "Rename a team",
|
||||
Long: `Rename a team.`,
|
||||
Example: ` team rename myteam newteamname --display_name "My New Team Name"
|
||||
team rename myteam - --display_name "My New Team Name"`,
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
RunE: renameTeamCmdF,
|
||||
}
|
||||
|
||||
var ModifyTeamCmd = &cobra.Command{
|
||||
Use: "modify [team] [flag]",
|
||||
Short: "Modify a team's privacy setting to public or private",
|
||||
Long: `Modify a team's privacy setting to public or private.`,
|
||||
Example: " team modify myteam --private",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: modifyTeamCmdF,
|
||||
}
|
||||
|
||||
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.")
|
||||
|
||||
TeamRenameCmd.Flags().String("display_name", "", "Team Display Name")
|
||||
|
||||
ModifyTeamCmd.Flags().Bool("private", false, "Convert the team to a private team")
|
||||
ModifyTeamCmd.Flags().Bool("public", false, "Convert the team to a public team")
|
||||
|
||||
TeamCmd.AddCommand(
|
||||
TeamCreateCmd,
|
||||
RemoveUsersCmd,
|
||||
AddUsersCmd,
|
||||
DeleteTeamsCmd,
|
||||
ListTeamsCmd,
|
||||
SearchTeamCmd,
|
||||
ArchiveTeamCmd,
|
||||
RestoreTeamsCmd,
|
||||
TeamRenameCmd,
|
||||
ModifyTeamCmd,
|
||||
)
|
||||
RootCmd.AddCommand(TeamCmd)
|
||||
}
|
||||
|
||||
func createTeamCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
name, errn := command.Flags().GetString("name")
|
||||
if errn != nil || name == "" {
|
||||
return errors.New("Name is required")
|
||||
}
|
||||
displayname, errdn := command.Flags().GetString("display_name")
|
||||
if errdn != nil || displayname == "" {
|
||||
return errors.New("Display Name is required")
|
||||
}
|
||||
email, _ := command.Flags().GetString("email")
|
||||
email = strings.ToLower(email)
|
||||
useprivate, _ := command.Flags().GetBool("private")
|
||||
|
||||
teamType := model.TeamOpen
|
||||
if useprivate {
|
||||
teamType = model.TeamInvite
|
||||
}
|
||||
|
||||
team := &model.Team{
|
||||
Name: name,
|
||||
DisplayName: displayname,
|
||||
Email: email,
|
||||
Type: teamType,
|
||||
}
|
||||
|
||||
createdTeam, errCreate := a.CreateTeam(&request.Context{}, team)
|
||||
if errCreate != nil {
|
||||
return errors.New("Team creation failed: " + errCreate.Error())
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("createTeam", audit.Success)
|
||||
auditRec.AddMeta("team", createdTeam)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeUsersCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
team := getTeamFromTeamArg(a, args[0])
|
||||
if team == nil {
|
||||
return errors.New("Unable to find team '" + args[0] + "'")
|
||||
}
|
||||
|
||||
users := getUsersFromUserArgs(a, args[1:])
|
||||
for i, user := range users {
|
||||
removeUserFromTeam(a, team, user, args[i+1])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeUserFromTeam(a *app.App, team *model.Team, user *model.User, userArg string) {
|
||||
if user == nil {
|
||||
CommandPrintErrorln("Can't find user '" + userArg + "'")
|
||||
return
|
||||
}
|
||||
if err := a.LeaveTeam(&request.Context{}, team, user, ""); err != nil {
|
||||
CommandPrintErrorln("Unable to remove '" + userArg + "' from " + team.Name + ". Error: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("removeUserFromTeam", audit.Success)
|
||||
auditRec.AddMeta("user", user)
|
||||
auditRec.AddMeta("team", team)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
|
||||
func addUsersCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
team := getTeamFromTeamArg(a, args[0])
|
||||
if team == nil {
|
||||
return errors.New("Unable to find team '" + args[0] + "'")
|
||||
}
|
||||
|
||||
users := getUsersFromUserArgs(a, args[1:])
|
||||
for i, user := range users {
|
||||
addUserToTeam(a, team, user, args[i+1])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addUserToTeam(a *app.App, team *model.Team, user *model.User, userArg string) {
|
||||
if user == nil {
|
||||
CommandPrintErrorln("Can't find user '" + userArg + "'")
|
||||
return
|
||||
}
|
||||
if _, err := a.JoinUserToTeam(&request.Context{}, team, user, ""); err != nil {
|
||||
CommandPrintErrorln("Unable to add '" + userArg + "' to " + team.Name)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("addUserToTeam", audit.Success)
|
||||
auditRec.AddMeta("user", user)
|
||||
auditRec.AddMeta("team", team)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
|
||||
func deleteTeamsCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
confirmFlag, _ := command.Flags().GetBool("confirm")
|
||||
if !confirmFlag {
|
||||
var confirm string
|
||||
CommandPrettyPrintln("Have you performed a database backup? (YES/NO): ")
|
||||
fmt.Scanln(&confirm)
|
||||
|
||||
if confirm != "YES" {
|
||||
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
|
||||
}
|
||||
CommandPrettyPrintln("Are you sure you want to delete the teams specified? All data will be permanently deleted? (YES/NO): ")
|
||||
fmt.Scanln(&confirm)
|
||||
if confirm != "YES" {
|
||||
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
|
||||
}
|
||||
}
|
||||
|
||||
teams := getTeamsFromTeamArgs(a, args)
|
||||
for i, team := range teams {
|
||||
if team == nil {
|
||||
CommandPrintErrorln("Unable to find team '" + args[i] + "'")
|
||||
continue
|
||||
}
|
||||
if err := deleteTeam(a, team); err != nil {
|
||||
CommandPrintErrorln("Unable to delete team '" + team.Name + "' error: " + err.Error())
|
||||
} else {
|
||||
CommandPrettyPrintln("Deleted team '" + team.Name + "'")
|
||||
|
||||
auditRec := a.MakeAuditRecord("deleteTeams", audit.Success)
|
||||
auditRec.AddMeta("team", team)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteTeam(a *app.App, team *model.Team) *model.AppError {
|
||||
return a.PermanentDeleteTeam(team)
|
||||
}
|
||||
|
||||
func listTeamsCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
teams, err2 := a.GetAllTeams()
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
|
||||
for _, team := range teams {
|
||||
if team.DeleteAt > 0 {
|
||||
CommandPrettyPrintln(team.Name + " (archived)")
|
||||
} else {
|
||||
CommandPrettyPrintln(team.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func searchTeamCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
var teams []*model.Team
|
||||
|
||||
for _, searchTerm := range args {
|
||||
foundTeams, _, err := a.SearchAllTeams(&model.TeamSearch{Term: searchTerm})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
teams = append(teams, foundTeams...)
|
||||
}
|
||||
|
||||
sortedTeams := removeDuplicatesAndSortTeams(teams)
|
||||
|
||||
for _, team := range sortedTeams {
|
||||
if team.DeleteAt > 0 {
|
||||
CommandPrettyPrintln(team.Name + ": " + team.DisplayName + " (" + team.Id + ")" + " (archived)")
|
||||
} else {
|
||||
CommandPrettyPrintln(team.Name + ": " + team.DisplayName + " (" + team.Id + ")")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restores archived teams by name
|
||||
func restoreTeamsCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
teams := getTeamsFromTeamArgs(a, args)
|
||||
for i, team := range teams {
|
||||
if team == nil {
|
||||
CommandPrintErrorln("Unable to find team '" + args[i] + "'")
|
||||
continue
|
||||
}
|
||||
err := a.RestoreTeam(team.Id)
|
||||
if err != nil {
|
||||
CommandPrintErrorln("Unable to restore team '" + team.Name + "' error: " + err.Error())
|
||||
} else {
|
||||
auditRec := a.MakeAuditRecord("restoreTeams", audit.Success)
|
||||
auditRec.AddMeta("team", team)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
}
|
||||
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 archiveTeamCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
foundTeams := getTeamsFromTeamArgs(a, args)
|
||||
for i, team := range foundTeams {
|
||||
if team == nil {
|
||||
CommandPrintErrorln("Unable to find team '" + args[i] + "'")
|
||||
continue
|
||||
}
|
||||
if err := a.SoftDeleteTeam(team.Id); err != nil {
|
||||
CommandPrintErrorln("Unable to archive team '"+team.Name+"' error: ", err)
|
||||
} else {
|
||||
auditRec := a.MakeAuditRecord("archiveTeam", audit.Success)
|
||||
auditRec.AddMeta("team", team)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func renameTeamCmdF(command *cobra.Command, args []string) error {
|
||||
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
team := getTeamFromTeamArg(a, args[0])
|
||||
if team == nil {
|
||||
return errors.New("Unable to find team '" + args[0] + "'")
|
||||
}
|
||||
|
||||
var newDisplayName, newTeamName string
|
||||
|
||||
newTeamName = args[1]
|
||||
|
||||
// let user use old team Name when only Display Name change is wanted
|
||||
if newTeamName == team.Name {
|
||||
newTeamName = "-"
|
||||
}
|
||||
|
||||
newDisplayName, errdn := command.Flags().GetString("display_name")
|
||||
if errdn != nil {
|
||||
return errdn
|
||||
}
|
||||
|
||||
updatedTeam, errrt := a.RenameTeam(team, newTeamName, newDisplayName)
|
||||
if errrt != nil {
|
||||
CommandPrintErrorln("Unable to rename team to '"+newTeamName+"' error: ", errrt)
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("renameTeam", audit.Success)
|
||||
auditRec.AddMeta("team", team)
|
||||
auditRec.AddMeta("update", updatedTeam)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func modifyTeamCmdF(command *cobra.Command, args []string) error {
|
||||
a, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.Srv().Shutdown()
|
||||
|
||||
team := getTeamFromTeamArg(a, args[0])
|
||||
if team == nil {
|
||||
return errors.New("Unable to find team '" + args[0] + "'")
|
||||
}
|
||||
|
||||
public, _ := command.Flags().GetBool("public")
|
||||
private, _ := command.Flags().GetBool("private")
|
||||
|
||||
if public == private {
|
||||
return errors.New("You must specify only one of --public or --private")
|
||||
}
|
||||
|
||||
if public {
|
||||
team.Type = model.TeamOpen
|
||||
team.AllowOpenInvite = true
|
||||
} else if private {
|
||||
team.Type = model.TeamInvite
|
||||
team.AllowOpenInvite = false
|
||||
}
|
||||
|
||||
if err := a.UpdateTeamPrivacy(team.Id, team.Type, team.AllowOpenInvite); err != nil {
|
||||
return errors.New("Failed to update privacy for team" + args[0])
|
||||
}
|
||||
|
||||
auditRec := a.MakeAuditRecord("modifyTeam", audit.Success)
|
||||
auditRec.AddMeta("team", team)
|
||||
auditRec.AddMeta("type", team.Type)
|
||||
auditRec.AddMeta("allow_open_invite", team.AllowOpenInvite)
|
||||
a.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
func TestCreateTeam(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
name := "name" + id
|
||||
displayName := "Name " + id
|
||||
|
||||
th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName)
|
||||
|
||||
found, _, err := th.SystemAdminClient.TeamExists(name, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, found, "Failed to create Team")
|
||||
}
|
||||
|
||||
func TestJoinTeam(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.CheckCommand(t, "team", "add", th.BasicTeam.Name, th.BasicUser.Email)
|
||||
|
||||
profiles, _, err := th.SystemAdminClient.GetUsersInTeam(th.BasicTeam.Id, 0, 1000, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
found := false
|
||||
|
||||
for _, user := range profiles {
|
||||
if user.Email == th.BasicUser.Email {
|
||||
found = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
require.True(t, found, "Failed to create User")
|
||||
}
|
||||
|
||||
func TestLeaveTeam(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.CheckCommand(t, "team", "remove", th.BasicTeam.Name, th.BasicUser.Email)
|
||||
|
||||
profiles, _, err := th.Client.GetUsersInTeam(th.BasicTeam.Id, 0, 1000, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
found := false
|
||||
|
||||
for _, user := range profiles {
|
||||
if user.Email == th.BasicUser.Email {
|
||||
found = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
require.False(t, found, "profile should not be on team")
|
||||
|
||||
teams, err := th.App.Srv().Store.Team().GetTeamsByUserId(th.BasicUser.Id)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(teams), "Shouldn't be in team")
|
||||
}
|
||||
|
||||
func TestListTeams(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
name := "name" + id
|
||||
displayName := "Name " + id
|
||||
|
||||
th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName)
|
||||
|
||||
output := th.CheckCommand(t, "team", "list", th.BasicTeam.Name, th.BasicUser.Email)
|
||||
|
||||
assert.Contains(t, output, name, "should have the created team")
|
||||
}
|
||||
|
||||
func TestListArchivedTeams(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
name := "name" + id
|
||||
displayName := "Name " + id
|
||||
|
||||
th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName)
|
||||
|
||||
th.CheckCommand(t, "team", "archive", name)
|
||||
|
||||
output := th.CheckCommand(t, "team", "list", th.BasicTeam.Name, th.BasicUser.Email)
|
||||
|
||||
assert.Contains(t, output, name+" (archived)", "should have archived team")
|
||||
}
|
||||
|
||||
func TestSearchTeamsByName(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
name := "name" + id
|
||||
displayName := "Name " + id
|
||||
|
||||
th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName)
|
||||
|
||||
output := th.CheckCommand(t, "team", "search", name)
|
||||
|
||||
assert.Contains(t, output, name, "should have the created team")
|
||||
}
|
||||
|
||||
func TestSearchTeamsByDisplayName(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
name := "name" + id
|
||||
displayName := "Name " + id
|
||||
|
||||
th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName)
|
||||
|
||||
output := th.CheckCommand(t, "team", "search", displayName)
|
||||
|
||||
assert.Contains(t, output, name, "should have the created team")
|
||||
}
|
||||
|
||||
func TestSearchArchivedTeamsByName(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
name := "name" + id
|
||||
displayName := "Name " + id
|
||||
|
||||
th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName)
|
||||
|
||||
th.CheckCommand(t, "team", "archive", name)
|
||||
|
||||
output := th.CheckCommand(t, "team", "search", name)
|
||||
|
||||
assert.Contains(t, output, "(archived)", "should have archived team")
|
||||
}
|
||||
|
||||
func TestArchiveTeams(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
name := "name" + id
|
||||
displayName := "Name " + id
|
||||
|
||||
th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName)
|
||||
|
||||
th.CheckCommand(t, "team", "archive", name)
|
||||
|
||||
output := th.CheckCommand(t, "team", "list")
|
||||
|
||||
assert.Contains(t, output, name+" (archived)", "should have archived team")
|
||||
}
|
||||
|
||||
func TestRestoreTeams(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
name := "name" + id
|
||||
displayName := "Name " + id
|
||||
|
||||
th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName)
|
||||
|
||||
th.CheckCommand(t, "team", "archive", name)
|
||||
|
||||
th.CheckCommand(t, "team", "restore", name)
|
||||
|
||||
found, _, err := th.SystemAdminClient.TeamExists(name, "")
|
||||
require.NoError(t, err)
|
||||
require.True(t, found)
|
||||
}
|
||||
|
||||
func TestRenameTeam(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
team := th.CreateTeam()
|
||||
|
||||
newTeamName := "newteamnamex3"
|
||||
newDisplayName := "New Display NameX"
|
||||
|
||||
th.CheckCommand(t, "team", "rename", team.Name, newTeamName, "--display_name", newDisplayName)
|
||||
|
||||
// Get the team from the DB
|
||||
updatedTeam, _ := th.App.GetTeam(team.Id)
|
||||
|
||||
require.Equal(t, updatedTeam.Name, newTeamName, "failed renaming team")
|
||||
require.Equal(t, updatedTeam.DisplayName, newDisplayName, "failed updating team display name")
|
||||
|
||||
// Try to rename to occupied name
|
||||
team2 := th.CreateTeam()
|
||||
n := team2.Name
|
||||
dn := team2.DisplayName
|
||||
|
||||
th.CheckCommand(t, "team", "rename", team2.Name, newTeamName, "--display_name", newDisplayName)
|
||||
|
||||
// No renaming should have occurred
|
||||
require.Equal(t, team2.Name, n, "team was renamed when it should have not been")
|
||||
require.Equal(t, team2.DisplayName, dn, "team display name was changed when it should have not been")
|
||||
|
||||
// Try to change only Display Name
|
||||
team3 := th.CreateTeam()
|
||||
|
||||
// trying to change only Display Name (using "-" as a new team name)
|
||||
th.CheckCommand(t, "team", "rename", team3.Name, "-", "--display_name", newDisplayName)
|
||||
|
||||
// Get the team from the DB
|
||||
updatedTeam, _ = th.App.GetTeam(team3.Id)
|
||||
|
||||
require.NotEqual(t, updatedTeam.Name, "-", "team was renamed to `-` but only display name should have been changed")
|
||||
require.Equal(t, updatedTeam.DisplayName, newDisplayName, "team Display Name was not properly updated")
|
||||
|
||||
// now try to change Display Name using old team name
|
||||
th.CheckCommand(t, "team", "rename", team3.Name, team3.Name, "--display_name", "Brand New DName")
|
||||
|
||||
// Get the team from the DB
|
||||
updatedTeam, _ = th.App.GetTeam(team3.Id)
|
||||
|
||||
require.Equal(t, updatedTeam.DisplayName, "Brand New DName", "team Display Name was not properly updated")
|
||||
}
|
||||
|
||||
func TestModifyTeam(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
team := th.CreateTeam()
|
||||
|
||||
th.CheckCommand(t, "team", "modify", team.Name, "--private")
|
||||
|
||||
updatedTeam, _ := th.App.GetTeam(team.Id)
|
||||
|
||||
require.False(t, !updatedTeam.AllowOpenInvite && team.Type == model.TeamInvite, "Failed modifying team's privacy to private")
|
||||
|
||||
th.CheckCommand(t, "team", "modify", team.Name, "--public")
|
||||
|
||||
require.False(t, updatedTeam.AllowOpenInvite && team.Type == model.TeamOpen, "Failed modifying team's privacy to private")
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/v6/app"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
func getTeamsFromTeamArgs(a *app.App, teamArgs []string) []*model.Team {
|
||||
teams := make([]*model.Team, 0, len(teamArgs))
|
||||
for _, teamArg := range teamArgs {
|
||||
team := getTeamFromTeamArg(a, teamArg)
|
||||
teams = append(teams, team)
|
||||
}
|
||||
return teams
|
||||
}
|
||||
|
||||
func getTeamFromTeamArg(a *app.App, teamArg string) *model.Team {
|
||||
var team *model.Team
|
||||
team, err := a.Srv().Store.Team().GetByName(teamArg)
|
||||
|
||||
if err != nil {
|
||||
var t *model.Team
|
||||
if t, err = a.Srv().Store.Team().Get(teamArg); err == nil {
|
||||
team = t
|
||||
}
|
||||
}
|
||||
return team
|
||||
}
|
||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -1,215 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
func TestCreateUserWithTeam(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
email := "success+" + id + "@simulator.amazonses.com"
|
||||
username := "name" + id
|
||||
|
||||
th.CheckCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username)
|
||||
|
||||
th.CheckCommand(t, "team", "add", th.BasicTeam.Id, email)
|
||||
|
||||
profiles, _, err := th.SystemAdminClient.GetUsersInTeam(th.BasicTeam.Id, 0, 1000, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
found := false
|
||||
|
||||
for _, user := range profiles {
|
||||
if user.Email == email {
|
||||
found = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
require.True(t, found, "Failed to create User")
|
||||
}
|
||||
|
||||
func TestCreateUserWithoutTeam(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
id := model.NewId()
|
||||
email := "success+" + id + "@simulator.amazonses.com"
|
||||
username := "name" + id
|
||||
|
||||
th.CheckCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username)
|
||||
|
||||
user, err := th.App.Srv().Store.User().GetByEmail(email)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, email, user.Email)
|
||||
}
|
||||
|
||||
func TestResetPassword(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.CheckCommand(t, "user", "password", th.BasicUser.Email, "password2")
|
||||
|
||||
th.Client.Logout()
|
||||
th.BasicUser.Password = "password2"
|
||||
th.LoginBasic()
|
||||
}
|
||||
|
||||
func TestMakeUserActiveAndInactive(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
// first inactivate the user
|
||||
th.CheckCommand(t, "user", "deactivate", th.BasicUser.Email)
|
||||
|
||||
// activate the inactive user
|
||||
th.CheckCommand(t, "user", "activate", th.BasicUser.Email)
|
||||
}
|
||||
|
||||
func TestChangeUserEmail(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
newEmail := model.NewId() + "@mattermost-test.com"
|
||||
|
||||
th.CheckCommand(t, "user", "email", th.BasicUser.Username, newEmail)
|
||||
_, err := th.App.Srv().Store.User().GetByEmail(th.BasicUser.Email)
|
||||
require.Error(t, err, "should've updated to the new email")
|
||||
|
||||
user, err := th.App.Srv().Store.User().GetByEmail(newEmail)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, user.Email, newEmail, "should've updated to the new email")
|
||||
|
||||
// should fail because using an invalid email
|
||||
require.Error(t, th.RunCommand(t, "user", "email", th.BasicUser.Username, "wrong$email.com"))
|
||||
|
||||
// should fail because missing one parameter
|
||||
require.Error(t, th.RunCommand(t, "user", "email", th.BasicUser.Username))
|
||||
|
||||
// should fail because missing both parameters
|
||||
require.Error(t, th.RunCommand(t, "user", "email"))
|
||||
|
||||
// should fail because have more than 2 parameters
|
||||
require.Error(t, th.RunCommand(t, "user", "email", th.BasicUser.Username, "new@email.com", "extra!"))
|
||||
|
||||
// should fail because user not found
|
||||
require.Error(t, th.RunCommand(t, "user", "email", "invalidUser", newEmail))
|
||||
|
||||
// should fail because email already in use
|
||||
require.Error(t, th.RunCommand(t, "user", "email", th.BasicUser.Username, th.BasicUser2.Email))
|
||||
|
||||
}
|
||||
|
||||
func TestDeleteUserBotUser(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.CheckCommand(t, "user", "delete", th.BasicUser.Username, "--confirm")
|
||||
_, err := th.App.Srv().Store.User().Get(context.Background(), th.BasicUser.Id)
|
||||
require.Error(t, err)
|
||||
|
||||
// Make a bot
|
||||
bot := &model.Bot{
|
||||
Username: "bottodelete",
|
||||
Description: "Delete me!",
|
||||
OwnerId: model.NewId(),
|
||||
}
|
||||
user, err := th.App.Srv().Store.User().Save(model.UserFromBot(bot))
|
||||
require.NoError(t, err)
|
||||
bot.UserId = user.Id
|
||||
bot, nErr := th.App.Srv().Store.Bot().Save(bot)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
th.CheckCommand(t, "user", "delete", bot.Username, "--confirm")
|
||||
_, err = th.App.Srv().Store.User().Get(context.Background(), user.Id)
|
||||
require.Error(t, err)
|
||||
_, nErr = th.App.Srv().Store.Bot().Get(user.Id, true)
|
||||
require.Error(t, nErr)
|
||||
}
|
||||
|
||||
func TestConvertUser(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
t.Run("Invalid command line input", func(t *testing.T) {
|
||||
err := th.RunCommand(t, "user", "convert", th.BasicUser.Username)
|
||||
require.Error(t, err)
|
||||
|
||||
err = th.RunCommand(t, "user", "convert", th.BasicUser.Username, "--user", "--bot")
|
||||
require.Error(t, err)
|
||||
|
||||
err = th.RunCommand(t, "user", "convert", "--bot")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Convert to bot from username", func(t *testing.T) {
|
||||
th.CheckCommand(t, "user", "convert", th.BasicUser.Username, "anotherinvaliduser", "--bot")
|
||||
_, err := th.App.Srv().Store.Bot().Get(th.BasicUser.Id, false)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Unable to convert to user with missing password", func(t *testing.T) {
|
||||
err := th.RunCommand(t, "user", "convert", th.BasicUser.Username, "--user")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Unable to convert to user with invalid email", func(t *testing.T) {
|
||||
err := th.RunCommand(t, "user", "convert", th.BasicUser.Username, "--user",
|
||||
"--password", "password",
|
||||
"--email", "invalidEmail")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Convert to user with minimum flags", func(t *testing.T) {
|
||||
err := th.RunCommand(t, "user", "convert", th.BasicUser.Username, "--user",
|
||||
"--password", "password")
|
||||
require.NoError(t, err)
|
||||
_, err = th.App.Srv().Store.Bot().Get(th.BasicUser.Id, false)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Convert to bot from email", func(t *testing.T) {
|
||||
th.CheckCommand(t, "user", "convert", th.BasicUser2.Email, "--bot")
|
||||
_, err := th.App.Srv().Store.Bot().Get(th.BasicUser2.Id, false)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Convert to user with all flags", func(t *testing.T) {
|
||||
err := th.RunCommand(t, "user", "convert", th.BasicUser2.Username, "--user",
|
||||
"--password", "password",
|
||||
"--username", "newusername",
|
||||
"--email", "valid@email.com",
|
||||
"--nickname", "newNickname",
|
||||
"--firstname", "newFirstName",
|
||||
"--lastname", "newLastName",
|
||||
"--locale", "en_CA",
|
||||
"--system_admin")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = th.App.Srv().Store.Bot().Get(th.BasicUser2.Id, false)
|
||||
require.Error(t, err)
|
||||
|
||||
user, appErr := th.App.Srv().Store.User().Get(context.Background(), th.BasicUser2.Id)
|
||||
require.NoError(t, appErr)
|
||||
require.Equal(t, "newusername", user.Username)
|
||||
require.Equal(t, "valid@email.com", user.Email)
|
||||
require.Equal(t, "newNickname", user.Nickname)
|
||||
require.Equal(t, "newFirstName", user.FirstName)
|
||||
require.Equal(t, "newLastName", user.LastName)
|
||||
require.Equal(t, "en_CA", user.Locale)
|
||||
require.True(t, user.IsInRole("system_admin"))
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
func getUsersFromUserArgs(a *app.App, userArgs []string) []*model.User {
|
||||
users := make([]*model.User, 0, len(userArgs))
|
||||
for _, userArg := range userArgs {
|
||||
user := getUserFromUserArg(a, userArg)
|
||||
users = append(users, user)
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
func getUserFromUserArg(a *app.App, userArg string) *model.User {
|
||||
user, _ := a.Srv().Store.User().GetByEmail(userArg)
|
||||
|
||||
if user == nil {
|
||||
var err error
|
||||
if user, err = a.Srv().Store.User().GetByUsername(userArg); err == nil {
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
user, _ = a.Srv().Store.User().Get(context.Background(), userArg)
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
@@ -15,64 +15,10 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||
)
|
||||
|
||||
const CustomDefaultsEnvVar = "MM_CUSTOM_DEFAULTS_PATH"
|
||||
|
||||
// prettyPrintStruct will return a prettyPrint version of a given struct
|
||||
func prettyPrintStruct(t interface{}) string {
|
||||
return prettyPrintMap(structToMap(t))
|
||||
}
|
||||
|
||||
// structToMap converts a struct into a map
|
||||
func structToMap(t interface{}) map[string]interface{} {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
mlog.Warn("Panicked in structToMap. This should never happen.", mlog.Any("recover", r))
|
||||
}
|
||||
}()
|
||||
|
||||
val := reflect.ValueOf(t)
|
||||
|
||||
if val.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
|
||||
out := map[string]interface{}{}
|
||||
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field := val.Field(i)
|
||||
|
||||
var value interface{}
|
||||
|
||||
switch field.Kind() {
|
||||
case reflect.Struct:
|
||||
value = structToMap(field.Interface())
|
||||
case reflect.Ptr:
|
||||
indirectType := field.Elem()
|
||||
|
||||
if indirectType.Kind() == reflect.Struct {
|
||||
value = structToMap(indirectType.Interface())
|
||||
} else if indirectType.Kind() != reflect.Invalid {
|
||||
value = indirectType.Interface()
|
||||
}
|
||||
default:
|
||||
value = field.Interface()
|
||||
}
|
||||
|
||||
out[val.Type().Field(i).Name] = value
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// prettyPrintMap will return a prettyPrint version of a given map
|
||||
func prettyPrintMap(configMap map[string]interface{}) string {
|
||||
value := reflect.ValueOf(configMap)
|
||||
return printStringMap(value, 0)
|
||||
}
|
||||
|
||||
// printStringMap takes a reflect.Value and prints it out alphabetically based on key values, which must be strings.
|
||||
// This is done recursively if it's a map, and uses the given tab settings.
|
||||
func printStringMap(value reflect.Value, tabVal int) string {
|
||||
|
||||
@@ -10,90 +10,6 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStructToMap(t *testing.T) {
|
||||
cases := []struct {
|
||||
Name string
|
||||
Input interface{}
|
||||
Expected map[string]interface{}
|
||||
}{
|
||||
{
|
||||
Name: "Struct with one string field",
|
||||
Input: struct {
|
||||
Test string
|
||||
}{
|
||||
Test: "test",
|
||||
},
|
||||
Expected: map[string]interface{}{
|
||||
"Test": "test",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "String with multiple fields of different ",
|
||||
Input: struct {
|
||||
Test1 string
|
||||
Test2 int
|
||||
Test3 string
|
||||
Test4 bool
|
||||
}{
|
||||
Test1: "test1",
|
||||
Test2: 21,
|
||||
Test3: "test2",
|
||||
Test4: false,
|
||||
},
|
||||
Expected: map[string]interface{}{
|
||||
"Test1": "test1",
|
||||
"Test2": 21,
|
||||
"Test3": "test2",
|
||||
"Test4": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Nested fields",
|
||||
Input: TestConfig{
|
||||
TestServiceSettings{"abc", "def", "ghi"},
|
||||
TestTeamSettings{"abc", 1},
|
||||
TestClientRequirements{"abc", "def", "ghi"},
|
||||
TestMessageExportSettings{true, "abc", TestGlobalRelaySettings{"abc", "def", "ghi"}},
|
||||
},
|
||||
Expected: map[string]interface{}{
|
||||
"TestServiceSettings": map[string]interface{}{
|
||||
"Siteurl": "abc",
|
||||
"Websocketurl": "def",
|
||||
"Licensedfieldlocation": "ghi",
|
||||
},
|
||||
"TestTeamSettings": map[string]interface{}{
|
||||
"Sitename": "abc",
|
||||
"Maxuserperteam": 1,
|
||||
},
|
||||
"TestClientRequirements": map[string]interface{}{
|
||||
"Androidlatestversion": "abc",
|
||||
"Androidminversion": "def",
|
||||
"Desktoplatestversion": "ghi",
|
||||
},
|
||||
"TestMessageExportSettings": map[string]interface{}{
|
||||
"Enableexport": true,
|
||||
"Exportformat": "abc",
|
||||
"TestGlobalRelaySettings": map[string]interface{}{
|
||||
"Customertype": "abc",
|
||||
"SMTPUsername": "def",
|
||||
"SMTPPassword": "ghi",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
res := structToMap(test.Input)
|
||||
|
||||
if !reflect.DeepEqual(res, test.Expected) {
|
||||
t.Errorf("got %v want %v ", res, test.Expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrintMap(t *testing.T) {
|
||||
inputCases := []interface{}{
|
||||
map[string]interface{}{
|
||||
|
||||
@@ -1,608 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/audit"
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/store"
|
||||
)
|
||||
|
||||
var WebhookCmd = &cobra.Command{
|
||||
Use: "webhook",
|
||||
Short: "Management of webhooks",
|
||||
}
|
||||
|
||||
var WebhookListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List webhooks",
|
||||
Long: "list all webhooks",
|
||||
Example: " webhook list myteam",
|
||||
RunE: listWebhookCmdF,
|
||||
}
|
||||
|
||||
var WebhookShowCmd = &cobra.Command{
|
||||
Use: "show [webhookId]",
|
||||
Short: "Show a webhook",
|
||||
Long: "Show the webhook specified by [webhookId]",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Example: " webhook show w16zb5tu3n1zkqo18goqry1je",
|
||||
RunE: showWebhookCmdF,
|
||||
}
|
||||
|
||||
var WebhookCreateIncomingCmd = &cobra.Command{
|
||||
Use: "create-incoming",
|
||||
Short: "Create incoming webhook",
|
||||
Long: "create incoming webhook which allows external posting of messages to specific channel",
|
||||
Example: " webhook create-incoming --channel [channelID] --user [userID] --display-name [displayName] --description [webhookDescription] --lock-to-channel --icon [iconURL]",
|
||||
RunE: createIncomingWebhookCmdF,
|
||||
}
|
||||
|
||||
var WebhookModifyIncomingCmd = &cobra.Command{
|
||||
Use: "modify-incoming",
|
||||
Short: "Modify incoming webhook",
|
||||
Long: "Modify existing incoming webhook by changing its title, description, channel or icon url",
|
||||
Example: " webhook modify-incoming [webhookID] --channel [channelID] --display-name [displayName] --description [webhookDescription] --lock-to-channel --icon [iconURL]",
|
||||
RunE: modifyIncomingWebhookCmdF,
|
||||
}
|
||||
|
||||
var WebhookCreateOutgoingCmd = &cobra.Command{
|
||||
Use: "create-outgoing",
|
||||
Short: "Create outgoing webhook",
|
||||
Long: "create outgoing webhook which allows external posting of messages from a specific channel",
|
||||
Example: ` webhook create-outgoing --team myteam --user myusername --display-name mywebhook --trigger-word "build" --trigger-word "test" --url http://localhost:8000/my-webhook-handler
|
||||
webhook create-outgoing --team myteam --channel mychannel --user myusername --display-name mywebhook --description "My cool webhook" --trigger-when start --trigger-word build --trigger-word test --icon http://localhost:8000/my-slash-handler-bot-icon.png --url http://localhost:8000/my-webhook-handler --content-type "application/json"`,
|
||||
RunE: createOutgoingWebhookCmdF,
|
||||
}
|
||||
|
||||
var WebhookModifyOutgoingCmd = &cobra.Command{
|
||||
Use: "modify-outgoing",
|
||||
Short: "Modify outgoing webhook",
|
||||
Long: "Modify existing outgoing webhook by changing its title, description, channel, icon, url, content-type, and triggers",
|
||||
Example: ` webhook modify-outgoing [webhookId] --channel [channelId] --display-name [displayName] --description "New webhook description" --icon http://localhost:8000/my-slash-handler-bot-icon.png --url http://localhost:8000/my-webhook-handler --content-type "application/json" --trigger-word test --trigger-when start`,
|
||||
RunE: modifyOutgoingWebhookCmdF,
|
||||
}
|
||||
|
||||
var WebhookDeleteCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete webhooks",
|
||||
Long: "Delete webhook with given id",
|
||||
Example: " webhook delete [webhookID]",
|
||||
RunE: deleteWebhookCmdF,
|
||||
}
|
||||
|
||||
var WebhookMoveOutgoingCmd = &cobra.Command{
|
||||
Use: "move-outgoing",
|
||||
Short: "Move outgoing webhook",
|
||||
Long: "Move outgoing webhook with an id",
|
||||
Example: " webhook move-outgoing newteam oldteam:webhook-id --channel new-default-channel",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: moveOutgoingWebhookCmd,
|
||||
}
|
||||
|
||||
func listWebhookCmdF(command *cobra.Command, args []string) error {
|
||||
app, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer app.Srv().Shutdown()
|
||||
|
||||
var teams []*model.Team
|
||||
if len(args) < 1 {
|
||||
var getErr *model.AppError
|
||||
// If no team is specified, list all teams
|
||||
teams, getErr = app.GetAllTeams()
|
||||
if getErr != nil {
|
||||
return getErr
|
||||
}
|
||||
} else {
|
||||
teams = getTeamsFromTeamArgs(app, args)
|
||||
}
|
||||
|
||||
for i, team := range teams {
|
||||
if team == nil {
|
||||
CommandPrintErrorln("Unable to find team '" + args[i] + "'")
|
||||
continue
|
||||
}
|
||||
|
||||
// Fetch all hooks with a very large limit so we get them all.
|
||||
incomingResult := make(chan store.StoreResult, 1)
|
||||
go func() {
|
||||
incomingHooks, err := app.Srv().Store.Webhook().GetIncomingByTeam(team.Id, 0, 100000000)
|
||||
incomingResult <- store.StoreResult{Data: incomingHooks, NErr: err}
|
||||
close(incomingResult)
|
||||
}()
|
||||
outgoingResult := make(chan store.StoreResult, 1)
|
||||
go func() {
|
||||
outgoingHooks, err := app.Srv().Store.Webhook().GetOutgoingByTeam(team.Id, 0, 100000000)
|
||||
outgoingResult <- store.StoreResult{Data: outgoingHooks, NErr: err}
|
||||
close(outgoingResult)
|
||||
}()
|
||||
|
||||
if result := <-incomingResult; result.NErr == nil {
|
||||
CommandPrettyPrintln(fmt.Sprintf("Incoming webhooks for %s (%s):", team.DisplayName, team.Name))
|
||||
hooks := result.Data.([]*model.IncomingWebhook)
|
||||
for _, hook := range hooks {
|
||||
CommandPrettyPrintln("\t" + hook.DisplayName + " (" + hook.Id + ")")
|
||||
}
|
||||
} else {
|
||||
CommandPrintErrorln("Unable to list incoming webhooks for '" + args[i] + "'")
|
||||
}
|
||||
|
||||
if result := <-outgoingResult; result.NErr == nil {
|
||||
hooks := result.Data.([]*model.OutgoingWebhook)
|
||||
CommandPrettyPrintln(fmt.Sprintf("Outgoing webhooks for %s (%s):", team.DisplayName, team.Name))
|
||||
for _, hook := range hooks {
|
||||
CommandPrettyPrintln("\t" + hook.DisplayName + " (" + hook.Id + ")")
|
||||
}
|
||||
} else {
|
||||
CommandPrintErrorln("Unable to list outgoing webhooks for '" + args[i] + "'")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createIncomingWebhookCmdF(command *cobra.Command, args []string) error {
|
||||
app, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer app.Srv().Shutdown()
|
||||
|
||||
channelArg, errChannel := command.Flags().GetString("channel")
|
||||
if errChannel != nil || channelArg == "" {
|
||||
return errors.New("Channel is required")
|
||||
}
|
||||
channel := getChannelFromChannelArg(app, channelArg)
|
||||
if channel == nil {
|
||||
return errors.New("Unable to find channel '" + channelArg + "'")
|
||||
}
|
||||
|
||||
userArg, errUser := command.Flags().GetString("user")
|
||||
if errUser != nil || userArg == "" {
|
||||
return errors.New("User is required")
|
||||
}
|
||||
user := getUserFromUserArg(app, userArg)
|
||||
if user == nil {
|
||||
return errors.New("Unable to find user '" + userArg + "'")
|
||||
}
|
||||
|
||||
displayName, _ := command.Flags().GetString("display-name")
|
||||
description, _ := command.Flags().GetString("description")
|
||||
iconURL, _ := command.Flags().GetString("icon")
|
||||
channelLocked, _ := command.Flags().GetBool("lock-to-channel")
|
||||
|
||||
incomingWebhook := &model.IncomingWebhook{
|
||||
ChannelId: channel.Id,
|
||||
DisplayName: displayName,
|
||||
Description: description,
|
||||
IconURL: iconURL,
|
||||
ChannelLocked: channelLocked,
|
||||
}
|
||||
|
||||
createdIncoming, errIncomingWebhook := app.CreateIncomingWebhookForChannel(user.Id, channel, incomingWebhook)
|
||||
if errIncomingWebhook != nil {
|
||||
return errIncomingWebhook
|
||||
}
|
||||
|
||||
CommandPrettyPrintln("Id: " + createdIncoming.Id)
|
||||
CommandPrettyPrintln("Display Name: " + createdIncoming.DisplayName)
|
||||
|
||||
auditRec := app.MakeAuditRecord("createIncomingWebhook", audit.Success)
|
||||
auditRec.AddMeta("user", user)
|
||||
auditRec.AddMeta("channel", channel)
|
||||
auditRec.AddMeta("hook", createdIncoming)
|
||||
app.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func modifyIncomingWebhookCmdF(command *cobra.Command, args []string) (cmdError error) {
|
||||
app, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer app.Srv().Shutdown()
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.New("WebhookID is not specified")
|
||||
}
|
||||
|
||||
webhookArg := args[0]
|
||||
oldHook, getErr := app.GetIncomingWebhook(webhookArg)
|
||||
if getErr != nil {
|
||||
return errors.New("Unable to find webhook '" + webhookArg + "'")
|
||||
}
|
||||
|
||||
updatedHook := oldHook
|
||||
|
||||
auditRec := app.MakeAuditRecord("createIncomingWebhook", audit.Fail)
|
||||
defer func() { app.LogAuditRec(auditRec, cmdError) }()
|
||||
auditRec.AddMeta("hook", oldHook)
|
||||
|
||||
channelArg, _ := command.Flags().GetString("channel")
|
||||
if channelArg != "" {
|
||||
channel := getChannelFromChannelArg(app, channelArg)
|
||||
if channel == nil {
|
||||
return errors.New("Unable to find channel '" + channelArg + "'")
|
||||
}
|
||||
updatedHook.ChannelId = channel.Id
|
||||
}
|
||||
|
||||
displayName, _ := command.Flags().GetString("display-name")
|
||||
if displayName != "" {
|
||||
updatedHook.DisplayName = displayName
|
||||
}
|
||||
description, _ := command.Flags().GetString("description")
|
||||
if description != "" {
|
||||
updatedHook.Description = description
|
||||
}
|
||||
iconURL, _ := command.Flags().GetString("icon")
|
||||
if iconURL != "" {
|
||||
updatedHook.IconURL = iconURL
|
||||
}
|
||||
channelLocked, _ := command.Flags().GetBool("lock-to-channel")
|
||||
updatedHook.ChannelLocked = channelLocked
|
||||
|
||||
updatedIncomingHook, errUpdated := app.UpdateIncomingWebhook(oldHook, updatedHook)
|
||||
if errUpdated != nil {
|
||||
return errUpdated
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddMeta("update", updatedIncomingHook)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createOutgoingWebhookCmdF(command *cobra.Command, args []string) error {
|
||||
app, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer app.Srv().Shutdown()
|
||||
|
||||
teamArg, errTeam := command.Flags().GetString("team")
|
||||
if errTeam != nil || teamArg == "" {
|
||||
return errors.New("Team is required")
|
||||
}
|
||||
team := getTeamFromTeamArg(app, teamArg)
|
||||
if team == nil {
|
||||
return errors.New("Unable to find team: " + teamArg)
|
||||
}
|
||||
|
||||
userArg, errUser := command.Flags().GetString("user")
|
||||
if errUser != nil || userArg == "" {
|
||||
return errors.New("User is required")
|
||||
}
|
||||
user := getUserFromUserArg(app, userArg)
|
||||
if user == nil {
|
||||
return errors.New("Unable to find user: " + userArg)
|
||||
}
|
||||
|
||||
displayName, errName := command.Flags().GetString("display-name")
|
||||
if errName != nil || displayName == "" {
|
||||
return errors.New("Display name is required")
|
||||
}
|
||||
|
||||
triggerWords, errWords := command.Flags().GetStringArray("trigger-word")
|
||||
if errWords != nil || len(triggerWords) == 0 {
|
||||
return errors.New("Trigger word or words required")
|
||||
}
|
||||
|
||||
callbackURLs, errURL := command.Flags().GetStringArray("url")
|
||||
if errURL != nil || len(callbackURLs) == 0 {
|
||||
return errors.New("Callback URL or URLs required")
|
||||
}
|
||||
|
||||
triggerWhenString, _ := command.Flags().GetString("trigger-when")
|
||||
var triggerWhen int
|
||||
if triggerWhenString == "exact" {
|
||||
triggerWhen = 0
|
||||
} else if triggerWhenString == "start" {
|
||||
triggerWhen = 1
|
||||
} else {
|
||||
return errors.New("Invalid trigger when parameter")
|
||||
}
|
||||
description, _ := command.Flags().GetString("description")
|
||||
contentType, _ := command.Flags().GetString("content-type")
|
||||
iconURL, _ := command.Flags().GetString("icon")
|
||||
|
||||
outgoingWebhook := &model.OutgoingWebhook{
|
||||
CreatorId: user.Id,
|
||||
Username: user.Username,
|
||||
TeamId: team.Id,
|
||||
TriggerWords: triggerWords,
|
||||
TriggerWhen: triggerWhen,
|
||||
CallbackURLs: callbackURLs,
|
||||
DisplayName: displayName,
|
||||
Description: description,
|
||||
ContentType: contentType,
|
||||
IconURL: iconURL,
|
||||
}
|
||||
|
||||
var channel *model.Channel
|
||||
channelArg, _ := command.Flags().GetString("channel")
|
||||
if channelArg != "" {
|
||||
channel = getChannelFromChannelArg(app, channelArg)
|
||||
if channel != nil {
|
||||
outgoingWebhook.ChannelId = channel.Id
|
||||
}
|
||||
}
|
||||
|
||||
createdOutgoing, errOutgoing := app.CreateOutgoingWebhook(outgoingWebhook)
|
||||
if errOutgoing != nil {
|
||||
return errOutgoing
|
||||
}
|
||||
|
||||
CommandPrettyPrintln("Id: " + createdOutgoing.Id)
|
||||
CommandPrettyPrintln("Display Name: " + createdOutgoing.DisplayName)
|
||||
|
||||
auditRec := app.MakeAuditRecord("createOutgoingWebhook", audit.Success)
|
||||
auditRec.AddMeta("user", user)
|
||||
auditRec.AddMeta("hook", createdOutgoing)
|
||||
if channel != nil {
|
||||
auditRec.AddMeta("channel", channel)
|
||||
}
|
||||
app.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func modifyOutgoingWebhookCmdF(command *cobra.Command, args []string) error {
|
||||
app, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer app.Srv().Shutdown()
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.New("WebhookID is not specified")
|
||||
}
|
||||
|
||||
webhookArg := args[0]
|
||||
oldHook, appErr := app.GetOutgoingWebhook(webhookArg)
|
||||
if appErr != nil {
|
||||
return fmt.Errorf("unable to find webhook '%s'", webhookArg)
|
||||
}
|
||||
|
||||
updatedHook := model.OutgoingWebhookFromJson(strings.NewReader(oldHook.ToJson()))
|
||||
|
||||
channelArg, _ := command.Flags().GetString("channel")
|
||||
if channelArg != "" {
|
||||
channel := getChannelFromChannelArg(app, channelArg)
|
||||
if channel == nil {
|
||||
return fmt.Errorf("unable to find channel '%s'", channelArg)
|
||||
}
|
||||
updatedHook.ChannelId = channel.Id
|
||||
}
|
||||
|
||||
displayName, _ := command.Flags().GetString("display-name")
|
||||
if displayName != "" {
|
||||
updatedHook.DisplayName = displayName
|
||||
}
|
||||
|
||||
description, _ := command.Flags().GetString("description")
|
||||
if description != "" {
|
||||
updatedHook.Description = description
|
||||
}
|
||||
|
||||
triggerWords, err := command.Flags().GetStringArray("trigger-word")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid trigger-word parameter")
|
||||
}
|
||||
if len(triggerWords) > 0 {
|
||||
updatedHook.TriggerWords = triggerWords
|
||||
}
|
||||
|
||||
triggerWhenString, _ := command.Flags().GetString("trigger-when")
|
||||
if triggerWhenString != "" {
|
||||
var triggerWhen int
|
||||
if triggerWhenString == "exact" {
|
||||
triggerWhen = 0
|
||||
} else if triggerWhenString == "start" {
|
||||
triggerWhen = 1
|
||||
} else {
|
||||
return errors.New("invalid trigger-when parameter")
|
||||
}
|
||||
updatedHook.TriggerWhen = triggerWhen
|
||||
}
|
||||
|
||||
iconURL, _ := command.Flags().GetString("icon")
|
||||
if iconURL != "" {
|
||||
updatedHook.IconURL = iconURL
|
||||
}
|
||||
|
||||
contentType, _ := command.Flags().GetString("content-type")
|
||||
if contentType != "" {
|
||||
updatedHook.ContentType = contentType
|
||||
}
|
||||
|
||||
callbackURLs, err := command.Flags().GetStringArray("url")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid URL parameter")
|
||||
}
|
||||
if len(callbackURLs) > 0 {
|
||||
updatedHook.CallbackURLs = callbackURLs
|
||||
}
|
||||
|
||||
updatedWebhook, appErr := app.UpdateOutgoingWebhook(oldHook, updatedHook)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
auditRec := app.MakeAuditRecord("modifyOutgoingWebhook", audit.Success)
|
||||
auditRec.AddMeta("hook", oldHook)
|
||||
auditRec.AddMeta("update", updatedWebhook)
|
||||
app.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteWebhookCmdF(command *cobra.Command, args []string) error {
|
||||
app, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer app.Srv().Shutdown()
|
||||
|
||||
if len(args) < 1 {
|
||||
return errors.New("WebhookID is not specified")
|
||||
}
|
||||
|
||||
webhookId := args[0]
|
||||
errIncomingWebhook := app.DeleteIncomingWebhook(webhookId)
|
||||
errOutgoingWebhook := app.DeleteOutgoingWebhook(webhookId)
|
||||
|
||||
if errIncomingWebhook != nil && errOutgoingWebhook != nil {
|
||||
return errors.New("Unable to delete webhook '" + webhookId + "'")
|
||||
}
|
||||
|
||||
auditRec := app.MakeAuditRecord("deleteWebhook", audit.Success)
|
||||
auditRec.AddMeta("hook_id", webhookId)
|
||||
app.LogAuditRec(auditRec, nil)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func showWebhookCmdF(command *cobra.Command, args []string) error {
|
||||
app, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer app.Srv().Shutdown()
|
||||
|
||||
webhookId := args[0]
|
||||
if incomingWebhook, err := app.GetIncomingWebhook(webhookId); err == nil {
|
||||
fmt.Printf("%s", prettyPrintStruct(*incomingWebhook))
|
||||
return nil
|
||||
}
|
||||
if outgoingWebhook, err := app.GetOutgoingWebhook(webhookId); err == nil {
|
||||
fmt.Printf("%s", prettyPrintStruct(*outgoingWebhook))
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("Webhook with id " + webhookId + " not found")
|
||||
}
|
||||
|
||||
func moveOutgoingWebhookCmd(command *cobra.Command, args []string) (cmdError error) {
|
||||
app, err := InitDBCommandContextCobra(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer app.Srv().Shutdown()
|
||||
|
||||
newTeamId := args[0]
|
||||
_, teamError := app.GetTeam(newTeamId)
|
||||
if teamError != nil {
|
||||
return teamError
|
||||
}
|
||||
|
||||
webhookInformation := strings.Split(args[1], ":")
|
||||
sourceTeam := webhookInformation[0]
|
||||
_, teamErr := app.GetTeam(sourceTeam)
|
||||
if teamErr != nil {
|
||||
return teamErr
|
||||
}
|
||||
|
||||
webhookId := webhookInformation[1]
|
||||
webhook, appError := app.GetOutgoingWebhook(webhookId)
|
||||
if appError != nil {
|
||||
return appError
|
||||
}
|
||||
|
||||
auditRec := app.MakeAuditRecord("moveOutgoingWebhook", audit.Fail)
|
||||
defer func() { app.LogAuditRec(auditRec, cmdError) }()
|
||||
auditRec.AddMeta("hook", webhook)
|
||||
|
||||
channelName, channelErr := command.Flags().GetString("channel")
|
||||
if channelErr != nil {
|
||||
return channelErr
|
||||
}
|
||||
channel, getChannelErr := app.GetChannelByName(channelName, newTeamId, false)
|
||||
|
||||
if webhook.ChannelId != "" {
|
||||
if getChannelErr != nil {
|
||||
return getChannelErr
|
||||
}
|
||||
webhook.ChannelId = channel.Id
|
||||
} else if channelName != "" {
|
||||
webhook.ChannelId = channel.Id
|
||||
}
|
||||
|
||||
deleteErr := app.DeleteOutgoingWebhook(webhook.Id)
|
||||
if deleteErr != nil {
|
||||
return deleteErr
|
||||
}
|
||||
|
||||
webhook.Id = ""
|
||||
webhook.TeamId = newTeamId
|
||||
|
||||
updatedWebHook, createErr := app.CreateOutgoingWebhook(webhook)
|
||||
if createErr != nil {
|
||||
return model.NewAppError("moveOutgoingWebhookCmd", "cli.outgoing_webhook.inconsistent_state.app_error", nil, createErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
auditRec.AddMeta("update", updatedWebHook)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
WebhookCreateIncomingCmd.Flags().String("channel", "", "Channel ID (required)")
|
||||
WebhookCreateIncomingCmd.Flags().String("user", "", "User ID (required)")
|
||||
WebhookCreateIncomingCmd.Flags().String("display-name", "", "Incoming webhook display name")
|
||||
WebhookCreateIncomingCmd.Flags().String("description", "", "Incoming webhook description")
|
||||
WebhookCreateIncomingCmd.Flags().String("icon", "", "Icon URL")
|
||||
WebhookCreateIncomingCmd.Flags().Bool("lock-to-channel", false, "Lock to channel")
|
||||
|
||||
WebhookModifyIncomingCmd.Flags().String("channel", "", "Channel ID")
|
||||
WebhookModifyIncomingCmd.Flags().String("display-name", "", "Incoming webhook display name")
|
||||
WebhookModifyIncomingCmd.Flags().String("description", "", "Incoming webhook description")
|
||||
WebhookModifyIncomingCmd.Flags().String("icon", "", "Icon URL")
|
||||
WebhookModifyIncomingCmd.Flags().Bool("lock-to-channel", false, "Lock to channel")
|
||||
|
||||
WebhookCreateOutgoingCmd.Flags().String("team", "", "Team name or ID (required)")
|
||||
WebhookCreateOutgoingCmd.Flags().String("channel", "", "Channel name or ID")
|
||||
WebhookCreateOutgoingCmd.Flags().String("user", "", "User username, email, or ID (required)")
|
||||
WebhookCreateOutgoingCmd.Flags().String("display-name", "", "Outgoing webhook display name (required)")
|
||||
WebhookCreateOutgoingCmd.Flags().String("description", "", "Outgoing webhook description")
|
||||
WebhookCreateOutgoingCmd.Flags().StringArray("trigger-word", []string{}, "Word to trigger webhook (required)")
|
||||
WebhookCreateOutgoingCmd.Flags().String("trigger-when", "exact", "When to trigger webhook (exact: for first word matches a trigger word exactly, start: for first word starts with a trigger word)")
|
||||
WebhookCreateOutgoingCmd.Flags().String("icon", "", "Icon URL")
|
||||
WebhookCreateOutgoingCmd.Flags().StringArray("url", []string{}, "Callback URL (required)")
|
||||
WebhookCreateOutgoingCmd.Flags().String("content-type", "", "Content-type")
|
||||
|
||||
WebhookModifyOutgoingCmd.Flags().String("channel", "", "Channel name or ID")
|
||||
WebhookModifyOutgoingCmd.Flags().String("display-name", "", "Outgoing webhook display name")
|
||||
WebhookModifyOutgoingCmd.Flags().String("description", "", "Outgoing webhook description")
|
||||
WebhookModifyOutgoingCmd.Flags().StringArray("trigger-word", []string{}, "Word to trigger webhook")
|
||||
WebhookModifyOutgoingCmd.Flags().String("trigger-when", "", "When to trigger webhook (exact: for first word matches a trigger word exactly, start: for first word starts with a trigger word)")
|
||||
WebhookModifyOutgoingCmd.Flags().String("icon", "", "Icon URL")
|
||||
WebhookModifyOutgoingCmd.Flags().StringArray("url", []string{}, "Callback URL")
|
||||
WebhookModifyOutgoingCmd.Flags().String("content-type", "", "Content-type")
|
||||
|
||||
WebhookMoveOutgoingCmd.Flags().String("channel", "", "Channel name or ID")
|
||||
|
||||
WebhookCmd.AddCommand(
|
||||
WebhookListCmd,
|
||||
WebhookCreateIncomingCmd,
|
||||
WebhookModifyIncomingCmd,
|
||||
WebhookCreateOutgoingCmd,
|
||||
WebhookModifyOutgoingCmd,
|
||||
WebhookDeleteCmd,
|
||||
WebhookShowCmd,
|
||||
WebhookMoveOutgoingCmd,
|
||||
)
|
||||
|
||||
RootCmd.AddCommand(WebhookCmd)
|
||||
}
|
||||
@@ -1,532 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
)
|
||||
|
||||
func TestListWebhooks(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
adminClient := th.SystemAdminClient
|
||||
|
||||
config := th.Config()
|
||||
*config.ServiceSettings.EnableCommands = true
|
||||
*config.ServiceSettings.EnableIncomingWebhooks = true
|
||||
*config.ServiceSettings.EnableOutgoingWebhooks = true
|
||||
*config.ServiceSettings.EnablePostUsernameOverride = true
|
||||
*config.ServiceSettings.EnablePostIconOverride = true
|
||||
th.SetConfig(config)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOutgoingWebhooks = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostUsernameOverride = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostIconOverride = true })
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PermissionManageIncomingWebhooks.Id, model.TeamAdminRoleId)
|
||||
th.RemovePermissionFromRole(model.PermissionManageIncomingWebhooks.Id, model.TeamUserRoleId)
|
||||
th.AddPermissionToRole(model.PermissionManageOutgoingWebhooks.Id, model.TeamAdminRoleId)
|
||||
th.RemovePermissionFromRole(model.PermissionManageOutgoingWebhooks.Id, model.TeamUserRoleId)
|
||||
|
||||
dispName := "myhookinc"
|
||||
hook := &model.IncomingWebhook{DisplayName: dispName, ChannelId: th.BasicChannel.Id, TeamId: th.BasicChannel.TeamId}
|
||||
_, _, err := adminClient.CreateIncomingWebhook(hook)
|
||||
require.NoError(t, err)
|
||||
|
||||
dispName2 := "myhookout"
|
||||
outHook := &model.OutgoingWebhook{DisplayName: dispName2, ChannelId: th.BasicChannel.Id, TeamId: th.BasicChannel.TeamId, CallbackURLs: []string{"http://nowhere.com"}, Username: "some-user-name", IconURL: "http://some-icon-url/"}
|
||||
_, _, err = adminClient.CreateOutgoingWebhook(outHook)
|
||||
require.NoError(t, err)
|
||||
|
||||
output := th.CheckCommand(t, "webhook", "list", th.BasicTeam.Name)
|
||||
|
||||
assert.Contains(t, output, dispName, "should have incoming webhooks")
|
||||
assert.Contains(t, output, dispName2, "should have outgoing webhooks")
|
||||
}
|
||||
|
||||
func TestShowWebhook(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
adminClient := th.SystemAdminClient
|
||||
|
||||
config := th.Config()
|
||||
*config.ServiceSettings.EnableCommands = true
|
||||
*config.ServiceSettings.EnableIncomingWebhooks = true
|
||||
*config.ServiceSettings.EnableOutgoingWebhooks = true
|
||||
*config.ServiceSettings.EnablePostUsernameOverride = true
|
||||
*config.ServiceSettings.EnablePostIconOverride = true
|
||||
th.SetConfig(config)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOutgoingWebhooks = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostUsernameOverride = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostIconOverride = true })
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PermissionManageIncomingWebhooks.Id, model.TeamAdminRoleId)
|
||||
th.RemovePermissionFromRole(model.PermissionManageIncomingWebhooks.Id, model.TeamUserRoleId)
|
||||
th.AddPermissionToRole(model.PermissionManageOutgoingWebhooks.Id, model.TeamAdminRoleId)
|
||||
th.RemovePermissionFromRole(model.PermissionManageOutgoingWebhooks.Id, model.TeamUserRoleId)
|
||||
|
||||
dispName := "incominghook"
|
||||
hook := &model.IncomingWebhook{
|
||||
DisplayName: dispName,
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
TeamId: th.BasicChannel.TeamId,
|
||||
}
|
||||
incomingWebhook, _, err := adminClient.CreateIncomingWebhook(hook)
|
||||
require.NoError(t, err)
|
||||
|
||||
// should return an error when no webhookid is provided
|
||||
require.Error(t, th.RunCommand(t, "webhook", "show"))
|
||||
|
||||
// invalid webhook should return error
|
||||
require.Error(t, th.RunCommand(t, "webhook", "show", "invalid-webhook"))
|
||||
|
||||
// valid incoming webhook should return webhook data
|
||||
output := th.CheckCommand(t, "webhook", "show", incomingWebhook.Id)
|
||||
assert.Contains(t, output, "DisplayName: \""+dispName+"\"", "incoming: should have incominghook as displayname")
|
||||
assert.Contains(t, output, "ChannelId: \""+hook.ChannelId+"\"", "incoming: should have a valid channelId")
|
||||
|
||||
dispName = "outgoinghook"
|
||||
outgoingHook := &model.OutgoingWebhook{
|
||||
DisplayName: dispName,
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
TeamId: th.BasicChannel.TeamId,
|
||||
CallbackURLs: []string{"http://nowhere.com"},
|
||||
Username: "some-user-name",
|
||||
IconURL: "http://some-icon-url/",
|
||||
}
|
||||
outgoingWebhook, _, err := adminClient.CreateOutgoingWebhook(outgoingHook)
|
||||
require.NoError(t, err)
|
||||
|
||||
// valid outgoing webhook should return webhook data
|
||||
output = th.CheckCommand(t, "webhook", "show", outgoingWebhook.Id)
|
||||
|
||||
assert.Contains(t, output, "DisplayName: \""+dispName+"\"", "outgoing: should have outgoinghook as displayname")
|
||||
assert.Contains(t, output, "ChannelId: \""+hook.ChannelId+"\"", "outgoing: should have a valid channelId")
|
||||
}
|
||||
|
||||
func TestCreateIncomingWebhook(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
config := th.Config()
|
||||
*config.ServiceSettings.EnableCommands = true
|
||||
*config.ServiceSettings.EnableIncomingWebhooks = true
|
||||
*config.ServiceSettings.EnableOutgoingWebhooks = true
|
||||
*config.ServiceSettings.EnablePostUsernameOverride = true
|
||||
*config.ServiceSettings.EnablePostIconOverride = true
|
||||
th.SetConfig(config)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOutgoingWebhooks = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostUsernameOverride = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostIconOverride = true })
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PermissionManageIncomingWebhooks.Id, model.TeamAdminRoleId)
|
||||
th.RemovePermissionFromRole(model.PermissionManageIncomingWebhooks.Id, model.TeamUserRoleId)
|
||||
|
||||
// should fail because you need to specify valid channel
|
||||
require.Error(t, th.RunCommand(t, "webhook", "create-incoming"))
|
||||
require.Error(t, th.RunCommand(t, "webhook", "create-incoming", "--channel", th.BasicTeam.Name+":doesnotexist"))
|
||||
|
||||
// should fail because you need to specify valid user
|
||||
require.Error(t, th.RunCommand(t, "webhook", "create-incoming", "--channel", th.BasicChannel.Id))
|
||||
require.Error(t, th.RunCommand(t, "webhook", "create-incoming", "--channel", th.BasicChannel.Id, "--user", "doesnotexist"))
|
||||
|
||||
description := "myhookinc"
|
||||
displayName := "myhookinc"
|
||||
th.CheckCommand(t, "webhook", "create-incoming", "--channel", th.BasicChannel.Id, "--user", th.BasicUser.Email, "--description", description, "--display-name", displayName)
|
||||
|
||||
webhooks, err := th.App.GetIncomingWebhooksPage(0, 1000)
|
||||
require.Nil(t, err, "unable to retrieve incoming webhooks")
|
||||
|
||||
found := false
|
||||
for _, webhook := range webhooks {
|
||||
if webhook.Description == description && webhook.UserId == th.BasicUser.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
require.True(t, found, "Failed to create incoming webhook")
|
||||
}
|
||||
|
||||
func TestModifyIncomingWebhook(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
config := th.Config()
|
||||
*config.ServiceSettings.EnableCommands = true
|
||||
*config.ServiceSettings.EnableIncomingWebhooks = true
|
||||
*config.ServiceSettings.EnableOutgoingWebhooks = true
|
||||
*config.ServiceSettings.EnablePostUsernameOverride = true
|
||||
*config.ServiceSettings.EnablePostIconOverride = true
|
||||
th.SetConfig(config)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOutgoingWebhooks = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostUsernameOverride = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostIconOverride = true })
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PermissionManageIncomingWebhooks.Id, model.TeamAdminRoleId)
|
||||
th.RemovePermissionFromRole(model.PermissionManageIncomingWebhooks.Id, model.TeamUserRoleId)
|
||||
|
||||
description := "myhookincdesc"
|
||||
displayName := "myhookincname"
|
||||
|
||||
incomingWebhook := &model.IncomingWebhook{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
DisplayName: displayName,
|
||||
Description: description,
|
||||
}
|
||||
|
||||
oldHook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, incomingWebhook)
|
||||
require.Nil(t, err, "unable to create incoming webhooks")
|
||||
|
||||
defer func() {
|
||||
th.App.DeleteIncomingWebhook(oldHook.Id)
|
||||
}()
|
||||
|
||||
// should fail because you need to specify valid incoming webhook
|
||||
require.Error(t, th.RunCommand(t, "webhook", "modify-incoming", "doesnotexist"))
|
||||
// should fail because you need to specify valid channel
|
||||
require.Error(t, th.RunCommand(t, "webhook", "modify-incoming", oldHook.Id, "--channel", th.BasicTeam.Name+":doesnotexist"))
|
||||
|
||||
modifiedDescription := "myhookincdesc2"
|
||||
modifiedDisplayName := "myhookincname2"
|
||||
modifiedIconURL := "myhookincicon2"
|
||||
modifiedChannelLocked := true
|
||||
modifiedChannelId := th.BasicChannel2.Id
|
||||
|
||||
th.CheckCommand(t, "webhook", "modify-incoming", oldHook.Id, "--channel", modifiedChannelId, "--description", modifiedDescription, "--display-name", modifiedDisplayName, "--icon", modifiedIconURL, "--lock-to-channel", strconv.FormatBool(modifiedChannelLocked))
|
||||
|
||||
modifiedHook, err := th.App.GetIncomingWebhook(oldHook.Id)
|
||||
require.Nil(t, err, "unable to retrieve modified incoming webhook")
|
||||
|
||||
successUpdate := modifiedHook.DisplayName != modifiedDisplayName ||
|
||||
modifiedHook.Description != modifiedDescription ||
|
||||
modifiedHook.IconURL != modifiedIconURL ||
|
||||
modifiedHook.ChannelLocked != modifiedChannelLocked ||
|
||||
modifiedHook.ChannelId != modifiedChannelId
|
||||
require.False(t, successUpdate, "Failed to update incoming webhook")
|
||||
}
|
||||
|
||||
func TestCreateOutgoingWebhook(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
config := th.Config()
|
||||
*config.ServiceSettings.EnableCommands = true
|
||||
*config.ServiceSettings.EnableIncomingWebhooks = true
|
||||
*config.ServiceSettings.EnableOutgoingWebhooks = true
|
||||
*config.ServiceSettings.EnablePostUsernameOverride = true
|
||||
*config.ServiceSettings.EnablePostIconOverride = true
|
||||
th.SetConfig(config)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOutgoingWebhooks = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostUsernameOverride = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostIconOverride = true })
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PermissionManageOutgoingWebhooks.Id, model.TeamAdminRoleId)
|
||||
th.RemovePermissionFromRole(model.PermissionManageOutgoingWebhooks.Id, model.TeamUserRoleId)
|
||||
|
||||
// team, user, display name, trigger words, callback urls are required
|
||||
team := th.BasicTeam.Id
|
||||
user := th.BasicUser.Id
|
||||
displayName := "totally radical webhook"
|
||||
triggerWord1 := "build"
|
||||
triggerWord2 := "defenestrate"
|
||||
callbackURL1 := "http://localhost:8000/my-webhook-handler"
|
||||
callbackURL2 := "http://localhost:8000/my-webhook-handler2"
|
||||
|
||||
// should fail because team is not specified
|
||||
require.Error(t, th.RunCommand(t, "webhook", "create-outgoing", "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2, "--user", user))
|
||||
|
||||
// should fail because user is not specified
|
||||
require.Error(t, th.RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2))
|
||||
|
||||
// should fail because display name is not specified
|
||||
require.Error(t, th.RunCommand(t, "webhook", "create-outgoing", "--team", team, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2, "--user", user))
|
||||
|
||||
// should fail because trigger words are not specified
|
||||
require.Error(t, th.RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--url", callbackURL1, "--url", callbackURL2, "--user", user))
|
||||
|
||||
// should fail because callback URLs are not specified
|
||||
require.Error(t, th.RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--user", user))
|
||||
|
||||
// should fail because outgoing webhooks cannot be made for private channels
|
||||
require.Error(t, th.RunCommand(t, "webhook", "create-outgoing", "--team", team, "--channel", th.BasicPrivateChannel.Id, "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2, "--user", user))
|
||||
|
||||
th.CheckCommand(t, "webhook", "create-outgoing", "--team", team, "--channel", th.BasicChannel.Id, "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2, "--user", user)
|
||||
|
||||
webhooks, err := th.App.GetOutgoingWebhooksPage(0, 1000)
|
||||
require.Nil(t, err, "Unable to retrieve outgoing webhooks")
|
||||
|
||||
found := false
|
||||
for _, webhook := range webhooks {
|
||||
if webhook.DisplayName == displayName && webhook.CreatorId == th.BasicUser.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
require.True(t, found, "Failed to create incoming webhook")
|
||||
}
|
||||
|
||||
func TestModifyOutgoingWebhook(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
config := th.Config()
|
||||
*config.ServiceSettings.EnableOutgoingWebhooks = true
|
||||
th.SetConfig(config)
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PermissionManageOutgoingWebhooks.Id, model.TeamAdminRoleId)
|
||||
th.RemovePermissionFromRole(model.PermissionManageOutgoingWebhooks.Id, model.TeamUserRoleId)
|
||||
|
||||
description := "myhookoutdesc"
|
||||
displayName := "myhookoutname"
|
||||
triggerWords := model.StringArray{"myhookoutword1"}
|
||||
triggerWhen := 0
|
||||
callbackURLs := model.StringArray{"http://myhookouturl1"}
|
||||
iconURL := "myhookicon1"
|
||||
contentType := "myhookcontent1"
|
||||
|
||||
outgoingWebhook := &model.OutgoingWebhook{
|
||||
CreatorId: th.BasicUser.Id,
|
||||
Username: th.BasicUser.Username,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
DisplayName: displayName,
|
||||
Description: description,
|
||||
TriggerWords: triggerWords,
|
||||
TriggerWhen: triggerWhen,
|
||||
CallbackURLs: callbackURLs,
|
||||
IconURL: iconURL,
|
||||
ContentType: contentType,
|
||||
}
|
||||
|
||||
oldHook, err := th.App.CreateOutgoingWebhook(outgoingWebhook)
|
||||
require.Nil(t, err, "unable to create outgoing webhooks: ")
|
||||
|
||||
defer func() {
|
||||
th.App.DeleteOutgoingWebhook(oldHook.Id)
|
||||
}()
|
||||
|
||||
// should fail because you need to specify valid outgoing webhook
|
||||
require.Error(t, th.RunCommand(t, "webhook", "modify-outgoing", "doesnotexist"))
|
||||
// should fail because you need to specify valid channel
|
||||
require.Error(t, th.RunCommand(t, "webhook", "modify-outgoing", oldHook.Id, "--channel", th.BasicTeam.Name+":doesnotexist"))
|
||||
// should fail because you need to specify valid trigger when
|
||||
require.Error(t, th.RunCommand(t, "webhook", "modify-outgoing", oldHook.Id, "--channel", th.BasicTeam.Name+th.BasicChannel.Id, "--trigger-when", "invalid"))
|
||||
// should fail because you need to specify a valid callback URL
|
||||
require.Error(t, th.RunCommand(t, "webhook", "modify-outgoing", oldHook.Id, "--channel", th.BasicTeam.Name+th.BasicChannel.Id, "--callback-url", "invalid"))
|
||||
|
||||
modifiedChannelID := th.BasicChannel2.Id
|
||||
modifiedDisplayName := "myhookoutname2"
|
||||
modifiedDescription := "myhookoutdesc2"
|
||||
modifiedTriggerWords := model.StringArray{"myhookoutword2A", "myhookoutword2B"}
|
||||
modifiedTriggerWhen := "start"
|
||||
modifiedIconURL := "myhookouticon2"
|
||||
modifiedContentType := "myhookcontent2"
|
||||
modifiedCallbackURLs := model.StringArray{"http://myhookouturl2A", "http://myhookouturl2B"}
|
||||
|
||||
th.CheckCommand(t, "webhook", "modify-outgoing", oldHook.Id,
|
||||
"--channel", modifiedChannelID,
|
||||
"--display-name", modifiedDisplayName,
|
||||
"--description", modifiedDescription,
|
||||
"--trigger-word", modifiedTriggerWords[0],
|
||||
"--trigger-word", modifiedTriggerWords[1],
|
||||
"--trigger-when", modifiedTriggerWhen,
|
||||
"--icon", modifiedIconURL,
|
||||
"--content-type", modifiedContentType,
|
||||
"--url", modifiedCallbackURLs[0],
|
||||
"--url", modifiedCallbackURLs[1],
|
||||
)
|
||||
|
||||
modifiedHook, err := th.App.GetOutgoingWebhook(oldHook.Id)
|
||||
require.Nil(t, err, "unable to retrieve modified outgoing webhook")
|
||||
|
||||
updateFailed := modifiedHook.ChannelId != modifiedChannelID ||
|
||||
modifiedHook.DisplayName != modifiedDisplayName ||
|
||||
modifiedHook.Description != modifiedDescription ||
|
||||
len(modifiedHook.TriggerWords) != len(modifiedTriggerWords) ||
|
||||
modifiedHook.TriggerWords[0] != modifiedTriggerWords[0] ||
|
||||
modifiedHook.TriggerWords[1] != modifiedTriggerWords[1] ||
|
||||
modifiedHook.TriggerWhen != 1 ||
|
||||
modifiedHook.IconURL != modifiedIconURL ||
|
||||
modifiedHook.ContentType != modifiedContentType ||
|
||||
len(modifiedHook.CallbackURLs) != len(modifiedCallbackURLs) ||
|
||||
modifiedHook.CallbackURLs[0] != modifiedCallbackURLs[0] ||
|
||||
modifiedHook.CallbackURLs[1] != modifiedCallbackURLs[1]
|
||||
|
||||
require.False(t, updateFailed, "Failed to update outgoing webhook")
|
||||
}
|
||||
|
||||
func TestDeleteWebhooks(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
adminClient := th.SystemAdminClient
|
||||
|
||||
config := th.Config()
|
||||
*config.ServiceSettings.EnableCommands = true
|
||||
*config.ServiceSettings.EnableIncomingWebhooks = true
|
||||
*config.ServiceSettings.EnableOutgoingWebhooks = true
|
||||
*config.ServiceSettings.EnablePostUsernameOverride = true
|
||||
*config.ServiceSettings.EnablePostIconOverride = true
|
||||
th.SetConfig(config)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOutgoingWebhooks = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostUsernameOverride = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostIconOverride = true })
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PermissionManageIncomingWebhooks.Id, model.TeamAdminRoleId)
|
||||
th.RemovePermissionFromRole(model.PermissionManageIncomingWebhooks.Id, model.TeamUserRoleId)
|
||||
th.AddPermissionToRole(model.PermissionManageOutgoingWebhooks.Id, model.TeamAdminRoleId)
|
||||
th.RemovePermissionFromRole(model.PermissionManageOutgoingWebhooks.Id, model.TeamUserRoleId)
|
||||
|
||||
dispName := "myhookinc"
|
||||
inHookStruct := &model.IncomingWebhook{DisplayName: dispName, ChannelId: th.BasicChannel.Id, TeamId: th.BasicChannel.TeamId}
|
||||
incomingHook, _, err := adminClient.CreateIncomingWebhook(inHookStruct)
|
||||
require.NoError(t, err)
|
||||
|
||||
dispName2 := "myhookout"
|
||||
outHookStruct := &model.OutgoingWebhook{DisplayName: dispName2, ChannelId: th.BasicChannel.Id, TeamId: th.BasicChannel.TeamId, CallbackURLs: []string{"http://nowhere.com"}, Username: "some-user-name", IconURL: "http://some-icon-url/"}
|
||||
outgoingHook, _, err := adminClient.CreateOutgoingWebhook(outHookStruct)
|
||||
require.NoError(t, err)
|
||||
|
||||
hooksBeforeDeletion := th.CheckCommand(t, "webhook", "list", th.BasicTeam.Name)
|
||||
|
||||
assert.Contains(t, hooksBeforeDeletion, dispName, "should have incoming webhooks")
|
||||
assert.Contains(t, hooksBeforeDeletion, dispName2, "Should have outgoing webhooks")
|
||||
|
||||
th.CheckCommand(t, "webhook", "delete", incomingHook.Id)
|
||||
th.CheckCommand(t, "webhook", "delete", outgoingHook.Id)
|
||||
|
||||
hooksAfterDeletion := th.CheckCommand(t, "webhook", "list", th.BasicTeam.Name)
|
||||
|
||||
assert.NotContains(t, hooksAfterDeletion, dispName, "Should not have incoming webhooks")
|
||||
assert.NotContains(t, hooksAfterDeletion, dispName2, "Should not have outgoing webhooks")
|
||||
}
|
||||
|
||||
func TestMoveOutgoingWebhook(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
config := th.Config()
|
||||
*config.ServiceSettings.EnableOutgoingWebhooks = true
|
||||
th.SetConfig(config)
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
|
||||
th.AddPermissionToRole(model.PermissionManageOutgoingWebhooks.Id, model.TeamAdminRoleId)
|
||||
th.RemovePermissionFromRole(model.PermissionManageOutgoingWebhooks.Id, model.TeamUserRoleId)
|
||||
|
||||
description := "myhookoutdesc"
|
||||
displayName := "myhookoutname"
|
||||
triggerWords := model.StringArray{"myhookoutword1"}
|
||||
triggerWhen := 0
|
||||
callbackURLs := model.StringArray{"http://myhookouturl1"}
|
||||
iconURL := "myhookicon1"
|
||||
contentType := "myhookcontent1"
|
||||
|
||||
outgoingWebhookWithChannel := &model.OutgoingWebhook{
|
||||
CreatorId: th.BasicUser.Id,
|
||||
Username: th.BasicUser.Username,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
DisplayName: displayName,
|
||||
Description: description,
|
||||
TriggerWords: triggerWords,
|
||||
TriggerWhen: triggerWhen,
|
||||
CallbackURLs: callbackURLs,
|
||||
IconURL: iconURL,
|
||||
ContentType: contentType,
|
||||
}
|
||||
|
||||
oldHook, err := th.App.CreateOutgoingWebhook(outgoingWebhookWithChannel)
|
||||
require.Nil(t, err)
|
||||
defer th.App.DeleteOutgoingWebhook(oldHook.Id)
|
||||
|
||||
require.Error(t, th.RunCommand(t, "webhook", "move-outgoing"))
|
||||
require.Error(t, th.RunCommand(t, "webhook", "move-outgoing", th.BasicTeam.Id))
|
||||
require.Error(t, th.RunCommand(t, "webhook", "move-outgoing", "invalid-team", "webhook"))
|
||||
require.Error(t, th.RunCommand(t, "webhook", "move-outgoing", "invalid-team", "webhook", "--channel"))
|
||||
|
||||
newTeam := th.CreateTeam()
|
||||
|
||||
webhookInformation := "oldTeam" + ":" + "webhookId"
|
||||
require.Error(t, th.RunCommand(t, "webhook", "move-outgoing", newTeam.Id, webhookInformation))
|
||||
|
||||
webhookInformation = th.BasicTeam.Id + ":" + "webhookId"
|
||||
require.Error(t, th.RunCommand(t, "webhook", "move-outgoing", newTeam.Id, webhookInformation))
|
||||
|
||||
require.Error(t, th.RunCommand(t, "webhook", "move-outgoing", newTeam.Id, th.BasicTeam.Id+":"+oldHook.Id, "--channel", "invalid"))
|
||||
|
||||
channel := th.CreateChannelWithClientAndTeam(th.SystemAdminClient, model.ChannelTypeOpen, newTeam.Id)
|
||||
th.CheckCommand(t, "webhook", "move-outgoing", newTeam.Id, th.BasicTeam.Id+":"+oldHook.Id, "--channel", channel.Name)
|
||||
|
||||
_, webhookErr := th.App.GetOutgoingWebhook(oldHook.Id)
|
||||
assert.NotNil(t, webhookErr)
|
||||
|
||||
output := th.CheckCommand(t, "webhook", "list", newTeam.Name)
|
||||
assert.True(t, strings.Contains(output, displayName))
|
||||
|
||||
outgoingWebhookWithoutChannel := &model.OutgoingWebhook{
|
||||
CreatorId: th.BasicUser.Id,
|
||||
Username: th.BasicUser.Username,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
DisplayName: displayName + "2",
|
||||
Description: description,
|
||||
TriggerWords: triggerWords,
|
||||
TriggerWhen: triggerWhen,
|
||||
CallbackURLs: callbackURLs,
|
||||
IconURL: iconURL,
|
||||
ContentType: contentType,
|
||||
}
|
||||
|
||||
oldHook2, err := th.App.CreateOutgoingWebhook(outgoingWebhookWithoutChannel)
|
||||
require.Nil(t, err)
|
||||
defer th.App.DeleteOutgoingWebhook(oldHook2.Id)
|
||||
|
||||
th.CheckCommand(t, "webhook", "move-outgoing", newTeam.Id, th.BasicTeam.Id+":"+oldHook2.Id)
|
||||
output = th.CheckCommand(t, "webhook", "list", newTeam.Name)
|
||||
assert.True(t, strings.Contains(output, displayName+"2"))
|
||||
}
|
||||
12
i18n/en.json
12
i18n/en.json
@@ -2096,10 +2096,6 @@
|
||||
"id": "api.outgoing_webhook.disabled.app_error",
|
||||
"translation": "Outgoing webhooks have been disabled by the system admin."
|
||||
},
|
||||
{
|
||||
"id": "api.plugin.add_public_key.open.app_error",
|
||||
"translation": "An error occurred while opening the public key file."
|
||||
},
|
||||
{
|
||||
"id": "api.plugin.install.download_failed.app_error",
|
||||
"translation": "An error occurred while downloading the plugin."
|
||||
@@ -6766,14 +6762,6 @@
|
||||
"id": "brand.save_brand_image.save_image.app_error",
|
||||
"translation": "Unable to write the image file to your file storage. Please check your connection and try again."
|
||||
},
|
||||
{
|
||||
"id": "cli.license.critical",
|
||||
"translation": "Feature requires an upgrade to Enterprise Edition and the inclusion of a license key. Please contact your System Administrator."
|
||||
},
|
||||
{
|
||||
"id": "cli.outgoing_webhook.inconsistent_state.app_error",
|
||||
"translation": "The outgoing webhook is deleted but unable to create a new one due to some error."
|
||||
},
|
||||
{
|
||||
"id": "ent.account_migration.get_all_failed",
|
||||
"translation": "Unable to get users."
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
jq_cmd=jq
|
||||
[[ $(type -P "$jq_cmd") ]] || {
|
||||
echo "'$jq_cmd' command line JSON processor not found";
|
||||
echo "Please install on linux with 'sudo apt-get install jq'"
|
||||
echo "Please install on mac with 'brew install jq'"
|
||||
exit 1;
|
||||
}
|
||||
./jq-dep-check.sh
|
||||
|
||||
if [ -z "$FROM" ]
|
||||
then
|
||||
|
||||
9
scripts/jq-dep-check.sh
Исполняемый файл
9
scripts/jq-dep-check.sh
Исполняемый файл
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
jq_cmd=jq
|
||||
[[ $(type -P "$jq_cmd") ]] || {
|
||||
echo "'$jq_cmd' command line JSON processor not found";
|
||||
echo "Please install on linux with 'sudo apt-get install jq'"
|
||||
echo "Please install on mac with 'brew install jq'"
|
||||
exit 1;
|
||||
}
|
||||
@@ -1,12 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
jq_cmd=jq
|
||||
[[ $(type -P "$jq_cmd") ]] || {
|
||||
echo "'$jq_cmd' command line JSON processor not found";
|
||||
echo "Please install on linux with 'sudo apt-get install jq'"
|
||||
echo "Please install on mac with 'brew install jq'"
|
||||
exit 1;
|
||||
}
|
||||
./jq-dep-check.sh
|
||||
|
||||
ldapsearch_cmd=ldapsearch
|
||||
[[ $(type -P "$ldapsearch_cmd") ]] || {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
./scripts/jq-dep-check.sh
|
||||
|
||||
TMPDIR=`mktemp -d 2>/dev/null || mktemp -d -t 'tmpConfigDir'`
|
||||
DUMPDIR=`mktemp -d 2>/dev/null || mktemp -d -t 'dumpDir'`
|
||||
|
||||
@@ -10,14 +12,16 @@ echo "Importing mysql dump from version 5.0"
|
||||
docker exec -i mattermost-mysql mysql -D migrated -uroot -pmostest < $(pwd)/scripts/mattermost-mysql-5.0.sql
|
||||
|
||||
echo "Setting up config for db migration"
|
||||
make ARGS="config set SqlSettings.DataSource 'mmuser:mostest@tcp(localhost:3306)/migrated?charset=utf8mb4,utf8&readTimeout=30s&writeTimeout=30s' --config $TMPDIR/config.json" run-cli
|
||||
make ARGS="config set SqlSettings.DriverName 'mysql' --config $TMPDIR/config.json" run-cli
|
||||
cat $TMPDIR/config.json | \
|
||||
jq '.SqlSettings.DataSource = "mmuser:mostest@tcp(localhost:3306)/migrated?charset=utf8mb4,utf8&readTimeout=30s&writeTimeout=30s"' | \
|
||||
jq '.SqlSettings.DriverName = "mysql"' > $TMPDIR/config.json
|
||||
|
||||
echo "Running the migration"
|
||||
make ARGS="version --config $TMPDIR/config.json" run-cli
|
||||
|
||||
echo "Setting up config for fresh db setup"
|
||||
make ARGS="config set SqlSettings.DataSource 'mmuser:mostest@tcp(localhost:3306)/latest?charset=utf8mb4,utf8&readTimeout=30s&writeTimeout=30s' --config $TMPDIR/config.json" run-cli
|
||||
cat $TMPDIR/config.json | \
|
||||
jq '.SqlSettings.DataSource = "mmuser:mostest@tcp(localhost:3306)/latest?charset=utf8mb4,utf8&readTimeout=30s&writeTimeout=30s"' > $TMPDIR/config.json
|
||||
|
||||
echo "Setting up fresh db"
|
||||
make ARGS="version --config $TMPDIR/config.json" run-cli
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
./scripts/jq-dep-check.sh
|
||||
|
||||
TMPDIR=`mktemp -d 2>/dev/null || mktemp -d -t 'tmpConfigDir'`
|
||||
DUMPDIR=`mktemp -d 2>/dev/null || mktemp -d -t 'dumpDir'`
|
||||
|
||||
@@ -10,14 +12,16 @@ echo "Importing postgres dump from version 5.0"
|
||||
docker exec -i mattermost-postgres psql -U mmuser -d migrated < $(pwd)/scripts/mattermost-postgresql-5.0.sql
|
||||
|
||||
echo "Setting up config for db migration"
|
||||
make ARGS="config set SqlSettings.DataSource 'postgres://mmuser:mostest@localhost:5432/migrated?sslmode=disable&connect_timeout=10' --config $TMPDIR/config.json" run-cli
|
||||
make ARGS="config set SqlSettings.DriverName 'postgres' --config $TMPDIR/config.json" run-cli
|
||||
cat $TMPDIR/config.json | \
|
||||
jq '.SqlSettings.DataSource = "postgres://mmuser:mostest@localhost:5432/migrated?sslmode=disable&connect_timeout=10"'| \
|
||||
jq '.SqlSettings.DriverName = "postgres"' > $TMPDIR/config.json
|
||||
|
||||
echo "Running the migration"
|
||||
make ARGS="version --config $TMPDIR/config.json" run-cli
|
||||
|
||||
echo "Setting up config for fresh db setup"
|
||||
make ARGS="config set SqlSettings.DataSource 'postgres://mmuser:mostest@localhost:5432/latest?sslmode=disable&connect_timeout=10' --config $TMPDIR/config.json" run-cli
|
||||
cat $TMPDIR/config.json | \
|
||||
jq '.SqlSettings.DataSource = "postgres://mmuser:mostest@localhost:5432/latest?sslmode=disable&connect_timeout=10"' > $TMPDIR/config.json
|
||||
|
||||
echo "Setting up fresh db"
|
||||
make ARGS="version --config $TMPDIR/config.json" run-cli
|
||||
|
||||
Ссылка в новой задаче
Block a user