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

mendersoftware / mender-server / 1622978334

13 Jan 2025 03:51PM UTC coverage: 72.802% (-3.8%) from 76.608%
1622978334

Pull #300

gitlab-ci

alfrunes
fix: Deployment device count should not exceed max devices

Added a condition to skip deployments when the device count reaches max
devices.

Changelog: Title
Ticket: MEN-7847
Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #300: fix: Deployment device count should not exceed max devices

4251 of 6164 branches covered (68.96%)

Branch coverage included in aggregate %.

0 of 18 new or added lines in 1 file covered. (0.0%)

2544 existing lines in 83 files now uncovered.

42741 of 58384 relevant lines covered (73.21%)

21.49 hits per line

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

90.46
/backend/services/deployments/api/http/routing.go
1
// Copyright 2024 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

26
        "github.com/mendersoftware/mender-server/pkg/accesslog"
27
        "github.com/mendersoftware/mender-server/pkg/identity"
28
        "github.com/mendersoftware/mender-server/pkg/log"
29
        "github.com/mendersoftware/mender-server/pkg/requestid"
30
        "github.com/mendersoftware/mender-server/pkg/requestlog"
31

32
        "github.com/mendersoftware/mender-server/services/deployments/app"
33
        "github.com/mendersoftware/mender-server/services/deployments/store"
34
        "github.com/mendersoftware/mender-server/services/deployments/utils/restutil"
35
        "github.com/mendersoftware/mender-server/services/deployments/utils/restutil/view"
36
)
37

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
189
        restApp, err := rest.MakeRouter(routes...)
1✔
190
        if err != nil {
1✔
191
                return nil, err
×
192
        }
×
193

194
        api.SetApp(restApp)
1✔
195
        return api.MakeHandler(), nil
1✔
196
}
197

198
func NewImagesResourceRoutes(controller *DeploymentsApiHandlers, cfg *Config) []*rest.Route {
1✔
199

1✔
200
        if controller == nil {
1✔
201
                return []*rest.Route{}
×
202
        }
×
203

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

247
func NewDeploymentsResourceRoutes(controller *DeploymentsApiHandlers) []*rest.Route {
1✔
248

1✔
249
        if controller == nil {
1✔
250
                return []*rest.Route{}
×
251
        }
×
252

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

1✔
279
                // Devices
1✔
280
                rest.Get(ApiUrlDevicesDeploymentsNext, controller.GetDeploymentForDevice),
1✔
281
                rest.Post(ApiUrlDevicesDeploymentsNext, controller.GetDeploymentForDevice),
1✔
282
                rest.Put(ApiUrlDevicesDeploymentStatus,
1✔
283
                        controller.PutDeploymentStatusForDevice),
1✔
284
                rest.Put(ApiUrlDevicesDeploymentsLog,
1✔
285
                        controller.PutDeploymentLogForDevice),
1✔
286
                rest.Get(ApiUrlDevicesDownloadConfig,
1✔
287
                        controller.DownloadConfiguration),
1✔
288
        }
1✔
289
}
290

291
func NewLimitsResourceRoutes(controller *DeploymentsApiHandlers) []*rest.Route {
1✔
292

1✔
293
        if controller == nil {
1✔
294
                return []*rest.Route{}
×
295
        }
×
296

297
        return []*rest.Route{
1✔
298
                // limits
1✔
299
                rest.Get(ApiUrlManagementLimitsName, controller.GetLimit),
1✔
300
        }
1✔
301
}
302

303
func InternalRoutes(controller *DeploymentsApiHandlers) []*rest.Route {
1✔
304
        if controller == nil {
1✔
305
                return []*rest.Route{}
×
306
        }
×
307

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

1✔
321
                // Configuration deployments (internal)
1✔
322
                rest.Post(ApiUrlInternalDeviceConfigurationDeployments,
1✔
323
                        controller.PostDeviceConfigurationDeployment),
1✔
324

1✔
325
                // Last device deployment status deployments (internal)
1✔
326
                rest.Post(ApiUrlInternalDeviceDeploymentLastStatusDeployments,
1✔
327
                        controller.GetDeviceDeploymentLastStatus),
1✔
328

1✔
329
                // Health Check
1✔
330
                rest.Get(ApiUrlInternalAlive, controller.AliveHandler),
1✔
331
                rest.Get(ApiUrlInternalHealth, controller.HealthHandler),
1✔
332
        }
1✔
333

1✔
334
        if !controller.config.DisableNewReleasesFeature {
2✔
335
                routes = append(routes,
1✔
336
                        rest.Post(ApiUrlInternalTenantArtifacts, controller.NewImageForTenantHandler),
1✔
337
                )
1✔
338
        } else {
2✔
339
                routes = append(routes,
1✔
340
                        rest.Post(ApiUrlInternalTenantArtifacts, ServiceUnavailable),
1✔
341
                )
1✔
342
        }
1✔
343

344
        return routes
1✔
345
}
346

347
func ReleasesRoutes(controller *DeploymentsApiHandlers) []*rest.Route {
1✔
348
        if controller == nil {
1✔
349
                return []*rest.Route{}
×
350
        }
×
351

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

372
func FMTConfigURL(scheme, hostname, deploymentID, deviceType, deviceID string) string {
1✔
373
        repl := strings.NewReplacer(
1✔
374
                "#"+ParamDeploymentID, url.PathEscape(deploymentID),
1✔
375
                "#"+ParamDeviceType, url.PathEscape(deviceType),
1✔
376
                "#"+ParamDeviceID, url.PathEscape(deviceID),
1✔
377
        )
1✔
378
        return scheme + "://" + hostname + repl.Replace(ApiUrlDevicesDownloadConfig)
1✔
379
}
1✔
380

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