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

mendersoftware / iot-manager / 1401702106

05 Aug 2024 08:32PM UTC coverage: 87.577%. Remained the same
1401702106

push

gitlab-ci

web-flow
Merge pull request #295 from mendersoftware/dependabot/docker/docker-dependencies-03b04ac819

chore: bump golang from 1.22.4-alpine3.19 to 1.22.5-alpine3.19 in the docker-dependencies group

3264 of 3727 relevant lines covered (87.58%)

11.44 hits per line

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

95.61
/client/devauth/client.go
1
// Copyright 2022 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 devauth
16

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

26
        common "github.com/mendersoftware/iot-manager/client"
27

28
        validation "github.com/go-ozzo/ozzo-validation/v4"
29
        "github.com/mendersoftware/go-lib-micro/identity"
30
        "github.com/mendersoftware/go-lib-micro/requestid"
31
        "github.com/mendersoftware/go-lib-micro/rest.utils"
32
        "github.com/pkg/errors"
33
)
34

35
const (
36
        URIInternal        = "/api/internal/v1/devauth"
37
        URIInternalDevices = URIInternal + "/tenants/:tenant/devices"
38
        URIInternalAlive   = URIInternal + "/alive"
39
)
40

41
var (
42
        ErrInvalidURL = errors.New("invalid URL format")
43
)
44

45
const DefaultTimeout = time.Second * 10
46

47
// Client interface exposing a portion of the internal deviceauth API.
48
//
49
//go:generate ../../utils/mockgen.sh
50
type Client interface {
51
        Ping(ctx context.Context) error
52
        GetDevices(context.Context, []string) ([]Device, error)
53
}
54

55
// Config provides initialization options for creating a new client.
56
type Config struct {
57
        // Client provides an option to override the http.Client used for
58
        // performing the requests.
59
        Client *http.Client
60
        // DevauthAddress is the base URI to the deviceauth service (requires
61
        // a minimal of an addressable hostname, to a full blown URL).
62
        DevauthAddress string
63
}
64

65
type urlValidator struct{}
66

67
func (urlValidator) Validate(v interface{}) error {
25✔
68
        if uri, ok := v.(string); ok {
50✔
69
                _, err := url.Parse(uri)
25✔
70
                if err != nil {
27✔
71
                        return ErrInvalidURL
2✔
72
                }
2✔
73
                return nil
23✔
74
        }
75
        return ErrInvalidURL
×
76
}
77

78
func (conf Config) Validate() error {
27✔
79
        return validation.ValidateStruct(&conf,
27✔
80
                validation.Field(
27✔
81
                        &conf.DevauthAddress, validation.Required,
27✔
82
                ),
27✔
83
                validation.Field(
27✔
84
                        &conf.DevauthAddress, validation.Required, urlValidator{},
27✔
85
                ),
27✔
86
        )
27✔
87
}
27✔
88

89
type client struct {
90
        *http.Client
91
        uri string
92
}
93

94
// NewClient initializes a new client from the given configuration options.
95
func NewClient(config Config) (Client, error) {
27✔
96
        client := &client{
27✔
97
                Client: config.Client,
27✔
98
        }
27✔
99
        if client.Client == nil {
51✔
100
                client.Client = new(http.Client)
24✔
101
        }
24✔
102
        if err := config.Validate(); err != nil {
31✔
103
                return nil, err
4✔
104
        }
4✔
105
        client.uri = strings.TrimRight(config.DevauthAddress, "/")
23✔
106
        if !strings.Contains(client.uri, "://") {
25✔
107
                client.uri = "http://" + client.uri
2✔
108
        }
2✔
109

110
        return client, nil
23✔
111
}
112

113
// GET /api/internal/v1/devauth/alive
114
func (c *client) Ping(ctx context.Context) error {
6✔
115
        if ctx == nil {
8✔
116
                ctx = context.Background()
2✔
117
        }
2✔
118
        if _, ok := ctx.Deadline(); !ok {
10✔
119
                var cancel context.CancelFunc
4✔
120
                ctx, cancel = context.WithTimeout(ctx, DefaultTimeout)
4✔
121
                defer cancel()
4✔
122
        }
4✔
123

124
        //nolint:errcheck
125
        req, _ := http.NewRequestWithContext(ctx, http.MethodGet, c.uri+URIInternalAlive, nil)
6✔
126
        rsp, err := c.Do(req)
6✔
127
        if err != nil {
8✔
128
                return errors.Wrap(err, "error checking deviceauth liveliness")
2✔
129
        }
2✔
130
        defer rsp.Body.Close()
4✔
131
        if rsp.StatusCode < 300 {
6✔
132
                return nil
2✔
133
        }
2✔
134
        return errors.Errorf(
2✔
135
                "received bad status code from deviceauth liveliness probe: %s",
2✔
136
                rsp.Status,
2✔
137
        )
2✔
138
}
139

140
// GetDevice provides a functional handle to the API endpoint:
141
// GET /api/internal/v1/devauth/tenants/{tenantID}/devices
142
func (c *client) GetDevices(
143
        ctx context.Context,
144
        deviceIDs []string,
145
) ([]Device, error) {
18✔
146
        var tenantID string
18✔
147
        if ctx == nil {
18✔
148
                ctx = context.Background()
×
149
        }
×
150
        if _, ok := ctx.Deadline(); !ok {
36✔
151
                var cancel context.CancelFunc
18✔
152
                ctx, cancel = context.WithTimeout(ctx, DefaultTimeout)
18✔
153
                defer cancel()
18✔
154
        }
18✔
155
        if id := identity.FromContext(ctx); id != nil {
34✔
156
                tenantID = id.Tenant
16✔
157
        }
16✔
158

159
        // Prepare request
160
        repl := strings.NewReplacer(":tenant", tenantID)
18✔
161
        req, err := http.NewRequestWithContext(
18✔
162
                ctx,
18✔
163
                http.MethodGet,
18✔
164
                c.uri+repl.Replace(URIInternalDevices),
18✔
165
                nil,
18✔
166
        )
18✔
167
        if err != nil {
20✔
168
                return nil, errors.Wrap(err, "devauth: error preparing request")
2✔
169
        }
2✔
170
        q := url.Values{
16✔
171
                "id":       deviceIDs,
16✔
172
                "per_page": []string{strconv.Itoa(len(deviceIDs))},
16✔
173
        }
16✔
174
        req.URL.RawQuery = q.Encode()
16✔
175

16✔
176
        if reqID := requestid.FromContext(ctx); reqID != "" {
16✔
177
                req.Header.Set(requestid.RequestIdHeader, reqID)
×
178
        }
×
179
        // Execute request
180
        rsp, err := c.Do(req)
16✔
181
        if err != nil {
18✔
182
                return nil, errors.Wrap(err, "devauth: error performing request")
2✔
183
        }
2✔
184
        defer rsp.Body.Close()
14✔
185

14✔
186
        switch rsp.StatusCode {
14✔
187
        case http.StatusOK:
10✔
188
                var devices []Device
10✔
189
                decoder := json.NewDecoder(rsp.Body)
10✔
190
                err := decoder.Decode(&devices)
10✔
191
                if err != nil {
12✔
192
                        return nil, errors.Wrap(err,
2✔
193
                                "devauth: error decoding HTTP response body",
2✔
194
                        )
2✔
195
                }
2✔
196
                return devices, nil
8✔
197

198
        default:
4✔
199
                var err error
4✔
200
                apiErr := new(rest.Error)
4✔
201
                decoder := json.NewDecoder(rsp.Body)
4✔
202
                if decoder.Decode(apiErr) == nil {
6✔
203
                        err = common.WrapHTTPError(apiErr, rsp.StatusCode)
2✔
204
                } else {
4✔
205
                        err = common.NewHTTPError(rsp.StatusCode)
2✔
206
                }
2✔
207
                return nil, err
4✔
208
        }
209
}
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