From 3bfd384a54d6f36842786e67c2acd790e6e31d0b Mon Sep 17 00:00:00 2001 From: Jeroen Vervaeke <9132134+jeroenvervaeke@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:00:07 +0100 Subject: [PATCH] CLOUDP-239577: [AtlasCLI] Move deployment connect opts into connect command (#3109) --- internal/cli/deployments/connect.go | 185 +++++++++++++++++- internal/cli/deployments/connect_test.go | 9 +- .../cli/deployments/options/connect_opts.go | 116 ----------- .../deployments/options/connect_opts_atlas.go | 97 --------- .../deployments/options/connect_opts_local.go | 27 --- 5 files changed, 185 insertions(+), 249 deletions(-) delete mode 100644 internal/cli/deployments/options/connect_opts.go delete mode 100644 internal/cli/deployments/options/connect_opts_atlas.go delete mode 100644 internal/cli/deployments/options/connect_opts_local.go diff --git a/internal/cli/deployments/connect.go b/internal/cli/deployments/connect.go index 8a3aa3ef2f..7c03c4ab11 100644 --- a/internal/cli/deployments/connect.go +++ b/internal/cli/deployments/connect.go @@ -16,25 +16,45 @@ package deployments import ( "context" + "errors" + "fmt" + "github.com/AlecAivazis/survey/v2" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/deployments/options" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/require" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/compass" + "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/log" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mongosh" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/search" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/telemetry" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/usage" "github.com/spf13/cobra" ) -func Run(ctx context.Context, opts *options.ConnectOpts) error { +var ( + ConnectionStringTypeStandard = "standard" + connectionStringTypePrivate = "private" + connectionStringTypeOptions = []string{ConnectionStringTypeStandard, connectionStringTypePrivate} + errConnectionStringTypeNotImplemented = errors.New("connection string type not implemented") + errNetworkPeeringConnectionNotConfigured = errors.New("network peering connection is not configured for this deployment") + promptConnectionStringType = "What type of connection string type would you like to use?" +) + +func Run(ctx context.Context, opts *ConnectOpts) error { return opts.Connect(ctx) } -func PostRun(opts *options.ConnectOpts) { +func PostRun(opts *ConnectOpts) { opts.DeploymentTelemetry.AppendDeploymentType() } // atlas deployments connect [clusterName]. func ConnectBuilder() *cobra.Command { - opts := &options.ConnectOpts{} + opts := &ConnectOpts{} cmd := &cobra.Command{ Use: "connect [deploymentName]", Short: "Connect to a deployment that is running locally or in Atlas. If the deployment is paused, make sure to run atlas deployments start first.", @@ -67,7 +87,7 @@ func ConnectBuilder() *cobra.Command { cmd.Flags().StringVar(&opts.DeploymentType, flag.TypeFlag, "", usage.DeploymentType) cmd.Flags().StringVar(&opts.DeploymentOpts.DBUsername, flag.Username, "", usage.DBUsername) cmd.Flags().StringVar(&opts.DeploymentOpts.DBUserPassword, flag.Password, "", usage.Password) - cmd.Flags().StringVar(&opts.ConnectionStringType, flag.ConnectionStringType, options.ConnectionStringTypeStandard, usage.ConnectionStringType) + cmd.Flags().StringVar(&opts.ConnectionStringType, flag.ConnectionStringType, ConnectionStringTypeStandard, usage.ConnectionStringType) _ = cmd.RegisterFlagCompletionFunc(flag.ConnectWith, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { return options.ConnectWithOptions, cobra.ShellCompDirectiveDefault @@ -78,3 +98,160 @@ func ConnectBuilder() *cobra.Command { return cmd } + +type ConnectOpts struct { + cli.OutputOpts + options.DeploymentOpts + ConnectWith string + ConnectToAtlasOpts +} + +func (opts *ConnectOpts) Connect(ctx context.Context) error { + if _, err := opts.SelectDeployments(ctx, opts.ConfigProjectID()); err != nil { + return err + } + + if err := opts.askConnectWith(); err != nil { + return err + } + + if opts.IsAtlasDeploymentType() { + if err := opts.validateAndPromptAtlasOpts(); err != nil { + return err + } + + return opts.connectToAtlas() + } + + return opts.connectToLocal(ctx) +} + +func (opts *ConnectOpts) askConnectWith() error { + if opts.ConnectWith == "" { + var err error + if opts.ConnectWith, err = opts.DeploymentOpts.PromptConnectWith(); err != nil { + return err + } + } + + return options.ValidateConnectWith(opts.ConnectWith) +} + +func (opts *ConnectOpts) connectToDeployment(connectionString string) error { + switch opts.ConnectWith { + case options.ConnectWithConnectionString: + return opts.Print(connectionString) + case options.CompassConnect: + if !compass.Detect() { + return options.ErrCompassNotInstalled + } + if _, err := log.Warningln("Launching MongoDB Compass..."); err != nil { + return err + } + return compass.Run(opts.DeploymentOpts.DBUsername, opts.DeploymentOpts.DBUserPassword, connectionString) + case options.MongoshConnect: + if !mongosh.Detect() { + return options.ErrMongoshNotInstalled + } + return mongosh.Run(opts.DeploymentOpts.DBUsername, opts.DeploymentOpts.DBUserPassword, connectionString) + } + + return nil +} + +func (opts *ConnectOpts) promptDBUsername() error { + p := &survey.Input{ + Message: "Username for authenticating to MongoDB deployment", + } + return telemetry.TrackAskOne(p, &opts.DeploymentOpts.DBUsername) +} + +func (opts *ConnectOpts) promptDBUserPassword() error { + if !opts.IsTerminalInput() { + _, err := fmt.Fscanln(opts.InReader, &opts.DeploymentOpts.DBUserPassword) + return err + } + + p := &survey.Password{ + Message: "Password for authenticating to MongoDB deployment", + } + return telemetry.TrackAskOne(p, &opts.DeploymentOpts.DBUserPassword) +} + +func (opts *ConnectOpts) connectToLocal(ctx context.Context) error { + connectionString, err := opts.ConnectionString(ctx) + if err != nil { + return err + } + + return opts.connectToDeployment(connectionString) +} + +type ConnectToAtlasOpts struct { + cli.GlobalOpts + cli.InputOpts + ConnectionStringType string + Store store.ClusterDescriber +} + +func (opts *ConnectToAtlasOpts) InitAtlasStore(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 *ConnectOpts) validateAndPromptAtlasOpts() error { + requiresAuth := opts.ConnectWith == options.MongoshConnect || opts.ConnectWith == options.CompassConnect + if requiresAuth && opts.DeploymentOpts.DBUsername == "" { + if err := opts.promptDBUsername(); err != nil { + return err + } + } + + if requiresAuth && opts.DeploymentOpts.DBUserPassword == "" { + if err := opts.promptDBUserPassword(); err != nil { + return err + } + } + + return opts.validateAndPromptConnectionStringType() +} + +func (opts *ConnectToAtlasOpts) validateAndPromptConnectionStringType() error { + if opts.ConnectionStringType == "" { + p := &survey.Select{ + Message: promptConnectionStringType, + Options: connectionStringTypeOptions, + Help: usage.ConnectionStringType, + } + + err := telemetry.TrackAskOne(p, &opts.ConnectionStringType, nil) + if err != nil { + return err + } + } + + if !search.StringInSliceFold(connectionStringTypeOptions, opts.ConnectionStringType) { + return fmt.Errorf("%w: %s", errConnectionStringTypeNotImplemented, opts.ConnectionStringType) + } + + return nil +} + +func (opts *ConnectOpts) connectToAtlas() error { + r, err := opts.Store.AtlasCluster(opts.ConfigProjectID(), opts.DeploymentName) + if err != nil { + return err + } + + if opts.ConnectionStringType == connectionStringTypePrivate { + if r.ConnectionStrings.PrivateSrv == nil { + return errNetworkPeeringConnectionNotConfigured + } + return opts.connectToDeployment(*r.ConnectionStrings.PrivateSrv) + } + + return opts.connectToDeployment(*r.ConnectionStrings.StandardSrv) +} diff --git a/internal/cli/deployments/connect_test.go b/internal/cli/deployments/connect_test.go index 68cdd75232..891025876e 100644 --- a/internal/cli/deployments/connect_test.go +++ b/internal/cli/deployments/connect_test.go @@ -23,7 +23,6 @@ import ( "github.com/golang/mock/gomock" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli" - "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/deployments/options" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/deployments/test/fixture" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/container" "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/flag" @@ -45,7 +44,7 @@ func TestRun_ConnectLocal(t *testing.T) { buf := new(bytes.Buffer) deploymenTest := fixture.NewMockLocalDeploymentOpts(ctrl, expectedLocalDeployment) - connectOpts := &options.ConnectOpts{ + connectOpts := &ConnectOpts{ ConnectWith: "connectionString", DeploymentOpts: *deploymenTest.Opts, OutputOpts: cli.OutputOpts{ @@ -96,10 +95,10 @@ func TestRun_ConnectAtlas(t *testing.T) { mockAtlasClusterDescriber := mocks.NewMockClusterDescriber(ctrl) deploymenTest := fixture.NewMockAtlasDeploymentOpts(ctrl, expectedAtlasDeployment) - connectOpts := &options.ConnectOpts{ + connectOpts := &ConnectOpts{ ConnectWith: "connectionString", DeploymentOpts: *deploymenTest.Opts, - ConnectToAtlasOpts: options.ConnectToAtlasOpts{ + ConnectToAtlasOpts: ConnectToAtlasOpts{ Store: mockAtlasClusterDescriber, GlobalOpts: cli.GlobalOpts{ ProjectID: "projectID", @@ -147,7 +146,7 @@ func TestPostRun(t *testing.T) { deploymentsTest := fixture.NewMockLocalDeploymentOpts(ctrl, "localDeployment") buf := new(bytes.Buffer) - opts := &options.ConnectOpts{ + opts := &ConnectOpts{ DeploymentOpts: *deploymentsTest.Opts, OutputOpts: cli.OutputOpts{ OutWriter: buf, diff --git a/internal/cli/deployments/options/connect_opts.go b/internal/cli/deployments/options/connect_opts.go deleted file mode 100644 index b56effc094..0000000000 --- a/internal/cli/deployments/options/connect_opts.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2023 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 options - -import ( - "context" - "errors" - "fmt" - - "github.com/AlecAivazis/survey/v2" - "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli" - "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/compass" - "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/log" - "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mongosh" - "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/telemetry" -) - -var ( - ConnectionStringTypeStandard = "standard" - connectionStringTypePrivate = "private" - connectionStringTypeOptions = []string{ConnectionStringTypeStandard, connectionStringTypePrivate} - errConnectionStringTypeNotImplemented = errors.New("connection string type not implemented") - errNetworkPeeringConnectionNotConfigured = errors.New("network peering connection is not configured for this deployment") - promptConnectionStringType = "What type of connection string type would you like to use?" -) - -type ConnectOpts struct { - cli.OutputOpts - DeploymentOpts - ConnectWith string - ConnectToAtlasOpts -} - -func (opts *ConnectOpts) Connect(ctx context.Context) error { - if _, err := opts.SelectDeployments(ctx, opts.ConfigProjectID()); err != nil { - return err - } - - if err := opts.askConnectWith(); err != nil { - return err - } - - if opts.IsAtlasDeploymentType() { - if err := opts.validateAndPromptAtlasOpts(); err != nil { - return err - } - - return opts.connectToAtlas() - } - - return opts.connectToLocal(ctx) -} - -func (opts *ConnectOpts) askConnectWith() error { - if opts.ConnectWith == "" { - var err error - if opts.ConnectWith, err = opts.DeploymentOpts.PromptConnectWith(); err != nil { - return err - } - } - - return ValidateConnectWith(opts.ConnectWith) -} - -func (opts *ConnectOpts) connectToDeployment(connectionString string) error { - switch opts.ConnectWith { - case ConnectWithConnectionString: - return opts.Print(connectionString) - case CompassConnect: - if !compass.Detect() { - return ErrCompassNotInstalled - } - if _, err := log.Warningln("Launching MongoDB Compass..."); err != nil { - return err - } - return compass.Run(opts.DeploymentOpts.DBUsername, opts.DeploymentOpts.DBUserPassword, connectionString) - case MongoshConnect: - if !mongosh.Detect() { - return ErrMongoshNotInstalled - } - return mongosh.Run(opts.DeploymentOpts.DBUsername, opts.DeploymentOpts.DBUserPassword, connectionString) - } - - return nil -} - -func (opts *ConnectOpts) promptDBUsername() error { - p := &survey.Input{ - Message: "Username for authenticating to MongoDB deployment", - } - return telemetry.TrackAskOne(p, &opts.DeploymentOpts.DBUsername) -} - -func (opts *ConnectOpts) promptDBUserPassword() error { - if !opts.IsTerminalInput() { - _, err := fmt.Fscanln(opts.InReader, &opts.DeploymentOpts.DBUserPassword) - return err - } - - p := &survey.Password{ - Message: "Password for authenticating to MongoDB deployment", - } - return telemetry.TrackAskOne(p, &opts.DeploymentOpts.DBUserPassword) -} diff --git a/internal/cli/deployments/options/connect_opts_atlas.go b/internal/cli/deployments/options/connect_opts_atlas.go deleted file mode 100644 index 4306e02eae..0000000000 --- a/internal/cli/deployments/options/connect_opts_atlas.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2023 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 options - -import ( - "context" - "fmt" - - "github.com/AlecAivazis/survey/v2" - "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli" - "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/config" - "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/search" - "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store" - "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/telemetry" - "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/usage" -) - -type ConnectToAtlasOpts struct { - cli.GlobalOpts - cli.InputOpts - ConnectionStringType string - Store store.ClusterDescriber -} - -func (opts *ConnectToAtlasOpts) InitAtlasStore(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 *ConnectOpts) validateAndPromptAtlasOpts() error { - requiresAuth := opts.ConnectWith == MongoshConnect || opts.ConnectWith == CompassConnect - if requiresAuth && opts.DeploymentOpts.DBUsername == "" { - if err := opts.promptDBUsername(); err != nil { - return err - } - } - - if requiresAuth && opts.DeploymentOpts.DBUserPassword == "" { - if err := opts.promptDBUserPassword(); err != nil { - return err - } - } - - return opts.validateAndPromptConnectionStringType() -} - -func (opts *ConnectToAtlasOpts) validateAndPromptConnectionStringType() error { - if opts.ConnectionStringType == "" { - p := &survey.Select{ - Message: promptConnectionStringType, - Options: connectionStringTypeOptions, - Help: usage.ConnectionStringType, - } - - err := telemetry.TrackAskOne(p, &opts.ConnectionStringType, nil) - if err != nil { - return err - } - } - - if !search.StringInSliceFold(connectionStringTypeOptions, opts.ConnectionStringType) { - return fmt.Errorf("%w: %s", errConnectionStringTypeNotImplemented, opts.ConnectionStringType) - } - - return nil -} - -func (opts *ConnectOpts) connectToAtlas() error { - r, err := opts.Store.AtlasCluster(opts.ConfigProjectID(), opts.DeploymentName) - if err != nil { - return err - } - - if opts.ConnectionStringType == connectionStringTypePrivate { - if r.ConnectionStrings.PrivateSrv == nil { - return errNetworkPeeringConnectionNotConfigured - } - return opts.connectToDeployment(*r.ConnectionStrings.PrivateSrv) - } - - return opts.connectToDeployment(*r.ConnectionStrings.StandardSrv) -} diff --git a/internal/cli/deployments/options/connect_opts_local.go b/internal/cli/deployments/options/connect_opts_local.go deleted file mode 100644 index 7b726b4d32..0000000000 --- a/internal/cli/deployments/options/connect_opts_local.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2023 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 options - -import ( - "context" -) - -func (opts *ConnectOpts) connectToLocal(ctx context.Context) error { - connectionString, err := opts.ConnectionString(ctx) - if err != nil { - return err - } - - return opts.connectToDeployment(connectionString) -}