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

mongodb / mongodb-atlas-cli / 25848964073

14 May 2026 07:59AM UTC coverage: 22.479% (-41.3%) from 63.771%
25848964073

push

github

web-flow
build(deps): bump test-summary/action from 2.4 to 2.6 (#4576)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

8987 of 39979 relevant lines covered (22.48%)

0.25 hits per line

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

40.7
/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
        candidates := []string{
1✔
149
                opts.MongodDockerImageName(),
1✔
150
                opts.MongodDockerImageNameFallback(),
1✔
151
        }
1✔
152

1✔
153
        prevImage := ""
1✔
154
        for _, image := range candidates {
2✔
155
                if prevImage != "" {
2✔
156
                        _, _ = log.Debugf("Image '%s' not available, trying to download fallback image '%s'\n", prevImage, image)
1✔
157
                }
1✔
158
                err := opts.ContainerEngine.ImagePull(ctx, image)
1✔
159
                if err == nil {
2✔
160
                        _, _ = log.Debugf("Successfully pulled image '%s'\n", image)
1✔
161
                        opts.SetResolvedImageName(image)
1✔
162
                        return nil
1✔
163
                }
1✔
164
                _, _ = log.Debugf("Error encountered while pulling image '%s': %s\n", image, err.Error())
1✔
165
                if opts.isDiskSpaceError(err) {
2✔
166
                        return errInsufficientDiskSpace
1✔
167
                }
1✔
168
                prevImage = image
1✔
169
        }
170

171
        _, _ = log.Debugf("Could not pull images, checking if any candidate exists locally\n")
1✔
172
        prevImage = ""
1✔
173
        for _, image := range candidates {
2✔
174
                if prevImage != "" {
2✔
175
                        _, _ = log.Debugf("Image '%s' not found locally, looking for fallback image '%s'\n", prevImage, image)
1✔
176
                }
1✔
177
                images, _ := opts.ContainerEngine.ImageList(ctx, image)
1✔
178
                if len(images) != 0 {
2✔
179
                        _, _ = log.Debugf("Found image '%s' locally, continuing with existing image\n", image)
1✔
180
                        opts.SetResolvedImageName(image)
1✔
181
                        return nil
1✔
182
                }
1✔
183
                prevImage = image
1✔
184
        }
185

186
        _, _ = log.Debugf("Failed to find any of %v locally\n", candidates)
1✔
187
        return errFailedToDownloadImage
1✔
188
}
189

190
// isDiskSpaceError detects if error is disk space related failure.
191
func (*SetupOpts) isDiskSpaceError(err error) bool {
1✔
192
        if err == nil {
2✔
193
                return false
1✔
194
        }
1✔
195

196
        errMsg := strings.ToLower(err.Error())
1✔
197

1✔
198
        return strings.Contains(errMsg, "no space left on device")
1✔
199
}
200

201
func (opts *SetupOpts) startEnvironment(ctx context.Context, currentStep int, steps int) error {
1✔
202
        opts.logStepStarted("Starting your local environment...", currentStep, steps)
1✔
203
        defer opts.stop()
1✔
204

1✔
205
        containers, err := opts.GetLocalContainers(ctx)
1✔
206
        if err != nil {
1✔
207
                return fmt.Errorf("%w: %w", errListContainer, err)
×
208
        }
×
209

210
        return opts.validateLocalDeploymentsSettings(containers)
1✔
211
}
212

213
func (opts *SetupOpts) createLocalDeployment(ctx context.Context) error {
1✔
214
        currentStep := 1
1✔
215

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

1✔
218
        // verify that the host meets the minimum requirements, if not, print a warning
1✔
219
        if err := opts.ValidateMinimumRequirements(); err != nil {
1✔
220
                return err
×
221
        }
×
222

223
        // containers check
224
        if err := opts.startEnvironment(ctx, currentStep, steps); err != nil {
2✔
225
                return err
1✔
226
        }
1✔
227
        currentStep++
1✔
228

1✔
229
        // always download the latest image
1✔
230
        if err := opts.downloadImage(ctx, currentStep); err != nil {
2✔
231
                return fmt.Errorf("%w: %w", errDownloadImage, err)
1✔
232
        }
1✔
233
        currentStep++
1✔
234

1✔
235
        // create local deployment
1✔
236
        opts.logStepStarted(fmt.Sprintf("Creating your deployment %s...", opts.DeploymentName), currentStep, steps)
1✔
237
        defer opts.stop()
1✔
238

1✔
239
        if err := opts.configureContainer(ctx); err != nil {
2✔
240
                return fmt.Errorf("%w: %w", errConfigContainer, err)
1✔
241
        }
1✔
242

243
        return nil
1✔
244
}
245

246
func (opts *SetupOpts) configureContainer(ctx context.Context) error {
1✔
247
        envVars := map[string]string{
1✔
248
                "TOOL": "ATLASCLI",
1✔
249
        }
1✔
250

1✔
251
        if log.IsDebugLevel() {
2✔
252
                envVars["RUNNER_LOG_FILE"] = "/dev/stdout"
1✔
253
        }
1✔
254

255
        if !config.TelemetryEnabled() {
1✔
256
                envVars["DO_NOT_TRACK"] = "1"
×
257
        }
×
258

259
        if opts.IsAuthEnabled() {
1✔
260
                envVars["MONGODB_INITDB_ROOT_USERNAME"] = opts.DBUsername
×
261
                envVars["MONGODB_INITDB_ROOT_PASSWORD"] = opts.DBUserPassword
×
262
        }
×
263

264
        flags := container.RunFlags{}
1✔
265
        flags.Detach = pointer.Get(true)
1✔
266
        flags.Name = pointer.Get(opts.LocalMongodHostname())
1✔
267
        flags.Hostname = pointer.Get(opts.LocalMongodHostname())
1✔
268
        flags.Env = envVars
1✔
269
        flags.BindIPAll = &opts.bindIPAll
1✔
270
        flags.IP = &opts.mongodIP
1✔
271
        flags.Ports = []container.PortMapping{{HostPort: opts.Port, ContainerPort: internalMongodPort}}
1✔
272
        if opts.initdb != "" {
1✔
273
                flags.Volumes = []container.VolumeMapping{
×
274
                        {
×
275
                                HostPath:      opts.initdb,
×
276
                                ContainerPath: "/docker-entrypoint-initdb.d",
×
277
                        },
×
278
                }
×
279
        }
×
280
        healthCheck, err := opts.ContainerEngine.ImageHealthCheck(ctx, opts.MongodDockerImageName())
1✔
281
        if err != nil {
1✔
282
                return fmt.Errorf("%w: %w", errInspectHealthCheck, err)
×
283
        }
×
284

285
        // Temporary fix until https://github.com/containers/podman/issues/18904 is closed
286
        if healthCheck == nil {
1✔
287
                const HealthcheckInterval = 30
×
288
                const HealthStartPeriod = 1
×
289
                const HealthTimeout = 30
×
290
                const HealthRetries = 3
×
291

×
292
                flags.HealthCmd = &[]string{"/usr/local/bin/runner", "healthcheck"}
×
293
                flags.HealthInterval = pointer.Get(HealthcheckInterval * time.Second)
×
294
                flags.HealthStartPeriod = pointer.Get(HealthStartPeriod * time.Second)
×
295
                flags.HealthTimeout = pointer.Get(HealthTimeout * time.Second)
×
296
                flags.HealthRetries = pointer.Get(HealthRetries)
×
297
        }
×
298

299
        _, err = opts.ContainerEngine.ContainerRun(ctx, opts.MongodDockerImageName(), &flags)
1✔
300
        if err != nil {
2✔
301
                _, _ = log.Debugf("Error encountered while trying to run container '%s': %s\n", opts.LocalMongodHostname(), err.Error())
1✔
302
                if opts.isDiskSpaceError(err) {
2✔
303
                        return fmt.Errorf("%w: %w", errRunContainer, errInsufficientDiskSpace)
1✔
304
                }
1✔
305
                return fmt.Errorf("%w: %w", errRunContainer, err)
×
306
        }
307

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

1✔
311
        err = opts.WaitForHealthyDeployment(ctx, healthyDeploymentTimeout)
1✔
312
        if err != nil && log.IsDebugLevel() {
2✔
313
                logs, err := opts.ContainerEngine.ContainerLogs(ctx, opts.LocalMongodHostname())
1✔
314
                if err != nil {
1✔
315
                        _, _ = log.Debugf("Container unhealthy (hostname=%s), failed to get container logs : %s\n", opts.LocalMongodHostname(), err)
×
316
                } else {
1✔
317
                        _, _ = log.Debugf("Container unhealthy (hostname=%s), container logs:\n", opts.LocalMongodHostname())
1✔
318
                        for _, logLine := range logs {
2✔
319
                                _, _ = log.Debugln(logLine)
1✔
320
                        }
1✔
321
                }
322
        }
323

324
        return err
1✔
325
}
326

327
func (opts *SetupOpts) WaitForHealthyDeployment(ctx context.Context, duration time.Duration) error {
1✔
328
        start := time.Now()
1✔
329

1✔
330
        for {
2✔
331
                if time.Since(start) > duration {
1✔
332
                        return errHealthCheckTimeout
×
333
                }
×
334

335
                status, err := opts.ContainerEngine.ContainerHealthStatus(ctx, opts.LocalMongodHostname())
1✔
336
                if err != nil {
1✔
337
                        return fmt.Errorf("%w: %w", errQueryHealthCheckStatus, err)
×
338
                }
×
339

340
                switch status {
1✔
341
                case container.DockerHealthcheckStatusHealthy:
1✔
342
                        return nil
1✔
343
                case container.DockerHealthcheckStatusUnhealthy:
1✔
344
                        return errDeploymentUnhealthy
1✔
345
                case container.DockerHealthcheckStatusNone:
×
346
                        return errDeploymentNoHealthCheck
×
347
                case container.DockerHealthcheckStatusStarting:
×
348
                        time.Sleep(1 * time.Second)
×
349
                }
350
        }
351
}
352

353
func (opts *SetupOpts) validateLocalDeploymentsSettings(containers []container.Container) error {
1✔
354
        mongodContainerName := opts.LocalMongodHostname()
1✔
355
        for _, c := range containers {
2✔
356
                for _, n := range c.Names {
2✔
357
                        if n == mongodContainerName {
2✔
358
                                return fmt.Errorf("%w: \"%s\", state:\"%s\"", ErrDeploymentExists, opts.DeploymentName, c.State)
1✔
359
                        }
1✔
360
                }
361
        }
362

363
        return nil
1✔
364
}
365

366
func (opts *SetupOpts) promptSettings() error {
×
367
        p := &survey.Select{
×
368
                Message: "How do you want to set up your local Atlas deployment?",
×
369
                Options: settingOptions,
×
370
                Default: opts.settings,
×
371
                Description: func(value string, _ int) string {
×
372
                        return settingsDescription[value]
×
373
                },
×
374
        }
375

376
        return telemetry.TrackAskOne(p, &opts.settings, nil)
×
377
}
378

379
func (opts *SetupOpts) generateDeploymentName() {
×
380
        opts.DeploymentName = fmt.Sprintf("local%v", rand.Intn(10000)) //nolint // no need for crypto here
×
381
}
×
382

383
func (opts *SetupOpts) promptDeploymentName() error {
×
384
        p := &survey.Input{
×
385
                Message: "Deployment Name [You can't change this value later]",
×
386
                Help:    usage.ClusterName,
×
387
                Default: opts.DeploymentName,
×
388
        }
×
389

×
390
        return telemetry.TrackAskOne(p, &opts.DeploymentName, survey.WithValidator(func(ans any) error {
×
391
                name, _ := ans.(string)
×
392
                return options.ValidateDeploymentName(name)
×
393
        }))
×
394
}
395

396
func (opts *SetupOpts) promptMdbVersion() error {
×
397
        p := &survey.Select{
×
398
                Message: "Major MongoDB Version (latest minor version will be used)",
×
399
                Options: mdbMajorVersions,
×
400
                Default: opts.MdbVersion,
×
401
                Help:    "Major MongoDB Version for the deployment. Atlas CLI applies the latest minor version available.",
×
402
        }
×
403

×
404
        return telemetry.TrackAskOne(p, &opts.MdbVersion, nil)
×
405
}
×
406

407
func checkPort(p int) error {
×
408
        lc := net.ListenConfig{}
×
409
        server, err := lc.Listen(context.Background(), "tcp", fmt.Sprintf("localhost:%d", p))
×
410
        if err != nil {
×
411
                return fmt.Errorf("%w: %d", errPortNotAvailable, p)
×
412
        }
×
413
        _ = server.Close()
×
414
        return nil
×
415
}
416

417
func validatePort(p int) error {
×
418
        if p <= 0 || p > 65535 {
×
419
                return errPortOutOfRange
×
420
        }
×
421
        return checkPort(p)
×
422
}
423

424
func (opts *SetupOpts) promptPort() error {
×
425
        exportPort := autoassignPort
×
426
        if opts.Port != 0 {
×
427
                exportPort = strconv.Itoa(opts.Port)
×
428
        }
×
429

430
        p := &survey.Input{
×
431
                Message: "Specify a port",
×
432
                Default: exportPort,
×
433
        }
×
434

×
435
        err := telemetry.TrackAskOne(p, &exportPort, survey.WithValidator(func(ans any) error {
×
436
                input, _ := ans.(string)
×
437
                if input == autoassignPort {
×
438
                        return nil
×
439
                }
×
440
                value, err := strconv.Atoi(input)
×
441
                if err != nil {
×
442
                        return errMustBeInt
×
443
                }
×
444

445
                return validatePort(value)
×
446
        }))
447

448
        if err != nil {
×
449
                return err
×
450
        }
×
451

452
        if exportPort != autoassignPort {
×
453
                if opts.Port, err = strconv.Atoi(exportPort); err != nil {
×
454
                        return err
×
455
                }
×
456
        }
457

458
        return nil
×
459
}
460

461
func (opts *SetupOpts) validateDeploymentTypeFlag() error {
1✔
462
        if opts.DeploymentType == "" && opts.force {
1✔
463
                return errFlagTypeRequired
×
464
        }
×
465

466
        if !opts.IsLocalDeploymentType() && opts.bindIPAll {
1✔
467
                return errIncompatibleDeploymentType
×
468
        }
×
469

470
        if opts.DeploymentType != "" && !strings.EqualFold(opts.DeploymentType, connect.AtlasCluster) && !strings.EqualFold(opts.DeploymentType, options.LocalCluster) {
1✔
471
                return fmt.Errorf("%w: %s", errInvalidDeploymentType, opts.DeploymentType)
×
472
        }
×
473

474
        return nil
1✔
475
}
476

477
func (opts *SetupOpts) validateBindIPAllFlag() error {
1✔
478
        if !opts.bindIPAll {
2✔
479
                return nil
1✔
480
        }
1✔
481

482
        if opts.force && (opts.DeploymentType == "" || opts.DBUsername == "" || opts.DBUserPassword == "") {
×
483
                return errFlagsTypeAndAuthRequired
×
484
        }
×
485

486
        if opts.DBUsername == "" {
×
487
                return errFlagUsernameRequired
×
488
        }
×
489

490
        return nil
×
491
}
492

493
func (opts *SetupOpts) validateFlags() error {
1✔
494
        if err := opts.validateDeploymentTypeFlag(); err != nil {
1✔
495
                return err
×
496
        }
×
497

498
        if opts.DeploymentName != "" {
2✔
499
                if err := options.ValidateDeploymentName(opts.DeploymentName); err != nil {
1✔
500
                        return err
×
501
                }
×
502
        }
503

504
        if opts.MdbVersion != "" && !slices.Contains(mdbVersions, opts.MdbVersion) {
2✔
505
                return fmt.Errorf("%w: %s", errInvalidMongoDBVersion, opts.MdbVersion)
1✔
506
        }
1✔
507

508
        if opts.Port != 0 {
1✔
509
                if err := validatePort(opts.Port); err != nil {
×
510
                        return err
×
511
                }
×
512
        }
513

514
        if opts.connectWith != "" && !search.StringInSliceFold(connectWithOptions, opts.connectWith) {
1✔
515
                return fmt.Errorf("%w: %s", errUnsupportedConnectWith, opts.connectWith)
×
516
        }
×
517

518
        if opts.initdb != "" {
1✔
519
                info, err := os.Stat(opts.initdb)
×
520
                if err != nil {
×
521
                        return fmt.Errorf("%w: %w", errInvalidInit, err)
×
522
                }
×
523
                if !info.IsDir() {
×
524
                        return errInitMustBeDir
×
525
                }
×
526
                opts.initdb, err = filepath.Abs(opts.initdb)
×
527
                if err != nil {
×
528
                        return fmt.Errorf("%w: %w", errInvalidInit, err)
×
529
                }
×
530
        }
531

532
        return opts.validateBindIPAllFlag()
1✔
533
}
534

535
func (opts *SetupOpts) promptLocalAdminPassword() error {
×
536
        if !opts.IsTerminalInput() {
×
537
                _, err := fmt.Fscanln(opts.InReader, &opts.DBUserPassword)
×
538
                return err
×
539
        }
×
540

541
        p := &survey.Password{
×
542
                Message: "Password for authenticating to local deployment",
×
543
        }
×
544
        return telemetry.TrackAskOne(p, &opts.DBUserPassword)
×
545
}
546

547
func (opts *SetupOpts) setDefaultSettings() error {
1✔
548
        opts.settings = defaultSettings
1✔
549
        defaultValuesSet := false
1✔
550

1✔
551
        if opts.DeploymentName == "" {
1✔
552
                opts.generateDeploymentName()
×
553
                defaultValuesSet = true
×
554
        }
×
555

556
        if opts.MdbVersion == "" {
2✔
557
                opts.MdbVersion = mdb8
1✔
558
                defaultValuesSet = true
1✔
559
        }
1✔
560

561
        if opts.Port == 0 {
2✔
562
                defaultValuesSet = true
1✔
563
        }
1✔
564

565
        if defaultValuesSet {
2✔
566
                if err := templatewriter.Print(os.Stderr, `
1✔
567
[Default Settings]
1✔
568
Deployment Name        {{.DeploymentName}}
1✔
569
MongoDB Major Version        {{.MdbVersion}} (latest minor version)
1✔
570

1✔
571
`, opts); err != nil {
1✔
572
                        return err
×
573
                }
×
574
                if !opts.force {
1✔
575
                        if err := opts.promptSettings(); err != nil {
×
576
                                return err
×
577
                        }
×
578
                }
579
        }
580

581
        return nil
1✔
582
}
583

584
func (opts *SetupOpts) promptConnect() error {
×
585
        p := &survey.Select{
×
586
                Message: fmt.Sprintf("How would you like to connect to %s?", opts.DeploymentName),
×
587
                Options: connectWithOptions,
×
588
                Description: func(value string, _ int) string {
×
589
                        return connectWithDescription[value]
×
590
                },
×
591
        }
592

593
        return telemetry.TrackAskOne(p, &opts.connectWith, nil)
×
594
}
595

596
func (opts *SetupOpts) runConnectWith(cs string) error {
1✔
597
        if opts.connectWith == "" {
2✔
598
                if opts.force {
2✔
599
                        opts.connectWith = skipConnect
1✔
600
                } else {
1✔
601
                        if err := opts.promptConnect(); err != nil {
×
602
                                return err
×
603
                        }
×
604
                }
605
        }
606

607
        switch opts.connectWith {
1✔
608
        case skipConnect:
1✔
609
                _, _ = fmt.Fprintln(os.Stderr, "connection skipped")
1✔
610
        case connect.ConnectWithCompass:
×
611
                if !compass.Detect() {
×
612
                        return compass.ErrCompassNotInstalled
×
613
                }
×
614
                if _, err := log.Warningln("Launching MongoDB Compass..."); err != nil {
×
615
                        return err
×
616
                }
×
617
                return compass.Run("", "", cs)
×
618
        case connect.ConnectWithMongosh:
×
619
                if !mongosh.Detect() {
×
620
                        return mongosh.ErrMongoshNotInstalled
×
621
                }
×
622
                return mongosh.Run("", "", cs)
×
623
        case connect.ConnectWithVsCode:
×
624
                if !vscode.Detect() {
×
625
                        return vscode.ErrVsCodeCliNotInstalled
×
626
                }
×
627
                if _, err := log.Warningln("Launching VsCode..."); err != nil {
×
628
                        return err
×
629
                }
×
630
                return vscode.SaveConnection(cs, opts.DeploymentName, opts.DeploymentType)
×
631
        }
632

633
        return nil
1✔
634
}
635

636
func (opts *SetupOpts) validateAndPrompt() error {
1✔
637
        if err := opts.validateFlags(); err != nil {
1✔
638
                return err
×
639
        }
×
640

641
        if err := opts.ValidateAndPromptDeploymentType(); err != nil {
1✔
642
                return err
×
643
        }
×
644

645
        // Defer prompts to Atlas command
646
        if opts.IsAtlasDeploymentType() {
1✔
647
                return nil
×
648
        }
×
649

650
        if opts.DBUsername != "" && opts.DBUserPassword == "" {
1✔
651
                if err := opts.promptLocalAdminPassword(); err != nil {
×
652
                        return err
×
653
                }
×
654
        }
655

656
        if err := opts.setDefaultSettings(); err != nil {
1✔
657
                return err
×
658
        }
×
659

660
        switch opts.settings {
1✔
661
        case cancelSettings:
×
662
                return errCancel
×
663
        case customSettings:
×
664
                if err := opts.promptDeploymentName(); err != nil {
×
665
                        return err
×
666
                }
×
667

668
                if err := opts.promptMdbVersion(); err != nil {
×
669
                        return err
×
670
                }
×
671

672
                if err := opts.promptPort(); err != nil {
×
673
                        return err
×
674
                }
×
675
        }
676

677
        return nil
1✔
678
}
679

680
func (opts *SetupOpts) runLocal(ctx context.Context) error {
1✔
681
        if err := opts.LocalDeploymentPreRun(ctx); err != nil {
1✔
682
                return err
×
683
        }
×
684

685
        if err := opts.createLocalDeployment(ctx); err != nil {
2✔
686
                // in case the deployment already exists we shouldn't delete it
1✔
687
                if !errors.Is(err, ErrDeploymentExists) {
2✔
688
                        _ = opts.RemoveLocal(ctx)
1✔
689
                }
1✔
690
                return err
1✔
691
        }
692

693
        cs, err := opts.ConnectionString(ctx)
1✔
694
        if err != nil {
1✔
695
                return err
×
696
        }
×
697

698
        _, _ = log.Warningln("Deployment created!")
1✔
699
        _, _ = fmt.Fprintf(opts.OutWriter, `Connection string: "%s"
1✔
700
`, cs)
1✔
701
        _, _ = log.Warningln("")
1✔
702

1✔
703
        return opts.runConnectWith(cs)
1✔
704
}
705

706
func (opts *SetupOpts) runAtlas(ctx context.Context) error {
×
707
        s := setup.Builder()
×
708

×
709
        // remove global flags and unknown flags
×
710
        var newArgs []string
×
711
        _, _ = log.Debugf("Removing flags and args from original args %s\n", os.Args)
×
712

×
713
        flagstoRemove := map[string]string{
×
714
                flag.TypeFlag: "1",
×
715
        }
×
716

×
717
        newArgs, err := workflows.RemoveFlagsAndArgs(flagstoRemove, map[string]bool{opts.DeploymentName: true}, os.Args)
×
718
        if err != nil {
×
719
                return err
×
720
        }
×
721

722
        // replace deployment name with cluster name
723
        if opts.DeploymentName != "" {
×
724
                newArgs = append(newArgs, "--"+flag.ClusterName, opts.DeploymentName)
×
725
        }
×
726

727
        // update args
728
        s.SetArgs(newArgs)
×
729

×
730
        // run atlas setup
×
731
        _, _ = log.Debugf("Starting to run atlas setup with args %s\n", newArgs)
×
732
        _, err = s.ExecuteContextC(ctx)
×
733
        return err
×
734
}
735

736
func (opts *SetupOpts) Run(ctx context.Context) error {
1✔
737
        if err := opts.validateAndPrompt(); err != nil {
1✔
738
                if errors.Is(err, errCancel) {
×
739
                        _, _ = log.Warningln(err)
×
740
                        return nil
×
741
                }
×
742

743
                return err
×
744
        }
745

746
        if strings.EqualFold(options.LocalCluster, opts.DeploymentType) {
2✔
747
                return opts.runLocal(ctx)
1✔
748
        }
1✔
749

750
        return opts.runAtlas(ctx)
×
751
}
752

753
func (opts *SetupOpts) PostRun() {
1✔
754
        opts.DeploymentTelemetry.AppendDeploymentType()
1✔
755
        opts.DeploymentTelemetry.AppendDeploymentUUID()
1✔
756
}
1✔
757

758
func (opts *SetupOpts) validateTier() error {
×
759
        opts.atlasSetup.Tier = strings.ToUpper(opts.atlasSetup.Tier)
×
760
        if opts.atlasSetup.Tier == atlasM2 || opts.atlasSetup.Tier == atlasM5 {
×
761
                _, _ = fmt.Fprintf(os.Stderr, deprecateMessageSharedTier, opts.atlasSetup.Tier)
×
762
        }
×
763
        return nil
×
764
}
765

766
// SetupBuilder builds a cobra.Command that can run as:
767
// atlas deployments setup.
768
func SetupBuilder() *cobra.Command {
×
769
        opts := &SetupOpts{
×
770
                settings:   defaultSettings,
×
771
                atlasSetup: &setup.Opts{},
×
772
        }
×
773
        cmd := &cobra.Command{
×
774
                Use:   "setup [deploymentName]",
×
775
                Short: "Create a local deployment.",
×
776
                Long:  "To learn more about local atlas deployments, see https://www.mongodb.com/docs/atlas/cli/current/atlas-cli-deploy-local/",
×
777
                Deprecated: `This command has been deprecated and will be removed in a future release.
×
778

×
779
Please switch to the new command structure based on your target environment:
×
780
- For Atlas (cloud) deployments, use 'atlas setup'.
×
781
- For Local (Docker) deployments, use 'atlas local setup'.
×
782
`,
×
783
                Args:    require.MaximumNArgs(1),
×
784
                GroupID: "all",
×
785
                Annotations: map[string]string{
×
786
                        "deploymentNameDesc": "Name of the deployment that you want to set up.",
×
787
                },
×
788
                PreRunE: func(cmd *cobra.Command, args []string) error {
×
789
                        if len(args) == 1 {
×
790
                                opts.DeploymentName = args[0]
×
791
                        }
×
792

793
                        opts.force = opts.atlasSetup.Confirm
×
794
                        opts.DBUsername = opts.atlasSetup.DBUsername
×
795
                        opts.DBUserPassword = opts.atlasSetup.DBUserPassword
×
796

×
797
                        return opts.PreRunE(
×
798
                                opts.validateTier,
×
799
                                opts.InitOutput(cmd.OutOrStdout(), ""),
×
800
                                opts.InitInput(cmd.InOrStdin()),
×
801
                                opts.InitStore(cmd.Context(), cmd.OutOrStdout()),
×
802
                                opts.initMongoDBClient,
×
803
                        )
×
804
                },
805
                RunE: func(cmd *cobra.Command, _ []string) error {
×
806
                        return opts.Run(cmd.Context())
×
807
                },
×
808
                PostRun: func(_ *cobra.Command, _ []string) {
×
809
                        opts.PostRun()
×
810
                },
×
811
        }
812

813
        // Local and Atlas
814
        cmd.Flags().StringVar(&opts.DeploymentType, flag.TypeFlag, "", usage.DeploymentTypeSetup)
×
815
        cmd.Flags().StringVar(&opts.MdbVersion, flag.MDBVersion, "", usage.DeploymentMDBVersion)
×
816
        cmd.Flags().StringVar(&opts.connectWith, flag.ConnectWith, "", usage.ConnectWithSetup)
×
817

×
818
        // Local only
×
819
        cmd.Flags().IntVar(&opts.Port, flag.Port, 0, usage.MongodPort)
×
820
        cmd.Flags().BoolVar(&opts.bindIPAll, flag.BindIPAll, false, usage.BindIPAll)
×
821
        cmd.Flags().StringVar(&opts.initdb, flag.InitDB, "", usage.InitDB)
×
822

×
823
        // Atlas only
×
824
        opts.atlasSetup.SetupAtlasFlags(cmd)
×
825
        opts.atlasSetup.SetupFlowFlags(cmd)
×
826
        cmd.Flags().Lookup(flag.Region).Usage = usage.DeploymentRegion
×
827
        cmd.Flags().Lookup(flag.Tag).Usage = usage.DeploymentTag
×
828
        cmd.Flags().Lookup(flag.Tier).Usage = usage.DeploymentTier
×
829
        cmd.Flags().Lookup(flag.EnableTerminationProtection).Usage = usage.EnableTerminationProtectionForDeployment
×
830
        cmd.Flags().Lookup(flag.SkipSampleData).Usage = usage.SkipSampleDataDeployment
×
831
        cmd.Flags().Lookup(flag.Force).Usage = usage.ForceDeploymentsSetup
×
832

×
833
        _ = cmd.RegisterFlagCompletionFunc(flag.MDBVersion, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
×
834
                return mdbVersions, cobra.ShellCompDirectiveDefault
×
835
        })
×
836
        _ = cmd.RegisterFlagCompletionFunc(flag.TypeFlag, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
×
837
                return options.DeploymentTypeOptions, cobra.ShellCompDirectiveDefault
×
838
        })
×
839
        _ = cmd.RegisterFlagCompletionFunc(flag.ConnectWith, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
×
840
                return connectWithOptions, cobra.ShellCompDirectiveDefault
×
841
        })
×
842
        return cmd
×
843
}
844

845
func (opts *SetupOpts) start() {
1✔
846
        if opts.IsTerminal() {
1✔
847
                opts.s = spinner.New(spinner.CharSets[9], spinnerSpeed)
×
848
                _ = opts.s.Color("cyan", "bold")
×
849
                opts.s.Start()
×
850
        }
×
851
}
852

853
func (opts *SetupOpts) stop() {
1✔
854
        if opts.IsTerminal() {
1✔
855
                opts.s.Stop()
×
856
        }
×
857
}
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