-
Notifications
You must be signed in to change notification settings - Fork 90
Expand file tree
/
Copy pathmisc.go
More file actions
343 lines (315 loc) · 9.89 KB
/
Copy pathmisc.go
File metadata and controls
343 lines (315 loc) · 9.89 KB
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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
// Copyright 2019 HAProxy Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package misc
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"os"
"strconv"
"strings"
"github.com/haproxytech/client-native/v6/configuration"
client_errors "github.com/haproxytech/client-native/v6/errors"
"github.com/haproxytech/client-native/v6/models"
jsoniter "github.com/json-iterator/go"
"github.com/haproxytech/dataplaneapi/rate"
"github.com/haproxytech/dataplaneapi/reload_agent"
)
const (
// ErrHTTPNotFound HTTP status code 404
ErrHTTPNotFound = int64(404)
// ErrHTTPConflict HTTP status code 409
ErrHTTPConflict = int64(409)
// ErrHTTPInternalServerError HTTP status code 500
ErrHTTPInternalServerError = int64(500)
// ErrHTTPBadRequest HTTP status code 400
ErrHTTPBadRequest = int64(400)
// ErrHTTPRateLimit HTTP status code 429
ErrHTTPRateLimit = int64(429)
// ErrHTTPOk HTTP status code 200
ErrHTTPOk = int64(200)
)
func OutdatedTransactionError(id string) *models.Error {
var code int64 = 406
msg := fmt.Sprintf("transaction %s is outdated and cannot be committed", id)
return &models.Error{
Code: &code,
Message: &msg,
}
}
func FailedTransactionError(id string) *models.Error {
var code int64 = 406
msg := fmt.Sprintf("transaction %s is failed and cannot be committed", id)
return &models.Error{
Code: &code,
Message: &msg,
}
}
// HandleError translates error codes from client native into models.Error with appropriate http status code
func HandleError(err error) *models.Error {
switch t := err.(type) {
case *configuration.ConfError:
msg := t.Error()
httpCode := ErrHTTPInternalServerError
switch t.Err() {
case configuration.ErrObjectDoesNotExist:
httpCode = ErrHTTPNotFound
case configuration.ErrObjectAlreadyExists, configuration.ErrVersionMismatch, configuration.ErrTransactionAlreadyExists:
httpCode = ErrHTTPConflict
case configuration.ErrObjectIndexOutOfRange, configuration.ErrValidationError, configuration.ErrBothVersionTransaction,
configuration.ErrNoVersionTransaction, configuration.ErrNoParentSpecified, configuration.ErrParentDoesNotExist,
configuration.ErrTransactionDoesNotExist, configuration.ErrGeneralError:
httpCode = ErrHTTPBadRequest
}
return &models.Error{Code: &httpCode, Message: &msg}
case *reload_agent.ReloadError:
httpCode := ErrHTTPBadRequest
msg := t.Error()
return &models.Error{Code: &httpCode, Message: &msg}
case *rate.TransactionLimitReachedError:
httpCode := ErrHTTPRateLimit
msg := t.Error()
return &models.Error{Code: &httpCode, Message: &msg}
default:
msg := t.Error()
code := ErrHTTPInternalServerError
if errors.Is(t, client_errors.ErrNotFound) {
code = ErrHTTPNotFound
} else if errors.Is(t, client_errors.ErrAlreadyExists) {
code = ErrHTTPConflict
}
return &models.Error{Code: &code, Message: &msg}
}
}
// HandleContainerGetError translates error codes from client native into models.Error with appropriate http status code. Intended for get requests on container endpoints.
func HandleContainerGetError(err error) *models.Error {
if t, ok := err.(*configuration.ConfError); ok {
if t.Is(configuration.ErrParentDoesNotExist) {
code := ErrHTTPOk
return &models.Error{Code: &code}
}
}
return HandleError(err)
}
// DiscoverChildPaths return children models.Endpoints given path
func DiscoverChildPaths(path string, spec json.RawMessage) (models.Endpoints, error) {
var m map[string]any
json := jsoniter.ConfigCompatibleWithStandardLibrary
err := json.Unmarshal(spec, &m)
if err != nil {
return nil, err
}
es := make(models.Endpoints, 0, 1)
paths := m["paths"].(map[string]any)
for key, value := range paths {
v := value.(map[string]any)
if g, ok := v["get"].(map[string]any); ok {
title := ""
if titleInterface, ok := g["summary"]; ok && titleInterface != nil {
title = titleInterface.(string)
}
description := ""
if descInterface, ok := g["description"]; ok && descInterface != nil {
description = descInterface.(string)
}
if strings.HasPrefix(key, path) && key != path {
resource := key[len(path):]
if strings.HasPrefix(resource, "/") && len(strings.Split(resource[1:], "/")) == 1 {
e := models.Endpoint{
URL: key,
Title: title,
Description: description,
}
es = append(es, &e)
}
}
}
}
return es, nil
}
// IsUnixSocketAddr reports whether addr designates a UNIX socket, either as a
// bare filesystem path or prefixed with the "unix@" address family used by
// HAProxy. Every other address family ("ipv4@", "sockpair@", "fd@", ...) and
// host:port addresses are rejected, as is the empty string.
func IsUnixSocketAddr(addr string) bool {
if addr == "" {
return false
}
if family, _, found := strings.Cut(addr, "@"); found {
return family == "unix"
}
// A bare address containing a colon is a host:port, not a socket path.
return !strings.Contains(addr, ":")
}
// MasterSocketFromEnv extracts the master CLI socket path from the raw value of
// the HAPROXY_MASTER_CLI environment variable. HAProxy advertises its master
// CLI sockets as a ";"-separated list, for example
// "unix@/var/run/master.sock;sockpair@7", and the Data Plane API can only talk
// to the UNIX ones.
//
// The first socket already bound on the filesystem wins. When none of the
// candidates exists yet the first valid one is returned anyway, so that a
// delayed runtime start can pick it up once HAProxy binds it. The second return
// value is false when the value holds no usable UNIX socket at all, in which
// case the caller must keep whatever master runtime it was configured with.
func MasterSocketFromEnv(value string) (string, bool) {
var candidates []string
for addr := range strings.SplitSeq(value, ";") {
addr = strings.TrimSpace(addr)
if !IsUnixSocketAddr(addr) {
continue
}
socket := strings.TrimPrefix(addr, "unix@")
if socket == "" {
continue
}
if info, err := os.Stat(socket); err == nil && info.Mode()&os.ModeSocket != 0 {
return socket, true
}
candidates = append(candidates, socket)
}
if len(candidates) == 0 {
return "", false
}
return candidates[0], true
}
func ParseTimeout(tOut string) *int64 {
var v int64
switch {
case strings.HasSuffix(tOut, "ms"):
v, _ = strconv.ParseInt(strings.TrimSuffix(tOut, "ms"), 10, 64)
case strings.HasSuffix(tOut, "s"):
v, _ = strconv.ParseInt(strings.TrimSuffix(tOut, "s"), 10, 64)
v *= 1000
case strings.HasSuffix(tOut, "m"):
v, _ = strconv.ParseInt(strings.TrimSuffix(tOut, "m"), 10, 64)
v = v * 1000 * 60
case strings.HasSuffix(tOut, "h"):
v, _ = strconv.ParseInt(strings.TrimSuffix(tOut, "h"), 10, 64)
v = v * 1000 * 60 * 60
case strings.HasSuffix(tOut, "d"):
v, _ = strconv.ParseInt(strings.TrimSuffix(tOut, "d"), 10, 64)
v = v * 1000 * 60 * 60 * 24
default:
v, _ = strconv.ParseInt(tOut, 10, 64)
}
if v != 0 {
return &v
}
return nil
}
func GetHTTPStatusFromConfErr(err *configuration.ConfError) int {
switch err.Err() {
case configuration.ErrObjectDoesNotExist:
return http.StatusNotFound
case configuration.ErrObjectAlreadyExists:
return http.StatusConflict
case configuration.ErrNoParentSpecified:
return http.StatusBadRequest
default:
return http.StatusInternalServerError
}
}
func GetHTTPStatusFromErr(err error) int {
confError := &configuration.ConfError{}
switch {
case errors.As(err, &confError):
return GetHTTPStatusFromConfErr(confError)
case errors.Is(err, client_errors.ErrAlreadyExists):
return http.StatusConflict
case errors.Is(err, client_errors.ErrNotFound):
return http.StatusNotFound
case errors.Is(err, client_errors.ErrGeneral):
return http.StatusBadRequest
default:
return http.StatusInternalServerError
}
}
// extractEnvVar extracts and returns env variable from HAProxy variable
// provided in "${SOME_VAR}" format
func ExtractEnvVar(pass string) string {
return strings.TrimLeft(strings.TrimRight(pass, "\"}"), "\"${")
}
func HasOSArg(short, long, env string) bool {
if short == "" && long == "" && env == "" {
return false
}
target1 := "--" + long
hasShort := short != ""
target2 := "-" + short
if env != "" {
if os.Getenv(env) != "" {
return true
}
}
for _, arg := range os.Args {
if hasShort && arg == target2 {
return true
}
if arg == target1 {
return true
}
if strings.HasPrefix(arg, target1) {
p := strings.Split(arg, "=")
if len(p) > 1 {
return true
}
}
}
return false
}
func RandomString(size int) (string, error) {
str, err := randomString(size)
if err != nil {
return "", err
}
for len(str) < size {
str2, _ := randomString(size)
str += str2
}
return str[:size], nil
}
// randomString generates a random string of the recommended size.
// Result is not guaranteed to be correct length.
func randomString(recommendedSize int) (string, error) {
b := make([]byte, recommendedSize+8)
_, err := rand.Read(b)
result := strings.ReplaceAll(base64.URLEncoding.EncodeToString(b), `=`, ``)
result = strings.ReplaceAll(result, `-`, ``)
result = strings.ReplaceAll(result, `_`, ``)
return result, err
}
func IsNetworkErr(err error) bool {
if err == nil {
return false
}
if _, ok := err.(net.Error); ok {
return true
}
return false
}
// ConvertStruct tries to convert a struct from one type to another.
func ConvertStruct[T1 any, T2 any](from T1, to T2) error {
json := jsoniter.ConfigCompatibleWithStandardLibrary
js, err := json.Marshal(from)
if err != nil {
return err
}
return json.Unmarshal(js, to)
}