[MM-31790] Support Packet Generation BACKEND (#16667)
* init commit * clean up the code * make mocks * fix translations * mocks and lint fixes * add tests * little fixes * Update i18n/en.json Co-authored-by: Scott Bishel <scott.bishel@mattermost.com> * Update i18n/en.json Co-authored-by: Scott Bishel <scott.bishel@mattermost.com> * Update i18n/en.json Co-authored-by: Scott Bishel <scott.bishel@mattermost.com> * Update i18n/en.json Co-authored-by: Scott Bishel <scott.bishel@mattermost.com> * Update i18n/en.json Co-authored-by: Scott Bishel <scott.bishel@mattermost.com> * Address Comments * fix i18n * update api endpoint * add enable file and file level for conditional show of banner * Address Comments * Make it more clear about returns * Create zip file utility function * update en.json * address comments * write tests * check for data in test * remove warning string * Correct expected and actual * set database through environment variables * reset environment variable at end of test Co-authored-by: Mattermod <mattermod@users.noreply.github.com> Co-authored-by: Scott Bishel <scott.bishel@mattermost.com>
Этот коммит содержится в:
@@ -4,11 +4,13 @@
|
|||||||
package api4
|
package api4
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -64,6 +66,64 @@ func (api *API) InitSystem() {
|
|||||||
api.BaseRoutes.ApiRoot.Handle("/warn_metrics/trial-license-ack/{warn_metric_id:[A-Za-z0-9-_]+}", api.ApiHandler(requestTrialLicenseAndAckWarnMetric)).Methods("POST")
|
api.BaseRoutes.ApiRoot.Handle("/warn_metrics/trial-license-ack/{warn_metric_id:[A-Za-z0-9-_]+}", api.ApiHandler(requestTrialLicenseAndAckWarnMetric)).Methods("POST")
|
||||||
api.BaseRoutes.System.Handle("/notices/{team_id:[A-Za-z0-9]+}", api.ApiSessionRequired(getProductNotices)).Methods("GET")
|
api.BaseRoutes.System.Handle("/notices/{team_id:[A-Za-z0-9]+}", api.ApiSessionRequired(getProductNotices)).Methods("GET")
|
||||||
api.BaseRoutes.System.Handle("/notices/view", api.ApiSessionRequired(updateViewedProductNotices)).Methods("PUT")
|
api.BaseRoutes.System.Handle("/notices/view", api.ApiSessionRequired(updateViewedProductNotices)).Methods("PUT")
|
||||||
|
|
||||||
|
api.BaseRoutes.System.Handle("/support_packet", api.ApiSessionRequired(generateSupportPacket)).Methods("GET")
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
const FileMime = "application/zip"
|
||||||
|
const OutputDirectory = "support_packet"
|
||||||
|
|
||||||
|
// Checking to see if the user is a admin of any sort or not
|
||||||
|
// If they are a admin, they should theoritcally have access to one or more of the system console read permissions
|
||||||
|
if !c.App.SessionHasPermissionToAny(*c.App.Session(), model.SysconsoleReadPermissions) {
|
||||||
|
c.SetPermissionError(model.SysconsoleReadPermissions...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checking to see if the server has a e10 or e20 license (this feature is only permitted for servers with licenses)
|
||||||
|
if c.App.Srv().License() == nil {
|
||||||
|
c.Err = model.NewAppError("Api4.generateSupportPacket", "api.no_license", nil, "", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileDatas := c.App.GenerateSupportPacket()
|
||||||
|
|
||||||
|
// Constructing the ZIP file name as per spec (mattermost_support_packet_YYYY-MM-DD-HH-MM.zip)
|
||||||
|
now := time.Now()
|
||||||
|
outputZipFilename := fmt.Sprintf("mattermost_support_packet_%s.zip", now.Format("2006-01-02-03-04"))
|
||||||
|
|
||||||
|
fileStorageBackend, fileBackendErr := c.App.FileBackend()
|
||||||
|
if fileBackendErr != nil {
|
||||||
|
c.Err = fileBackendErr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do this incase we get concurrent requests, we will always have a unique directory.
|
||||||
|
// This is to avoid the situation where we try to write to the same directory while we are trying to delete it (further down)
|
||||||
|
outputDirectoryToUse := OutputDirectory + "_" + model.NewId()
|
||||||
|
err := c.App.CreateZipFileAndAddFiles(fileStorageBackend, fileDatas, outputZipFilename, outputDirectoryToUse)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = model.NewAppError("Api4.generateSupportPacket", "api.unable_to_create_zip_file", nil, err.Error(), http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileBytes, err := fileStorageBackend.ReadFile(path.Join(outputDirectoryToUse, outputZipFilename))
|
||||||
|
defer fileStorageBackend.RemoveDirectory(outputDirectoryToUse)
|
||||||
|
if err != nil {
|
||||||
|
c.Err = model.NewAppError("Api4.generateSupportPacket", "api.unable_to_read_file_from_backend", nil, err.Error(), http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileBytesReader := bytes.NewReader(fileBytes)
|
||||||
|
|
||||||
|
// Send the zip file back to client
|
||||||
|
// We are able to pass 0 for content size due to the fact that Golang's serveContent (https://golang.org/src/net/http/fs.go)
|
||||||
|
// already sets that for us
|
||||||
|
writeFileResponseErr := writeFileResponse(outputZipFilename, FileMime, 0, now, *c.App.Config().ServiceSettings.WebserverMode, fileBytesReader, true, w, r)
|
||||||
|
if writeFileResponseErr != nil {
|
||||||
|
c.Err = model.NewAppError("generateSupportPacket", "api.unable_write_file_response", nil, writeFileResponseErr.Error(), http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) {
|
func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -187,6 +187,34 @@ func TestEmailTest(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenerateSupportPacket(t *testing.T) {
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
t.Run("As a System Administrator", func(t *testing.T) {
|
||||||
|
l := model.NewTestLicense()
|
||||||
|
th.App.Srv().SetLicense(l)
|
||||||
|
|
||||||
|
file, resp := th.SystemAdminClient.GenerateSupportPacket()
|
||||||
|
require.Nil(t, resp.Error)
|
||||||
|
require.NotZero(t, len(file))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("As a Regular User", func(t *testing.T) {
|
||||||
|
_, resp := th.Client.GenerateSupportPacket()
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Server with no License", func(t *testing.T) {
|
||||||
|
ok, resp := th.SystemAdminClient.RemoveLicenseFile()
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
_, resp = th.SystemAdminClient.GenerateSupportPacket()
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestSiteURLTest(t *testing.T) {
|
func TestSiteURLTest(t *testing.T) {
|
||||||
th := Setup(t)
|
th := Setup(t)
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|||||||
@@ -308,6 +308,9 @@ type AppIface interface {
|
|||||||
TeamMembersMinusGroupMembers(teamID string, groupIDs []string, page, perPage int) ([]*model.UserWithGroups, int64, *model.AppError)
|
TeamMembersMinusGroupMembers(teamID string, groupIDs []string, page, perPage int) ([]*model.UserWithGroups, int64, *model.AppError)
|
||||||
// This function migrates the default built in roles from code/config to the database.
|
// This function migrates the default built in roles from code/config to the database.
|
||||||
DoAdvancedPermissionsMigration()
|
DoAdvancedPermissionsMigration()
|
||||||
|
// This function zip's up all the files in fileDatas array and then saves it to the directory specified with the specified zip file name
|
||||||
|
// Ensure the zip file name ends with a .zip
|
||||||
|
CreateZipFileAndAddFiles(fileBackend filesstore.FileBackend, fileDatas []model.FileData, zipFileName, directory string) error
|
||||||
// This to be used for places we check the users password when they are already logged in
|
// This to be used for places we check the users password when they are already logged in
|
||||||
DoubleCheckPassword(user *model.User, password string) *model.AppError
|
DoubleCheckPassword(user *model.User, password string) *model.AppError
|
||||||
// UpdateBotActive marks a bot as active or inactive, along with its corresponding user.
|
// UpdateBotActive marks a bot as active or inactive, along with its corresponding user.
|
||||||
@@ -506,6 +509,7 @@ type AppIface interface {
|
|||||||
FindTeamByName(name string) bool
|
FindTeamByName(name string) bool
|
||||||
GenerateMfaSecret(userId string) (*model.MfaSecret, *model.AppError)
|
GenerateMfaSecret(userId string) (*model.MfaSecret, *model.AppError)
|
||||||
GeneratePublicLink(siteURL string, info *model.FileInfo) string
|
GeneratePublicLink(siteURL string, info *model.FileInfo) string
|
||||||
|
GenerateSupportPacket() []model.FileData
|
||||||
GetActivePluginManifests() ([]*model.Manifest, *model.AppError)
|
GetActivePluginManifests() ([]*model.Manifest, *model.AppError)
|
||||||
GetAllChannels(page, perPage int, opts model.ChannelSearchOpts) (*model.ChannelListWithTeamData, *model.AppError)
|
GetAllChannels(page, perPage int, opts model.ChannelSearchOpts) (*model.ChannelListWithTeamData, *model.AppError)
|
||||||
GetAllChannelsCount(opts model.ChannelSearchOpts) (int64, *model.AppError)
|
GetAllChannelsCount(opts model.ChannelSearchOpts) (int64, *model.AppError)
|
||||||
|
|||||||
49
app/file.go
49
app/file.go
@@ -4,6 +4,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
@@ -18,6 +19,8 @@ import (
|
|||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -1258,3 +1261,49 @@ func (a *App) CopyFileInfos(userId string, fileIds []string) ([]string, *model.A
|
|||||||
|
|
||||||
return newFileIds, nil
|
return newFileIds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function zip's up all the files in fileDatas array and then saves it to the directory specified with the specified zip file name
|
||||||
|
// Ensure the zip file name ends with a .zip
|
||||||
|
func (a *App) CreateZipFileAndAddFiles(fileBackend filesstore.FileBackend, fileDatas []model.FileData, zipFileName, directory string) error {
|
||||||
|
// Create Zip File (temporarily stored on disk)
|
||||||
|
conglomerateZipFile, err := os.Create(zipFileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(zipFileName)
|
||||||
|
|
||||||
|
// Create a new zip archive.
|
||||||
|
zipFileWriter := zip.NewWriter(conglomerateZipFile)
|
||||||
|
|
||||||
|
// Populate Zip file with File Datas array
|
||||||
|
err = populateZipfile(zipFileWriter, fileDatas)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
conglomerateZipFile.Seek(0, 0)
|
||||||
|
_, err = fileBackend.WriteFile(conglomerateZipFile, path.Join(directory, zipFileName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a implementation of Go's example of writing files to zip (with slight modification)
|
||||||
|
// https://golang.org/src/archive/zip/example_test.go
|
||||||
|
func populateZipfile(w *zip.Writer, fileDatas []model.FileData) error {
|
||||||
|
defer w.Close()
|
||||||
|
for _, fd := range fileDatas {
|
||||||
|
f, err := w.Create(fd.Filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.Write(fd.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -14,6 +15,8 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/plugin/plugintest/mock"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/services/filesstore/mocks"
|
||||||
"github.com/mattermost/mattermost-server/v5/utils/fileutils"
|
"github.com/mattermost/mattermost-server/v5/utils/fileutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -269,6 +272,23 @@ func TestMigrateFilenamesToFileInfos(t *testing.T) {
|
|||||||
assert.Equal(t, 0, len(infos))
|
assert.Equal(t, 0, len(infos))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreateZipFileAndAddFiles(t *testing.T) {
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
mockBackend := mocks.FileBackend{}
|
||||||
|
mockBackend.On("WriteFile", mock.Anything, "directory-to-heaven/zip-file-name-to-heaven.zip").Return(int64(666), errors.New("Only those who dare to fail greatly can ever achieve greatly"))
|
||||||
|
|
||||||
|
err := th.App.CreateZipFileAndAddFiles(&mockBackend, []model.FileData{}, "zip-file-name-to-heaven.zip", "directory-to-heaven")
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.Equal(t, err.Error(), "Only those who dare to fail greatly can ever achieve greatly")
|
||||||
|
|
||||||
|
mockBackend = mocks.FileBackend{}
|
||||||
|
mockBackend.On("WriteFile", mock.Anything, "directory-to-heaven/zip-file-name-to-heaven.zip").Return(int64(666), nil)
|
||||||
|
err = th.App.CreateZipFileAndAddFiles(&mockBackend, []model.FileData{}, "zip-file-name-to-heaven.zip", "directory-to-heaven")
|
||||||
|
require.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCopyFileInfos(t *testing.T) {
|
func TestCopyFileInfos(t *testing.T) {
|
||||||
th := Setup(t)
|
th := Setup(t)
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|||||||
@@ -2416,6 +2416,28 @@ func (a *OpenTracingAppLayer) CreateWebhookPost(userId string, channel *model.Ch
|
|||||||
return resultVar0, resultVar1
|
return resultVar0, resultVar1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *OpenTracingAppLayer) CreateZipFileAndAddFiles(fileBackend filesstore.FileBackend, fileDatas []model.FileData, zipFileName string, directory string) error {
|
||||||
|
origCtx := a.ctx
|
||||||
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateZipFileAndAddFiles")
|
||||||
|
|
||||||
|
a.ctx = newCtx
|
||||||
|
a.app.Srv().Store.SetContext(newCtx)
|
||||||
|
defer func() {
|
||||||
|
a.app.Srv().Store.SetContext(origCtx)
|
||||||
|
a.ctx = origCtx
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer span.Finish()
|
||||||
|
resultVar0 := a.app.CreateZipFileAndAddFiles(fileBackend, fileDatas, zipFileName, directory)
|
||||||
|
|
||||||
|
if resultVar0 != nil {
|
||||||
|
span.LogFields(spanlog.Error(resultVar0))
|
||||||
|
ext.Error.Set(span, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultVar0
|
||||||
|
}
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) DBHealthCheckDelete() error {
|
func (a *OpenTracingAppLayer) DBHealthCheckDelete() error {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DBHealthCheckDelete")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.DBHealthCheckDelete")
|
||||||
@@ -3943,6 +3965,23 @@ func (a *OpenTracingAppLayer) GeneratePublicLink(siteURL string, info *model.Fil
|
|||||||
return resultVar0
|
return resultVar0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *OpenTracingAppLayer) GenerateSupportPacket() []model.FileData {
|
||||||
|
origCtx := a.ctx
|
||||||
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GenerateSupportPacket")
|
||||||
|
|
||||||
|
a.ctx = newCtx
|
||||||
|
a.app.Srv().Store.SetContext(newCtx)
|
||||||
|
defer func() {
|
||||||
|
a.app.Srv().Store.SetContext(origCtx)
|
||||||
|
a.ctx = origCtx
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer span.Finish()
|
||||||
|
resultVar0 := a.app.GenerateSupportPacket()
|
||||||
|
|
||||||
|
return resultVar0
|
||||||
|
}
|
||||||
|
|
||||||
func (a *OpenTracingAppLayer) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) {
|
func (a *OpenTracingAppLayer) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) {
|
||||||
origCtx := a.ctx
|
origCtx := a.ctx
|
||||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetActivePluginManifests")
|
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetActivePluginManifests")
|
||||||
|
|||||||
185
app/server.go
185
app/server.go
@@ -6,8 +6,10 @@ package app
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/maphash"
|
"hash/maphash"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -23,6 +25,8 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
sentryhttp "github.com/getsentry/sentry-go/http"
|
sentryhttp "github.com/getsentry/sentry-go/http"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
@@ -636,6 +640,12 @@ func (s *Server) AppOptions() []AppOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return Database type (postgres or mysql) and current version of Mattermost
|
||||||
|
func (s *Server) DatabaseTypeAndMattermostVersion() (string, string) {
|
||||||
|
mattermostVersion, _ := s.Store.System().GetByName("Version")
|
||||||
|
return *s.Config().SqlSettings.DriverName, mattermostVersion.Value
|
||||||
|
}
|
||||||
|
|
||||||
// initLogging initializes and configures the logger. This may be called more than once.
|
// initLogging initializes and configures the logger. This may be called more than once.
|
||||||
func (s *Server) initLogging() error {
|
func (s *Server) initLogging() error {
|
||||||
if s.Log == nil {
|
if s.Log == nil {
|
||||||
@@ -1611,3 +1621,178 @@ func (s *Server) HttpService() httpservice.HTTPService {
|
|||||||
func (s *Server) SetLog(l *mlog.Logger) {
|
func (s *Server) SetLog(l *mlog.Logger) {
|
||||||
s.Log = l
|
s.Log = l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) GenerateSupportPacket() []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
|
||||||
|
|
||||||
|
// Creating an array of files that we are going to be adding to our zip file
|
||||||
|
fileDatas := []model.FileData{}
|
||||||
|
|
||||||
|
// A array of the functions that we can iterate through since they all have the same return value
|
||||||
|
functions := []func() (*model.FileData, string){
|
||||||
|
a.generateSupportPacketYaml,
|
||||||
|
a.createPluginsFile,
|
||||||
|
a.createSanitizedConfigFile,
|
||||||
|
a.getMattermostLog,
|
||||||
|
a.getNotificationsLog,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range functions {
|
||||||
|
fileData, warning := fn()
|
||||||
|
|
||||||
|
if fileData != nil {
|
||||||
|
fileDatas = append(fileDatas, *fileData)
|
||||||
|
} else {
|
||||||
|
warnings = append(warnings, warning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding a warning.txt file to the fileDatas if any warning
|
||||||
|
if len(warnings) > 0 {
|
||||||
|
finalWarning := strings.Join(warnings, "\n")
|
||||||
|
fileDatas = append(fileDatas, model.FileData{
|
||||||
|
Filename: "warning.txt",
|
||||||
|
Body: []byte(finalWarning),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileDatas
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) getNotificationsLog() (*model.FileData, string) {
|
||||||
|
var warning string
|
||||||
|
|
||||||
|
// Getting notifications.log
|
||||||
|
if *a.Srv().Config().NotificationLogSettings.EnableFile {
|
||||||
|
// notifications.log
|
||||||
|
notificationsLog := utils.GetNotificationsLogFileLocation(*a.Srv().Config().LogSettings.FileLocation)
|
||||||
|
|
||||||
|
notificationsLogFileData, notificationsLogFileDataErr := ioutil.ReadFile(notificationsLog)
|
||||||
|
|
||||||
|
if notificationsLogFileDataErr == nil {
|
||||||
|
fileData := model.FileData{
|
||||||
|
Filename: "notifications.log",
|
||||||
|
Body: notificationsLogFileData,
|
||||||
|
}
|
||||||
|
return &fileData, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
warning = fmt.Sprintf("ioutil.ReadFile(notificationsLog) Error: %s", notificationsLogFileDataErr.Error())
|
||||||
|
|
||||||
|
} else {
|
||||||
|
warning = "Unable to retrieve notifications.log because LogSettings: EnableFile is false in config.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, warning
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) getMattermostLog() (*model.FileData, string) {
|
||||||
|
var warning string
|
||||||
|
|
||||||
|
// Getting mattermost.log
|
||||||
|
if *a.Srv().Config().LogSettings.EnableFile {
|
||||||
|
// mattermost.log
|
||||||
|
mattermostLog := utils.GetLogFileLocation(*a.Srv().Config().LogSettings.FileLocation)
|
||||||
|
|
||||||
|
mattermostLogFileData, mattermostLogFileDataErr := ioutil.ReadFile(mattermostLog)
|
||||||
|
|
||||||
|
if mattermostLogFileDataErr == nil {
|
||||||
|
fileData := model.FileData{
|
||||||
|
Filename: "mattermost.log",
|
||||||
|
Body: mattermostLogFileData,
|
||||||
|
}
|
||||||
|
return &fileData, ""
|
||||||
|
}
|
||||||
|
warning = fmt.Sprintf("ioutil.ReadFile(mattermostLog) Error: %s", mattermostLogFileDataErr.Error())
|
||||||
|
|
||||||
|
} else {
|
||||||
|
warning = "Unable to retrieve mattermost.log because LogSettings: EnableFile is false in config.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, warning
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) createSanitizedConfigFile() (*model.FileData, string) {
|
||||||
|
// Getting sanitized config, prettifying it, and then adding it to our file data array
|
||||||
|
sanitizedConfigPrettyJSON, err := json.MarshalIndent(a.GetSanitizedConfig(), "", " ")
|
||||||
|
if err == nil {
|
||||||
|
fileData := model.FileData{
|
||||||
|
Filename: "sanitized_config.json",
|
||||||
|
Body: sanitizedConfigPrettyJSON,
|
||||||
|
}
|
||||||
|
return &fileData, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
warning := fmt.Sprintf("json.MarshalIndent(c.App.GetSanitizedConfig()) Error: %s", err.Error())
|
||||||
|
return nil, warning
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) createPluginsFile() (*model.FileData, string) {
|
||||||
|
var warning string
|
||||||
|
|
||||||
|
// Getting the plugins installed on the server, prettify it, and then add them to the file data array
|
||||||
|
pluginsResponse, appErr := a.GetPlugins()
|
||||||
|
if appErr == nil {
|
||||||
|
pluginsPrettyJSON, err := json.MarshalIndent(pluginsResponse, "", " ")
|
||||||
|
if err == nil {
|
||||||
|
fileData := model.FileData{
|
||||||
|
Filename: "plugins.json",
|
||||||
|
Body: pluginsPrettyJSON,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &fileData, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
warning = fmt.Sprintf("json.MarshalIndent(pluginsResponse) Error: %s", err.Error())
|
||||||
|
} else {
|
||||||
|
warning = fmt.Sprintf("c.App.GetPlugins() Error: %s", appErr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, warning
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) generateSupportPacketYaml() (*model.FileData, string) {
|
||||||
|
// Here we are getting information regarding Elastic Search
|
||||||
|
var elasticServerVersion string
|
||||||
|
var elasticServerPlugins []string
|
||||||
|
if a.Srv().SearchEngine.ElasticsearchEngine != nil {
|
||||||
|
elasticServerVersion = a.Srv().SearchEngine.ElasticsearchEngine.GetFullVersion()
|
||||||
|
elasticServerPlugins = a.Srv().SearchEngine.ElasticsearchEngine.GetPlugins()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here we are getting information regarding LDAP
|
||||||
|
ldapInterface := a.Srv().Ldap
|
||||||
|
var vendorName, vendorVersion string
|
||||||
|
if ldapInterface != nil {
|
||||||
|
vendorName, vendorVersion = ldapInterface.GetVendorNameAndVendorVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here we are getting information regarding the database (mysql/postgres + current Mattermost version)
|
||||||
|
databaseType, databaseVersion := a.Srv().DatabaseTypeAndMattermostVersion()
|
||||||
|
|
||||||
|
// Creating the struct for support packet yaml file
|
||||||
|
supportPacket := model.SupportPacket{
|
||||||
|
ServerOS: runtime.GOOS,
|
||||||
|
ServerArchitecture: runtime.GOARCH,
|
||||||
|
DatabaseType: databaseType,
|
||||||
|
DatabaseVersion: databaseVersion,
|
||||||
|
LdapVendorName: vendorName,
|
||||||
|
LdapVendorVersion: vendorVersion,
|
||||||
|
ElasticServerVersion: elasticServerVersion,
|
||||||
|
ElasticServerPlugins: elasticServerPlugins,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal to a Yaml File
|
||||||
|
supportPacketYaml, err := yaml.Marshal(&supportPacket)
|
||||||
|
if err == nil {
|
||||||
|
fileData := model.FileData{
|
||||||
|
Filename: "support_packet.yaml",
|
||||||
|
Body: supportPacketYaml,
|
||||||
|
}
|
||||||
|
return &fileData, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
warning := fmt.Sprintf("yaml.Marshal(&supportPacket) Error: %s", err.Error())
|
||||||
|
return nil, warning
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/config"
|
"github.com/mattermost/mattermost-server/v5/config"
|
||||||
@@ -161,6 +162,184 @@ func TestStartServerTLSSuccess(t *testing.T) {
|
|||||||
require.NoError(t, serverErr)
|
require.NoError(t, serverErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDatabaseTypeAndMattermostVersion(t *testing.T) {
|
||||||
|
sqlDrivernameEnvironment := os.Getenv("MM_SQLSETTINGS_DRIVERNAME")
|
||||||
|
defer os.Setenv("MM_SQLSETTINGS_DRIVERNAME", sqlDrivernameEnvironment)
|
||||||
|
|
||||||
|
os.Setenv("MM_SQLSETTINGS_DRIVERNAME", "postgres")
|
||||||
|
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
databaseType, mattermostVersion := th.Server.DatabaseTypeAndMattermostVersion()
|
||||||
|
assert.Equal(t, "postgres", databaseType)
|
||||||
|
assert.Equal(t, "5.31.0", mattermostVersion)
|
||||||
|
|
||||||
|
os.Setenv("MM_SQLSETTINGS_DRIVERNAME", "mysql")
|
||||||
|
|
||||||
|
th2 := Setup(t)
|
||||||
|
defer th2.TearDown()
|
||||||
|
|
||||||
|
databaseType, mattermostVersion = th2.Server.DatabaseTypeAndMattermostVersion()
|
||||||
|
assert.Equal(t, "mysql", databaseType)
|
||||||
|
assert.Equal(t, "5.31.0", mattermostVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateSupportPacket(t *testing.T) {
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
d1 := []byte("hello\ngo\n")
|
||||||
|
err := ioutil.WriteFile("mattermost.log", d1, 0777)
|
||||||
|
require.Nil(t, err)
|
||||||
|
err = ioutil.WriteFile("notifications.log", d1, 0777)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
fileDatas := th.App.GenerateSupportPacket()
|
||||||
|
testFiles := []string{"support_packet.yaml", "plugins.json", "sanitized_config.json", "mattermost.log", "notifications.log"}
|
||||||
|
for i, fileData := range fileDatas {
|
||||||
|
require.NotNil(t, fileData)
|
||||||
|
assert.Equal(t, testFiles[i], fileData.Filename)
|
||||||
|
assert.Positive(t, len(fileData.Body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove these two files and ensure that warning.txt file is generated
|
||||||
|
err = os.Remove("notifications.log")
|
||||||
|
require.Nil(t, err)
|
||||||
|
err = os.Remove("mattermost.log")
|
||||||
|
require.Nil(t, err)
|
||||||
|
fileDatas = th.App.GenerateSupportPacket()
|
||||||
|
testFiles = []string{"support_packet.yaml", "plugins.json", "sanitized_config.json", "warning.txt"}
|
||||||
|
for i, fileData := range fileDatas {
|
||||||
|
require.NotNil(t, fileData)
|
||||||
|
assert.Equal(t, testFiles[i], fileData.Filename)
|
||||||
|
assert.Positive(t, len(fileData.Body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNotificationsLog(t *testing.T) {
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
// Disable notifications file to get an error
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||||
|
*cfg.NotificationLogSettings.EnableFile = false
|
||||||
|
})
|
||||||
|
|
||||||
|
fileData, warning := th.App.getNotificationsLog()
|
||||||
|
assert.Nil(t, fileData)
|
||||||
|
assert.Equal(t, warning, "Unable to retrieve notifications.log because LogSettings: EnableFile is false in config.json")
|
||||||
|
|
||||||
|
// Enable notifications file but delete any notifications file to get an error trying to read the file
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||||
|
*cfg.NotificationLogSettings.EnableFile = true
|
||||||
|
})
|
||||||
|
|
||||||
|
// If any previous notifications.log file, lets delete it
|
||||||
|
os.Remove("notifications.log")
|
||||||
|
|
||||||
|
fileData, warning = th.App.getNotificationsLog()
|
||||||
|
assert.Nil(t, fileData)
|
||||||
|
assert.Contains(t, warning, "ioutil.ReadFile(notificationsLog) Error:")
|
||||||
|
|
||||||
|
// Happy path where we have file and no warning
|
||||||
|
d1 := []byte("hello\ngo\n")
|
||||||
|
err := ioutil.WriteFile("notifications.log", d1, 0777)
|
||||||
|
defer os.Remove("notifications.log")
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
fileData, warning = th.App.getNotificationsLog()
|
||||||
|
require.NotNil(t, fileData)
|
||||||
|
assert.Equal(t, "notifications.log", fileData.Filename)
|
||||||
|
assert.Positive(t, len(fileData.Body))
|
||||||
|
assert.Empty(t, warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMattermostLog(t *testing.T) {
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
// disable mattermost log file setting in config so we should get an warning
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||||
|
*cfg.LogSettings.EnableFile = false
|
||||||
|
})
|
||||||
|
|
||||||
|
fileData, warning := th.App.getMattermostLog()
|
||||||
|
assert.Nil(t, fileData)
|
||||||
|
assert.Equal(t, "Unable to retrieve mattermost.log because LogSettings: EnableFile is false in config.json", warning)
|
||||||
|
|
||||||
|
// We enable the setting but delete any mattermost log file
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||||
|
*cfg.LogSettings.EnableFile = true
|
||||||
|
})
|
||||||
|
|
||||||
|
// If any previous mattermost.log file, lets delete it
|
||||||
|
os.Remove("mattermost.log")
|
||||||
|
|
||||||
|
fileData, warning = th.App.getMattermostLog()
|
||||||
|
assert.Nil(t, fileData)
|
||||||
|
assert.Contains(t, warning, "ioutil.ReadFile(mattermostLog) Error:")
|
||||||
|
|
||||||
|
// Happy path where we get a log file and no warning
|
||||||
|
d1 := []byte("hello\ngo\n")
|
||||||
|
err := ioutil.WriteFile("mattermost.log", d1, 0777)
|
||||||
|
defer os.Remove("mattermost.log")
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
fileData, warning = th.App.getMattermostLog()
|
||||||
|
require.NotNil(t, fileData)
|
||||||
|
assert.Equal(t, "mattermost.log", fileData.Filename)
|
||||||
|
assert.Positive(t, len(fileData.Body))
|
||||||
|
assert.Empty(t, warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateSanitizedConfigFile(t *testing.T) {
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
// Happy path where we have a sanitized config file with no warning
|
||||||
|
fileData, warning := th.App.createSanitizedConfigFile()
|
||||||
|
require.NotNil(t, fileData)
|
||||||
|
assert.Equal(t, "sanitized_config.json", fileData.Filename)
|
||||||
|
assert.Positive(t, len(fileData.Body))
|
||||||
|
assert.Empty(t, warning)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreatePluginsFile(t *testing.T) {
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
// Happy path where we have a plugins file with no warning
|
||||||
|
fileData, warning := th.App.createPluginsFile()
|
||||||
|
require.NotNil(t, fileData)
|
||||||
|
assert.Equal(t, "plugins.json", fileData.Filename)
|
||||||
|
assert.Positive(t, len(fileData.Body))
|
||||||
|
assert.Empty(t, warning)
|
||||||
|
|
||||||
|
// Turn off plugins so we can get an error
|
||||||
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||||
|
*cfg.PluginSettings.Enable = false
|
||||||
|
})
|
||||||
|
|
||||||
|
// Plugins off in settings so no fileData and we get a warning instead
|
||||||
|
fileData, warning = th.App.createPluginsFile()
|
||||||
|
assert.Nil(t, fileData)
|
||||||
|
assert.Contains(t, warning, "c.App.GetPlugins() Error:")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateSupportPacketYaml(t *testing.T) {
|
||||||
|
th := Setup(t)
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
// Happy path where we have a support packet yaml file without any warnings
|
||||||
|
fileData, warning := th.App.generateSupportPacketYaml()
|
||||||
|
require.NotNil(t, fileData)
|
||||||
|
assert.Equal(t, "support_packet.yaml", fileData.Filename)
|
||||||
|
assert.Positive(t, len(fileData.Body))
|
||||||
|
assert.Empty(t, warning)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestStartServerTLSVersion(t *testing.T) {
|
func TestStartServerTLSVersion(t *testing.T) {
|
||||||
s, err := NewServer()
|
s, err := NewServer()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -222,6 +222,8 @@ func GenerateLimitedClientConfig(c *model.Config, telemetryID string, license *m
|
|||||||
props["BuildEnterpriseReady"] = model.BuildEnterpriseReady
|
props["BuildEnterpriseReady"] = model.BuildEnterpriseReady
|
||||||
|
|
||||||
props["EnableBotAccountCreation"] = strconv.FormatBool(*c.ServiceSettings.EnableBotAccountCreation)
|
props["EnableBotAccountCreation"] = strconv.FormatBool(*c.ServiceSettings.EnableBotAccountCreation)
|
||||||
|
props["EnableFile"] = strconv.FormatBool(*c.LogSettings.EnableFile)
|
||||||
|
props["FileLevel"] = *c.LogSettings.FileLevel
|
||||||
|
|
||||||
props["SiteName"] = *c.TeamSettings.SiteName
|
props["SiteName"] = *c.TeamSettings.SiteName
|
||||||
props["WebsocketURL"] = strings.TrimRight(*c.ServiceSettings.WebsocketURL, "/")
|
props["WebsocketURL"] = strings.TrimRight(*c.ServiceSettings.WebsocketURL, "/")
|
||||||
|
|||||||
@@ -23,4 +23,5 @@ type LdapInterface interface {
|
|||||||
FirstLoginSync(user *model.User, userAuthService, userAuthData, email string) *model.AppError
|
FirstLoginSync(user *model.User, userAuthService, userAuthData, email string) *model.AppError
|
||||||
UpdateProfilePictureIfNecessary(model.User, model.Session)
|
UpdateProfilePictureIfNecessary(model.User, model.Session)
|
||||||
GetADLdapIdFromSAMLId(authData string) string
|
GetADLdapIdFromSAMLId(authData string) string
|
||||||
|
GetVendorNameAndVendorVersion() (string, string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,6 +233,27 @@ func (_m *LdapInterface) GetUserAttributes(id string, attributes []string) (map[
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetVendorNameAndVendorVersion provides a mock function with given fields:
|
||||||
|
func (_m *LdapInterface) GetVendorNameAndVendorVersion() (string, string) {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
var r0 string
|
||||||
|
if rf, ok := ret.Get(0).(func() string); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 string
|
||||||
|
if rf, ok := ret.Get(1).(func() string); ok {
|
||||||
|
r1 = rf()
|
||||||
|
} else {
|
||||||
|
r1 = ret.Get(1).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// MigrateIDAttribute provides a mock function with given fields: toAttribute
|
// MigrateIDAttribute provides a mock function with given fields: toAttribute
|
||||||
func (_m *LdapInterface) MigrateIDAttribute(toAttribute string) error {
|
func (_m *LdapInterface) MigrateIDAttribute(toAttribute string) error {
|
||||||
ret := _m.Called(toAttribute)
|
ret := _m.Called(toAttribute)
|
||||||
|
|||||||
16
i18n/en.json
16
i18n/en.json
@@ -1580,6 +1580,10 @@
|
|||||||
"id": "api.migrate_to_saml.error",
|
"id": "api.migrate_to_saml.error",
|
||||||
"translation": "Unable to migrate SAML."
|
"translation": "Unable to migrate SAML."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.no_license",
|
||||||
|
"translation": "E10 or E20 license required to use this endpoint."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.oauth.allow_oauth.redirect_callback.app_error",
|
"id": "api.oauth.allow_oauth.redirect_callback.app_error",
|
||||||
"translation": "invalid_request: Supplied redirect_uri did not match registered callback_url."
|
"translation": "invalid_request: Supplied redirect_uri did not match registered callback_url."
|
||||||
@@ -3050,6 +3054,18 @@
|
|||||||
"id": "api.templates.welcome_subject",
|
"id": "api.templates.welcome_subject",
|
||||||
"translation": "[{{ .SiteName }}] You joined {{ .ServerURL }}"
|
"translation": "[{{ .SiteName }}] You joined {{ .ServerURL }}"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.unable_to_create_zip_file",
|
||||||
|
"translation": "Error creating zip file."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.unable_to_read_file_from_backend",
|
||||||
|
"translation": "Error reading file from backend"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "api.unable_write_file_response",
|
||||||
|
"translation": "Error downloading support packet file."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.upgrade_to_enterprise.already-done.app_error",
|
"id": "api.upgrade_to_enterprise.already-done.app_error",
|
||||||
"translation": "You have already upgraded to Mattermost Enterprise Edition. Please restart the server to finish the upgrade."
|
"translation": "You have already upgraded to Mattermost Enterprise Edition. Please restart the server to finish the upgrade."
|
||||||
|
|||||||
@@ -3278,6 +3278,21 @@ func (c *Client4) GetFileInfosForPost(postId string, etag string) ([]*FileInfo,
|
|||||||
|
|
||||||
// General/System Section
|
// General/System Section
|
||||||
|
|
||||||
|
// GenerateSupportPacket downloads the generated support packet
|
||||||
|
func (c *Client4) GenerateSupportPacket() ([]byte, *Response) {
|
||||||
|
r, appErr := c.DoApiGet(c.GetSystemRoute()+"/support_packet", "")
|
||||||
|
if appErr != nil {
|
||||||
|
return nil, BuildErrorResponse(r, appErr)
|
||||||
|
}
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, BuildErrorResponse(r, NewAppError("GetFile", "model.client.read_job_result_file.app_error", nil, err.Error(), r.StatusCode))
|
||||||
|
}
|
||||||
|
return data, BuildResponse(r)
|
||||||
|
}
|
||||||
|
|
||||||
// GetPing will return ok if the running goRoutines are below the threshold and unhealthy for above.
|
// GetPing will return ok if the running goRoutines are below the threshold and unhealthy for above.
|
||||||
func (c *Client4) GetPing() (string, *Response) {
|
func (c *Client4) GetPing() (string, *Response) {
|
||||||
r, err := c.DoApiGet(c.GetSystemRoute()+"/ping", "")
|
r, err := c.DoApiGet(c.GetSystemRoute()+"/ping", "")
|
||||||
|
|||||||
@@ -87,6 +87,22 @@ type ServerBusyState struct {
|
|||||||
Expires_ts string `json:"expires_ts,omitempty"`
|
Expires_ts string `json:"expires_ts,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SupportPacket struct {
|
||||||
|
ServerOS string `yaml:"server_os"`
|
||||||
|
ServerArchitecture string `yaml:"server_architecture"`
|
||||||
|
DatabaseType string `yaml:"database_type"`
|
||||||
|
DatabaseVersion string `yaml:"database_version"`
|
||||||
|
LdapVendorName string `yaml:"ldap_vendor_name,omitempty"`
|
||||||
|
LdapVendorVersion string `yaml:"ldap_vendor_version,omitempty"`
|
||||||
|
ElasticServerVersion string `yaml:"elastic_server_version,omitempty"`
|
||||||
|
ElasticServerPlugins []string `yaml:"elastic_server_plugins,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileData struct {
|
||||||
|
Filename string
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
|
|
||||||
func (sbs *ServerBusyState) ToJson() string {
|
func (sbs *ServerBusyState) ToJson() string {
|
||||||
b, _ := json.Marshal(sbs)
|
b, _ := json.Marshal(sbs)
|
||||||
return string(b)
|
return string(b)
|
||||||
|
|||||||
@@ -231,6 +231,14 @@ func (b *BleveEngine) GetVersion() int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BleveEngine) GetFullVersion() string {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BleveEngine) GetPlugins() []string {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BleveEngine) GetName() string {
|
func (b *BleveEngine) GetName() string {
|
||||||
return EngineName
|
return EngineName
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import (
|
|||||||
type SearchEngineInterface interface {
|
type SearchEngineInterface interface {
|
||||||
Start() *model.AppError
|
Start() *model.AppError
|
||||||
Stop() *model.AppError
|
Stop() *model.AppError
|
||||||
|
GetFullVersion() string
|
||||||
GetVersion() int
|
GetVersion() int
|
||||||
|
GetPlugins() []string
|
||||||
UpdateConfig(cfg *model.Config)
|
UpdateConfig(cfg *model.Config)
|
||||||
GetName() string
|
GetName() string
|
||||||
IsActive() bool
|
IsActive() bool
|
||||||
|
|||||||
@@ -176,6 +176,20 @@ func (_m *SearchEngineInterface) DeleteUserPosts(userID string) *model.AppError
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFullVersion provides a mock function with given fields:
|
||||||
|
func (_m *SearchEngineInterface) GetFullVersion() string {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
var r0 string
|
||||||
|
if rf, ok := ret.Get(0).(func() string); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
// GetName provides a mock function with given fields:
|
// GetName provides a mock function with given fields:
|
||||||
func (_m *SearchEngineInterface) GetName() string {
|
func (_m *SearchEngineInterface) GetName() string {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
@@ -190,6 +204,22 @@ func (_m *SearchEngineInterface) GetName() string {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPlugins provides a mock function with given fields:
|
||||||
|
func (_m *SearchEngineInterface) GetPlugins() []string {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
var r0 []string
|
||||||
|
if rf, ok := ret.Get(0).(func() []string); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
// GetVersion provides a mock function with given fields:
|
// GetVersion provides a mock function with given fields:
|
||||||
func (_m *SearchEngineInterface) GetVersion() int {
|
func (_m *SearchEngineInterface) GetVersion() int {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
|
|||||||
1
testlib/testdata/mysql_migration_warmup.sql
поставляемый
1
testlib/testdata/mysql_migration_warmup.sql
поставляемый
@@ -35,6 +35,7 @@ INSERT INTO `Systems` VALUES ('add_use_group_mentions_permission', 'true');
|
|||||||
INSERT INTO `Systems` VALUES ('add_system_console_permissions', 'true');
|
INSERT INTO `Systems` VALUES ('add_system_console_permissions', 'true');
|
||||||
INSERT INTO `Systems` VALUES ('add_convert_channel_permissions', 'true');
|
INSERT INTO `Systems` VALUES ('add_convert_channel_permissions', 'true');
|
||||||
INSERT INTO `Systems` VALUES ('manage_shared_channel_permissions', 'true');
|
INSERT INTO `Systems` VALUES ('manage_shared_channel_permissions', 'true');
|
||||||
|
INSERT INTO `Systems` VALUES ('Version', '5.31.0');
|
||||||
|
|
||||||
|
|
||||||
/*!40000 ALTER TABLE `Systems` ENABLE KEYS */;
|
/*!40000 ALTER TABLE `Systems` ENABLE KEYS */;
|
||||||
|
|||||||
1
testlib/testdata/postgres_migration_warmup.sql
поставляемый
1
testlib/testdata/postgres_migration_warmup.sql
поставляемый
@@ -43,6 +43,7 @@ INSERT INTO public.systems VALUES ('add_use_group_mentions_permission', 'true');
|
|||||||
INSERT INTO public.systems VALUES ('add_system_console_permissions', 'true');
|
INSERT INTO public.systems VALUES ('add_system_console_permissions', 'true');
|
||||||
INSERT INTO public.systems VALUES ('add_convert_channel_permissions', 'true');
|
INSERT INTO public.systems VALUES ('add_convert_channel_permissions', 'true');
|
||||||
INSERT INTO public.systems VALUES ('manage_shared_channel_permissions', 'true');
|
INSERT INTO public.systems VALUES ('manage_shared_channel_permissions', 'true');
|
||||||
|
INSERT INTO public.systems VALUES ('Version', '5.31.0');
|
||||||
|
|
||||||
--
|
--
|
||||||
-- PostgreSQL database dump complete
|
-- PostgreSQL database dump complete
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user