Skip to content

Commit

Permalink
[MM-54852] Add mmctl command to download Support Packet (mattermost#2…
Browse files Browse the repository at this point in the history
…5419)

* Add mmctl command to download Support Packet

* Simplify file name

* Revert "Simplify file name"

This reverts commit 17084a3.

* Fix docs
  • Loading branch information
hanzei authored Nov 21, 2023
1 parent 88520e6 commit 5e94af1
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 2 deletions.
1 change: 1 addition & 0 deletions server/cmd/mmctl/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,5 @@ type Client interface {
DownloadExport(ctx context.Context, name string, wr io.Writer, offset int64) (int64, *model.Response, error)
GeneratePresignedURL(ctx context.Context, name string) (*model.PresignURLResponse, *model.Response, error)
ResetSamlAuthDataToEmail(ctx context.Context, includeDeleted bool, dryRun bool, userIDs []string) (int64, *model.Response, error)
GenerateSupportPacket(ctx context.Context) ([]byte, *model.Response, error)
}
48 changes: 48 additions & 0 deletions server/cmd/mmctl/commands/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package commands
import (
"context"
"fmt"
"os"
"time"

"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -65,15 +67,28 @@ var SystemStatusCmd = &cobra.Command{
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),
}

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", "", "Output file name (default \"mattermost_support_packet_YYYY-MM-DD-HH-MM.zip\")")

SystemCmd.AddCommand(
SystemGetBusyCmd,
SystemSetBusyCmd,
SystemClearBusyCmd,
SystemVersionCmd,
SystemStatusCmd,
SystemSupportPacketCmd,
)
RootCmd.AddCommand(SystemCmd)
}
Expand Down Expand Up @@ -152,3 +167,36 @@ Filestore Status: {{.filestore_status}}`, status)

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
}

if filename == "" {
filename = fmt.Sprintf("mattermost_support_packet_%s.zip", time.Now().Format("2006-01-02-03-04"))
}

printer.Print("Downloading Support Packet")

data, _, err := c.GenerateSupportPacket(context.TODO())
if err != nil {
return fmt.Errorf("unable to fetch Support Packet: %w", err)
}

file, err := os.Create(filename)
if err != nil {
return fmt.Errorf("failed to create zip file: %w", err)
}

_, err = file.Write(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
}
60 changes: 60 additions & 0 deletions server/cmd/mmctl/commands/system_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package commands

import (
"os"
"strings"
"time"

"github.com/mattermost/mattermost/server/v8/cmd/mmctl/client"
Expand Down Expand Up @@ -103,3 +105,61 @@ func (s *MmctlE2ETestSuite) TestClearBusyCmd() {
s.Require().False(s.th.App.Srv().Platform().Busy.IsBusy())
})
}

func (s *MmctlE2ETestSuite) TestSupportPacketCmdF() {
s.SetupEnterpriseTestHelper().InitBasic()

printer.SetFormat(printer.FormatPlain)
s.T().Cleanup(func() { printer.SetFormat(printer.FormatJSON) })

s.Run("Download support packet with default filename", func() {
printer.Clean()

s.T().Cleanup(cleanupSupportPacket(s.T()))

err := systemSupportPacketCmdF(s.th.SystemAdminClient, SystemSupportPacketCmd, []string{})
s.Require().NoError(err)
s.Require().Len(printer.GetLines(), 2)
s.Require().Equal(printer.GetLines()[0], "Downloading Support Packet")
s.Require().Contains(printer.GetLines()[1], "Downloaded Support Packet to ")
s.Require().Len(printer.GetErrorLines(), 0)

var found bool

entries, err := os.ReadDir(".")
s.Require().NoError(err)
for _, e := range entries {
if strings.HasPrefix(e.Name(), "mattermost_support_packet_") && strings.HasSuffix(e.Name(), ".zip") {
b, err := os.ReadFile(e.Name())
s.NoError(err)

s.NotEmpty(b, b)

found = true
}
}
s.True(found)
})

s.Run("Download support packet with custom filename", func() {
printer.Clean()

err := SystemSupportPacketCmd.ParseFlags([]string{"-o", "foo.zip"})
s.Require().NoError(err)

s.T().Cleanup(func() {
s.Require().NoError(os.Remove("foo.zip"))
})

err = systemSupportPacketCmdF(s.th.SystemAdminClient, SystemSupportPacketCmd, []string{})
s.Require().NoError(err)
s.Require().Len(printer.GetErrorLines(), 0)
s.Require().Len(printer.GetLines(), 2)
s.Require().Equal(printer.GetLines()[0], "Downloading Support Packet")
s.Require().Equal(printer.GetLines()[1], "Downloaded Support Packet to foo.zip")

b, err := os.ReadFile("foo.zip")
s.Require().NoError(err)
s.NotNil(b, b)
})
}
107 changes: 105 additions & 2 deletions server/cmd/mmctl/commands/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ package commands
import (
"context"
"net/http"
"os"
"strconv"
"strings"
"testing"
"time"

"github.com/mattermost/mattermost/server/public/model"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer"

"github.com/spf13/cobra"
)

func (s *MmctlUnitTestSuite) TestGetBusyCmd() {
Expand Down Expand Up @@ -210,3 +214,102 @@ func (s *MmctlUnitTestSuite) TestServerStatusCmd() {
s.Require().Len(printer.GetLines(), 0)
})
}

func cleanupSupportPacket(t *testing.T) func() {
return func() {
entries, err := os.ReadDir(".")
require.NoError(t, err)
for _, e := range entries {
if strings.HasPrefix(e.Name(), "mattermost_support_packet_") && strings.HasSuffix(e.Name(), ".zip") {
err = os.Remove(e.Name())
assert.NoError(t, err)
}
}
}
}

func (s *MmctlUnitTestSuite) TestSupportPacketCmdF() {
printer.SetFormat(printer.FormatPlain)
s.T().Cleanup(func() { printer.SetFormat(printer.FormatJSON) })

s.Run("Download support packet with default filename", func() {
printer.Clean()

s.T().Cleanup(cleanupSupportPacket(s.T()))

data := []byte("some bytes")
s.client.
EXPECT().
GenerateSupportPacket(context.TODO()).
Return(data, &model.Response{}, nil).
Times(1)

err := systemSupportPacketCmdF(s.client, SystemSupportPacketCmd, []string{})
s.Require().NoError(err)
s.Require().Len(printer.GetErrorLines(), 0)
s.Require().Len(printer.GetLines(), 2)
s.Require().Equal(printer.GetLines()[0], "Downloading Support Packet")
s.Require().Contains(printer.GetLines()[1], "Downloaded Support Packet to ")

var found bool

entries, err := os.ReadDir(".")
s.Require().NoError(err)
for _, e := range entries {
if strings.HasPrefix(e.Name(), "mattermost_support_packet_") && strings.HasSuffix(e.Name(), ".zip") {
b, err := os.ReadFile(e.Name())
s.NoError(err)
s.Equal(b, data)

found = true
}
}

s.True(found)
})

s.Run("Download support packet with custom filename", func() {
printer.Clean()

data := []byte("some bytes")
s.client.
EXPECT().
GenerateSupportPacket(context.TODO()).
Return(data, &model.Response{}, nil).
Times(1)

err := SystemSupportPacketCmd.ParseFlags([]string{"-o", "foo.zip"})
s.Require().NoError(err)

s.T().Cleanup(func() {
s.Require().NoError(os.Remove("foo.zip"))
})

err = systemSupportPacketCmdF(s.client, SystemSupportPacketCmd, []string{})
s.Require().NoError(err)
s.Require().Len(printer.GetErrorLines(), 0)
s.Require().Len(printer.GetLines(), 2)
s.Require().Equal(printer.GetLines()[0], "Downloading Support Packet")
s.Require().Equal(printer.GetLines()[1], "Downloaded Support Packet to foo.zip")

b, err := os.ReadFile("foo.zip")
s.Require().NoError(err)
s.Equal(b, data)
})

s.Run("Request to the server fails", func() {
printer.Clean()

s.client.
EXPECT().
GenerateSupportPacket(context.TODO()).
Return(nil, &model.Response{}, errors.New("mock error")).
Times(1)

err := systemSupportPacketCmdF(s.client, SystemSupportPacketCmd, []string{})
s.Require().Error(err)
s.Require().Len(printer.GetErrorLines(), 0)
s.Require().Len(printer.GetLines(), 1)
s.Require().Equal(printer.GetLines()[0], "Downloading Support Packet")
})
}
1 change: 1 addition & 0 deletions server/cmd/mmctl/docs/mmctl_system.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ SEE ALSO
* `mmctl system getbusy <mmctl_system_getbusy.rst>`_ - Get the current busy state
* `mmctl system setbusy <mmctl_system_setbusy.rst>`_ - Set the busy state to true
* `mmctl system status <mmctl_system_status.rst>`_ - Prints the status of the server
* `mmctl system supportpacket <mmctl_system_supportpacket.rst>`_ - Download a Support Packet
* `mmctl system version <mmctl_system_version.rst>`_ - Prints the remote server version

52 changes: 52 additions & 0 deletions server/cmd/mmctl/docs/mmctl_system_supportpacket.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
.. _mmctl_system_supportpacket:

mmctl system supportpacket
--------------------------

Download a Support Packet

Synopsis
~~~~~~~~


Generate and download a Support Packet of the server to share it with Mattermost Support

::

mmctl system supportpacket [flags]

Examples
~~~~~~~~

::

system supportpacket

Options
~~~~~~~

::

-h, --help help for supportpacket
-o, --output-file string Output file name (default "mattermost_support_packet_YYYY-MM-DD-HH-MM.zip")

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 system <mmctl_system.rst>`_ - System management

16 changes: 16 additions & 0 deletions server/cmd/mmctl/mocks/client_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 5e94af1

Please sign in to comment.