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

mongodb / mongodb-atlas-cli / 18374670151

09 Oct 2025 11:26AM UTC coverage: 64.321% (-0.001%) from 64.322%
18374670151

push

github

GitHub
CLOUDP-350267: accept updating state while creating a cluster (#4253)

25699 of 39954 relevant lines covered (64.32%)

0.8 hits per line

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

71.08
/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 != "" {
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 {
1✔
122
                return nil, err
×
123
        }
×
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 {
2✔
134
                        // Go through all commands that have not been touched/modified by the user and try to populate them from the users profile
1✔
135
                        // Common usecases:
1✔
136
                        // - set orgId
1✔
137
                        // - set projectId
1✔
138
                        // - default api version
1✔
139
                        if err := setUnTouchedFlags(NewProfileFlagValueProviderForDefaultProfile(), cmd); err != nil {
1✔
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)
1✔
145

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

239
                                responseOutput = formattedOutput
1✔
240
                        }
241

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

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

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

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

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

276
                        return nil
1✔
277
                },
278
        }
279

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

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

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

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

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

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

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

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

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

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

327
        return name
1✔
328
}
329

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

337
        return nil
2✔
338
}
339

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

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

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

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

371
        return visitErr
2✔
372
}
373

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

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

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

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

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

405
        return file, nil
×
406
}
407

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

522
        return false
1✔
523
}
524

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

594
        return nil
1✔
595
}
596

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

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

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

1✔
616
        return supportedContentTypesList
1✔
617
}
618

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

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