mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
* Move mmctl user deleteall to system nuke users Relocate the destructive PermanentDeleteAllUsers command out of the user namespace into a new system nuke subcommand to reduce the risk of accidental invocation. Regenerate mmctl docs and move associated unit and e2e tests accordingly. Co-authored-by: mattermost-code <[email protected]> * Address CodeRabbit feedback for system nuke users E2E tests Exercise SystemNukeUsersCmd with localOnlyPrecheck and --confirm in E2E tests, verify non-local mode is blocked via subprocess, and update confirmation text to mention users, posts, and related data. Co-authored-by: mattermost-code <[email protected]> * Fix viper local flag pollution in nuke users E2E tests Avoid setting viper local directly so unit tests that bind the local flag are not affected when mmctl unit and e2e suites run together. Co-authored-by: mattermost-code <[email protected]> * Simplify nuke users E2E test to reuse existing deleteall pattern Replace the subprocess-based localOnlyPrecheck verification with the straightforward client-based approach from the original TestDeleteAllUserCmd. The local-only enforcement is already covered by the local-only PermanentDeleteAllUsers API endpoint, so the regular and system-admin clients fail naturally without invoking the precheck. This also removes the viper local flag manipulation that previously polluted other E2E tests. --------- Co-authored-by: mattermost-code <[email protected]> Co-authored-by: Hanzo Dev <[email protected]>
258 lines
7.7 KiB
Go
258 lines
7.7 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package commands
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/client"
|
|
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer"
|
|
)
|
|
|
|
var SystemCmd = &cobra.Command{
|
|
Use: "system",
|
|
Short: "System management",
|
|
Long: `System management commands for interacting with the server state and configuration.`,
|
|
}
|
|
|
|
var SystemGetBusyCmd = &cobra.Command{
|
|
Use: "getbusy",
|
|
Short: "Get the current busy state",
|
|
Long: `Gets the server busy state (high load) and timestamp corresponding to when the server busy flag will be automatically cleared.`,
|
|
Example: ` system getbusy`,
|
|
Args: cobra.NoArgs,
|
|
RunE: withClient(getBusyCmdF),
|
|
}
|
|
|
|
var SystemSetBusyCmd = &cobra.Command{
|
|
Use: "setbusy -s [seconds]",
|
|
Short: "Set the busy state to true",
|
|
Long: `Set the busy state to true for the specified number of seconds, which disables non-critical services.`,
|
|
Example: ` system setbusy -s 3600`,
|
|
Args: cobra.NoArgs,
|
|
RunE: withClient(setBusyCmdF),
|
|
}
|
|
|
|
var SystemClearBusyCmd = &cobra.Command{
|
|
Use: "clearbusy",
|
|
Short: "Clears the busy state",
|
|
Long: `Clear the busy state, which re-enables non-critical services.`,
|
|
Example: ` system clearbusy`,
|
|
Args: cobra.NoArgs,
|
|
RunE: withClient(clearBusyCmdF),
|
|
}
|
|
|
|
var SystemVersionCmd = &cobra.Command{
|
|
Use: "version",
|
|
Short: "Prints the remote server version",
|
|
Long: "Prints the server version of the currently connected Mattermost instance",
|
|
Example: ` system version`,
|
|
Args: cobra.NoArgs,
|
|
RunE: withClient(systemVersionCmdF),
|
|
}
|
|
|
|
var SystemStatusCmd = &cobra.Command{
|
|
Use: "status",
|
|
Short: "Prints the status of the server",
|
|
Long: "Prints the server status calculated using several basic server healthchecks",
|
|
Example: ` system status`,
|
|
Args: cobra.NoArgs,
|
|
RunE: withClient(systemStatusCmdF),
|
|
}
|
|
|
|
var SystemSupportPacketCmd = &cobra.Command{
|
|
Use: "supportpacket",
|
|
Short: "Download a Support Packet",
|
|
Long: "Generate and download a Support Packet of the server to share it with Mattermost Support",
|
|
Example: ` system supportpacket`,
|
|
Args: cobra.NoArgs,
|
|
RunE: withClient(systemSupportPacketCmdF),
|
|
}
|
|
|
|
var SystemNukeCmd = &cobra.Command{
|
|
Use: "nuke",
|
|
Short: "Destructive operations that permanently delete data",
|
|
Long: "Destructive operations that permanently and irreversibly delete server data. Use with extreme caution.",
|
|
}
|
|
|
|
var SystemNukeUsersCmd = &cobra.Command{
|
|
Use: "users",
|
|
Short: "Delete all users and all posts. Local command only.",
|
|
Long: "Permanently delete all users and all related information including posts. This command can only be run in local mode.",
|
|
Example: " system nuke users",
|
|
Args: cobra.NoArgs,
|
|
PreRun: localOnlyPrecheck,
|
|
RunE: withClient(nukeUsersCmdF),
|
|
}
|
|
|
|
func init() {
|
|
SystemSetBusyCmd.Flags().UintP("seconds", "s", 3600, "Number of seconds until server is automatically marked as not busy.")
|
|
_ = SystemSetBusyCmd.MarkFlagRequired("seconds")
|
|
|
|
SystemSupportPacketCmd.Flags().StringP("output-file", "o", "", "Define the output file name")
|
|
|
|
SystemNukeUsersCmd.Flags().Bool("confirm", false, "Confirm you really want to permanently delete all users, posts, and related data, and that a DB backup has been performed")
|
|
|
|
SystemNukeCmd.AddCommand(
|
|
SystemNukeUsersCmd,
|
|
)
|
|
|
|
SystemCmd.AddCommand(
|
|
SystemGetBusyCmd,
|
|
SystemSetBusyCmd,
|
|
SystemClearBusyCmd,
|
|
SystemVersionCmd,
|
|
SystemStatusCmd,
|
|
SystemSupportPacketCmd,
|
|
SystemNukeCmd,
|
|
)
|
|
RootCmd.AddCommand(SystemCmd)
|
|
}
|
|
|
|
func getBusyCmdF(c client.Client, cmd *cobra.Command, _ []string) error {
|
|
printer.SetSingle(true)
|
|
|
|
sbs, _, err := c.GetServerBusy(context.TODO())
|
|
if err != nil {
|
|
return fmt.Errorf("unable to get busy state: %w", err)
|
|
}
|
|
printer.PrintT("busy:{{.Busy}} expires:{{.Expires_ts}}", sbs)
|
|
return nil
|
|
}
|
|
|
|
func setBusyCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
printer.SetSingle(true)
|
|
|
|
seconds, err := cmd.Flags().GetUint("seconds")
|
|
if err != nil || seconds == 0 {
|
|
return errors.New("seconds must be a number > 0")
|
|
}
|
|
|
|
_, err = c.SetServerBusy(context.TODO(), int(seconds))
|
|
if err != nil {
|
|
return fmt.Errorf("unable to set busy state: %w", err)
|
|
}
|
|
|
|
printer.PrintT("Busy state set", map[string]string{"status": "ok"})
|
|
return nil
|
|
}
|
|
|
|
func clearBusyCmdF(c client.Client, cmd *cobra.Command, _ []string) error {
|
|
printer.SetSingle(true)
|
|
|
|
_, err := c.ClearServerBusy(context.TODO())
|
|
if err != nil {
|
|
return fmt.Errorf("unable to clear busy state: %w", err)
|
|
}
|
|
printer.PrintT("Busy state cleared", map[string]string{"status": "ok"})
|
|
return nil
|
|
}
|
|
|
|
func systemVersionCmdF(c client.Client, cmd *cobra.Command, _ []string) error {
|
|
printer.SetSingle(true)
|
|
// server version information comes with all responses. We can't
|
|
// use the initial "withClient" connection information as local
|
|
// mode doesn't need to log in, so we use an endpoint that will
|
|
// always return a valid response
|
|
_, resp, err := c.GetPing(context.TODO())
|
|
if err != nil {
|
|
return fmt.Errorf("unable to fetch server version: %w", err)
|
|
}
|
|
|
|
printer.PrintT("Server version {{.version}}", map[string]string{"version": resp.ServerVersion})
|
|
return nil
|
|
}
|
|
|
|
func systemStatusCmdF(c client.Client, cmd *cobra.Command, _ []string) error {
|
|
printer.SetSingle(true)
|
|
|
|
status, _, err := c.GetPingWithOptions(context.TODO(), model.SystemPingOptions{
|
|
FullStatus: true,
|
|
RESTSemantics: true,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("unable to fetch server status: %w", err)
|
|
}
|
|
|
|
printer.PrintT(`Server status: {{.status}}
|
|
Android Latest Version: {{.AndroidLatestVersion}}
|
|
Android Minimum Version: {{.AndroidMinVersion}}
|
|
Desktop Latest Version: {{.DesktopLatestVersion}}
|
|
Desktop Minimum Version: {{.DesktopMinVersion}}
|
|
Ios Latest Version: {{.IosLatestVersion}}
|
|
Ios Minimum Version: {{.IosMinVersion}}
|
|
Database Status: {{.database_status}}
|
|
Filestore Status: {{.filestore_status}}`, status)
|
|
|
|
// Check health status and return non-zero exit code if any component is unhealthy
|
|
if status["status"] != model.StatusOk {
|
|
return fmt.Errorf("server status is unhealthy: %s", status["status"])
|
|
}
|
|
if dbStatus, ok := status["database_status"]; ok && dbStatus != model.StatusOk {
|
|
return fmt.Errorf("database status is unhealthy: %s", dbStatus)
|
|
}
|
|
if filestoreStatus, ok := status["filestore_status"]; ok && filestoreStatus != model.StatusOk {
|
|
return fmt.Errorf("filestore status is unhealthy: %s", filestoreStatus)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func systemSupportPacketCmdF(c client.Client, cmd *cobra.Command, _ []string) error {
|
|
printer.SetSingle(true)
|
|
|
|
filename, err := cmd.Flags().GetString("output-file")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
printer.Print("Downloading Support Packet")
|
|
|
|
data, rFilename, _, err := c.GenerateSupportPacket(context.TODO())
|
|
if err != nil {
|
|
return fmt.Errorf("unable to fetch Support Packet: %w", err)
|
|
}
|
|
|
|
if filename == "" {
|
|
filename = rFilename
|
|
}
|
|
|
|
file, err := os.Create(filename)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create zip file: %w", err)
|
|
}
|
|
|
|
_, err = io.Copy(file, data)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write to zip file: %w", err)
|
|
}
|
|
|
|
printer.PrintT("Downloaded Support Packet to {{ .filename }}", map[string]string{"filename": filename})
|
|
return nil
|
|
}
|
|
|
|
func nukeUsersCmdF(c client.Client, cmd *cobra.Command, args []string) error {
|
|
confirmFlag, _ := cmd.Flags().GetBool("confirm")
|
|
if !confirmFlag {
|
|
if err := getConfirmation("Are you sure you want to permanently delete all users, posts, and related data?", true); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if _, err := c.PermanentDeleteAllUsers(context.TODO()); err != nil {
|
|
return err
|
|
}
|
|
|
|
defer printer.Print("All users successfully deleted")
|
|
|
|
return nil
|
|
}
|