-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathclient_util.go
More file actions
121 lines (103 loc) · 3.53 KB
/
client_util.go
File metadata and controls
121 lines (103 loc) · 3.53 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
package api
import (
"context"
"encoding/base64"
"errors"
"fmt"
"net/http"
"sync"
"time"
"github.com/timescale/tiger-cli/internal/tiger/config"
)
// Shared HTTP client with resource limits to prevent resource exhaustion under load
var (
httpClientOnce sync.Once
sharedHTTPClient *http.Client
)
// getHTTPClient returns a singleton HTTP client with essential resource limits
// Focuses on preventing resource leaks while using reasonable Go defaults elsewhere
func getHTTPClient() *http.Client {
httpClientOnce.Do(func() {
// Clone default transport to inherit sensible defaults, then customize key settings
transport := http.DefaultTransport.(*http.Transport).Clone()
// Essential resource limits to prevent exhaustion
transport.MaxIdleConns = 100 // Limit total idle connections
transport.MaxIdleConnsPerHost = 10 // Limit per-host idle connections
transport.IdleConnTimeout = 90 * time.Second // Clean up idle connections
sharedHTTPClient = &http.Client{
Transport: transport,
Timeout: 30 * time.Second, // Overall request timeout
}
})
return sharedHTTPClient
}
// NewTigerClient creates a new API client with the given API key
func NewTigerClient(apiKey string) (*ClientWithResponses, error) {
cfg, err := config.Load()
if err != nil {
return nil, fmt.Errorf("failed to load config: %w", err)
}
// Use shared HTTP client with resource limits
httpClient := getHTTPClient()
// Create the API client
client, err := NewClientWithResponses(cfg.APIURL, WithHTTPClient(httpClient), WithRequestEditorFn(func(ctx context.Context, req *http.Request) error {
// Add API key to Authorization header
encodedKey := base64.StdEncoding.EncodeToString([]byte(apiKey))
req.Header.Set("Authorization", "Basic "+encodedKey)
return nil
}))
if err != nil {
return nil, fmt.Errorf("failed to create API client: %w", err)
}
return client, nil
}
// ValidateAPIKey validates the API key by making a test API call
func ValidateAPIKey(apiKey string, projectID string) error {
client, err := NewTigerClient(apiKey)
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}
return ValidateAPIKeyWithClient(client, projectID)
}
// ValidateAPIKeyWithClient validates the API key using the provided client interface
func ValidateAPIKeyWithClient(client ClientWithResponsesInterface, projectID string) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Use provided project ID if available, otherwise use a dummy one
targetProjectID := projectID
if targetProjectID == "" {
// Use a dummy project ID for validation when none is provided
targetProjectID = "00000000-0000-0000-0000-000000000000"
}
// Try to call a simple endpoint
// The API should return 401/403 for invalid API key, and 404 for non-existent project
resp, err := client.GetProjectsProjectIdServicesWithResponse(ctx, targetProjectID)
if err != nil {
return fmt.Errorf("API call failed: %w", err)
}
// Check the response status
if resp.StatusCode() != 200 {
if resp.StatusCode() == 404 {
// Project not found, but API key is valid
return nil
}
if resp.JSON4XX != nil {
return resp.JSON4XX
} else {
return errors.New("unexpected API response: 500")
}
} else {
return nil
}
}
// Error implements the error interface for the Error type.
// This allows Error values to be used directly as Go errors.
func (e *Error) Error() string {
if e == nil {
return "unknown error"
}
if e.Message != nil && *e.Message != "" {
return *e.Message
}
return "unknown error"
}