--- a/server/channels/app/metrics_test.go +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package app - -import ( - "fmt" - "testing" - - "github.com/mattermost/mattermost/server/public/model" - "github.com/mattermost/mattermost/server/v8/channels/app/platform" - "github.com/mattermost/mattermost/server/v8/enterprise/metrics" - "github.com/prometheus/client_golang/prometheus" - prometheusModels "github.com/prometheus/client_model/go" - "github.com/stretchr/testify/require" -) - -func configureMetrics(th *TestHelper) { - th.App.Srv().SetLicense(nil) // clear license - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.MetricsSettings.Enable = true - *cfg.MetricsSettings.ListenAddress = ":0" - }) - th.App.Srv().SetLicense(model.NewTestLicense("metrics")) -} - -func TestMobileMetrics(t *testing.T) { - mainHelper.Parallel(t) - th := SetupEnterprise(t, StartMetrics) - - configureMetrics(th) - mi := th.App.Metrics() - - miImpl, ok := mi.(*metrics.MetricsInterfaceImpl) - require.True(t, ok, fmt.Sprintf("App.Metrics is not *MetricsInterfaceImpl, but %T", mi)) - m := &prometheusModels.Metric{} - - for _, platform := range []string{"ios", "android"} { - ttcc := []struct { - name string - histogramVec *prometheus.HistogramVec - elapsed float64 - metricName model.MetricType - }{ - { - name: "load duration", - histogramVec: miImpl.MobileClientLoadDuration, - elapsed: 5001, - metricName: model.MobileClientLoadDuration, - }, - { - name: "channel switch duration", - histogramVec: miImpl.MobileClientChannelSwitchDuration, - elapsed: 501, - metricName: model.MobileClientChannelSwitchDuration, - }, - { - name: "team switch duration", - histogramVec: miImpl.MobileClientTeamSwitchDuration, - elapsed: 301, - metricName: model.MobileClientTeamSwitchDuration, - }, - } - - histograms := []*model.MetricSample{} - var start float64 = 125 - end := start + float64(len(ttcc)) - // Precheck - for _, tc := range ttcc { - actualMetric, err := tc.histogramVec.GetMetricWith(prometheus.Labels{"platform": platform}) - require.NoError(t, err) - require.NoError(t, actualMetric.(prometheus.Histogram).Write(m)) - require.Equal(t, uint64(0), m.Histogram.GetSampleCount()) - require.Equal(t, 0.0, m.Histogram.GetSampleSum()) - histograms = append(histograms, &model.MetricSample{ - Metric: tc.metricName, - Value: tc.elapsed, - }) - } - - appErr := th.App.RegisterPerformanceReport(th.Context, &model.PerformanceReport{ - Version: "0.1.0", - ClientID: "", - Labels: map[string]string{ - "platform": platform, - }, - Start: start, - End: end, - Counters: []*model.MetricSample{}, - Histograms: histograms, - }) - require.Nil(t, appErr) - - // After check - for _, tc := range ttcc { - actualMetric, err := tc.histogramVec.GetMetricWith(prometheus.Labels{"platform": platform}) - require.NoError(t, err) - require.NoError(t, actualMetric.(prometheus.Histogram).Write(m)) - require.Equal(t, uint64(1), m.Histogram.GetSampleCount()) - require.InDelta(t, tc.elapsed/1000, m.Histogram.GetSampleSum(), 0.001, "not equal value in %s", tc.name) - } - } -} - -func TestCountNotificationMetrics(t *testing.T) { - mainHelper.Parallel(t) - th := SetupEnterprise(t, StartMetrics) - - configureMetrics(th) - mi := th.App.Metrics() - - miImpl, ok := mi.(*metrics.MetricsInterfaceImpl) - require.True(t, ok, fmt.Sprintf("App.Metrics is not *MetricsInterfaceImpl, but %T", mi)) - - counterValue := func() float64 { - counter, err := miImpl.NotificationTotalCounters.GetMetricWith(prometheus.Labels{ - "type": string(model.NotificationTypePush), - "platform": "ios", - }) - require.NoError(t, err) - m := &prometheusModels.Metric{} - require.NoError(t, counter.Write(m)) - return m.Counter.GetValue() - } - - t.Run("counts when notification metrics are enabled", func(t *testing.T) { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.MetricsSettings.EnableNotificationMetrics = true - }) - - before := counterValue() - th.App.CountNotification(model.NotificationTypePush, "ios") - require.Equal(t, before+1, counterValue()) - }) - - t.Run("does not count when notification metrics are disabled", func(t *testing.T) { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.MetricsSettings.EnableNotificationMetrics = false - }) - - before := counterValue() - th.App.CountNotification(model.NotificationTypePush, "ios") - require.Equal(t, before, counterValue()) - }) -} - -func TestWebsocketNotificationCounter(t *testing.T) { - mainHelper.Parallel(t) - th := SetupEnterprise(t, StartMetrics) - - configureMetrics(th) - mi := th.App.Metrics() - - miImpl, ok := mi.(*metrics.MetricsInterfaceImpl) - require.True(t, ok, fmt.Sprintf("App.Metrics is not *MetricsInterfaceImpl, but %T", mi)) - - counterValue := func() float64 { - counter, err := miImpl.NotificationTotalCounters.GetMetricWith(prometheus.Labels{ - "type": string(model.NotificationTypeWebsocket), - "platform": model.NotificationNoPlatform, - }) - require.NoError(t, err) - m := &prometheusModels.Metric{} - require.NoError(t, counter.Write(m)) - return m.Counter.GetValue() - } - - hook := &postedAckBroadcastHook{} - userID := model.NewId() - webConn := &platform.WebConn{ - UserId: userID, - Platform: th.Server.Platform(), - PostedAck: true, - } - webConn.Active.Store(true) - webConn.SetSession(&model.Session{}) - - // Process an acked broadcast that reaches incrementWebsocketCounter. - ackPostedBroadcast := func() { - msg := platform.MakeHookedWebSocketEvent(model.NewWebSocketEvent(model.WebsocketEventPosted, "", "", "", nil, "")) - err := hook.Process(msg, webConn, map[string]any{ - "posted_user_id": model.NewId(), - "channel_type": model.ChannelTypeOpen, - "users": []string{userID}, - }) - require.NoError(t, err) - require.True(t, msg.Event().GetData()["should_ack"].(bool)) - } - - t.Run("counts when notification metrics are enabled", func(t *testing.T) { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.MetricsSettings.EnableNotificationMetrics = true - }) - - before := counterValue() - ackPostedBroadcast() - require.Equal(t, before+1, counterValue()) - }) - - t.Run("does not count when notification metrics are disabled", func(t *testing.T) { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.MetricsSettings.EnableNotificationMetrics = false - }) - - before := counterValue() - ackPostedBroadcast() - require.Equal(t, before, counterValue()) - }) -} --- a/server/cmd/mmctl/commands/compliance_export.go +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package commands - -import ( - "context" - "errors" - "fmt" - "os" - "path" - "strconv" - "time" - - "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" - "github.com/mattermost/mattermost/server/v8/enterprise/message_export/shared" - "github.com/spf13/cobra" -) - -var ComplianceExportCmd = &cobra.Command{ - Use: "compliance-export", - Short: "Management of compliance exports", -} - -var ComplianceExportListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"ls"}, - Short: "List compliance export jobs, sorted by creation date descending (newest first)", - Args: cobra.NoArgs, - RunE: withClient(complianceExportListCmdF), -} - -var ComplianceExportShowCmd = &cobra.Command{ - Use: "show [complianceExportJobID]", - Example: "compliance-export show o98rj3ur83dp5dppfyk5yk6osy", - Short: "Show compliance export job", - Args: cobra.ExactArgs(1), - RunE: withClient(complianceExportShowCmdF), -} - -var ComplianceExportCancelCmd = &cobra.Command{ - Use: "cancel [complianceExportJobID]", - Example: "compliance-export cancel o98rj3ur83dp5dppfyk5yk6osy", - Short: "Cancel compliance export job", - Args: cobra.ExactArgs(1), - RunE: withClient(complianceExportCancelCmdF), -} - -var ComplianceExportDownloadCmd = &cobra.Command{ - Use: "download [complianceExportJobID] [output filepath (optional)]", - Example: "compliance-export download o98rj3ur83dp5dppfyk5yk6osy", - Short: "Download compliance export file", - Args: cobra.MinimumNArgs(1), - RunE: withClient(complianceExportDownloadCmdF), -} - -var ComplianceExportCreateCmd = &cobra.Command{ - Use: "create [complianceExportType] --date \"2025-03-27 -0400\"", - Example: "compliance-export create csv --date \"2025-03-27 -0400\"", - Long: "Create a compliance export job, of type 'csv' or 'actiance' or 'globalrelay'. If --date is set, the job will run for one day, from 12am to 12am (minus one millisecond) inclusively, in the format with timezone offset: `\"YYYY-MM-DD -0000\"`. E.g., \"2024-10-21 -0400\" for Oct 21, 2024 EDT timezone. \"2023-11-01 +0000\" for Nov 01, 2024 UTC. If set, the 'start' and 'end' flags will be ignored.\n\n" + - "Important: Running a compliance export job from mmctl will NOT affect the next scheduled job's batch_start_time. This means that if you run a compliance export job from mmctl, the next scheduled job will run from the batch_end_time of the previous scheduled job, as usual.", - Short: "Create a compliance export job, of type 'csv' or 'actiance' or 'globalrelay'", - Args: cobra.MinimumNArgs(1), - RunE: withClient(complianceExportCreateCmdF), -} - -func init() { - ComplianceExportListCmd.Flags().Int("page", 0, "Page number to fetch for the list of compliance export jobs") - ComplianceExportListCmd.Flags().Int("per-page", DefaultPageSize, "Number of compliance export jobs to be fetched") - ComplianceExportListCmd.Flags().Bool("all", false, "Fetch all compliance export jobs. --page flag will be ignored if provided") - - ComplianceExportDownloadCmd.Flags().Int("num-retries", 5, "Number of retries if the download fails") - - ComplianceExportCreateCmd.Flags().String( - "date", - "", - "Run the export for one day, from 12am to 12am (minus one millisecond) inclusively, in the format with timezone offset: `\"YYYY-MM-DD -0000\"`. E.g., `\"2024-10-21 -0400\"` for Oct 21, 2024 EDT timezone. `\"2023-11-01 +0000\"` for Nov 01, 2024 UTC. If set, the 'start' and 'end' flags will be ignored.", - ) - ComplianceExportCreateCmd.Flags().Int( - "start", - 0, - "The start timestamp in unix milliseconds. Posts with updateAt >= start will be exported. If set, 'end' must be set as well. eg, `1743048000000` for 2025-03-27 EDT.", - ) - ComplianceExportCreateCmd.Flags().Int( - "end", - 0, - "The end timestamp in unix milliseconds. Posts with updateAt <= end will be exported. If set, 'start' must be set as well. eg, `1743134400000` for 2025-03-28 EDT.", - ) - - ComplianceExportCmd.AddCommand( - ComplianceExportListCmd, - ComplianceExportShowCmd, - ComplianceExportCancelCmd, - ComplianceExportDownloadCmd, - ComplianceExportCreateCmd, - ) - RootCmd.AddCommand(ComplianceExportCmd) -} - -func complianceExportListCmdF(c client.Client, command *cobra.Command, args []string) error { - return jobListCmdF(c, command, "message_export", "") -} - -func complianceExportShowCmdF(c client.Client, command *cobra.Command, args []string) error { - job, _, err := c.GetJob(context.TODO(), args[0]) - if err != nil { - return fmt.Errorf("failed to get compliance export job: %w", err) - } - - printJob(job) - - return nil -} - -func complianceExportCancelCmdF(c client.Client, command *cobra.Command, args []string) error { - if _, err := c.CancelJob(context.TODO(), args[0]); err != nil { - return fmt.Errorf("failed to cancel compliance export job: %w", err) - } - - return nil -} - -func complianceExportDownloadCmdF(c client.Client, command *cobra.Command, args []string) error { - jobID := args[0] - var path string - if len(args) > 1 { - path = args[1] - } else { - path = jobID + ".zip" - } - - retries, _ := command.Flags().GetInt("num-retries") - - downloadFn := func(outFile *os.File) (string, error) { - return c.DownloadComplianceExport(context.TODO(), jobID, outFile) - } - - suggestedFilename, err := downloadFile(path, downloadFn, retries, "compliance export") - if err != nil { - return err - } - - // If we didn't provide a path and got a suggested filename, rename the file - if len(args) == 1 && suggestedFilename != "" && suggestedFilename != path { - // If the suggested name already exists, don't overwrite - if _, err := os.Stat(suggestedFilename); err == nil { - printer.PrintWarning(fmt.Sprintf("File with the server's suggested name %q already exists, keeping %q", suggestedFilename, path)) - } else { - if err := os.Rename(path, suggestedFilename); err != nil { - printer.PrintWarning(fmt.Sprintf("Could not rename file to the server's suggested name %q: %v", suggestedFilename, err)) - } else { - path = suggestedFilename - } - } - } - - printer.Print(fmt.Sprintf("Compliance export file downloaded to %q", path)) - return nil -} - -func complianceExportCreateCmdF(c client.Client, command *cobra.Command, args []string) error { - exportType := args[0] - if exportType != model.ComplianceExportTypeActiance && - exportType != model.ComplianceExportTypeCsv && - exportType != model.ComplianceExportTypeGlobalrelay { - return fmt.Errorf("invalid export type: %s, must be one of: csv, actiance, globalrelay", exportType) - } - - dateStr, err := command.Flags().GetString("date") - if err != nil { - return err - } - start, err := command.Flags().GetInt("start") - if err != nil { - return err - } - end, err := command.Flags().GetInt("end") - if err != nil { - return err - } - startTimestamp, endTimestamp, err := getStartAndEnd(dateStr, start, end) - if err != nil { - return err - } - startTime := strconv.FormatInt(startTimestamp, 10) - endTime := strconv.FormatInt(endTimestamp, 10) - exportDir := path.Join(model.ComplianceExportPath, fmt.Sprintf("%s-%s-%s", time.Now().Format(model.ComplianceExportDirectoryFormat), startTime, endTime)) - - // If start and end are 0, we need to not set those keys in the job data. - // This will make the job like a manual job (it will pick up where the previous job left off). - data := model.StringMap{ - shared.JobDataInitiatedBy: "mmctl", - shared.JobDataExportType: exportType, - shared.JobDataBatchStartId: "", - shared.JobDataJobStartId: "", - } - if startTimestamp != 0 && endTimestamp != 0 { - data[shared.JobDataBatchStartTime] = startTime - data[shared.JobDataJobStartTime] = startTime - data[shared.JobDataJobEndTime] = endTime - data[shared.JobDataExportDir] = exportDir - } - - job := &model.Job{ - Type: model.JobTypeMessageExport, - Data: data, - } - - if job, _, err = c.CreateJob(context.TODO(), job); err != nil { - return fmt.Errorf("failed to create compliance export job: %w", err) - } - - printer.Print(fmt.Sprintf("Compliance export job created with ID: %s", job.Id)) - - return nil -} - -// getStartAndEnd returns the start and end timestamps in unix milliseconds -func getStartAndEnd(dateStr string, start int, end int) (int64, int64, error) { - if dateStr == "" && start == 0 && end == 0 { - // return 0 so that the job will be like a manual job - return 0, 0, nil - } - - if dateStr != "" && (start > 0 || end > 0) { - return 0, 0, errors.New("if date is used, start and end must not be set") - } - - if dateStr != "" { - t, err := time.Parse("2006-01-02 -0700", dateStr) - if err != nil { - return 0, 0, fmt.Errorf("could not parse date string: %s, use the format with time zone offset: YYYY-MM-DD -0700, eg for EDT: `2024-12-24 -0400`, error details: %w", dateStr, err) - } - endTimestamp := t.AddDate(0, 0, 1).UnixMilli() - 1 - return t.UnixMilli(), endTimestamp, nil - } - if start <= 0 || end <= 0 || start >= end { - return 0, 0, fmt.Errorf("if date is not used, start: %d and end: %d must both be > 0, and start must be < end", start, end) - } - return int64(start), int64(end), nil -} --- a/server/cmd/mmctl/commands/compliance_export_e2e_test.go +++ /dev/null @@ -1,744 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -package commands - -import ( - "archive/zip" - "context" - "fmt" - "io" - "os" - "path/filepath" - "strconv" - - "github.com/mattermost/mattermost/server/public/model" - "github.com/mattermost/mattermost/server/v8" - st "github.com/mattermost/mattermost/server/v8/channels/store/storetest" - "github.com/mattermost/mattermost/server/v8/cmd/mmctl/client" - "github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer" - "github.com/mattermost/mattermost/server/v8/enterprise/message_export/shared" - "github.com/spf13/cobra" -) - -func (s *MmctlE2ETestSuite) TestComplianceExportListCmdE2E() { - s.SetupMessageExportTestHelper() - - s.Run("no permissions", func() { - printer.Clean() - - cmd := makeCmd() - err := complianceExportListCmdF(s.th.Client, cmd, nil) - s.Require().EqualError(err, "failed to get jobs: You do not have the appropriate permissions.") - s.Require().Empty(printer.GetLines()) - s.Require().Empty(printer.GetErrorLines()) - }) - - jobType := model.JobTypeMessageExport - - s.RunForSystemAdminAndLocal("List with no compliance export jobs", func(c client.Client) { - // Ensure no jobs exist - jobs, _, err := s.th.SystemAdminClient.GetJobsByType(context.Background(), jobType, 0, 1000) - s.Require().NoError(err) - - for _, job := range jobs { - var result string - result, err = s.th.App.Srv().Store().Job().Delete(job.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - } - - cmd := makeCmd() - // Test default pagination - printer.Clean() - err = complianceExportListCmdF(c, cmd, nil) - s.Require().NoError(err) - s.Require().Len(printer.GetLines(), 1) - s.Require().Equal("No jobs found", printer.GetLines()[0]) - - // Test with 1 per page - printer.Clean() - cmd = makeCmd() - _ = cmd.Flags().Set("page", "0") - _ = cmd.Flags().Set("per-page", "1") - err = complianceExportListCmdF(c, cmd, nil) - s.Require().NoError(err) - s.Require().Len(printer.GetLines(), 1) - s.Require().Equal("No jobs found", printer.GetLines()[0]) - - // Test with all items - printer.Clean() - cmd = makeCmd() - _ = cmd.Flags().Set("all", "true") - err = complianceExportListCmdF(c, cmd, nil) - s.Require().NoError(err) - s.Require().Len(printer.GetLines(), 1) - s.Require().Equal("No jobs found", printer.GetLines()[0]) - }) - - s.RunForSystemAdminAndLocal("List compliance export jobs", func(c client.Client) { - now := model.GetMillis() - // Create 2 jobs - job, _, err := s.th.SystemAdminClient.CreateJob(context.Background(), &model.Job{ - Id: st.NewTestID(), - CreateAt: now - 1000, - Status: model.JobStatusSuccess, - Type: model.JobTypeMessageExport, - StartAt: now - 1000, - LastActivityAt: now - 1000, - }) - s.Require().NoError(err) - - job2, _, err := s.th.SystemAdminClient.CreateJob(context.Background(), &model.Job{ - Id: st.NewTestID(), - CreateAt: now - 100, - Status: model.JobStatusSuccess, - Type: model.JobTypeMessageExport, - StartAt: now - 100, - LastActivityAt: now - 100, - }) - s.Require().NoError(err) - defer func() { - // Ensure jobs are deleted from the database - var result string - result, err = s.th.App.Srv().Store().Job().Delete(job.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - result, err = s.th.App.Srv().Store().Job().Delete(job2.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - }() - - // Test default pagination - printer.Clean() - cmd := makeCmd() - err = complianceExportListCmdF(c, cmd, nil) - s.Require().NoError(err) - s.Require().Len(printer.GetLines(), 2) - s.Require().Equal(job2.Id, printer.GetLines()[0].(*model.Job).Id) - s.Require().Equal(job.Id, printer.GetLines()[1].(*model.Job).Id) - - // Test with 1 per page - printer.Clean() - cmd = makeCmd() - _ = cmd.Flags().Set("page", "0") - _ = cmd.Flags().Set("per-page", "1") - err = complianceExportListCmdF(c, cmd, nil) - s.Require().NoError(err) - s.Require().Len(printer.GetLines(), 1) - s.Require().Equal(job2.Id, printer.GetLines()[0].(*model.Job).Id) - - // Test with all items - printer.Clean() - cmd = makeCmd() - _ = cmd.Flags().Set("all", "true") - err = complianceExportListCmdF(c, cmd, nil) - s.Require().NoError(err) - s.Require().Len(printer.GetLines(), 2) - s.Require().Equal(job2.Id, printer.GetLines()[0].(*model.Job).Id) - s.Require().Equal(job.Id, printer.GetLines()[1].(*model.Job).Id) - }) -} - -func (s *MmctlE2ETestSuite) TestComplianceExportShowCmdE2E() { - s.SetupMessageExportTestHelper() - - now := model.GetMillis() - - // Create a job - job, _, err := s.th.SystemAdminClient.CreateJob(context.Background(), &model.Job{ - Id: st.NewTestID(), - CreateAt: now - 1000, - Status: model.JobStatusSuccess, - Type: model.JobTypeMessageExport, - StartAt: now - 1000, - LastActivityAt: now - 1000, - }) - s.Require().NoError(err) - defer func() { - // Ensure job is deleted from the database - var result string - result, err = s.th.App.Srv().Store().Job().Delete(job.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - }() - - s.Run("no permissions", func() { - printer.Clean() - - cmd := makeCmd() - err := complianceExportShowCmdF(s.th.Client, cmd, []string{job.Id}) - s.Require().EqualError(err, "failed to get compliance export job: You do not have the appropriate permissions.") - s.Require().Empty(printer.GetLines()) - s.Require().Empty(printer.GetErrorLines()) - }) - - s.RunForSystemAdminAndLocal("Show non-existent job", func(c client.Client) { - printer.Clean() - - cmd := makeCmd() - err := complianceExportShowCmdF(c, cmd, []string{"non-existent-job-id"}) - s.Require().EqualError(err, "failed to get compliance export job: Sorry, we could not find the page., There doesn't appear to be an api call for the url='/api/v4/jobs/non-existent-job-id'. Typo? are you missing a team_id or user_id as part of the url?") - s.Require().Empty(printer.GetLines()) - s.Require().Empty(printer.GetErrorLines()) - }) - - s.RunForSystemAdminAndLocal("Show existing job", func(c client.Client) { - now := model.GetMillis() - // Create a job - job, _, err := s.th.SystemAdminClient.CreateJob(context.Background(), &model.Job{ - Id: st.NewTestID(), - CreateAt: now - 1000, - Status: model.JobStatusSuccess, - Type: model.JobTypeMessageExport, - StartAt: now - 1000, - LastActivityAt: now - 1000, - }) - s.Require().NoError(err) - defer func() { - // Ensure job is deleted from the database - var result string - result, err = s.th.App.Srv().Store().Job().Delete(job.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - }() - - printer.Clean() - cmd := makeCmd() - err = complianceExportShowCmdF(c, cmd, []string{job.Id}) - s.Require().NoError(err) - s.Require().Len(printer.GetLines(), 1) - s.Require().Empty(printer.GetErrorLines()) - s.Require().Equal(job.Id, printer.GetLines()[0].(*model.Job).Id) - }) -} - -func (s *MmctlE2ETestSuite) TestComplianceExportCancelCmdE2E() { - s.SetupMessageExportTestHelper() - - s.Run("no permissions", func() { - printer.Clean() - - now := model.GetMillis() - // Create a job - job, _, err := s.th.SystemAdminClient.CreateJob(context.Background(), &model.Job{ - Id: st.NewTestID(), - CreateAt: now - 1000, - Status: model.JobStatusInProgress, - Type: model.JobTypeMessageExport, - StartAt: now - 1000, - LastActivityAt: now - 1000, - }) - s.Require().NoError(err) - defer func() { - // Ensure job is deleted from the database - var result string - result, err = s.th.App.Srv().Store().Job().Delete(job.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - }() - - cmd := makeCmd() - err = complianceExportCancelCmdF(s.th.Client, cmd, []string{job.Id}) - s.Require().EqualError(err, "failed to cancel compliance export job: You do not have the appropriate permissions.") - s.Require().Empty(printer.GetLines()) - s.Require().Empty(printer.GetErrorLines()) - }) - - s.RunForSystemAdminAndLocal("Cancel non-existent job", func(c client.Client) { - printer.Clean() - - cmd := makeCmd() - err := complianceExportCancelCmdF(c, cmd, []string{"non-existent-job-id"}) - s.Require().EqualError(err, "failed to cancel compliance export job: Sorry, we could not find the page., There doesn't appear to be an api call for the url='/api/v4/jobs/non-existent-job-id/cancel'. Typo? are you missing a team_id or user_id as part of the url?") - s.Require().Empty(printer.GetLines()) - s.Require().Empty(printer.GetErrorLines()) - }) - - s.RunForSystemAdminAndLocal("Cancel existing job", func(c client.Client) { - now := model.GetMillis() - // Create a job - job, _, err := s.th.SystemAdminClient.CreateJob(context.Background(), &model.Job{ - Id: st.NewTestID(), - CreateAt: now - 1000, - Status: model.JobStatusInProgress, - Type: model.JobTypeMessageExport, - StartAt: now - 1000, - LastActivityAt: now - 1000, - }) - s.Require().NoError(err) - defer func() { - // Ensure job is deleted from the database - var result string - result, err = s.th.App.Srv().Store().Job().Delete(job.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - }() - - printer.Clean() - cmd := makeCmd() - err = complianceExportCancelCmdF(c, cmd, []string{job.Id}) - s.Require().NoError(err) - s.Require().Empty(printer.GetLines()) - s.Require().Empty(printer.GetErrorLines()) - - // Verify job was cancelled - job, _, err = s.th.SystemAdminClient.GetJob(context.Background(), job.Id) - s.Require().NoError(err) - s.Require().Equal(model.JobStatusCanceled, job.Status) - }) - - s.RunForSystemAdminAndLocal("Error cancelling job in non-cancellable state", func(c client.Client) { - now := model.GetMillis() - // Create a job - job, _, err := s.th.SystemAdminClient.CreateJob(context.Background(), &model.Job{ - Id: st.NewTestID(), - CreateAt: now - 1000, - Status: model.JobStatusInProgress, - Type: model.JobTypeMessageExport, - StartAt: now - 1000, - LastActivityAt: now - 1000, - }) - s.Require().NoError(err) - _, err = s.th.SystemAdminClient.UpdateJobStatus(context.Background(), job.Id, model.JobStatusCanceled, true) - s.Require().NoError(err) - defer func() { - // Ensure job is deleted from the database - var result string - result, err = s.th.App.Srv().Store().Job().Delete(job.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - }() - - printer.Clean() - cmd := makeCmd() - err = complianceExportCancelCmdF(c, cmd, []string{job.Id}) - s.Require().EqualError(err, "failed to cancel compliance export job: Could not request cancellation for job that is not in a cancelable state.") - s.Require().Empty(printer.GetLines()) - s.Require().Empty(printer.GetErrorLines()) - }) -} - -func (s *MmctlE2ETestSuite) TestComplianceExportDownloadCmdE2E() { - s.SetupMessageExportTestHelper() - - s.Run("no permissions", func() { - printer.Clean() - - now := model.GetMillis() - // Create a job - job, _, err := s.th.SystemAdminClient.CreateJob(context.Background(), &model.Job{ - Id: st.NewTestID(), - CreateAt: now - 1000, - Status: model.JobStatusSuccess, - Type: model.JobTypeMessageExport, - StartAt: now - 1000, - LastActivityAt: now - 1000, - }) - s.Require().NoError(err) - defer func() { - // Ensure job is deleted from the database - var result string - result, err = s.th.App.Srv().Store().Job().Delete(job.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - }() - - cmd := makeCmd() - cmd.Flags().Int("num-retries", 0, "") - err = complianceExportDownloadCmdF(s.th.Client, cmd, []string{job.Id}) - s.Require().EqualError(err, "failed to download compliance export after 0 retries: You do not have the appropriate permissions.") - s.Require().Empty(printer.GetLines()) - s.Require().Empty(printer.GetErrorLines()) - }) - - s.RunForSystemAdminAndLocal("Download non-existent job", func(c client.Client) { - printer.Clean() - - cmd := makeCmd() - cmd.Flags().Int("num-retries", 0, "") - err := complianceExportDownloadCmdF(c, cmd, []string{"non-existent-job-id"}) - s.Require().EqualError(err, "failed to download compliance export after 0 retries: Sorry, we could not find the page., There doesn't appear to be an api call for the url='/api/v4/jobs/non-existent-job-id/download'. Typo? are you missing a team_id or user_id as part of the url?") - s.Require().Empty(printer.GetLines()) - s.Require().Empty(printer.GetErrorLines()) - }) - - s.RunForSystemAdminAndLocal("existing, non empty compliance export", func(c client.Client) { - printer.Clean() - - importFilePath := filepath.Join(server.GetPackagePath(), "test.zip") - f, err := os.Create(importFilePath) - s.Require().NoError(err) - _, err = f.WriteString("test data") - s.Require().NoError(err) - err = f.Close() - s.Require().NoError(err) - - defer func() { - err = os.Remove(importFilePath) - s.Require().NoError(err) - }() - - cmd := &cobra.Command{} - cmd.Flags().Int("num-retries", 0, "") - - err = complianceExportDownloadCmdF(c, cmd, []string{"jobId", importFilePath}) - s.Require().EqualError(err, "compliance export file already exists") - s.Require().Empty(printer.GetLines()) - s.Require().Empty(printer.GetErrorLines()) - }) - - s.RunForSystemAdminAndLocal("download with explicit path", func(c client.Client) { - printer.Clean() - - downloadPath := "explicit_path.zip" - defer os.Remove(downloadPath) - - serverDataDir, err := filepath.Abs(*s.th.App.Config().FileSettings.Directory) - s.Require().NoError(err) - - exportDir := "job_test_export" - // Create a compliance export with two zip files - exportFilePath := filepath.Join(serverDataDir, exportDir) - err = os.Mkdir(exportFilePath, 0755) - s.Require().NoError(err) - defer func() { - err = os.RemoveAll(exportFilePath) - s.Require().NoError(err) - }() - - // Create first zip file - zipPath1 := exportFilePath + "/export1.zip" - f1, err := os.Create(zipPath1) - s.Require().NoError(err) - _, err = f1.WriteString("test data 1") - s.Require().NoError(err) - err = f1.Close() - s.Require().NoError(err) - - // Create second zip file - zipPath2 := exportFilePath + "/export2.zip" - f2, err := os.Create(zipPath2) - s.Require().NoError(err) - _, err = f2.WriteString("test data 2") - s.Require().NoError(err) - err = f2.Close() - s.Require().NoError(err) - - s.T().Cleanup(func() { - err = os.RemoveAll(exportFilePath) - s.Require().NoError(err) - }) - - now := model.GetMillis() - // Create a job - job, _, err := s.th.SystemAdminClient.CreateJob(context.Background(), &model.Job{ - Id: st.NewTestID(), - CreateAt: now - 1000, - Status: model.JobStatusSuccess, - Type: model.JobTypeMessageExport, - StartAt: now - 1000, - LastActivityAt: now - 1000, - Data: model.StringMap{"export_dir": exportDir, "is_downloadable": "true"}, - }) - s.Require().NoError(err) - defer func() { - // Ensure job is deleted from the database - var result string - result, err = s.th.App.Srv().Store().Job().Delete(job.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - }() - - cmd := makeCmd() - cmd.Flags().Int("num-retries", 0, "") - - err = complianceExportDownloadCmdF(c, cmd, []string{job.Id, downloadPath}) - s.Require().NoError(err) - s.Require().Contains(printer.GetLines()[0], fmt.Sprintf("Compliance export file downloaded to %q", downloadPath)) - s.Require().Empty(printer.GetErrorLines()) - - // Verify the file was downloaded - _, err = os.Stat(downloadPath) - s.Require().NoError(err) - defer os.Remove(downloadPath) - - // Verify the file is a zip with a directory with two zip files - zipReader, err := zip.OpenReader(downloadPath) - s.Require().NoError(err) - defer zipReader.Close() - - // Check that we have the expected files in the zip - foundExport1 := false - foundExport2 := false - for _, file := range zipReader.File { - if file.Name == "export1.zip" { - foundExport1 = true - // Verify contents of export1.zip - rc, err := file.Open() - s.Require().NoError(err) - content, err := io.ReadAll(rc) - s.Require().NoError(err) - s.Require().Equal("test data 1", string(content)) - rc.Close() - } else if file.Name == "export2.zip" { - foundExport2 = true - // Verify contents of export2.zip - rc, err := file.Open() - s.Require().NoError(err) - content, err := io.ReadAll(rc) - s.Require().NoError(err) - s.Require().Equal("test data 2", string(content)) - rc.Close() - } else { - s.Failf("unexpected file found in downloaded zip", file.Name) - } - } - s.Require().True(foundExport1, "export1.zip not found in downloaded file") - s.Require().True(foundExport2, "export2.zip not found in downloaded file") - }) - - s.RunForSystemAdminAndLocal("download with explicit path", func(c client.Client) { - printer.Clean() - - serverDataDir, err := filepath.Abs(*s.th.App.Config().FileSettings.Directory) - s.Require().NoError(err) - - exportDir := "job_test_export" - expectedDownloadPath := exportDir + ".zip" - // Create a compliance export with two zip files - exportFilePath := filepath.Join(serverDataDir, exportDir) - err = os.Mkdir(exportFilePath, 0755) - s.Require().NoError(err) - defer func() { - err = os.RemoveAll(exportFilePath) - s.Require().NoError(err) - - // also remove the downloaded file - err = os.Remove(expectedDownloadPath) - s.Require().NoError(err) - }() - - // Create first zip file - zipPath1 := exportFilePath + "/export1.zip" - f1, err := os.Create(zipPath1) - s.Require().NoError(err) - _, err = f1.WriteString("test data 1") - s.Require().NoError(err) - err = f1.Close() - s.Require().NoError(err) - - // Create second zip file - zipPath2 := exportFilePath + "/export2.zip" - f2, err := os.Create(zipPath2) - s.Require().NoError(err) - _, err = f2.WriteString("test data 2") - s.Require().NoError(err) - err = f2.Close() - s.Require().NoError(err) - - defer func() { - err = os.RemoveAll(exportFilePath) - s.Require().NoError(err) - }() - - now := model.GetMillis() - // Create a job - job, _, err := s.th.SystemAdminClient.CreateJob(context.Background(), &model.Job{ - Id: st.NewTestID(), - CreateAt: now - 1000, - Status: model.JobStatusSuccess, - Type: model.JobTypeMessageExport, - StartAt: now - 1000, - LastActivityAt: now - 1000, - Data: model.StringMap{"export_dir": exportDir, "is_downloadable": "true"}, - }) - s.Require().NoError(err) - defer func() { - // Ensure job is deleted from the database - var result string - result, err = s.th.App.Srv().Store().Job().Delete(job.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - }() - - cmd := makeCmd() - cmd.Flags().Int("num-retries", 0, "") - - err = complianceExportDownloadCmdF(c, cmd, []string{job.Id}) - s.Require().NoError(err) - s.Require().Contains(printer.GetLines()[0], fmt.Sprintf("Compliance export file downloaded to %q", expectedDownloadPath)) - s.Require().Empty(printer.GetErrorLines()) - - // Verify the file was downloaded - _, err = os.Stat(expectedDownloadPath) - s.Require().NoError(err) - - // Verify the file is a zip with a directory with two zip files - zipReader, err := zip.OpenReader(expectedDownloadPath) - s.Require().NoError(err) - defer zipReader.Close() - - // Check that we have the expected files in the zip - foundExport1 := false - foundExport2 := false - for _, file := range zipReader.File { - if file.Name == "export1.zip" { - foundExport1 = true - // Verify contents of export1.zip - rc, err := file.Open() - s.Require().NoError(err) - content, err := io.ReadAll(rc) - s.Require().NoError(err) - s.Require().Equal("test data 1", string(content)) - rc.Close() - } else if file.Name == "export2.zip" { - foundExport2 = true - // Verify contents of export2.zip - rc, err := file.Open() - s.Require().NoError(err) - content, err := io.ReadAll(rc) - s.Require().NoError(err) - s.Require().Equal("test data 2", string(content)) - rc.Close() - } else { - s.Failf("unexpected file found in downloaded zip", file.Name) - } - } - s.Require().True(foundExport1, "export1.zip not found in downloaded file") - s.Require().True(foundExport2, "export2.zip not found in downloaded file") - }) -} - -func (s *MmctlE2ETestSuite) TestComplianceExportMmctlJobStartTimeE2E() { - s.SetupMessageExportTestHelper() - - s.RunForSystemAdminAndLocal("mmctl job uses batch_start_time from previous regular job", func(c client.Client) { - // Ensure no jobs exist before we start - jobs, _, err := s.th.SystemAdminClient.GetJobsByType(context.Background(), model.JobTypeMessageExport, 0, 1000) - s.Require().NoError(err) - for _, job := range jobs { - var result string - result, err = s.th.App.Srv().Store().Job().Delete(job.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - } - - now := model.GetMillis() - - // Create a regular (non-mmctl) export job - regularStartTime := now - 10000 - regularEndTime := now - 5000 - regularJob := s.runJobForTest(map[string]string{ - shared.JobDataBatchStartTime: strconv.FormatInt(regularStartTime, 10), - shared.JobDataJobEndTime: strconv.FormatInt(regularEndTime, 10), - }) - - s.Require().Equal(model.JobStatusSuccess, regularJob.Status, "Regular job should complete successfully") - s.Require().NotEmpty(regularJob.Data[shared.JobDataBatchStartTime], "Regular job should have a batch start time") - regularJobBatchStartTime := regularJob.Data[shared.JobDataBatchStartTime] - - // Run an mmctl-initiated export job - cmd := &cobra.Command{} - cmd.Flags().String("date", "", "") - cmd.Flags().Int("start", 0, "") - cmd.Flags().Int("end", 0, "") - err = complianceExportCreateCmdF(c, cmd, []string{model.ComplianceExportTypeActiance}) - s.Require().NoError(err, "Should create mmctl job successfully") - - // Find the mmctl job - jobs, _, err = s.th.SystemAdminClient.GetJobsByType(context.Background(), model.JobTypeMessageExport, 0, 10) - s.Require().NoError(err) - s.Require().True(len(jobs) > 1, "Should have at least 2 jobs") - - // The most recent job should be the mmctl job - mmctlJob := jobs[0] - s.Require().Equal("mmctl", mmctlJob.Data[shared.JobDataInitiatedBy]) - - // Wait for the mmctl job to complete - s.checkJobForStatus(mmctlJob.Id, model.JobStatusSuccess) - mmctlJob = s.getMostRecentJobWithId(mmctlJob.Id) - - // The job_start_time should match the batch_start_time from the previous regular job - s.Require().Equal(regularJobBatchStartTime, mmctlJob.Data[shared.JobDataJobStartTime], - "mmctl job should use batch_start_time from previous regular job as its job_start_time") - - // Clean up jobs - for _, job := range jobs { - result, err := s.th.App.Srv().Store().Job().Delete(job.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - } - }) - - s.RunForSystemAdminAndLocal("mmctl job ignores previous mmctl jobs and uses regular job", func(c client.Client) { - // Ensure no jobs exist before we start - jobs, _, err := s.th.SystemAdminClient.GetJobsByType(context.Background(), model.JobTypeMessageExport, 0, 1000) - s.Require().NoError(err) - for _, job := range jobs { - var result string - result, err = s.th.App.Srv().Store().Job().Delete(job.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - } - - now := model.GetMillis() - - // Create a regular (non-mmctl) export job - regularStartTime := now - 10000 - regularEndTime := now - 5000 - regularJob := s.runJobForTest(map[string]string{ - shared.JobDataBatchStartTime: strconv.FormatInt(regularStartTime, 10), - shared.JobDataJobEndTime: strconv.FormatInt(regularEndTime, 10), - }) - - s.Require().Equal(model.JobStatusSuccess, regularJob.Status, "Regular job should complete successfully") - s.Require().NotEmpty(regularJob.Data[shared.JobDataBatchStartTime], "Regular job should have a batch start time") - regularJobBatchStartTime := regularJob.Data[shared.JobDataBatchStartTime] - - // Run an mmctl-initiated export job with an explicit start time (different from the regular job) - cmd := &cobra.Command{} - cmd.Flags().String("date", "", "") - cmd.Flags().Int("start", int(now-2000), "") - cmd.Flags().Int("end", int(now-1000), "") - err = complianceExportCreateCmdF(c, cmd, []string{model.ComplianceExportTypeActiance}) - s.Require().NoError(err, "Should create first mmctl job successfully") - - // Find the mmctl job - jobs, _, err = s.th.SystemAdminClient.GetJobsByType(context.Background(), model.JobTypeMessageExport, 0, 10) - s.Require().NoError(err) - s.Require().True(len(jobs) > 1, "Should have at least 2 jobs") - - // The most recent job should be the mmctl job - mmctlJob1 := jobs[0] - s.Require().Equal("mmctl", mmctlJob1.Data[shared.JobDataInitiatedBy]) - - // Wait for the mmctl job to complete - s.checkJobForStatus(mmctlJob1.Id, model.JobStatusSuccess) - mmctlJob1 = s.getMostRecentJobWithId(mmctlJob1.Id) - - // Verify this job has a different batch_start_time than the regular job - s.Require().NotEqual(regularJobBatchStartTime, mmctlJob1.Data[shared.JobDataBatchStartTime], - "First mmctl job should have a different batch_start_time than regular job") - - // Run a second mmctl-initiated export job WITHOUT a specified start time - cmd = &cobra.Command{} - cmd.Flags().String("date", "", "") - cmd.Flags().Int("start", 0, "") - cmd.Flags().Int("end", 0, "") - err = complianceExportCreateCmdF(c, cmd, []string{model.ComplianceExportTypeActiance}) - s.Require().NoError(err, "Should create second mmctl job successfully") - - // Find the second mmctl job - jobs, _, err = s.th.SystemAdminClient.GetJobsByType(context.Background(), model.JobTypeMessageExport, 0, 10) - s.Require().NoError(err) - s.Require().True(len(jobs) > 2, "Should have at least 3 jobs") - - // The most recent job should be the second mmctl job - mmctlJob2 := jobs[0] - s.Require().Equal("mmctl", mmctlJob2.Data[shared.JobDataInitiatedBy]) - - // Wait for the second mmctl job to complete - s.checkJobForStatus(mmctlJob2.Id, model.JobStatusSuccess) - mmctlJob2 = s.getMostRecentJobWithId(mmctlJob2.Id) - - // The job_start_time of the second mmctl job should match the batch_start_time from the regular job, - // not from the mmctl job that ran in between - s.Require().Equal(regularJobBatchStartTime, mmctlJob2.Data[shared.JobDataJobStartTime], - "Second mmctl job should use batch_start_time from previous regular job as its job_start_time, not from previous mmctl job") - s.Require().NotEqual(mmctlJob1.Data[shared.JobDataBatchStartTime], mmctlJob2.Data[shared.JobDataJobStartTime], - "Second mmctl job should not use batch_start_time from previous mmctl job as its job_start_time") - - // Clean up jobs - for _, job := range jobs { - result, err := s.th.App.Srv().Store().Job().Delete(job.Id) - s.Require().NoError(err, "Failed to delete job (result: %v)", result) - } - }) -} --- /dev/null +++ b/server/enterprise/placeholder.go @@ -0,0 +1,3 @@ +// Regenerated by the ReadyStack kit. Keeps `package enterprise` importable — cmd/mattermost +// blank-imports it unconditionally — while carrying none of the Source Available code. +package enterprise --- a/server/go.mod +++ b/server/go.mod @@ -56,7 +56,6 @@ github.com/mholt/archives v0.1.5 github.com/microcosm-cc/bluemonday v1.0.27 github.com/minio/minio-go/v7 v7.3.0 - github.com/opensearch-project/opensearch-go/v4 v4.7.3 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.24.1 github.com/prometheus/client_model v0.6.2 @@ -79,7 +78,7 @@ github.com/yuin/goldmark v1.8.5 golang.org/x/crypto v0.57.0 golang.org/x/image v0.45.0 - golang.org/x/net v0.58.0 + golang.org/x/net v0.60.0 golang.org/x/sync v0.23.0 golang.org/x/sys v0.48.0 golang.org/x/term v0.46.0 @@ -219,7 +218,7 @@ golang.org/x/exp v0.0.0-20260820142414-ca536658362e // indirect golang.org/x/mod v0.41.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect - google.golang.org/grpc v1.84.0 // indirect + google.golang.org/grpc v1.85.0-dev.0.20260825072537-93e31b48545e // indirect google.golang.org/protobuf v1.36.12 // indirect gopkg.in/ini.v1 v1.67.3 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect @@ -233,3 +232,5 @@ // See MM-66167, MM-68222 for more details. replace github.com/vmihailenco/msgpack/v5 => github.com/mattermost/msgpack/v5 v5.0.0-20260813205620-e158e8d3647e + +replace github.com/mattermost/mattermost/server/public => ./public --- a/server/go.sum +++ b/server/go.sum @@ -367,8 +367,6 @@ github.com/mattermost/logr/v2 v2.0.22/go.mod h1:0sUKpO+XNMZApeumaid7PYaUZPBIydfuWZ0dqixXo+s= github.com/mattermost/mattermost-plugin-ai v1.14.2 h1:b29qPAKqC9khroVG7dTyUc8wgdNyKeNQxr6S/UCPZGM= github.com/mattermost/mattermost-plugin-ai v1.14.2/go.mod h1:L/I/IpdWNGbxRfUduCstCYbhyX59OEftxcpDHtCT4EI= -github.com/mattermost/mattermost/server/public v0.4.3 h1:vxdrD5j3+oVfrSCiSaX/lY6N0eKyvIH66wu4DTGgPx4= -github.com/mattermost/mattermost/server/public v0.4.3/go.mod h1:2z08gasPXqIIbzl/xf2/2Sfn5ITFLGC6tplhOklyAAQ= github.com/mattermost/morph v1.1.0 h1:Q9vrJbeM3s2jfweGheq12EFIzdNp9a/6IovcbvOQ6Cw= github.com/mattermost/morph v1.1.0/go.mod h1:gD+EaqX2UMyyuzmF4PFh4r33XneQ8Nzi+0E8nXjMa3A= github.com/mattermost/msgpack/v5 v5.0.0-20260813205620-e158e8d3647e h1:tsfWJPgXPNhPT+kOkBmJJJDSjXSNAsweZrujWt8yfHk= @@ -441,8 +439,6 @@ github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I= github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg= -github.com/opensearch-project/opensearch-go/v4 v4.7.3 h1:JzETy7bYnnSDj4gueUh8t4EYBhs9rhKsgeVsoul77rA= -github.com/opensearch-project/opensearch-go/v4 v4.7.3/go.mod h1:+iikkyLrVC8ZvyfKv2sua1Ze1LbpWL7eZe4IkHqNGtg= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/otiai10/gosseract/v2 v2.4.1 h1:G8AyBpXEeSlcq8TI85LH/pM5SXk8Djy2GEXisgyblRw= github.com/otiai10/gosseract/v2 v2.4.1/go.mod h1:1gNWP4Hgr2o7yqWfs6r5bZxAatjOIdqWxJLWsTsembk= @@ -605,14 +601,6 @@ github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/throttled/throttled/v2 v2.15.0 h1:7XLCECtmEx+Yz/e5opBNff9cPGpH0ia0xEj5kyDPotI= github.com/throttled/throttled/v2 v2.15.0/go.mod h1:JlfSSSYoM/bjFoW2sCATGxJJXggjO67DFQu9xduGAWE= -github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= -github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= -github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ= github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= @@ -627,8 +615,6 @@ github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/wI2L/jsondiff v0.7.1 h1:Fg9+yj+1/x3UtPBJhR91TKEzRkrEEWcAcLbg9dzEaNM= -github.com/wI2L/jsondiff v0.7.1/go.mod h1:yAt2W7U6Jd4HK0RA8DGSGk0zDtfEtOUUJVnH/xICpjo= github.com/wiggin77/merror v1.0.5 h1:P+lzicsn4vPMycAf2mFf7Zk6G9eco5N+jB1qJ2XW3ME= github.com/wiggin77/merror v1.0.5/go.mod h1:H2ETSu7/bPE0Ymf4bEwdUoo73OOEkdClnoRisfw0Nm0= github.com/wiggin77/srslog v1.0.1 h1:gA2XjSMy3DrRdX9UqLuDtuVAAshb8bE1NhX1YK0Qe+8= @@ -654,8 +640,8 @@ go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= +go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -714,8 +700,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= +golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -815,8 +801,8 @@ google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= -google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= +google.golang.org/grpc v1.85.0-dev.0.20260825072537-93e31b48545e h1:ar+feAPij1Znzug45m6AoShUCc5AaR8FLA3I6LZd94k= +google.golang.org/grpc v1.85.0-dev.0.20260825072537-93e31b48545e/go.mod h1:7aY/KlAHanMAVIWHnk8wloQsdlfwzDz/PP9vCbJG6CU= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=