-
Notifications
You must be signed in to change notification settings - Fork 4
/
client.go
300 lines (257 loc) · 9.21 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
package sophos
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
)
// DefaultHTTPClient is the default http.Client used. Caller can modify Client (e.g. to allow SkipInsecure)
var DefaultHTTPClient HTTPClient
// HttpClient is an interface which represents an http.Client
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
func init() { DefaultHTTPClient = &http.Client{} }
// ClientInterface represents a Sophos 9 REST API client
type ClientInterface interface {
Get(path string, options ...Option) (*Response, error)
Put(path string, body io.Reader, options ...Option) (*Response, error)
Post(path string, body io.Reader, options ...Option) (*Response, error)
Patch(path string, body io.Reader, options ...Option) (*Response, error)
Delete(path string, options ...Option) (*Response, error)
}
// ObjectClient is an interface with syntactic sugar dealing with Objects
type ObjectClient interface {
GetObject(o RestGetter, options ...Option) error
PutObject(o RestObject, options ...Option) error
PatchObject(o RestObject, options ...Option) error
PostObject(o RestObject, options ...Option) error
DeleteObject(o RestObject, options ...Option) error
GetUsedBy(o RestObject, options ...Option) (*UsedBy, error)
GetEndpointSwag(e Endpoint, options ...Option) (Swag, error)
}
// Client implements ClientInterface to provide a REST client
type Client struct {
endpoint string
apiKey string
opts []Option
}
var ensureInterface Client
var _ ClientInterface = ensureInterface
var _ ObjectClient = ensureInterface
// ErrRefRequired is an error that is returned when the resource requires a Referencee to fetch data
var ErrRefRequired = errors.New("client: Reference is required")
// New returns a new Client.
// The endpoint provided should point to the Sophos Gateway.
func New(endpoint string, opts ...Option) (*Client, error) {
if endpoint == "" {
return nil, errors.New("endpoint is required")
}
if !strings.HasPrefix(endpoint, "http:") && !strings.HasPrefix(endpoint, "https:") {
endpoint = "https://" + endpoint
}
return &Client{
endpoint: endpoint,
opts: opts,
}, nil
}
// Do executes the call and returns a *Response
func (c Client) Do(method, path string, body io.Reader, options ...Option) (resp *Response, err error) {
resp = &Response{Response: &http.Response{}}
resp.Request, err = c.Request(method, path, body, options...)
if err != nil {
return
}
res, err := DefaultHTTPClient.Do(resp.Request)
if err != nil {
return
}
resp.Response = res
if res.StatusCode >= 400 && res.StatusCode <= 422 {
err = fmt.Errorf("client do: error from server: %s", res.Status)
// check for Errors
var ee Errors
_ = resp.MarshalTo(&ee)
if len(ee) > 0 {
resp.Errors = &ee
err = resp.Errors
}
return
}
if !(res.StatusCode >= 200 && res.StatusCode <= 204) {
err = fmt.Errorf("client do: error from server: %s", res.Status)
}
return
}
// Delete executes a DELETE call
//
// You can use DELETE requests to destroy object resources that were created with
// POST or PUT requests.
// Confd may deny DELETE requests due to validation checks. To use confd auto
// correction, use the special headers described X-Restd-Err-Ack header.
//
// DELETE /api/objects/packetfilter/packetfilter/REF_PacPacAllowAnyFTPOut
func (c Client) Delete(path string, options ...Option) (*Response, error) {
return c.Do(http.MethodDelete, path, nil, options...)
}
// Get requests are used to retrieve information. The GET request cannot modify the data from confd.
//
// Examples for GET calls:
// GET /api/nodes
// GET /api/nodes/webadmin.port
// GET /api/objects/network/network
// GET /api/objects/network/network/REF_NetNet100008
func (c Client) Get(path string, options ...Option) (*Response, error) {
return c.Do(http.MethodGet, path, nil, options...)
}
// Put executes a PUT call
//
// You can use PUT and POST for creating new resources. POST will create a new
// resource with an auto generated REF_ string whereas PUT will create resource for the
// REF_ string. You can use PUT to update the same resource after creation. PUT and
// POST require that you set all mandatory attributes of an object or node. You may need
// to override changes by removing locks (see XRestdLockOverride header).
//
// PUT /api/nodes/packetfilter.rules
// POST /api/objects/packetfilter/packetfilter
// PUT /api/objects/packetfilter/packetfilter/REF_PacPacAllowAnyFTPOut
func (c Client) Put(path string, body io.Reader, options ...Option) (*Response, error) {
r, err := c.Do(http.MethodPut, path, body, options...)
if err != nil {
return r, err
}
return r, nil
}
// Post executes a POST call
//
// You can use PUT and POST for creating new resources. POST will create a new
// resource with an auto generated REF_ string whereas PUT will create resource for the
// REF_ string. You can use PUT to update the same resource after creation. PUT and
// POST require that you set all mandatory attributes of an object or node. You may need
// to override changes by removing locks (see chapter X-Restd-Lock-Override header).
//
// PUT /api/nodes/packetfilter.rules
// POST /api/objects/packetfilter/packetfilter
// PUT /api/objects/packetfilter/packetfilter/REF_PacPacAllowAnyFTPOut
func (c Client) Post(path string, body io.Reader, options ...Option) (*Response, error) {
return c.Do(http.MethodPost, path, body, options...)
}
// Patch executes a PATCH call
//
// You can use PATCH requests to update fields on an existing object:
// PATCH /api/objects/packetfilter/packetfilter/REF_PacPacAllowAnyFTPOut
func (c Client) Patch(path string, body io.Reader, options ...Option) (*Response, error) {
return c.Do(http.MethodPatch, path, body, options...)
}
// Version contains the UTMs version data
type Version struct {
UTM string `json:"utm"`
Restd string `json:"restd"`
}
// Ping the gateway to retrieve its versioning
func (c Client) Ping(options ...Option) (v *Version, err error) {
r, err := c.Get("/api/status/version", options...)
if err != nil {
return nil, fmt.Errorf("ping: error retrieving version from gateway: %s", err.Error())
}
v = &Version{}
err = r.MarshalTo(v)
return
}
// Endpoint returns the client's UTM endpoint
func (c Client) Endpoint() string {
return c.endpoint
}
// Request generates a new *http.Request that is modified with the Client's Options (set on New)
// and with the provided Options.
func Request(method, path string, body io.Reader, options ...Option) (*http.Request, error) {
req, err := http.NewRequest(method, path, body)
if err != nil {
return nil, fmt.Errorf("request: error generating new http.Request: %s", err.Error())
}
req.Header.Set("Content-Type", "application/json")
if err := evaluateOpts(req, options); err != nil {
return req, fmt.Errorf("request: error evaluation Options: %s", err.Error())
}
return req, nil
}
// Request generates a new *http.Request that is modified with the Client's Options (set on New)
// and with the provided Options.
func (c *Client) Request(method, path string, body io.Reader, options ...Option) (*http.Request, error) {
return Request(method, c.endpoint+path, body, append(c.opts, options...)...)
}
// GetObject implements TypeClient
func (c Client) GetObject(o RestGetter, options ...Option) error {
if ref, required := o.RefRequired(); required && ref == "" {
return ErrRefRequired
}
res, err := c.Get(o.GetPath(), options...)
if err != nil {
return err
}
err = res.MarshalTo(o)
return err
}
// PostObject POSTs the RestObject
func (c Client) PostObject(o RestObject, options ...Option) error {
byt, _ := json.Marshal(o)
res, err := c.Post(o.PostPath(), bytes.NewReader(byt), options...)
if res.StatusCode == http.StatusCreated {
// Operation successful and created a new resource. The newly created
// resource and its path and REF_ string are returned in the
// Location header
err = res.MarshalTo(o)
}
return err
}
// PatchObject PATCHes the RestObject
func (c Client) PatchObject(o RestObject, options ...Option) error {
ref, required := o.RefRequired()
if required && ref == "" {
return ErrRefRequired
}
byt, _ := json.Marshal(o)
_, err := c.Patch(o.PatchPath(ref), bytes.NewReader(byt), options...)
return err
}
// PutObject PUTs the RestObject
func (c Client) PutObject(o RestObject, options ...Option) error {
ref, required := o.RefRequired()
if required && ref == "" {
return ErrRefRequired
}
byt, _ := json.Marshal(o)
_, err := c.Put(o.PutPath(ref), bytes.NewReader(byt), options...)
return err
}
// DeleteObject DELETEs the RestObject
func (c Client) DeleteObject(o RestObject, options ...Option) error {
ref, required := o.RefRequired()
if required && ref == "" {
return ErrRefRequired
}
_, err := c.Delete(o.DeletePath(ref), options...)
return err
}
// GetEndpointSwag is syntactic sugar around an Endpoint Definition's GetSwag method.
func (c Client) GetEndpointSwag(e Endpoint, options ...Option) (Swag, error) {
def := e.Definition()
return def.GetSwag(&c)
}
// GetUsedBy GETs the Objects UsedBy data
func (c Client) GetUsedBy(o RestObject, options ...Option) (*UsedBy, error) {
ref, required := o.RefRequired()
if required && ref == "" {
return nil, ErrRefRequired
}
var usedBy UsedBy
resp, err := c.Get(o.UsedByPath(ref), options...)
if err != nil {
return nil, err
}
err = resp.MarshalTo(&usedBy)
return &usedBy, err
}