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

mongodb / mongodb-atlas-cli / 26228926989

21 May 2026 01:27PM UTC coverage: 22.63% (-41.6%) from 64.198%
26228926989

push

github

apix-bot[bot]
Update compliance report for v1.55.0

8987 of 39713 relevant lines covered (22.63%)

0.25 hits per line

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

41.98
/internal/cli/api/api.go
1
// Copyright 2024 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 api
16

17
import (
18
        "bytes"
19
        "errors"
20
        "fmt"
21
        "io"
22
        "os"
23
        "slices"
24
        "strings"
25
        "time"
26

27
        "github.com/iancoleman/strcase"
28
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/api"
29
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli"
30
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/flag"
31
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/log"
32
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/usage"
33
        shared_api "github.com/mongodb/mongodb-atlas-cli/atlascli/tools/shared/api"
34
        "github.com/spf13/cobra"
35
        "github.com/spf13/pflag"
36
)
37

38
var (
39
        ErrFailedToSetUntouchedFlags         = errors.New("failed to set untouched flags")
40
        ErrServerReturnedAnErrorResponseCode = errors.New("server returned an error response code")
41
        ErrAPICommandsHasNoVersions          = errors.New("api command has no versions")
42
        ErrFormattingOutput                  = errors.New("error formatting output")
43
        ErrRunningWatcher                    = errors.New("error while running watcher")
44
        BinaryOutputTypes                    = []string{"gzip"}
45
)
46

47
func Builder() *cobra.Command {
×
48
        apiCmd := createRootAPICommand()
×
49

×
50
        for _, tag := range api.Commands {
×
51
                tagCmd := createAPICommandGroupToCobraCommand(tag)
×
52

×
53
                for _, command := range tag.Commands {
×
54
                        cobraCommand, err := convertAPIToCobraCommand(command)
×
55
                        // err should always be nil, errors happening here should be covered by the generate api commands tool
×
56
                        // if err != nil there is a bug in the converter tool
×
57
                        if err != nil {
×
58
                                _, _ = log.Warningf("failed to add command for operationId: %s, err: %s", command.OperationID, err)
×
59
                                continue
×
60
                        }
61

62
                        tagCmd.AddCommand(cobraCommand)
×
63
                }
64

65
                apiCmd.AddCommand(tagCmd)
×
66
        }
67

68
        return apiCmd
×
69
}
70

71
func createRootAPICommand() *cobra.Command {
×
72
        rootCmd := &cobra.Command{
×
73
                Use:   "api",
×
74
                Short: `Access all features of the Atlas Administration API through the Atlas CLI by using the 'atlas api <tag> <operationId>' command.`,
×
75
                Long: `This feature streamlines script development by letting you interact directly with any Atlas Administration API endpoint through the Atlas CLI.
×
76

×
77
For more information on
×
78
- Atlas Administration API see: https://www.mongodb.com/docs/api/doc/atlas-admin-api-v2/
×
79
- Getting started with the Atlas Administration API: https://www.mongodb.com/docs/atlas/configure-api-access/#std-label-atlas-admin-api-access`,
×
80
        }
×
81

×
82
        rootCmd.SetHelpTemplate(cli.APICommandHelpTemplate)
×
83

×
84
        return rootCmd
×
85
}
×
86

87
func createAPICommandGroupToCobraCommand(group shared_api.Group) *cobra.Command {
×
88
        groupName := strcase.ToLowerCamel(group.Name)
×
89
        shortDescription, longDescription := splitShortAndLongDescription(group.Description)
×
90

×
91
        return &cobra.Command{
×
92
                Use:   groupName,
×
93
                Short: shortDescription,
×
94
                Long:  longDescription,
×
95
        }
×
96
}
×
97

98
//nolint:gocyclo
99
func convertAPIToCobraCommand(command shared_api.Command) (*cobra.Command, error) {
1✔
100
        // command properties
1✔
101
        commandName := strcase.ToLowerCamel(command.OperationID)
1✔
102
        commandOperationID := command.OperationID
1✔
103
        commandAliases := command.Aliases
1✔
104

1✔
105
        if command.ShortOperationID != "" {
2✔
106
                // Add original operation ID to aliases
1✔
107
                commandAliases = append(commandAliases, commandName)
1✔
108
                // Use shortOperationID as the command name
1✔
109
                commandName = strcase.ToLowerCamel(command.ShortOperationID)
1✔
110
        }
1✔
111

112
        shortDescription, longDescription := splitShortAndLongDescription(command.Description)
1✔
113

1✔
114
        // flag values
1✔
115
        file := ""
1✔
116
        format := ""
1✔
117
        outputFile := ""
1✔
118
        version, err := defaultAPIVersion(command)
1✔
119
        watch := false
1✔
120
        watchTimeout := int64(0)
1✔
121
        if err != nil {
2✔
122
                return nil, err
1✔
123
        }
1✔
124

125
        cmd := &cobra.Command{
1✔
126
                Use:     commandName,
1✔
127
                Aliases: commandAliases,
1✔
128
                Short:   shortDescription,
1✔
129
                Long:    longDescription,
1✔
130
                Annotations: map[string]string{
1✔
131
                        "operationId": commandOperationID,
1✔
132
                },
1✔
133
                PreRunE: func(cmd *cobra.Command, _ []string) error {
1✔
134
                        // Go through all commands that have not been touched/modified by the user and try to populate them from the users profile
×
135
                        // Common usecases:
×
136
                        // - set orgId
×
137
                        // - set projectId
×
138
                        // - default api version
×
139
                        if err := setUnTouchedFlags(NewProfileFlagValueProviderForDefaultProfile(), cmd); err != nil {
×
140
                                return errors.Join(ErrFailedToSetUntouchedFlags, err)
×
141
                        }
×
142

143
                        // Remind the user to pin their api command to a specific version to avoid breaking changes
144
                        remindUserToPinVersion(cmd)
×
145

×
146
                        // Reset version to default if unsupported version was selected
×
147
                        // This can happen when the profile contains a default version which is not supported for a specific endpoint
×
148
                        ensureVersionIsSupported(command, &version)
×
149

×
150
                        // Print a warning if the version is a preview version
×
151
                        printPreviewWarning(command, &version)
×
152

×
153
                        // Print a warning if the version has a sunset date or is deprecated
×
154
                        printDeprecatedVersionWarning(command, &version)
×
155

×
156
                        // Detect if stdout is being piped (atlas api myTag myOperationId > output.json)
×
157
                        isPiped, err := IsStdOutPiped()
×
158
                        if err != nil {
×
159
                                return err
×
160
                        }
×
161

162
                        // If the selected output format is binary and stdout is not being piped, mark output as required
163
                        // This ensures that the console isn't flooded with binary contents (for example gzip contents)
164
                        if slices.Contains(BinaryOutputTypes, format) && !isPiped {
×
165
                                if err := cmd.MarkFlagRequired(flag.Output); err != nil {
×
166
                                        return err
×
167
                                }
×
168
                        }
169

170
                        return nil
×
171
                },
172
                RunE: func(cmd *cobra.Command, _ []string) error {
×
173
                        // Get the request input if needed
×
174
                        // This is needed for most PATCH/POST/PUT requests
×
175
                        var content io.ReadCloser
×
176
                        if needsFileFlag(command) {
×
177
                                content, err = handleInput(cmd)
×
178
                                if err != nil {
×
179
                                        return err
×
180
                                }
×
181
                                defer content.Close()
×
182
                        }
183

184
                        // Create a new executor
185
                        // This is the piece of code which knows how to execute api.Commands
186
                        formatter := api.NewFormatter()
×
187
                        executor, err := api.NewDefaultExecutor(formatter)
×
188
                        if err != nil {
×
189
                                return err
×
190
                        }
×
191

192
                        // Convert the version string into an api version
193
                        apiVersion, err := shared_api.ParseVersion(version)
×
194
                        if err != nil {
×
195
                                // This should never happen, the version is already validated in the prerun function
×
196
                                return err
×
197
                        }
×
198

199
                        // Convert the api command + cobra command into a api command request
200
                        commandRequest, err := NewCommandRequestFromCobraCommand(cmd, command, content, format, apiVersion)
×
201
                        if err != nil {
×
202
                                return err
×
203
                        }
×
204

205
                        // Execute the api command request
206
                        // This function will return an error if the http request failed
207
                        // When the http request returns a non-success code error will still be nil
208
                        result, err := executor.ExecuteCommand(cmd.Context(), *commandRequest)
×
209
                        if err != nil {
×
210
                                return err
×
211
                        }
×
212

213
                        // Properly free up result output
214
                        defer result.Output.Close()
×
215

×
216
                        // Output that will be send to stdout/file
×
217
                        responseOutput := result.Output
×
218

×
219
                        // Response body used for the watcher
×
220
                        var watchResponseBody []byte
×
221

×
222
                        // If the response was successful, handle --format
×
223
                        if result.IsSuccess {
×
224
                                // If we're watching, we need to cache the original output before formatting so we don't read twice from the same reader
×
225
                                // In case we're not watching the http output will be piped straight into the formatter, which should be a little more memory efficient
×
226
                                if watch {
×
227
                                        responseBytes, err := io.ReadAll(result.Output)
×
228
                                        if err != nil {
×
229
                                                return errors.Join(errors.New("failed to read output"), err)
×
230
                                        }
×
231
                                        watchResponseBody = responseBytes
×
232

×
233
                                        // Create a new reader for the formatter
×
234
                                        responseOutput = io.NopCloser(bytes.NewReader(responseBytes))
×
235
                                }
236

237
                                formattedOutput, err := formatter.Format(format, responseOutput)
×
238
                                if err != nil {
×
239
                                        return errors.Join(ErrFormattingOutput, err)
×
240
                                }
×
241

242
                                responseOutput = formattedOutput
×
243
                        }
244

245
                        // Determine where to write the
246
                        output, err := getOutputWriteCloser(outputFile)
×
247
                        if err != nil {
×
248
                                return err
×
249
                        }
×
250
                        // Properly free up output
251
                        defer output.Close()
×
252

×
253
                        // Write the output
×
254
                        _, err = io.Copy(output, responseOutput)
×
255
                        if err != nil {
×
256
                                return err
×
257
                        }
×
258

259
                        // In case the http status code was non-success
260
                        // Return an error, this causes the CLI to exit with a non-zero exit code while still running all telemetry code
261
                        if !result.IsSuccess {
×
262
                                return ErrServerReturnedAnErrorResponseCode
×
263
                        }
×
264

265
                        // In case watcher is set we wait for the watcher to succeed before we exit the program
266
                        if watch {
×
267
                                // Create a new watcher
×
268
                                watcher, err := NewWatcher(executor, commandRequest.Parameters, watchResponseBody, *command.Watcher)
×
269
                                if err != nil {
×
270
                                        return err
×
271
                                }
×
272

273
                                // Wait until we're in the desired state or until an error occures when watching
274
                                if err := watcher.Wait(cmd.Context(), time.Duration(watchTimeout)); err != nil {
×
275
                                        return errors.Join(ErrRunningWatcher, err)
×
276
                                }
×
277
                        }
278

279
                        return nil
×
280
                },
281
        }
282

283
        // Add deprecation message if all versions are sunset
284
        addDeprecationMessageIfNeeded(cmd, command)
1✔
285

1✔
286
        // Common flags
1✔
287
        addWatchFlagIfNeeded(cmd, command, &watch, &watchTimeout)
1✔
288
        addVersionFlag(cmd, command, &version)
1✔
289

1✔
290
        if needsFileFlag(command) {
2✔
291
                cmd.Flags().StringVar(&file, flag.File, "", "path to your API request file. Leave empty to use standard input instead - you must provide one or the other, but not both.")
1✔
292
        }
1✔
293

294
        // Add output flags:
295
        // - `--output`: desired output format, translates to ContentType. Can also be a go template
296
        // - `--output-file`: file where we want to write the output to
297
        if err := addOutputFlags(cmd, command, &format, &outputFile); err != nil {
1✔
298
                return nil, err
×
299
        }
×
300

301
        // Add URL parameters as flags
302
        if err := addParameters(cmd, command.RequestParameters.URLParameters); err != nil {
1✔
303
                return nil, err
×
304
        }
×
305

306
        // Add query parameters as flags
307
        if err := addParameters(cmd, command.RequestParameters.QueryParameters); err != nil {
1✔
308
                return nil, err
×
309
        }
×
310

311
        // Handle parameter aliases
312
        cmd.Flags().SetNormalizeFunc(normalizeFlagFunc(command))
1✔
313

1✔
314
        return cmd, nil
1✔
315
}
316

317
func normalizeFlagFunc(command shared_api.Command) func(f *pflag.FlagSet, name string) pflag.NormalizedName {
1✔
318
        return func(_ *pflag.FlagSet, name string) pflag.NormalizedName {
2✔
319
                name = normalizeFlagName(command.RequestParameters.QueryParameters, name)
1✔
320
                name = normalizeFlagName(command.RequestParameters.URLParameters, name)
1✔
321

1✔
322
                return pflag.NormalizedName(name)
1✔
323
        }
1✔
324
}
325

326
func normalizeFlagName(parameters []shared_api.Parameter, name string) string {
1✔
327
        for _, parameter := range parameters {
1✔
328
                if slices.Contains(parameter.Aliases, name) {
×
329
                        return parameter.Name
×
330
                }
×
331
        }
332

333
        return name
1✔
334
}
335

336
func addParameters(cmd *cobra.Command, parameters []shared_api.Parameter) error {
1✔
337
        for _, parameter := range parameters {
2✔
338
                if err := addFlag(cmd, parameter); err != nil {
2✔
339
                        return err
1✔
340
                }
1✔
341
        }
342

343
        return nil
1✔
344
}
345

346
func setUnTouchedFlags(flagValueProvider FlagValueProvider, cmd *cobra.Command) error {
1✔
347
        var visitErr error
1✔
348
        cmd.NonInheritedFlags().VisitAll(func(f *pflag.Flag) {
2✔
349
                // There is no VisitAll which accepts an error
1✔
350
                // because of this we set visitErr when an error occures
1✔
351
                // if visitErr != nil we stop processing other flags
1✔
352
                if visitErr != nil {
1✔
353
                        return
×
354
                }
×
355

356
                // Only update flags thave have been un-touched by the user
357
                if !f.Changed {
2✔
358
                        value, err := flagValueProvider.ValueForFlag(f.Name)
1✔
359
                        if err != nil {
1✔
360
                                visitErr = err
×
361
                        }
×
362

363
                        // If we get a value back from the FlagValueProvider:
364
                        // - Set the value
365
                        // - Mark the flag as changed, this is needed to mark required flags as set
366
                        if value != nil {
2✔
367
                                if err := f.Value.Set(*value); err != nil {
1✔
368
                                        visitErr = err
×
369
                                        return
×
370
                                }
×
371

372
                                f.Changed = true
1✔
373
                        }
374
                }
375
        })
376

377
        return visitErr
1✔
378
}
379

380
func handleInput(cmd *cobra.Command) (io.ReadCloser, error) {
×
381
        isPiped, err := IsStdInPiped()
×
382
        if err != nil {
×
383
                return nil, err
×
384
        }
×
385

386
        // If not piped, get the file flag
387
        filePath, err := cmd.Flags().GetString(flag.File)
×
388
        if err != nil {
×
389
                return nil, fmt.Errorf("error getting file flag: %w", err)
×
390
        }
×
391

392
        if isPiped {
×
393
                if filePath != "" {
×
394
                        return nil, errors.New("cannot use --file flag and also input from standard input")
×
395
                }
×
396
                // Use stdin as the input
397
                return os.Stdin, nil
×
398
        }
399

400
        // Require file flag if not piped
401
        if filePath == "" {
×
402
                return nil, errors.New("--file flag is required when not using piped input")
×
403
        }
×
404

405
        // Open the file
406
        file, err := os.Open(filePath)
×
407
        if err != nil {
×
408
                return nil, fmt.Errorf("error opening file %s: %w", filePath, err)
×
409
        }
×
410

411
        return file, nil
×
412
}
413

414
func IsStdInPiped() (bool, error) {
×
415
        return isPiped(os.Stdin)
×
416
}
×
417

418
func IsStdOutPiped() (bool, error) {
×
419
        return isPiped(os.Stdout)
×
420
}
×
421

422
func isPiped(file *os.File) (bool, error) {
×
423
        // Check if data is being piped to stdin
×
424
        info, err := file.Stat()
×
425
        if err != nil {
×
426
                return false, fmt.Errorf("isPiped, error checking: %w", err)
×
427
        }
×
428

429
        // Check if there's data in stdin (piped input)
430
        isPiped := (info.Mode() & os.ModeCharDevice) == 0
×
431

×
432
        return isPiped, nil
×
433
}
434

435
func defaultAPIVersion(command shared_api.Command) (string, error) {
1✔
436
        // Command versions are sorted by the generation tool
1✔
437
        nVersions := len(command.Versions)
1✔
438
        if nVersions == 0 {
2✔
439
                return "", ErrAPICommandsHasNoVersions
1✔
440
        }
1✔
441

442
        lastVersion := command.Versions[nVersions-1]
1✔
443
        return lastVersion.Version.String(), nil
1✔
444
}
445

446
func remindUserToPinVersion(cmd *cobra.Command) {
×
447
        versionFlag := cmd.Flag(flag.Version)
×
448
        // if we fail to get the version flag (which should never happen), then quit
×
449
        if versionFlag == nil {
×
450
                return
×
451
        }
×
452

453
        // check if the version flag is still in it's default state:
454
        // - not set by the user
455
        // - not set using api_version on the users profile
456
        // in that case, print a warning
457
        if !versionFlag.Changed {
×
458
                fmt.Fprintf(os.Stderr, "warning: using default API version '%s'; consider pinning a version to ensure consisentcy when updating the CLI\n", versionFlag.Value.String())
×
459
        }
×
460
}
461

462
func ensureVersionIsSupported(apiCommand shared_api.Command, versionString *string) {
×
463
        version, err := shared_api.ParseVersion(*versionString)
×
464

×
465
        // If the version is valid, check if it's supported
×
466
        if err == nil {
×
467
                for _, commandVersion := range apiCommand.Versions {
×
468
                        if commandVersion.Version.Equal(version) {
×
469
                                return
×
470
                        }
×
471
                }
472
        }
473

474
        // if we get here it means that the picked version is not supported
475
        defaultVersion, err := defaultAPIVersion(apiCommand)
×
476
        // if we fail to get a version (which should never happen), then quit
×
477
        if err != nil {
×
478
                fmt.Fprintf(os.Stderr, "error in 'ensureVersionIsSupported': default version has an invalid format '%s'\n", *versionString)
×
479
                return
×
480
        }
×
481

482
        fmt.Fprintf(os.Stderr, "warning: version '%s' is not supported for this endpoint, using default API version '%s'; consider pinning a version to ensure consisentcy when updating the CLI\n", *versionString, defaultVersion)
×
483
        *versionString = defaultVersion
×
484
}
485

486
func printPreviewWarning(apiCommand shared_api.Command, versionString *string) {
×
487
        version, err := shared_api.ParseVersion(*versionString)
×
488

×
489
        // If the version is invalid return, this should never happen
×
490
        if err != nil {
×
491
                fmt.Fprintf(os.Stderr, "error in 'printPreviewWarning': received an invalid version '%s'\n", *versionString)
×
492
                return
×
493
        }
×
494

495
        // If the version is not a preview version, return
496
        if version.StabilityLevel() != shared_api.StabilityLevelPreview {
×
497
                return
×
498
        }
×
499

500
        // Find the version in the command versions
501
        var commandVersion *shared_api.CommandVersion
×
502
        for _, cv := range apiCommand.Versions {
×
503
                if cv.Version.Equal(version) {
×
504
                        commandVersion = &cv
×
505
                        break
×
506
                }
507
        }
508

509
        // If the version is not found, return (should also never happen)
510
        if commandVersion == nil {
×
511
                return
×
512
        }
×
513

514
        if commandVersion.PublicPreview {
×
515
                fmt.Fprintf(os.Stderr, "warning: you've selected a public preview version of the endpoint, this version is subject to breaking changes.\n")
×
516
        } else {
×
517
                fmt.Fprintf(os.Stderr, "warning: you've selected a private preview version of the endpoint, this version might not be available for your account and is subject to breaking changes.\n")
×
518
        }
×
519
}
520

521
// printDeprecatedVersionWarning is a convenience wrapper that calls printDeprecatedVersionWarningWithTime with the current time.
522
func printDeprecatedVersionWarning(apiCommand shared_api.Command, versionString *string) {
×
523
        printDeprecatedVersionWarningWithTime(apiCommand, versionString, time.Now())
×
524
}
×
525

526
// printDeprecatedVersionWarningWithTime prints a warning if the version is deprecated or has a sunset date.
527
// only warn if the command is not fully deprecated, assume that if all versions are deprecated,
528
// then the command will be marked as deprecated in Cobra.
529
// The time parameter `now` allows injection of static date for deterministic testing.
530
func printDeprecatedVersionWarningWithTime(apiCommand shared_api.Command, versionString *string, now time.Time) {
1✔
531
        if allVersionsDeprecated(apiCommand) {
2✔
532
                return
1✔
533
        }
1✔
534

535
        version, err := shared_api.ParseVersion(*versionString)
1✔
536
        if err != nil {
1✔
537
                return
×
538
        }
×
539

540
        // Find the version in the command versions
541
        var commandVersion *shared_api.CommandVersion
1✔
542
        for i := range apiCommand.Versions {
2✔
543
                if apiCommand.Versions[i].Version.Equal(version) {
2✔
544
                        commandVersion = &apiCommand.Versions[i]
1✔
545
                        break
1✔
546
                }
547
        }
548

549
        // If the version is not found or is not deprecated and has no sunset date, return
550
        if commandVersion == nil {
1✔
551
                return
×
552
        }
×
553

554
        // Print a warning or error if the version has a sunset date
555
        if commandVersion.Sunset != nil {
2✔
556
                sunsetDate := commandVersion.Sunset.Format("2006-01-02")
1✔
557
                // if date is in the past, warn the user that it will not work
1✔
558
                if commandVersion.Sunset.Before(now) {
2✔
559
                        fmt.Fprintf(os.Stderr, "error: version '%s' is deprecated for this command and has already been sunset since %s. Consider upgrading to a newer version if available.\n", *versionString, sunsetDate)
1✔
560
                        return
1✔
561
                }
1✔
562
                fmt.Fprintf(os.Stderr, "warning: version '%s' is deprecated for this command and will be sunset on %s. Consider upgrading to a newer version if available.\n", *versionString, sunsetDate)
1✔
563
                return
1✔
564
        }
565

566
        // Find the latest command version
567
        latestCommandVersion, err := defaultAPIVersion(apiCommand)
1✔
568
        if err != nil || latestCommandVersion == commandVersion.Version.String() {
2✔
569
                latestCommandVersion = ""
1✔
570
        }
1✔
571

572
        // Print a warning if the version is deprecated but is not sunset
573
        if commandVersion.Deprecated {
2✔
574
                fmt.Fprintf(os.Stderr, "warning: version '%s' is deprecated. ", *versionString)
1✔
575
                if latestCommandVersion != "" {
2✔
576
                        fmt.Fprintf(os.Stderr, "Consider upgrading to a newer version: %s.", latestCommandVersion)
1✔
577
                }
1✔
578
                fmt.Fprintf(os.Stderr, "\n")
1✔
579
                return
1✔
580
        }
581
}
582

583
func needsFileFlag(apiCommand shared_api.Command) bool {
1✔
584
        for _, version := range apiCommand.Versions {
2✔
585
                if version.RequestContentType != "" {
2✔
586
                        return true
1✔
587
                }
1✔
588
        }
589

590
        return false
1✔
591
}
592

593
func addWatchFlagIfNeeded(cmd *cobra.Command, apiCommand shared_api.Command, watch *bool, watchTimeout *int64) {
1✔
594
        if apiCommand.Watcher == nil || apiCommand.Watcher.Get.OperationID == "" {
2✔
595
                return
1✔
596
        }
1✔
597

598
        cmd.Flags().BoolVarP(watch, flag.EnableWatch, flag.EnableWatchShort, false, usage.EnableWatchDefault)
1✔
599
        cmd.Flags().Int64Var(watchTimeout, flag.WatchTimeout, 0, usage.WatchTimeout)
1✔
600
}
601

602
func addVersionFlag(cmd *cobra.Command, apiCommand shared_api.Command, version *string) {
1✔
603
        // Create a unique list of all supported versions
1✔
604
        versions := make(map[string]struct{}, 0)
1✔
605
        for _, version := range apiCommand.Versions {
2✔
606
                versions[version.Version.String()] = struct{}{}
1✔
607
        }
1✔
608

609
        // Convert the keys of the map into a list
610
        supportedVersionsVersions := make([]string, 0, len(versions))
1✔
611
        for version := range versions {
2✔
612
                supportedVersionsVersions = append(supportedVersionsVersions, `"`+version+`"`)
1✔
613
        }
1✔
614

615
        // Sort the list
616
        slices.Sort(supportedVersionsVersions)
1✔
617

1✔
618
        // Convert the list to a string
1✔
619
        supportedVersionsVersionsString := strings.Join(supportedVersionsVersions, ", ")
1✔
620

1✔
621
        cmd.Flags().StringVar(version, flag.Version, *version, fmt.Sprintf("API version to use when calling the Atlas API endpoints [options: %s]. If not set by the user, defaults to the latest version or the profile's api_version config value if set.", supportedVersionsVersionsString))
1✔
622
}
623

624
func addOutputFlags(cmd *cobra.Command, apiCommand shared_api.Command, format *string, outputFile *string) error {
1✔
625
        // Get the list of supported content types for the apiCommand
1✔
626
        supportedContentTypesList := getContentTypes(&apiCommand)
1✔
627

1✔
628
        // If there's only one content type, set the format to that
1✔
629
        numSupportedContentTypes := len(supportedContentTypesList)
1✔
630
        if numSupportedContentTypes == 1 {
1✔
631
                *format = supportedContentTypesList[0]
×
632
        }
×
633

634
        // If the content type has json, also add go-template as an option to the --format help
635
        containsJSON := slices.Contains(supportedContentTypesList, "json")
1✔
636

1✔
637
        // Place quotes around every supported content type
1✔
638
        for i, value := range supportedContentTypesList {
1✔
639
                supportedContentTypesList[i] = `"` + value + `"`
×
640
        }
×
641

642
        // Add go-template, we add it here because we don't want go-template to be between quotes
643
        if containsJSON {
1✔
644
                supportedContentTypesList = append(supportedContentTypesList, "go-template")
×
645
        }
×
646

647
        // Generate a list of supported content types and add it to --help for --format
648
        // Example ['csv', 'json', go-template]
649
        supportedContentTypesString := strings.Join(supportedContentTypesList, ", ")
1✔
650

1✔
651
        // Set the flags
1✔
652
        cmd.Flags().StringVarP(format, flag.Output, flag.OutputShort, *format, fmt.Sprintf("preferred api format, can be [%s]", supportedContentTypesString))
1✔
653
        cmd.Flags().StringVar(outputFile, flag.OutputFile, "", "file to write the api output to. This flag is required when the output of an endpoint is binary (ex: gzip) and the command is not piped (ex: atlas command > out.zip)")
1✔
654

1✔
655
        // If there's multiple content types, mark --format as required
1✔
656
        if numSupportedContentTypes > 1 {
1✔
657
                if err := cmd.MarkFlagRequired(flag.Output); err != nil {
×
658
                        return err
×
659
                }
×
660
        }
661

662
        return nil
1✔
663
}
664

665
func getContentTypes(apiCommand *shared_api.Command) []string {
1✔
666
        // Create a unique list of all supported content types
1✔
667
        // First create a map to convert 2 nested lists into a map
1✔
668
        supportedContentTypes := make(map[string]struct{}, 0)
1✔
669
        for _, version := range apiCommand.Versions {
2✔
670
                for _, contentType := range version.ResponseContentTypes {
1✔
671
                        supportedContentTypes[contentType] = struct{}{}
×
672
                }
×
673
        }
674

675
        // Convert the keys of the map into a list
676
        supportedContentTypesList := make([]string, 0, len(supportedContentTypes))
1✔
677
        for contentType := range supportedContentTypes {
1✔
678
                supportedContentTypesList = append(supportedContentTypesList, contentType)
×
679
        }
×
680

681
        // Sort the list
682
        slices.Sort(supportedContentTypesList)
1✔
683

1✔
684
        return supportedContentTypesList
1✔
685
}
686

687
func getOutputWriteCloser(outputFile string) (io.WriteCloser, error) {
×
688
        // If an output file is specified, create/open the file and return the writer
×
689
        if outputFile != "" {
×
690
                //nolint: mnd
×
691
                file, err := os.OpenFile(outputFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
×
692
                if err != nil {
×
693
                        return nil, err
×
694
                }
×
695
                return file, nil
×
696
        }
697

698
        // Return stdout by default
699
        return os.Stdout, nil
×
700
}
701

702
// addDeprecationMessageIfNeeded adds a deprecation message to the command if the command is deprecated.
703
func addDeprecationMessageIfNeeded(cmd *cobra.Command, apiCommand shared_api.Command) {
1✔
704
        allVersionsAreDeprecated := allVersionsDeprecated(apiCommand)
1✔
705
        // if the command is not fully deprecated, then we don't need to add a deprecation message, there will be a warning for each version.
1✔
706
        if !allVersionsAreDeprecated {
2✔
707
                return
1✔
708
        }
1✔
709

710
        cmd.Deprecated = "all of the available endpoint versions have been deprecated."
1✔
711

1✔
712
        if len(apiCommand.Versions) == 1 && apiCommand.Versions[0].Sunset != nil {
2✔
713
                version := apiCommand.Versions[0]
1✔
714
                sunsetDate := version.Sunset.Format("2006-01-02")
1✔
715
                cmd.Deprecated += fmt.Sprintf(" The API endpoint version %s will no longer be available after the sunset date of %s.\n", version.Version.String(), sunsetDate)
1✔
716
                return
1✔
717
        }
1✔
718

719
        for _, version := range apiCommand.Versions {
2✔
720
                if version.Sunset != nil {
2✔
721
                        sunsetDate := version.Sunset.Format("2006-01-02")
1✔
722
                        cmd.Deprecated += fmt.Sprintf(" The API endpoint version %s will no longer be available after the sunset date of %s.", version.Version.String(), sunsetDate)
1✔
723
                        break
1✔
724
                }
725
        }
726

727
        cmd.Deprecated += "\n"
1✔
728
}
729

730
// allVersionsDeprecated checks if all the versions are deprecated.
731
// we classify the command as deprecated if all the versions have a sunset date or all the versions are deprecated.
732
func allVersionsDeprecated(apiCommand shared_api.Command) bool {
1✔
733
        for _, version := range apiCommand.Versions {
2✔
734
                if version.Sunset == nil && !version.Deprecated {
2✔
735
                        return false
1✔
736
                }
1✔
737
        }
738

739
        return true
1✔
740
}
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