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

mendersoftware / deployments / 1014824879

24 Sep 2023 12:33PM UTC coverage: 80.415% (+0.6%) from 79.834%
1014824879

Pull #923

gitlab-ci

tranchitella
feat: filter the list of releases by tags

Add support for filtering the release list by tags; this commit
deprecates the end-point `/v1/deployments/releases/lists` and introduces
a new end-point `/v2/deployments/releases/list` to fix the
capitalization of the keys in the response paylod, which was capitalized
by mistake and cannot be changed without breaking API
back-compatibility.

Ticket: MEN-6349
Changelog: deprecate the `/v1/deployments/releases/list` and-point
Changelog: new end-point `/v2/deployments/releases/list` which includes support for filtering the releases by tags

Signed-off-by: Fabio Tranchitella <fabio.tranchitella@northern.tech>
Pull Request #923: feat: filter the list of releases by tags

54 of 54 new or added lines in 5 files covered. (100.0%)

401 existing lines in 3 files now uncovered.

7243 of 9007 relevant lines covered (80.42%)

36.5 hits per line

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

88.44
/api/http/api_deployments_releases.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
        "encoding/json"
19
        "net/http"
20
        "strconv"
21

22
        "github.com/ant0ine/go-json-rest/rest"
23
        "github.com/pkg/errors"
24

25
        "github.com/mendersoftware/go-lib-micro/log"
26
        "github.com/mendersoftware/go-lib-micro/requestlog"
27
        "github.com/mendersoftware/go-lib-micro/rest_utils"
28

29
        "github.com/mendersoftware/deployments/app"
30
        "github.com/mendersoftware/deployments/model"
31
)
32

33
func redactReleaseName(r *rest.Request) {
21✔
34
        q := r.URL.Query()
21✔
35
        if q.Get(ParamName) != "" {
27✔
36
                q.Set(ParamName, Redacted)
6✔
37
                r.URL.RawQuery = q.Encode()
6✔
38
        }
6✔
39
}
40

41
func (d *DeploymentsApiHandlers) GetReleases(w rest.ResponseWriter, r *rest.Request) {
5✔
42
        l := requestlog.GetRequestLogger(r)
5✔
43

5✔
44
        defer redactReleaseName(r)
5✔
45
        filter := getReleaseOrImageFilter(r, false)
5✔
46
        releases, _, err := d.store.GetReleases(r.Context(), filter)
5✔
47
        if err != nil {
6✔
48
                d.view.RenderInternalError(w, r, err, l)
1✔
49
                return
1✔
50
        }
1✔
51

52
        d.view.RenderSuccessGet(w, model.ConvertReleasesToV1(releases))
4✔
53
}
54

55
func (d *DeploymentsApiHandlers) listReleases(w rest.ResponseWriter, r *rest.Request, v1 bool) {
8✔
56
        l := requestlog.GetRequestLogger(r)
8✔
57

8✔
58
        defer redactReleaseName(r)
8✔
59
        filter := getReleaseOrImageFilter(r, true)
8✔
60
        releases, totalCount, err := d.store.GetReleases(r.Context(), filter)
8✔
61
        if err != nil {
10✔
62
                d.view.RenderInternalError(w, r, err, l)
2✔
63
                return
2✔
64
        }
2✔
65

66
        hasNext := totalCount > int(filter.Page*filter.PerPage)
6✔
67
        links := rest_utils.MakePageLinkHdrs(r, uint64(filter.Page), uint64(filter.PerPage), hasNext)
6✔
68
        for _, l := range links {
12✔
69
                w.Header().Add("Link", l)
6✔
70
        }
6✔
71
        w.Header().Add(hdrTotalCount, strconv.Itoa(totalCount))
6✔
72

6✔
73
        if v1 {
9✔
74
                d.view.RenderSuccessGet(w, model.ConvertReleasesToV1(releases))
3✔
75
        } else {
6✔
76
                d.view.RenderSuccessGet(w, releases)
3✔
77
        }
3✔
78
}
79

80
func (d *DeploymentsApiHandlers) ListReleases(w rest.ResponseWriter, r *rest.Request) {
4✔
81
        d.listReleases(w, r, true)
4✔
82
}
4✔
83

84
func (d *DeploymentsApiHandlers) ListReleasesV2(w rest.ResponseWriter, r *rest.Request) {
4✔
85
        d.listReleases(w, r, false)
4✔
86
}
4✔
87

88
func (d *DeploymentsApiHandlers) PatchRelease(w rest.ResponseWriter, r *rest.Request) {
4✔
89
        ctx := r.Context()
4✔
90
        l := log.FromContext(ctx)
4✔
91

4✔
92
        releaseName := r.PathParam(ParamName)
4✔
93
        if releaseName == "" {
4✔
UNCOV
94
                err := errors.New("path parameter 'release_name' cannot be empty")
×
UNCOV
95
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
×
UNCOV
96
                return
×
UNCOV
97
        }
×
98

99
        var release model.ReleasePatch
4✔
100
        dec := json.NewDecoder(r.Body)
4✔
101
        if err := dec.Decode(&release); err != nil {
4✔
102
                rest_utils.RestErrWithLog(w, r, l,
×
103
                        errors.WithMessage(err,
×
104
                                "malformed JSON in request body"),
×
105
                        http.StatusBadRequest)
×
UNCOV
106
                return
×
UNCOV
107
        }
×
108
        if err := release.Validate(); err != nil {
5✔
109
                rest_utils.RestErrWithLog(w, r, l,
1✔
110
                        errors.WithMessage(err,
1✔
111
                                "invalid request body"),
1✔
112
                        http.StatusBadRequest)
1✔
113
                return
1✔
114
        }
1✔
115

116
        err := d.app.UpdateRelease(ctx, releaseName, release)
3✔
117
        if err != nil {
4✔
118
                status := http.StatusInternalServerError
1✔
119
                if errors.Is(err, app.ErrReleaseNotFound) {
1✔
UNCOV
120
                        status = http.StatusNotFound
×
121
                } else if errors.Is(err, model.ErrTooManyUniqueTags) {
1✔
UNCOV
122
                        status = http.StatusConflict
×
UNCOV
123
                }
×
124
                rest_utils.RestErrWithLog(w, r, l, err, status)
1✔
125
                return
1✔
126
        }
127

128
        w.WriteHeader(http.StatusNoContent)
2✔
129
}
130

131
func (d *DeploymentsApiHandlers) PutReleaseTags(
132
        w rest.ResponseWriter,
133
        r *rest.Request,
134
) {
8✔
135
        ctx := r.Context()
8✔
136
        l := log.FromContext(ctx)
8✔
137

8✔
138
        releaseName := r.PathParam(ParamName)
8✔
139
        if releaseName == "" {
9✔
140
                err := errors.New("path parameter 'release_name' cannot be empty")
1✔
141
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
1✔
142
                return
1✔
143
        }
1✔
144

145
        var tags model.Tags
7✔
146
        dec := json.NewDecoder(r.Body)
7✔
147
        if err := dec.Decode(&tags); err != nil {
8✔
148
                rest_utils.RestErrWithLog(w, r, l,
1✔
149
                        errors.WithMessage(err,
1✔
150
                                "malformed JSON in request body"),
1✔
151
                        http.StatusBadRequest)
1✔
152
                return
1✔
153
        }
1✔
154
        if err := tags.Validate(); err != nil {
7✔
155
                rest_utils.RestErrWithLog(w, r, l,
1✔
156
                        errors.WithMessage(err,
1✔
157
                                "invalid request body"),
1✔
158
                        http.StatusBadRequest)
1✔
159
                return
1✔
160
        }
1✔
161

162
        err := d.app.ReplaceReleaseTags(ctx, releaseName, tags)
5✔
163
        if err != nil {
8✔
164
                status := http.StatusInternalServerError
3✔
165
                if errors.Is(err, app.ErrReleaseNotFound) {
4✔
166
                        status = http.StatusNotFound
1✔
167
                } else if errors.Is(err, model.ErrTooManyUniqueTags) {
4✔
168
                        status = http.StatusConflict
1✔
169
                }
1✔
170
                rest_utils.RestErrWithLog(w, r, l, err, status)
3✔
171
                return
3✔
172
        }
173

174
        w.WriteHeader(http.StatusNoContent)
2✔
175
}
176

177
func (d *DeploymentsApiHandlers) GetReleaseTagKeys(
178
        w rest.ResponseWriter,
179
        r *rest.Request,
180
) {
3✔
181
        ctx := r.Context()
3✔
182
        l := log.FromContext(ctx)
3✔
183

3✔
184
        tags, err := d.app.ListReleaseTags(ctx)
3✔
185
        if err != nil {
5✔
186
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusInternalServerError)
2✔
187
                return
2✔
188
        }
2✔
189

190
        w.WriteHeader(http.StatusOK)
1✔
191
        err = w.WriteJson(tags)
1✔
192
        if err != nil {
1✔
UNCOV
193
                l.Errorf("failed to serialize JSON response: %s", err.Error())
×
UNCOV
194
        }
×
195
}
196

197
func (d *DeploymentsApiHandlers) GetReleasesUpdateTypes(
198
        w rest.ResponseWriter,
199
        r *rest.Request,
200
) {
3✔
201
        ctx := r.Context()
3✔
202
        l := log.FromContext(ctx)
3✔
203

3✔
204
        updateTypes, err := d.app.GetReleasesUpdateTypes(ctx)
3✔
205
        if err != nil {
4✔
206
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusInternalServerError)
1✔
207
                return
1✔
208
        }
1✔
209

210
        w.WriteHeader(http.StatusOK)
2✔
211
        err = w.WriteJson(updateTypes)
2✔
212
        if err != nil {
2✔
UNCOV
213
                l.Errorf("failed to serialize JSON response: %s", err.Error())
×
UNCOV
214
        }
×
215
}
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