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

mongodb / mongodb-atlas-cli / 16803258387

07 Aug 2025 11:31AM UTC coverage: 57.852%. First build
16803258387

Pull #4096

github

cveticm
aadress pr comment
Pull Request #4096: CLOUDP-329793: Replace L1 transport

19 of 23 new or added lines in 3 files covered. (82.61%)

23769 of 41086 relevant lines covered (57.85%)

2.73 hits per line

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

68.57
/internal/api/executor.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
        "context"
19
        "errors"
20
        "net/http"
21
        "net/http/httputil"
22

23
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/config"
24
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/log"
25
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store"
26
        "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/transport"
27
)
28

29
var (
30
        ErrFailedToAccessToken          = errors.New("failed to get access token")
31
        ErrFailedToConvertToHTTPRequest = errors.New("failed to convert to HTTP request")
32
        ErrFailedToExecuteHTTPRequest   = errors.New("failed to execute HTTP request")
33
        ErrFailedToGetBaseURL           = errors.New("failed to get base url")
34
        ErrFailedToHandleFormat         = errors.New("failed to handle format")
35
        ErrMissingDependency            = errors.New("missing executor dependency")
36
)
37

38
type Executor struct {
39
        commandConverter CommandConverter
40
        httpClient       Doer
41
        formatter        ResponseFormatter
42
        logger           Logger
43
}
44

45
// We're expecting a http client that's authenticated.
46
func NewExecutor(commandConverter CommandConverter, httpClient Doer, formatter ResponseFormatter, logger Logger) (*Executor, error) {
1✔
47
        if commandConverter == nil {
1✔
48
                return nil, errors.Join(ErrMissingDependency, errors.New("commandConverter is nil"))
×
49
        }
×
50

51
        if httpClient == nil {
1✔
52
                return nil, errors.Join(ErrMissingDependency, errors.New("httpClient is nil"))
×
53
        }
×
54

55
        if formatter == nil {
1✔
56
                return nil, errors.Join(ErrMissingDependency, errors.New("formatter is nil"))
×
57
        }
×
58

59
        if logger == nil {
1✔
60
                return nil, errors.Join(ErrMissingDependency, errors.New("logger is nil"))
×
61
        }
×
62

63
        return &Executor{
1✔
64
                commandConverter: commandConverter,
1✔
65
                httpClient:       httpClient,
1✔
66
                formatter:        formatter,
1✔
67
                logger:           logger,
1✔
68
        }, nil
1✔
69
}
70

71
// Executor wired up to use the default profile and static functions on config.
72
func NewDefaultExecutor(formatter ResponseFormatter) (*Executor, error) {
1✔
73
        profile := config.Default()
1✔
74

1✔
75
        client, err := store.HTTPClient(profile, transport.Default())
1✔
76
        if err != nil {
1✔
NEW
77
                return nil, err
×
78
        }
×
79

80
        configWrapper := NewAuthenticatedConfigWrapper(profile)
1✔
81
        commandConverter, err := NewDefaultCommandConverter(configWrapper)
1✔
82
        if err != nil {
1✔
83
                return nil, err
×
84
        }
×
85

86
        return NewExecutor(
1✔
87
                commandConverter,
1✔
88
                client,
1✔
89
                formatter,
1✔
90
                log.Default(),
1✔
91
        )
1✔
92
}
93

94
func (e *Executor) ensureInitialized() {
1✔
95
        if e.commandConverter == nil || e.httpClient == nil {
1✔
96
                // panic because this is developer error, not user error
×
97
                // should never happen
×
98
                panic("the executor was not properly initialized, use the NewExecutor method to initialize this struct")
×
99
        }
100
}
101

102
func (e *Executor) ExecuteCommand(ctx context.Context, commandRequest CommandRequest) (*CommandResponse, error) {
1✔
103
        e.ensureInitialized()
1✔
104

1✔
105
        // Set the content type
1✔
106
        if err := e.SetContentType(&commandRequest); err != nil {
1✔
107
                return nil, err
×
108
        }
×
109

110
        // Convert the request (api command definition + execution context) into a http request
111
        httpRequest, err := e.commandConverter.ConvertToHTTPRequest(commandRequest)
1✔
112
        if err != nil {
1✔
113
                return nil, errors.Join(ErrFailedToBuildHTTPRequest, err)
×
114
        }
×
115

116
        // Set the context, so we can cancel the request
117
        httpRequest = httpRequest.WithContext(ctx)
1✔
118
        e.logRequest(httpRequest)
1✔
119

1✔
120
        // Execute the request
1✔
121
        httpResponse, err := e.httpClient.Do(httpRequest)
1✔
122
        if err != nil {
1✔
123
                return nil, errors.Join(ErrFailedToConvertToHTTPRequest, err)
×
124
        }
×
125

126
        e.logResponse(httpResponse)
1✔
127

1✔
128
        //nolint: mnd // httpResponse.StatusCode >= StatusOK && httpResponse.StatusCode < StatusMultipleChoices makes this code harder to read
1✔
129
        isSuccess := httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300
1✔
130
        httpCode := httpResponse.StatusCode
1✔
131
        output := httpResponse.Body
1✔
132

1✔
133
        response := CommandResponse{
1✔
134
                IsSuccess: isSuccess,
1✔
135
                HTTPCode:  httpCode,
1✔
136
                Output:    output,
1✔
137
        }
1✔
138

1✔
139
        return &response, nil
1✔
140
}
141

142
func (e *Executor) SetContentType(commandRequest *CommandRequest) error {
1✔
143
        e.ensureInitialized()
1✔
144

1✔
145
        // Update the format if needed
1✔
146
        // For example if the requested format is a go template, change the request format to json
1✔
147
        contentType, err := e.formatter.ContentType(commandRequest.Format)
1✔
148
        if err != nil {
1✔
149
                return errors.Join(ErrFailedToHandleFormat, err)
×
150
        }
×
151
        commandRequest.ContentType = contentType
1✔
152

1✔
153
        return nil
1✔
154
}
155

156
// Log the request if the logger is set to debug
157
// Copied behavior and format used in the SDK: https://github.com/mongodb/atlas-sdk-go/blob/b3fee40e236a8ff2a1f1c160b6984a242136dbe6/admin/client.go#L322
158
func (e *Executor) logRequest(httpRequest *http.Request) {
1✔
159
        if !e.logger.IsDebugLevel() {
2✔
160
                return
1✔
161
        }
1✔
162

163
        dump, err := httputil.DumpRequestOut(httpRequest, true)
×
164
        if err != nil {
×
165
                return
×
166
        }
×
167

168
        _, _ = e.logger.Debugf("\n%s\n", string(dump))
×
169
}
170

171
// Log the response if the logger is set to debug
172
// Copied behavior and format used in the SDK: https://github.com/mongodb/atlas-sdk-go/blob/b3fee40e236a8ff2a1f1c160b6984a242136dbe6/admin/client.go#L335
173
func (e *Executor) logResponse(httpResponse *http.Response) {
1✔
174
        if !e.logger.IsDebugLevel() {
2✔
175
                return
1✔
176
        }
1✔
177

178
        dump, err := httputil.DumpResponse(httpResponse, true)
×
179
        if err != nil {
×
180
                return
×
181
        }
×
182

183
        _, _ = e.logger.Debugf("\n%s\n", string(dump))
×
184
}
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