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

mongodb / mongodb-atlas-cli / 16670272475

01 Aug 2025 08:27AM UTC coverage: 57.953% (-7.1%) from 65.017%
16670272475

Pull #4071

github

fmenezes
lint
Pull Request #4071: chore: remove unit tag from tests

23613 of 40745 relevant lines covered (57.95%)

2.74 hits per line

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

7.22
/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/mongodb-atlas-cli/atlascli/internal/cli"
33
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/deployments/options"
34
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/require"
35
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/setup"
36
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/workflows"
37
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/compass"
38
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/config"
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
        mdb7                       = "7.0"
56
        mdb8                       = "8.0"
57
        defaultSettings            = "default"
58
        customSettings             = "custom"
59
        cancelSettings             = "cancel"
60
        skipConnect                = "skip"
61
        autoassignPort             = "autoassign"
62
        spinnerSpeed               = 100 * time.Millisecond
63
        steps                      = 3
64
        atlasM2                    = "M2"
65
        atlasM5                    = "M5"
66
        deprecateMessageSharedTier = "The '%s' tier is deprecated. For the migration guide and timeline, visit: https://dochub.mongodb.org/core/flex-migration.\n"
67
)
68

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

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

128
func (opts *SetupOpts) initMongoDBClient() error {
×
129
        opts.mongodbClient = mongodbclient.NewClient()
×
130
        return nil
×
131
}
×
132

133
func (opts *SetupOpts) logStepStarted(msg string, currentStep int, totalSteps int) {
×
134
        fullMessage := fmt.Sprintf("%d/%d: %s", currentStep, totalSteps, msg)
×
135
        _, _ = log.Warningln(fullMessage)
×
136
        opts.start()
×
137
}
×
138

139
func (opts *SetupOpts) downloadImage(ctx context.Context, currentStep int, steps int) error {
×
140
        opts.logStepStarted("Downloading the latest MongoDB image to your local environment...", currentStep, steps)
×
141
        defer opts.stop()
×
142

×
143
        err := opts.ContainerEngine.ImagePull(ctx, opts.MongodDockerImageName())
×
144
        if err == nil {
×
145
                return nil
×
146
        }
×
147

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

154
        return errFailedToDownloadImage
×
155
}
156

157
func (opts *SetupOpts) startEnvironment(ctx context.Context, currentStep int, steps int) error {
×
158
        opts.logStepStarted("Starting your local environment...", currentStep, steps)
×
159
        defer opts.stop()
×
160

×
161
        containers, err := opts.GetLocalContainers(ctx)
×
162
        if err != nil {
×
163
                return fmt.Errorf("%w: %w", errListContainer, err)
×
164
        }
×
165

166
        return opts.validateLocalDeploymentsSettings(containers)
×
167
}
168

169
func (opts *SetupOpts) createLocalDeployment(ctx context.Context) error {
×
170
        currentStep := 1
×
171

×
172
        _, _ = log.Warningf("Creating your cluster %s\n", opts.DeploymentName)
×
173

×
174
        // verify that the host meets the minimum requirements, if not, print a warning
×
175
        if err := opts.ValidateMinimumRequirements(); err != nil {
×
176
                return err
×
177
        }
×
178

179
        // containers check
180
        if err := opts.startEnvironment(ctx, currentStep, steps); err != nil {
×
181
                return err
×
182
        }
×
183
        currentStep++
×
184

×
185
        // always download the latest image
×
186
        if err := opts.downloadImage(ctx, currentStep, steps); err != nil {
×
187
                return fmt.Errorf("%w: %w", errDownloadImage, err)
×
188
        }
×
189
        currentStep++
×
190

×
191
        // create local deployment
×
192
        opts.logStepStarted(fmt.Sprintf("Creating your deployment %s...", opts.DeploymentName), currentStep, steps)
×
193
        defer opts.stop()
×
194

×
195
        if err := opts.configureContainer(ctx); err != nil {
×
196
                return fmt.Errorf("%w: %w", errConfigContainer, err)
×
197
        }
×
198

199
        return nil
×
200
}
201

202
func (opts *SetupOpts) configureContainer(ctx context.Context) error {
×
203
        envVars := map[string]string{
×
204
                "TOOL": "ATLASCLI",
×
205
        }
×
206

×
207
        if log.IsDebugLevel() {
×
208
                envVars["RUNNER_LOG_FILE"] = "/dev/stdout"
×
209
        }
×
210

211
        if !config.TelemetryEnabled() {
×
212
                envVars["DO_NOT_TRACK"] = "1"
×
213
        }
×
214

215
        if opts.IsAuthEnabled() {
×
216
                envVars["MONGODB_INITDB_ROOT_USERNAME"] = opts.DBUsername
×
217
                envVars["MONGODB_INITDB_ROOT_PASSWORD"] = opts.DBUserPassword
×
218
        }
×
219

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

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

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

255
        _, err = opts.ContainerEngine.ContainerRun(ctx, opts.MongodDockerImageName(), &flags)
×
256
        if err != nil {
×
257
                return fmt.Errorf("%w: %w", errRunContainer, err)
×
258
        }
×
259

260
        // This can be a high number because the container will become unhealthy before the 10 minutes is reached
261
        const healthyDeploymentTimeout = 10 * time.Minute
×
262

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

276
        return err
×
277
}
278

279
func (opts *SetupOpts) WaitForHealthyDeployment(ctx context.Context, duration time.Duration) error {
×
280
        start := time.Now()
×
281

×
282
        for {
×
283
                if time.Since(start) > duration {
×
284
                        return errHealthCheckTimeout
×
285
                }
×
286

287
                status, err := opts.ContainerEngine.ContainerHealthStatus(ctx, opts.LocalMongodHostname())
×
288
                if err != nil {
×
289
                        return fmt.Errorf("%w: %w", errQueryHealthCheckStatus, err)
×
290
                }
×
291

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

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

315
        return nil
×
316
}
317

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

328
        return telemetry.TrackAskOne(p, &opts.settings, nil)
×
329
}
330

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

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

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

348
func (opts *SetupOpts) promptMdbVersion() error {
×
349
        p := &survey.Select{
×
350
                Message: "Major MongoDB Version",
×
351
                Options: mdbVersions,
×
352
                Default: opts.MdbVersion,
×
353
                Help:    "Major MongoDB Version for the deployment. Atlas CLI applies the latest minor version available.",
×
354
        }
×
355

×
356
        return telemetry.TrackAskOne(p, &opts.MdbVersion, nil)
×
357
}
×
358

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

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

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

381
        p := &survey.Input{
×
382
                Message: "Specify a port",
×
383
                Default: exportPort,
×
384
        }
×
385

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

396
                return validatePort(value)
×
397
        }))
398

399
        if err != nil {
×
400
                return err
×
401
        }
×
402

403
        if exportPort != autoassignPort {
×
404
                if opts.Port, err = strconv.Atoi(exportPort); err != nil {
×
405
                        return err
×
406
                }
×
407
        }
408

409
        return nil
×
410
}
411

412
func (opts *SetupOpts) validateDeploymentTypeFlag() error {
×
413
        if opts.DeploymentType == "" && opts.force {
×
414
                return errFlagTypeRequired
×
415
        }
×
416

417
        if !opts.IsLocalDeploymentType() && opts.bindIPAll {
×
418
                return errIncompatibleDeploymentType
×
419
        }
×
420

421
        if opts.DeploymentType != "" && !strings.EqualFold(opts.DeploymentType, options.AtlasCluster) && !strings.EqualFold(opts.DeploymentType, options.LocalCluster) {
×
422
                return fmt.Errorf("%w: %s", errInvalidDeploymentType, opts.DeploymentType)
×
423
        }
×
424

425
        return nil
×
426
}
427

428
func (opts *SetupOpts) validateBindIPAllFlag() error {
×
429
        if !opts.bindIPAll {
×
430
                return nil
×
431
        }
×
432

433
        if opts.force && (opts.DeploymentType == "" || opts.DBUsername == "" || opts.DBUserPassword == "") {
×
434
                return errFlagsTypeAndAuthRequired
×
435
        }
×
436

437
        if opts.DBUsername == "" {
×
438
                return errFlagUsernameRequired
×
439
        }
×
440

441
        return nil
×
442
}
443

444
func (opts *SetupOpts) validateFlags() error {
×
445
        if err := opts.validateDeploymentTypeFlag(); err != nil {
×
446
                return err
×
447
        }
×
448

449
        if opts.DeploymentName != "" {
×
450
                if err := options.ValidateDeploymentName(opts.DeploymentName); err != nil {
×
451
                        return err
×
452
                }
×
453
        }
454

455
        if opts.MdbVersion != "" && !slices.Contains(mdbVersions, opts.MdbVersion) {
×
456
                return fmt.Errorf("%w: %s", errInvalidMongoDBVersion, opts.MdbVersion)
×
457
        }
×
458

459
        if opts.Port != 0 {
×
460
                if err := validatePort(opts.Port); err != nil {
×
461
                        return err
×
462
                }
×
463
        }
464

465
        if opts.connectWith != "" && !search.StringInSliceFold(connectWithOptions, opts.connectWith) {
×
466
                return fmt.Errorf("%w: %s", errUnsupportedConnectWith, opts.connectWith)
×
467
        }
×
468

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

483
        return opts.validateBindIPAllFlag()
×
484
}
485

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

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

498
func (opts *SetupOpts) setDefaultSettings() error {
×
499
        opts.settings = defaultSettings
×
500
        defaultValuesSet := false
×
501

×
502
        if opts.DeploymentName == "" {
×
503
                opts.generateDeploymentName()
×
504
                defaultValuesSet = true
×
505
        }
×
506

507
        if opts.MdbVersion == "" {
×
508
                opts.MdbVersion = mdb8
×
509
                defaultValuesSet = true
×
510
        }
×
511

512
        if opts.Port == 0 {
×
513
                defaultValuesSet = true
×
514
        }
×
515

516
        if defaultValuesSet {
×
517
                if err := templatewriter.Print(os.Stderr, `
×
518
[Default Settings]
×
519
Deployment Name        {{.DeploymentName}}
×
520
MongoDB Version        {{.MdbVersion}}
×
521

×
522
`, opts); err != nil {
×
523
                        return err
×
524
                }
×
525
                if !opts.force {
×
526
                        if err := opts.promptSettings(); err != nil {
×
527
                                return err
×
528
                        }
×
529
                }
530
        }
531

532
        return nil
×
533
}
534

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

544
        return telemetry.TrackAskOne(p, &opts.connectWith, nil)
×
545
}
546

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

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

584
        return nil
×
585
}
586

587
func (opts *SetupOpts) validateAndPrompt() error {
×
588
        if err := opts.validateFlags(); err != nil {
×
589
                return err
×
590
        }
×
591

592
        if err := opts.ValidateAndPromptDeploymentType(); err != nil {
×
593
                return err
×
594
        }
×
595

596
        // Defer prompts to Atlas command
597
        if opts.IsAtlasDeploymentType() {
×
598
                return nil
×
599
        }
×
600

601
        if opts.DBUsername != "" && opts.DBUserPassword == "" {
×
602
                if err := opts.promptLocalAdminPassword(); err != nil {
×
603
                        return err
×
604
                }
×
605
        }
606

607
        if err := opts.setDefaultSettings(); err != nil {
×
608
                return err
×
609
        }
×
610

611
        switch opts.settings {
×
612
        case cancelSettings:
×
613
                return errCancel
×
614
        case customSettings:
×
615
                if err := opts.promptDeploymentName(); err != nil {
×
616
                        return err
×
617
                }
×
618

619
                if err := opts.promptMdbVersion(); err != nil {
×
620
                        return err
×
621
                }
×
622

623
                if err := opts.promptPort(); err != nil {
×
624
                        return err
×
625
                }
×
626
        }
627

628
        return nil
×
629
}
630

631
func (opts *SetupOpts) runLocal(ctx context.Context) error {
×
632
        if err := opts.LocalDeploymentPreRun(ctx); err != nil {
×
633
                return err
×
634
        }
×
635

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

644
        cs, err := opts.ConnectionString(ctx)
×
645
        if err != nil {
×
646
                return err
×
647
        }
×
648

649
        _, _ = log.Warningln("Deployment created!")
×
650
        _, _ = fmt.Fprintf(opts.OutWriter, `Connection string: "%s"
×
651
`, cs)
×
652
        _, _ = log.Warningln("")
×
653

×
654
        return opts.runConnectWith(cs)
×
655
}
656

657
func (opts *SetupOpts) runAtlas(ctx context.Context) error {
×
658
        s := setup.Builder()
×
659

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

×
664
        flagstoRemove := map[string]string{
×
665
                flag.TypeFlag: "1",
×
666
        }
×
667

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

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

678
        // update args
679
        s.SetArgs(newArgs)
×
680

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

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

694
                return err
×
695
        }
696

697
        if strings.EqualFold(options.LocalCluster, opts.DeploymentType) {
×
698
                return opts.runLocal(ctx)
×
699
        }
×
700

701
        return opts.runAtlas(ctx)
×
702
}
703

704
func (opts *SetupOpts) PostRun() {
×
705
        opts.DeploymentTelemetry.AppendDeploymentType()
×
706
        opts.DeploymentTelemetry.AppendDeploymentUUID()
×
707
}
×
708

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

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

738
                        opts.force = opts.atlasSetup.Confirm
×
739
                        opts.DBUsername = opts.atlasSetup.DBUsername
×
740
                        opts.DBUserPassword = opts.atlasSetup.DBUserPassword
×
741

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

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

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

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

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

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

798
func (opts *SetupOpts) stop() {
×
799
        if opts.IsTerminal() {
×
800
                opts.s.Stop()
×
801
        }
×
802
}
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