|
|
|
@@ -612,38 +612,58 @@ func (c *UniversalClient) FindInstanceByDisplayName(ctx context.Context, service
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *UniversalClient) GetInstanceState(ctx context.Context, instanceUid string) (*InstanceStateResponse, error) {
|
|
|
|
|
url := c.buildURL(fmt.Sprintf("/instances/%s", instanceUid))
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
const maxRetries = 3
|
|
|
|
|
baseDelay := 2 * time.Second
|
|
|
|
|
|
|
|
|
|
resp, err := c.HttpClient.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
var lastErr error
|
|
|
|
|
for attempt := 0; attempt <= maxRetries; attempt++ {
|
|
|
|
|
if attempt > 0 {
|
|
|
|
|
select {
|
|
|
|
|
case <-time.After(baseDelay * time.Duration(1<<(attempt-1))):
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return nil, ctx.Err()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if resp.StatusCode != 200 {
|
|
|
|
|
return nil, fmt.Errorf("HTTP статус %d", resp.StatusCode)
|
|
|
|
|
}
|
|
|
|
|
url := c.buildURL(fmt.Sprintf("/instances/%s", instanceUid))
|
|
|
|
|
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 {
|
|
|
|
|
Instance InstanceStateResponse `json:"instance"`
|
|
|
|
|
}
|
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
resp, err := c.HttpClient.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
lastErr = err
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
if err := validateInstanceStatus(&res.Instance); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if resp.StatusCode == 401 {
|
|
|
|
|
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 {
|
|
|
|
@@ -659,34 +679,54 @@ func isInstanceDeleted(state *InstanceStateResponse) bool {
|
|
|
|
|
// GetInstanceStateRaw получает состояние инстанса БЕЗ валидации статуса.
|
|
|
|
|
// Используется для проверки ref-параметров: нужно читать даже deleted/suspended инстансы.
|
|
|
|
|
func (c *UniversalClient) GetInstanceStateRaw(ctx context.Context, instanceUid string) (*InstanceStateResponse, error) {
|
|
|
|
|
reqURL := c.buildURL(fmt.Sprintf("/instances/%s", instanceUid))
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
const maxRetries = 3
|
|
|
|
|
baseDelay := 2 * time.Second
|
|
|
|
|
|
|
|
|
|
resp, err := c.HttpClient.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
var lastErr error
|
|
|
|
|
for attempt := 0; attempt <= maxRetries; attempt++ {
|
|
|
|
|
if attempt > 0 {
|
|
|
|
|
select {
|
|
|
|
|
case <-time.After(baseDelay * time.Duration(1<<(attempt-1))):
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return nil, ctx.Err()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if resp.StatusCode != 200 {
|
|
|
|
|
return nil, fmt.Errorf("HTTP статус %d", resp.StatusCode)
|
|
|
|
|
}
|
|
|
|
|
reqURL := c.buildURL(fmt.Sprintf("/instances/%s", instanceUid))
|
|
|
|
|
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 {
|
|
|
|
|
Instance InstanceStateResponse `json:"instance"`
|
|
|
|
|
}
|
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
resp, err := c.HttpClient.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
lastErr = err
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
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) =====
|
|
|
|
@@ -932,7 +972,8 @@ func isRetryable(statusCode int) bool {
|
|
|
|
|
return statusCode == http.StatusTooManyRequests || // 429
|
|
|
|
|
statusCode == http.StatusServiceUnavailable || // 503
|
|
|
|
|
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) {
|
|
|
|
|