diff --git a/internal/cli/atlas_logs_download.go b/internal/cli/atlas_logs_download.go index f2cf20e896..17078e0f8e 100644 --- a/internal/cli/atlas_logs_download.go +++ b/internal/cli/atlas_logs_download.go @@ -85,10 +85,9 @@ func AtlasLogsDownloadBuilder() *cobra.Command { fs: afero.NewOsFs(), } cmd := &cobra.Command{ - Use: "download [hostname] [logname]", - Short: description.ListDisks, - Aliases: []string{"ls"}, - Args: cobra.ExactArgs(2), + Use: "download [hostname] [logname]", + Short: description.ListDisks, + Args: cobra.ExactArgs(2), PreRunE: func(cmd *cobra.Command, args []string) error { return opts.PreRunE(opts.initStore) }, diff --git a/internal/cli/ops_manager_logs.go b/internal/cli/ops_manager_logs.go index 334667af7f..8a0a3e3018 100644 --- a/internal/cli/ops_manager_logs.go +++ b/internal/cli/ops_manager_logs.go @@ -28,6 +28,7 @@ func OpsManagerLogsBuilder() *cobra.Command { cmd.AddCommand(OpsManagerLogsCollectOptsBuilder()) cmd.AddCommand(OpsManagerLogsListOptsBuilder()) + cmd.AddCommand(OpsManagerLogsDownloadOptsBuilder()) return cmd } diff --git a/internal/cli/ops_manager_logs_download.go b/internal/cli/ops_manager_logs_download.go new file mode 100644 index 0000000000..83ffa452a8 --- /dev/null +++ b/internal/cli/ops_manager_logs_download.go @@ -0,0 +1,88 @@ +// Copyright 2020 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli + +import ( + "io" + "os" + + "github.com/mongodb/mongocli/internal/description" + "github.com/mongodb/mongocli/internal/flags" + "github.com/mongodb/mongocli/internal/store" + "github.com/mongodb/mongocli/internal/usage" + "github.com/spf13/afero" + "github.com/spf13/cobra" +) + +type opsManagerLogsDownloadOpts struct { + globalOpts + id string + out string + fs afero.Fs + store store.LogJobsDownloader +} + +func (opts *opsManagerLogsDownloadOpts) initStore() error { + var err error + opts.store, err = store.New() + return err +} + +func (opts *opsManagerLogsDownloadOpts) Run() error { + out, err := opts.newWriteCloser() + if err != nil { + return err + } + + if err := opts.store.DownloadLogJob(opts.ProjectID(), opts.id, out); err != nil { + return err + } + + return nil +} + +func (opts *opsManagerLogsDownloadOpts) newWriteCloser() (io.WriteCloser, error) { + // Create file only if is not there already (don't overwrite) + ff := os.O_CREATE | os.O_TRUNC | os.O_WRONLY | os.O_EXCL + f, err := opts.fs.OpenFile(opts.out, ff, 0777) + return f, err +} + +// mongocli om logs download id [--out out] [--projectId projectId] +func OpsManagerLogsDownloadOptsBuilder() *cobra.Command { + opts := &opsManagerLogsDownloadOpts{ + fs: afero.NewOsFs(), + } + cmd := &cobra.Command{ + Use: "download [id]", + Short: description.DownloadLogs, + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { + return opts.PreRunE(opts.initStore) + }, + RunE: func(cmd *cobra.Command, args []string) error { + opts.id = args[0] + return opts.Run() + }, + } + + cmd.Flags().StringVarP(&opts.out, flags.Out, flags.OutShort, "", usage.LogOut) + + cmd.Flags().StringVar(&opts.projectID, flags.ProjectID, "", usage.ProjectID) + + _ = cmd.MarkFlagRequired(flags.Out) + + return cmd +} diff --git a/internal/cli/ops_manager_logs_download_test.go b/internal/cli/ops_manager_logs_download_test.go new file mode 100644 index 0000000000..50aec00aea --- /dev/null +++ b/internal/cli/ops_manager_logs_download_test.go @@ -0,0 +1,51 @@ +// Copyright 2020 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cli + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/mongodb/mongocli/internal/mocks" + "github.com/spf13/afero" +) + +func TestOpsManagerLogsDownloadOpts_Run(t *testing.T) { + ctrl := gomock.NewController(t) + mockStore := mocks.NewMockLogJobsDownloader(ctrl) + + appFS := afero.NewMemMapFs() + + opts := &opsManagerLogsDownloadOpts{ + id: "1", + fs: appFS, + store: mockStore, + } + + f, err := opts.newWriteCloser() + if err != nil { + t.Fatalf("newWriteCloser() unexpected error: %v", err) + } + + mockStore. + EXPECT(). + DownloadLogJob(opts.projectID, opts.id, f). + Return(nil). + Times(1) + + if err := opts.Run(); err != nil { + t.Fatalf("Run() unexpected error: %v", err) + } +} diff --git a/internal/description/description.go b/internal/description/description.go index 78c82575d7..475015f0e5 100644 --- a/internal/description/description.go +++ b/internal/description/description.go @@ -84,6 +84,7 @@ const ( CreateProject = "Create a project." DeleteProject = "Delete a project." ListProjects = "List projects." + DownloadLogs = "Download logs from a log collection job." OpsManager = "Ops Manager operations." ListGlobalAlerts = "List global alerts." Automation = "Manage Ops Manager automation config." diff --git a/internal/flags/flags.go b/internal/flags/flags.go index d4ee74f80a..839f9971e7 100644 --- a/internal/flags/flags.go +++ b/internal/flags/flags.go @@ -116,5 +116,5 @@ const ( Strength = "strength" // Strength flag SizeRequestedPerFileBytes = "sizeRequestedPerFileBytes" //SizeRequestedPerFileBytes flag Redacted = "redacted" // Redacted flag - Verbose = "verbose" + Verbose = "verbose" // Verbose flag ) diff --git a/internal/mocks/mock_logs.go b/internal/mocks/mock_logs.go index 62aef1d7a7..82002952a9 100644 --- a/internal/mocks/mock_logs.go +++ b/internal/mocks/mock_logs.go @@ -49,6 +49,43 @@ func (mr *MockLogsDownloaderMockRecorder) DownloadLog(arg0, arg1, arg2, arg3, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadLog", reflect.TypeOf((*MockLogsDownloader)(nil).DownloadLog), arg0, arg1, arg2, arg3, arg4) } +// MockLogJobsDownloader is a mock of LogJobsDownloader interface +type MockLogJobsDownloader struct { + ctrl *gomock.Controller + recorder *MockLogJobsDownloaderMockRecorder +} + +// MockLogJobsDownloaderMockRecorder is the mock recorder for MockLogJobsDownloader +type MockLogJobsDownloaderMockRecorder struct { + mock *MockLogJobsDownloader +} + +// NewMockLogJobsDownloader creates a new mock instance +func NewMockLogJobsDownloader(ctrl *gomock.Controller) *MockLogJobsDownloader { + mock := &MockLogJobsDownloader{ctrl: ctrl} + mock.recorder = &MockLogJobsDownloaderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockLogJobsDownloader) EXPECT() *MockLogJobsDownloaderMockRecorder { + return m.recorder +} + +// DownloadLogJob mocks base method +func (m *MockLogJobsDownloader) DownloadLogJob(arg0, arg1 string, arg2 io.Writer) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DownloadLogJob", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// DownloadLogJob indicates an expected call of DownloadLogJob +func (mr *MockLogJobsDownloaderMockRecorder) DownloadLogJob(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadLogJob", reflect.TypeOf((*MockLogJobsDownloader)(nil).DownloadLogJob), arg0, arg1, arg2) +} + // MockLogCollector is a mock of LogCollector interface type MockLogCollector struct { ctrl *gomock.Controller diff --git a/internal/store/logs.go b/internal/store/logs.go index 0568bc0e35..20c922a766 100644 --- a/internal/store/logs.go +++ b/internal/store/logs.go @@ -28,6 +28,10 @@ type LogsDownloader interface { DownloadLog(string, string, string, io.Writer, *atlas.DateRangetOptions) error } +type LogJobsDownloader interface { + DownloadLogJob(string, string, io.Writer) error +} + type LogCollector interface { Collect(string, *om.LogCollectionJob) (*om.LogCollectionJob, error) } @@ -68,3 +72,14 @@ func (s *Store) DownloadLog(groupID, host, name string, out io.Writer, opts *atl return fmt.Errorf("unsupported service: %s", s.service) } } + +// DownloadLogJob encapsulate the logic to manage different cloud providers +func (s *Store) DownloadLogJob(groupID, jobID string, out io.Writer) error { + switch s.service { + case config.OpsManagerService, config.CloudManagerService: + _, err := s.client.(*om.Client).Logs.Download(context.Background(), groupID, jobID, out) + return err + default: + return fmt.Errorf("unsupported service: %s", s.service) + } +}