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

mendersoftware / reporting / 844901443

pending completion
844901443

Pull #134

gitlab-ci

Krzysztof Jaskiewicz
feat: index last device deployment status
Pull Request #134: feat: index last device deployment status

37 of 42 new or added lines in 2 files covered. (88.1%)

4 existing lines in 2 files now uncovered.

2830 of 3328 relevant lines covered (85.04%)

16.82 hits per line

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

96.67
/client/deployments/client.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 deployments
16

17
import (
18
        "bytes"
19
        "context"
20
        "encoding/json"
21
        "io"
22
        "net/http"
23
        "strconv"
24
        "strings"
25
        "time"
26

27
        "github.com/pkg/errors"
28

29
        "github.com/mendersoftware/go-lib-micro/log"
30

31
        "github.com/mendersoftware/reporting/utils"
32
)
33

34
const (
35
        urlDeviceDeployments    = "/api/internal/v1/deployments/tenants/:tid/deployments/devices"
36
        urlLastDeviceDeployment = "/api/internal/v1/deployments/tenants/:tid/devices/deployments/last"
37
        defaultTimeout          = 10 * time.Second
38
)
39

40
//go:generate ../../x/mockgen.sh
41
type Client interface {
42
        // GetDeployments retrieves a list of deployments by ID
43
        GetDeployments(
44
                ctx context.Context,
45
                tenantID string,
46
                IDs []string,
47
        ) ([]*DeviceDeployment, error)
48
        // GetLatestDeployment retrieves the latest deployment for a given devices
49
        GetLatestFinishedDeployment(
50
                ctx context.Context,
51
                tenantID string,
52
                deviceIDs []string,
53
        ) ([]LastDeviceDeployment, error)
54
}
55

56
type client struct {
57
        client  *http.Client
58
        urlBase string
59
}
60

61
func NewClient(urlBase string) Client {
31✔
62
        return &client{
31✔
63
                client:  &http.Client{},
31✔
64
                urlBase: urlBase,
31✔
65
        }
31✔
66
}
31✔
67

68
func (c *client) GetDeployments(
69
        ctx context.Context,
70
        tenantID string,
71
        IDs []string,
72
) ([]*DeviceDeployment, error) {
18✔
73
        const maxDeploymentIDs = 20 // API constraint
18✔
74
        l := log.FromContext(ctx)
18✔
75

18✔
76
        url := utils.JoinURL(c.urlBase, urlDeviceDeployments)
18✔
77
        url = strings.Replace(url, ":tid", tenantID, 1)
18✔
78

18✔
79
        ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
18✔
80
        defer cancel()
18✔
81

18✔
82
        req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
18✔
83
        if err != nil {
20✔
84
                return nil, errors.Wrapf(err, "failed to create request")
2✔
85
        }
2✔
86

87
        var (
16✔
88
                i, j    int
16✔
89
                body    io.ReadCloser
16✔
90
                devDevs []*DeviceDeployment
16✔
91
                rsp     *http.Response
16✔
92
        )
16✔
93
        defer func() {
32✔
94
                if body != nil {
16✔
95
                        body.Close()
×
96
                }
×
97
        }()
98

99
        for i < len(IDs) && err == nil {
34✔
100
                j += maxDeploymentIDs
18✔
101
                if j > len(IDs) {
34✔
102
                        j = len(IDs)
16✔
103
                }
16✔
104
                q := req.URL.Query()
18✔
105
                q.Set("page", "1")
18✔
106
                q.Set("per_page", strconv.Itoa(j-i))
18✔
107
                q.Del("id")
18✔
108
                for k := i; k < j; k++ {
76✔
109
                        q.Add("id", IDs[k])
58✔
110
                }
58✔
111
                req.URL.RawQuery = q.Encode()
18✔
112
                rsp, err = c.client.Do(req) //nolint:bodyclose
18✔
113
                if err != nil {
20✔
114
                        err = errors.Wrapf(err, "failed to submit %s %s", req.Method, req.URL)
2✔
115
                        break
2✔
116
                }
117
                body = rsp.Body
16✔
118

16✔
119
                switch rsp.StatusCode {
16✔
120
                case http.StatusNotFound:
2✔
121
                        // pass
122
                case http.StatusOK:
12✔
123
                        dec := json.NewDecoder(rsp.Body)
12✔
124
                        var batch []*DeviceDeployment
12✔
125
                        if err = dec.Decode(&batch); err != nil {
14✔
126
                                err = errors.Wrap(err, "failed to parse request body")
2✔
127
                                break
2✔
128
                        }
129
                        if devDevs == nil {
18✔
130
                                devDevs = batch
8✔
131
                        } else {
10✔
132
                                devDevs = append(devDevs, batch...)
2✔
133
                        }
2✔
134
                default:
2✔
135
                        err = errors.Errorf("%s %s request failed with status %v",
2✔
136
                                req.Method, req.URL, rsp.Status)
2✔
137
                        l.Errorf(err.Error())
2✔
138
                }
139
                body.Close()
16✔
140
                body = nil
16✔
141
                i = j
16✔
142
        }
143
        if len(devDevs) == 0 {
26✔
144
                return nil, err
10✔
145
        }
10✔
146

147
        return devDevs, err
6✔
148
}
149

150
func (c *client) GetLatestFinishedDeployment(
151
        ctx context.Context,
152
        tenantID string,
153
        deviceIDs []string,
154
) ([]LastDeviceDeployment, error) {
20✔
155
        l := log.FromContext(ctx)
20✔
156

20✔
157
        url := utils.JoinURL(c.urlBase, urlLastDeviceDeployment)
20✔
158
        url = strings.Replace(url, ":tid", tenantID, 1)
20✔
159

20✔
160
        ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
20✔
161
        defer cancel()
20✔
162

20✔
163
        getReq := &GetLastDeviceDeploymentReq{
20✔
164
                DeviceIDs: deviceIDs,
20✔
165
        }
20✔
166

20✔
167
        body, err := json.Marshal(getReq)
20✔
168
        if err != nil {
20✔
NEW
169
                return nil, errors.Wrapf(err, "failed to serialize get last device deployment request")
×
NEW
170
        }
×
171

172
        rd := bytes.NewReader(body)
20✔
173

20✔
174
        req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, rd)
20✔
175
        if err != nil {
22✔
176
                return nil, errors.Wrapf(err, "failed to create request")
2✔
177
        }
2✔
178

179
        req.Header.Set("Content-Type", "application/json")
18✔
180

18✔
181
        rsp, err := c.client.Do(req)
18✔
182
        if err != nil {
20✔
183
                return nil, errors.Wrapf(err, "failed to submit %s %s", req.Method, req.URL)
2✔
184
        }
2✔
185
        defer rsp.Body.Close()
16✔
186

16✔
187
        if rsp.StatusCode == http.StatusNotFound {
18✔
188
                return nil, nil
2✔
189
        } else if rsp.StatusCode != http.StatusOK {
18✔
190
                err := errors.Errorf("%s %s request failed with status %v",
2✔
191
                        req.Method, req.URL, rsp.Status)
2✔
192
                l.Errorf(err.Error())
2✔
193
                return nil, err
2✔
194
        }
2✔
195

196
        dec := json.NewDecoder(rsp.Body)
12✔
197
        var lastDeployments GetLastDeviceDeploymentRsp
12✔
198
        if err = dec.Decode(&lastDeployments); err != nil {
14✔
199
                return nil, errors.Wrap(err, "failed to parse request body")
2✔
200
        } else if len(lastDeployments.DeviceDeploymentLastStatuses) == 0 {
14✔
201
                return nil, nil
2✔
202
        }
2✔
203
        return lastDeployments.DeviceDeploymentLastStatuses, nil
8✔
204
}
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