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

mendersoftware / mender-server / 1689576603

26 Feb 2025 01:36PM UTC coverage: 76.183% (-0.02%) from 76.199%
1689576603

Pull #483

gitlab-ci

merlin-northern
feat(iot-manager): getting events by integration by ID

Allows to:
* get device events from iot-manager by integration ID
* creating new integration creates a new ID
* maintains backward compatibility for GET /event
* with UI calling the new endpoint this fixes the showing
  of events from old webhooks in the integraiton details

Changelog: Do not show events from previous webhooks in the integration details by making possible for the UI to get the events by integration ID, at the same time create new ID on ech integration creation.
Ticket: MEN-7801
Signed-off-by: Peter Grzybowski <peter@northern.tech>
Pull Request #483: feat(iot-manager): getting events by integration by ID

36 of 46 new or added lines in 6 files covered. (78.26%)

13 existing lines in 4 files now uncovered.

36875 of 48403 relevant lines covered (76.18%)

1.49 hits per line

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

93.64
/backend/services/iot-manager/api/http/management.go
1
// Copyright 2025 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/mender-server/pkg/identity"
27
        "github.com/mendersoftware/mender-server/pkg/rest.utils"
28

29
        "github.com/mendersoftware/mender-server/services/iot-manager/app"
30
        "github.com/mendersoftware/mender-server/services/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) {
3✔
43
        var (
3✔
44
                ctx = c.Request.Context()
3✔
45
                id  = identity.FromContext(ctx)
3✔
46
        )
3✔
47
        if id == nil || !id.IsUser {
4✔
48
                rest.RenderError(c, http.StatusForbidden, ErrMissingUserAuthentication)
1✔
49
                return nil, nil, ErrMissingUserAuthentication
1✔
50
        }
1✔
51
        return ctx, id, nil
3✔
52
}
53

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

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

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

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

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

83
        integration := model.Integration{}
3✔
84
        if err := c.ShouldBindJSON(&integration); err != nil {
4✔
85
                rest.RenderError(c,
1✔
86
                        http.StatusBadRequest,
1✔
87
                        errors.Wrap(err, "malformed request body"),
1✔
88
                )
1✔
89
                return
1✔
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)
3✔
97
        if err != nil {
4✔
98
                switch cause := errors.Cause(err); cause {
1✔
99
                case app.ErrIntegrationExists:
1✔
100
                        // NOTE: temporary limitation
1✔
101
                        rest.RenderError(c, http.StatusConflict, cause)
1✔
102
                default:
1✔
103
                        _ = c.Error(err)
1✔
104
                        rest.RenderError(c,
1✔
105
                                http.StatusInternalServerError,
1✔
106
                                err,
1✔
107
                        )
1✔
108
                }
109
                return
1✔
110
        }
111

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

116
}
117

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

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

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

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

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

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

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

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

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

229
        filter, err := getEventsFilterFromQuery(c)
3✔
230
        if err != nil {
4✔
231
                rest.RenderError(c,
1✔
232
                        http.StatusBadRequest,
1✔
233
                        err,
1✔
234
                )
1✔
235
                return
1✔
236
        }
1✔
237
        integrationID := c.Param(paramIntegrationID)
3✔
238
        if len(integrationID) > 0 {
3✔
NEW
239
                if err := uuid.Validate(integrationID); err == nil {
×
NEW
240
                        filter.IntegrationID = &integrationID
×
NEW
241
                }
×
242
        }
243

244
        events, err := h.app.GetEvents(ctx, *filter)
3✔
245
        if err != nil {
3✔
246
                rest.RenderError(c,
×
247
                        http.StatusInternalServerError,
×
248
                        err,
×
249
                )
×
250
                return
×
251
        }
×
252

253
        c.JSON(http.StatusOK, events)
3✔
254
}
255

256
// get events filter from query params
257
func getEventsFilterFromQuery(c *gin.Context) (*model.EventsFilter, error) {
3✔
258
        filter := model.EventsFilter{}
3✔
259
        page, perPage, err := rest.ParsePagingParameters(c.Request)
3✔
260
        if err != nil {
4✔
261
                return nil, err
1✔
262
        }
1✔
263
        filter.Skip = (page - 1) * perPage
3✔
264
        filter.Limit = perPage
3✔
265
        return &filter, err
3✔
266
}
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