• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mongodb / mongodb-atlas-cli / 18092081670

29 Sep 2025 09:24AM UTC coverage: 64.114% (+0.009%) from 64.105%
18092081670

push

github

GitHub
CLOUDP-346977: Update Test Snapshots (#4236)

26233 of 40916 relevant lines covered (64.11%)

0.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

44.11
/internal/cli/deployments/setup.go
1
// Copyright 2023 MongoDB Inc
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
package deployments
16

17
import (
18
        "context"
19
        "errors"
20
        "fmt"
21
        "math/rand"
22
        "net"
23
        "os"
24
        "path/filepath"
25
        "slices"
26
        "strconv"
27
        "strings"
28
        "time"
29

30
        "github.com/AlecAivazis/survey/v2"
31
        "github.com/briandowns/spinner"
32
        "github.com/mongodb/atlas-cli-core/config"
33
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli"
34
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/deployments/options"
35
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/require"
36
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/setup"
37
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/workflows"
38
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/compass"
39
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/container"
40
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/flag"
41
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/log"
42
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mongodbclient"
43
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mongosh"
44
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/pointer"
45
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/search"
46
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/telemetry"
47
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/templatewriter"
48
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/usage"
49
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/vscode"
50
        "github.com/spf13/cobra"
51
)
52

53
const (
54
        internalMongodPort         = 27017
55
        mdb70                      = "7.0"
56
        mdb80                      = "8.0"
57
        mdb8                       = "8"
58
        mdb7                       = "7"
59
        defaultSettings            = "default"
60
        customSettings             = "custom"
61
        cancelSettings             = "cancel"
62
        skipConnect                = "skip"
63
        autoassignPort             = "autoassign"
64
        spinnerSpeed               = 100 * time.Millisecond
65
        steps                      = 3
66
        atlasM2                    = "M2"
67
        atlasM5                    = "M5"
68
        deprecateMessageSharedTier = "The '%s' tier is deprecated. For the migration guide and timeline, visit: https://dochub.mongodb.org/core/flex-migration.\n"
69
)
70

71
var (
72
        errInvalidInit              = errors.New("invalid --initdb flag")
73
        errInitMustBeDir            = fmt.Errorf("%w: must be a directory", errInvalidInit)
74
        errCancel                   = errors.New("the setup was cancelled")
75
        ErrDeploymentExists         = errors.New("deployment already exists")
76
        errMustBeInt                = errors.New("you must specify an integer")
77
        errPortOutOfRange           = errors.New("you must specify a port within the range 1..65535")
78
        errPortNotAvailable         = errors.New("the port is unavailable")
79
        errFlagTypeRequired         = fmt.Errorf("the --%s flag is required when the --%s flag is set", flag.TypeFlag, flag.Force)
80
        errFlagsTypeAndAuthRequired = fmt.Errorf("the --%s, --%s and --%s flags are required when the --%s and --%s flags are set",
81
                flag.TypeFlag, flag.Username, flag.Password, flag.Force, flag.BindIPAll)
82
        errInvalidDeploymentType      = errors.New("the deployment type is invalid")
83
        errIncompatibleDeploymentType = fmt.Errorf("the --%s flag applies only to LOCAL deployments", flag.BindIPAll)
84
        errInvalidMongoDBVersion      = errors.New("the mongodb version is invalid")
85
        errUnsupportedConnectWith     = fmt.Errorf("the --%s flag is unsupported", flag.ConnectWith)
86
        errFlagUsernameRequired       = fmt.Errorf("the --%s is required to enable authentication when --%s flag is set",
87
                flag.Username, flag.BindIPAll)
88
        errFailedToDownloadImage   = errors.New("failed to download the MongoDB image")
89
        errDeploymentUnhealthy     = errors.New("the deployment is unhealthy")
90
        errDeploymentNoHealthCheck = errors.New("the deployment does not have a healthcheck")
91
        errHealthCheckTimeout      = errors.New("timed out waiting for the deployment to be healthy")
92
        errDownloadImage           = errors.New("image download failed")
93
        errInspectHealthCheck      = errors.New("inspect healthcheck failed")
94
        errQueryHealthCheckStatus  = errors.New("query healthcheck status failed")
95
        errConfigContainer         = errors.New("container configuration failed")
96
        errRunContainer            = errors.New("container run failed")
97
        errListContainer           = errors.New("listing containers failed")
98
        settingOptions             = []string{defaultSettings, customSettings, cancelSettings}
99
        settingsDescription        = map[string]string{
100
                defaultSettings: "With default settings",
101
                customSettings:  "With custom settings",
102
                cancelSettings:  "Cancel setup",
103
        }
104
        connectWithOptions     = []string{options.MongoshConnect, options.CompassConnect, options.VsCodeConnect, skipConnect}
105
        connectWithDescription = map[string]string{
106
                options.MongoshConnect: "MongoDB Shell",
107
                options.CompassConnect: "MongoDB Compass",
108
                options.VsCodeConnect:  "MongoDB for VsCode",
109
                skipConnect:            "Skip Connection",
110
        }
111
        mdbVersions      = []string{mdb70, mdb80, mdb8, mdb7}
112
        mdbMajorVersions = []string{mdb7, mdb8}
113
)
114

115
type SetupOpts struct {
116
        options.DeploymentOpts
117
        cli.OutputOpts
118
        cli.ProjectOpts
119
        cli.InputOpts
120
        mongodbClient mongodbclient.MongoDBClient
121
        settings      string
122
        connectWith   string
123
        force         bool
124
        bindIPAll     bool
125
        mongodIP      string
126
        initdb        string
127
        s             *spinner.Spinner
128
        atlasSetup    *setup.Opts
129
}
130

131
func (opts *SetupOpts) initMongoDBClient() error {
×
132
        opts.mongodbClient = mongodbclient.NewClient()
×
133
        return nil
×
134
}
×
135

136
func (opts *SetupOpts) logStepStarted(msg string, currentStep int, totalSteps int) {
1✔
137
        fullMessage := fmt.Sprintf("%d/%d: %s", currentStep, totalSteps, msg)
1✔
138
        _, _ = log.Warningln(fullMessage)
1✔
139
        opts.start()
1✔
140
}
1✔
141

142
func (opts *SetupOpts) downloadImage(ctx context.Context, currentStep int, steps int) error {
1✔
143
        opts.logStepStarted("Downloading the latest MongoDB image to your local environment...", currentStep, steps)
1✔
144
        defer opts.stop()
1✔
145

1✔
146
        err := opts.ContainerEngine.ImagePull(ctx, opts.MongodDockerImageName())
1✔
147
        if err == nil {
2✔
148
                return nil
1✔
149
        }
1✔
150

151
        // In case we already have an image present and the download fails, we can continue with the existing image
152
        images, _ := opts.ContainerEngine.ImageList(ctx, opts.MongodDockerImageName())
1✔
153
        if len(images) != 0 {
2✔
154
                return nil
1✔
155
        }
1✔
156

157
        return errFailedToDownloadImage
1✔
158
}
159

160
func (opts *SetupOpts) startEnvironment(ctx context.Context, currentStep int, steps int) error {
1✔
161
        opts.logStepStarted("Starting your local environment...", currentStep, steps)
1✔
162
        defer opts.stop()
1✔
163

1✔
164
        containers, err := opts.GetLocalContainers(ctx)
1✔
165
        if err != nil {
1✔
166
                return fmt.Errorf("%w: %w", errListContainer, err)
×
167
        }
×
168

169
        return opts.validateLocalDeploymentsSettings(containers)
1✔
170
}
171

172
func (opts *SetupOpts) createLocalDeployment(ctx context.Context) error {
1✔
173
        currentStep := 1
1✔
174

1✔
175
        _, _ = log.Warningf("Creating your cluster %s\n", opts.DeploymentName)
1✔
176

1✔
177
        // verify that the host meets the minimum requirements, if not, print a warning
1✔
178
        if err := opts.ValidateMinimumRequirements(); err != nil {
1✔
179
                return err
×
180
        }
×
181

182
        // containers check
183
        if err := opts.startEnvironment(ctx, currentStep, steps); err != nil {
2✔
184
                return err
1✔
185
        }
1✔
186
        currentStep++
1✔
187

1✔
188
        // always download the latest image
1✔
189
        if err := opts.downloadImage(ctx, currentStep, steps); err != nil {
2✔
190
                return fmt.Errorf("%w: %w", errDownloadImage, err)
1✔
191
        }
1✔
192
        currentStep++
1✔
193

1✔
194
        // create local deployment
1✔
195
        opts.logStepStarted(fmt.Sprintf("Creating your deployment %s...", opts.DeploymentName), currentStep, steps)
1✔
196
        defer opts.stop()
1✔
197

1✔
198
        if err := opts.configureContainer(ctx); err != nil {
2✔
199
                return fmt.Errorf("%w: %w", errConfigContainer, err)
1✔
200
        }
1✔
201

202
        return nil
1✔
203
}
204

205
func (opts *SetupOpts) configureContainer(ctx context.Context) error {
1✔
206
        envVars := map[string]string{
1✔
207
                "TOOL": "ATLASCLI",
1✔
208
        }
1✔
209

1✔
210
        if log.IsDebugLevel() {
2✔
211
                envVars["RUNNER_LOG_FILE"] = "/dev/stdout"
1✔
212
        }
1✔
213

214
        if !config.TelemetryEnabled() {
1✔
215
                envVars["DO_NOT_TRACK"] = "1"
×
216
        }
×
217

218
        if opts.IsAuthEnabled() {
1✔
219
                envVars["MONGODB_INITDB_ROOT_USERNAME"] = opts.DBUsername
×
220
                envVars["MONGODB_INITDB_ROOT_PASSWORD"] = opts.DBUserPassword
×
221
        }
×
222

223
        flags := container.RunFlags{}
1✔
224
        flags.Detach = pointer.Get(true)
1✔
225
        flags.Name = pointer.Get(opts.LocalMongodHostname())
1✔
226
        flags.Hostname = pointer.Get(opts.LocalMongodHostname())
1✔
227
        flags.Env = envVars
1✔
228
        flags.BindIPAll = &opts.bindIPAll
1✔
229
        flags.IP = &opts.mongodIP
1✔
230
        flags.Ports = []container.PortMapping{{HostPort: opts.Port, ContainerPort: internalMongodPort}}
1✔
231
        if opts.initdb != "" {
1✔
232
                flags.Volumes = []container.VolumeMapping{
×
233
                        {
×
234
                                HostPath:      opts.initdb,
×
235
                                ContainerPath: "/docker-entrypoint-initdb.d",
×
236
                        },
×
237
                }
×
238
        }
×
239
        healthCheck, err := opts.ContainerEngine.ImageHealthCheck(ctx, opts.MongodDockerImageName())
1✔
240
        if err != nil {
1✔
241
                return fmt.Errorf("%w: %w", errInspectHealthCheck, err)
×
242
        }
×
243

244
        // Temporary fix until https://github.com/containers/podman/issues/18904 is closed
245
        if healthCheck == nil {
1✔
246
                const HealthcheckInterval = 30
×
247
                const HealthStartPeriod = 1
×
248
                const HealthTimeout = 30
×
249
                const HealthRetries = 3
×
250

×
251
                flags.HealthCmd = &[]string{"/usr/local/bin/runner", "healthcheck"}
×
252
                flags.HealthInterval = pointer.Get(HealthcheckInterval * time.Second)
×
253
                flags.HealthStartPeriod = pointer.Get(HealthStartPeriod * time.Second)
×
254
                flags.HealthTimeout = pointer.Get(HealthTimeout * time.Second)
×
255
                flags.HealthRetries = pointer.Get(HealthRetries)
×
256
        }
×
257

258
        _, err = opts.ContainerEngine.ContainerRun(ctx, opts.MongodDockerImageName(), &flags)
1✔
259
        if err != nil {
1✔
260
                return fmt.Errorf("%w: %w", errRunContainer, err)
×
261
        }
×
262

263
        // This can be a high number because the container will become unhealthy before the 10 minutes is reached
264
        const healthyDeploymentTimeout = 10 * time.Minute
1✔
265

1✔
266
        err = opts.WaitForHealthyDeployment(ctx, healthyDeploymentTimeout)
1✔
267
        if err != nil && log.IsDebugLevel() {
2✔
268
                logs, err := opts.ContainerEngine.ContainerLogs(ctx, opts.LocalMongodHostname())
1✔
269
                if err != nil {
1✔
270
                        _, _ = log.Debugf("Container unhealthy (hostname=%s), failed to get container logs : %s\n", opts.LocalMongodHostname(), err)
×
271
                } else {
1✔
272
                        _, _ = log.Debugf("Container unhealthy (hostname=%s), container logs:\n", opts.LocalMongodHostname())
1✔
273
                        for _, logLine := range logs {
2✔
274
                                _, _ = log.Debugln(logLine)
1✔
275
                        }
1✔
276
                }
277
        }
278

279
        return err
1✔
280
}
281

282
func (opts *SetupOpts) WaitForHealthyDeployment(ctx context.Context, duration time.Duration) error {
1✔
283
        start := time.Now()
1✔
284

1✔
285
        for {
2✔
286
                if time.Since(start) > duration {
1✔
287
                        return errHealthCheckTimeout
×
288
                }
×
289

290
                status, err := opts.ContainerEngine.ContainerHealthStatus(ctx, opts.LocalMongodHostname())
1✔
291
                if err != nil {
1✔
292
                        return fmt.Errorf("%w: %w", errQueryHealthCheckStatus, err)
×
293
                }
×
294

295
                switch status {
1✔
296
                case container.DockerHealthcheckStatusHealthy:
1✔
297
                        return nil
1✔
298
                case container.DockerHealthcheckStatusUnhealthy:
1✔
299
                        return errDeploymentUnhealthy
1✔
300
                case container.DockerHealthcheckStatusNone:
×
301
                        return errDeploymentNoHealthCheck
×
302
                case container.DockerHealthcheckStatusStarting:
×
303
                        time.Sleep(1 * time.Second)
×
304
                }
305
        }
306
}
307

308
func (opts *SetupOpts) validateLocalDeploymentsSettings(containers []container.Container) error {
1✔
309
        mongodContainerName := opts.LocalMongodHostname()
1✔
310
        for _, c := range containers {
2✔
311
                for _, n := range c.Names {
2✔
312
                        if n == mongodContainerName {
2✔
313
                                return fmt.Errorf("%w: \"%s\", state:\"%s\"", ErrDeploymentExists, opts.DeploymentName, c.State)
1✔
314
                        }
1✔
315
                }
316
        }
317

318
        return nil
1✔
319
}
320

321
func (opts *SetupOpts) promptSettings() error {
×
322
        p := &survey.Select{
×
323
                Message: "How do you want to set up your local Atlas deployment?",
×
324
                Options: settingOptions,
×
325
                Default: opts.settings,
×
326
                Description: func(value string, _ int) string {
×
327
                        return settingsDescription[value]
×
328
                },
×
329
        }
330

331
        return telemetry.TrackAskOne(p, &opts.settings, nil)
×
332
}
333

334
func (opts *SetupOpts) generateDeploymentName() {
×
335
        opts.DeploymentName = fmt.Sprintf("local%v", rand.Intn(10000)) //nolint // no need for crypto here
×
336
}
×
337

338
func (opts *SetupOpts) promptDeploymentName() error {
×
339
        p := &survey.Input{
×
340
                Message: "Deployment Name [You can't change this value later]",
×
341
                Help:    usage.ClusterName,
×
342
                Default: opts.DeploymentName,
×
343
        }
×
344

×
345
        return telemetry.TrackAskOne(p, &opts.DeploymentName, survey.WithValidator(func(ans any) error {
×
346
                name, _ := ans.(string)
×
347
                return options.ValidateDeploymentName(name)
×
348
        }))
×
349
}
350

351
func (opts *SetupOpts) promptMdbVersion() error {
×
352
        p := &survey.Select{
×
353
                Message: "Major MongoDB Version (latest minor version will be used)",
×
354
                Options: mdbMajorVersions,
×
355
                Default: opts.MdbVersion,
×
356
                Help:    "Major MongoDB Version for the deployment. Atlas CLI applies the latest minor version available.",
×
357
        }
×
358

×
359
        return telemetry.TrackAskOne(p, &opts.MdbVersion, nil)
×
360
}
×
361

362
func checkPort(p int) error {
×
363
        server, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", p))
×
364
        if err != nil {
×
365
                return fmt.Errorf("%w: %d", errPortNotAvailable, p)
×
366
        }
×
367
        _ = server.Close()
×
368
        return nil
×
369
}
370

371
func validatePort(p int) error {
×
372
        if p <= 0 || p > 65535 {
×
373
                return errPortOutOfRange
×
374
        }
×
375
        return checkPort(p)
×
376
}
377

378
func (opts *SetupOpts) promptPort() error {
×
379
        exportPort := autoassignPort
×
380
        if opts.Port != 0 {
×
381
                exportPort = strconv.Itoa(opts.Port)
×
382
        }
×
383

384
        p := &survey.Input{
×
385
                Message: "Specify a port",
×
386
                Default: exportPort,
×
387
        }
×
388

×
389
        err := telemetry.TrackAskOne(p, &exportPort, survey.WithValidator(func(ans any) error {
×
390
                input, _ := ans.(string)
×
391
                if input == autoassignPort {
×
392
                        return nil
×
393
                }
×
394
                value, err := strconv.Atoi(input)
×
395
                if err != nil {
×
396
                        return errMustBeInt
×
397
                }
×
398

399
                return validatePort(value)
×
400
        }))
401

402
        if err != nil {
×
403
                return err
×
404
        }
×
405

406
        if exportPort != autoassignPort {
×
407
                if opts.Port, err = strconv.Atoi(exportPort); err != nil {
×
408
                        return err
×
409
                }
×
410
        }
411

412
        return nil
×
413
}
414

415
func (opts *SetupOpts) validateDeploymentTypeFlag() error {
1✔
416
        if opts.DeploymentType == "" && opts.force {
1✔
417
                return errFlagTypeRequired
×
418
        }
×
419

420
        if !opts.IsLocalDeploymentType() && opts.bindIPAll {
1✔
421
                return errIncompatibleDeploymentType
×
422
        }
×
423

424
        if opts.DeploymentType != "" && !strings.EqualFold(opts.DeploymentType, options.AtlasCluster) && !strings.EqualFold(opts.DeploymentType, options.LocalCluster) {
1✔
425
                return fmt.Errorf("%w: %s", errInvalidDeploymentType, opts.DeploymentType)
×
426
        }
×
427

428
        return nil
1✔
429
}
430

431
func (opts *SetupOpts) validateBindIPAllFlag() error {
1✔
432
        if !opts.bindIPAll {
2✔
433
                return nil
1✔
434
        }
1✔
435

436
        if opts.force && (opts.DeploymentType == "" || opts.DBUsername == "" || opts.DBUserPassword == "") {
×
437
                return errFlagsTypeAndAuthRequired
×
438
        }
×
439

440
        if opts.DBUsername == "" {
×
441
                return errFlagUsernameRequired
×
442
        }
×
443

444
        return nil
×
445
}
446

447
func (opts *SetupOpts) validateFlags() error {
1✔
448
        if err := opts.validateDeploymentTypeFlag(); err != nil {
1✔
449
                return err
×
450
        }
×
451

452
        if opts.DeploymentName != "" {
2✔
453
                if err := options.ValidateDeploymentName(opts.DeploymentName); err != nil {
1✔
454
                        return err
×
455
                }
×
456
        }
457

458
        if opts.MdbVersion != "" && !slices.Contains(mdbVersions, opts.MdbVersion) {
2✔
459
                return fmt.Errorf("%w: %s", errInvalidMongoDBVersion, opts.MdbVersion)
1✔
460
        }
1✔
461

462
        if opts.Port != 0 {
1✔
463
                if err := validatePort(opts.Port); err != nil {
×
464
                        return err
×
465
                }
×
466
        }
467

468
        if opts.connectWith != "" && !search.StringInSliceFold(connectWithOptions, opts.connectWith) {
1✔
469
                return fmt.Errorf("%w: %s", errUnsupportedConnectWith, opts.connectWith)
×
470
        }
×
471

472
        if opts.initdb != "" {
1✔
473
                info, err := os.Stat(opts.initdb)
×
474
                if err != nil {
×
475
                        return fmt.Errorf("%w: %w", errInvalidInit, err)
×
476
                }
×
477
                if !info.IsDir() {
×
478
                        return errInitMustBeDir
×
479
                }
×
480
                opts.initdb, err = filepath.Abs(opts.initdb)
×
481
                if err != nil {
×
482
                        return fmt.Errorf("%w: %w", errInvalidInit, err)
×
483
                }
×
484
        }
485

486
        return opts.validateBindIPAllFlag()
1✔
487
}
488

489
func (opts *SetupOpts) promptLocalAdminPassword() error {
×
490
        if !opts.IsTerminalInput() {
×
491
                _, err := fmt.Fscanln(opts.InReader, &opts.DBUserPassword)
×
492
                return err
×
493
        }
×
494

495
        p := &survey.Password{
×
496
                Message: "Password for authenticating to local deployment",
×
497
        }
×
498
        return telemetry.TrackAskOne(p, &opts.DBUserPassword)
×
499
}
500

501
func (opts *SetupOpts) setDefaultSettings() error {
1✔
502
        opts.settings = defaultSettings
1✔
503
        defaultValuesSet := false
1✔
504

1✔
505
        if opts.DeploymentName == "" {
1✔
506
                opts.generateDeploymentName()
×
507
                defaultValuesSet = true
×
508
        }
×
509

510
        if opts.MdbVersion == "" {
2✔
511
                opts.MdbVersion = mdb8
1✔
512
                defaultValuesSet = true
1✔
513
        }
1✔
514

515
        if opts.Port == 0 {
2✔
516
                defaultValuesSet = true
1✔
517
        }
1✔
518

519
        if defaultValuesSet {
2✔
520
                if err := templatewriter.Print(os.Stderr, `
1✔
521
[Default Settings]
1✔
522
Deployment Name        {{.DeploymentName}}
1✔
523
MongoDB Major Version        {{.MdbVersion}} (latest minor version)
1✔
524

1✔
525
`, opts); err != nil {
1✔
526
                        return err
×
527
                }
×
528
                if !opts.force {
1✔
529
                        if err := opts.promptSettings(); err != nil {
×
530
                                return err
×
531
                        }
×
532
                }
533
        }
534

535
        return nil
1✔
536
}
537

538
func (opts *SetupOpts) promptConnect() error {
×
539
        p := &survey.Select{
×
540
                Message: fmt.Sprintf("How would you like to connect to %s?", opts.DeploymentName),
×
541
                Options: connectWithOptions,
×
542
                Description: func(value string, _ int) string {
×
543
                        return connectWithDescription[value]
×
544
                },
×
545
        }
546

547
        return telemetry.TrackAskOne(p, &opts.connectWith, nil)
×
548
}
549

550
func (opts *SetupOpts) runConnectWith(cs string) error {
1✔
551
        if opts.connectWith == "" {
2✔
552
                if opts.force {
2✔
553
                        opts.connectWith = skipConnect
1✔
554
                } else {
1✔
555
                        if err := opts.promptConnect(); err != nil {
×
556
                                return err
×
557
                        }
×
558
                }
559
        }
560

561
        switch opts.connectWith {
1✔
562
        case skipConnect:
1✔
563
                _, _ = fmt.Fprintln(os.Stderr, "connection skipped")
1✔
564
        case options.CompassConnect:
×
565
                if !compass.Detect() {
×
566
                        return compass.ErrCompassNotInstalled
×
567
                }
×
568
                if _, err := log.Warningln("Launching MongoDB Compass..."); err != nil {
×
569
                        return err
×
570
                }
×
571
                return compass.Run("", "", cs)
×
572
        case options.MongoshConnect:
×
573
                if !mongosh.Detect() {
×
574
                        return mongosh.ErrMongoshNotInstalled
×
575
                }
×
576
                return mongosh.Run("", "", cs)
×
577
        case options.VsCodeConnect:
×
578
                if !vscode.Detect() {
×
579
                        return vscode.ErrVsCodeCliNotInstalled
×
580
                }
×
581
                if _, err := log.Warningln("Launching VsCode..."); err != nil {
×
582
                        return err
×
583
                }
×
584
                return vscode.SaveConnection(cs, opts.DeploymentName, opts.DeploymentType)
×
585
        }
586

587
        return nil
1✔
588
}
589

590
func (opts *SetupOpts) validateAndPrompt() error {
1✔
591
        if err := opts.validateFlags(); err != nil {
1✔
592
                return err
×
593
        }
×
594

595
        if err := opts.ValidateAndPromptDeploymentType(); err != nil {
1✔
596
                return err
×
597
        }
×
598

599
        // Defer prompts to Atlas command
600
        if opts.IsAtlasDeploymentType() {
1✔
601
                return nil
×
602
        }
×
603

604
        if opts.DBUsername != "" && opts.DBUserPassword == "" {
1✔
605
                if err := opts.promptLocalAdminPassword(); err != nil {
×
606
                        return err
×
607
                }
×
608
        }
609

610
        if err := opts.setDefaultSettings(); err != nil {
1✔
611
                return err
×
612
        }
×
613

614
        switch opts.settings {
1✔
615
        case cancelSettings:
×
616
                return errCancel
×
617
        case customSettings:
×
618
                if err := opts.promptDeploymentName(); err != nil {
×
619
                        return err
×
620
                }
×
621

622
                if err := opts.promptMdbVersion(); err != nil {
×
623
                        return err
×
624
                }
×
625

626
                if err := opts.promptPort(); err != nil {
×
627
                        return err
×
628
                }
×
629
        }
630

631
        return nil
1✔
632
}
633

634
func (opts *SetupOpts) runLocal(ctx context.Context) error {
1✔
635
        if err := opts.LocalDeploymentPreRun(ctx); err != nil {
1✔
636
                return err
×
637
        }
×
638

639
        if err := opts.createLocalDeployment(ctx); err != nil {
2✔
640
                // in case the deployment already exists we shouldn't delete it
1✔
641
                if !errors.Is(err, ErrDeploymentExists) {
2✔
642
                        _ = opts.RemoveLocal(ctx)
1✔
643
                }
1✔
644
                return err
1✔
645
        }
646

647
        cs, err := opts.ConnectionString(ctx)
1✔
648
        if err != nil {
1✔
649
                return err
×
650
        }
×
651

652
        _, _ = log.Warningln("Deployment created!")
1✔
653
        _, _ = fmt.Fprintf(opts.OutWriter, `Connection string: "%s"
1✔
654
`, cs)
1✔
655
        _, _ = log.Warningln("")
1✔
656

1✔
657
        return opts.runConnectWith(cs)
1✔
658
}
659

660
func (opts *SetupOpts) runAtlas(ctx context.Context) error {
×
661
        s := setup.Builder()
×
662

×
663
        // remove global flags and unknown flags
×
664
        var newArgs []string
×
665
        _, _ = log.Debugf("Removing flags and args from original args %s\n", os.Args)
×
666

×
667
        flagstoRemove := map[string]string{
×
668
                flag.TypeFlag: "1",
×
669
        }
×
670

×
671
        newArgs, err := workflows.RemoveFlagsAndArgs(flagstoRemove, map[string]bool{opts.DeploymentName: true}, os.Args)
×
672
        if err != nil {
×
673
                return err
×
674
        }
×
675

676
        // replace deployment name with cluster name
677
        if opts.DeploymentName != "" {
×
678
                newArgs = append(newArgs, "--"+flag.ClusterName, opts.DeploymentName)
×
679
        }
×
680

681
        // update args
682
        s.SetArgs(newArgs)
×
683

×
684
        // run atlas setup
×
685
        _, _ = log.Debugf("Starting to run atlas setup with args %s\n", newArgs)
×
686
        _, err = s.ExecuteContextC(ctx)
×
687
        return err
×
688
}
689

690
func (opts *SetupOpts) Run(ctx context.Context) error {
1✔
691
        if err := opts.validateAndPrompt(); err != nil {
1✔
692
                if errors.Is(err, errCancel) {
×
693
                        _, _ = log.Warningln(err)
×
694
                        return nil
×
695
                }
×
696

697
                return err
×
698
        }
699

700
        if strings.EqualFold(options.LocalCluster, opts.DeploymentType) {
2✔
701
                return opts.runLocal(ctx)
1✔
702
        }
1✔
703

704
        return opts.runAtlas(ctx)
×
705
}
706

707
func (opts *SetupOpts) PostRun() {
1✔
708
        opts.DeploymentTelemetry.AppendDeploymentType()
1✔
709
        opts.DeploymentTelemetry.AppendDeploymentUUID()
1✔
710
}
1✔
711

712
func (opts *SetupOpts) validateTier() error {
×
713
        opts.atlasSetup.Tier = strings.ToUpper(opts.atlasSetup.Tier)
×
714
        if opts.atlasSetup.Tier == atlasM2 || opts.atlasSetup.Tier == atlasM5 {
×
715
                _, _ = fmt.Fprintf(os.Stderr, deprecateMessageSharedTier, opts.atlasSetup.Tier)
×
716
        }
×
717
        return nil
×
718
}
719

720
// SetupBuilder builds a cobra.Command that can run as:
721
// atlas deployments setup.
722
func SetupBuilder() *cobra.Command {
1✔
723
        opts := &SetupOpts{
1✔
724
                settings:   defaultSettings,
1✔
725
                atlasSetup: &setup.Opts{},
1✔
726
        }
1✔
727
        cmd := &cobra.Command{
1✔
728
                Use:     "setup [deploymentName]",
1✔
729
                Short:   "Create a local deployment.",
1✔
730
                Long:    "To learn more about local atlas deployments, see https://www.mongodb.com/docs/atlas/cli/current/atlas-cli-deploy-local/",
1✔
731
                Args:    require.MaximumNArgs(1),
1✔
732
                GroupID: "all",
1✔
733
                Annotations: map[string]string{
1✔
734
                        "deploymentNameDesc": "Name of the deployment that you want to set up.",
1✔
735
                },
1✔
736
                PreRunE: func(cmd *cobra.Command, args []string) error {
1✔
737
                        if len(args) == 1 {
×
738
                                opts.DeploymentName = args[0]
×
739
                        }
×
740

741
                        opts.force = opts.atlasSetup.Confirm
×
742
                        opts.DBUsername = opts.atlasSetup.DBUsername
×
743
                        opts.DBUserPassword = opts.atlasSetup.DBUserPassword
×
744

×
745
                        return opts.PreRunE(
×
746
                                opts.validateTier,
×
747
                                opts.InitOutput(cmd.OutOrStdout(), ""),
×
748
                                opts.InitInput(cmd.InOrStdin()),
×
749
                                opts.InitStore(cmd.Context(), cmd.OutOrStdout()),
×
750
                                opts.initMongoDBClient,
×
751
                        )
×
752
                },
753
                RunE: func(cmd *cobra.Command, _ []string) error {
×
754
                        return opts.Run(cmd.Context())
×
755
                },
×
756
                PostRun: func(_ *cobra.Command, _ []string) {
×
757
                        opts.PostRun()
×
758
                },
×
759
        }
760

761
        // Local and Atlas
762
        cmd.Flags().StringVar(&opts.DeploymentType, flag.TypeFlag, "", usage.DeploymentTypeSetup)
1✔
763
        cmd.Flags().StringVar(&opts.MdbVersion, flag.MDBVersion, "", usage.DeploymentMDBVersion)
1✔
764
        cmd.Flags().StringVar(&opts.connectWith, flag.ConnectWith, "", usage.ConnectWithSetup)
1✔
765

1✔
766
        // Local only
1✔
767
        cmd.Flags().IntVar(&opts.Port, flag.Port, 0, usage.MongodPort)
1✔
768
        cmd.Flags().BoolVar(&opts.bindIPAll, flag.BindIPAll, false, usage.BindIPAll)
1✔
769
        cmd.Flags().StringVar(&opts.initdb, flag.InitDB, "", usage.InitDB)
1✔
770

1✔
771
        // Atlas only
1✔
772
        opts.atlasSetup.SetupAtlasFlags(cmd)
1✔
773
        opts.atlasSetup.SetupFlowFlags(cmd)
1✔
774
        cmd.Flags().Lookup(flag.Region).Usage = usage.DeploymentRegion
1✔
775
        cmd.Flags().Lookup(flag.Tag).Usage = usage.DeploymentTag
1✔
776
        cmd.Flags().Lookup(flag.Tier).Usage = usage.DeploymentTier
1✔
777
        cmd.Flags().Lookup(flag.EnableTerminationProtection).Usage = usage.EnableTerminationProtectionForDeployment
1✔
778
        cmd.Flags().Lookup(flag.SkipSampleData).Usage = usage.SkipSampleDataDeployment
1✔
779
        cmd.Flags().Lookup(flag.Force).Usage = usage.ForceDeploymentsSetup
1✔
780

1✔
781
        _ = cmd.RegisterFlagCompletionFunc(flag.MDBVersion, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
1✔
782
                return mdbVersions, cobra.ShellCompDirectiveDefault
×
783
        })
×
784
        _ = cmd.RegisterFlagCompletionFunc(flag.TypeFlag, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
1✔
785
                return options.DeploymentTypeOptions, cobra.ShellCompDirectiveDefault
×
786
        })
×
787
        _ = cmd.RegisterFlagCompletionFunc(flag.ConnectWith, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
1✔
788
                return connectWithOptions, cobra.ShellCompDirectiveDefault
×
789
        })
×
790
        return cmd
1✔
791
}
792

793
func (opts *SetupOpts) start() {
1✔
794
        if opts.IsTerminal() {
1✔
795
                opts.s = spinner.New(spinner.CharSets[9], spinnerSpeed)
×
796
                _ = opts.s.Color("cyan", "bold")
×
797
                opts.s.Start()
×
798
        }
×
799
}
800

801
func (opts *SetupOpts) stop() {
1✔
802
        if opts.IsTerminal() {
1✔
803
                opts.s.Stop()
×
804
        }
×
805
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc