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

mendersoftware / deviceauth / 1507843008

13 Sep 2024 11:01AM UTC coverage: 81.326%. Remained the same
1507843008

push

gitlab-ci

web-flow
Merge pull request #727 from mzedel/chore/deprecate

Chore/deprecate

4834 of 5944 relevant lines covered (81.33%)

42.77 hits per line

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

93.29
/client/inventory/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
package inventory
15

16
import (
17
        "bytes"
18
        "context"
19
        "crypto/tls"
20
        "encoding/json"
21
        "io/ioutil"
22
        "net/http"
23
        "strings"
24
        "time"
25

26
        "github.com/mendersoftware/go-lib-micro/log"
27
        "github.com/mendersoftware/go-lib-micro/rest_utils"
28
        "github.com/pkg/errors"
29

30
        "github.com/mendersoftware/deviceauth/model"
31
        "github.com/mendersoftware/deviceauth/utils"
32
)
33

34
const (
35
        urlHealth             = "/api/internal/v1/inventory/health"
36
        urlUpdateDeviceStatus = "/api/internal/v1/inventory/tenants/#tid/devices/status/"
37
        urlSetDeviceAttribute = "/api/internal/v1/inventory/tenants/#tid/device/" +
38
                "#did/attribute/scope/#scope"
39
        defaultTimeout = 10 * time.Second
40
)
41

42
//go:generate ../../utils/mockgen.sh
43
type Client interface {
44
        CheckHealth(ctx context.Context) error
45
        SetDeviceStatus(
46
                ctx context.Context,
47
                tenantId string,
48
                deviceUpdates []model.DeviceInventoryUpdate,
49
                status string,
50
        ) error
51
        SetDeviceIdentity(
52
                ctx context.Context,
53
                tenantId,
54
                deviceId string,
55
                idData map[string]interface{},
56
        ) error
57
}
58

59
type client struct {
60
        client  *http.Client
61
        urlBase string
62
}
63

64
func NewClient(urlBase string, skipVerify bool) *client {
31✔
65
        tr := &http.Transport{
31✔
66
                TLSClientConfig: &tls.Config{InsecureSkipVerify: skipVerify},
31✔
67
        }
31✔
68

31✔
69
        return &client{
31✔
70
                client: &http.Client{
31✔
71
                        Transport: tr,
31✔
72
                },
31✔
73
                urlBase: urlBase,
31✔
74
        }
31✔
75
}
31✔
76

77
func (c *client) CheckHealth(ctx context.Context) error {
8✔
78
        var apiErr rest_utils.ApiError
8✔
79

8✔
80
        if ctx == nil {
10✔
81
                ctx = context.Background()
2✔
82
        }
2✔
83
        if _, ok := ctx.Deadline(); !ok {
12✔
84
                var cancel context.CancelFunc
4✔
85
                ctx, cancel = context.WithTimeout(ctx, defaultTimeout)
4✔
86
                defer cancel()
4✔
87
        }
4✔
88
        req, _ := http.NewRequestWithContext(
8✔
89
                ctx, "GET",
8✔
90
                utils.JoinURL(c.urlBase, urlHealth), nil,
8✔
91
        )
8✔
92

8✔
93
        rsp, err := c.client.Do(req)
8✔
94
        if err != nil {
10✔
95
                return err
2✔
96
        }
2✔
97
        defer rsp.Body.Close()
6✔
98
        if rsp.StatusCode >= http.StatusOK && rsp.StatusCode < 300 {
8✔
99
                return nil
2✔
100
        }
2✔
101
        decoder := json.NewDecoder(rsp.Body)
4✔
102
        err = decoder.Decode(&apiErr)
4✔
103
        if err != nil {
6✔
104
                return errors.Errorf("health check HTTP error: %s", rsp.Status)
2✔
105
        }
2✔
106
        return &apiErr
2✔
107
}
108

109
func (c *client) SetDeviceStatus(
110
        ctx context.Context,
111
        tenantId string,
112
        deviceUpdates []model.DeviceInventoryUpdate,
113
        status string,
114
) error {
12✔
115
        l := log.FromContext(ctx)
12✔
116

12✔
117
        if len(deviceUpdates) < 1 {
14✔
118
                return errors.New("no devices to update")
2✔
119
        }
2✔
120
        body, err := json.Marshal(deviceUpdates)
10✔
121
        if err != nil {
10✔
122
                return errors.Wrapf(err, "failed to serialize devices")
×
123
        }
×
124

125
        rd := bytes.NewReader(body)
10✔
126

10✔
127
        url := utils.JoinURL(c.urlBase, urlUpdateDeviceStatus+status)
10✔
128
        url = strings.Replace(url, "#tid", tenantId, 1)
10✔
129

10✔
130
        req, err := http.NewRequest(http.MethodPost, url, rd)
10✔
131
        if err != nil {
12✔
132
                return errors.Wrapf(err, "failed to create request")
2✔
133
        }
2✔
134

135
        req.Header.Set("X-MEN-Source", "deviceauth")
8✔
136
        req.Header.Set("Content-Type", "application/json")
8✔
137

8✔
138
        ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
8✔
139
        defer cancel()
8✔
140

8✔
141
        rsp, err := c.client.Do(req.WithContext(ctx))
8✔
142
        if err != nil {
10✔
143
                return errors.Wrapf(err, "failed to submit %s %s", req.Method, req.URL)
2✔
144
        }
2✔
145
        defer rsp.Body.Close()
6✔
146

6✔
147
        if rsp.StatusCode != http.StatusOK {
8✔
148
                body, err := ioutil.ReadAll(rsp.Body)
2✔
149
                if err != nil {
2✔
150
                        body = []byte("<failed to read>")
×
151
                }
×
152
                l.Errorf("request %s %s failed with status %v, response: %s",
2✔
153
                        req.Method, req.URL, rsp.Status, body)
2✔
154

2✔
155
                return errors.Errorf(
2✔
156
                        "%s %s request failed with status %v", req.Method, req.URL, rsp.Status)
2✔
157
        }
158

159
        return nil
4✔
160
}
161

162
func (c *client) SetDeviceIdentity(
163
        ctx context.Context,
164
        tenantId,
165
        deviceId string,
166
        idData map[string]interface{},
167
) error {
16✔
168
        l := log.FromContext(ctx)
16✔
169

16✔
170
        if deviceId == "" {
18✔
171
                return errors.New("device id is needed")
2✔
172
        }
2✔
173

174
        attributes := make([]model.DeviceAttribute, len(idData))
14✔
175
        i := 0
14✔
176
        for name, value := range idData {
46✔
177
                if name == "status" {
34✔
178
                        //we have to forbid the client to override attribute status in identity scope
2✔
179
                        //since it stands for status of a device (as in: accepted, rejected, preauthorized)
2✔
180
                        continue
2✔
181
                }
182
                attribute := model.DeviceAttribute{
30✔
183
                        Name:        name,
30✔
184
                        Description: nil,
30✔
185
                        Value:       value,
30✔
186
                        Scope:       "identity",
30✔
187
                }
30✔
188
                attributes[i] = attribute
30✔
189
                i++
30✔
190
        }
191

192
        if i < 1 {
18✔
193
                return errors.New("no attributes to update")
4✔
194
        }
4✔
195

196
        if i != len(idData) {
10✔
197
                attributes = attributes[:i]
×
198
        }
×
199

200
        body, err := json.Marshal(attributes)
10✔
201
        if err != nil {
10✔
202
                return errors.Wrapf(err, "failed to serialize device attribute")
×
203
        }
×
204

205
        rd := bytes.NewReader(body)
10✔
206

10✔
207
        url := utils.JoinURL(c.urlBase, urlSetDeviceAttribute)
10✔
208
        url = strings.Replace(url, "#tid", tenantId, 1)
10✔
209
        url = strings.Replace(url, "#did", deviceId, 1)
10✔
210
        url = strings.Replace(url, "#scope", "identity", 1)
10✔
211

10✔
212
        req, err := http.NewRequest(http.MethodPatch, url, rd)
10✔
213
        if err != nil {
12✔
214
                return errors.Wrapf(err, "failed to create request")
2✔
215
        }
2✔
216

217
        req.Header.Set("X-MEN-Source", "deviceauth")
8✔
218
        req.Header.Set("Content-Type", "application/json")
8✔
219

8✔
220
        ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
8✔
221
        defer cancel()
8✔
222

8✔
223
        rsp, err := c.client.Do(req.WithContext(ctx))
8✔
224
        if err != nil {
10✔
225
                return errors.Wrapf(err, "failed to submit %s %s", req.Method, req.URL)
2✔
226
        }
2✔
227
        defer rsp.Body.Close()
6✔
228

6✔
229
        if rsp.StatusCode != http.StatusOK {
8✔
230
                body, err := ioutil.ReadAll(rsp.Body)
2✔
231
                if err != nil {
2✔
232
                        body = []byte("<failed to read>")
×
233
                }
×
234
                l.Errorf("request %s %s failed with status %v, response: %s",
2✔
235
                        req.Method, req.URL, rsp.Status, body)
2✔
236

2✔
237
                return errors.Errorf(
2✔
238
                        "%s %s request failed with status %v", req.Method, req.URL, rsp.Status)
2✔
239
        }
240

241
        return nil
4✔
242
}
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