fix: P0 — add User-Agent + retry to bash urllib, retry to service_spec_gen, empty-list guard
This commit is contained in:
@@ -54,3 +54,6 @@ universal_rebuild/docs_template_gen_v2
|
|||||||
secrets/test.token.new
|
secrets/test.token.new
|
||||||
secrets/*.token.new
|
secrets/*.token.new
|
||||||
terraform-provider-nubes_*
|
terraform-provider-nubes_*
|
||||||
|
universal_rebuild/service_spec_gen
|
||||||
|
universal_rebuild/service_ops_gen
|
||||||
|
universal_rebuild/service_params_gen
|
||||||
|
|||||||
+32
-11
@@ -139,6 +139,14 @@ fi
|
|||||||
|
|
||||||
rm -f "$FAILURES_FILE"
|
rm -f "$FAILURES_FILE"
|
||||||
|
|
||||||
|
# Проверка что список сервисов не пустой
|
||||||
|
svc_count=$(grep -cv '^\s*$\|^\s*#' "$SERVICES_FILE" || true)
|
||||||
|
if [[ "$svc_count" -eq 0 ]]; then
|
||||||
|
echo "Error: services list is empty or all commented out: $SERVICES_FILE" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo "Generating YAML for ${svc_count} services from ${SERVICES_FILE}"
|
||||||
|
|
||||||
# Читаем список сервисов, пропуская пустые строки и комментарии.
|
# Читаем список сервисов, пропуская пустые строки и комментарии.
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
line="${line//$'\r'/}"
|
line="${line//$'\r'/}"
|
||||||
@@ -164,28 +172,41 @@ while IFS= read -r line; do
|
|||||||
# Если имя не указано в списке — подтягиваем по API.
|
# Если имя не указано в списке — подтягиваем по API.
|
||||||
if [[ -z "$svc_name" ]]; then
|
if [[ -z "$svc_name" ]]; then
|
||||||
svc_name=$(python3 - <<PY
|
svc_name=$(python3 - <<PY
|
||||||
import json
|
import json, sys, time, urllib.parse, urllib.request
|
||||||
import urllib.parse
|
|
||||||
import urllib.request
|
|
||||||
|
|
||||||
sid = "${sid}"
|
sid = "${sid}"
|
||||||
endpoint = "${API_ENDPOINT}"
|
endpoint = "${API_ENDPOINT}"
|
||||||
token = "${NUBES_API_TOKEN}"
|
token = "${NUBES_API_TOKEN}"
|
||||||
|
max_retries = 3
|
||||||
|
|
||||||
params = urllib.parse.urlencode({"endpoint": f"/services/{sid}"})
|
params = urllib.parse.urlencode({"endpoint": f"/services/{sid}"})
|
||||||
url = f"{endpoint}?{params}"
|
url = f"{endpoint}?{params}"
|
||||||
req = urllib.request.Request(url)
|
|
||||||
if token:
|
for attempt in range(1, max_retries + 1):
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url)
|
||||||
|
if token:
|
||||||
req.add_header("Authorization", f"Bearer {token}")
|
req.add_header("Authorization", f"Bearer {token}")
|
||||||
|
req.add_header("User-Agent", "Mozilla/5.0 (compatible; Terraform-Provider-Nubes/BashGenerator)")
|
||||||
with urllib.request.urlopen(req) as resp:
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||||
data = json.loads(resp.read().decode("utf-8"))
|
data = json.loads(resp.read().decode("utf-8"))
|
||||||
|
svc = data.get("svc", {})
|
||||||
svc = data.get("svc", {})
|
name = svc.get("svcShort") or svc.get("name") or svc.get("title") or f"service_{sid}"
|
||||||
name = svc.get("svcShort") or svc.get("name") or svc.get("title") or f"service_{sid}"
|
print(name)
|
||||||
print(name)
|
sys.exit(0)
|
||||||
|
except Exception as e:
|
||||||
|
if attempt < max_retries:
|
||||||
|
time.sleep(2 * attempt)
|
||||||
|
else:
|
||||||
|
print(f"ERROR: failed to fetch service {sid} after {max_retries} attempts: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
PY
|
PY
|
||||||
)
|
)
|
||||||
|
if [[ "$svc_name" == ERROR:* ]]; then
|
||||||
|
echo "$svc_name" >&2
|
||||||
|
echo "${sid} api_error" >> "$FAILURES_FILE"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Один YAML на сервис формируется Go-генератором.
|
# Один YAML на сервис формируется Go-генератором.
|
||||||
|
|||||||
@@ -388,7 +388,15 @@ func (c *apiClient) getServiceOperation(svcOperationId int) (serviceOperationInf
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *apiClient) getViaProxy(endpoint string, out interface{}) error {
|
func (c *apiClient) getViaProxy(endpoint string, out interface{}) error {
|
||||||
// The API uses a proxy query parameter called "endpoint".
|
const maxRetries = 3
|
||||||
|
baseDelay := 2 * time.Second
|
||||||
|
|
||||||
|
var lastErr error
|
||||||
|
for attempt := 0; attempt <= maxRetries; attempt++ {
|
||||||
|
if attempt > 0 {
|
||||||
|
time.Sleep(baseDelay * time.Duration(1<<(attempt-1)))
|
||||||
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", c.endpoint, nil)
|
req, err := http.NewRequest("GET", c.endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -399,19 +407,40 @@ func (c *apiClient) getViaProxy(endpoint string, out interface{}) error {
|
|||||||
if c.token != "" {
|
if c.token != "" {
|
||||||
req.Header.Set("Authorization", "Bearer "+c.token)
|
req.Header.Set("Authorization", "Bearer "+c.token)
|
||||||
}
|
}
|
||||||
// User-Agent: браузерный, чтобы пройти DDoS-Guard (см. docs/ops/API_TOKENS.md).
|
|
||||||
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; Terraform-Provider-Nubes/Generator)")
|
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; Terraform-Provider-Nubes/Generator)")
|
||||||
resp, err := c.httpClient.Do(req)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
if attempt < maxRetries {
|
||||||
|
continue
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode != 200 {
|
body, readErr := io.ReadAll(resp.Body)
|
||||||
body, _ := io.ReadAll(resp.Body)
|
resp.Body.Close()
|
||||||
return fmt.Errorf("status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
if readErr != nil {
|
||||||
|
lastErr = readErr
|
||||||
|
if attempt < maxRetries {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
dec := json.NewDecoder(resp.Body)
|
return readErr
|
||||||
return dec.Decode(out)
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
err := fmt.Errorf("status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||||
|
// Retry on transient errors
|
||||||
|
if attempt < maxRetries && (resp.StatusCode == 429 || resp.StatusCode >= 500) {
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(body, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("getViaProxy failed after %d retries: %w", maxRetries, lastErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *apiClient) collectOperations(ops []operationInfo) ([]OperationSpec, bool, bool, bool, error) {
|
func (c *apiClient) collectOperations(ops []operationInfo) ([]OperationSpec, bool, bool, bool, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user