Make support packet composable with plugins (#26403)

---------

Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com>
Этот коммит содержится в:
Ibrahim Serdar Acikgoz
2024-04-12 10:05:58 +02:00
коммит произвёл GitHub
родитель 165b5ea821
Коммит 92f11f8971
19 изменённых файлов: 505 добавлений и 158 удалений

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

@@ -89,15 +89,28 @@ func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
// We support the existing API hence the logs are always included
// if nothing specified.
includeLogs := true
if r.FormValue("basic_server_logs") == "false" {
includeLogs = false
}
supportPacketOptions := &model.SupportPacketOptions{
IncludeLogs: includeLogs,
PluginPackets: r.Form["plugin_packets"],
}
// Checking to see if the server has a e10 or e20 license (this feature is only permitted for servers with licenses)
if c.App.Channels().License() == nil {
c.Err = model.NewAppError("Api4.generateSupportPacket", "api.no_license", nil, "", http.StatusForbidden)
return
}
fileDatas := c.App.GenerateSupportPacket(c.AppContext)
fileDatas := c.App.GenerateSupportPacket(c.AppContext, supportPacketOptions)
// Constructing the ZIP file name as per spec (mattermost_support_packet_YYYY-MM-DD-HH-MM.zip)
// Note that this filename is also being checked at the webapp, please update the
// regex within the commercial_support_modal.tsx file if the naming convention ever changes.
now := time.Now()
outputZipFilename := fmt.Sprintf("mattermost_support_packet_%s.zip", now.Format("2006-01-02-03-04"))

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

@@ -618,7 +618,7 @@ type AppIface interface {
GenerateMfaSecret(userID string) (*model.MfaSecret, *model.AppError)
GeneratePresignURLForExport(name string) (*model.PresignURLResponse, *model.AppError)
GeneratePublicLink(siteURL string, info *model.FileInfo) string
GenerateSupportPacket(c request.CTX) []model.FileData
GenerateSupportPacket(c request.CTX, options *model.SupportPacketOptions) []model.FileData
GetAcknowledgementsForPost(postID string) ([]*model.PostAcknowledgement, *model.AppError)
GetAcknowledgementsForPostList(postList *model.PostList) (map[string][]*model.PostAcknowledgement, *model.AppError)
GetActivePluginManifests() ([]*model.Manifest, *model.AppError)

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

@@ -4786,7 +4786,7 @@ func (a *OpenTracingAppLayer) GeneratePublicLink(siteURL string, info *model.Fil
return resultVar0
}
func (a *OpenTracingAppLayer) GenerateSupportPacket(c request.CTX) []model.FileData {
func (a *OpenTracingAppLayer) GenerateSupportPacket(c request.CTX, options *model.SupportPacketOptions) []model.FileData {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GenerateSupportPacket")
@@ -4798,7 +4798,7 @@ func (a *OpenTracingAppLayer) GenerateSupportPacket(c request.CTX) []model.FileD
}()
defer span.Finish()
resultVar0 := a.app.GenerateSupportPacket(c)
resultVar0 := a.app.GenerateSupportPacket(c, options)
return resultVar0
}

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

@@ -26,7 +26,7 @@ const (
cpuProfileDuration = 5 * time.Second
)
func (a *App) GenerateSupportPacket(c request.CTX) []model.FileData {
func (a *App) GenerateSupportPacket(c request.CTX, options *model.SupportPacketOptions) []model.FileData {
// If any errors we come across within this function, we will log it in a warning.txt file so that we know why certain files did not get produced if any
var warnings []string
@@ -35,14 +35,17 @@ func (a *App) GenerateSupportPacket(c request.CTX) []model.FileData {
// A array of the functions that we can iterate through since they all have the same return value
functions := map[string]func(c request.CTX) (*model.FileData, error){
"support package": a.generateSupportPacketYaml,
"plugins": a.createPluginsFile,
"config": a.createSanitizedConfigFile,
"mattermost log": a.getMattermostLog,
"notification log": a.getNotificationsLog,
"cpu profile": a.createCPUProfile,
"heap profile": a.createHeapProfile,
"goroutines": a.createGoroutineProfile,
"support package": a.generateSupportPacketYaml,
"plugins": a.createPluginsFile,
"config": a.createSanitizedConfigFile,
"cpu profile": a.createCPUProfile,
"heap profile": a.createHeapProfile,
"goroutines": a.createGoroutineProfile,
}
if options.IncludeLogs {
functions["mattermost log"] = a.getMattermostLog
functions["notification log"] = a.getNotificationsLog
}
for name, fn := range functions {
@@ -57,6 +60,27 @@ func (a *App) GenerateSupportPacket(c request.CTX) []model.FileData {
}
}
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
pluginContext := pluginContext(c)
for _, id := range options.PluginPackets {
hooks, err := pluginsEnvironment.HooksForPlugin(id)
if err != nil {
c.Logger().Error("Failed to call hooks for plugin", mlog.Err(err), mlog.String("plugin", id))
warnings = append(warnings, err.Error())
continue
}
pluginData, err := hooks.GenerateSupportData(pluginContext)
if err != nil {
c.Logger().Warn("Failed to generate plugin file for support package", mlog.Err(err), mlog.String("plugin", id))
warnings = append(warnings, err.Error())
continue
}
for _, data := range pluginData {
fileDatas = append(fileDatas, *data)
}
}
}
// Adding a warning.txt file to the fileDatas if any warning
if len(warnings) > 0 {
finalWarning := strings.Join(warnings, "\n")

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

@@ -168,55 +168,91 @@ func TestGenerateSupportPacket(t *testing.T) {
logLocation := config.GetLogFileLocation(dir)
notificationsLogLocation := config.GetNotificationsLogFileLocation(dir)
d1 := []byte("hello\ngo\n")
err = os.WriteFile(logLocation, d1, 0777)
require.NoError(t, err)
err = os.WriteFile(notificationsLogLocation, d1, 0777)
require.NoError(t, err)
fileDatas := th.App.GenerateSupportPacket(th.Context)
var rFileNames []string
testFiles := []string{
"support_packet.yaml",
"plugins.json",
"sanitized_config.json",
"mattermost.log",
"notifications.log",
"cpu.prof",
"heap.prof",
"goroutines",
genMockLogFiles := func() {
d1 := []byte("hello\ngo\n")
genErr := os.WriteFile(logLocation, d1, 0777)
require.NoError(t, genErr)
genErr = os.WriteFile(notificationsLogLocation, d1, 0777)
require.NoError(t, genErr)
}
for _, fileData := range fileDatas {
require.NotNil(t, fileData)
assert.Positive(t, len(fileData.Body))
genMockLogFiles()
rFileNames = append(rFileNames, fileData.Filename)
}
assert.ElementsMatch(t, testFiles, rFileNames)
t.Run("generate support packet with logs", func(t *testing.T) {
fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
IncludeLogs: true,
})
var rFileNames []string
testFiles := []string{
"support_packet.yaml",
"plugins.json",
"sanitized_config.json",
"mattermost.log",
"notifications.log",
"cpu.prof",
"heap.prof",
"goroutines",
}
for _, fileData := range fileDatas {
require.NotNil(t, fileData)
assert.Positive(t, len(fileData.Body))
// Remove these two files and ensure that warning.txt file is generated
err = os.Remove(logLocation)
require.NoError(t, err)
err = os.Remove(notificationsLogLocation)
require.NoError(t, err)
fileDatas = th.App.GenerateSupportPacket(th.Context)
testFiles = []string{
"support_packet.yaml",
"plugins.json",
"sanitized_config.json",
"cpu.prof",
"heap.prof",
"warning.txt",
"goroutines",
}
rFileNames = nil
for _, fileData := range fileDatas {
require.NotNil(t, fileData)
assert.Positive(t, len(fileData.Body))
rFileNames = append(rFileNames, fileData.Filename)
}
assert.ElementsMatch(t, testFiles, rFileNames)
})
rFileNames = append(rFileNames, fileData.Filename)
}
assert.ElementsMatch(t, testFiles, rFileNames)
t.Run("generate support packet without logs", func(t *testing.T) {
fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
IncludeLogs: false,
})
testFiles := []string{
"support_packet.yaml",
"plugins.json",
"sanitized_config.json",
"cpu.prof",
"heap.prof",
"goroutines",
}
var rFileNames []string
for _, fileData := range fileDatas {
require.NotNil(t, fileData)
assert.Positive(t, len(fileData.Body))
rFileNames = append(rFileNames, fileData.Filename)
}
assert.ElementsMatch(t, testFiles, rFileNames)
})
t.Run("remove the log files and ensure that warning.txt file is generated", func(t *testing.T) {
// Remove these two files and ensure that warning.txt file is generated
err = os.Remove(logLocation)
require.NoError(t, err)
err = os.Remove(notificationsLogLocation)
require.NoError(t, err)
t.Cleanup(genMockLogFiles)
fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
IncludeLogs: true,
})
testFiles := []string{
"support_packet.yaml",
"plugins.json",
"sanitized_config.json",
"cpu.prof",
"heap.prof",
"warning.txt",
"goroutines",
}
var rFileNames []string
for _, fileData := range fileDatas {
require.NotNil(t, fileData)
assert.Positive(t, len(fileData.Body))
rFileNames = append(rFileNames, fileData.Filename)
}
assert.ElementsMatch(t, testFiles, rFileNames)
})
t.Run("steps that generated an error should still return file data", func(t *testing.T) {
mockStore := smocks.Store{}
@@ -241,7 +277,9 @@ func TestGenerateSupportPacket(t *testing.T) {
mockStore.On("GetDbVersion", false).Return("1.0.0", nil)
th.App.Srv().SetStore(&mockStore)
fileDatas := th.App.GenerateSupportPacket(th.Context)
fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
IncludeLogs: false,
})
var rFileNames []string
for _, fileData := range fileDatas {