fix(core): graceful fallback for cfsParams 500 error in createInstanceWithContext

- In provider/internal/core/client.go:
  - If GET /instanceOperations/{opUid}?fields=cfsParams fails with 500 or JSON unmarshal error,
    check whether provided parameters contain unresolved names via hasUnresolvedParams.
  - If all parameters are already resolved (UUIDs/numbers/booleans/JSON), proceed to send
    parameters via POST /instanceOperationCfsParams without hard-failing.
  - If unresolved names remain, fail immediately with original error.
- Bumped DEV provider version to 2.0.2 in TOOLS/config/dev/profile.env, VERSIONS.md, and DEV_STAND/FullPipe/versions.tf.
- Built, signed, and published provider 2.0.2 to DEV registry bucket nubes-terraform-registry.
This commit is contained in:
Repinoid
2026-09-21 09:59:15 +03:00
parent 7ff98f8edc
commit 308f92bc37
4 changed files with 76 additions and 34 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ terraform {
required_providers {
nubes = {
source = "tf-registry.containerk8s.services.ngcloud.ru/nubes-dev/nubes"
version = "2.0.1"
version = "2.0.2"
}
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ TOKEN_FILE="secrets/dev.token"
# Release versions
# Version
VERSION="2.0.0"
VERSION="2.0.2"
NAMESPACE="nubes-dev"
PROVIDER_NAME="nubes"
+1 -1
View File
@@ -5,7 +5,7 @@
| Стенд | Namespace | Версия | Дата заливки |
|---|---|---|---|
| PROD | `nubes` | `1.0.0` | 2026-09-03 | (новая нумерация) |
| DEV | `nubes-dev` | `2.0.1` | 2026-09-20 | (добавлены parent modify/modifier ресурсы) |
| DEV | `nubes-dev` | `2.0.2` | 2026-09-21 | (graceful fallback для cfsParams при 500) |
| TEST | `nubes-test` | `3.0.0` | 2026-09-03 | (новая нумерация) |
## Как проверить
+73 -31
View File
@@ -270,17 +270,22 @@ func (c *UniversalClient) CreateGenericInstanceUniversalV6(ctx context.Context,
}
opDetailsResp, _, err := c.doRequest(ctx, "GET", fmt.Sprintf("/instanceOperations/%s?fields=cfsParams", opUid), nil)
if err != nil {
return "", fmt.Errorf("не удалось получить детали операции: %w", err)
}
var opDetails universalOpResponse
if err := json.Unmarshal(opDetailsResp, &opDetails); err != nil {
return "", fmt.Errorf("не удалось разобрать детали операции: %w", err)
}
params, err = c.resolveRefSvcParamValues(ctx, opDetails.InstanceOperation.CfsParams, params)
hasOpDetails := false
if err != nil {
return "", err
if hasUnresolvedParams(params) {
return "", fmt.Errorf("не удалось получить детали операции: %w", err)
}
} else if jsonErr := json.Unmarshal(opDetailsResp, &opDetails); jsonErr != nil {
if hasUnresolvedParams(params) {
return "", fmt.Errorf("не удалось разобрать детали операции: %w", jsonErr)
}
} else {
hasOpDetails = true
params, err = c.resolveRefSvcParamValues(ctx, opDetails.InstanceOperation.CfsParams, params)
if err != nil {
return "", err
}
}
sent := make(map[int]bool)
@@ -297,30 +302,32 @@ func (c *UniversalClient) CreateGenericInstanceUniversalV6(ctx context.Context,
sent[paramId] = true
}
for _, param := range opDetails.InstanceOperation.CfsParams {
if sent[param.SvcOperationCfsParamId] {
continue
}
if !param.IsRequired {
continue
}
if hasOpDetails {
for _, param := range opDetails.InstanceOperation.CfsParams {
if sent[param.SvcOperationCfsParamId] {
continue
}
if !param.IsRequired {
continue
}
val := ""
if param.ParamValue != nil {
val = *param.ParamValue
} else if param.DefaultValue != nil {
val = *param.DefaultValue
}
val = normalizeUniversalValueV6(val, param)
val := ""
if param.ParamValue != nil {
val = *param.ParamValue
} else if param.DefaultValue != nil {
val = *param.DefaultValue
}
val = normalizeUniversalValueV6(val, param)
pPayload := genericParamReq{
InstanceOperationUid: opUid,
SvcOperationCfsParamId: param.SvcOperationCfsParamId,
ParamValue: val,
}
_, _, err := c.doRequest(ctx, "POST", "/instanceOperationCfsParams", pPayload)
if err != nil {
return "", fmt.Errorf("не удалось отправить параметр по умолчанию %d: %w", param.SvcOperationCfsParamId, err)
pPayload := genericParamReq{
InstanceOperationUid: opUid,
SvcOperationCfsParamId: param.SvcOperationCfsParamId,
ParamValue: val,
}
_, _, err := c.doRequest(ctx, "POST", "/instanceOperationCfsParams", pPayload)
if err != nil {
return "", fmt.Errorf("не удалось отправить параметр по умолчанию %d: %w", param.SvcOperationCfsParamId, err)
}
}
}
@@ -1352,6 +1359,41 @@ func isUUIDLike(value string) bool {
return true
}
// hasUnresolvedParams возвращает true, если хотя бы одно значение параметров похоже на
// неразрешённое имя вместо UUID. Значения map-fixed/json/array, числа и булевы пропускаются.
func hasUnresolvedParams(params map[int]string) bool {
for _, val := range params {
trimmed := strings.TrimSpace(val)
if trimmed == "" || trimmed == "{}" || trimmed == "[]" {
continue
}
if strings.HasPrefix(trimmed, "{") || strings.HasPrefix(trimmed, "[") {
// json/map-fixed/array не являются именами одиночных инстансов
continue
}
if trimmed == "true" || trimmed == "false" {
continue
}
isNum := true
for _, r := range trimmed {
if r < '0' || r > '9' {
isNum = false
break
}
}
if isNum {
continue
}
if isUUIDLike(trimmed) {
continue
}
// Обнаружена не-числовая строка, не JSON и не UUID (потенциальное имя).
// Если GET ?fields=cfsParams упал, такое имя невозможно разрешить в UUID.
return true
}
return false
}
func isHexDigit(r rune) bool {
return (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')
}