diff --git a/docs/command/atlas-streams-privateLinks-list.txt b/docs/command/atlas-streams-privateLinks-list.txt new file mode 100644 index 0000000000..064f1e9cf7 --- /dev/null +++ b/docs/command/atlas-streams-privateLinks-list.txt @@ -0,0 +1,88 @@ +.. _atlas-streams-privateLinks-list: + +=============================== +atlas streams privateLinks list +=============================== + +.. default-domain:: mongodb + +.. contents:: On this page + :local: + :backlinks: none + :depth: 1 + :class: singlecol + +Lists the PrivateLink endpoints in the project that can be used as Atlas Stream Processor connections. + +To use this command, you must authenticate with a user account or an API key with any of the following roles: Project Owner, Project Stream Processing Owner. + +Syntax +------ + +.. code-block:: + :caption: Command Syntax + + atlas streams privateLinks list [options] + +.. Code end marker, please don't delete this comment + +Options +------- + +.. list-table:: + :header-rows: 1 + :widths: 20 10 10 60 + + * - Name + - Type + - Required + - Description + * - -h, --help + - + - false + - help for list + * - -o, --output + - string + - false + - Output format. Valid values are json, json-path, go-template, or go-template-file. To see the full output, use the -o json option. + * - --projectId + - string + - false + - Hexadecimal string that identifies the project to use. This option overrides the settings in the configuration file or environment variable. + +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:: + + ID PROVIDER REGION VENDOR STATE INTERFACE_ENDPOINT_ID SERVICE_ENDPOINT_ID DNS_DOMAIN DNS_SUBDOMAIN + + + +Examples +-------- + +.. code-block:: + :copyable: false + + # list PrivateLink endpoints for Atlas Stream Processing: + atlas streams privateLink list + diff --git a/docs/command/atlas-streams-privateLinks.txt b/docs/command/atlas-streams-privateLinks.txt index d6a9d98b8b..21a0c1c18d 100644 --- a/docs/command/atlas-streams-privateLinks.txt +++ b/docs/command/atlas-streams-privateLinks.txt @@ -53,6 +53,7 @@ Related Commands * :ref:`atlas-streams-privateLinks-create` - Creates a PrivateLink endpoint that can be used as an Atlas Stream Processor connection. * :ref:`atlas-streams-privateLinks-describe` - Describes a PrivateLink endpoint that can be used as an Atlas Stream Processor connection. +* :ref:`atlas-streams-privateLinks-list` - Lists the PrivateLink endpoints in the project that can be used as Atlas Stream Processor connections. .. toctree:: @@ -60,4 +61,5 @@ Related Commands create describe + list diff --git a/internal/cli/events/list_test.go b/internal/cli/events/list_test.go index 4bdc5d5e63..6a6c722c1c 100644 --- a/internal/cli/events/list_test.go +++ b/internal/cli/events/list_test.go @@ -17,7 +17,6 @@ package events import ( - "strings" "testing" "github.com/golang/mock/gomock" @@ -124,7 +123,7 @@ func TestList_Run(t *testing.T) { if err == nil { t.Fatal("Run() expected an error, but got none") } - assert.True(t, strings.Contains(err.Error(), "parsing time")) + assert.Contains(t, err.Error(), "parsing time") }) t.Run("for an project with invalid dates", func(t *testing.T) { listOpts := &ListOpts{ @@ -140,7 +139,7 @@ func TestList_Run(t *testing.T) { if err == nil { t.Fatal("Run() expected an error, but got none") } - assert.True(t, strings.Contains(err.Error(), "parsing time")) + assert.Contains(t, err.Error(), "parsing time") }) } @@ -158,6 +157,6 @@ func TestParseDate(t *testing.T) { if err == nil { t.Fatalf("expected error from parseDate() but got none") } - assert.True(t, strings.Contains(err.Error(), "parsing time")) + assert.Contains(t, err.Error(), "parsing time") }) } diff --git a/internal/cli/events/projects_list_test.go b/internal/cli/events/projects_list_test.go index 03b5874ef5..42455b581b 100644 --- a/internal/cli/events/projects_list_test.go +++ b/internal/cli/events/projects_list_test.go @@ -17,7 +17,6 @@ package events import ( - "strings" "testing" "github.com/golang/mock/gomock" @@ -87,5 +86,5 @@ func Test_projectListOpts_Run_WithInvalidDate(t *testing.T) { if err == nil { t.Fatal("Run() expected error") } - assert.True(t, strings.Contains(err.Error(), "parsing time")) + assert.Contains(t, err.Error(), "parsing time") } diff --git a/internal/cli/streams/privatelink/describe_test.go b/internal/cli/streams/privatelink/describe_test.go index 58c7a9dc42..e8c1150e48 100644 --- a/internal/cli/streams/privatelink/describe_test.go +++ b/internal/cli/streams/privatelink/describe_test.go @@ -23,7 +23,7 @@ import ( "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mocks" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/test" "github.com/stretchr/testify/require" - atlasv2 "go.mongodb.org/atlas-sdk/v20241113004/admin" + atlasv2 "go.mongodb.org/atlas-sdk/v20241113005/admin" ) func TestDescribeOpts_Run(t *testing.T) { diff --git a/internal/cli/streams/privatelink/list.go b/internal/cli/streams/privatelink/list.go new file mode 100644 index 0000000000..b0039ab511 --- /dev/null +++ b/internal/cli/streams/privatelink/list.go @@ -0,0 +1,87 @@ +// Copyright 2025 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 privatelink + +import ( + "context" + "fmt" + + "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/store" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/usage" + "github.com/spf13/cobra" +) + +var listTemplate = `ID PROVIDER REGION VENDOR STATE INTERFACE_ENDPOINT_ID SERVICE_ENDPOINT_ID DNS_DOMAIN DNS_SUBDOMAIN{{range valueOrEmptySlice .Results}} +{{.Id}} {{.Provider}} {{.Region}} {{.Vendor}} {{.State}} {{.InterfaceEndpointId}} {{.ServiceEndpointId}} {{.DnsDomain}} {{.DnsSubDomain}}{{end}} +` + +type ListOpts struct { + cli.ProjectOpts + cli.OutputOpts + store store.PrivateLinkLister +} + +func (opts *ListOpts) Run() error { + result, err := opts.store.ListPrivateLinkEndpoints(opts.ConfigProjectID()) + if err != nil { + return err + } + + return opts.Print(result) +} + +func (opts *ListOpts) 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 + } +} + +// atlas streams privateLink list +// List the PrivateLink endpoints in the project that can be used as Atlas Stream Processor connections. +func ListBuilder() *cobra.Command { + opts := &ListOpts{} + cmd := &cobra.Command{ + Use: "list", + Short: "Lists the PrivateLink endpoints in the project that can be used as Atlas Stream Processor connections.", + Long: fmt.Sprintf(usage.RequiredOneOfRoles, commandRoles), + Args: require.NoArgs, + Annotations: map[string]string{ + "output": listTemplate, + }, + Example: `# list PrivateLink endpoints for Atlas Stream Processing: + atlas streams privateLink list +`, + PreRunE: func(cmd *cobra.Command, _ []string) error { + return opts.PreRunE( + opts.ValidateProjectID, + opts.initStore(cmd.Context()), + opts.InitOutput(cmd.OutOrStdout(), createTemplate), + ) + }, + RunE: func(_ *cobra.Command, _ []string) error { + return opts.Run() + }, + } + + opts.AddProjectOptsFlags(cmd) + opts.AddOutputOptFlags(cmd) + + return cmd +} diff --git a/internal/cli/streams/privatelink/list_test.go b/internal/cli/streams/privatelink/list_test.go new file mode 100644 index 0000000000..e836bfc5dd --- /dev/null +++ b/internal/cli/streams/privatelink/list_test.go @@ -0,0 +1,72 @@ +// Copyright 2025 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 privatelink + +import ( + "bytes" + "fmt" + "testing" + + "github.com/golang/mock/gomock" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mocks" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/test" + "github.com/stretchr/testify/require" + atlasv2 "go.mongodb.org/atlas-sdk/v20241113005/admin" +) + +func getPrivateLinkConnections() []atlasv2.StreamsPrivateLinkConnection { + var connections []atlasv2.StreamsPrivateLinkConnection + + for i := 0; i < 5; i++ { + conn := atlasv2.NewStreamsPrivateLinkConnection() + conn.SetId(fmt.Sprintf("testId%d", i)) + conn.SetProvider("Azure") + conn.SetRegion("US_EAST_2") + conn.SetServiceEndpointId("/subscriptions/fd01adff-b37e-4693-8497-83ecf183a145/resourceGroups/test-rg/providers/Microsoft.EventHub/namespaces/test-namespace") + conn.SetDnsDomain("test-namespace.servicebus.windows.net") + + connections = append(connections, *conn) + } + + return connections +} + +func TestListOpts_Run(t *testing.T) { + ctrl := gomock.NewController(t) + mockStore := mocks.NewMockPrivateLinkLister(ctrl) + + buf := new(bytes.Buffer) + listOpts := &ListOpts{ + store: mockStore, + OutputOpts: cli.OutputOpts{ + Template: listTemplate, + OutWriter: buf, + }, + } + + connections := getPrivateLinkConnections() + expected := atlasv2.NewPaginatedApiStreamsPrivateLink() + expected.SetResults(connections) + + mockStore. + EXPECT(). + ListPrivateLinkEndpoints(gomock.Eq(listOpts.ConfigProjectID())). + Return(expected, nil). + Times(1) + + require.NoError(t, listOpts.Run()) + test.VerifyOutputTemplate(t, listTemplate, expected) +} diff --git a/internal/cli/streams/privatelink/privatelink.go b/internal/cli/streams/privatelink/privatelink.go index 8eb61cb6ac..421ab9d8c9 100644 --- a/internal/cli/streams/privatelink/privatelink.go +++ b/internal/cli/streams/privatelink/privatelink.go @@ -34,6 +34,7 @@ func Builder() *cobra.Command { cmd.AddCommand( CreateBuilder(), + ListBuilder(), DescribeBuilder(), ) diff --git a/internal/mocks/mock_streams.go b/internal/mocks/mock_streams.go index b727b51363..40f9025669 100644 --- a/internal/mocks/mock_streams.go +++ b/internal/mocks/mock_streams.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store (interfaces: StreamsLister,StreamsDescriber,StreamsCreator,StreamsDeleter,StreamsUpdater,StreamsDownloader,ConnectionCreator,ConnectionDeleter,ConnectionUpdater,StreamsConnectionDescriber,StreamsConnectionLister,PrivateLinkCreator,PrivateLinkDescriber) +// Source: github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store (interfaces: StreamsLister,StreamsDescriber,StreamsCreator,StreamsDeleter,StreamsUpdater,StreamsDownloader,ConnectionCreator,ConnectionDeleter,ConnectionUpdater,StreamsConnectionDescriber,StreamsConnectionLister,PrivateLinkCreator,PrivateLinkLister,PrivateLinkDescriber) // Package mocks is a generated GoMock package. package mocks @@ -466,6 +466,44 @@ func (mr *MockPrivateLinkCreatorMockRecorder) CreatePrivateLinkEndpoint(arg0, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePrivateLinkEndpoint", reflect.TypeOf((*MockPrivateLinkCreator)(nil).CreatePrivateLinkEndpoint), arg0, arg1) } +// MockPrivateLinkLister is a mock of PrivateLinkLister interface. +type MockPrivateLinkLister struct { + ctrl *gomock.Controller + recorder *MockPrivateLinkListerMockRecorder +} + +// MockPrivateLinkListerMockRecorder is the mock recorder for MockPrivateLinkLister. +type MockPrivateLinkListerMockRecorder struct { + mock *MockPrivateLinkLister +} + +// NewMockPrivateLinkLister creates a new mock instance. +func NewMockPrivateLinkLister(ctrl *gomock.Controller) *MockPrivateLinkLister { + mock := &MockPrivateLinkLister{ctrl: ctrl} + mock.recorder = &MockPrivateLinkListerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPrivateLinkLister) EXPECT() *MockPrivateLinkListerMockRecorder { + return m.recorder +} + +// ListPrivateLinkEndpoints mocks base method. +func (m *MockPrivateLinkLister) ListPrivateLinkEndpoints(arg0 string) (*admin.PaginatedApiStreamsPrivateLink, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListPrivateLinkEndpoints", arg0) + ret0, _ := ret[0].(*admin.PaginatedApiStreamsPrivateLink) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListPrivateLinkEndpoints indicates an expected call of ListPrivateLinkEndpoints. +func (mr *MockPrivateLinkListerMockRecorder) ListPrivateLinkEndpoints(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPrivateLinkEndpoints", reflect.TypeOf((*MockPrivateLinkLister)(nil).ListPrivateLinkEndpoints), arg0) +} + // MockPrivateLinkDescriber is a mock of PrivateLinkDescriber interface. type MockPrivateLinkDescriber struct { ctrl *gomock.Controller diff --git a/internal/store/streams.go b/internal/store/streams.go index 652c01f2ec..cdfe5fdda9 100644 --- a/internal/store/streams.go +++ b/internal/store/streams.go @@ -21,7 +21,7 @@ import ( atlasv2 "go.mongodb.org/atlas-sdk/v20241113005/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,StreamsDownloader,ConnectionCreator,ConnectionDeleter,ConnectionUpdater,StreamsConnectionDescriber,StreamsConnectionLister,PrivateLinkCreator,PrivateLinkDescriber +//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,PrivateLinkCreator,PrivateLinkLister,PrivateLinkDescriber type StreamsLister interface { ProjectStreams(*atlasv2.ListStreamInstancesApiParams) (*atlasv2.PaginatedApiStreamsTenant, error) @@ -71,6 +71,10 @@ type PrivateLinkCreator interface { CreatePrivateLinkEndpoint(projectID string, connection *atlasv2.StreamsPrivateLinkConnection) (*atlasv2.StreamsPrivateLinkConnection, error) } +type PrivateLinkLister interface { + ListPrivateLinkEndpoints(projectID string) (*atlasv2.PaginatedApiStreamsPrivateLink, error) +} + type PrivateLinkDescriber interface { DescribePrivateLinkEndpoint(projectID, connectionID string) (*atlasv2.StreamsPrivateLinkConnection, error) } @@ -146,6 +150,11 @@ func (s *Store) CreatePrivateLinkEndpoint(projectID string, connection *atlasv2. return result, err } +func (s *Store) ListPrivateLinkEndpoints(projectID string) (*atlasv2.PaginatedApiStreamsPrivateLink, error) { + result, _, err := s.clientv2.StreamsApi.ListPrivateLinkConnections(s.ctx, projectID).Execute() + return result, err +} + func (s *Store) DescribePrivateLinkEndpoint(projectID, connectionID string) (*atlasv2.StreamsPrivateLinkConnection, error) { result, _, err := s.clientv2.StreamsApi.GetPrivateLinkConnection(s.ctx, projectID, connectionID).Execute() return result, err diff --git a/test/README.md b/test/README.md index dadde5bac6..ba7e35fbba 100644 --- a/test/README.md +++ b/test/README.md @@ -195,6 +195,7 @@ | `streams instance log` | Y | Y | | `streams privateLink` | | | | `streams privateLink create` | Y | Y | +| `streams privateLink list` | Y | Y | | `streams privateLink describe` | Y | Y | | `config` | | | | `completion` | Y | Y | diff --git a/test/e2e/atlas/auditing_test.go b/test/e2e/atlas/auditing_test.go index a7def3012d..e630d40298 100644 --- a/test/e2e/atlas/auditing_test.go +++ b/test/e2e/atlas/auditing_test.go @@ -63,7 +63,7 @@ func TestAuditing(t *testing.T) { require.NoError(t, json.Unmarshal(resp, &setting), string(resp)) assert.True(t, *setting.Enabled) assert.True(t, *setting.AuditAuthorizationSuccess) - assert.Equal(t, "{\"atype\": \"authenticate\"}", *setting.AuditFilter) + assert.JSONEq(t, "{\"atype\": \"authenticate\"}", *setting.AuditFilter) }) t.Run("Update via file", func(t *testing.T) { diff --git a/test/e2e/atlas/streams_test.go b/test/e2e/atlas/streams_test.go index 1e4d255466..ea75b0f360 100644 --- a/test/e2e/atlas/streams_test.go +++ b/test/e2e/atlas/streams_test.go @@ -252,6 +252,31 @@ func TestStreams(t *testing.T) { a.Equal("test-namespace.servicebus.windows.net", privateLinkEndpoint.GetDnsDomain()) }) + t.Run("List all streams privateLink endpoints", func(t *testing.T) { + streamsCmd := exec.Command(cliPath, + "streams", + "privateLinks", + "list", + "-o=json", + "--projectId", + g.projectID, + ) + + streamsCmd.Env = os.Environ() + streamsResp, err := e2e.RunAndGetStdOut(streamsCmd) + req.NoError(err, string(streamsResp)) + + var privateLinkEndpoints atlasv2.PaginatedApiStreamsPrivateLink + req.NoError(json.Unmarshal(streamsResp, &privateLinkEndpoints)) + + a := assert.New(t) + a.Len(privateLinkEndpoints.GetResults(), 1) + a.Equal("AZURE", privateLinkEndpoints.GetResults()[0].GetProvider()) + a.Equal("US_EAST_2", privateLinkEndpoints.GetResults()[0].GetRegion()) + a.Equal("/subscriptions/fd01adff-b37e-4693-8497-83ecf183a145/resourceGroups/test-rg/providers/Microsoft.EventHub/namespaces/test-namespace", privateLinkEndpoints.GetResults()[0].GetServiceEndpointId()) + a.Equal("test-namespace.servicebus.windows.net", privateLinkEndpoints.GetResults()[0].GetDnsDomain()) + }) + // Connections t.Run("Creating a streams connection", func(t *testing.T) { cmd := exec.Command(cliPath,