github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/cmd/mattermost/commands/export.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package commands
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"time"
    11  
    12  	"github.com/mattermost/mattermost-server/v5/audit"
    13  	"github.com/mattermost/mattermost-server/v5/model"
    14  	"github.com/pkg/errors"
    15  	"github.com/spf13/cobra"
    16  )
    17  
    18  var ExportCmd = &cobra.Command{
    19  	Use:   "export",
    20  	Short: "Export data from Mattermost",
    21  	Long:  "Export data from Mattermost in a format suitable for import into a third-party application or another Mattermost instance",
    22  }
    23  
    24  var ScheduleExportCmd = &cobra.Command{
    25  	Use:     "schedule",
    26  	Short:   "Schedule an export data job in Mattermost",
    27  	Long:    "Schedule an export data job in Mattermost (this will run asynchronously via a background worker)",
    28  	Example: "export schedule --format=actiance --exportFrom=12345 --timeoutSeconds=12345",
    29  	RunE:    scheduleExportCmdF,
    30  }
    31  
    32  var CsvExportCmd = &cobra.Command{
    33  	Use:     "csv",
    34  	Short:   "Export data from Mattermost in CSV format",
    35  	Long:    "Export data from Mattermost in CSV format",
    36  	Example: "export csv --exportFrom=12345",
    37  	RunE:    buildExportCmdF("csv"),
    38  }
    39  
    40  var ActianceExportCmd = &cobra.Command{
    41  	Use:     "actiance",
    42  	Short:   "Export data from Mattermost in Actiance format",
    43  	Long:    "Export data from Mattermost in Actiance format",
    44  	Example: "export actiance --exportFrom=12345",
    45  	RunE:    buildExportCmdF("actiance"),
    46  }
    47  
    48  var GlobalRelayZipExportCmd = &cobra.Command{
    49  	Use:     "global-relay-zip",
    50  	Short:   "Export data from Mattermost into a zip file containing emails to send to Global Relay for debug and testing purposes only.",
    51  	Long:    "Export data from Mattermost into a zip file containing emails to send to Global Relay for debug and testing purposes only. This does not archive any information in Global Relay.",
    52  	Example: "export global-relay-zip --exportFrom=12345",
    53  	RunE:    buildExportCmdF("globalrelay-zip"),
    54  }
    55  
    56  var BulkExportCmd = &cobra.Command{
    57  	Use:     "bulk [file]",
    58  	Short:   "Export bulk data.",
    59  	Long:    "Export data to a file compatible with the Mattermost Bulk Import format.",
    60  	Example: "export bulk bulk_data.json",
    61  	RunE:    bulkExportCmdF,
    62  	Args:    cobra.ExactArgs(1),
    63  }
    64  
    65  func init() {
    66  	ScheduleExportCmd.Flags().String("format", "actiance", "The format to export data")
    67  	ScheduleExportCmd.Flags().Int64("exportFrom", -1, "The timestamp of the earliest post to export, expressed in seconds since the unix epoch.")
    68  	ScheduleExportCmd.Flags().Int("timeoutSeconds", -1, "The maximum number of seconds to wait for the job to complete before timing out.")
    69  
    70  	CsvExportCmd.Flags().Int64("exportFrom", -1, "The timestamp of the earliest post to export, expressed in seconds since the unix epoch.")
    71  
    72  	ActianceExportCmd.Flags().Int64("exportFrom", -1, "The timestamp of the earliest post to export, expressed in seconds since the unix epoch.")
    73  	GlobalRelayZipExportCmd.Flags().Int64("exportFrom", -1, "The timestamp of the earliest post to export, expressed in seconds since the unix epoch.")
    74  
    75  	BulkExportCmd.Flags().Bool("all-teams", true, "Export all teams from the server.")
    76  
    77  	ExportCmd.AddCommand(ScheduleExportCmd)
    78  	ExportCmd.AddCommand(CsvExportCmd)
    79  	ExportCmd.AddCommand(ActianceExportCmd)
    80  	ExportCmd.AddCommand(GlobalRelayZipExportCmd)
    81  	ExportCmd.AddCommand(BulkExportCmd)
    82  
    83  	RootCmd.AddCommand(ExportCmd)
    84  }
    85  
    86  func scheduleExportCmdF(command *cobra.Command, args []string) error {
    87  	a, err := InitDBCommandContextCobra(command)
    88  	if err != nil {
    89  		return err
    90  	}
    91  	defer a.Srv().Shutdown()
    92  
    93  	if !*a.Config().MessageExportSettings.EnableExport {
    94  		return errors.New("ERROR: The message export feature is not enabled")
    95  	}
    96  
    97  	// for now, format is hard-coded to actiance. In time, we'll have to support other formats and inject them into job data
    98  	format, err := command.Flags().GetString("format")
    99  	if err != nil {
   100  		return errors.New("format flag error")
   101  	}
   102  	if format != "actiance" {
   103  		return errors.New("unsupported export format")
   104  	}
   105  
   106  	startTime, err := command.Flags().GetInt64("exportFrom")
   107  	if err != nil {
   108  		return errors.New("exportFrom flag error")
   109  	}
   110  	if startTime < 0 {
   111  		return errors.New("exportFrom must be a positive integer")
   112  	}
   113  
   114  	timeoutSeconds, err := command.Flags().GetInt("timeoutSeconds")
   115  	if err != nil {
   116  		return errors.New("timeoutSeconds error")
   117  	}
   118  	if timeoutSeconds < 0 {
   119  		return errors.New("timeoutSeconds must be a positive integer")
   120  	}
   121  
   122  	if messageExportI := a.MessageExport(); messageExportI != nil {
   123  		ctx := context.Background()
   124  		if timeoutSeconds > 0 {
   125  			var cancel context.CancelFunc
   126  			ctx, cancel = context.WithTimeout(ctx, time.Second*time.Duration(timeoutSeconds))
   127  			defer cancel()
   128  		}
   129  
   130  		job, err := messageExportI.StartSynchronizeJob(ctx, startTime)
   131  		if err != nil || job.Status == model.JOB_STATUS_ERROR || job.Status == model.JOB_STATUS_CANCELED {
   132  			CommandPrintErrorln("ERROR: Message export job failed. Please check the server logs")
   133  		} else {
   134  			CommandPrettyPrintln("SUCCESS: Message export job complete")
   135  
   136  			auditRec := a.MakeAuditRecord("scheduleExport", audit.Success)
   137  			auditRec.AddMeta("format", format)
   138  			auditRec.AddMeta("start", startTime)
   139  			a.LogAuditRec(auditRec, nil)
   140  		}
   141  	}
   142  	return nil
   143  }
   144  
   145  func buildExportCmdF(format string) func(command *cobra.Command, args []string) error {
   146  	return func(command *cobra.Command, args []string) error {
   147  		a, err := InitDBCommandContextCobra(command)
   148  		license := a.Srv().License()
   149  		if err != nil {
   150  			return err
   151  		}
   152  		defer a.Srv().Shutdown()
   153  
   154  		startTime, err := command.Flags().GetInt64("exportFrom")
   155  		if err != nil {
   156  			return errors.New("exportFrom flag error")
   157  		}
   158  		if startTime < 0 {
   159  			return errors.New("exportFrom must be a positive integer")
   160  		}
   161  
   162  		if a.MessageExport() == nil || license == nil || !*license.Features.MessageExport {
   163  			return errors.New("message export feature not available")
   164  		}
   165  
   166  		warningsCount, appErr := a.MessageExport().RunExport(format, startTime)
   167  		if appErr != nil {
   168  			return appErr
   169  		}
   170  		if warningsCount == 0 {
   171  			CommandPrettyPrintln("SUCCESS: Your data was exported.")
   172  		} else {
   173  			CommandPrettyPrintln(fmt.Sprintf("WARNING: %d warnings encountered, see warning.txt for details.", warningsCount))
   174  		}
   175  
   176  		auditRec := a.MakeAuditRecord("buildExport", audit.Success)
   177  		auditRec.AddMeta("format", format)
   178  		auditRec.AddMeta("start", startTime)
   179  		a.LogAuditRec(auditRec, nil)
   180  
   181  		return nil
   182  	}
   183  }
   184  
   185  func bulkExportCmdF(command *cobra.Command, args []string) error {
   186  	a, err := InitDBCommandContextCobra(command)
   187  	if err != nil {
   188  		return err
   189  	}
   190  	defer a.Srv().Shutdown()
   191  
   192  	allTeams, err := command.Flags().GetBool("all-teams")
   193  	if err != nil {
   194  		return errors.Wrap(err, "all-teams flag error")
   195  	}
   196  	if !allTeams {
   197  		return errors.New("Nothing to export. Please specify the --all-teams flag to export all teams.")
   198  	}
   199  
   200  	fileWriter, err := os.Create(args[0])
   201  	if err != nil {
   202  		return err
   203  	}
   204  	defer fileWriter.Close()
   205  
   206  	// Path to directory of custom emoji
   207  	pathToEmojiDir := "data/emoji/"
   208  
   209  	customDataDir := a.Config().FileSettings.Directory
   210  	if customDataDir != nil && *customDataDir != "" {
   211  		pathToEmojiDir = *customDataDir + "emoji/"
   212  	}
   213  
   214  	// Name of the directory to export custom emoji
   215  	dirNameToExportEmoji := "exported_emoji"
   216  
   217  	// args[0] points to the filename/filepath passed with export bulk command
   218  	if err := a.BulkExport(fileWriter, args[0], pathToEmojiDir, dirNameToExportEmoji); err != nil {
   219  		CommandPrintErrorln(err.Error())
   220  		return err
   221  	}
   222  
   223  	auditRec := a.MakeAuditRecord("bulkExport", audit.Success)
   224  	auditRec.AddMeta("all_teams", allTeams)
   225  	auditRec.AddMeta("file", args[0])
   226  	a.LogAuditRec(auditRec, nil)
   227  
   228  	return nil
   229  }