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

mongodb / mongodb-atlas-cli / 22219697489

20 Feb 2026 10:06AM UTC coverage: 64.114% (+0.005%) from 64.109%
22219697489

push

github

GitHub
chore(go): bump go version (#4460)

25429 of 39662 relevant lines covered (64.11%)

0.81 hits per line

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

46.74
/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/clusters/connect"
35
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/deployments/options"
36
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/require"
37
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/setup"
38
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/workflows"
39
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/compass"
40
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/container"
41
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/flag"
42
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/log"
43
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mongodbclient"
44
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mongosh"
45
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/pointer"
46
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/search"
47
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/telemetry"
48
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/templatewriter"
49
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/usage"
50
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/vscode"
51
        "github.com/spf13/cobra"
52
)
53

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

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

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

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

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

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

1✔
148
        err := opts.ContainerEngine.ImagePull(ctx, opts.MongodDockerImageName())
1✔
149
        if err == nil {
2✔
150
                return nil
1✔
151
        }
1✔
152
        _, _ = log.Debugf("Error encountered while pulling image '%s': %s\n", opts.MongodDockerImageName(), err.Error())
1✔
153
        if opts.isDiskSpaceError(err) {
2✔
154
                return errInsufficientDiskSpace
1✔
155
        }
1✔
156
        _, _ = log.Debugf("Checking if image exists locally\n")
1✔
157
        // In case we already have an image present and the download fails, we can continue with the existing image
1✔
158
        images, _ := opts.ContainerEngine.ImageList(ctx, opts.MongodDockerImageName())
1✔
159
        if len(images) != 0 {
2✔
160
                _, _ = log.Debugf("Image '%s' exists locally, continuing with the existing image\n", opts.MongodDockerImageName())
1✔
161
                return nil
1✔
162
        }
1✔
163
        _, _ = log.Debugf("Failed to find image '%s' locally\n", opts.MongodDockerImageName())
1✔
164

1✔
165
        return errFailedToDownloadImage
1✔
166
}
167

168
// isDiskSpaceError detects if error is disk space related failure.
169
func (*SetupOpts) isDiskSpaceError(err error) bool {
1✔
170
        if err == nil {
2✔
171
                return false
1✔
172
        }
1✔
173

174
        errMsg := strings.ToLower(err.Error())
1✔
175

1✔
176
        return strings.Contains(errMsg, "no space left on device")
1✔
177
}
178

179
func (opts *SetupOpts) startEnvironment(ctx context.Context, currentStep int, steps int) error {
1✔
180
        opts.logStepStarted("Starting your local environment...", currentStep, steps)
1✔
181
        defer opts.stop()
1✔
182

1✔
183
        containers, err := opts.GetLocalContainers(ctx)
1✔
184
        if err != nil {
1✔
185
                return fmt.Errorf("%w: %w", errListContainer, err)
×
186
        }
×
187

188
        return opts.validateLocalDeploymentsSettings(containers)
1✔
189
}
190

191
func (opts *SetupOpts) createLocalDeployment(ctx context.Context) error {
1✔
192
        currentStep := 1
1✔
193

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

1✔
196
        // verify that the host meets the minimum requirements, if not, print a warning
1✔
197
        if err := opts.ValidateMinimumRequirements(); err != nil {
1✔
198
                return err
×
199
        }
×
200

201
        // containers check
202
        if err := opts.startEnvironment(ctx, currentStep, steps); err != nil {
2✔
203
                return err
1✔
204
        }
1✔
205
        currentStep++
1✔
206

1✔
207
        // always download the latest image
1✔
208
        if err := opts.downloadImage(ctx, currentStep); err != nil {
2✔
209
                return fmt.Errorf("%w: %w", errDownloadImage, err)
1✔
210
        }
1✔
211
        currentStep++
1✔
212

1✔
213
        // create local deployment
1✔
214
        opts.logStepStarted(fmt.Sprintf("Creating your deployment %s...", opts.DeploymentName), currentStep, steps)
1✔
215
        defer opts.stop()
1✔
216

1✔
217
        if err := opts.configureContainer(ctx); err != nil {
2✔
218
                return fmt.Errorf("%w: %w", errConfigContainer, err)
1✔
219
        }
1✔
220

221
        return nil
1✔
222
}
223

224
func (opts *SetupOpts) configureContainer(ctx context.Context) error {
1✔
225
        envVars := map[string]string{
1✔
226
                "TOOL": "ATLASCLI",
1✔
227
        }
1✔
228

1✔
229
        if log.IsDebugLevel() {
2✔
230
                envVars["RUNNER_LOG_FILE"] = "/dev/stdout"
1✔
231
        }
1✔
232

233
        if !config.TelemetryEnabled() {
1✔
234
                envVars["DO_NOT_TRACK"] = "1"
×
235
        }
×
236

237
        if opts.IsAuthEnabled() {
1✔
238
                envVars["MONGODB_INITDB_ROOT_USERNAME"] = opts.DBUsername
×
239
                envVars["MONGODB_INITDB_ROOT_PASSWORD"] = opts.DBUserPassword
×
240
        }
×
241

242
        flags := container.RunFlags{}
1✔
243
        flags.Detach = pointer.Get(true)
1✔
244
        flags.Name = pointer.Get(opts.LocalMongodHostname())
1✔
245
        flags.Hostname = pointer.Get(opts.LocalMongodHostname())
1✔
246
        flags.Env = envVars
1✔
247
        flags.BindIPAll = &opts.bindIPAll
1✔
248
        flags.IP = &opts.mongodIP
1✔
249
        flags.Ports = []container.PortMapping{{HostPort: opts.Port, ContainerPort: internalMongodPort}}
1✔
250
        if opts.initdb != "" {
1✔
251
                flags.Volumes = []container.VolumeMapping{
×
252
                        {
×
253
                                HostPath:      opts.initdb,
×
254
                                ContainerPath: "/docker-entrypoint-initdb.d",
×
255
                        },
×
256
                }
×
257
        }
×
258
        healthCheck, err := opts.ContainerEngine.ImageHealthCheck(ctx, opts.MongodDockerImageName())
1✔
259
        if err != nil {
1✔
260
                return fmt.Errorf("%w: %w", errInspectHealthCheck, err)
×
261
        }
×
262

263
        // Temporary fix until https://github.com/containers/podman/issues/18904 is closed
264
        if healthCheck == nil {
1✔
265
                const HealthcheckInterval = 30
×
266
                const HealthStartPeriod = 1
×
267
                const HealthTimeout = 30
×
268
                const HealthRetries = 3
×
269

×
270
                flags.HealthCmd = &[]string{"/usr/local/bin/runner", "healthcheck"}
×
271
                flags.HealthInterval = pointer.Get(HealthcheckInterval * time.Second)
×
272
                flags.HealthStartPeriod = pointer.Get(HealthStartPeriod * time.Second)
×
273
                flags.HealthTimeout = pointer.Get(HealthTimeout * time.Second)
×
274
                flags.HealthRetries = pointer.Get(HealthRetries)
×
275
        }
×
276

277
        _, err = opts.ContainerEngine.ContainerRun(ctx, opts.MongodDockerImageName(), &flags)
1✔
278
        if err != nil {
2✔
279
                _, _ = log.Debugf("Error encountered while trying to run container '%s': %s\n", opts.LocalMongodHostname(), err.Error())
1✔
280
                if opts.isDiskSpaceError(err) {
2✔
281
                        return fmt.Errorf("%w: %w", errRunContainer, errInsufficientDiskSpace)
1✔
282
                }
1✔
283
                return fmt.Errorf("%w: %w", errRunContainer, err)
×
284
        }
285

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

1✔
289
        err = opts.WaitForHealthyDeployment(ctx, healthyDeploymentTimeout)
1✔
290
        if err != nil && log.IsDebugLevel() {
2✔
291
                logs, err := opts.ContainerEngine.ContainerLogs(ctx, opts.LocalMongodHostname())
1✔
292
                if err != nil {
1✔
293
                        _, _ = log.Debugf("Container unhealthy (hostname=%s), failed to get container logs : %s\n", opts.LocalMongodHostname(), err)
×
294
                } else {
1✔
295
                        _, _ = log.Debugf("Container unhealthy (hostname=%s), container logs:\n", opts.LocalMongodHostname())
1✔
296
                        for _, logLine := range logs {
2✔
297
                                _, _ = log.Debugln(logLine)
1✔
298
                        }
1✔
299
                }
300
        }
301

302
        return err
1✔
303
}
304

305
func (opts *SetupOpts) WaitForHealthyDeployment(ctx context.Context, duration time.Duration) error {
1✔
306
        start := time.Now()
1✔
307

1✔
308
        for {
2✔
309
                if time.Since(start) > duration {
1✔
310
                        return errHealthCheckTimeout
×
311
                }
×
312

313
                status, err := opts.ContainerEngine.ContainerHealthStatus(ctx, opts.LocalMongodHostname())
1✔
314
                if err != nil {
1✔
315
                        return fmt.Errorf("%w: %w", errQueryHealthCheckStatus, err)
×
316
                }
×
317

318
                switch status {
1✔
319
                case container.DockerHealthcheckStatusHealthy:
1✔
320
                        return nil
1✔
321
                case container.DockerHealthcheckStatusUnhealthy:
1✔
322
                        return errDeploymentUnhealthy
1✔
323
                case container.DockerHealthcheckStatusNone:
×
324
                        return errDeploymentNoHealthCheck
×
325
                case container.DockerHealthcheckStatusStarting:
×
326
                        time.Sleep(1 * time.Second)
×
327
                }
328
        }
329
}
330

331
func (opts *SetupOpts) validateLocalDeploymentsSettings(containers []container.Container) error {
1✔
332
        mongodContainerName := opts.LocalMongodHostname()
1✔
333
        for _, c := range containers {
2✔
334
                for _, n := range c.Names {
2✔
335
                        if n == mongodContainerName {
2✔
336
                                return fmt.Errorf("%w: \"%s\", state:\"%s\"", ErrDeploymentExists, opts.DeploymentName, c.State)
1✔
337
                        }
1✔
338
                }
339
        }
340

341
        return nil
1✔
342
}
343

344
func (opts *SetupOpts) promptSettings() error {
×
345
        p := &survey.Select{
×
346
                Message: "How do you want to set up your local Atlas deployment?",
×
347
                Options: settingOptions,
×
348
                Default: opts.settings,
×
349
                Description: func(value string, _ int) string {
×
350
                        return settingsDescription[value]
×
351
                },
×
352
        }
353

354
        return telemetry.TrackAskOne(p, &opts.settings, nil)
×
355
}
356

357
func (opts *SetupOpts) generateDeploymentName() {
×
358
        opts.DeploymentName = fmt.Sprintf("local%v", rand.Intn(10000)) //nolint // no need for crypto here
×
359
}
×
360

361
func (opts *SetupOpts) promptDeploymentName() error {
×
362
        p := &survey.Input{
×
363
                Message: "Deployment Name [You can't change this value later]",
×
364
                Help:    usage.ClusterName,
×
365
                Default: opts.DeploymentName,
×
366
        }
×
367

×
368
        return telemetry.TrackAskOne(p, &opts.DeploymentName, survey.WithValidator(func(ans any) error {
×
369
                name, _ := ans.(string)
×
370
                return options.ValidateDeploymentName(name)
×
371
        }))
×
372
}
373

374
func (opts *SetupOpts) promptMdbVersion() error {
×
375
        p := &survey.Select{
×
376
                Message: "Major MongoDB Version (latest minor version will be used)",
×
377
                Options: mdbMajorVersions,
×
378
                Default: opts.MdbVersion,
×
379
                Help:    "Major MongoDB Version for the deployment. Atlas CLI applies the latest minor version available.",
×
380
        }
×
381

×
382
        return telemetry.TrackAskOne(p, &opts.MdbVersion, nil)
×
383
}
×
384

385
func checkPort(p int) error {
×
386
        lc := net.ListenConfig{}
×
387
        server, err := lc.Listen(context.Background(), "tcp", fmt.Sprintf("localhost:%d", p))
×
388
        if err != nil {
×
389
                return fmt.Errorf("%w: %d", errPortNotAvailable, p)
×
390
        }
×
391
        _ = server.Close()
×
392
        return nil
×
393
}
394

395
func validatePort(p int) error {
×
396
        if p <= 0 || p > 65535 {
×
397
                return errPortOutOfRange
×
398
        }
×
399
        return checkPort(p)
×
400
}
401

402
func (opts *SetupOpts) promptPort() error {
×
403
        exportPort := autoassignPort
×
404
        if opts.Port != 0 {
×
405
                exportPort = strconv.Itoa(opts.Port)
×
406
        }
×
407

408
        p := &survey.Input{
×
409
                Message: "Specify a port",
×
410
                Default: exportPort,
×
411
        }
×
412

×
413
        err := telemetry.TrackAskOne(p, &exportPort, survey.WithValidator(func(ans any) error {
×
414
                input, _ := ans.(string)
×
415
                if input == autoassignPort {
×
416
                        return nil
×
417
                }
×
418
                value, err := strconv.Atoi(input)
×
419
                if err != nil {
×
420
                        return errMustBeInt
×
421
                }
×
422

423
                return validatePort(value)
×
424
        }))
425

426
        if err != nil {
×
427
                return err
×
428
        }
×
429

430
        if exportPort != autoassignPort {
×
431
                if opts.Port, err = strconv.Atoi(exportPort); err != nil {
×
432
                        return err
×
433
                }
×
434
        }
435

436
        return nil
×
437
}
438

439
func (opts *SetupOpts) validateDeploymentTypeFlag() error {
1✔
440
        if opts.DeploymentType == "" && opts.force {
1✔
441
                return errFlagTypeRequired
×
442
        }
×
443

444
        if !opts.IsLocalDeploymentType() && opts.bindIPAll {
1✔
445
                return errIncompatibleDeploymentType
×
446
        }
×
447

448
        if opts.DeploymentType != "" && !strings.EqualFold(opts.DeploymentType, connect.AtlasCluster) && !strings.EqualFold(opts.DeploymentType, options.LocalCluster) {
1✔
449
                return fmt.Errorf("%w: %s", errInvalidDeploymentType, opts.DeploymentType)
×
450
        }
×
451

452
        return nil
1✔
453
}
454

455
func (opts *SetupOpts) validateBindIPAllFlag() error {
1✔
456
        if !opts.bindIPAll {
2✔
457
                return nil
1✔
458
        }
1✔
459

460
        if opts.force && (opts.DeploymentType == "" || opts.DBUsername == "" || opts.DBUserPassword == "") {
×
461
                return errFlagsTypeAndAuthRequired
×
462
        }
×
463

464
        if opts.DBUsername == "" {
×
465
                return errFlagUsernameRequired
×
466
        }
×
467

468
        return nil
×
469
}
470

471
func (opts *SetupOpts) validateFlags() error {
1✔
472
        if err := opts.validateDeploymentTypeFlag(); err != nil {
1✔
473
                return err
×
474
        }
×
475

476
        if opts.DeploymentName != "" {
2✔
477
                if err := options.ValidateDeploymentName(opts.DeploymentName); err != nil {
1✔
478
                        return err
×
479
                }
×
480
        }
481

482
        if opts.MdbVersion != "" && !slices.Contains(mdbVersions, opts.MdbVersion) {
2✔
483
                return fmt.Errorf("%w: %s", errInvalidMongoDBVersion, opts.MdbVersion)
1✔
484
        }
1✔
485

486
        if opts.Port != 0 {
1✔
487
                if err := validatePort(opts.Port); err != nil {
×
488
                        return err
×
489
                }
×
490
        }
491

492
        if opts.connectWith != "" && !search.StringInSliceFold(connectWithOptions, opts.connectWith) {
1✔
493
                return fmt.Errorf("%w: %s", errUnsupportedConnectWith, opts.connectWith)
×
494
        }
×
495

496
        if opts.initdb != "" {
1✔
497
                info, err := os.Stat(opts.initdb)
×
498
                if err != nil {
×
499
                        return fmt.Errorf("%w: %w", errInvalidInit, err)
×
500
                }
×
501
                if !info.IsDir() {
×
502
                        return errInitMustBeDir
×
503
                }
×
504
                opts.initdb, err = filepath.Abs(opts.initdb)
×
505
                if err != nil {
×
506
                        return fmt.Errorf("%w: %w", errInvalidInit, err)
×
507
                }
×
508
        }
509

510
        return opts.validateBindIPAllFlag()
1✔
511
}
512

513
func (opts *SetupOpts) promptLocalAdminPassword() error {
×
514
        if !opts.IsTerminalInput() {
×
515
                _, err := fmt.Fscanln(opts.InReader, &opts.DBUserPassword)
×
516
                return err
×
517
        }
×
518

519
        p := &survey.Password{
×
520
                Message: "Password for authenticating to local deployment",
×
521
        }
×
522
        return telemetry.TrackAskOne(p, &opts.DBUserPassword)
×
523
}
524

525
func (opts *SetupOpts) setDefaultSettings() error {
1✔
526
        opts.settings = defaultSettings
1✔
527
        defaultValuesSet := false
1✔
528

1✔
529
        if opts.DeploymentName == "" {
1✔
530
                opts.generateDeploymentName()
×
531
                defaultValuesSet = true
×
532
        }
×
533

534
        if opts.MdbVersion == "" {
2✔
535
                opts.MdbVersion = mdb8
1✔
536
                defaultValuesSet = true
1✔
537
        }
1✔
538

539
        if opts.Port == 0 {
2✔
540
                defaultValuesSet = true
1✔
541
        }
1✔
542

543
        if defaultValuesSet {
2✔
544
                if err := templatewriter.Print(os.Stderr, `
1✔
545
[Default Settings]
1✔
546
Deployment Name        {{.DeploymentName}}
1✔
547
MongoDB Major Version        {{.MdbVersion}} (latest minor version)
1✔
548

1✔
549
`, opts); err != nil {
1✔
550
                        return err
×
551
                }
×
552
                if !opts.force {
1✔
553
                        if err := opts.promptSettings(); err != nil {
×
554
                                return err
×
555
                        }
×
556
                }
557
        }
558

559
        return nil
1✔
560
}
561

562
func (opts *SetupOpts) promptConnect() error {
×
563
        p := &survey.Select{
×
564
                Message: fmt.Sprintf("How would you like to connect to %s?", opts.DeploymentName),
×
565
                Options: connectWithOptions,
×
566
                Description: func(value string, _ int) string {
×
567
                        return connectWithDescription[value]
×
568
                },
×
569
        }
570

571
        return telemetry.TrackAskOne(p, &opts.connectWith, nil)
×
572
}
573

574
func (opts *SetupOpts) runConnectWith(cs string) error {
1✔
575
        if opts.connectWith == "" {
2✔
576
                if opts.force {
2✔
577
                        opts.connectWith = skipConnect
1✔
578
                } else {
1✔
579
                        if err := opts.promptConnect(); err != nil {
×
580
                                return err
×
581
                        }
×
582
                }
583
        }
584

585
        switch opts.connectWith {
1✔
586
        case skipConnect:
1✔
587
                _, _ = fmt.Fprintln(os.Stderr, "connection skipped")
1✔
588
        case connect.ConnectWithCompass:
×
589
                if !compass.Detect() {
×
590
                        return compass.ErrCompassNotInstalled
×
591
                }
×
592
                if _, err := log.Warningln("Launching MongoDB Compass..."); err != nil {
×
593
                        return err
×
594
                }
×
595
                return compass.Run("", "", cs)
×
596
        case connect.ConnectWithMongosh:
×
597
                if !mongosh.Detect() {
×
598
                        return mongosh.ErrMongoshNotInstalled
×
599
                }
×
600
                return mongosh.Run("", "", cs)
×
601
        case connect.ConnectWithVsCode:
×
602
                if !vscode.Detect() {
×
603
                        return vscode.ErrVsCodeCliNotInstalled
×
604
                }
×
605
                if _, err := log.Warningln("Launching VsCode..."); err != nil {
×
606
                        return err
×
607
                }
×
608
                return vscode.SaveConnection(cs, opts.DeploymentName, opts.DeploymentType)
×
609
        }
610

611
        return nil
1✔
612
}
613

614
func (opts *SetupOpts) validateAndPrompt() error {
1✔
615
        if err := opts.validateFlags(); err != nil {
1✔
616
                return err
×
617
        }
×
618

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

623
        // Defer prompts to Atlas command
624
        if opts.IsAtlasDeploymentType() {
1✔
625
                return nil
×
626
        }
×
627

628
        if opts.DBUsername != "" && opts.DBUserPassword == "" {
1✔
629
                if err := opts.promptLocalAdminPassword(); err != nil {
×
630
                        return err
×
631
                }
×
632
        }
633

634
        if err := opts.setDefaultSettings(); err != nil {
1✔
635
                return err
×
636
        }
×
637

638
        switch opts.settings {
1✔
639
        case cancelSettings:
×
640
                return errCancel
×
641
        case customSettings:
×
642
                if err := opts.promptDeploymentName(); err != nil {
×
643
                        return err
×
644
                }
×
645

646
                if err := opts.promptMdbVersion(); err != nil {
×
647
                        return err
×
648
                }
×
649

650
                if err := opts.promptPort(); err != nil {
×
651
                        return err
×
652
                }
×
653
        }
654

655
        return nil
1✔
656
}
657

658
func (opts *SetupOpts) runLocal(ctx context.Context) error {
1✔
659
        if err := opts.LocalDeploymentPreRun(ctx); err != nil {
1✔
660
                return err
×
661
        }
×
662

663
        if err := opts.createLocalDeployment(ctx); err != nil {
2✔
664
                // in case the deployment already exists we shouldn't delete it
1✔
665
                if !errors.Is(err, ErrDeploymentExists) {
2✔
666
                        _ = opts.RemoveLocal(ctx)
1✔
667
                }
1✔
668
                return err
1✔
669
        }
670

671
        cs, err := opts.ConnectionString(ctx)
1✔
672
        if err != nil {
1✔
673
                return err
×
674
        }
×
675

676
        _, _ = log.Warningln("Deployment created!")
1✔
677
        _, _ = fmt.Fprintf(opts.OutWriter, `Connection string: "%s"
1✔
678
`, cs)
1✔
679
        _, _ = log.Warningln("")
1✔
680

1✔
681
        return opts.runConnectWith(cs)
1✔
682
}
683

684
func (opts *SetupOpts) runAtlas(ctx context.Context) error {
×
685
        s := setup.Builder()
×
686

×
687
        // remove global flags and unknown flags
×
688
        var newArgs []string
×
689
        _, _ = log.Debugf("Removing flags and args from original args %s\n", os.Args)
×
690

×
691
        flagstoRemove := map[string]string{
×
692
                flag.TypeFlag: "1",
×
693
        }
×
694

×
695
        newArgs, err := workflows.RemoveFlagsAndArgs(flagstoRemove, map[string]bool{opts.DeploymentName: true}, os.Args)
×
696
        if err != nil {
×
697
                return err
×
698
        }
×
699

700
        // replace deployment name with cluster name
701
        if opts.DeploymentName != "" {
×
702
                newArgs = append(newArgs, "--"+flag.ClusterName, opts.DeploymentName)
×
703
        }
×
704

705
        // update args
706
        s.SetArgs(newArgs)
×
707

×
708
        // run atlas setup
×
709
        _, _ = log.Debugf("Starting to run atlas setup with args %s\n", newArgs)
×
710
        _, err = s.ExecuteContextC(ctx)
×
711
        return err
×
712
}
713

714
func (opts *SetupOpts) Run(ctx context.Context) error {
1✔
715
        if err := opts.validateAndPrompt(); err != nil {
1✔
716
                if errors.Is(err, errCancel) {
×
717
                        _, _ = log.Warningln(err)
×
718
                        return nil
×
719
                }
×
720

721
                return err
×
722
        }
723

724
        if strings.EqualFold(options.LocalCluster, opts.DeploymentType) {
2✔
725
                return opts.runLocal(ctx)
1✔
726
        }
1✔
727

728
        return opts.runAtlas(ctx)
×
729
}
730

731
func (opts *SetupOpts) PostRun() {
1✔
732
        opts.DeploymentTelemetry.AppendDeploymentType()
1✔
733
        opts.DeploymentTelemetry.AppendDeploymentUUID()
1✔
734
}
1✔
735

736
func (opts *SetupOpts) validateTier() error {
×
737
        opts.atlasSetup.Tier = strings.ToUpper(opts.atlasSetup.Tier)
×
738
        if opts.atlasSetup.Tier == atlasM2 || opts.atlasSetup.Tier == atlasM5 {
×
739
                _, _ = fmt.Fprintf(os.Stderr, deprecateMessageSharedTier, opts.atlasSetup.Tier)
×
740
        }
×
741
        return nil
×
742
}
743

744
// SetupBuilder builds a cobra.Command that can run as:
745
// atlas deployments setup.
746
func SetupBuilder() *cobra.Command {
1✔
747
        opts := &SetupOpts{
1✔
748
                settings:   defaultSettings,
1✔
749
                atlasSetup: &setup.Opts{},
1✔
750
        }
1✔
751
        cmd := &cobra.Command{
1✔
752
                Use:   "setup [deploymentName]",
1✔
753
                Short: "Create a local deployment.",
1✔
754
                Long:  "To learn more about local atlas deployments, see https://www.mongodb.com/docs/atlas/cli/current/atlas-cli-deploy-local/",
1✔
755
                Deprecated: `This command has been deprecated and will be removed in a future release.
1✔
756

1✔
757
Please switch to the new command structure based on your target environment:
1✔
758
- For Atlas (cloud) deployments, use 'atlas setup'.
1✔
759
- For Local (Docker) deployments, use 'atlas local setup'.
1✔
760
`,
1✔
761
                Args:    require.MaximumNArgs(1),
1✔
762
                GroupID: "all",
1✔
763
                Annotations: map[string]string{
1✔
764
                        "deploymentNameDesc": "Name of the deployment that you want to set up.",
1✔
765
                },
1✔
766
                PreRunE: func(cmd *cobra.Command, args []string) error {
1✔
767
                        if len(args) == 1 {
×
768
                                opts.DeploymentName = args[0]
×
769
                        }
×
770

771
                        opts.force = opts.atlasSetup.Confirm
×
772
                        opts.DBUsername = opts.atlasSetup.DBUsername
×
773
                        opts.DBUserPassword = opts.atlasSetup.DBUserPassword
×
774

×
775
                        return opts.PreRunE(
×
776
                                opts.validateTier,
×
777
                                opts.InitOutput(cmd.OutOrStdout(), ""),
×
778
                                opts.InitInput(cmd.InOrStdin()),
×
779
                                opts.InitStore(cmd.Context(), cmd.OutOrStdout()),
×
780
                                opts.initMongoDBClient,
×
781
                        )
×
782
                },
783
                RunE: func(cmd *cobra.Command, _ []string) error {
×
784
                        return opts.Run(cmd.Context())
×
785
                },
×
786
                PostRun: func(_ *cobra.Command, _ []string) {
×
787
                        opts.PostRun()
×
788
                },
×
789
        }
790

791
        // Local and Atlas
792
        cmd.Flags().StringVar(&opts.DeploymentType, flag.TypeFlag, "", usage.DeploymentTypeSetup)
1✔
793
        cmd.Flags().StringVar(&opts.MdbVersion, flag.MDBVersion, "", usage.DeploymentMDBVersion)
1✔
794
        cmd.Flags().StringVar(&opts.connectWith, flag.ConnectWith, "", usage.ConnectWithSetup)
1✔
795

1✔
796
        // Local only
1✔
797
        cmd.Flags().IntVar(&opts.Port, flag.Port, 0, usage.MongodPort)
1✔
798
        cmd.Flags().BoolVar(&opts.bindIPAll, flag.BindIPAll, false, usage.BindIPAll)
1✔
799
        cmd.Flags().StringVar(&opts.initdb, flag.InitDB, "", usage.InitDB)
1✔
800

1✔
801
        // Atlas only
1✔
802
        opts.atlasSetup.SetupAtlasFlags(cmd)
1✔
803
        opts.atlasSetup.SetupFlowFlags(cmd)
1✔
804
        cmd.Flags().Lookup(flag.Region).Usage = usage.DeploymentRegion
1✔
805
        cmd.Flags().Lookup(flag.Tag).Usage = usage.DeploymentTag
1✔
806
        cmd.Flags().Lookup(flag.Tier).Usage = usage.DeploymentTier
1✔
807
        cmd.Flags().Lookup(flag.EnableTerminationProtection).Usage = usage.EnableTerminationProtectionForDeployment
1✔
808
        cmd.Flags().Lookup(flag.SkipSampleData).Usage = usage.SkipSampleDataDeployment
1✔
809
        cmd.Flags().Lookup(flag.Force).Usage = usage.ForceDeploymentsSetup
1✔
810

1✔
811
        _ = cmd.RegisterFlagCompletionFunc(flag.MDBVersion, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
1✔
812
                return mdbVersions, cobra.ShellCompDirectiveDefault
×
813
        })
×
814
        _ = cmd.RegisterFlagCompletionFunc(flag.TypeFlag, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
1✔
815
                return options.DeploymentTypeOptions, cobra.ShellCompDirectiveDefault
×
816
        })
×
817
        _ = cmd.RegisterFlagCompletionFunc(flag.ConnectWith, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
1✔
818
                return connectWithOptions, cobra.ShellCompDirectiveDefault
×
819
        })
×
820
        return cmd
1✔
821
}
822

823
func (opts *SetupOpts) start() {
1✔
824
        if opts.IsTerminal() {
1✔
825
                opts.s = spinner.New(spinner.CharSets[9], spinnerSpeed)
×
826
                _ = opts.s.Color("cyan", "bold")
×
827
                opts.s.Start()
×
828
        }
×
829
}
830

831
func (opts *SetupOpts) stop() {
1✔
832
        if opts.IsTerminal() {
1✔
833
                opts.s.Stop()
×
834
        }
×
835
}
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