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

mendersoftware / reporting / 846379869

pending completion
846379869

push

gitlab-ci

GitHub
Merge pull request #136 from mendersoftware/master

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

2 existing lines in 1 file now uncovered.

2830 of 3328 relevant lines covered (85.04%)

16.78 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 {
62
        return &client{
63
                client:  &http.Client{},
64
                urlBase: urlBase,
65
        }
66
}
67

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

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

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

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

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

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

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

147
        return devDevs, err
148
}
149

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

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

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

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

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

172
        rd := bytes.NewReader(body)
173

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

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

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

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

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