[MM-59503] export: enable exporting configuration with mmctl (#28412)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
ca72cdb445
Коммит
424ce2b8db
@@ -91,6 +91,7 @@ type Client interface {
|
||||
MoveCommand(ctx context.Context, teamID string, commandID string) (*model.Response, error)
|
||||
DeleteCommand(ctx context.Context, commandID string) (*model.Response, error)
|
||||
GetConfig(ctx context.Context) (*model.Config, *model.Response, error)
|
||||
GetConfigWithOptions(ctx context.Context, options model.GetConfigOptions) (map[string]any, *model.Response, error)
|
||||
GetOldClientConfig(ctx context.Context, etag string) (map[string]string, *model.Response, error)
|
||||
UpdateConfig(context.Context, *model.Config) (*model.Config, *model.Response, error)
|
||||
PatchConfig(context.Context, *model.Config) (*model.Config, *model.Response, error)
|
||||
|
||||
@@ -120,6 +120,15 @@ var ConfigSubpathCmd = &cobra.Command{
|
||||
RunE: configSubpathCmdF,
|
||||
}
|
||||
|
||||
var ConfigExportCmd = &cobra.Command{
|
||||
Use: "export",
|
||||
Short: "Export the server configuration",
|
||||
Long: "Export the server configuration in case you want to import somewhere else.",
|
||||
Example: "config export --remove-masked --remove-defaults",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: withClient(configExportCmdF),
|
||||
}
|
||||
|
||||
func init() {
|
||||
ConfigResetCmd.Flags().Bool("confirm", false, "confirm you really want to reset all configuration settings to its default value")
|
||||
|
||||
@@ -128,6 +137,9 @@ func init() {
|
||||
ConfigSubpathCmd.Flags().StringP("path", "p", "", "path to update the assets with")
|
||||
_ = ConfigSubpathCmd.MarkFlagRequired("path")
|
||||
|
||||
ConfigExportCmd.Flags().Bool("remove-masked", true, "remove masked values from the exported configuration")
|
||||
ConfigExportCmd.Flags().Bool("remove-defaults", false, "remove default values from the exported configuration")
|
||||
|
||||
ConfigCmd.AddCommand(
|
||||
ConfigGetCmd,
|
||||
ConfigSetCmd,
|
||||
@@ -138,6 +150,7 @@ func init() {
|
||||
ConfigReloadCmd,
|
||||
ConfigMigrateCmd,
|
||||
ConfigSubpathCmd,
|
||||
ConfigExportCmd,
|
||||
)
|
||||
RootCmd.AddCommand(ConfigCmd)
|
||||
}
|
||||
@@ -581,3 +594,22 @@ func cloudRestrictedR(t reflect.Type, path []string) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func configExportCmdF(c client.Client, cmd *cobra.Command, _ []string) error {
|
||||
removeDefaults, _ := cmd.Flags().GetBool("remove-defaults")
|
||||
removeMasked, _ := cmd.Flags().GetBool("remove-masked")
|
||||
config, _, err := c.GetConfigWithOptions(context.TODO(), model.GetConfigOptions{
|
||||
RemoveDefaults: removeDefaults,
|
||||
RemoveMasked: removeMasked,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printer.SetSingle(true)
|
||||
printer.SetFormat(printer.FormatJSON)
|
||||
|
||||
printer.Print(config)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -233,3 +233,93 @@ func (s *MmctlE2ETestSuite) TestConfigShowCmdF() {
|
||||
s.Require().Len(printer.GetErrorLines(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *MmctlE2ETestSuite) TestConfigExportCmdF() {
|
||||
s.SetupTestHelper().InitBasic()
|
||||
|
||||
s.RunForSystemAdminAndLocal("Get config normally", func(c client.Client) {
|
||||
printer.Clean()
|
||||
|
||||
err := configExportCmdF(c, &cobra.Command{}, nil)
|
||||
s.Require().Nil(err)
|
||||
s.Require().Len(printer.GetLines(), 1)
|
||||
s.Require().Len(printer.GetErrorLines(), 0)
|
||||
|
||||
m, ok := printer.GetLines()[0].(map[string]any)
|
||||
s.Require().True(ok)
|
||||
if c == s.th.LocalClient {
|
||||
// filter config is used to convert the config to a map[string]any
|
||||
// local client has unrestricted access to the config
|
||||
expectedConfig, err2 := model.FilterConfig(s.th.App.Config(), model.ConfigFilterOptions{GetConfigOptions: model.GetConfigOptions{}})
|
||||
s.Require().NoError(err2)
|
||||
s.Require().Equal(expectedConfig, m)
|
||||
} else {
|
||||
// filter config is used to convert the config to a map[string]any
|
||||
// system admin client has restricted access to the config
|
||||
expectedConfig, err2 := model.FilterConfig(s.th.App.GetSanitizedConfig(), model.ConfigFilterOptions{GetConfigOptions: model.GetConfigOptions{}})
|
||||
s.Require().NoError(err2)
|
||||
s.Require().Equal(expectedConfig, m)
|
||||
}
|
||||
})
|
||||
|
||||
s.Run("Should remove masked values for system admin client", func() {
|
||||
printer.Clean()
|
||||
|
||||
exportCmd := &cobra.Command{}
|
||||
exportCmd.Flags().Bool("remove-masked", true, "")
|
||||
err := configExportCmdF(s.th.SystemAdminClient, exportCmd, nil)
|
||||
s.Require().Nil(err)
|
||||
s.Require().Len(printer.GetLines(), 1)
|
||||
m, ok := printer.GetLines()[0].(map[string]any)
|
||||
s.Require().True(ok)
|
||||
ss, ok := m["SqlSettings"].(map[string]any)
|
||||
s.Require().True(ok)
|
||||
_, ok = ss["DataSource"]
|
||||
s.Require().False(ok)
|
||||
s.Require().Len(printer.GetErrorLines(), 0)
|
||||
})
|
||||
|
||||
s.Run("Should retrieve configuration as-is with local client", func() {
|
||||
printer.Clean()
|
||||
|
||||
exportCmd := &cobra.Command{}
|
||||
err := configExportCmdF(s.th.LocalClient, exportCmd, nil)
|
||||
s.Require().Nil(err)
|
||||
s.Require().Len(printer.GetLines(), 1)
|
||||
m, ok := printer.GetLines()[0].(map[string]any)
|
||||
s.Require().True(ok)
|
||||
ss, ok := m["SqlSettings"].(map[string]any)
|
||||
s.Require().True(ok)
|
||||
ds, ok := ss["DataSource"]
|
||||
s.Require().True(ok)
|
||||
cfg := s.th.App.Config()
|
||||
s.Require().Equal(*cfg.SqlSettings.DataSource, ds)
|
||||
s.Require().Len(printer.GetErrorLines(), 0)
|
||||
})
|
||||
|
||||
s.RunForSystemAdminAndLocal("Should remove default values", func(c client.Client) {
|
||||
printer.Clean()
|
||||
|
||||
exportCmd := &cobra.Command{}
|
||||
exportCmd.Flags().Bool("remove-defaults", true, "")
|
||||
err := configExportCmdF(c, exportCmd, nil)
|
||||
s.Require().Nil(err)
|
||||
s.Require().Len(printer.GetLines(), 1)
|
||||
m, ok := printer.GetLines()[0].(map[string]any)
|
||||
s.Require().True(ok)
|
||||
ss, ok := m["TeamSettings"].(map[string]any)
|
||||
s.Require().True(ok)
|
||||
_, ok = ss["MaxUsersPerTeam"] // it's not being changed by the test suite
|
||||
s.Require().False(ok)
|
||||
s.Require().Len(printer.GetErrorLines(), 0)
|
||||
})
|
||||
|
||||
s.Run("Get config value for a given key without permissions", func() {
|
||||
printer.Clean()
|
||||
|
||||
err := configExportCmdF(s.th.Client, &cobra.Command{}, nil)
|
||||
s.Require().NotNil(err)
|
||||
s.Require().Len(printer.GetLines(), 0)
|
||||
s.Require().Len(printer.GetErrorLines(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1002,3 +1002,26 @@ func TestSetConfigValue(t *testing.T) {
|
||||
assert.Equal(t, tc.expectedConfig, tc.config, name)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MmctlUnitTestSuite) TestConfigExportCmd() {
|
||||
s.Run("Should get the config as-is", func() {
|
||||
// there is not much to test as the config is returned as-is
|
||||
// adding a test to make sure future changes are not breaking this
|
||||
printer.Clean()
|
||||
|
||||
s.client.
|
||||
EXPECT().
|
||||
GetConfigWithOptions(context.TODO(), model.GetConfigOptions{}).
|
||||
Return(map[string]any{
|
||||
"SqlSettings": map[string]any{
|
||||
"DriverName": "postgres",
|
||||
},
|
||||
}, &model.Response{}, nil).
|
||||
Times(1)
|
||||
|
||||
err := configExportCmdF(s.client, &cobra.Command{}, nil)
|
||||
s.Require().Nil(err)
|
||||
s.Require().Len(printer.GetLines(), 1)
|
||||
s.Require().Len(printer.GetErrorLines(), 0)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ SEE ALSO
|
||||
|
||||
* `mmctl <mmctl.rst>`_ - Remote client for the Open Source, self-hosted Slack-alternative
|
||||
* `mmctl config edit <mmctl_config_edit.rst>`_ - Edit the config
|
||||
* `mmctl config export <mmctl_config_export.rst>`_ - Export the server configuration
|
||||
* `mmctl config get <mmctl_config_get.rst>`_ - Get config setting
|
||||
* `mmctl config migrate <mmctl_config_migrate.rst>`_ - Migrate existing config between backends
|
||||
* `mmctl config patch <mmctl_config_patch.rst>`_ - Patch the config
|
||||
|
||||
53
server/cmd/mmctl/docs/mmctl_config_export.rst
Обычный файл
53
server/cmd/mmctl/docs/mmctl_config_export.rst
Обычный файл
@@ -0,0 +1,53 @@
|
||||
.. _mmctl_config_export:
|
||||
|
||||
mmctl config export
|
||||
-------------------
|
||||
|
||||
Export the server configuration
|
||||
|
||||
Synopsis
|
||||
~~~~~~~~
|
||||
|
||||
|
||||
Export the server configuration in case you want to import somewhere else.
|
||||
|
||||
::
|
||||
|
||||
mmctl config export [flags]
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
config export --remove-masked --remove-defaults
|
||||
|
||||
Options
|
||||
~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
-h, --help help for export
|
||||
--remove-defaults remove default values from the exported configuration
|
||||
--remove-masked remove masked values from the exported configuration (default true)
|
||||
|
||||
Options inherited from parent commands
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
--config string path to the configuration file (default "$XDG_CONFIG_HOME/mmctl/config")
|
||||
--disable-pager disables paged output
|
||||
--insecure-sha1-intermediate allows to use insecure TLS protocols, such as SHA-1
|
||||
--insecure-tls-version allows to use TLS versions 1.0 and 1.1
|
||||
--json the output format will be in json format
|
||||
--local allows communicating with the server through a unix socket
|
||||
--quiet prevent mmctl to generate output for the commands
|
||||
--strict will only run commands if the mmctl version matches the server one
|
||||
--suppress-warnings disables printing warning messages
|
||||
|
||||
SEE ALSO
|
||||
~~~~~~~~
|
||||
|
||||
* `mmctl config <mmctl_config.rst>`_ - Configuration
|
||||
|
||||
@@ -763,6 +763,22 @@ func (mr *MockClientMockRecorder) GetConfig(arg0 interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfig", reflect.TypeOf((*MockClient)(nil).GetConfig), arg0)
|
||||
}
|
||||
|
||||
// GetConfigWithOptions mocks base method.
|
||||
func (m *MockClient) GetConfigWithOptions(arg0 context.Context, arg1 model.GetConfigOptions) (map[string]interface{}, *model.Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetConfigWithOptions", arg0, arg1)
|
||||
ret0, _ := ret[0].(map[string]interface{})
|
||||
ret1, _ := ret[1].(*model.Response)
|
||||
ret2, _ := ret[2].(error)
|
||||
return ret0, ret1, ret2
|
||||
}
|
||||
|
||||
// GetConfigWithOptions indicates an expected call of GetConfigWithOptions.
|
||||
func (mr *MockClientMockRecorder) GetConfigWithOptions(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfigWithOptions", reflect.TypeOf((*MockClient)(nil).GetConfigWithOptions), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetDeletedChannelsForTeam mocks base method.
|
||||
func (m *MockClient) GetDeletedChannelsForTeam(arg0 context.Context, arg1 string, arg2, arg3 int, arg4 string) ([]*model.Channel, *model.Response, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Ссылка в новой задаче
Block a user