v5.0.58: retry on 401 from Gateway, bump test profile version

This commit is contained in:
“Naeel”
2026-07-05 07:51:23 +04:00
parent 595c9250a1
commit e9901c6309
5 changed files with 99 additions and 58 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ terraform {
required_providers { required_providers {
nubes = { nubes = {
source = "terra.k8c.ru/nubes-test/nubes" source = "terra.k8c.ru/nubes-test/nubes"
version = "5.0.57" version = "5.0.58"
} }
} }
} }
+1 -1
View File
@@ -2,7 +2,7 @@ terraform {
required_providers { required_providers {
nubes = { nubes = {
source = "terra.k8c.ru/nubes-test/nubes" source = "terra.k8c.ru/nubes-test/nubes"
version = "5.0.57" version = "5.0.58"
} }
} }
} }
+1 -1
View File
@@ -2,7 +2,7 @@ terraform {
required_providers { required_providers {
nubes = { nubes = {
source = "terra.k8c.ru/nubes-test/nubes" source = "terra.k8c.ru/nubes-test/nubes"
version = "5.0.57" version = "5.0.58"
} }
} }
} }
+3 -3
View File
@@ -3,9 +3,9 @@ NUBES_API_ENDPOINT="https://lk-api-gateway-test.ngcloud.ru/api/v1/svc"
TOKEN_FILE="secrets/test.token" TOKEN_FILE="secrets/test.token"
# Release versions # Release versions
RELEASE_VERSION="5.0.57" RELEASE_VERSION="5.0.58"
PROVIDER_VERSION="5.0.57" PROVIDER_VERSION="5.0.58"
DOCS_VERSION="5.0.57" DOCS_VERSION="5.0.58"
# Docs generation — ONLY from docs_gen/<stand>/ (never from docs/) # Docs generation — ONLY from docs_gen/<stand>/ (never from docs/)
DOCS_GEN_DIR="universal_rebuild/docs_gen/test" DOCS_GEN_DIR="universal_rebuild/docs_gen/test"
+93 -52
View File
@@ -612,38 +612,58 @@ func (c *UniversalClient) FindInstanceByDisplayName(ctx context.Context, service
} }
func (c *UniversalClient) GetInstanceState(ctx context.Context, instanceUid string) (*InstanceStateResponse, error) { func (c *UniversalClient) GetInstanceState(ctx context.Context, instanceUid string) (*InstanceStateResponse, error) {
url := c.buildURL(fmt.Sprintf("/instances/%s", instanceUid)) const maxRetries = 3
req, err := http.NewRequestWithContext(ctx, "GET", url, nil) baseDelay := 2 * time.Second
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", userAgent)
if c.ApiToken != "" {
req.Header.Set("Authorization", "Bearer "+c.ApiToken)
}
resp, err := c.HttpClient.Do(req) var lastErr error
if err != nil { for attempt := 0; attempt <= maxRetries; attempt++ {
return nil, err if attempt > 0 {
} select {
defer resp.Body.Close() case <-time.After(baseDelay * time.Duration(1<<(attempt-1))):
case <-ctx.Done():
return nil, ctx.Err()
}
}
if resp.StatusCode != 200 { url := c.buildURL(fmt.Sprintf("/instances/%s", instanceUid))
return nil, fmt.Errorf("HTTP статус %d", resp.StatusCode) req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
} if err != nil {
return nil, err
}
req.Header.Set("User-Agent", userAgent)
if c.ApiToken != "" {
req.Header.Set("Authorization", "Bearer "+c.ApiToken)
}
var res struct { resp, err := c.HttpClient.Do(req)
Instance InstanceStateResponse `json:"instance"` if err != nil {
} lastErr = err
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil { continue
return nil, err }
} defer resp.Body.Close()
if err := validateInstanceStatus(&res.Instance); err != nil { if resp.StatusCode == 401 {
return nil, err lastErr = fmt.Errorf("HTTP статус %d", resp.StatusCode)
} continue
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("HTTP статус %d", resp.StatusCode)
}
return &res.Instance, nil var res struct {
Instance InstanceStateResponse `json:"instance"`
}
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return nil, err
}
if err := validateInstanceStatus(&res.Instance); err != nil {
return nil, err
}
return &res.Instance, nil
}
return nil, fmt.Errorf("GetInstanceState failed after %d retries: %w", maxRetries, lastErr)
} }
func isInstanceDeleted(state *InstanceStateResponse) bool { func isInstanceDeleted(state *InstanceStateResponse) bool {
@@ -659,34 +679,54 @@ func isInstanceDeleted(state *InstanceStateResponse) bool {
// GetInstanceStateRaw получает состояние инстанса БЕЗ валидации статуса. // GetInstanceStateRaw получает состояние инстанса БЕЗ валидации статуса.
// Используется для проверки ref-параметров: нужно читать даже deleted/suspended инстансы. // Используется для проверки ref-параметров: нужно читать даже deleted/suspended инстансы.
func (c *UniversalClient) GetInstanceStateRaw(ctx context.Context, instanceUid string) (*InstanceStateResponse, error) { func (c *UniversalClient) GetInstanceStateRaw(ctx context.Context, instanceUid string) (*InstanceStateResponse, error) {
reqURL := c.buildURL(fmt.Sprintf("/instances/%s", instanceUid)) const maxRetries = 3
req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil) baseDelay := 2 * time.Second
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", userAgent)
if c.ApiToken != "" {
req.Header.Set("Authorization", "Bearer "+c.ApiToken)
}
resp, err := c.HttpClient.Do(req) var lastErr error
if err != nil { for attempt := 0; attempt <= maxRetries; attempt++ {
return nil, err if attempt > 0 {
} select {
defer resp.Body.Close() case <-time.After(baseDelay * time.Duration(1<<(attempt-1))):
case <-ctx.Done():
return nil, ctx.Err()
}
}
if resp.StatusCode != 200 { reqURL := c.buildURL(fmt.Sprintf("/instances/%s", instanceUid))
return nil, fmt.Errorf("HTTP статус %d", resp.StatusCode) req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil)
} if err != nil {
return nil, err
}
req.Header.Set("User-Agent", userAgent)
if c.ApiToken != "" {
req.Header.Set("Authorization", "Bearer "+c.ApiToken)
}
var res struct { resp, err := c.HttpClient.Do(req)
Instance InstanceStateResponse `json:"instance"` if err != nil {
} lastErr = err
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil { continue
return nil, err }
} defer resp.Body.Close()
return &res.Instance, nil if resp.StatusCode == 401 {
lastErr = fmt.Errorf("HTTP статус %d", resp.StatusCode)
continue
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("HTTP статус %d", resp.StatusCode)
}
var res struct {
Instance InstanceStateResponse `json:"instance"`
}
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
return nil, err
}
return &res.Instance, nil
}
return nil, fmt.Errorf("GetInstanceStateRaw failed after %d retries: %w", maxRetries, lastErr)
} }
// ===== UNIVERSAL OPERATION WAIT (APPEND-ONLY) ===== // ===== UNIVERSAL OPERATION WAIT (APPEND-ONLY) =====
@@ -932,7 +972,8 @@ func isRetryable(statusCode int) bool {
return statusCode == http.StatusTooManyRequests || // 429 return statusCode == http.StatusTooManyRequests || // 429
statusCode == http.StatusServiceUnavailable || // 503 statusCode == http.StatusServiceUnavailable || // 503
statusCode == http.StatusBadGateway || // 502 statusCode == http.StatusBadGateway || // 502
statusCode == http.StatusGatewayTimeout // 504 statusCode == http.StatusGatewayTimeout || // 504
statusCode == http.StatusUnauthorized // 401 — Gateway иногда отбрасывает валидный JWT
} }
func (c *UniversalClient) postIgnoreResponse(ctx context.Context, path string, payload interface{}, returnLocation bool) (string, error) { func (c *UniversalClient) postIgnoreResponse(ctx context.Context, path string, payload interface{}, returnLocation bool) (string, error) {