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

mongodb / mongodb-atlas-cli / 18284572001

06 Oct 2025 02:43PM UTC coverage: 64.3%. Remained the same
18284572001

push

github

GitHub
CLOUDP-345026: Update the L1 generation tool to support x-xgen-operation-id-override (#4233)

25691 of 39955 relevant lines covered (64.3%)

0.8 hits per line

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

69.71
/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: `Access all features of the Atlas Administration API through the Atlas CLI by using the 'atlas api <tag> <operationId>' command.`,
1✔
75
                Long: `This feature streamlines script development by letting you interact directly with any Atlas Administration API endpoint through 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.APICommandHelpTemplate)
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
        commandOperationID := command.OperationID
1✔
103
        commandAliases := command.Aliases
1✔
104

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

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

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

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

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

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

1✔
151
                        // Print a warning if the version is a preview version
1✔
152
                        printPreviewWarning(command, &version)
1✔
153

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

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

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

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

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

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

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

211
                        // Properly free up result output
212
                        defer result.Output.Close()
1✔
213

1✔
214
                        // Output that will be send to stdout/file
1✔
215
                        responseOutput := result.Output
1✔
216

1✔
217
                        // Response body used for the watcher
1✔
218
                        var watchResponseBody []byte
1✔
219

1✔
220
                        // If the response was successful, handle --format
1✔
221
                        if result.IsSuccess {
2✔
222
                                // If we're watching, we need to cache the original output before formatting so we don't read twice from the same reader
1✔
223
                                // 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✔
224
                                if watch {
1✔
225
                                        responseBytes, err := io.ReadAll(result.Output)
×
226
                                        if err != nil {
×
227
                                                return errors.Join(errors.New("failed to read output"), err)
×
228
                                        }
×
229
                                        watchResponseBody = responseBytes
×
230

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

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

240
                                responseOutput = formattedOutput
1✔
241
                        }
242

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

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

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

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

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

277
                        return nil
1✔
278
                },
279
        }
280

281
        // Common flags
282
        addWatchFlagIfNeeded(cmd, command, &watch, &watchTimeout)
1✔
283
        addVersionFlag(cmd, command, &version)
1✔
284

1✔
285
        if needsFileFlag(command) {
2✔
286
                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✔
287
        }
1✔
288

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

296
        // Add URL parameters as flags
297
        if err := addParameters(cmd, command.RequestParameters.URLParameters); err != nil {
1✔
298
                return nil, err
×
299
        }
×
300

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

306
        // Handle parameter aliases
307
        cmd.Flags().SetNormalizeFunc(normalizeFlagFunc(command))
1✔
308

1✔
309
        return cmd, nil
1✔
310
}
311

312
func normalizeFlagFunc(command shared_api.Command) func(f *pflag.FlagSet, name string) pflag.NormalizedName {
1✔
313
        return func(_ *pflag.FlagSet, name string) pflag.NormalizedName {
2✔
314
                name = normalizeFlagName(command.RequestParameters.QueryParameters, name)
1✔
315
                name = normalizeFlagName(command.RequestParameters.URLParameters, name)
1✔
316

1✔
317
                return pflag.NormalizedName(name)
1✔
318
        }
1✔
319
}
320

321
func normalizeFlagName(parameters []shared_api.Parameter, name string) string {
1✔
322
        for _, parameter := range parameters {
2✔
323
                if slices.Contains(parameter.Aliases, name) {
2✔
324
                        return parameter.Name
1✔
325
                }
1✔
326
        }
327

328
        return name
1✔
329
}
330

331
func addParameters(cmd *cobra.Command, parameters []shared_api.Parameter) error {
2✔
332
        for _, parameter := range parameters {
4✔
333
                if err := addFlag(cmd, parameter); err != nil {
3✔
334
                        return err
1✔
335
                }
1✔
336
        }
337

338
        return nil
2✔
339
}
340

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

351
                // Only update flags thave have been un-touched by the user
352
                if !f.Changed {
4✔
353
                        value, err := flagValueProvider.ValueForFlag(f.Name)
2✔
354
                        if err != nil {
2✔
355
                                visitErr = err
×
356
                        }
×
357

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

367
                                f.Changed = true
2✔
368
                        }
369
                }
370
        })
371

372
        return visitErr
2✔
373
}
374

375
func handleInput(cmd *cobra.Command) (io.ReadCloser, error) {
×
376
        isPiped, err := IsStdInPiped()
×
377
        if err != nil {
×
378
                return nil, err
×
379
        }
×
380

381
        // If not piped, get the file flag
382
        filePath, err := cmd.Flags().GetString(flag.File)
×
383
        if err != nil {
×
384
                return nil, fmt.Errorf("error getting file flag: %w", err)
×
385
        }
×
386

387
        if isPiped {
×
388
                if filePath != "" {
×
389
                        return nil, errors.New("cannot use --file flag and also input from standard input")
×
390
                }
×
391
                // Use stdin as the input
392
                return os.Stdin, nil
×
393
        }
394

395
        // Require file flag if not piped
396
        if filePath == "" {
×
397
                return nil, errors.New("--file flag is required when not using piped input")
×
398
        }
×
399

400
        // Open the file
401
        file, err := os.Open(filePath)
×
402
        if err != nil {
×
403
                return nil, fmt.Errorf("error opening file %s: %w", filePath, err)
×
404
        }
×
405

406
        return file, nil
×
407
}
408

409
func IsStdInPiped() (bool, error) {
×
410
        return isPiped(os.Stdin)
×
411
}
×
412

413
func IsStdOutPiped() (bool, error) {
1✔
414
        return isPiped(os.Stdout)
1✔
415
}
1✔
416

417
func isPiped(file *os.File) (bool, error) {
1✔
418
        // Check if data is being piped to stdin
1✔
419
        info, err := file.Stat()
1✔
420
        if err != nil {
1✔
421
                return false, fmt.Errorf("isPiped, error checking: %w", err)
×
422
        }
×
423

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

1✔
427
        return isPiped, nil
1✔
428
}
429

430
func defaultAPIVersion(command shared_api.Command) (string, error) {
1✔
431
        // Command versions are sorted by the generation tool
1✔
432
        nVersions := len(command.Versions)
1✔
433
        if nVersions == 0 {
1✔
434
                return "", ErrAPICommandsHasNoVersions
×
435
        }
×
436

437
        lastVersion := command.Versions[nVersions-1]
1✔
438
        return lastVersion.Version.String(), nil
1✔
439
}
440

441
func remindUserToPinVersion(cmd *cobra.Command) {
1✔
442
        versionFlag := cmd.Flag(flag.Version)
1✔
443
        // if we fail to get the version flag (which should never happen), then quit
1✔
444
        if versionFlag == nil {
1✔
445
                return
×
446
        }
×
447

448
        // check if the version flag is still in it's default state:
449
        // - not set by the user
450
        // - not set using api_version on the users profile
451
        // in that case, print a warning
452
        if !versionFlag.Changed {
2✔
453
                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✔
454
        }
1✔
455
}
456

457
func ensureVersionIsSupported(apiCommand shared_api.Command, versionString *string) {
1✔
458
        version, err := shared_api.ParseVersion(*versionString)
1✔
459

1✔
460
        // If the version is valid, check if it's supported
1✔
461
        if err == nil {
2✔
462
                for _, commandVersion := range apiCommand.Versions {
2✔
463
                        if commandVersion.Version.Equal(version) {
2✔
464
                                return
1✔
465
                        }
1✔
466
                }
467
        }
468

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

477
        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✔
478
        *versionString = defaultVersion
1✔
479
}
480

481
func printPreviewWarning(apiCommand shared_api.Command, versionString *string) {
1✔
482
        version, err := shared_api.ParseVersion(*versionString)
1✔
483

1✔
484
        // If the version is invalid return, this should never happen
1✔
485
        if err != nil {
1✔
486
                fmt.Fprintf(os.Stderr, "error in 'printPreviewWarning': received an invalid version '%s'\n", *versionString)
×
487
                return
×
488
        }
×
489

490
        // If the version is not a preview version, return
491
        if version.StabilityLevel() != shared_api.StabilityLevelPreview {
2✔
492
                return
1✔
493
        }
1✔
494

495
        // Find the version in the command versions
496
        var commandVersion *shared_api.CommandVersion
×
497
        for _, cv := range apiCommand.Versions {
×
498
                if cv.Version.Equal(version) {
×
499
                        commandVersion = &cv
×
500
                        break
×
501
                }
502
        }
503

504
        // If the version is not found, return (should also never happen)
505
        if commandVersion == nil {
×
506
                return
×
507
        }
×
508

509
        if commandVersion.PublicPreview {
×
510
                fmt.Fprintf(os.Stderr, "warning: you've selected a public preview version of the endpoint, this version is subject to breaking changes.\n")
×
511
        } else {
×
512
                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")
×
513
        }
×
514
}
515

516
func needsFileFlag(apiCommand shared_api.Command) bool {
1✔
517
        for _, version := range apiCommand.Versions {
2✔
518
                if version.RequestContentType != "" {
2✔
519
                        return true
1✔
520
                }
1✔
521
        }
522

523
        return false
1✔
524
}
525

526
func addWatchFlagIfNeeded(cmd *cobra.Command, apiCommand shared_api.Command, watch *bool, watchTimeout *int64) {
1✔
527
        if apiCommand.Watcher == nil || apiCommand.Watcher.Get.OperationID == "" {
2✔
528
                return
1✔
529
        }
1✔
530

531
        cmd.Flags().BoolVarP(watch, flag.EnableWatch, flag.EnableWatchShort, false, usage.EnableWatchDefault)
1✔
532
        cmd.Flags().Int64Var(watchTimeout, flag.WatchTimeout, 0, usage.WatchTimeout)
1✔
533
}
534

535
func addVersionFlag(cmd *cobra.Command, apiCommand shared_api.Command, version *string) {
1✔
536
        // Create a unique list of all supported versions
1✔
537
        versions := make(map[string]struct{}, 0)
1✔
538
        for _, version := range apiCommand.Versions {
2✔
539
                versions[version.Version.String()] = struct{}{}
1✔
540
        }
1✔
541

542
        // Convert the keys of the map into a list
543
        supportedVersionsVersions := make([]string, 0, len(versions))
1✔
544
        for version := range versions {
2✔
545
                supportedVersionsVersions = append(supportedVersionsVersions, `"`+version+`"`)
1✔
546
        }
1✔
547

548
        // Sort the list
549
        slices.Sort(supportedVersionsVersions)
1✔
550

1✔
551
        // Convert the list to a string
1✔
552
        supportedVersionsVersionsString := strings.Join(supportedVersionsVersions, ", ")
1✔
553

1✔
554
        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✔
555
}
556

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

1✔
561
        // If there's only one content type, set the format to that
1✔
562
        numSupportedContentTypes := len(supportedContentTypesList)
1✔
563
        if numSupportedContentTypes == 1 {
2✔
564
                *format = supportedContentTypesList[0]
1✔
565
        }
1✔
566

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

1✔
570
        // Place quotes around every supported content type
1✔
571
        for i, value := range supportedContentTypesList {
2✔
572
                supportedContentTypesList[i] = `"` + value + `"`
1✔
573
        }
1✔
574

575
        // Add go-template, we add it here because we don't want go-template to be between quotes
576
        if containsJSON {
2✔
577
                supportedContentTypesList = append(supportedContentTypesList, "go-template")
1✔
578
        }
1✔
579

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

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

1✔
588
        // If there's multiple content types, mark --format as required
1✔
589
        if numSupportedContentTypes > 1 {
2✔
590
                if err := cmd.MarkFlagRequired(flag.Output); err != nil {
1✔
591
                        return err
×
592
                }
×
593
        }
594

595
        return nil
1✔
596
}
597

598
func getContentTypes(apiCommand *shared_api.Command) []string {
1✔
599
        // Create a unique list of all supported content types
1✔
600
        // First create a map to convert 2 nested lists into a map
1✔
601
        supportedContentTypes := make(map[string]struct{}, 0)
1✔
602
        for _, version := range apiCommand.Versions {
2✔
603
                for _, contentType := range version.ResponseContentTypes {
2✔
604
                        supportedContentTypes[contentType] = struct{}{}
1✔
605
                }
1✔
606
        }
607

608
        // Convert the keys of the map into a list
609
        supportedContentTypesList := make([]string, 0, len(supportedContentTypes))
1✔
610
        for contentType := range supportedContentTypes {
2✔
611
                supportedContentTypesList = append(supportedContentTypesList, contentType)
1✔
612
        }
1✔
613

614
        // Sort the list
615
        slices.Sort(supportedContentTypesList)
1✔
616

1✔
617
        return supportedContentTypesList
1✔
618
}
619

620
func getOutputWriteCloser(outputFile string) (io.WriteCloser, error) {
1✔
621
        // If an output file is specified, create/open the file and return the writer
1✔
622
        if outputFile != "" {
1✔
623
                //nolint: mnd
×
624
                file, err := os.OpenFile(outputFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
×
625
                if err != nil {
×
626
                        return nil, err
×
627
                }
×
628
                return file, nil
×
629
        }
630

631
        // Return stdout by default
632
        return os.Stdout, nil
1✔
633
}
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