[MM-31085] Create a db init command to initialize the database (#16489)
Automatic Merge
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
6a3bc50f13
Коммит
e057e5b10b
72
cmd/mattermost/commands/db.go
Обычный файл
72
cmd/mattermost/commands/db.go
Обычный файл
@@ -0,0 +1,72 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/v5/config"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/store/sqlstore"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DbCmd = &cobra.Command{
|
||||||
|
Use: "db",
|
||||||
|
Short: "Commands related to the database",
|
||||||
|
}
|
||||||
|
|
||||||
|
var InitDbCmd = &cobra.Command{
|
||||||
|
Use: "init",
|
||||||
|
Short: "Initialize the database",
|
||||||
|
Long: `Initialize the database for a given DSN, executing the migrations and loading the custom defaults if any.
|
||||||
|
|
||||||
|
This command should be run using a database configuration DSN.`,
|
||||||
|
Example: ` # you can use the config flag to pass the DSN
|
||||||
|
$ mattermost db init --config postgres://localhost/mattermost
|
||||||
|
|
||||||
|
# or you can use the MM_CONFIG environment variable
|
||||||
|
$ MM_CONFIG=postgres://localhost/mattermost mattermost db init
|
||||||
|
|
||||||
|
# and you can set a custom defaults file to be loaded into the database
|
||||||
|
$ MM_CUSTOM_DEFAULTS_PATH=custom.json MM_CONFIG=postgres://localhost/mattermost mattermost db init`,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: initDbCmdF,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
DbCmd.AddCommand(
|
||||||
|
InitDbCmd,
|
||||||
|
)
|
||||||
|
|
||||||
|
RootCmd.AddCommand(
|
||||||
|
DbCmd,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDbCmdF(command *cobra.Command, _ []string) error {
|
||||||
|
dsn := getConfigDSN(command, config.GetEnvironment())
|
||||||
|
if !config.IsDatabaseDSN(dsn) {
|
||||||
|
return errors.New("this command should be run using a database configuration DSN")
|
||||||
|
}
|
||||||
|
|
||||||
|
customDefaults, err := loadCustomDefaults()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error loading custom configuration defaults")
|
||||||
|
}
|
||||||
|
|
||||||
|
configStore, err := config.NewStore(getConfigDSN(command, config.GetEnvironment()), false, customDefaults)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to load configuration")
|
||||||
|
}
|
||||||
|
defer configStore.Close()
|
||||||
|
|
||||||
|
sqlStore := sqlstore.New(configStore.Get().SqlSettings, nil)
|
||||||
|
defer sqlStore.Close()
|
||||||
|
|
||||||
|
fmt.Println("Database store correctly initialised")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -16,7 +15,6 @@ import (
|
|||||||
"github.com/mattermost/mattermost-server/v5/config"
|
"github.com/mattermost/mattermost-server/v5/config"
|
||||||
"github.com/mattermost/mattermost-server/v5/manualtesting"
|
"github.com/mattermost/mattermost-server/v5/manualtesting"
|
||||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
|
||||||
"github.com/mattermost/mattermost-server/v5/utils"
|
"github.com/mattermost/mattermost-server/v5/utils"
|
||||||
"github.com/mattermost/mattermost-server/v5/web"
|
"github.com/mattermost/mattermost-server/v5/web"
|
||||||
"github.com/mattermost/mattermost-server/v5/wsapi"
|
"github.com/mattermost/mattermost-server/v5/wsapi"
|
||||||
@@ -24,8 +22,6 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
const CUSTOM_DEFAULTS_ENV_VAR = "MM_CUSTOM_DEFAULTS_PATH"
|
|
||||||
|
|
||||||
var serverCmd = &cobra.Command{
|
var serverCmd = &cobra.Command{
|
||||||
Use: "server",
|
Use: "server",
|
||||||
Short: "Run the Mattermost server",
|
Short: "Run the Mattermost server",
|
||||||
@@ -38,27 +34,6 @@ func init() {
|
|||||||
RootCmd.RunE = serverCmdF
|
RootCmd.RunE = serverCmdF
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadCustomDefaults() (*model.Config, error) {
|
|
||||||
customDefaultsPath := os.Getenv(CUSTOM_DEFAULTS_ENV_VAR)
|
|
||||||
if customDefaultsPath == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.Open(customDefaultsPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "unable to open custom defaults file at %q", customDefaultsPath)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
var customDefaults *model.Config
|
|
||||||
err = json.NewDecoder(file).Decode(&customDefaults)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "unable to decode custom defaults configuration")
|
|
||||||
}
|
|
||||||
|
|
||||||
return customDefaults, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func serverCmdF(command *cobra.Command, args []string) error {
|
func serverCmdF(command *cobra.Command, args []string) error {
|
||||||
disableConfigWatch, _ := command.Flags().GetBool("disableconfigwatch")
|
disableConfigWatch, _ := command.Flags().GetBool("disableconfigwatch")
|
||||||
usedPlatform, _ := command.Flags().GetBool("platform")
|
usedPlatform, _ := command.Flags().GetBool("platform")
|
||||||
|
|||||||
@@ -5,15 +5,20 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/mlog"
|
"github.com/mattermost/mattermost-server/v5/mlog"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const CUSTOM_DEFAULTS_ENV_VAR = "MM_CUSTOM_DEFAULTS_PATH"
|
||||||
|
|
||||||
// prettyPrintStruct will return a prettyPrint version of a given struct
|
// prettyPrintStruct will return a prettyPrint version of a given struct
|
||||||
func prettyPrintStruct(t interface{}) string {
|
func prettyPrintStruct(t interface{}) string {
|
||||||
return prettyPrintMap(structToMap(t))
|
return prettyPrintMap(structToMap(t))
|
||||||
@@ -116,3 +121,24 @@ func getConfigDSN(command *cobra.Command, env map[string]string) string {
|
|||||||
|
|
||||||
return configDSN
|
return configDSN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadCustomDefaults() (*model.Config, error) {
|
||||||
|
customDefaultsPath := os.Getenv(CUSTOM_DEFAULTS_ENV_VAR)
|
||||||
|
if customDefaultsPath == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(customDefaultsPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to open custom defaults file at %q: %w", customDefaultsPath, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var customDefaults *model.Config
|
||||||
|
err = json.NewDecoder(file).Decode(&customDefaults)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to decode custom defaults configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return customDefaults, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package config
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/v5/model"
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
@@ -83,7 +82,7 @@ func NewStoreFromBacking(backingStore BackingStore, customDefaults *model.Config
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getBackingStore(dsn string, watch bool) (BackingStore, error) {
|
func getBackingStore(dsn string, watch bool) (BackingStore, error) {
|
||||||
if strings.HasPrefix(dsn, "mysql://") || strings.HasPrefix(dsn, "postgres://") {
|
if IsDatabaseDSN(dsn) {
|
||||||
return NewDatabaseStore(dsn)
|
return NewDatabaseStore(dsn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -173,6 +173,10 @@ func Merge(cfg *model.Config, patch *model.Config, mergeConfig *utils.MergeConfi
|
|||||||
return &retCfg, nil
|
return &retCfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsDatabaseDSN(dsn string) bool {
|
||||||
|
return strings.HasPrefix(dsn, "mysql://") || strings.HasPrefix(dsn, "postgres://")
|
||||||
|
}
|
||||||
|
|
||||||
// stripPassword remove the password from a given DSN
|
// stripPassword remove the password from a given DSN
|
||||||
func stripPassword(dsn, schema string) string {
|
func stripPassword(dsn, schema string) string {
|
||||||
prefix := schema + "://"
|
prefix := schema + "://"
|
||||||
|
|||||||
@@ -145,6 +145,51 @@ func TestFixInvalidLocales(t *testing.T) {
|
|||||||
assert.Contains(t, *cfg.LocalizationSettings.AvailableLocales, *cfg.LocalizationSettings.DefaultClientLocale, "DefaultClientLocale should have been added to AvailableLocales")
|
assert.Contains(t, *cfg.LocalizationSettings.AvailableLocales, *cfg.LocalizationSettings.DefaultClientLocale, "DefaultClientLocale should have been added to AvailableLocales")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsDatabaseDSN(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
Name string
|
||||||
|
DSN string
|
||||||
|
Expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "Mysql DSN",
|
||||||
|
DSN: "mysql://localhost",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Mysql DSN",
|
||||||
|
DSN: "mysql://localhost",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Empty DSN",
|
||||||
|
DSN: "",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Default file DSN",
|
||||||
|
DSN: "config.json",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Relative path DSN",
|
||||||
|
DSN: "configuration/config.json",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Absolute path DSN",
|
||||||
|
DSN: "/opt/mattermost/configuration/config.json",
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.Expected, IsDatabaseDSN(tc.DSN))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStripPassword(t *testing.T) {
|
func TestStripPassword(t *testing.T) {
|
||||||
for name, test := range map[string]struct {
|
for name, test := range map[string]struct {
|
||||||
DSN string
|
DSN string
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user