Skip to content

Commit e569546

Browse files
dushimsamclaranceliberiCopilot
authored
feat: extract the cloud API Url from the metadata (#19)
* feat: extract the cloud API Url from the metadata * Update pkg/clients/tsclient/client.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Clarance Liberiste Ntwari <60586899+claranceliberi@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent f11a715 commit e569546

12 files changed

Lines changed: 247 additions & 117 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
build
2+
coverage.*

Dockerfile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ FROM alpine:3.19
2828
RUN apk add --no-cache \
2929
ca-certificates \
3030
tzdata \
31-
procfs \
3231
&& rm -rf /var/cache/apk/*
3332

3433
# Create non-root user (though process metrics may require root)
@@ -54,7 +53,6 @@ RUN chmod +x sc-agent
5453
# Environment variables with defaults
5554
ENV SC_LOG_LEVEL=info
5655
ENV SC_COLLECTION_INTERVAL=30s
57-
ENV SC_INGESTOR_ENDPOINT=http://localhost:8080/ingest
5856
ENV SC_HTTP_TIMEOUT=30s
5957
ENV SC_COLLECTOR_PROCESSES=true
6058

README.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,6 @@ sudo ./build/sc-agent
125125

126126
# Specify a config file
127127
sudo SC_AGENT_CONFIG=/path/to/your/config.yaml ./build/sc-agent
128-
129-
# Override settings with environment variables
130-
sudo SC_INGESTOR_ENDPOINT=http://localhost:8080 SC_LOG_LEVEL=debug ./build/sc-agent
131128
```
132129

133130
### Checking Version

cmd/agent/main.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ func main() {
119119

120120
logger.Info("Starting SC metrics agent",
121121
zap.Duration("collection_interval", cfg.CollectionInterval),
122-
zap.String("ingestor_endpoint", cfg.IngestorEndpoint),
123122
zap.String("metadata_service_endpoint", cfg.MetadataServiceEndpoint),
124123
zap.String("vm_id", cfg.VMID),
125124
zap.Any("collectors", cfg.Collectors),
@@ -141,17 +140,17 @@ func main() {
141140

142141
metricDecorator := decorator.NewMetricDecorator(cfg.VMID, cfg.Labels, logger)
143142
aggregator := aggregate.NewAggregator(logger)
143+
authMgr := metadata.NewAuthManager(cfg, logger)
144144

145145
// Create HTTP client for metric writing
146146
clientConfig := tsclient.ClientConfig{
147-
Endpoint: cfg.IngestorEndpoint,
147+
AuthMgr: authMgr,
148148
Timeout: cfg.HTTPTimeout,
149149
MaxRetries: cfg.MaxRetries,
150150
RetryDelay: cfg.RetryInterval,
151151
}
152-
httpClient := tsclient.NewClientWithConfig(clientConfig, logger)
152+
httpClient := tsclient.NewClient(clientConfig, logger)
153153
metricWriter := tsclient.NewMetricWriter(httpClient, logger)
154-
authMgr := metadata.NewAuthManager(cfg, logger)
155154

156155
// Create processing pipeline
157156
pipelineProcessor := pipeline.NewProcessor(

config.example.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
collection_interval: 30s
66
http_timeout: 30s
77

8-
# Ingestor endpoint configuration
9-
ingestor_endpoint: "https://api.cloud.strettch.dev/resource-manager/api/v1/metrics/ingest"
108

119
# Metadata service endpoint for authentication
1210
metadata_service_endpoint: "http://169.254.169.254/metadata/v1/auth-token"

pkg/clients/metadata/auth.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import (
1010

1111
// AuthManager handles periodic token refresh for metadata service authentication.
1212
type AuthManager struct {
13-
client *Client
14-
vmID string
15-
logger *zap.Logger
16-
refreshTicker *time.Ticker
17-
stopCh chan struct{}
18-
currentToken string
13+
client *Client
14+
vmID string
15+
logger *zap.Logger
16+
refreshTicker *time.Ticker
17+
stopCh chan struct{}
18+
currentToken string
19+
cloudAPIURL string
1920
}
2021

2122
// NewAuthManager creates a new auth manager.
@@ -35,20 +36,28 @@ func (am *AuthManager) IsRefreshRunning() bool {
3536
return am.refreshTicker != nil
3637
}
3738

38-
// fetchAndStoreToken fetches a token and stores it internally.
39+
// fetchAndStoreToken fetches a token and CloudAPI URL and stores them internally.
3940
func (am *AuthManager) fetchAndStoreToken(ctx context.Context, forceFetch bool) error {
4041
if forceFetch {
4142
am.logger.Debug("Forcing token refresh")
4243
am.client.InvalidateCache()
4344
} else {
4445
am.logger.Debug("Fetching token (using cache if valid)")
4546
}
47+
4648
token, err := am.client.GetAuthToken(ctx, am.vmID)
4749
if err != nil {
4850
return err
4951
}
5052
am.currentToken = token
51-
am.logger.Debug("Token stored successfully")
53+
54+
cloudAPIURL, err := am.client.GetCloudAPIURL(ctx, am.vmID)
55+
if err != nil {
56+
return err
57+
}
58+
am.cloudAPIURL = cloudAPIURL
59+
60+
am.logger.Debug("Token and CloudAPI URL stored successfully")
5261
return nil
5362
}
5463

@@ -87,6 +96,11 @@ func (am *AuthManager) GetCurrentToken() string {
8796
return am.currentToken
8897
}
8998

99+
// GetCloudAPIURL returns the cached CloudAPI URL
100+
func (am *AuthManager) GetCloudAPIURL() string {
101+
return am.cloudAPIURL
102+
}
103+
90104
// Close stops the refresh loop.
91105
func (am *AuthManager) Close() {
92106
if am.refreshTicker != nil {

pkg/clients/metadata/client.go

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ const (
2626

2727
// TokenResponse represents the response from the metadata service
2828
type TokenResponse struct {
29-
Token string `json:"token"`
29+
Token string `json:"token"`
30+
CloudAPIUrl string `json:"cloudAPIUrl"`
3031
}
3132

3233
// Client handles communication with the metadata service with token caching
@@ -35,9 +36,10 @@ type Client struct {
3536
httpClient *http.Client
3637
logger *zap.Logger
3738

38-
// Token caching
39+
// Token and CloudAPI URL caching
3940
tokenMu sync.RWMutex
4041
cachedToken string
42+
cachedAPIURL string
4143
tokenExpiry time.Time
4244
tokenLifetime time.Duration
4345
}
@@ -81,30 +83,31 @@ func (c *Client) GetAuthToken(ctx context.Context, vmID string) (string, error)
8183
return c.cachedToken, nil
8284
}
8385

84-
// Fetch new token
85-
token, err := c.fetchAuthToken(ctx, vmID)
86+
// Fetch new token and metadata
87+
tokenResp, err := c.fetchAuthToken(ctx, vmID)
8688
if err != nil {
8789
return "", err
8890
}
8991

90-
// Cache the token
91-
c.cachedToken = token
92+
// Cache the token and CloudAPI URL
93+
c.cachedToken = tokenResp.Token
94+
c.cachedAPIURL = tokenResp.CloudAPIUrl
9295
c.tokenExpiry = time.Now().Add(c.tokenLifetime)
9396

9497
c.logger.Info("Successfully fetched and cached new auth token",
9598
zap.Time("expires_at", c.tokenExpiry),
9699
zap.Duration("lifetime", c.tokenLifetime))
97100

98-
return token, nil
101+
return tokenResp.Token, nil
99102
}
100103

101-
// fetchAuthToken fetches a new authentication token from the metadata service
102-
func (c *Client) fetchAuthToken(ctx context.Context, vmID string) (string, error) {
104+
// fetchAuthToken fetches a new authentication token and metadata
105+
func (c *Client) fetchAuthToken(ctx context.Context, vmID string) (*TokenResponse, error) {
103106
c.logger.Debug("Fetching new auth token from metadata service", zap.String("endpoint", c.endpoint))
104107

105108
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.endpoint, nil)
106109
if err != nil {
107-
return "", fmt.Errorf("failed to create request: %w", err)
110+
return nil, fmt.Errorf("failed to create request: %w", err)
108111
}
109112

110113
req.Header.Set(HeaderAccept, AcceptJSON)
@@ -113,7 +116,7 @@ func (c *Client) fetchAuthToken(ctx context.Context, vmID string) (string, error
113116
resp, err := c.httpClient.Do(req)
114117
if err != nil {
115118
c.logger.Error("Failed to fetch auth token", zap.Error(err))
116-
return "", fmt.Errorf("failed to fetch auth token: %w", err)
119+
return nil, fmt.Errorf("failed to fetch auth token: %w", err)
117120
}
118121
defer func() {
119122
if closeErr := resp.Body.Close(); closeErr != nil {
@@ -126,25 +129,25 @@ func (c *Client) fetchAuthToken(ctx context.Context, vmID string) (string, error
126129
c.logger.Error("Metadata service returned error",
127130
zap.Int("status_code", resp.StatusCode),
128131
zap.String("response", string(body)))
129-
return "", fmt.Errorf("metadata service returned status %d: %s", resp.StatusCode, string(body))
132+
return nil, fmt.Errorf("metadata service returned status %d: %s", resp.StatusCode, string(body))
130133
}
131134

132135
body, err := io.ReadAll(resp.Body)
133136
if err != nil {
134-
return "", fmt.Errorf("failed to read response body: %w", err)
137+
return nil, fmt.Errorf("failed to read response body: %w", err)
135138
}
136139

137140
var tokenResp TokenResponse
138141
if err := json.Unmarshal(body, &tokenResp); err != nil {
139-
return "", fmt.Errorf("failed to unmarshal token response: %w", err)
142+
return nil, fmt.Errorf("failed to unmarshal token response: %w", err)
140143
}
141144

142145
if tokenResp.Token == "" {
143-
return "", fmt.Errorf("received empty token from metadata service")
146+
return nil, fmt.Errorf("received empty token from metadata service")
144147
}
145148

146-
c.logger.Debug("Successfully fetched auth token")
147-
return tokenResp.Token, nil
149+
c.logger.Debug("Successfully fetched auth token and metadata")
150+
return &tokenResp, nil
148151
}
149152

150153
// GetAuthTokenWithRetry fetches an auth token with retry logic similar to tsclient
@@ -191,10 +194,39 @@ func (c *Client) InvalidateCache() {
191194
defer c.tokenMu.Unlock()
192195

193196
c.cachedToken = ""
197+
c.cachedAPIURL = ""
194198
c.tokenExpiry = time.Time{}
195199
c.logger.Debug("Token cache invalidated")
196200
}
197201

202+
// GetCloudAPIURL returns the cached CloudAPI URL
203+
func (c *Client) GetCloudAPIURL(ctx context.Context, vmID string) (string, error) {
204+
c.tokenMu.RLock()
205+
if c.cachedAPIURL != "" && time.Now().Before(c.tokenExpiry) {
206+
url := c.cachedAPIURL
207+
c.tokenMu.RUnlock()
208+
c.logger.Debug("Using cached CloudAPI URL", zap.String("url", url))
209+
return url, nil
210+
}
211+
c.tokenMu.RUnlock()
212+
213+
// Need to fetch new metadata to get the URL
214+
_, err := c.GetAuthToken(ctx, vmID)
215+
if err != nil {
216+
return "", fmt.Errorf("failed to get CloudAPI URL: %w", err)
217+
}
218+
219+
c.tokenMu.RLock()
220+
url := c.cachedAPIURL
221+
c.tokenMu.RUnlock()
222+
223+
if url == "" {
224+
return "", fmt.Errorf("CloudAPI URL not available in metadata response")
225+
}
226+
227+
return url, nil
228+
}
229+
198230
// Close closes the HTTP client
199231
func (c *Client) Close() error {
200232
c.httpClient.CloseIdleConnections()

0 commit comments

Comments
 (0)