From c4ec90314b6bf4dae1c10306db5cc51973160790 Mon Sep 17 00:00:00 2001 From: Nathan Smyth Date: Wed, 13 Mar 2024 23:16:24 +0000 Subject: [PATCH 01/12] feat: Initial commit --- .../atlas-streams-instances-download.txt | 121 ++++++++++++++++++ docs/command/atlas-streams-instances.txt | 2 + .../cli/atlas/streams/instance/instance.go | 2 +- .../atlas/streams/instance/instance_test.go | 2 +- internal/cli/atlas/streams/instance/logs.go | 119 +++++++++++++++++ .../cli/atlas/streams/instance/logs_test.go | 95 ++++++++++++++ internal/mocks/mock_streams.go | 41 +++++- internal/store/streams.go | 20 ++- 8 files changed, 398 insertions(+), 4 deletions(-) create mode 100644 docs/command/atlas-streams-instances-download.txt create mode 100644 internal/cli/atlas/streams/instance/logs.go create mode 100644 internal/cli/atlas/streams/instance/logs_test.go diff --git a/docs/command/atlas-streams-instances-download.txt b/docs/command/atlas-streams-instances-download.txt new file mode 100644 index 0000000000..e24630ad7a --- /dev/null +++ b/docs/command/atlas-streams-instances-download.txt @@ -0,0 +1,121 @@ +.. _atlas-streams-instances-download: + +================================ +atlas streams instances download +================================ + +.. default-domain:: mongodb + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +Download a compressed file that contains the logs for the specified Atlas Stream Processing instance. + +This command downloads a file with a .gz extension.To use this command, you must authenticate with a user account or an API key with the Project Data Access Read/Write role. + +Syntax +------ + +.. code-block:: + :caption: Command Syntax + + atlas streams instances download [options] + +.. Code end marker, please don't delete this comment + +Arguments +--------- + +.. list-table:: + :header-rows: 1 + :widths: 20 10 10 60 + + * - Name + - Type + - Required + - Description + * - tenantName + - string + - true + - Label that identifies the tenant that stores the log files that you want to download. + * - auditLogs.gz + - string + - true + - Log file that you want to return. + +Options +------- + +.. list-table:: + :header-rows: 1 + :widths: 20 10 10 60 + + * - Name + - Type + - Required + - Description + * - -d, --decompress + - + - false + - Flag that indicates whether to decompress the log files. + * - --end + - int + - false + - UNIX Epoch-formatted ending date and time for the range of log messages to retrieve. This value defaults to the current timestamp. + * - --force + - + - false + - Flag that indicates whether to overwrite the destination file. + * - -h, --help + - + - false + - help for download + * - --out + - string + - false + - Output file name. This value defaults to the log name. + * - --projectId + - string + - false + - Hexadecimal string that identifies the project to use. This option overrides the settings in the configuration file or environment variable. + * - --start + - int + - false + - UNIX Epoch-formatted starting date and time for the range of log messages to retrieve. This value defaults to 24 hours prior to the current timestamp. + +Inherited Options +----------------- + +.. list-table:: + :header-rows: 1 + :widths: 20 10 10 60 + + * - Name + - Type + - Required + - Description + * - -P, --profile + - string + - false + - Name of the profile to use from your configuration file. To learn about profiles for the Atlas CLI, see https://dochub.mongodb.org/core/atlas-cli-save-connection-settings. + +Output +------ + +If the command succeeds, the CLI returns output similar to the following sample. Values in brackets represent your values. + +.. code-block:: + + Download of completed. + + +Examples +-------- + +.. code-block:: + + # Download the audit log file from the instance myProcessor for the project with the ID 5e2211c17a3e5a48f5497de3: + atlas streams instance logs myProcessor auditLogs.gz --projectId 5e2211c17a3e5a48f5497de3 diff --git a/docs/command/atlas-streams-instances.txt b/docs/command/atlas-streams-instances.txt index 03adcf2697..88cb43a8a5 100644 --- a/docs/command/atlas-streams-instances.txt +++ b/docs/command/atlas-streams-instances.txt @@ -54,6 +54,7 @@ Related Commands * :ref:`atlas-streams-instances-create` - Create an Atlas Stream Processing instance for your project * :ref:`atlas-streams-instances-delete` - Delete an Atlas Stream Processing instance. * :ref:`atlas-streams-instances-describe` - Describe an Atlas Stream Processing instance for your project. +* :ref:`atlas-streams-instances-download` - Download a compressed file that contains the logs for the specified Atlas Stream Processing instance. * :ref:`atlas-streams-instances-list` - List all the Atlas Stream Processing instances for your project. * :ref:`atlas-streams-instances-update` - Updates an Atlas Stream Processing instance for your project. @@ -64,6 +65,7 @@ Related Commands create delete describe + download list update diff --git a/internal/cli/atlas/streams/instance/instance.go b/internal/cli/atlas/streams/instance/instance.go index c21e2d4603..fa9b2be1ac 100644 --- a/internal/cli/atlas/streams/instance/instance.go +++ b/internal/cli/atlas/streams/instance/instance.go @@ -27,7 +27,7 @@ func Builder() *cobra.Command { Short: "Manage Atlas Stream Processing instances.", Long: `Create, list, update and delete your Atlas Stream Processing instances.`, } - cmd.AddCommand(CreateBuilder(), UpdateBuilder(), ListBuilder(), DeleteBuilder(), DescribeBuilder()) + cmd.AddCommand(CreateBuilder(), UpdateBuilder(), ListBuilder(), DeleteBuilder(), DescribeBuilder(), DownloadBuilder()) return cmd } diff --git a/internal/cli/atlas/streams/instance/instance_test.go b/internal/cli/atlas/streams/instance/instance_test.go index 685e4e4323..22bd648cab 100644 --- a/internal/cli/atlas/streams/instance/instance_test.go +++ b/internal/cli/atlas/streams/instance/instance_test.go @@ -24,7 +24,7 @@ func TestBuilder(t *testing.T) { test.CmdValidator( t, Builder(), - 5, + 6, []string{}, ) } diff --git a/internal/cli/atlas/streams/instance/logs.go b/internal/cli/atlas/streams/instance/logs.go new file mode 100644 index 0000000000..663d71fde0 --- /dev/null +++ b/internal/cli/atlas/streams/instance/logs.go @@ -0,0 +1,119 @@ +// Copyright 2024 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 instance + +import ( + "context" + "fmt" + "slices" + + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/require" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/config" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/flag" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/usage" + "github.com/spf13/afero" + "github.com/spf13/cobra" +) + +var downloadMessage = "Download of %s completed.\n" + +type DownloadOpts struct { + cli.GlobalOpts + cli.DownloaderOpts + tenantName string + fileName string + start int64 + end int64 + decompress bool + store store.StreamsDownloader +} + +func (opts *DownloadOpts) initStore(ctx context.Context) func() error { + return func() error { + var err error + opts.store, err = store.New(store.AuthenticatedPreset(config.Default()), store.WithContext(ctx)) + return err + } +} + +func (opts *DownloadOpts) initDefaultOut() error { + if opts.Out == "" { + opts.Out = opts.fileName + } + return nil +} + +func (opts *DownloadOpts) Run() error { + w, err := opts.NewWriteCloser() + if err != nil { + return err + } + defer w.Close() + + return nil +} + +// DownloadBuilder +// atlas streams logs [tenantName] audit.gz --projectId [projectID]. +func DownloadBuilder() *cobra.Command { + const argsN = 2 + opts := &DownloadOpts{} + opts.Fs = afero.NewOsFs() + cmd := &cobra.Command{ + Use: "download ", + Short: "Download a compressed file that contains the logs for the specified Atlas Stream Processing instance.", + Long: `This command downloads a file with a .gz extension.` + fmt.Sprintf(usage.RequiredRole, "Project Data Access Read/Write"), + Args: cobra.MatchAll( + require.ExactArgs(argsN), + func(cmd *cobra.Command, args []string) error { + if !slices.Contains(cmd.ValidArgs, args[1]) { + return fmt.Errorf(" must be one of %s", cmd.ValidArgs) + } + return nil + }, + ), + Example: ` # Download the audit log file from the instance myProcessor for the project with the ID 5e2211c17a3e5a48f5497de3: + atlas streams instance logs myProcessor auditLogs.gz --projectId 5e2211c17a3e5a48f5497de3`, + Annotations: map[string]string{ + "tenantNameDesc": "Label that identifies the tenant that stores the log files that you want to download.", + "auditLogs.gzDesc": "Log file that you want to return.", + "output": downloadMessage, + }, + PreRunE: func(cmd *cobra.Command, args []string) error { + opts.tenantName = args[0] + opts.fileName = args[1] + return opts.PreRunE(opts.ValidateProjectID, opts.initStore(cmd.Context()), opts.initDefaultOut) + }, + RunE: func(_ *cobra.Command, _ []string) error { + return opts.Run() + }, + + ValidArgs: []string{"auditLogs.gz"}, + } + + cmd.Flags().StringVar(&opts.Out, flag.Out, "", usage.LogOut) + cmd.Flags().Int64Var(&opts.start, flag.Start, 0, usage.LogStart) + cmd.Flags().Int64Var(&opts.end, flag.End, 0, usage.LogEnd) + cmd.Flags().BoolVar(&opts.Force, flag.Force, false, usage.ForceFile) + cmd.Flags().BoolVarP(&opts.decompress, flag.Decompress, flag.DecompressShort, false, usage.Decompress) + + cmd.Flags().StringVar(&opts.ProjectID, flag.ProjectID, "", usage.ProjectID) + + _ = cmd.MarkFlagFilename(flag.Out) + + return cmd +} diff --git a/internal/cli/atlas/streams/instance/logs_test.go b/internal/cli/atlas/streams/instance/logs_test.go new file mode 100644 index 0000000000..d606f5f25b --- /dev/null +++ b/internal/cli/atlas/streams/instance/logs_test.go @@ -0,0 +1,95 @@ +// Copyright 2024 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 instance + +import ( + "io" + "os" + "testing" + + "github.com/golang/mock/gomock" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/flag" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mocks" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/test" + "github.com/spf13/afero" + "github.com/stretchr/testify/require" +) + +func TestDownloadOpts_Run(t *testing.T) { + ctrl := gomock.NewController(t) + mockStore := mocks.NewMockStreamsDownloader(ctrl) + + const contents = "expected" + const fileName = "auditLogs.gz" + + file, err := os.CreateTemp("", "") + if err != nil { + require.NoError(t, err) + } + filename := file.Name() + defer os.Remove(filename) + _, _ = file.WriteString(contents) + _ = file.Close() + + expected, _ := os.Open(filename) + defer expected.Close() + + fs := afero.NewMemMapFs() + downloadOpts := &DownloadOpts{ + store: mockStore, + DownloaderOpts: cli.DownloaderOpts{ + Out: fileName, + Fs: fs, + }, + } + + downloadOpts.ProjectID = "download-project-id" + downloadOpts.decompress = true + downloadOpts.fileName = fileName + + endDate := int64(0) + startDate := int64(0) + + downloadParams := new(store.DownloadStreamTenantAuditLogsApiParams) + downloadParams.EndDate = &endDate + downloadParams.StartDate = &startDate + downloadParams.TenantName = "streams-tenant" + + mockStore. + EXPECT(). + DownloadAuditLog(downloadParams). + Return(expected, nil). + Times(1) + + if err := downloadOpts.Run(); err != nil { + t.Fatalf("Run() unexpected error: %v", err) + } + + of, _ := fs.Open(fileName) + defer of.Close() + b, _ := io.ReadAll(of) + require.Equal(t, contents, string(b)) +} + +func TestDownloadBuilder(t *testing.T) { + test.CmdValidator( + t, + DownloadBuilder(), + 0, + []string{flag.Out, flag.Start, flag.End, flag.Force, flag.Decompress, flag.ProjectID}, + ) +} diff --git a/internal/mocks/mock_streams.go b/internal/mocks/mock_streams.go index 34ce43220a..eafc79c8a1 100644 --- a/internal/mocks/mock_streams.go +++ b/internal/mocks/mock_streams.go @@ -1,10 +1,11 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store (interfaces: StreamsLister,StreamsDescriber,StreamsCreator,StreamsDeleter,StreamsUpdater,ConnectionCreator,ConnectionDeleter,ConnectionUpdater,StreamsConnectionDescriber,StreamsConnectionLister) +// Source: github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store (interfaces: StreamsLister,StreamsDescriber,StreamsCreator,StreamsDeleter,StreamsUpdater,StreamsDownloader,ConnectionCreator,ConnectionDeleter,ConnectionUpdater,StreamsConnectionDescriber,StreamsConnectionLister) // Package mocks is a generated GoMock package. package mocks import ( + io "io" reflect "reflect" gomock "github.com/golang/mock/gomock" @@ -200,6 +201,44 @@ func (mr *MockStreamsUpdaterMockRecorder) UpdateStream(arg0, arg1, arg2 interfac return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStream", reflect.TypeOf((*MockStreamsUpdater)(nil).UpdateStream), arg0, arg1, arg2) } +// MockStreamsDownloader is a mock of StreamsDownloader interface. +type MockStreamsDownloader struct { + ctrl *gomock.Controller + recorder *MockStreamsDownloaderMockRecorder +} + +// MockStreamsDownloaderMockRecorder is the mock recorder for MockStreamsDownloader. +type MockStreamsDownloaderMockRecorder struct { + mock *MockStreamsDownloader +} + +// NewMockStreamsDownloader creates a new mock instance. +func NewMockStreamsDownloader(ctrl *gomock.Controller) *MockStreamsDownloader { + mock := &MockStreamsDownloader{ctrl: ctrl} + mock.recorder = &MockStreamsDownloaderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStreamsDownloader) EXPECT() *MockStreamsDownloaderMockRecorder { + return m.recorder +} + +// DownloadAuditLog mocks base method. +func (m *MockStreamsDownloader) DownloadAuditLog(arg0 *admin.DownloadStreamTenantAuditLogsApiParams) (io.ReadCloser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DownloadAuditLog", arg0) + ret0, _ := ret[0].(io.ReadCloser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DownloadAuditLog indicates an expected call of DownloadAuditLog. +func (mr *MockStreamsDownloaderMockRecorder) DownloadAuditLog(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadAuditLog", reflect.TypeOf((*MockStreamsDownloader)(nil).DownloadAuditLog), arg0) +} + // MockConnectionCreator is a mock of ConnectionCreator interface. type MockConnectionCreator struct { ctrl *gomock.Controller diff --git a/internal/store/streams.go b/internal/store/streams.go index a4d2e4b83f..0dec279749 100644 --- a/internal/store/streams.go +++ b/internal/store/streams.go @@ -15,10 +15,13 @@ package store import ( + "fmt" + "io" + atlasv2 "go.mongodb.org/atlas-sdk/v20231115008/admin" ) -//go:generate mockgen -destination=../mocks/mock_streams.go -package=mocks github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store StreamsLister,StreamsDescriber,StreamsCreator,StreamsDeleter,StreamsUpdater,ConnectionCreator,ConnectionDeleter,ConnectionUpdater,StreamsConnectionDescriber,StreamsConnectionLister +//go:generate mockgen -destination=../mocks/mock_streams.go -package=mocks github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store StreamsLister,StreamsDescriber,StreamsCreator,StreamsDeleter,StreamsUpdater,StreamsDownloader,ConnectionCreator,ConnectionDeleter,ConnectionUpdater,StreamsConnectionDescriber,StreamsConnectionLister type StreamsLister interface { ProjectStreams(*atlasv2.ListStreamInstancesApiParams) (*atlasv2.PaginatedApiStreamsTenant, error) @@ -40,6 +43,10 @@ type StreamsUpdater interface { UpdateStream(string, string, *atlasv2.StreamsDataProcessRegion) (*atlasv2.StreamsTenant, error) } +type StreamsDownloader interface { + DownloadAuditLog(*atlasv2.DownloadStreamTenantAuditLogsApiParams) (io.ReadCloser, error) +} + type StreamsConnectionLister interface { StreamsConnections(string, string) (*atlasv2.PaginatedApiStreamsConnection, error) } @@ -85,6 +92,17 @@ func (s *Store) UpdateStream(projectID, name string, streamsDataProcessRegion *a return result, err } +func (s *Store) DownloadAuditLog(request *atlasv2.DownloadStreamTenantAuditLogsApiParams) (io.ReadCloser, error) { + result, _, err := s.clientv2.StreamsApi.DownloadStreamTenantAuditLogsWithParams(s.ctx, request).Execute() + if err != nil { + return nil, err + } + if result == nil { + return nil, fmt.Errorf("returned file is empty") + } + return result, nil +} + // StreamsConnections encapsulates the logic to manage different cloud providers. func (s *Store) StreamsConnections(projectID, tenantName string) (*atlasv2.PaginatedApiStreamsConnection, error) { connections, _, err := s.clientv2.StreamsApi.ListStreamConnections(s.ctx, projectID, tenantName).Execute() From cdce9f932620daf5302b6d4c0936180e572d6a8d Mon Sep 17 00:00:00 2001 From: Nathan Smyth Date: Thu, 14 Mar 2024 07:06:52 +0000 Subject: [PATCH 02/12] fix: Make audit log unit test pass --- internal/cli/atlas/streams/instance/logs.go | 26 +++++++++++++++---- .../cli/atlas/streams/instance/logs_test.go | 21 ++++++++------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/internal/cli/atlas/streams/instance/logs.go b/internal/cli/atlas/streams/instance/logs.go index 663d71fde0..3c72e3183a 100644 --- a/internal/cli/atlas/streams/instance/logs.go +++ b/internal/cli/atlas/streams/instance/logs.go @@ -17,6 +17,7 @@ package instance import ( "context" "fmt" + "io" "slices" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli" @@ -27,6 +28,8 @@ import ( "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/usage" "github.com/spf13/afero" "github.com/spf13/cobra" + + atlasv2 "go.mongodb.org/atlas-sdk/v20231115008/admin" ) var downloadMessage = "Download of %s completed.\n" @@ -38,7 +41,6 @@ type DownloadOpts struct { fileName string start int64 end int64 - decompress bool store store.StreamsDownloader } @@ -58,13 +60,28 @@ func (opts *DownloadOpts) initDefaultOut() error { } func (opts *DownloadOpts) Run() error { - w, err := opts.NewWriteCloser() + params := atlasv2.DownloadStreamTenantAuditLogsApiParams{ + GroupId: opts.ProjectID, + TenantName: opts.tenantName, + StartDate: &opts.start, + EndDate: &opts.end, + } + + f, err := opts.store.DownloadAuditLog(¶ms) if err != nil { return err } - defer w.Close() - return nil + defer f.Close() + + out, err := opts.NewWriteCloser() + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, f) + return err } // DownloadBuilder @@ -109,7 +126,6 @@ func DownloadBuilder() *cobra.Command { cmd.Flags().Int64Var(&opts.start, flag.Start, 0, usage.LogStart) cmd.Flags().Int64Var(&opts.end, flag.End, 0, usage.LogEnd) cmd.Flags().BoolVar(&opts.Force, flag.Force, false, usage.ForceFile) - cmd.Flags().BoolVarP(&opts.decompress, flag.Decompress, flag.DecompressShort, false, usage.Decompress) cmd.Flags().StringVar(&opts.ProjectID, flag.ProjectID, "", usage.ProjectID) diff --git a/internal/cli/atlas/streams/instance/logs_test.go b/internal/cli/atlas/streams/instance/logs_test.go index d606f5f25b..f782087df4 100644 --- a/internal/cli/atlas/streams/instance/logs_test.go +++ b/internal/cli/atlas/streams/instance/logs_test.go @@ -23,10 +23,10 @@ import ( "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/flag" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mocks" - "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/test" "github.com/spf13/afero" "github.com/stretchr/testify/require" + atlasv2 "go.mongodb.org/atlas-sdk/v20231115008/admin" ) func TestDownloadOpts_Run(t *testing.T) { @@ -34,7 +34,8 @@ func TestDownloadOpts_Run(t *testing.T) { mockStore := mocks.NewMockStreamsDownloader(ctrl) const contents = "expected" - const fileName = "auditLogs.gz" + const projectID = "download-project-id" + const tenantName = "streams-tenant" file, err := os.CreateTemp("", "") if err != nil { @@ -52,22 +53,22 @@ func TestDownloadOpts_Run(t *testing.T) { downloadOpts := &DownloadOpts{ store: mockStore, DownloaderOpts: cli.DownloaderOpts{ - Out: fileName, + Out: "auditLogs.gz", Fs: fs, }, } - downloadOpts.ProjectID = "download-project-id" - downloadOpts.decompress = true - downloadOpts.fileName = fileName + downloadOpts.ProjectID = projectID + downloadOpts.tenantName = tenantName endDate := int64(0) startDate := int64(0) - downloadParams := new(store.DownloadStreamTenantAuditLogsApiParams) + downloadParams := new(atlasv2.DownloadStreamTenantAuditLogsApiParams) downloadParams.EndDate = &endDate downloadParams.StartDate = &startDate - downloadParams.TenantName = "streams-tenant" + downloadParams.GroupId = projectID + downloadParams.TenantName = tenantName mockStore. EXPECT(). @@ -79,7 +80,7 @@ func TestDownloadOpts_Run(t *testing.T) { t.Fatalf("Run() unexpected error: %v", err) } - of, _ := fs.Open(fileName) + of, _ := fs.Open("auditLogs.gz") defer of.Close() b, _ := io.ReadAll(of) require.Equal(t, contents, string(b)) @@ -90,6 +91,6 @@ func TestDownloadBuilder(t *testing.T) { t, DownloadBuilder(), 0, - []string{flag.Out, flag.Start, flag.End, flag.Force, flag.Decompress, flag.ProjectID}, + []string{flag.Out, flag.Start, flag.End, flag.Force, flag.ProjectID}, ) } From 176ae42eb7d07da5b3b763d3d6956031a9224ff6 Mon Sep 17 00:00:00 2001 From: Nathan Smyth Date: Thu, 14 Mar 2024 07:26:38 +0000 Subject: [PATCH 03/12] test: Integration tests for streams instance logs --- test/README.md | 1 + test/e2e/atlas/streams_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/test/README.md b/test/README.md index d83e17a9d2..6f01f4b203 100644 --- a/test/README.md +++ b/test/README.md @@ -193,6 +193,7 @@ | `streams instance describe` | Y | Y | `streams instance list` | Y | Y | `streams instance update` | Y | Y +| `streams instance log` | Y | Y | `config` | | | `completion` | Y | Y | `config delete` | Y | Y diff --git a/test/e2e/atlas/streams_test.go b/test/e2e/atlas/streams_test.go index d67f441351..3416753f43 100644 --- a/test/e2e/atlas/streams_test.go +++ b/test/e2e/atlas/streams_test.go @@ -20,7 +20,9 @@ import ( "fmt" "os" "os/exec" + "strconv" "testing" + "time" "github.com/mongodb/mongodb-atlas-cli/atlascli/test/e2e" "github.com/stretchr/testify/assert" @@ -92,6 +94,29 @@ func TestStreams(t *testing.T) { assert.Equal(t, instance.GetName(), instanceName) }) + t.Run("Downloading streams instance logs instance", func(t *testing.T) { + cmd := exec.Command(cliPath, + "streams", + "instance", + "logs", + instanceName, + "auditLogs.gz", + "--out", + "-", + "--start", + strconv.FormatInt(time.Now().Add(-10*time.Second).Unix(), 10), + "--end", + strconv.FormatInt(time.Now().Unix(), 10), + "--force", + "--projectId", + g.projectID, + ) + cmd.Env = os.Environ() + + resp, err := cmd.CombinedOutput() + require.NoError(t, err, string(resp)) + }) + t.Run("List all streams in the e2e project after creating", func(t *testing.T) { cmd := exec.Command(cliPath, "streams", From 5a7c8c36eda4e1220c40112b2d498fc42a9c85c4 Mon Sep 17 00:00:00 2001 From: Nathan Smyth Date: Fri, 15 Mar 2024 04:51:42 +0000 Subject: [PATCH 04/12] fix: Correct issue with start & end times --- internal/cli/atlas/streams/instance/logs.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/cli/atlas/streams/instance/logs.go b/internal/cli/atlas/streams/instance/logs.go index 3c72e3183a..e4ed3f8236 100644 --- a/internal/cli/atlas/streams/instance/logs.go +++ b/internal/cli/atlas/streams/instance/logs.go @@ -63,8 +63,14 @@ func (opts *DownloadOpts) Run() error { params := atlasv2.DownloadStreamTenantAuditLogsApiParams{ GroupId: opts.ProjectID, TenantName: opts.tenantName, - StartDate: &opts.start, - EndDate: &opts.end, + } + + if opts.start != 0 { + params.StartDate = &opts.start + } + + if opts.end != 0 { + params.EndDate = &opts.end } f, err := opts.store.DownloadAuditLog(¶ms) From f1ce37ea236d3443805bc7d40481f09255611218 Mon Sep 17 00:00:00 2001 From: Nathan Smyth Date: Fri, 15 Mar 2024 04:58:24 +0000 Subject: [PATCH 05/12] chore: Update docs --- docs/command/atlas-streams-instances-download.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/command/atlas-streams-instances-download.txt b/docs/command/atlas-streams-instances-download.txt index e24630ad7a..b273a5c6f4 100644 --- a/docs/command/atlas-streams-instances-download.txt +++ b/docs/command/atlas-streams-instances-download.txt @@ -57,10 +57,6 @@ Options - Type - Required - Description - * - -d, --decompress - - - - false - - Flag that indicates whether to decompress the log files. * - --end - int - false From a51b400bac1a88e0821ebe1b9dd362d972f919c1 Mon Sep 17 00:00:00 2001 From: Nathan Smyth Date: Fri, 15 Mar 2024 04:59:58 +0000 Subject: [PATCH 06/12] chore: Correct lint issue --- internal/cli/atlas/streams/instance/logs.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/cli/atlas/streams/instance/logs.go b/internal/cli/atlas/streams/instance/logs.go index e4ed3f8236..48539917bc 100644 --- a/internal/cli/atlas/streams/instance/logs.go +++ b/internal/cli/atlas/streams/instance/logs.go @@ -28,7 +28,6 @@ import ( "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/usage" "github.com/spf13/afero" "github.com/spf13/cobra" - atlasv2 "go.mongodb.org/atlas-sdk/v20231115008/admin" ) From 66e49677d27eec7a82b4f2c5ec3051f886f00969 Mon Sep 17 00:00:00 2001 From: Nathan Smyth Date: Fri, 15 Mar 2024 05:01:11 +0000 Subject: [PATCH 07/12] test: Correct unit test --- internal/cli/atlas/streams/instance/logs_test.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/internal/cli/atlas/streams/instance/logs_test.go b/internal/cli/atlas/streams/instance/logs_test.go index f782087df4..dc43668aa6 100644 --- a/internal/cli/atlas/streams/instance/logs_test.go +++ b/internal/cli/atlas/streams/instance/logs_test.go @@ -61,12 +61,9 @@ func TestDownloadOpts_Run(t *testing.T) { downloadOpts.ProjectID = projectID downloadOpts.tenantName = tenantName - endDate := int64(0) - startDate := int64(0) - downloadParams := new(atlasv2.DownloadStreamTenantAuditLogsApiParams) - downloadParams.EndDate = &endDate - downloadParams.StartDate = &startDate + downloadParams.EndDate = nil + downloadParams.StartDate = nil downloadParams.GroupId = projectID downloadParams.TenantName = tenantName From 972cb99f9150cf9899493b493b043db79edb36f6 Mon Sep 17 00:00:00 2001 From: Nathan Smyth Date: Fri, 15 Mar 2024 06:38:35 +0000 Subject: [PATCH 08/12] fix: Rename incorrect command --- internal/cli/atlas/streams/instance/logs.go | 2 +- test/e2e/atlas/streams_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/cli/atlas/streams/instance/logs.go b/internal/cli/atlas/streams/instance/logs.go index 48539917bc..a39c9ab36d 100644 --- a/internal/cli/atlas/streams/instance/logs.go +++ b/internal/cli/atlas/streams/instance/logs.go @@ -90,7 +90,7 @@ func (opts *DownloadOpts) Run() error { } // DownloadBuilder -// atlas streams logs [tenantName] audit.gz --projectId [projectID]. +// atlas streams download [tenantName] audit.gz --projectId [projectID]. func DownloadBuilder() *cobra.Command { const argsN = 2 opts := &DownloadOpts{} diff --git a/test/e2e/atlas/streams_test.go b/test/e2e/atlas/streams_test.go index 3416753f43..220f0a60cd 100644 --- a/test/e2e/atlas/streams_test.go +++ b/test/e2e/atlas/streams_test.go @@ -98,7 +98,7 @@ func TestStreams(t *testing.T) { cmd := exec.Command(cliPath, "streams", "instance", - "logs", + "download", instanceName, "auditLogs.gz", "--out", From d1442efacb3c2821becda9c62bf59c5f10ff5caf Mon Sep 17 00:00:00 2001 From: Nathan Smyth Date: Fri, 15 Mar 2024 06:39:10 +0000 Subject: [PATCH 09/12] chore: Rename command from logs to download --- internal/cli/atlas/streams/instance/{logs.go => download.go} | 0 .../cli/atlas/streams/instance/{logs_test.go => download_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename internal/cli/atlas/streams/instance/{logs.go => download.go} (100%) rename internal/cli/atlas/streams/instance/{logs_test.go => download_test.go} (100%) diff --git a/internal/cli/atlas/streams/instance/logs.go b/internal/cli/atlas/streams/instance/download.go similarity index 100% rename from internal/cli/atlas/streams/instance/logs.go rename to internal/cli/atlas/streams/instance/download.go diff --git a/internal/cli/atlas/streams/instance/logs_test.go b/internal/cli/atlas/streams/instance/download_test.go similarity index 100% rename from internal/cli/atlas/streams/instance/logs_test.go rename to internal/cli/atlas/streams/instance/download_test.go From f5871ec96900d52b4afc0cd277700c010c2ae2c7 Mon Sep 17 00:00:00 2001 From: Nathan Smyth Date: Mon, 18 Mar 2024 01:29:36 +0000 Subject: [PATCH 10/12] chore: Cleanup of download command --- .../atlas-streams-instances-download.txt | 10 ++---- .../cli/atlas/streams/instance/download.go | 34 +++++-------------- test/e2e/atlas/streams_test.go | 1 - 3 files changed, 11 insertions(+), 34 deletions(-) diff --git a/docs/command/atlas-streams-instances-download.txt b/docs/command/atlas-streams-instances-download.txt index b273a5c6f4..9ba635d552 100644 --- a/docs/command/atlas-streams-instances-download.txt +++ b/docs/command/atlas-streams-instances-download.txt @@ -22,7 +22,7 @@ Syntax .. code-block:: :caption: Command Syntax - atlas streams instances download [options] + atlas streams instances download [options] .. Code end marker, please don't delete this comment @@ -41,10 +41,6 @@ Arguments - string - true - Label that identifies the tenant that stores the log files that you want to download. - * - auditLogs.gz - - string - - true - - Log file that you want to return. Options ------- @@ -71,7 +67,7 @@ Options - help for download * - --out - string - - false + - true - Output file name. This value defaults to the log name. * - --projectId - string @@ -114,4 +110,4 @@ Examples .. code-block:: # Download the audit log file from the instance myProcessor for the project with the ID 5e2211c17a3e5a48f5497de3: - atlas streams instance logs myProcessor auditLogs.gz --projectId 5e2211c17a3e5a48f5497de3 + atlas streams instance download myProcessor --projectId 5e2211c17a3e5a48f5497de3 diff --git a/internal/cli/atlas/streams/instance/download.go b/internal/cli/atlas/streams/instance/download.go index a39c9ab36d..32ef4ae013 100644 --- a/internal/cli/atlas/streams/instance/download.go +++ b/internal/cli/atlas/streams/instance/download.go @@ -18,7 +18,6 @@ import ( "context" "fmt" "io" - "slices" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/require" @@ -37,7 +36,6 @@ type DownloadOpts struct { cli.GlobalOpts cli.DownloaderOpts tenantName string - fileName string start int64 end int64 store store.StreamsDownloader @@ -51,13 +49,6 @@ func (opts *DownloadOpts) initStore(ctx context.Context) func() error { } } -func (opts *DownloadOpts) initDefaultOut() error { - if opts.Out == "" { - opts.Out = opts.fileName - } - return nil -} - func (opts *DownloadOpts) Run() error { params := atlasv2.DownloadStreamTenantAuditLogsApiParams{ GroupId: opts.ProjectID, @@ -90,41 +81,31 @@ func (opts *DownloadOpts) Run() error { } // DownloadBuilder -// atlas streams download [tenantName] audit.gz --projectId [projectID]. +// atlas streams download [tenantName] --projectId [projectID]. func DownloadBuilder() *cobra.Command { - const argsN = 2 + const argsN = 1 opts := &DownloadOpts{} opts.Fs = afero.NewOsFs() cmd := &cobra.Command{ - Use: "download ", + Use: "download ", Short: "Download a compressed file that contains the logs for the specified Atlas Stream Processing instance.", Long: `This command downloads a file with a .gz extension.` + fmt.Sprintf(usage.RequiredRole, "Project Data Access Read/Write"), Args: cobra.MatchAll( require.ExactArgs(argsN), - func(cmd *cobra.Command, args []string) error { - if !slices.Contains(cmd.ValidArgs, args[1]) { - return fmt.Errorf(" must be one of %s", cmd.ValidArgs) - } - return nil - }, ), Example: ` # Download the audit log file from the instance myProcessor for the project with the ID 5e2211c17a3e5a48f5497de3: - atlas streams instance logs myProcessor auditLogs.gz --projectId 5e2211c17a3e5a48f5497de3`, + atlas streams instance download myProcessor --projectId 5e2211c17a3e5a48f5497de3`, Annotations: map[string]string{ - "tenantNameDesc": "Label that identifies the tenant that stores the log files that you want to download.", - "auditLogs.gzDesc": "Log file that you want to return.", - "output": downloadMessage, + "tenantNameDesc": "Label that identifies the tenant that stores the log files that you want to download.", + "output": downloadMessage, }, PreRunE: func(cmd *cobra.Command, args []string) error { opts.tenantName = args[0] - opts.fileName = args[1] - return opts.PreRunE(opts.ValidateProjectID, opts.initStore(cmd.Context()), opts.initDefaultOut) + return opts.PreRunE(opts.ValidateProjectID, opts.initStore(cmd.Context())) }, RunE: func(_ *cobra.Command, _ []string) error { return opts.Run() }, - - ValidArgs: []string{"auditLogs.gz"}, } cmd.Flags().StringVar(&opts.Out, flag.Out, "", usage.LogOut) @@ -134,6 +115,7 @@ func DownloadBuilder() *cobra.Command { cmd.Flags().StringVar(&opts.ProjectID, flag.ProjectID, "", usage.ProjectID) + _ = cmd.MarkFlagRequired(flag.Out) _ = cmd.MarkFlagFilename(flag.Out) return cmd diff --git a/test/e2e/atlas/streams_test.go b/test/e2e/atlas/streams_test.go index 220f0a60cd..d0b9d77d90 100644 --- a/test/e2e/atlas/streams_test.go +++ b/test/e2e/atlas/streams_test.go @@ -100,7 +100,6 @@ func TestStreams(t *testing.T) { "instance", "download", instanceName, - "auditLogs.gz", "--out", "-", "--start", From ef5d5bf7f2caebbd0a26f2400b961cf6e2088d43 Mon Sep 17 00:00:00 2001 From: Nathan Smyth Date: Tue, 19 Mar 2024 03:57:15 +0000 Subject: [PATCH 11/12] chore: Update docs to resolve spacing nit --- docs/command/atlas-streams-instances-download.txt | 2 +- internal/cli/atlas/streams/instance/download.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/command/atlas-streams-instances-download.txt b/docs/command/atlas-streams-instances-download.txt index 9ba635d552..b73e63eeac 100644 --- a/docs/command/atlas-streams-instances-download.txt +++ b/docs/command/atlas-streams-instances-download.txt @@ -14,7 +14,7 @@ atlas streams instances download Download a compressed file that contains the logs for the specified Atlas Stream Processing instance. -This command downloads a file with a .gz extension.To use this command, you must authenticate with a user account or an API key with the Project Data Access Read/Write role. +This command downloads a file with a .gz extension. To use this command, you must authenticate with a user account or an API key with the Project Data Access Read/Write role. Syntax ------ diff --git a/internal/cli/atlas/streams/instance/download.go b/internal/cli/atlas/streams/instance/download.go index 32ef4ae013..94e263601b 100644 --- a/internal/cli/atlas/streams/instance/download.go +++ b/internal/cli/atlas/streams/instance/download.go @@ -89,7 +89,7 @@ func DownloadBuilder() *cobra.Command { cmd := &cobra.Command{ Use: "download ", Short: "Download a compressed file that contains the logs for the specified Atlas Stream Processing instance.", - Long: `This command downloads a file with a .gz extension.` + fmt.Sprintf(usage.RequiredRole, "Project Data Access Read/Write"), + Long: `This command downloads a file with a .gz extension. ` + fmt.Sprintf(usage.RequiredRole, "Project Data Access Read/Write"), Args: cobra.MatchAll( require.ExactArgs(argsN), ), From 488dbf4edbab27ce57599e91979023adcfcd7d7f Mon Sep 17 00:00:00 2001 From: Nathan Smyth Date: Wed, 20 Mar 2024 07:56:37 +0000 Subject: [PATCH 12/12] chore: Respond to PR feedback --- internal/cli/atlas/streams/instance/download.go | 2 +- .../cli/atlas/streams/instance/download_test.go | 17 ++++------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/internal/cli/atlas/streams/instance/download.go b/internal/cli/atlas/streams/instance/download.go index 94e263601b..48306d09e8 100644 --- a/internal/cli/atlas/streams/instance/download.go +++ b/internal/cli/atlas/streams/instance/download.go @@ -51,7 +51,7 @@ func (opts *DownloadOpts) initStore(ctx context.Context) func() error { func (opts *DownloadOpts) Run() error { params := atlasv2.DownloadStreamTenantAuditLogsApiParams{ - GroupId: opts.ProjectID, + GroupId: opts.ConfigProjectID(), TenantName: opts.tenantName, } diff --git a/internal/cli/atlas/streams/instance/download_test.go b/internal/cli/atlas/streams/instance/download_test.go index dc43668aa6..4a5370e58e 100644 --- a/internal/cli/atlas/streams/instance/download_test.go +++ b/internal/cli/atlas/streams/instance/download_test.go @@ -16,7 +16,7 @@ package instance import ( "io" - "os" + "strings" "testing" "github.com/golang/mock/gomock" @@ -37,19 +37,8 @@ func TestDownloadOpts_Run(t *testing.T) { const projectID = "download-project-id" const tenantName = "streams-tenant" - file, err := os.CreateTemp("", "") - if err != nil { - require.NoError(t, err) - } - filename := file.Name() - defer os.Remove(filename) - _, _ = file.WriteString(contents) - _ = file.Close() - - expected, _ := os.Open(filename) - defer expected.Close() - fs := afero.NewMemMapFs() + downloadOpts := &DownloadOpts{ store: mockStore, DownloaderOpts: cli.DownloaderOpts{ @@ -67,6 +56,8 @@ func TestDownloadOpts_Run(t *testing.T) { downloadParams.GroupId = projectID downloadParams.TenantName = tenantName + expected := io.NopCloser(strings.NewReader(contents)) + mockStore. EXPECT(). DownloadAuditLog(downloadParams).