Adding enterprise commands support (#8327)

Этот коммит содержится в:
Jesús Espino
2018-03-07 20:04:18 +00:00
коммит произвёл GitHub
родитель 03b6d1f652
Коммит b2dd00dd5b
39 изменённых файлов: 668 добавлений и 550 удалений

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

@@ -1,483 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"errors"
"fmt"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
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 removeChannelUsersCmd = &cobra.Command{
Use: "remove [channel] [users]",
Short: "Remove users from channel",
Long: "Remove some users from channel",
Example: " channel remove mychannel user@example.com username",
RunE: removeChannelUsersCmdF,
}
var addChannelUsersCmd = &cobra.Command{
Use: "add [channel] [users]",
Short: "Add users to channel",
Long: "Add some users to channel",
Example: " channel add mychannel user@example.com username",
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",
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",
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)'.`,
Example: " channel list myteam",
RunE: listChannelsCmdF,
}
var moveChannelsCmd = &cobra.Command{
Use: "move [team] [channels]",
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",
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",
RunE: restoreChannelsCmdF,
}
var modifyChannelCmd = &cobra.Command{
Use: "modify [channel]",
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",
RunE: modifyChannelCmdF,
}
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.")
channelCmd.AddCommand(
channelCreateCmd,
removeChannelUsersCmd,
addChannelUsersCmd,
archiveChannelsCmd,
deleteChannelsCmd,
listChannelsCmd,
moveChannelsCmd,
restoreChannelsCmd,
modifyChannelCmd,
)
}
func createChannelCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
name, errn := cmd.Flags().GetString("name")
if errn != nil || name == "" {
return errors.New("Name is required")
}
displayname, errdn := cmd.Flags().GetString("display_name")
if errdn != nil || displayname == "" {
return errors.New("Display Name is required")
}
teamArg, errteam := cmd.Flags().GetString("team")
if errteam != nil || teamArg == "" {
return errors.New("Team is required")
}
header, _ := cmd.Flags().GetString("header")
purpose, _ := cmd.Flags().GetString("purpose")
useprivate, _ := cmd.Flags().GetBool("private")
channelType := model.CHANNEL_OPEN
if useprivate {
channelType = model.CHANNEL_PRIVATE
}
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: "",
}
if _, err := a.CreateChannel(channel, false); err != nil {
return err
}
return nil
}
func removeChannelUsersCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 2 {
return errors.New("Not enough arguments.")
}
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 {
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(user.Id, "", channel); err != nil {
CommandPrintErrorln("Unable to remove '" + userArg + "' from " + channel.Name + ". Error: " + err.Error())
}
}
func addChannelUsersCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 2 {
return errors.New("Not enough arguments.")
}
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); err != nil {
CommandPrintErrorln("Unable to add '" + userArg + "' from " + channel.Name + ". Error: " + err.Error())
}
}
func archiveChannelsCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 1 {
return errors.New("Enter at least one channel to archive.")
}
channels := getChannelsFromChannelArgs(a, args)
for i, channel := range channels {
if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue
}
if result := <-a.Srv.Store.Channel().Delete(channel.Id, model.GetMillis()); result.Err != nil {
CommandPrintErrorln("Unable to archive channel '" + channel.Name + "' error: " + result.Err.Error())
}
}
return nil
}
func deleteChannelsCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 1 {
return errors.New("Enter at least one channel to delete.")
}
confirmFlag, _ := cmd.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 + "'")
}
}
return nil
}
func deleteChannel(a *app.App, channel *model.Channel) *model.AppError {
return a.PermanentDeleteChannel(channel)
}
func moveChannelsCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 2 {
return errors.New("Enter the destination team and at least one channel to move.")
}
team := getTeamFromTeamArg(a, args[0])
if team == nil {
return errors.New("Unable to find destination team '" + args[0] + "'")
}
username, erru := cmd.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] + "'")
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.MoveChannel(team, channel, user); err != nil {
return err
}
if incomingWebhooks, err := a.GetIncomingWebhooksForTeamPage(oldTeamId, 0, 10000000); err != nil {
return err
} else {
for _, webhook := range incomingWebhooks {
if webhook.ChannelId == channel.Id {
webhook.TeamId = team.Id
if result := <-a.Srv.Store.Webhook().UpdateIncoming(webhook); result.Err != nil {
CommandPrintErrorln("Failed to move incoming webhook '" + webhook.Id + "' to new team.")
}
}
}
}
if outgoingWebhooks, err := a.GetOutgoingWebhooksForTeamPage(oldTeamId, 0, 10000000); err != nil {
return err
} else {
for _, webhook := range outgoingWebhooks {
if webhook.ChannelId == channel.Id {
webhook.TeamId = team.Id
if result := <-a.Srv.Store.Webhook().UpdateOutgoing(webhook); result.Err != nil {
CommandPrintErrorln("Failed to move outgoing webhook '" + webhook.Id + "' to new team.")
}
}
}
}
return nil
}
func listChannelsCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 1 {
return errors.New("Enter at least one team.")
}
teams := getTeamsFromTeamArgs(a, args)
for i, team := range teams {
if team == nil {
CommandPrintErrorln("Unable to find team '" + args[i] + "'")
continue
}
if result := <-a.Srv.Store.Channel().GetAll(team.Id); result.Err != nil {
CommandPrintErrorln("Unable to list channels for '" + args[i] + "'")
} else {
channels := result.Data.([]*model.Channel)
for _, channel := range channels {
if channel.DeleteAt > 0 {
CommandPrettyPrintln(channel.Name + " (archived)")
} else {
CommandPrettyPrintln(channel.Name)
}
}
}
}
return nil
}
func restoreChannelsCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 1 {
return errors.New("Enter at least one channel.")
}
channels := getChannelsFromChannelArgs(a, args)
for i, channel := range channels {
if channel == nil {
CommandPrintErrorln("Unable to find channel '" + args[i] + "'")
continue
}
if result := <-a.Srv.Store.Channel().SetDeleteAt(channel.Id, 0, model.GetMillis()); result.Err != nil {
CommandPrintErrorln("Unable to restore channel '" + args[i] + "'")
}
}
return nil
}
func modifyChannelCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) != 1 {
return errors.New("Enter at one channel to modify.")
}
username, erru := cmd.Flags().GetString("username")
if erru != nil || username == "" {
return errors.New("Username is required")
}
public, _ := cmd.Flags().GetBool("public")
private, _ := cmd.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.CHANNEL_OPEN || channel.Type == model.CHANNEL_PRIVATE) {
return errors.New("You can only change the type of public/private channels.")
}
channel.Type = model.CHANNEL_OPEN
if private {
channel.Type = model.CHANNEL_PRIVATE
}
user := getUserFromUserArg(a, username)
if _, err := a.UpdateChannelPrivacy(channel, user); err != nil {
return errors.New("Failed to update channel ('" + args[0] + "') privacy - " + err.Error())
}
return nil
}

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

@@ -1,116 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"strings"
"testing"
"github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/require"
)
func TestJoinChannel(t *testing.T) {
th := api.Setup().InitBasic()
defer th.TearDown()
channel := th.CreateChannel(th.BasicClient, th.BasicTeam)
checkCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
// Joining twice should succeed
checkCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
// should fail because channel does not exist
require.Error(t, runCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name+"asdf", th.BasicUser2.Email))
}
func TestRemoveChannel(t *testing.T) {
th := api.Setup().InitBasic()
defer th.TearDown()
channel := th.CreateChannel(th.BasicClient, th.BasicTeam)
checkCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
// should fail because channel does not exist
require.Error(t, runCommand(t, "channel", "remove", th.BasicTeam.Name+":doesnotexist", th.BasicUser2.Email))
checkCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
// Leaving twice should succeed
checkCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email)
}
func TestMoveChannel(t *testing.T) {
th := api.Setup().InitBasic()
defer th.TearDown()
client := th.BasicClient
team1 := th.BasicTeam
team2 := th.CreateTeam(client)
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
checkCommand(t, "channel", "add", origin, adminEmail)
// should fail with nill because errors are logged instead of returned when a channel does not exist
require.Nil(t, runCommand(t, "channel", "move", dest, team1.Name+":doesnotexist", "--username", adminUsername))
checkCommand(t, "channel", "move", dest, origin, "--username", adminUsername)
}
func TestListChannels(t *testing.T) {
th := api.Setup().InitBasic()
defer th.TearDown()
channel := th.CreateChannel(th.BasicClient, th.BasicTeam)
th.BasicClient.Must(th.BasicClient.DeleteChannel(channel.Id))
output := checkCommand(t, "channel", "list", th.BasicTeam.Name)
if !strings.Contains(string(output), "town-square") {
t.Fatal("should have channels")
}
if !strings.Contains(string(output), channel.Name+" (archived)") {
t.Fatal("should have archived channel")
}
}
func TestRestoreChannel(t *testing.T) {
th := api.Setup().InitBasic()
defer th.TearDown()
channel := th.CreateChannel(th.BasicClient, th.BasicTeam)
th.BasicClient.Must(th.BasicClient.DeleteChannel(channel.Id))
checkCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name)
// restoring twice should succeed
checkCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name)
}
func TestCreateChannel(t *testing.T) {
th := api.Setup().InitBasic()
defer th.TearDown()
id := model.NewId()
name := "name" + id
checkCommand(t, "channel", "create", "--display_name", name, "--team", th.BasicTeam.Name, "--name", name)
name = name + "-private"
checkCommand(t, "channel", "create", "--display_name", name, "--team", th.BasicTeam.Name, "--private", "--name", name)
}

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

@@ -1,59 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"fmt"
"strings"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
)
const CHANNEL_ARG_SEPARATOR = ":"
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, CHANNEL_ARG_SEPARATOR, 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 := <-a.Srv.Store.Channel().GetByNameIncludeDeleted(team.Id, channelPart, true); result.Err == nil {
channel = result.Data.(*model.Channel)
} else {
fmt.Println(result.Err.Error())
}
}
if channel == nil {
if result := <-a.Srv.Store.Channel().Get(channelPart, true); result.Err == nil {
channel = result.Data.(*model.Channel)
}
}
return channel
}

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

@@ -1,65 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"errors"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var commandCmd = &cobra.Command{
Use: "command",
Short: "Management of slash commands",
}
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,
}
func init() {
commandCmd.AddCommand(
commandMoveCmd,
)
}
func moveCommandCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 2 {
return errors.New("Enter the destination team and at least one comamnd 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:])
CommandPrintErrorln(commands)
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.Trigger + "' error: " + err.Error())
} else {
CommandPrettyPrintln("Moved command '" + command.Trigger + "'")
}
}
return nil
}
func moveCommand(a *app.App, team *model.Team, command *model.Command) *model.AppError {
return a.MoveCommand(team, command)
}

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

@@ -1,63 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"fmt"
"strings"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
)
const COMMAND_ARGS_SEPARATOR = ":"
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, COMMAND_ARGS_SEPARATOR, 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
}
if result := <-a.Srv.Store.Command().GetByTrigger(team.Id, commandPart); result.Err == nil {
command = result.Data.(*model.Command)
} else {
fmt.Println(result.Err.Error())
}
}
if command == nil {
if result := <-a.Srv.Store.Command().Get(commandPart); result.Err == nil {
command = result.Data.(*model.Command)
}
}
return command
}

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

@@ -1,65 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"encoding/json"
"errors"
"os"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
"github.com/spf13/cobra"
)
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,
}
func init() {
configCmd.AddCommand(
validateConfigCmd,
)
}
func configValidateCmdF(cmd *cobra.Command, args []string) error {
utils.TranslationsPreInit()
model.AppErrorInit(utils.T)
filePath, err := cmd.Flags().GetString("config")
if err != nil {
return err
}
filePath = utils.FindConfigFile(filePath)
file, err := os.Open(filePath)
if err != nil {
return err
}
decoder := json.NewDecoder(file)
config := model.Config{}
err = decoder.Decode(&config)
if err != nil {
return err
}
if _, err := file.Stat(); err != nil {
return err
}
if err := config.IsValid(); err != nil {
return errors.New(utils.T(err.Id))
}
CommandPrettyPrintln("The document is valid")
return nil
}

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

@@ -1,30 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/model"
)
func TestConfigValidate(t *testing.T) {
dir, err := ioutil.TempDir("", "")
require.NoError(t, err)
defer os.RemoveAll(dir)
path := filepath.Join(dir, "config.json")
config := &model.Config{}
config.SetDefaults()
require.NoError(t, ioutil.WriteFile(path, []byte(config.ToJson()), 0600))
assert.Error(t, runCommand(t, "--config", "foo.json", "config", "validate"))
assert.NoError(t, runCommand(t, "--config", path, "config", "validate"))
}

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

@@ -1,139 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"errors"
"os"
"fmt"
"github.com/spf13/cobra"
)
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.")
importCmd.AddCommand(
bulkImportCmd,
slackImportCmd,
)
}
func slackImportCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
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.")
a.SlackImport(fileReader, fileInfo.Size(), team.Id)
CommandPrettyPrintln("Finished Slack Import.")
return nil
}
func bulkImportCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
apply, err := cmd.Flags().GetBool("apply")
if err != nil {
return errors.New("Apply flag error")
}
validate, err := cmd.Flags().GetBool("validate")
if err != nil {
return errors.New("Validate flag error")
}
workers, err := cmd.Flags().GetInt("workers")
if err != nil {
return errors.New("Workers 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
} else 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.BulkImport(fileReader, !apply, workers); err != nil {
CommandPrettyPrintln(err.Error())
if lineNumber != 0 {
CommandPrettyPrintln(fmt.Sprintf("Error occurred on data file line %v", lineNumber))
}
} else {
if apply {
CommandPrettyPrintln("Finished Bulk Import.")
} else {
CommandPrettyPrintln("Validation complete. You can now perform the import by rerunning this command with the --apply flag.")
}
}
return nil
}

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

@@ -1,46 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
"github.com/spf13/cobra"
)
func initDBCommandContextCobra(cmd *cobra.Command) (*app.App, error) {
config, err := cmd.Flags().GetString("config")
if err != nil {
return nil, err
}
a, err := initDBCommandContext(config)
if err != nil {
// Returning an error just prints the usage message, so actually panic
panic(err)
}
return a, nil
}
func initDBCommandContext(configFileLocation string) (*app.App, error) {
if err := utils.TranslationsPreInit(); err != nil {
return nil, err
}
model.AppErrorInit(utils.T)
utils.ConfigureCmdLineLog()
a, err := app.New(app.ConfigFile(configFileLocation))
if err != nil {
return nil, err
}
if model.BuildEnterpriseReady == "true" {
a.LoadLicense()
}
return a, nil
}

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

@@ -1,60 +0,0 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"os"
"os/signal"
"syscall"
l4g "github.com/alecthomas/log4go"
"github.com/spf13/cobra"
)
var jobserverCmd = &cobra.Command{
Use: "jobserver",
Short: "Start the Mattermost job server",
Run: 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.")
}
func jobserverCmdF(cmd *cobra.Command, args []string) {
// Options
noJobs, _ := cmd.Flags().GetBool("nojobs")
noSchedule, _ := cmd.Flags().GetBool("noschedule")
// Initialize
a, err := initDBCommandContext("config.json")
if err != nil {
panic(err.Error())
}
defer l4g.Close()
defer a.Shutdown()
a.LoadLicense()
// Run jobs
l4g.Info("Starting Mattermost job server")
if !noJobs {
a.Jobs.StartWorkers()
}
if !noSchedule {
a.Jobs.StartSchedulers()
}
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
l4g.Info("Stopping Mattermost job server")
a.Jobs.StopSchedulers()
a.Jobs.StopWorkers()
l4g.Info("Stopped Mattermost job server")
}

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

@@ -1,45 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
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,
}
func init() {
ldapCmd.AddCommand(
ldapSyncCmd,
)
}
func ldapSyncCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if ldapI := a.Ldap; ldapI != nil {
job, err := ldapI.StartSynchronizeJob(true)
if err != nil || job.Status == model.JOB_STATUS_ERROR || job.Status == model.JOB_STATUS_CANCELED {
CommandPrintErrorln("ERROR: AD/LDAP Synchronization please check the server logs")
} else {
CommandPrettyPrintln("SUCCESS: AD/LDAP Synchronization Complete")
}
}
return nil
}

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

@@ -1,51 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"errors"
"io/ioutil"
"github.com/spf13/cobra"
)
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)
}
func uploadLicenseCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
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.SaveLicense(fileBytes); err != nil {
return err
}
CommandPrettyPrintln("Uploaded license file")
return nil
}

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

@@ -1,88 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"errors"
"fmt"
"os"
"github.com/spf13/cobra"
// Plugins
_ "github.com/mattermost/mattermost-server/model/gitlab"
// Enterprise Imports
_ "github.com/mattermost/mattermost-server/imports"
// Enterprise Deps
_ "github.com/dgryski/dgoogauth"
_ "github.com/go-ldap/ldap"
_ "github.com/hashicorp/memberlist"
_ "github.com/mattermost/rsc/qr"
_ "github.com/prometheus/client_golang/prometheus"
_ "github.com/prometheus/client_golang/prometheus/promhttp"
_ "github.com/tylerb/graceful"
_ "gopkg.in/olivere/elastic.v5"
// Temp imports for new dependencies
_ "github.com/gorilla/schema"
)
func main() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
func init() {
rootCmd.PersistentFlags().StringP("config", "c", "config.json", "Configuration file to use.")
rootCmd.PersistentFlags().Bool("disableconfigwatch", false, "When set config.json will not be loaded from disk when the file is changed.")
resetCmd.Flags().Bool("confirm", false, "Confirm you really want to delete everything and a DB backup has been performed.")
rootCmd.AddCommand(serverCmd, versionCmd, userCmd, teamCmd, licenseCmd, importCmd, resetCmd, channelCmd, rolesCmd, testCmd, ldapCmd, configCmd, jobserverCmd, commandCmd, messageExportCmd, sampleDataCmd)
}
var rootCmd = &cobra.Command{
Use: "platform",
Short: "Open source, self-hosted Slack-alternative",
Long: `Mattermost offers workplace messaging across web, PC and phones with archiving, search and integration with your existing systems. Documentation available at https://docs.mattermost.com`,
RunE: runServerCmd,
}
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 resetCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
confirmFlag, _ := cmd.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 sucessfully reset")
return nil
}

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

@@ -1,41 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/utils"
)
func TestConfigFlag(t *testing.T) {
dir, err := ioutil.TempDir("", "")
require.NoError(t, err)
defer os.RemoveAll(dir)
utils.TranslationsPreInit()
config, _, err := utils.LoadConfig("config.json")
require.Nil(t, err)
configPath := filepath.Join(dir, "foo.json")
require.NoError(t, ioutil.WriteFile(configPath, []byte(config.ToJson()), 0600))
i18n, ok := utils.FindDir("i18n")
require.True(t, ok)
require.NoError(t, utils.CopyDir(i18n, filepath.Join(dir, "i18n")))
prevDir, err := os.Getwd()
require.NoError(t, err)
defer os.Chdir(prevDir)
os.Chdir(dir)
require.Error(t, runCommand(t, "version"))
checkCommand(t, "--config", "foo.json", "version")
checkCommand(t, "--config", "./foo.json", "version")
checkCommand(t, "--config", configPath, "version")
}

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

@@ -1,79 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"errors"
"context"
"time"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var messageExportCmd = &cobra.Command{
Use: "export",
Short: "Export data from Mattermost",
Long: "Export data from Mattermost in a format suitable for import into a third-party application",
Example: "export --format=actiance --exportFrom=12345",
RunE: messageExportCmdF,
}
func init() {
messageExportCmd.Flags().String("format", "actiance", "The format to export data in")
messageExportCmd.Flags().Int64("exportFrom", -1, "The timestamp of the earliest post to export, expressed in seconds since the unix epoch.")
messageExportCmd.Flags().Int("timeoutSeconds", -1, "The maximum number of seconds to wait for the job to complete before timing out.")
}
func messageExportCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if !*a.Config().MessageExportSettings.EnableExport {
return errors.New("ERROR: The message export feature is not enabled")
}
// for now, format is hard-coded to actiance. In time, we'll have to support other formats and inject them into job data
if format, err := cmd.Flags().GetString("format"); err != nil {
return errors.New("format flag error")
} else if format != "actiance" {
return errors.New("unsupported export format")
}
startTime, err := cmd.Flags().GetInt64("exportFrom")
if err != nil {
return errors.New("exportFrom flag error")
} else if startTime < 0 {
return errors.New("exportFrom must be a positive integer")
}
timeoutSeconds, err := cmd.Flags().GetInt("timeoutSeconds")
if err != nil {
return errors.New("timeoutSeconds error")
} else if timeoutSeconds < 0 {
return errors.New("timeoutSeconds must be a positive integer")
}
if messageExportI := a.MessageExport; messageExportI != nil {
ctx := context.Background()
if timeoutSeconds > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Second*time.Duration(timeoutSeconds))
defer cancel()
}
job, err := messageExportI.StartSynchronizeJob(ctx, startTime)
if err != nil || job.Status == model.JOB_STATUS_ERROR || job.Status == model.JOB_STATUS_CANCELED {
CommandPrintErrorln("ERROR: Message export job failed. Please check the server logs")
} else {
CommandPrettyPrintln("SUCCESS: Message export job complete")
}
}
return nil
}

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

@@ -1,66 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
)
// There are no tests that actually run the Message Export job, because it can take a long time to complete depending
// on the size of the database that the config is pointing to. As such, these tests just ensure that the CLI command
// fails fast if invalid flags are supplied
func TestMessageExportNotEnabled(t *testing.T) {
configPath := writeTempConfig(t, false)
defer os.RemoveAll(filepath.Dir(configPath))
// should fail fast because the feature isn't enabled
require.Error(t, runCommand(t, "--config", configPath, "export"))
}
func TestMessageExportInvalidFormat(t *testing.T) {
configPath := writeTempConfig(t, true)
defer os.RemoveAll(filepath.Dir(configPath))
// should fail fast because format isn't supported
require.Error(t, runCommand(t, "--config", configPath, "--format", "not_actiance", "export"))
}
func TestMessageExportNegativeExportFrom(t *testing.T) {
configPath := writeTempConfig(t, true)
defer os.RemoveAll(filepath.Dir(configPath))
// should fail fast because export from must be a valid timestamp
require.Error(t, runCommand(t, "--config", configPath, "--format", "actiance", "--exportFrom", "-1", "export"))
}
func TestMessageExportNegativeTimeoutSeconds(t *testing.T) {
configPath := writeTempConfig(t, true)
defer os.RemoveAll(filepath.Dir(configPath))
// should fail fast because timeout seconds must be a positive int
require.Error(t, runCommand(t, "--config", configPath, "--format", "actiance", "--exportFrom", "0", "--timeoutSeconds", "-1", "export"))
}
func writeTempConfig(t *testing.T, isMessageExportEnabled bool) string {
dir, err := ioutil.TempDir("", "")
require.NoError(t, err)
utils.TranslationsPreInit()
config, _, appErr := utils.LoadConfig("config.json")
require.Nil(t, appErr)
config.MessageExportSettings.EnableExport = model.NewBool(isMessageExportEnabled)
configPath := filepath.Join(dir, "foo.json")
require.NoError(t, ioutil.WriteFile(configPath, []byte(config.ToJson()), 0600))
return configPath
}

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

@@ -1,20 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"fmt"
"os"
)
func CommandPrintln(a ...interface{}) (int, error) {
return fmt.Println(a...)
}
func CommandPrintErrorln(a ...interface{}) (int, error) {
return fmt.Fprintln(os.Stderr, a...)
}
func CommandPrettyPrintln(a ...interface{}) (int, error) {
return fmt.Fprintln(os.Stderr, a...)
}

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

@@ -1,53 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
var coverprofileCounters map[string]int = make(map[string]int)
func execArgs(t *testing.T, args []string) []string {
ret := []string{"-test.run", "ExecCommand"}
if coverprofile := flag.Lookup("test.coverprofile").Value.String(); coverprofile != "" {
dir := filepath.Dir(coverprofile)
base := filepath.Base(coverprofile)
baseParts := strings.SplitN(base, ".", 2)
coverprofileCounters[t.Name()] = coverprofileCounters[t.Name()] + 1
baseParts[0] = fmt.Sprintf("%v-%v-%v", baseParts[0], t.Name(), coverprofileCounters[t.Name()])
ret = append(ret, "-test.coverprofile", filepath.Join(dir, strings.Join(baseParts, ".")))
}
return append(append(ret, "--", "--disableconfigwatch"), args...)
}
func checkCommand(t *testing.T, args ...string) string {
path, err := os.Executable()
require.NoError(t, err)
output, err := exec.Command(path, execArgs(t, args)...).CombinedOutput()
require.NoError(t, err, string(output))
return strings.TrimSpace(strings.TrimSuffix(strings.TrimSpace(string(output)), "PASS"))
}
func runCommand(t *testing.T, args ...string) error {
path, err := os.Executable()
require.NoError(t, err)
return exec.Command(path, execArgs(t, args)...).Run()
}
func TestExecCommand(t *testing.T) {
if filter := flag.Lookup("test.run").Value.String(); filter != "ExecCommand" {
t.Skip("use -run ExecCommand to execute a command via the test executable")
}
rootCmd.SetArgs(flag.Args())
require.NoError(t, rootCmd.Execute())
}

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

@@ -1,85 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"errors"
"github.com/spf13/cobra"
)
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,
)
}
func makeSystemAdminCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
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] + "'")
}
if _, err := a.UpdateUserRoles(user.Id, "system_admin system_user", true); err != nil {
return err
}
}
return nil
}
func makeMemberCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
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] + "'")
}
if _, err := a.UpdateUserRoles(user.Id, "system_user", true); err != nil {
return err
}
}
return nil
}

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

@@ -1,27 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"testing"
"github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/model"
)
func TestAssignRole(t *testing.T) {
th := api.Setup().InitBasic()
defer th.TearDown()
checkCommand(t, "roles", "system_admin", th.BasicUser.Email)
if result := <-th.App.Srv.Store.User().GetByEmail(th.BasicUser.Email); result.Err != nil {
t.Fatal()
} else {
user := result.Data.(*model.User)
if user.Roles != "system_admin system_user" {
t.Fatal()
}
}
}

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

@@ -1,625 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"os"
"path"
"sort"
"strings"
"time"
"github.com/icrowley/fake"
"github.com/mattermost/mattermost-server/app"
"github.com/spf13/cobra"
)
var sampleDataCmd = &cobra.Command{
Use: "sampledata",
Short: "Generate sample data",
RunE: sampleDataCmdF,
}
func sliceIncludes(vs []string, t string) bool {
for _, v := range vs {
if v == t {
return true
}
}
return false
}
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() - int64(rand.Intn(seconds*1000))
}
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 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().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.")
}
func sampleDataCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
seed, err := cmd.Flags().GetInt64("seed")
if err != nil {
return errors.New("Invalid seed parameter")
}
bulk, err := cmd.Flags().GetString("bulk")
if err != nil {
return errors.New("Invalid bulk parameter")
}
teams, err := cmd.Flags().GetInt("teams")
if err != nil || teams < 0 {
return errors.New("Invalid teams parameter")
}
channelsPerTeam, err := cmd.Flags().GetInt("channels-per-team")
if err != nil || channelsPerTeam < 0 {
return errors.New("Invalid channels-per-team parameter")
}
users, err := cmd.Flags().GetInt("users")
if err != nil || users < 0 {
return errors.New("Invalid users parameter")
}
teamMemberships, err := cmd.Flags().GetInt("team-memberships")
if err != nil || teamMemberships < 0 {
return errors.New("Invalid team-memberships parameter")
}
channelMemberships, err := cmd.Flags().GetInt("channel-memberships")
if err != nil || channelMemberships < 0 {
return errors.New("Invalid channel-memberships parameter")
}
postsPerChannel, err := cmd.Flags().GetInt("posts-per-channel")
if err != nil || postsPerChannel < 0 {
return errors.New("Invalid posts-per-channel parameter")
}
directChannels, err := cmd.Flags().GetInt("direct-channels")
if err != nil || directChannels < 0 {
return errors.New("Invalid direct-channels parameter")
}
postsPerDirectChannel, err := cmd.Flags().GetInt("posts-per-direct-channel")
if err != nil || postsPerDirectChannel < 0 {
return errors.New("Invalid posts-per-direct-channel parameter")
}
groupChannels, err := cmd.Flags().GetInt("group-channels")
if err != nil || groupChannels < 0 {
return errors.New("Invalid group-channels parameter")
}
postsPerGroupChannel, err := cmd.Flags().GetInt("posts-per-group-channel")
if err != nil || postsPerGroupChannel < 0 {
return errors.New("Invalid posts-per-group-channel parameter")
}
workers, err := cmd.Flags().GetInt("workers")
if err != nil {
return errors.New("Invalid workers parameter")
}
profileImagesPath, err := cmd.Flags().GetString("profile-images")
if err != nil {
return errors.New("Invalid profile-images parameter")
}
profileImages := []string{}
if profileImagesPath != "" {
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.")
}
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.")
}
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 team, channels := range teamsAndChannels {
for _, channel := range channels {
for i := 0; i < postsPerChannel; i++ {
postLine := createPost(team, channel, allUsers)
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 j := 0; j < postsPerDirectChannel; j++ {
postLine := createDirectPost([]string{user1, user2})
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 !sliceIncludes(users, user) {
users = append(users, user)
}
}
channelLine := createDirectChannel(users)
encoder.Encode(channelLine)
for j := 0; j < postsPerGroupChannel; j++ {
postLine := createDirectPost(users)
encoder.Encode(postLine)
}
}
if bulk == "" {
_, err := bulkFile.Seek(0, 0)
if err != nil {
return errors.New("Unable to read correctly the temporary file.")
}
importErr, lineNumber := a.BulkImport(bulkFile, false, workers)
if importErr != nil {
return fmt.Errorf("%s: %s, %s (line: %d)", importErr.Where, importErr.Message, importErr.DetailedError, lineNumber)
}
} 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) app.LineImportData {
password := fmt.Sprintf("user-%d", idx)
email := fmt.Sprintf("user-%d@sample.mattermost.com", idx)
firstName := fake.FirstName()
lastName := fake.LastName()
username := fmt.Sprintf("%s.%s", strings.ToLower(firstName), strings.ToLower(lastName))
position := fake.JobTitle()
roles := "system_user"
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()
}
// Half of users skip tutorial
tutorialStep := "999"
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))
}
}
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,
}
return app.LineImportData{
Type: "user",
User: &user,
}
}
func createTeamMembership(numOfchannels int, teamChannels []string, teamName *string) app.UserTeamImportData {
roles := "team_user"
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))
}
return app.UserTeamImportData{
Name: teamName,
Roles: &roles,
Channels: &channels,
}
}
func createChannelMembership(channelName string) app.UserChannelImportData {
roles := "channel_user"
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 createTeam(idx int) app.LineImportData {
displayName := fake.Word()
name := fmt.Sprintf("%s-%d", fake.Word(), 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 := "P"
if rand.Intn(2) == 0 {
channelType = "O"
}
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) app.LineImportData {
message := randomMessage(allUsers)
create_at := randomPastTime(50000)
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) app.LineImportData {
message := randomMessage(members)
create_at := randomPastTime(50000)
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,25 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"testing"
"github.com/mattermost/mattermost-server/api"
"github.com/stretchr/testify/require"
)
func TestSampledataBadParameters(t *testing.T) {
th := api.Setup().InitBasic()
defer th.TearDown()
// should fail because you need at least 1 worker
require.Error(t, runCommand(t, "sampledata", "--workers", "0"))
// should fail because you have more team memberships than teams
require.Error(t, runCommand(t, "sampledata", "--teams", "10", "--teams-memberships", "11"))
// should fail because you have more channel memberships than channels per team
require.Error(t, runCommand(t, "sampledata", "--channels-per-team", "10", "--channel-memberships", "11"))
}

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

@@ -1,289 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"net"
"os"
"os/signal"
"syscall"
"time"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/api4"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/manualtesting"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
"github.com/mattermost/mattermost-server/web"
"github.com/mattermost/mattermost-server/wsapi"
"github.com/spf13/cobra"
)
const (
SESSIONS_CLEANUP_BATCH_SIZE = 1000
)
var MaxNotificationsPerChannelDefault int64 = 1000000
var serverCmd = &cobra.Command{
Use: "server",
Short: "Run the Mattermost server",
RunE: runServerCmd,
SilenceUsage: true,
}
func runServerCmd(cmd *cobra.Command, args []string) error {
config, err := cmd.Flags().GetString("config")
if err != nil {
return err
}
disableConfigWatch, _ := cmd.Flags().GetBool("disableconfigwatch")
interruptChan := make(chan os.Signal, 1)
return runServer(config, disableConfigWatch, interruptChan)
}
func runServer(configFileLocation string, disableConfigWatch bool, interruptChan chan os.Signal) error {
options := []app.Option{app.ConfigFile(configFileLocation)}
if disableConfigWatch {
options = append(options, app.DisableConfigWatch)
}
a, err := app.New(options...)
if err != nil {
l4g.Critical(err.Error())
return err
}
defer a.Shutdown()
utils.TestConnection(a.Config())
pwd, _ := os.Getwd()
l4g.Info(utils.T("mattermost.current_version"), model.CurrentVersion, model.BuildNumber, model.BuildDate, model.BuildHash, model.BuildHashEnterprise)
l4g.Info(utils.T("mattermost.entreprise_enabled"), model.BuildEnterpriseReady)
l4g.Info(utils.T("mattermost.working_dir"), pwd)
l4g.Info(utils.T("mattermost.config_file"), utils.FindConfigFile(configFileLocation))
backend, appErr := a.FileBackend()
if appErr == nil {
appErr = backend.TestConnection()
}
if appErr != nil {
l4g.Error("Problem with file storage settings: " + appErr.Error())
}
if model.BuildEnterpriseReady == "true" {
a.LoadLicense()
}
a.InitPlugins(*a.Config().PluginSettings.Directory, *a.Config().PluginSettings.ClientDirectory, nil)
a.AddConfigListener(func(prevCfg, cfg *model.Config) {
if *cfg.PluginSettings.Enable {
a.InitPlugins(*cfg.PluginSettings.Directory, *a.Config().PluginSettings.ClientDirectory, nil)
} else {
a.ShutDownPlugins()
}
})
serverErr := a.StartServer()
if serverErr != nil {
l4g.Critical(serverErr.Error())
return serverErr
}
api4.Init(a, a.Srv.Router, false)
api3 := api.Init(a, a.Srv.Router)
wsapi.Init(a, a.Srv.WebSocketRouter)
web.Init(api3)
license := a.License()
if license == nil && len(a.Config().SqlSettings.DataSourceReplicas) > 1 {
l4g.Warn(utils.T("store.sql.read_replicas_not_licensed.critical"))
a.UpdateConfig(func(cfg *model.Config) {
cfg.SqlSettings.DataSourceReplicas = cfg.SqlSettings.DataSourceReplicas[:1]
})
}
if license == nil {
a.UpdateConfig(func(cfg *model.Config) {
cfg.TeamSettings.MaxNotificationsPerChannel = &MaxNotificationsPerChannelDefault
})
}
a.ReloadConfig()
// Enable developer settings if this is a "dev" build
if model.BuildNumber == "dev" {
a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableDeveloper = true })
}
resetStatuses(a)
// If we allow testing then listen for manual testing URL hits
if a.Config().ServiceSettings.EnableTesting {
manualtesting.Init(api3)
}
a.EnsureDiagnosticId()
a.Go(func() {
runSecurityJob(a)
})
a.Go(func() {
runDiagnosticsJob(a)
})
a.Go(func() {
runSessionCleanupJob(a)
})
a.Go(func() {
runTokenCleanupJob(a)
})
a.Go(func() {
runCommandWebhookCleanupJob(a)
})
if complianceI := a.Compliance; complianceI != nil {
complianceI.StartComplianceDailyJob()
}
if a.Cluster != nil {
a.RegisterAllClusterMessageHandlers()
a.Cluster.StartInterNodeCommunication()
}
if a.Metrics != nil {
a.Metrics.StartServer()
}
if a.Elasticsearch != nil {
a.Go(func() {
if err := a.Elasticsearch.Start(); err != nil {
l4g.Error(err.Error())
}
})
}
if *a.Config().JobSettings.RunJobs {
a.Jobs.StartWorkers()
}
if *a.Config().JobSettings.RunScheduler {
a.Jobs.StartSchedulers()
}
notifyReady()
// wait for kill signal before attempting to gracefully shutdown
// the running service
signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
<-interruptChan
if a.Cluster != nil {
a.Cluster.StopInterNodeCommunication()
}
if a.Metrics != nil {
a.Metrics.StopServer()
}
a.Jobs.StopSchedulers()
a.Jobs.StopWorkers()
return nil
}
func runSecurityJob(a *app.App) {
doSecurity(a)
model.CreateRecurringTask("Security", func() {
doSecurity(a)
}, time.Hour*4)
}
func runDiagnosticsJob(a *app.App) {
doDiagnostics(a)
model.CreateRecurringTask("Diagnostics", func() {
doDiagnostics(a)
}, time.Hour*24)
}
func runTokenCleanupJob(a *app.App) {
doTokenCleanup(a)
model.CreateRecurringTask("Token Cleanup", func() {
doTokenCleanup(a)
}, time.Hour*1)
}
func runCommandWebhookCleanupJob(a *app.App) {
doCommandWebhookCleanup(a)
model.CreateRecurringTask("Command Hook Cleanup", func() {
doCommandWebhookCleanup(a)
}, time.Hour*1)
}
func runSessionCleanupJob(a *app.App) {
doSessionCleanup(a)
model.CreateRecurringTask("Session Cleanup", func() {
doSessionCleanup(a)
}, time.Hour*24)
}
func resetStatuses(a *app.App) {
if result := <-a.Srv.Store.Status().ResetAll(); result.Err != nil {
l4g.Error(utils.T("mattermost.reset_status.error"), result.Err.Error())
}
}
func doSecurity(a *app.App) {
a.DoSecurityUpdateCheck()
}
func doDiagnostics(a *app.App) {
if *a.Config().LogSettings.EnableDiagnostics {
a.SendDailyDiagnostics()
}
}
func notifyReady() {
// If the environment vars provide a systemd notification socket,
// notify systemd that the server is ready.
systemdSocket := os.Getenv("NOTIFY_SOCKET")
if systemdSocket != "" {
l4g.Info("Sending systemd READY notification.")
err := sendSystemdReadyNotification(systemdSocket)
if err != nil {
l4g.Error(err.Error())
}
}
}
func sendSystemdReadyNotification(socketPath string) error {
msg := "READY=1"
addr := &net.UnixAddr{
Name: socketPath,
Net: "unixgram",
}
conn, err := net.DialUnix(addr.Net, nil, addr)
if err != nil {
return err
}
defer conn.Close()
_, err = conn.Write([]byte(msg))
return err
}
func doTokenCleanup(a *app.App) {
a.Srv.Store.Token().Cleanup()
}
func doCommandWebhookCleanup(a *app.App) {
a.Srv.Store.CommandWebhook().Cleanup()
}
func doSessionCleanup(a *app.App) {
a.Srv.Store.Session().Cleanup(model.GetMillis(), SESSIONS_CLEANUP_BATCH_SIZE)
}

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

@@ -1,136 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"io/ioutil"
"net"
"os"
"syscall"
"testing"
"github.com/mattermost/mattermost-server/jobs"
"github.com/mattermost/mattermost-server/utils"
"github.com/stretchr/testify/require"
)
type ServerTestHelper struct {
configPath string
disableConfigWatch bool
interruptChan chan os.Signal
originalInterval int
}
func SetupServerTest() *ServerTestHelper {
// Build a channel that will be used by the server to receive system signals…
interruptChan := make(chan os.Signal, 1)
// …and sent it immediately a SIGINT value.
// This will make the server loop stop as soon as it started successfully.
interruptChan <- syscall.SIGINT
// Let jobs poll for termination every 0.2s (instead of every 15s by default)
// Otherwise we would have to wait the whole polling duration before the test
// terminates.
originalInterval := jobs.DEFAULT_WATCHER_POLLING_INTERVAL
jobs.DEFAULT_WATCHER_POLLING_INTERVAL = 200
th := &ServerTestHelper{
configPath: utils.FindConfigFile("config.json"),
disableConfigWatch: true,
interruptChan: interruptChan,
originalInterval: originalInterval,
}
return th
}
func (th *ServerTestHelper) TearDownServerTest() {
jobs.DEFAULT_WATCHER_POLLING_INTERVAL = th.originalInterval
}
func TestRunServerSuccess(t *testing.T) {
th := SetupServerTest()
defer th.TearDownServerTest()
err := runServer(th.configPath, th.disableConfigWatch, th.interruptChan)
require.NoError(t, err)
}
func TestRunServerInvalidConfigFile(t *testing.T) {
th := SetupServerTest()
defer th.TearDownServerTest()
// Start the server with an unreadable config file
unreadableConfigFile, err := ioutil.TempFile("", "mattermost-unreadable-config-file-")
if err != nil {
panic(err)
}
os.Chmod(unreadableConfigFile.Name(), 0200)
defer os.Remove(unreadableConfigFile.Name())
err = runServer(unreadableConfigFile.Name(), th.disableConfigWatch, th.interruptChan)
require.Error(t, err)
}
func TestRunServerSystemdNotification(t *testing.T) {
th := SetupServerTest()
defer th.TearDownServerTest()
// Get a random temporary filename for using as a mock systemd socket
socketFile, err := ioutil.TempFile("", "mattermost-systemd-mock-socket-")
if err != nil {
panic(err)
}
socketPath := socketFile.Name()
os.Remove(socketPath)
// Set the socket path in the process environment
originalSocket := os.Getenv("NOTIFY_SOCKET")
os.Setenv("NOTIFY_SOCKET", socketPath)
defer os.Setenv("NOTIFY_SOCKET", originalSocket)
// Open the socket connection
addr := &net.UnixAddr{
Name: socketPath,
Net: "unixgram",
}
connection, err := net.ListenUnixgram("unixgram", addr)
if err != nil {
panic(err)
}
defer connection.Close()
defer os.Remove(socketPath)
// Listen for socket data
socketReader := make(chan string)
go func(ch chan string) {
buffer := make([]byte, 512)
count, err := connection.Read(buffer)
if err != nil {
panic(err)
}
data := buffer[0:count]
ch<- string(data)
}(socketReader)
// Start and stop the server
err = runServer(th.configPath, th.disableConfigWatch, th.interruptChan)
require.NoError(t, err)
// Ensure the notification has been sent on the socket and is correct
notification := <-socketReader
require.Equal(t, notification, "READY=1")
}
func TestRunServerNoSystemd(t *testing.T) {
th := SetupServerTest()
defer th.TearDownServerTest()
// Temporarily remove any Systemd socket defined in the environment
originalSocket := os.Getenv("NOTIFY_SOCKET")
os.Unsetenv("NOTIFY_SOCKET")
defer os.Setenv("NOTIFY_SOCKET", originalSocket)
err := runServer(th.configPath, th.disableConfigWatch, th.interruptChan)
require.NoError(t, err)
}

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

@@ -1,215 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"errors"
"fmt"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var teamCmd = &cobra.Command{
Use: "team",
Short: "Management of teams",
}
var teamCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a team",
Long: `Create a team.`,
Example: ` team create --name mynewteam --display_name "My New Team"
team create --name private --display_name "My New Private Team" --private`,
RunE: 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",
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",
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",
RunE: deleteTeamsCmdF,
}
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.")
teamCmd.AddCommand(
teamCreateCmd,
removeUsersCmd,
addUsersCmd,
deleteTeamsCmd,
)
}
func createTeamCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
name, errn := cmd.Flags().GetString("name")
if errn != nil || name == "" {
return errors.New("Name is required")
}
displayname, errdn := cmd.Flags().GetString("display_name")
if errdn != nil || displayname == "" {
return errors.New("Display Name is required")
}
email, _ := cmd.Flags().GetString("email")
useprivate, _ := cmd.Flags().GetBool("private")
teamType := model.TEAM_OPEN
if useprivate {
teamType = model.TEAM_INVITE
}
team := &model.Team{
Name: name,
DisplayName: displayname,
Email: email,
Type: teamType,
}
if _, err := a.CreateTeam(team); err != nil {
return errors.New("Team creation failed: " + err.Error())
}
return nil
}
func removeUsersCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 2 {
return errors.New("Not enough arguments.")
}
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(team, user, ""); err != nil {
CommandPrintErrorln("Unable to remove '" + userArg + "' from " + team.Name + ". Error: " + err.Error())
}
}
func addUsersCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 2 {
return errors.New("Not enough arguments.")
}
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(team, user, ""); err != nil {
CommandPrintErrorln("Unable to add '" + userArg + "' to " + team.Name)
}
}
func deleteTeamsCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 1 {
return errors.New("Not enough arguments.")
}
confirmFlag, _ := cmd.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 + "'")
}
}
return nil
}
func deleteTeam(a *app.App, team *model.Team) *model.AppError {
return a.PermanentDeleteTeam(team)
}

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

@@ -1,79 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"testing"
"github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/model"
)
func TestCreateTeam(t *testing.T) {
th := api.Setup().InitSystemAdmin()
defer th.TearDown()
id := model.NewId()
name := "name" + id
displayName := "Name " + id
checkCommand(t, "team", "create", "--name", name, "--display_name", displayName)
found := th.SystemAdminClient.Must(th.SystemAdminClient.FindTeamByName(name)).Data.(bool)
if !found {
t.Fatal("Failed to create Team")
}
}
func TestJoinTeam(t *testing.T) {
th := api.Setup().InitSystemAdmin().InitBasic()
defer th.TearDown()
checkCommand(t, "team", "add", th.SystemAdminTeam.Name, th.BasicUser.Email)
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfilesInTeam(th.SystemAdminTeam.Id, 0, 1000, "")).Data.(map[string]*model.User)
found := false
for _, user := range profiles {
if user.Email == th.BasicUser.Email {
found = true
}
}
if !found {
t.Fatal("Failed to create User")
}
}
func TestLeaveTeam(t *testing.T) {
th := api.Setup().InitBasic()
defer th.TearDown()
checkCommand(t, "team", "remove", th.BasicTeam.Name, th.BasicUser.Email)
profiles := th.BasicClient.Must(th.BasicClient.GetProfilesInTeam(th.BasicTeam.Id, 0, 1000, "")).Data.(map[string]*model.User)
found := false
for _, user := range profiles {
if user.Email == th.BasicUser.Email {
found = true
}
}
if found {
t.Fatal("profile should not be on team")
}
if result := <-th.App.Srv.Store.Team().GetTeamsByUserId(th.BasicUser.Id); result.Err != nil {
teamMembers := result.Data.([]*model.TeamMember)
if len(teamMembers) > 0 {
t.Fatal("Shouldn't be in team")
}
}
}

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

@@ -1,32 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/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
if result := <-a.Srv.Store.Team().GetByName(teamArg); result.Err == nil {
team = result.Data.(*model.Team)
}
if team == nil {
if result := <-a.Srv.Store.Team().Get(teamArg); result.Err == nil {
team = result.Data.(*model.Team)
}
}
return team
}

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

@@ -1,149 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"os/signal"
"syscall"
"github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/api4"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
"github.com/mattermost/mattermost-server/wsapi"
"github.com/spf13/cobra"
)
var testCmd = &cobra.Command{
Use: "test",
Short: "Testing Commands",
Hidden: true,
}
var runWebClientTestsCmd = &cobra.Command{
Use: "web_client_tests",
Short: "Run the web client tests",
RunE: webClientTestsCmdF,
}
var runServerForWebClientTestsCmd = &cobra.Command{
Use: "web_client_tests_server",
Short: "Run the server configured for running the web client tests against it",
RunE: serverForWebClientTestsCmdF,
}
func init() {
testCmd.AddCommand(
runWebClientTestsCmd,
runServerForWebClientTestsCmd,
)
}
func webClientTestsCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
defer a.Shutdown()
utils.InitTranslations(a.Config().LocalizationSettings)
serverErr := a.StartServer()
if serverErr != nil {
return serverErr
}
api4.Init(a, a.Srv.Router, false)
api.Init(a, a.Srv.Router)
wsapi.Init(a, a.Srv.WebSocketRouter)
a.UpdateConfig(setupClientTests)
runWebClientTests()
return nil
}
func serverForWebClientTestsCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
defer a.Shutdown()
utils.InitTranslations(a.Config().LocalizationSettings)
serverErr := a.StartServer()
if serverErr != nil {
return serverErr
}
api4.Init(a, a.Srv.Router, false)
api.Init(a, a.Srv.Router)
wsapi.Init(a, a.Srv.WebSocketRouter)
a.UpdateConfig(setupClientTests)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
<-c
return nil
}
func setupClientTests(cfg *model.Config) {
*cfg.TeamSettings.EnableOpenServer = true
*cfg.ServiceSettings.EnableCommands = false
*cfg.ServiceSettings.EnableOnlyAdminIntegrations = false
*cfg.ServiceSettings.EnableCustomEmoji = true
cfg.ServiceSettings.EnableIncomingWebhooks = false
cfg.ServiceSettings.EnableOutgoingWebhooks = false
}
func executeTestCommand(cmd *exec.Cmd) {
cmdOutPipe, err := cmd.StdoutPipe()
if err != nil {
CommandPrintErrorln("Failed to run tests")
os.Exit(1)
return
}
cmdErrOutPipe, err := cmd.StderrPipe()
if err != nil {
CommandPrintErrorln("Failed to run tests")
os.Exit(1)
return
}
cmdOutReader := bufio.NewScanner(cmdOutPipe)
cmdErrOutReader := bufio.NewScanner(cmdErrOutPipe)
go func() {
for cmdOutReader.Scan() {
fmt.Println(cmdOutReader.Text())
}
}()
go func() {
for cmdErrOutReader.Scan() {
fmt.Println(cmdErrOutReader.Text())
}
}()
if err := cmd.Run(); err != nil {
CommandPrintErrorln("Client Tests failed")
os.Exit(1)
return
}
}
func runWebClientTests() {
if webappDir := os.Getenv("WEBAPP_DIR"); webappDir != "" {
os.Chdir(webappDir)
} else {
os.Chdir("../mattermost-webapp")
}
cmd := exec.Command("npm", "test")
executeTestCommand(cmd)
}

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

@@ -1,654 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/spf13/cobra"
)
var userCmd = &cobra.Command{
Use: "user",
Short: "Management of users",
}
var userActivateCmd = &cobra.Command{
Use: "activate [emails, usernames, userIds]",
Short: "Activate users",
Long: "Activate users that have been deactivated.",
Example: ` user activate user@example.com
user activate username`,
RunE: userActivateCmdF,
}
var userDeactivateCmd = &cobra.Command{
Use: "deactivate [emails, usernames, userIds]",
Short: "Deactivate users",
Long: "Deactivate users. Deactivated users are immediately logged out of all sessions and are unable to log back in.",
Example: ` user deactivate user@example.com
user deactivate username`,
RunE: userDeactivateCmdF,
}
var userCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a user",
Long: "Create a user",
Example: ` user create --email user@example.com --username userexample --password Password1`,
RunE: userCreateCmdF,
}
var userInviteCmd = &cobra.Command{
Use: "invite [email] [teams]",
Short: "Send user an email invite to a team.",
Long: `Send user an email invite to a team.
You can invite a user to multiple teams by listing them.
You can specify teams by name or ID.`,
Example: ` user invite user@example.com myteam
user invite user@example.com myteam1 myteam2`,
RunE: userInviteCmdF,
}
var resetUserPasswordCmd = &cobra.Command{
Use: "password [user] [password]",
Short: "Set a user's password",
Long: "Set a user's password",
Example: " user password user@example.com Password1",
RunE: resetUserPasswordCmdF,
}
var resetUserMfaCmd = &cobra.Command{
Use: "resetmfa [users]",
Short: "Turn off MFA",
Long: `Turn off multi-factor authentication for a user.
If MFA enforcement is enabled, the user will be forced to re-enable MFA as soon as they login.`,
Example: " user resetmfa user@example.com",
RunE: resetUserMfaCmdF,
}
var deleteUserCmd = &cobra.Command{
Use: "delete [users]",
Short: "Delete users and all posts",
Long: "Permanently delete user and all related information including posts.",
Example: " user delete user@example.com",
RunE: deleteUserCmdF,
}
var deleteAllUsersCmd = &cobra.Command{
Use: "deleteall",
Short: "Delete all users and all posts",
Long: "Permanently delete all users and all related information including posts.",
Example: " user deleteall",
RunE: deleteAllUsersCommandF,
}
var migrateAuthCmd = &cobra.Command{
Use: "migrate_auth [from_auth] [to_auth] [migration-options]",
Short: "Mass migrate user accounts authentication type",
Long: `Migrates accounts from one authentication provider to another. For example, you can upgrade your authentication provider from email to ldap.`,
Example: " user migrate_auth email saml users.json",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("Auth migration requires at least 2 arguments.")
}
toAuth := args[1]
if toAuth != "ldap" && toAuth != "saml" {
return errors.New("Invalid to_auth parameter, must be saml or ldap.")
}
if toAuth == "ldap" && len(args) != 3 {
return errors.New("Ldap migration requires 3 arguments.")
}
autoFlag, _ := cmd.Flags().GetBool("auto")
if toAuth == "saml" && autoFlag {
if len(args) != 2 {
return errors.New("Saml migration requires two arguments when using the --auto flag. See help text for details.")
}
}
if toAuth == "saml" && !autoFlag {
if len(args) != 3 {
return errors.New("Saml migration requires three arguments when not using the --auto flag. See help text for details.")
}
}
return nil
},
RunE: migrateAuthCmdF,
}
var verifyUserCmd = &cobra.Command{
Use: "verify [users]",
Short: "Verify email of users",
Long: "Verify the emails of some users.",
Example: " user verify user1",
RunE: verifyUserCmdF,
}
var searchUserCmd = &cobra.Command{
Use: "search [users]",
Short: "Search for users",
Long: "Search for users based on username, email, or user ID.",
Example: " user search user1@mail.com user2@mail.com",
RunE: searchUserCmdF,
}
func init() {
userCreateCmd.Flags().String("username", "", "Required. Username for the new user account.")
userCreateCmd.Flags().String("email", "", "Required. The email address for the new user account.")
userCreateCmd.Flags().String("password", "", "Required. The password for the new user account.")
userCreateCmd.Flags().String("nickname", "", "Optional. The nickname for the new user account.")
userCreateCmd.Flags().String("firstname", "", "Optional. The first name for the new user account.")
userCreateCmd.Flags().String("lastname", "", "Optional. The last name for the new user account.")
userCreateCmd.Flags().String("locale", "", "Optional. The locale (ex: en, fr) for the new user account.")
userCreateCmd.Flags().Bool("system_admin", false, "Optional. If supplied, the new user will be a system administrator. Defaults to false.")
deleteUserCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the user and a DB backup has been performed.")
deleteAllUsersCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the user and a DB backup has been performed.")
migrateAuthCmd.Flags().Bool("force", false, "Force the migration to occur even if there are duplicates on the LDAP server. Duplicates will not be migrated. (ldap only)")
migrateAuthCmd.Flags().Bool("auto", false, "Automatically migrate all users. Assumes the usernames and emails are identical between Mattermost and SAML services. (saml only)")
migrateAuthCmd.Flags().Bool("dryRun", false, "Run a simulation of the migration process without changing the database.")
migrateAuthCmd.SetUsageTemplate(`Usage:
platform user migrate_auth [from_auth] [to_auth] [migration-options] [flags]
Examples:
{{.Example}}
Arguments:
from_auth:
The authentication service to migrate users accounts from.
Supported options: email, gitlab, ldap, saml.
to_auth:
The authentication service to migrate users to.
Supported options: ldap, saml.
migration-options:
Migration specific options, full command help for more information.
Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}
Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}
`)
migrateAuthCmd.SetHelpTemplate(`Usage:
platform user migrate_auth [from_auth] [to_auth] [migration-options] [flags]
Examples:
{{.Example}}
Arguments:
from_auth:
The authentication service to migrate users accounts from.
Supported options: email, gitlab, ldap, saml.
to_auth:
The authentication service to migrate users to.
Supported options: ldap, saml.
migration-options (ldap):
match_field:
The field that is guaranteed to be the same in both authentication services. For example, if the users emails are consistent set to email.
Supported options: email, username.
migration-options (saml):
users_file:
The path of a json file with the usernames and emails of all users to migrate to SAML. The username and email must be the same that the SAML service provider store. And the email must match with the email in mattermost database.
Example json content:
{
"usr1@email.com": "usr.one",
"usr2@email.com": "usr.two"
}
Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}
Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}
`)
userCmd.AddCommand(
userActivateCmd,
userDeactivateCmd,
userCreateCmd,
userInviteCmd,
resetUserPasswordCmd,
resetUserMfaCmd,
deleteUserCmd,
deleteAllUsersCmd,
migrateAuthCmd,
verifyUserCmd,
searchUserCmd,
)
}
func userActivateCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 1 {
return errors.New("Expected at least one argument. See help text for details.")
}
changeUsersActiveStatus(a, args, true)
return nil
}
func changeUsersActiveStatus(a *app.App, userArgs []string, active bool) {
users := getUsersFromUserArgs(a, userArgs)
for i, user := range users {
err := changeUserActiveStatus(a, user, userArgs[i], active)
if err != nil {
CommandPrintErrorln(err.Error())
}
}
}
func changeUserActiveStatus(a *app.App, user *model.User, userArg string, activate bool) error {
if user == nil {
return fmt.Errorf("Can't find user '%v'", userArg)
}
if user.IsSSOUser() {
fmt.Println("You must also deactivate this user in the SSO provider or they will be reactivated on next login or sync.")
}
if _, err := a.UpdateActive(user, activate); err != nil {
return fmt.Errorf("Unable to change activation status of user: %v", userArg)
}
return nil
}
func userDeactivateCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 1 {
return errors.New("Expected at least one argument. See help text for details.")
}
changeUsersActiveStatus(a, args, false)
return nil
}
func userCreateCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
username, erru := cmd.Flags().GetString("username")
if erru != nil || username == "" {
return errors.New("Username is required")
}
email, erre := cmd.Flags().GetString("email")
if erre != nil || email == "" {
return errors.New("Email is required")
}
password, errp := cmd.Flags().GetString("password")
if errp != nil || password == "" {
return errors.New("Password is required")
}
nickname, _ := cmd.Flags().GetString("nickname")
firstname, _ := cmd.Flags().GetString("firstname")
lastname, _ := cmd.Flags().GetString("lastname")
locale, _ := cmd.Flags().GetString("locale")
systemAdmin, _ := cmd.Flags().GetBool("system_admin")
user := &model.User{
Username: username,
Email: email,
Password: password,
Nickname: nickname,
FirstName: firstname,
LastName: lastname,
Locale: locale,
}
if ruser, err := a.CreateUser(user); err != nil {
return errors.New("Unable to create user. Error: " + err.Error())
} else if systemAdmin {
a.UpdateUserRoles(ruser.Id, "system_user system_admin", false)
}
CommandPrettyPrintln("Created User")
return nil
}
func userInviteCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 2 {
return errors.New("Expected at least two arguments. See help text for details.")
}
email := args[0]
if !model.IsValidEmail(email) {
return errors.New("Invalid email")
}
teams := getTeamsFromTeamArgs(a, args[1:])
for i, team := range teams {
err := inviteUser(a, email, team, args[i+1])
if err != nil {
CommandPrintErrorln(err.Error())
}
}
return nil
}
func inviteUser(a *app.App, email string, team *model.Team, teamArg string) error {
invites := []string{email}
if team == nil {
return fmt.Errorf("Can't find team '%v'", teamArg)
}
a.SendInviteEmails(team, "Administrator", invites, *a.Config().ServiceSettings.SiteURL)
CommandPrettyPrintln("Invites may or may not have been sent.")
return nil
}
func resetUserPasswordCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) != 2 {
return errors.New("Expected two arguments. See help text for details.")
}
user := getUserFromUserArg(a, args[0])
if user == nil {
return errors.New("Unable to find user '" + args[0] + "'")
}
password := args[1]
if result := <-a.Srv.Store.User().UpdatePassword(user.Id, model.HashPassword(password)); result.Err != nil {
return result.Err
}
return nil
}
func resetUserMfaCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 1 {
return errors.New("Expected at least one argument. See help text for details.")
}
users := getUsersFromUserArgs(a, args)
for i, user := range users {
if user == nil {
return errors.New("Unable to find user '" + args[i] + "'")
}
if err := a.DeactivateMfa(user.Id); err != nil {
return err
}
}
return nil
}
func deleteUserCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 1 {
return errors.New("Expected at least one argument. See help text for details.")
}
confirmFlag, _ := cmd.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 permanently delete the specified users? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
}
users := getUsersFromUserArgs(a, args)
for i, user := range users {
if user == nil {
return errors.New("Unable to find user '" + args[i] + "'")
}
if err := a.PermanentDeleteUser(user); err != nil {
return err
}
}
return nil
}
func deleteAllUsersCommandF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) > 0 {
return errors.New("Expected zero arguments.")
}
confirmFlag, _ := cmd.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 permanently delete all user accounts? (YES/NO): ")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
}
if err := a.PermanentDeleteAllUsers(); err != nil {
return err
}
CommandPrettyPrintln("All user accounts successfully deleted.")
return nil
}
func migrateAuthCmdF(cmd *cobra.Command, args []string) error {
if args[1] == "saml" {
return migrateAuthToSamlCmdF(cmd, args)
}
return migrateAuthToLdapCmdF(cmd, args)
}
func migrateAuthToLdapCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
fromAuth := args[0]
matchField := args[1]
if len(fromAuth) == 0 || (fromAuth != "email" && fromAuth != "gitlab" && fromAuth != "saml") {
return errors.New("Invalid from_auth argument")
}
// Email auth in Mattermost system is represented by ""
if fromAuth == "email" {
fromAuth = ""
}
if len(matchField) == 0 || (matchField != "email" && matchField != "username") {
return errors.New("Invalid match_field argument")
}
forceFlag, _ := cmd.Flags().GetBool("force")
dryRunFlag, _ := cmd.Flags().GetBool("dryRun")
if migrate := a.AccountMigration; migrate != nil {
if err := migrate.MigrateToLdap(fromAuth, matchField, forceFlag, dryRunFlag); err != nil {
return errors.New("Error while migrating users: " + err.Error())
}
CommandPrettyPrintln("Sucessfully migrated accounts.")
}
return nil
}
func migrateAuthToSamlCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
dryRunFlag, _ := cmd.Flags().GetBool("dryRun")
autoFlag, _ := cmd.Flags().GetBool("auto")
matchesFile := ""
matches := map[string]string{}
if !autoFlag {
matchesFile = args[1]
file, e := ioutil.ReadFile(matchesFile)
if e != nil {
return errors.New("Invalid users file.")
}
if json.Unmarshal(file, &matches) != nil {
return errors.New("Invalid users file.")
}
}
fromAuth := args[0]
if len(fromAuth) == 0 || (fromAuth != "email" && fromAuth != "gitlab" && fromAuth != "ldap") {
return errors.New("Invalid from_auth argument")
}
if autoFlag && !dryRunFlag {
var confirm string
CommandPrettyPrintln("You are about to perform an automatic \"" + fromAuth + " to saml\" migration. This must only be done if your current Mattermost users with " + fromAuth + " auth have the same username and email in your SAML service. Otherwise, provide the usernames and emails from your SAML Service using the \"users file\" without the \"--auto\" option.\n\nDo you want to proceed with automatic migration anyway? (YES/NO):")
fmt.Scanln(&confirm)
if confirm != "YES" {
return errors.New("ABORTED: You did not answer YES exactly, in all capitals.")
}
}
// Email auth in Mattermost system is represented by ""
if fromAuth == "email" {
fromAuth = ""
}
if migrate := a.AccountMigration; migrate != nil {
if err := migrate.MigrateToSaml(fromAuth, matches, autoFlag, dryRunFlag); err != nil {
return errors.New("Error while migrating users: " + err.Error())
}
l4g.Close()
CommandPrettyPrintln("Sucessfully migrated accounts.")
}
return nil
}
func verifyUserCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 1 {
return errors.New("Expected at least one argument. See help text for details.")
}
users := getUsersFromUserArgs(a, args)
for i, user := range users {
if user == nil {
CommandPrintErrorln("Unable to find user '" + args[i] + "'")
continue
}
if cresult := <-a.Srv.Store.User().VerifyEmail(user.Id); cresult.Err != nil {
CommandPrintErrorln("Unable to verify '" + args[i] + "' email. Error: " + cresult.Err.Error())
}
}
return nil
}
func searchUserCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
if len(args) < 1 {
return errors.New("Expected at least one argument. See help text for details.")
}
users := getUsersFromUserArgs(a, args)
for i, user := range users {
if i > 0 {
CommandPrettyPrintln("------------------------------")
}
if user == nil {
CommandPrintErrorln("Unable to find user '" + args[i] + "'")
continue
}
CommandPrettyPrintln("id: " + user.Id)
CommandPrettyPrintln("username: " + user.Username)
CommandPrettyPrintln("nickname: " + user.Nickname)
CommandPrettyPrintln("position: " + user.Position)
CommandPrettyPrintln("first_name: " + user.FirstName)
CommandPrettyPrintln("last_name: " + user.LastName)
CommandPrettyPrintln("email: " + user.Email)
CommandPrettyPrintln("auth_service: " + user.AuthService)
}
return nil
}

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

@@ -1,81 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"testing"
"github.com/mattermost/mattermost-server/api"
"github.com/mattermost/mattermost-server/model"
)
func TestCreateUserWithTeam(t *testing.T) {
th := api.Setup().InitSystemAdmin()
defer th.TearDown()
id := model.NewId()
email := "success+" + id + "@simulator.amazonses.com"
username := "name" + id
checkCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username)
checkCommand(t, "team", "add", th.SystemAdminTeam.Id, email)
profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetProfilesInTeam(th.SystemAdminTeam.Id, 0, 1000, "")).Data.(map[string]*model.User)
found := false
for _, user := range profiles {
if user.Email == email {
found = true
}
}
if !found {
t.Fatal("Failed to create User")
}
}
func TestCreateUserWithoutTeam(t *testing.T) {
th := api.Setup()
defer th.TearDown()
id := model.NewId()
email := "success+" + id + "@simulator.amazonses.com"
username := "name" + id
checkCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username)
if result := <-th.App.Srv.Store.User().GetByEmail(email); result.Err != nil {
t.Fatal()
} else {
user := result.Data.(*model.User)
if user.Email != email {
t.Fatal()
}
}
}
func TestResetPassword(t *testing.T) {
th := api.Setup().InitBasic()
defer th.TearDown()
checkCommand(t, "user", "password", th.BasicUser.Email, "password2")
th.BasicClient.Logout()
th.BasicUser.Password = "password2"
th.LoginBasic()
}
func TestMakeUserActiveAndInactive(t *testing.T) {
th := api.Setup().InitBasic()
defer th.TearDown()
// first inactivate the user
checkCommand(t, "user", "deactivate", th.BasicUser.Email)
// activate the inactive user
checkCommand(t, "user", "activate", th.BasicUser.Email)
}

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

@@ -1,38 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/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 {
var user *model.User
if result := <-a.Srv.Store.User().GetByEmail(userArg); result.Err == nil {
user = result.Data.(*model.User)
}
if user == nil {
if result := <-a.Srv.Store.User().GetByUsername(userArg); result.Err == nil {
user = result.Data.(*model.User)
}
}
if user == nil {
if result := <-a.Srv.Store.User().Get(userArg); result.Err == nil {
user = result.Data.(*model.User)
}
}
return user
}

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

@@ -1,39 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
"github.com/mattermost/mattermost-server/store/sqlstore"
"github.com/spf13/cobra"
)
var versionCmd = &cobra.Command{
Use: "version",
Short: "Display version information",
RunE: versionCmdF,
}
func versionCmdF(cmd *cobra.Command, args []string) error {
a, err := initDBCommandContextCobra(cmd)
if err != nil {
return err
}
printVersion(a)
return nil
}
func printVersion(a *app.App) {
CommandPrintln("Version: " + model.CurrentVersion)
CommandPrintln("Build Number: " + model.BuildNumber)
CommandPrintln("Build Date: " + model.BuildDate)
CommandPrintln("Build Hash: " + model.BuildHash)
CommandPrintln("Build Enterprise Ready: " + model.BuildEnterpriseReady)
if supplier, ok := a.Srv.Store.(*store.LayeredStore).DatabaseLayer.(*sqlstore.SqlSupplier); ok {
CommandPrintln("DB Version: " + supplier.GetCurrentSchemaVersion())
}
}

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

@@ -1,12 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package main
import (
"testing"
)
func TestVersion(t *testing.T) {
checkCommand(t, "version")
}