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 }