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

mongodb / mongodb-atlas-cli / 16670223591

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

Pull #4071

github

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

23613 of 40745 relevant lines covered (57.95%)

2.74 hits per line

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

69.95
/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 {
1✔
48
        apiCmd := createRootAPICommand()
1✔
49

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

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

62
                        tagCmd.AddCommand(cobraCommand)
1✔
63
                }
64

65
                apiCmd.AddCommand(tagCmd)
1✔
66
        }
67

68
        return apiCmd
1✔
69
}
70

71
func createRootAPICommand() *cobra.Command {
1✔
72
        rootCmd := &cobra.Command{
1✔
73
                Use:   "api",
1✔
74
                Short: "`Public Preview: please provide feedback <https://feedback.mongodb.com/forums/930808-atlas-cli>`_: " + `Access all features of the Atlas Administration API by using the Atlas CLI with the syntax: 'atlas api <tag> <operationId>'.`,
1✔
75
                Long: `This feature in public preview streamlines script development by letting you interact directly with any Atlas Administration API endpoint by using the Atlas CLI.
1✔
76

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

1✔
82
        rootCmd.SetHelpTemplate(cli.ExperimentalHelpTemplate)
1✔
83

1✔
84
        return rootCmd
1✔
85
}
1✔
86

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

1✔
91
        return &cobra.Command{
1✔
92
                Use:   groupName,
1✔
93
                Short: shortDescription,
1✔
94
                Long:  longDescription,
1✔
95
        }
1✔
96
}
1✔
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
        shortDescription, longDescription := splitShortAndLongDescription(command.Description)
1✔
103

1✔
104
        // flag values
1✔
105
        file := ""
1✔
106
        format := ""
1✔
107
        outputFile := ""
1✔
108
        version, err := defaultAPIVersion(command)
1✔
109
        watch := false
1✔
110
        watchTimeout := int64(0)
1✔
111
        if err != nil {
1✔
112
                return nil, err
×
113
        }
×
114

115
        cmd := &cobra.Command{
1✔
116
                Use:     commandName,
1✔
117
                Aliases: command.Aliases,
1✔
118
                Short:   shortDescription,
1✔
119
                Long:    longDescription,
1✔
120
                Annotations: map[string]string{
1✔
121
                        "operationId": command.OperationID,
1✔
122
                },
1✔
123
                PreRunE: func(cmd *cobra.Command, _ []string) error {
2✔
124
                        // Go through all commands that have not been touched/modified by the user and try to populate them from the users profile
1✔
125
                        // Common usecases:
1✔
126
                        // - set orgId
1✔
127
                        // - set projectId
1✔
128
                        // - default api version
1✔
129
                        if err := setUnTouchedFlags(NewProfileFlagValueProviderForDefaultProfile(), cmd); err != nil {
1✔
130
                                return errors.Join(ErrFailedToSetUntouchedFlags, err)
×
131
                        }
×
132

133
                        // Remind the user to pin their api command to a specific version to avoid breaking changes
134
                        remindUserToPinVersion(cmd)
1✔
135

1✔
136
                        // Reset version to default if unsupported version was selected
1✔
137
                        // This can happen when the profile contains a default version which is not supported for a specific endpoint
1✔
138
                        ensureVersionIsSupported(command, &version)
1✔
139

1✔
140
                        // Print a warning if the version is a preview version
1✔
141
                        printPreviewWarning(command, &version)
1✔
142

1✔
143
                        // Detect if stdout is being piped (atlas api myTag myOperationId > output.json)
1✔
144
                        isPiped, err := IsStdOutPiped()
1✔
145
                        if err != nil {
1✔
146
                                return err
×
147
                        }
×
148

149
                        // If the selected output format is binary and stdout is not being piped, mark output as required
150
                        // This ensures that the console isn't flooded with binary contents (for example gzip contents)
151
                        if slices.Contains(BinaryOutputTypes, format) && !isPiped {
1✔
152
                                if err := cmd.MarkFlagRequired(flag.Output); err != nil {
×
153
                                        return err
×
154
                                }
×
155
                        }
156

157
                        return nil
1✔
158
                },
159
                RunE: func(cmd *cobra.Command, _ []string) error {
1✔
160
                        // Get the request input if needed
1✔
161
                        // This is needed for most PATCH/POST/PUT requests
1✔
162
                        var content io.ReadCloser
1✔
163
                        if needsFileFlag(command) {
1✔
164
                                content, err = handleInput(cmd)
×
165
                                if err != nil {
×
166
                                        return err
×
167
                                }
×
168
                                defer content.Close()
×
169
                        }
170

171
                        // Create a new executor
172
                        // This is the piece of code which knows how to execute api.Commands
173
                        formatter := api.NewFormatter()
1✔
174
                        executor, err := api.NewDefaultExecutor(formatter)
1✔
175
                        if err != nil {
1✔
176
                                return err
×
177
                        }
×
178

179
                        // Convert the version string into an api version
180
                        apiVersion, err := shared_api.ParseVersion(version)
1✔
181
                        if err != nil {
1✔
182
                                // This should never happen, the version is already validated in the prerun function
×
183
                                return err
×
184
                        }
×
185

186
                        // Convert the api command + cobra command into a api command request
187
                        commandRequest, err := NewCommandRequestFromCobraCommand(cmd, command, content, format, apiVersion)
1✔
188
                        if err != nil {
1✔
189
                                return err
×
190
                        }
×
191

192
                        // Execute the api command request
193
                        // This function will return an error if the http request failed
194
                        // When the http request returns a non-success code error will still be nil
195
                        result, err := executor.ExecuteCommand(cmd.Context(), *commandRequest)
1✔
196
                        if err != nil {
1✔
197
                                return err
×
198
                        }
×
199

200
                        // Properly free up result output
201
                        defer result.Output.Close()
1✔
202

1✔
203
                        // Output that will be send to stdout/file
1✔
204
                        responseOutput := result.Output
1✔
205

1✔
206
                        // Response body used for the watcher
1✔
207
                        var watchResponseBody []byte
1✔
208

1✔
209
                        // If the response was successful, handle --format
1✔
210
                        if result.IsSuccess {
2✔
211
                                // If we're watching, we need to cache the original output before formatting so we don't read twice from the same reader
1✔
212
                                // In case we're not watching the http output will be piped straight into the formatter, which should be a little more memory efficient
1✔
213
                                if watch {
1✔
214
                                        responseBytes, err := io.ReadAll(result.Output)
×
215
                                        if err != nil {
×
216
                                                return errors.Join(errors.New("failed to read output"), err)
×
217
                                        }
×
218
                                        watchResponseBody = responseBytes
×
219

×
220
                                        // Create a new reader for the formatter
×
221
                                        responseOutput = io.NopCloser(bytes.NewReader(responseBytes))
×
222
                                }
223

224
                                formattedOutput, err := formatter.Format(format, responseOutput)
1✔
225
                                if err != nil {
1✔
226
                                        return errors.Join(ErrFormattingOutput, err)
×
227
                                }
×
228

229
                                responseOutput = formattedOutput
1✔
230
                        }
231

232
                        // Determine where to write the
233
                        output, err := getOutputWriteCloser(outputFile)
1✔
234
                        if err != nil {
1✔
235
                                return err
×
236
                        }
×
237
                        // Properly free up output
238
                        defer output.Close()
1✔
239

1✔
240
                        // Write the output
1✔
241
                        _, err = io.Copy(output, responseOutput)
1✔
242
                        if err != nil {
1✔
243
                                return err
×
244
                        }
×
245

246
                        // In case the http status code was non-success
247
                        // Return an error, this causes the CLI to exit with a non-zero exit code while still running all telemetry code
248
                        if !result.IsSuccess {
1✔
249
                                return ErrServerReturnedAnErrorResponseCode
×
250
                        }
×
251

252
                        // In case watcher is set we wait for the watcher to succeed before we exit the program
253
                        if watch {
1✔
254
                                // Create a new watcher
×
255
                                watcher, err := NewWatcher(executor, commandRequest.Parameters, watchResponseBody, *command.Watcher)
×
256
                                if err != nil {
×
257
                                        return err
×
258
                                }
×
259

260
                                // Wait until we're in the desired state or until an error occures when watching
261
                                if err := watcher.Wait(cmd.Context(), time.Duration(watchTimeout)); err != nil {
×
262
                                        return errors.Join(ErrRunningWatcher, err)
×
263
                                }
×
264
                        }
265

266
                        return nil
1✔
267
                },
268
        }
269

270
        // Common flags
271
        addWatchFlagIfNeeded(cmd, command, &watch, &watchTimeout)
1✔
272
        addVersionFlag(cmd, command, &version)
1✔
273

1✔
274
        if needsFileFlag(command) {
2✔
275
                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✔
276
        }
1✔
277

278
        // Add output flags:
279
        // - `--output`: desired output format, translates to ContentType. Can also be a go template
280
        // - `--output-file`: file where we want to write the output to
281
        if err := addOutputFlags(cmd, command, &format, &outputFile); err != nil {
1✔
282
                return nil, err
×
283
        }
×
284

285
        // Add URL parameters as flags
286
        if err := addParameters(cmd, command.RequestParameters.URLParameters); err != nil {
1✔
287
                return nil, err
×
288
        }
×
289

290
        // Add query parameters as flags
291
        if err := addParameters(cmd, command.RequestParameters.QueryParameters); err != nil {
1✔
292
                return nil, err
×
293
        }
×
294

295
        // Handle parameter aliases
296
        cmd.Flags().SetNormalizeFunc(normalizeFlagFunc(command))
1✔
297

1✔
298
        return cmd, nil
1✔
299
}
300

301
func normalizeFlagFunc(command shared_api.Command) func(f *pflag.FlagSet, name string) pflag.NormalizedName {
1✔
302
        return func(_ *pflag.FlagSet, name string) pflag.NormalizedName {
2✔
303
                name = normalizeFlagName(command.RequestParameters.QueryParameters, name)
1✔
304
                name = normalizeFlagName(command.RequestParameters.URLParameters, name)
1✔
305

1✔
306
                return pflag.NormalizedName(name)
1✔
307
        }
1✔
308
}
309

310
func normalizeFlagName(parameters []shared_api.Parameter, name string) string {
1✔
311
        for _, parameter := range parameters {
2✔
312
                if slices.Contains(parameter.Aliases, name) {
2✔
313
                        return parameter.Name
1✔
314
                }
1✔
315
        }
316

317
        return name
1✔
318
}
319

320
func addParameters(cmd *cobra.Command, parameters []shared_api.Parameter) error {
1✔
321
        for _, parameter := range parameters {
2✔
322
                if err := addFlag(cmd, parameter); err != nil {
1✔
323
                        return err
×
324
                }
×
325
        }
326

327
        return nil
1✔
328
}
329

330
func setUnTouchedFlags(flagValueProvider FlagValueProvider, cmd *cobra.Command) error {
1✔
331
        var visitErr error
1✔
332
        cmd.NonInheritedFlags().VisitAll(func(f *pflag.Flag) {
2✔
333
                // There is no VisitAll which accepts an error
1✔
334
                // because of this we set visitErr when an error occures
1✔
335
                // if visitErr != nil we stop processing other flags
1✔
336
                if visitErr != nil {
1✔
337
                        return
×
338
                }
×
339

340
                // Only update flags thave have been un-touched by the user
341
                if !f.Changed {
2✔
342
                        value, err := flagValueProvider.ValueForFlag(f.Name)
1✔
343
                        if err != nil {
1✔
344
                                visitErr = err
×
345
                        }
×
346

347
                        // If we get a value back from the FlagValueProvider:
348
                        // - Set the value
349
                        // - Mark the flag as changed, this is needed to mark required flags as set
350
                        if value != nil {
2✔
351
                                if err := f.Value.Set(*value); err != nil {
1✔
352
                                        visitErr = err
×
353
                                        return
×
354
                                }
×
355

356
                                f.Changed = true
1✔
357
                        }
358
                }
359
        })
360

361
        return visitErr
1✔
362
}
363

364
func handleInput(cmd *cobra.Command) (io.ReadCloser, error) {
×
365
        isPiped, err := IsStdInPiped()
×
366
        if err != nil {
×
367
                return nil, err
×
368
        }
×
369

370
        // If not piped, get the file flag
371
        filePath, err := cmd.Flags().GetString(flag.File)
×
372
        if err != nil {
×
373
                return nil, fmt.Errorf("error getting file flag: %w", err)
×
374
        }
×
375

376
        if isPiped {
×
377
                if filePath != "" {
×
378
                        return nil, errors.New("cannot use --file flag and also input from standard input")
×
379
                }
×
380
                // Use stdin as the input
381
                return os.Stdin, nil
×
382
        }
383

384
        // Require file flag if not piped
385
        if filePath == "" {
×
386
                return nil, errors.New("--file flag is required when not using piped input")
×
387
        }
×
388

389
        // Open the file
390
        file, err := os.Open(filePath)
×
391
        if err != nil {
×
392
                return nil, fmt.Errorf("error opening file %s: %w", filePath, err)
×
393
        }
×
394

395
        return file, nil
×
396
}
397

398
func IsStdInPiped() (bool, error) {
×
399
        return isPiped(os.Stdin)
×
400
}
×
401

402
func IsStdOutPiped() (bool, error) {
1✔
403
        return isPiped(os.Stdout)
1✔
404
}
1✔
405

406
func isPiped(file *os.File) (bool, error) {
1✔
407
        // Check if data is being piped to stdin
1✔
408
        info, err := file.Stat()
1✔
409
        if err != nil {
1✔
410
                return false, fmt.Errorf("isPiped, error checking: %w", err)
×
411
        }
×
412

413
        // Check if there's data in stdin (piped input)
414
        isPiped := (info.Mode() & os.ModeCharDevice) == 0
1✔
415

1✔
416
        return isPiped, nil
1✔
417
}
418

419
func defaultAPIVersion(command shared_api.Command) (string, error) {
1✔
420
        // Command versions are sorted by the generation tool
1✔
421
        nVersions := len(command.Versions)
1✔
422
        if nVersions == 0 {
1✔
423
                return "", ErrAPICommandsHasNoVersions
×
424
        }
×
425

426
        lastVersion := command.Versions[nVersions-1]
1✔
427
        return lastVersion.Version.String(), nil
1✔
428
}
429

430
func remindUserToPinVersion(cmd *cobra.Command) {
1✔
431
        versionFlag := cmd.Flag(flag.Version)
1✔
432
        // if we fail to get the version flag (which should never happen), then quit
1✔
433
        if versionFlag == nil {
1✔
434
                return
×
435
        }
×
436

437
        // check if the version flag is still in it's default state:
438
        // - not set by the user
439
        // - not set using api_version on the users profile
440
        // in that case, print a warning
441
        if !versionFlag.Changed {
2✔
442
                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())
1✔
443
        }
1✔
444
}
445

446
func ensureVersionIsSupported(apiCommand shared_api.Command, versionString *string) {
1✔
447
        version, err := shared_api.ParseVersion(*versionString)
1✔
448

1✔
449
        // If the version is valid, check if it's supported
1✔
450
        if err == nil {
2✔
451
                for _, commandVersion := range apiCommand.Versions {
2✔
452
                        if commandVersion.Version.Equal(version) {
2✔
453
                                return
1✔
454
                        }
1✔
455
                }
456
        }
457

458
        // if we get here it means that the picked version is not supported
459
        defaultVersion, err := defaultAPIVersion(apiCommand)
1✔
460
        // if we fail to get a version (which should never happen), then quit
1✔
461
        if err != nil {
1✔
462
                fmt.Fprintf(os.Stderr, "error in 'ensureVersionIsSupported': default version has an invalid format '%s'\n", *versionString)
×
463
                return
×
464
        }
×
465

466
        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)
1✔
467
        *versionString = defaultVersion
1✔
468
}
469

470
func printPreviewWarning(apiCommand shared_api.Command, versionString *string) {
1✔
471
        version, err := shared_api.ParseVersion(*versionString)
1✔
472

1✔
473
        // If the version is invalid return, this should never happen
1✔
474
        if err != nil {
1✔
475
                fmt.Fprintf(os.Stderr, "error in 'printPreviewWarning': received an invalid version '%s'\n", *versionString)
×
476
                return
×
477
        }
×
478

479
        // If the version is not a preview version, return
480
        if version.StabilityLevel() != shared_api.StabilityLevelPreview {
2✔
481
                return
1✔
482
        }
1✔
483

484
        // Find the version in the command versions
485
        var commandVersion *shared_api.CommandVersion
×
486
        for _, cv := range apiCommand.Versions {
×
487
                if cv.Version.Equal(version) {
×
488
                        commandVersion = &cv
×
489
                        break
×
490
                }
491
        }
492

493
        // If the version is not found, return (should also never happen)
494
        if commandVersion == nil {
×
495
                return
×
496
        }
×
497

498
        if commandVersion.PublicPreview {
×
499
                fmt.Fprintf(os.Stderr, "warning: you've selected a public preview version of the endpoint, this version is subject to breaking changes.\n")
×
500
        } else {
×
501
                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")
×
502
        }
×
503
}
504

505
func needsFileFlag(apiCommand shared_api.Command) bool {
1✔
506
        for _, version := range apiCommand.Versions {
2✔
507
                if version.RequestContentType != "" {
2✔
508
                        return true
1✔
509
                }
1✔
510
        }
511

512
        return false
1✔
513
}
514

515
func addWatchFlagIfNeeded(cmd *cobra.Command, apiCommand shared_api.Command, watch *bool, watchTimeout *int64) {
1✔
516
        if apiCommand.Watcher == nil || apiCommand.Watcher.Get.OperationID == "" {
2✔
517
                return
1✔
518
        }
1✔
519

520
        cmd.Flags().BoolVarP(watch, flag.EnableWatch, flag.EnableWatchShort, false, usage.EnableWatchDefault)
1✔
521
        cmd.Flags().Int64Var(watchTimeout, flag.WatchTimeout, 0, usage.WatchTimeout)
1✔
522
}
523

524
func addVersionFlag(cmd *cobra.Command, apiCommand shared_api.Command, version *string) {
1✔
525
        // Create a unique list of all supported versions
1✔
526
        versions := make(map[string]struct{}, 0)
1✔
527
        for _, version := range apiCommand.Versions {
2✔
528
                versions[version.Version.String()] = struct{}{}
1✔
529
        }
1✔
530

531
        // Convert the keys of the map into a list
532
        supportedVersionsVersions := make([]string, 0, len(versions))
1✔
533
        for version := range versions {
2✔
534
                supportedVersionsVersions = append(supportedVersionsVersions, `"`+version+`"`)
1✔
535
        }
1✔
536

537
        // Sort the list
538
        slices.Sort(supportedVersionsVersions)
1✔
539

1✔
540
        // Convert the list to a string
1✔
541
        supportedVersionsVersionsString := strings.Join(supportedVersionsVersions, ", ")
1✔
542

1✔
543
        cmd.Flags().StringVar(version, flag.Version, *version, fmt.Sprintf("api version to use when calling the api call [options: %s], defaults to the latest version or the profiles api_version config value if set", supportedVersionsVersionsString))
1✔
544
}
545

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

1✔
550
        // If there's only one content type, set the format to that
1✔
551
        numSupportedContentTypes := len(supportedContentTypesList)
1✔
552
        if numSupportedContentTypes == 1 {
2✔
553
                *format = supportedContentTypesList[0]
1✔
554
        }
1✔
555

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

1✔
559
        // Place quotes around every supported content type
1✔
560
        for i, value := range supportedContentTypesList {
2✔
561
                supportedContentTypesList[i] = `"` + value + `"`
1✔
562
        }
1✔
563

564
        // Add go-template, we add it here because we don't want go-template to be between quotes
565
        if containsJSON {
2✔
566
                supportedContentTypesList = append(supportedContentTypesList, "go-template")
1✔
567
        }
1✔
568

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

1✔
573
        // Set the flags
1✔
574
        cmd.Flags().StringVarP(format, flag.Output, flag.OutputShort, *format, fmt.Sprintf("preferred api format, can be [%s]", supportedContentTypesString))
1✔
575
        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✔
576

1✔
577
        // If there's multiple content types, mark --format as required
1✔
578
        if numSupportedContentTypes > 1 {
2✔
579
                if err := cmd.MarkFlagRequired(flag.Output); err != nil {
1✔
580
                        return err
×
581
                }
×
582
        }
583

584
        return nil
1✔
585
}
586

587
func getContentTypes(apiCommand *shared_api.Command) []string {
1✔
588
        // Create a unique list of all supported content types
1✔
589
        // First create a map to convert 2 nested lists into a map
1✔
590
        supportedContentTypes := make(map[string]struct{}, 0)
1✔
591
        for _, version := range apiCommand.Versions {
2✔
592
                for _, contentType := range version.ResponseContentTypes {
2✔
593
                        supportedContentTypes[contentType] = struct{}{}
1✔
594
                }
1✔
595
        }
596

597
        // Convert the keys of the map into a list
598
        supportedContentTypesList := make([]string, 0, len(supportedContentTypes))
1✔
599
        for contentType := range supportedContentTypes {
2✔
600
                supportedContentTypesList = append(supportedContentTypesList, contentType)
1✔
601
        }
1✔
602

603
        // Sort the list
604
        slices.Sort(supportedContentTypesList)
1✔
605

1✔
606
        return supportedContentTypesList
1✔
607
}
608

609
func getOutputWriteCloser(outputFile string) (io.WriteCloser, error) {
1✔
610
        // If an output file is specified, create/open the file and return the writer
1✔
611
        if outputFile != "" {
1✔
612
                //nolint: mnd
×
613
                file, err := os.OpenFile(outputFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
×
614
                if err != nil {
×
615
                        return nil, err
×
616
                }
×
617
                return file, nil
×
618
        }
619

620
        // Return stdout by default
621
        return os.Stdout, nil
1✔
622
}
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