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

mendersoftware / deployments / 1376315855

04 Jul 2024 10:22AM UTC coverage: 79.694% (-0.06%) from 79.752%
1376315855

push

gitlab-ci

web-flow
Merge pull request #1029 from alfrunes/perf-deployment-status

perf: Optimize database interactions when updating deployment stats

59 of 80 new or added lines in 3 files covered. (73.75%)

711 existing lines in 10 files now uncovered.

8124 of 10194 relevant lines covered (79.69%)

34.59 hits per line

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

92.05
/api/http/routing.go
1
// Copyright 2023 Northern.tech AS
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 http
16

17
import (
18
        "context"
19
        "mime"
20
        "net/http"
21
        "net/url"
22
        "strings"
23

24
        "github.com/ant0ine/go-json-rest/rest"
25
        "github.com/mendersoftware/go-lib-micro/accesslog"
26
        "github.com/mendersoftware/go-lib-micro/identity"
27
        "github.com/mendersoftware/go-lib-micro/log"
28
        "github.com/mendersoftware/go-lib-micro/requestid"
29
        "github.com/mendersoftware/go-lib-micro/requestlog"
30

31
        "github.com/mendersoftware/deployments/app"
32
        "github.com/mendersoftware/deployments/store"
33
        "github.com/mendersoftware/deployments/utils/restutil"
34
        "github.com/mendersoftware/deployments/utils/restutil/view"
35
)
36

37
const (
38
        ApiUrlInternal   = "/api/internal/v1/deployments"
39
        ApiUrlManagement = "/api/management/v1/deployments"
40
        ApiUrlDevices    = "/api/devices/v1/deployments"
41

42
        ApiUrlManagementArtifacts               = ApiUrlManagement + "/artifacts"
43
        ApiUrlManagementArtifactsList           = ApiUrlManagement + "/artifacts/list"
44
        ApiUrlManagementArtifactsGenerate       = ApiUrlManagement + "/artifacts/generate"
45
        ApiUrlManagementArtifactsDirectUpload   = ApiUrlManagement + "/artifacts/directupload"
46
        ApiUrlManagementArtifactsCompleteUpload = ApiUrlManagementArtifactsDirectUpload +
47
                "/#id/complete"
48
        ApiUrlManagementArtifactsId         = ApiUrlManagement + "/artifacts/#id"
49
        ApiUrlManagementArtifactsIdDownload = ApiUrlManagement + "/artifacts/#id/download"
50

51
        ApiUrlManagementDeployments                   = ApiUrlManagement + "/deployments"
52
        ApiUrlManagementMultipleDeploymentsStatistics = ApiUrlManagement +
53
                "/deployments/statistics/list"
54
        ApiUrlManagementDeploymentsGroup       = ApiUrlManagement + "/deployments/group/#name"
55
        ApiUrlManagementDeploymentsId          = ApiUrlManagement + "/deployments/#id"
56
        ApiUrlManagementDeploymentsStatistics  = ApiUrlManagement + "/deployments/#id/statistics"
57
        ApiUrlManagementDeploymentsStatus      = ApiUrlManagement + "/deployments/#id/status"
58
        ApiUrlManagementDeploymentsDevices     = ApiUrlManagement + "/deployments/#id/devices"
59
        ApiUrlManagementDeploymentsDevicesList = ApiUrlManagement + "/deployments/#id/devices/list"
60
        ApiUrlManagementDeploymentsLog         = ApiUrlManagement +
61
                "/deployments/#id/devices/#devid/log"
62
        ApiUrlManagementDeploymentsDeviceId      = ApiUrlManagement + "/deployments/devices/#id"
63
        ApiUrlManagementDeploymentsDeviceHistory = ApiUrlManagement + "/deployments/devices/#id/history"
64
        ApiUrlManagementDeploymentsDeviceList    = ApiUrlManagement + "/deployments/#id/device_list"
65

66
        ApiUrlManagementReleases     = ApiUrlManagement + "/deployments/releases"
67
        ApiUrlManagementReleasesList = ApiUrlManagement + "/deployments/releases/list"
68

69
        ApiUrlManagementLimitsName = ApiUrlManagement + "/limits/#name"
70

71
        ApiUrlManagementV2                      = "/api/management/v2/deployments"
72
        ApiUrlManagementV2Releases              = ApiUrlManagementV2 + "/deployments/releases"
73
        ApiUrlManagementV2ReleasesName          = ApiUrlManagementV2Releases + "/#name"
74
        ApiUrlManagementV2ReleaseTags           = ApiUrlManagementV2Releases + "/#name/tags"
75
        ApiUrlManagementV2ReleaseAllTags        = ApiUrlManagementV2 + "/releases/all/tags"
76
        ApiUrlManagementV2ReleaseAllUpdateTypes = ApiUrlManagementV2 + "/releases/all/types"
77

78
        ApiUrlDevicesDeploymentsNext  = ApiUrlDevices + "/device/deployments/next"
79
        ApiUrlDevicesDeploymentStatus = ApiUrlDevices + "/device/deployments/#id/status"
80
        ApiUrlDevicesDeploymentsLog   = ApiUrlDevices + "/device/deployments/#id/log"
81
        ApiUrlDevicesDownloadConfig   = ApiUrlDevices +
82
                "/download/configuration/#deployment_id/#device_type/#device_id"
83

84
        ApiUrlInternalAlive                    = ApiUrlInternal + "/alive"
85
        ApiUrlInternalHealth                   = ApiUrlInternal + "/health"
86
        ApiUrlInternalTenants                  = ApiUrlInternal + "/tenants"
87
        ApiUrlInternalTenantDeployments        = ApiUrlInternal + "/tenants/#tenant/deployments"
88
        ApiUrlInternalTenantDeploymentsDevices = ApiUrlInternal + "/tenants/#tenant/deployments/devices"
89
        ApiUrlInternalTenantDeploymentsDevice  = ApiUrlInternal +
90
                "/tenants/#tenant/deployments/devices/#id"
91
        ApiUrlInternalTenantArtifacts       = ApiUrlInternal + "/tenants/#tenant/artifacts"
92
        ApiUrlInternalTenantStorageSettings = ApiUrlInternal +
93
                "/tenants/#tenant/storage/settings"
94
        ApiUrlInternalDeviceConfigurationDeployments = ApiUrlInternal +
95
                "/tenants/#tenant/configuration/deployments/#deployment_id/devices/#device_id"
96
        ApiUrlInternalDeviceDeploymentLastStatusDeployments = ApiUrlInternal +
97
                "/tenants/#tenant/devices/deployments/last"
98
)
99

100
func contentTypeMiddleware(h rest.HandlerFunc) rest.HandlerFunc {
534✔
101
        checkJSON := (&rest.ContentTypeCheckerMiddleware{}).
534✔
102
                MiddlewareFunc(h)
534✔
103
        checkMultipart := func(w rest.ResponseWriter, r *rest.Request) {
537✔
104
                mediatype, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
3✔
105
                if r.ContentLength > 0 && !(mediatype == "multipart/form-data") {
3✔
106
                        rest.Error(w,
×
107
                                "Bad Content-Type, expected 'multipart/form-data'",
×
108
                                http.StatusUnsupportedMediaType)
×
109
                        return
×
110
                }
×
111
                h(w, r)
3✔
112
        }
113
        return func(w rest.ResponseWriter, r *rest.Request) {
543✔
114
                if r.Method == http.MethodPost &&
9✔
115
                        (r.URL.Path == ApiUrlManagementArtifacts ||
9✔
116
                                r.URL.Path == ApiUrlManagementArtifactsGenerate) {
12✔
117
                        checkMultipart(w, r)
3✔
118
                } else {
10✔
119
                        checkJSON(w, r)
7✔
120
                }
7✔
121
        }
122
}
123

124
func wrapMiddleware(middleware rest.Middleware, routes ...*rest.Route) []*rest.Route {
17✔
125
        for _, route := range routes {
1,084✔
126
                route.Func = middleware.MiddlewareFunc(route.Func)
1,067✔
127
        }
1,067✔
128
        return routes
17✔
129
}
130

131
// NewRouter defines all REST API routes.
132
func NewHandler(
133
        ctx context.Context,
134
        app app.App,
135
        ds store.DataStore,
136
        cfg *Config,
137
) (http.Handler, error) {
9✔
138
        api := rest.NewApi()
9✔
139
        api.Use(
9✔
140
                // logging
9✔
141
                &requestlog.RequestLogMiddleware{},
9✔
142
                &requestid.RequestIdMiddleware{},
9✔
143
                &accesslog.AccessLogMiddleware{
9✔
144
                        Format: accesslog.SimpleLogFormat,
9✔
145
                        DisableLog: func(statusCode int, r *rest.Request) bool {
18✔
146
                                if statusCode < 300 {
12✔
147
                                        if r.URL.Path == ApiUrlInternalHealth ||
3✔
148
                                                r.URL.Path == ApiUrlInternalAlive {
3✔
149
                                                return true
×
150
                                        }
×
151
                                }
152
                                return false
9✔
153
                        },
154
                },
155
        )
156

157
        // Create and configure API handlers
158
        //
159
        // Encode base64 secret in either std or URL encoding ignoring padding.
160
        deploymentsHandlers := NewDeploymentsApiHandlers(
9✔
161
                ds, new(view.RESTView), app, cfg,
9✔
162
        )
9✔
163

9✔
164
        // Routing
9✔
165
        imageRoutes := NewImagesResourceRoutes(deploymentsHandlers, cfg)
9✔
166
        deploymentsRoutes := NewDeploymentsResourceRoutes(deploymentsHandlers)
9✔
167
        limitsRoutes := NewLimitsResourceRoutes(deploymentsHandlers)
9✔
168
        internalRoutes := InternalRoutes(deploymentsHandlers)
9✔
169
        releasesRoutes := ReleasesRoutes(deploymentsHandlers)
9✔
170

9✔
171
        publicRoutes := append(releasesRoutes, deploymentsRoutes...)
9✔
172
        publicRoutes = append(publicRoutes, limitsRoutes...)
9✔
173
        publicRoutes = append(publicRoutes, imageRoutes...)
9✔
174
        publicRoutes = restutil.AutogenOptionsRoutes(
9✔
175
                restutil.NewOptionsHandler,
9✔
176
                publicRoutes...,
9✔
177
        )
9✔
178
        publicRoutes = wrapMiddleware(&identity.IdentityMiddleware{
9✔
179
                UpdateLogger: true,
9✔
180
        }, publicRoutes...)
9✔
181
        publicRoutes = wrapMiddleware(
9✔
182
                rest.MiddlewareSimple(contentTypeMiddleware),
9✔
183
                publicRoutes...,
9✔
184
        )
9✔
185
        routes := append(publicRoutes, internalRoutes...)
9✔
186

9✔
187
        restApp, err := rest.MakeRouter(routes...)
9✔
188
        if err != nil {
9✔
189
                return nil, err
×
190
        }
×
191

192
        api.SetApp(restApp)
9✔
193
        return api.MakeHandler(), nil
9✔
194
}
195

196
func NewImagesResourceRoutes(controller *DeploymentsApiHandlers, cfg *Config) []*rest.Route {
9✔
197

9✔
198
        if controller == nil {
9✔
199
                return []*rest.Route{}
×
200
        }
×
201

202
        routes := []*rest.Route{
9✔
203
                rest.Get(ApiUrlManagementArtifacts, controller.GetImages),
9✔
204
                rest.Get(ApiUrlManagementArtifactsList, controller.ListImages),
9✔
205
                rest.Get(ApiUrlManagementArtifactsId, controller.GetImage),
9✔
206
                rest.Get(ApiUrlManagementArtifactsIdDownload, controller.DownloadLink),
9✔
207
        }
9✔
208
        if !controller.config.DisableNewReleasesFeature {
17✔
209
                routes = append(routes,
8✔
210
                        rest.Post(ApiUrlManagementArtifacts, controller.NewImage),
8✔
211
                        rest.Post(ApiUrlManagementArtifactsGenerate, controller.GenerateImage),
8✔
212
                        rest.Delete(ApiUrlManagementArtifactsId, controller.DeleteImage),
8✔
213
                        rest.Put(ApiUrlManagementArtifactsId, controller.EditImage),
8✔
214
                )
8✔
215
        } else {
9✔
216
                routes = append(routes,
1✔
217
                        rest.Post(ApiUrlManagementArtifacts, ServiceUnavailable),
1✔
218
                        rest.Post(ApiUrlManagementArtifactsGenerate, ServiceUnavailable),
1✔
219
                        rest.Delete(ApiUrlManagementArtifactsId, ServiceUnavailable),
1✔
220
                        rest.Put(ApiUrlManagementArtifactsId, ServiceUnavailable),
1✔
221
                )
1✔
222
        }
1✔
223
        if !controller.config.DisableNewReleasesFeature && cfg.EnableDirectUpload {
16✔
224
                log.NewEmpty().Infof(
7✔
225
                        "direct upload enabled: POST %s",
7✔
226
                        ApiUrlManagementArtifactsDirectUpload,
7✔
227
                )
7✔
228
                if cfg.EnableDirectUploadSkipVerify {
8✔
UNCOV
229
                        log.NewEmpty().Info(
1✔
UNCOV
230
                                "direct upload enabled SkipVerify",
1✔
UNCOV
231
                        )
1✔
UNCOV
232
                }
1✔
233
                routes = append(routes, rest.Post(
7✔
234
                        ApiUrlManagementArtifactsDirectUpload,
7✔
235
                        controller.UploadLink,
7✔
236
                ))
7✔
237
                routes = append(routes, rest.Post(
7✔
238
                        ApiUrlManagementArtifactsCompleteUpload,
7✔
239
                        controller.CompleteUpload,
7✔
240
                ))
7✔
241
        }
242
        return routes
9✔
243
}
244

245
func NewDeploymentsResourceRoutes(controller *DeploymentsApiHandlers) []*rest.Route {
29✔
246

29✔
247
        if controller == nil {
29✔
248
                return []*rest.Route{}
×
249
        }
×
250

251
        return []*rest.Route{
29✔
252
                // Deployments
29✔
253
                rest.Post(ApiUrlManagementDeployments, controller.PostDeployment),
29✔
254
                rest.Post(ApiUrlManagementDeploymentsGroup, controller.DeployToGroup),
29✔
255
                rest.Get(ApiUrlManagementDeployments, controller.LookupDeployment),
29✔
256
                rest.Get(ApiUrlManagementDeploymentsId, controller.GetDeployment),
29✔
257
                rest.Post(ApiUrlManagementMultipleDeploymentsStatistics,
29✔
258
                        controller.GetDeploymentsStats),
29✔
259
                rest.Get(ApiUrlManagementDeploymentsStatistics, controller.GetDeploymentStats),
29✔
260
                rest.Put(ApiUrlManagementDeploymentsStatus, controller.AbortDeployment),
29✔
261
                rest.Get(ApiUrlManagementDeploymentsDevices,
29✔
262
                        controller.GetDeviceStatusesForDeployment),
29✔
263
                rest.Get(ApiUrlManagementDeploymentsDevicesList,
29✔
264
                        controller.GetDevicesListForDeployment),
29✔
265
                rest.Get(ApiUrlManagementDeploymentsLog,
29✔
266
                        controller.GetDeploymentLogForDevice),
29✔
267
                rest.Delete(ApiUrlManagementDeploymentsDeviceId,
29✔
268
                        controller.AbortDeviceDeployments),
29✔
269
                rest.Delete(ApiUrlManagementDeploymentsDeviceHistory,
29✔
270
                        controller.DeleteDeviceDeploymentsHistory),
29✔
271
                rest.Get(ApiUrlManagementDeploymentsDeviceId,
29✔
272
                        controller.ListDeviceDeployments),
29✔
273
                rest.Get(ApiUrlManagementDeploymentsDeviceList,
29✔
274
                        controller.GetDeploymentDeviceList),
29✔
275

29✔
276
                // Devices
29✔
277
                rest.Get(ApiUrlDevicesDeploymentsNext, controller.GetDeploymentForDevice),
29✔
278
                rest.Post(ApiUrlDevicesDeploymentsNext, controller.GetDeploymentForDevice),
29✔
279
                rest.Put(ApiUrlDevicesDeploymentStatus,
29✔
280
                        controller.PutDeploymentStatusForDevice),
29✔
281
                rest.Put(ApiUrlDevicesDeploymentsLog,
29✔
282
                        controller.PutDeploymentLogForDevice),
29✔
283
                rest.Get(ApiUrlDevicesDownloadConfig,
29✔
284
                        controller.DownloadConfiguration),
29✔
285
        }
29✔
286
}
287

288
func NewLimitsResourceRoutes(controller *DeploymentsApiHandlers) []*rest.Route {
9✔
289

9✔
290
        if controller == nil {
9✔
291
                return []*rest.Route{}
×
292
        }
×
293

294
        return []*rest.Route{
9✔
295
                // limits
9✔
296
                rest.Get(ApiUrlManagementLimitsName, controller.GetLimit),
9✔
297
        }
9✔
298
}
299

300
func InternalRoutes(controller *DeploymentsApiHandlers) []*rest.Route {
9✔
301
        if controller == nil {
9✔
302
                return []*rest.Route{}
×
303
        }
×
304

305
        routes := []*rest.Route{
9✔
306
                rest.Post(ApiUrlInternalTenants, controller.ProvisionTenantsHandler),
9✔
307
                rest.Get(ApiUrlInternalTenantDeployments, controller.DeploymentsPerTenantHandler),
9✔
308
                rest.Get(ApiUrlInternalTenantDeploymentsDevices,
9✔
309
                        controller.ListDeviceDeploymentsByIDsInternal),
9✔
310
                rest.Get(ApiUrlInternalTenantDeploymentsDevice,
9✔
311
                        controller.ListDeviceDeploymentsInternal),
9✔
312
                rest.Delete(ApiUrlInternalTenantDeploymentsDevice,
9✔
313
                        controller.AbortDeviceDeploymentsInternal),
9✔
314
                // per-tenant storage settings
9✔
315
                rest.Get(ApiUrlInternalTenantStorageSettings, controller.GetTenantStorageSettingsHandler),
9✔
316
                rest.Put(ApiUrlInternalTenantStorageSettings, controller.PutTenantStorageSettingsHandler),
9✔
317

9✔
318
                // Configuration deployments (internal)
9✔
319
                rest.Post(ApiUrlInternalDeviceConfigurationDeployments,
9✔
320
                        controller.PostDeviceConfigurationDeployment),
9✔
321

9✔
322
                // Last device deployment status deployments (internal)
9✔
323
                rest.Post(ApiUrlInternalDeviceDeploymentLastStatusDeployments,
9✔
324
                        controller.GetDeviceDeploymentLastStatus),
9✔
325

9✔
326
                // Health Check
9✔
327
                rest.Get(ApiUrlInternalAlive, controller.AliveHandler),
9✔
328
                rest.Get(ApiUrlInternalHealth, controller.HealthHandler),
9✔
329
        }
9✔
330

9✔
331
        if !controller.config.DisableNewReleasesFeature {
17✔
332
                routes = append(routes,
8✔
333
                        rest.Post(ApiUrlInternalTenantArtifacts, controller.NewImageForTenantHandler),
8✔
334
                )
8✔
335
        } else {
9✔
336
                routes = append(routes,
1✔
337
                        rest.Post(ApiUrlInternalTenantArtifacts, ServiceUnavailable),
1✔
338
                )
1✔
339
        }
1✔
340

341
        return routes
9✔
342
}
343

344
func ReleasesRoutes(controller *DeploymentsApiHandlers) []*rest.Route {
25✔
345
        if controller == nil {
25✔
346
                return []*rest.Route{}
×
347
        }
×
348

349
        if controller.config.DisableNewReleasesFeature {
26✔
350
                return []*rest.Route{
1✔
351
                        rest.Get(ApiUrlManagementReleases, controller.GetReleases),
1✔
352
                        rest.Get(ApiUrlManagementReleasesList, controller.ListReleases),
1✔
353
                }
1✔
354
        } else {
25✔
355
                return []*rest.Route{
24✔
356
                        rest.Get(ApiUrlManagementReleases, controller.GetReleases),
24✔
357
                        rest.Get(ApiUrlManagementReleasesList, controller.ListReleases),
24✔
358
                        rest.Get(ApiUrlManagementV2Releases, controller.ListReleasesV2),
24✔
359
                        rest.Put(ApiUrlManagementV2ReleaseTags, controller.PutReleaseTags),
24✔
360
                        rest.Get(ApiUrlManagementV2ReleaseAllTags, controller.GetReleaseTagKeys),
24✔
361
                        rest.Get(ApiUrlManagementV2ReleaseAllUpdateTypes, controller.GetReleasesUpdateTypes),
24✔
362
                        rest.Patch(ApiUrlManagementV2ReleasesName, controller.PatchRelease),
24✔
363
                        rest.Delete(ApiUrlManagementV2Releases, controller.DeleteReleases),
24✔
364
                }
24✔
365
        }
24✔
366
}
367

368
func FMTConfigURL(scheme, hostname, deploymentID, deviceType, deviceID string) string {
13✔
369
        repl := strings.NewReplacer(
13✔
370
                "#"+ParamDeploymentID, url.PathEscape(deploymentID),
13✔
371
                "#"+ParamDeviceType, url.PathEscape(deviceType),
13✔
372
                "#"+ParamDeviceID, url.PathEscape(deviceID),
13✔
373
        )
13✔
374
        return scheme + "://" + hostname + repl.Replace(ApiUrlDevicesDownloadConfig)
13✔
375
}
13✔
376

377
func ServiceUnavailable(w rest.ResponseWriter, r *rest.Request) {
1✔
378
        w.WriteHeader(http.StatusServiceUnavailable)
1✔
379
}
1✔
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

© 2025 Coveralls, Inc