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

mendersoftware / iot-manager / 1284435895

09 May 2024 10:28AM UTC coverage: 87.639% (+0.09%) from 87.551%
1284435895

Pull #283

gitlab-ci

tranchitella
feat: asynchronous processing of webhook requests and timeout

Changelog: process webhook requests asynchronously, returing `202 Accepted` instead of `204 No Content` or `200 OK`
Changelog: add a timeout for webhook requests, defaults to 10 seconds; you can modify it using the `webhooks_timeout_seconds` configuration setting

Ticket: MEN-7227

Signed-off-by: Fabio Tranchitella <fabio.tranchitella@northern.tech>
Pull Request #283: feat: asynchronous processing of webhook requests and timeout

49 of 53 new or added lines in 6 files covered. (92.45%)

902 existing lines in 13 files now uncovered.

3226 of 3681 relevant lines covered (87.64%)

11.44 hits per line

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

95.24
/api/http/management.go
1
// Copyright 2022 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
        "net/http"
20
        "strings"
21

22
        "github.com/gin-gonic/gin"
23
        "github.com/google/uuid"
24
        "github.com/pkg/errors"
25

26
        "github.com/mendersoftware/go-lib-micro/identity"
27
        "github.com/mendersoftware/go-lib-micro/rest.utils"
28

29
        "github.com/mendersoftware/iot-manager/app"
30
        "github.com/mendersoftware/iot-manager/model"
31
)
32

33
var (
34
        ErrMissingUserAuthentication = errors.New(
35
                "user identity missing from authorization token",
36
        )
37
        ErrIntegrationNotFound = errors.New("integration not found")
38
)
39

40
const hdrLocation = "Location"
41

42
func getContextAndIdentity(c *gin.Context) (context.Context, *identity.Identity, error) {
60✔
43
        var (
60✔
44
                ctx = c.Request.Context()
60✔
45
                id  = identity.FromContext(ctx)
60✔
46
        )
60✔
47
        if id == nil || !id.IsUser {
68✔
UNCOV
48
                rest.RenderError(c, http.StatusForbidden, ErrMissingUserAuthentication)
8✔
UNCOV
49
                return nil, nil, ErrMissingUserAuthentication
8✔
UNCOV
50
        }
8✔
51
        return ctx, id, nil
52✔
52
}
53

54
// ManagementHandler is the namespace for management API handlers.
55
type ManagementHandler APIHandler
56

57
// GET /integrations
UNCOV
58
func (h *ManagementHandler) GetIntegrations(c *gin.Context) {
4✔
UNCOV
59
        ctx, _, err := getContextAndIdentity(c)
4✔
UNCOV
60
        if err != nil {
5✔
UNCOV
61
                return
1✔
UNCOV
62
        }
1✔
63

UNCOV
64
        integrations, err := h.app.GetIntegrations(ctx)
3✔
UNCOV
65
        if err != nil {
4✔
UNCOV
66
                rest.RenderError(c,
1✔
UNCOV
67
                        http.StatusInternalServerError,
1✔
UNCOV
68
                        err,
1✔
UNCOV
69
                )
1✔
UNCOV
70
                return
1✔
UNCOV
71
        }
1✔
72

UNCOV
73
        c.JSON(http.StatusOK, integrations)
2✔
74
}
75

76
// POST /integrations
77
func (h *ManagementHandler) CreateIntegration(c *gin.Context) {
12✔
78
        ctx, _, err := getContextAndIdentity(c)
12✔
79
        if err != nil {
13✔
UNCOV
80
                return
1✔
UNCOV
81
        }
1✔
82

83
        integration := model.Integration{}
11✔
84
        if err := c.ShouldBindJSON(&integration); err != nil {
12✔
UNCOV
85
                rest.RenderError(c,
1✔
UNCOV
86
                        http.StatusBadRequest,
1✔
UNCOV
87
                        errors.Wrap(err, "malformed request body"),
1✔
UNCOV
88
                )
1✔
UNCOV
89
                return
1✔
UNCOV
90
        }
1✔
91

92
        // TODO verify that Azure connectionstring / AWS equivalent has correct permissions
93
        //      - service
94
        //      - registry read/write
95

96
        inserted, err := h.app.CreateIntegration(ctx, integration)
10✔
97
        if err != nil {
12✔
UNCOV
98
                switch cause := errors.Cause(err); cause {
2✔
UNCOV
99
                case app.ErrIntegrationExists:
1✔
UNCOV
100
                        // NOTE: temporary limitation
1✔
UNCOV
101
                        rest.RenderError(c, http.StatusConflict, cause)
1✔
UNCOV
102
                default:
1✔
UNCOV
103
                        _ = c.Error(err)
1✔
UNCOV
104
                        rest.RenderError(c,
1✔
UNCOV
105
                                http.StatusInternalServerError,
1✔
UNCOV
106
                                err,
1✔
UNCOV
107
                        )
1✔
108
                }
UNCOV
109
                return
2✔
110
        }
111

112
        path := strings.Replace(APIURLIntegration, ":id", inserted.ID.String(), 1)
8✔
113
        c.Header(hdrLocation, APIURLManagement+path)
8✔
114
        c.Status(http.StatusCreated)
8✔
115

116
}
117

118
// GET /integrations/{id}
UNCOV
119
func (h *ManagementHandler) GetIntegrationById(c *gin.Context) {
5✔
UNCOV
120
        ctx, _, err := getContextAndIdentity(c)
5✔
UNCOV
121
        if err != nil {
6✔
UNCOV
122
                return
1✔
UNCOV
123
        }
1✔
UNCOV
124
        integrationID, err := uuid.Parse(c.Param("id"))
4✔
UNCOV
125
        if err != nil {
5✔
UNCOV
126
                rest.RenderError(c,
1✔
UNCOV
127
                        http.StatusBadRequest,
1✔
UNCOV
128
                        errors.Wrap(err, "integration ID must be a valid UUID"),
1✔
UNCOV
129
                )
1✔
UNCOV
130
                return
1✔
UNCOV
131
        }
1✔
132

UNCOV
133
        integration, err := h.app.GetIntegrationById(ctx, integrationID)
3✔
UNCOV
134
        if err != nil {
5✔
UNCOV
135
                switch cause := errors.Cause(err); cause {
2✔
UNCOV
136
                case app.ErrIntegrationNotFound:
1✔
UNCOV
137
                        rest.RenderError(c, http.StatusNotFound, ErrIntegrationNotFound)
1✔
UNCOV
138
                default:
1✔
UNCOV
139
                        rest.RenderError(c,
1✔
UNCOV
140
                                http.StatusInternalServerError,
1✔
UNCOV
141
                                err,
1✔
UNCOV
142
                        )
1✔
143
                }
UNCOV
144
                return
2✔
145
        }
146

UNCOV
147
        c.JSON(http.StatusOK, integration)
1✔
148
}
149

150
// PUT /integrations/{id}/credentials
UNCOV
151
func (h *ManagementHandler) SetIntegrationCredentials(c *gin.Context) {
6✔
UNCOV
152
        ctx, _, err := getContextAndIdentity(c)
6✔
UNCOV
153
        if err != nil {
7✔
UNCOV
154
                return
1✔
UNCOV
155
        }
1✔
UNCOV
156
        integrationID, err := uuid.Parse(c.Param("id"))
5✔
UNCOV
157
        if err != nil {
6✔
UNCOV
158
                rest.RenderError(c,
1✔
UNCOV
159
                        http.StatusBadRequest,
1✔
UNCOV
160
                        errors.Wrap(err, "integration ID must be a valid UUID"),
1✔
UNCOV
161
                )
1✔
UNCOV
162
                return
1✔
UNCOV
163
        }
1✔
164

UNCOV
165
        credentials := model.Credentials{}
4✔
UNCOV
166
        if err := c.ShouldBindJSON(&credentials); err != nil {
5✔
UNCOV
167
                rest.RenderError(c,
1✔
UNCOV
168
                        http.StatusBadRequest,
1✔
UNCOV
169
                        errors.Wrap(err, "malformed request body"),
1✔
UNCOV
170
                )
1✔
UNCOV
171
                return
1✔
UNCOV
172
        }
1✔
173

UNCOV
174
        err = h.app.SetIntegrationCredentials(ctx, integrationID, credentials)
3✔
UNCOV
175
        if err != nil {
5✔
UNCOV
176
                switch cause := errors.Cause(err); cause {
2✔
UNCOV
177
                case app.ErrIntegrationNotFound:
1✔
UNCOV
178
                        rest.RenderError(c, http.StatusNotFound, ErrIntegrationNotFound)
1✔
UNCOV
179
                default:
1✔
UNCOV
180
                        rest.RenderError(c,
1✔
UNCOV
181
                                http.StatusInternalServerError,
1✔
UNCOV
182
                                err,
1✔
UNCOV
183
                        )
1✔
184
                }
UNCOV
185
                return
2✔
186
        }
187

UNCOV
188
        c.Status(http.StatusNoContent)
1✔
189
}
190

191
// DELETE /integrations/{id}
UNCOV
192
func (h *ManagementHandler) RemoveIntegration(c *gin.Context) {
6✔
UNCOV
193
        ctx, _, err := getContextAndIdentity(c)
6✔
UNCOV
194
        if err != nil {
7✔
UNCOV
195
                return
1✔
UNCOV
196
        }
1✔
UNCOV
197
        integrationID, err := uuid.Parse(c.Param("id"))
5✔
UNCOV
198
        if err != nil {
6✔
UNCOV
199
                rest.RenderError(c,
1✔
UNCOV
200
                        http.StatusBadRequest,
1✔
UNCOV
201
                        errors.Wrap(err, "integration ID must be a valid UUID"),
1✔
UNCOV
202
                )
1✔
UNCOV
203
                return
1✔
UNCOV
204
        }
1✔
UNCOV
205
        if err = h.app.RemoveIntegration(ctx, integrationID); err != nil {
7✔
UNCOV
206
                switch cause := errors.Cause(err); cause {
3✔
UNCOV
207
                case app.ErrIntegrationNotFound:
1✔
UNCOV
208
                        rest.RenderError(c, http.StatusNotFound, ErrIntegrationNotFound)
1✔
UNCOV
209
                case app.ErrCannotRemoveIntegration:
1✔
UNCOV
210
                        rest.RenderError(c, http.StatusConflict, app.ErrCannotRemoveIntegration)
1✔
UNCOV
211
                default:
1✔
UNCOV
212
                        rest.RenderError(c,
1✔
UNCOV
213
                                http.StatusInternalServerError,
1✔
UNCOV
214
                                err,
1✔
UNCOV
215
                        )
1✔
216
                }
UNCOV
217
                return
3✔
218
        }
UNCOV
219
        c.Status(http.StatusNoContent)
1✔
220
}
221

222
// GET /events
223
func (h *ManagementHandler) GetEvents(c *gin.Context) {
6✔
224
        ctx, _, err := getContextAndIdentity(c)
6✔
225
        if err != nil {
6✔
226
                return
×
227
        }
×
228

229
        filter, err := getEventsFilterFromQuery(c)
6✔
230
        if err != nil {
7✔
UNCOV
231
                rest.RenderError(c,
1✔
UNCOV
232
                        http.StatusBadRequest,
1✔
UNCOV
233
                        err,
1✔
UNCOV
234
                )
1✔
UNCOV
235
                return
1✔
UNCOV
236
        }
1✔
237

238
        events, err := h.app.GetEvents(ctx, *filter)
5✔
239
        if err != nil {
5✔
240
                rest.RenderError(c,
×
241
                        http.StatusInternalServerError,
×
242
                        err,
×
243
                )
×
244
                return
×
245
        }
×
246

247
        c.JSON(http.StatusOK, events)
5✔
248
}
249

250
// get events filter from query params
251
func getEventsFilterFromQuery(c *gin.Context) (*model.EventsFilter, error) {
6✔
252
        filter := model.EventsFilter{}
6✔
253
        page, perPage, err := rest.ParsePagingParameters(c.Request)
6✔
254
        if err != nil {
7✔
UNCOV
255
                return nil, err
1✔
UNCOV
256
        }
1✔
257
        filter.Skip = (page - 1) * perPage
5✔
258
        filter.Limit = perPage
5✔
259
        return &filter, err
5✔
260
}
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