refactor: extract all generators into independent TOOLS/ modules
Each generator is now a standalone Go module with its own go.mod: TOOLS/ ├── yaml-generator/ ← service_spec_gen (API → YAML) ├── resource-generator/ ← gen_v2 (YAML → Go resources) ├── docs-generator/ ← docs_template_gen_v2 (YAML → Markdown) ├── ops-generator/ ← ops_docs_gen (ops documentation) └── bin/ ← pre-built binaries All scripts updated to use TOOLS/bin/* binaries. Removed old universal_rebuild/tools/ and universal_rebuild/bin/.
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
// Package client — HTTP-клиент для API Nubes.
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"yaml-generator/normalize"
|
||||
"yaml-generator/types"
|
||||
)
|
||||
|
||||
// Client — HTTP-клиент для Nubes API.
|
||||
type Client struct {
|
||||
endpoint string
|
||||
token string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// New создаёт новый API-клиент.
|
||||
func New(endpoint string, token string) *Client {
|
||||
return &Client{
|
||||
endpoint: endpoint,
|
||||
token: token,
|
||||
httpClient: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetService получает метаданные сервиса.
|
||||
func (c *Client) GetService(serviceID int) (types.ServiceInfo, error) {
|
||||
endpoint := fmt.Sprintf("/services/%d", serviceID)
|
||||
var res types.ServiceResponse
|
||||
if err := c.callAPI(endpoint, &res); err != nil {
|
||||
return types.ServiceInfo{}, err
|
||||
}
|
||||
return res.Service, nil
|
||||
}
|
||||
|
||||
// GetServiceOperation получает детали операции (параметры + MAN).
|
||||
func (c *Client) GetServiceOperation(svcOperationID int) (types.ServiceOperationInfo, error) {
|
||||
endpoint := fmt.Sprintf("/serviceOperation/%d", svcOperationID)
|
||||
var res types.ServiceOperationResponse
|
||||
if err := c.callAPI(endpoint, &res); err != nil {
|
||||
return types.ServiceOperationInfo{}, err
|
||||
}
|
||||
return res.ServiceOperation, nil
|
||||
}
|
||||
|
||||
func (c *Client) callAPI(endpoint string, out interface{}) error {
|
||||
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)))
|
||||
}
|
||||
|
||||
var reqURL string
|
||||
if c.isProxyAPI() {
|
||||
reqURL = c.endpoint + "?endpoint=" + endpoint
|
||||
} else {
|
||||
reqURL = c.endpoint + endpoint
|
||||
}
|
||||
req, err := http.NewRequest("GET", reqURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.token)
|
||||
}
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; Terraform-Provider-Nubes/Generator)")
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
if attempt < maxRetries {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
body, readErr := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if readErr != nil {
|
||||
lastErr = readErr
|
||||
if attempt < maxRetries {
|
||||
continue
|
||||
}
|
||||
return readErr
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
err := fmt.Errorf("status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
if attempt < maxRetries && (resp.StatusCode == 429 || resp.StatusCode >= 500) {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(body, out)
|
||||
}
|
||||
|
||||
return fmt.Errorf("callAPI failed after %d retries: %w", maxRetries, lastErr)
|
||||
}
|
||||
|
||||
func (c *Client) isProxyAPI() bool {
|
||||
return strings.Contains(c.endpoint, "index.cfm")
|
||||
}
|
||||
|
||||
// CollectOperations собирает и классифицирует все операции сервиса.
|
||||
func (c *Client) CollectOperations(ops []types.OperationInfo) ([]types.OperationSpec, bool, bool, bool, error) {
|
||||
result := make([]types.OperationSpec, 0, len(ops))
|
||||
hasSuspend := false
|
||||
hasResume := false
|
||||
hasDelete := false
|
||||
for _, op := range ops {
|
||||
opName := normalize.OperationName(op.Operation)
|
||||
if opName == "" {
|
||||
continue
|
||||
}
|
||||
opInfo, err := c.GetServiceOperation(op.SvcOperationID)
|
||||
if err != nil {
|
||||
return nil, false, false, false, err
|
||||
}
|
||||
params := make([]types.ParamSpec, 0, len(opInfo.CfsParams))
|
||||
for _, p := range opInfo.CfsParams {
|
||||
params = append(params, types.ParamSpec{
|
||||
ID: p.ID,
|
||||
Code: p.Code,
|
||||
DataType: strings.TrimSpace(p.DataType),
|
||||
Required: p.IsRequired,
|
||||
Default: normalizeDefault(p.DefaultValue),
|
||||
ValueList: normalizeValueList(p.ValueList),
|
||||
RefSvcID: p.RefSvcID,
|
||||
Func: strings.TrimSpace(p.Func),
|
||||
Regex: strings.TrimSpace(p.Regex),
|
||||
UniqueScope: strings.TrimSpace(p.UniqueScope),
|
||||
MaxLength: p.MaxLength,
|
||||
MinLength: p.MinLength,
|
||||
MaxValue: normalizeDefault(p.MaxValue),
|
||||
MinValue: normalizeDefault(p.MinValue),
|
||||
Descr: strings.TrimSpace(p.Descr),
|
||||
Man: strings.TrimSpace(p.Man),
|
||||
Sort: p.Sort,
|
||||
DependsOn: p.DependsOnCfsParams,
|
||||
IsModifiable: p.IsModifiable,
|
||||
IsSensitive: p.IsSensitive,
|
||||
})
|
||||
}
|
||||
sort.Slice(params, func(i, j int) bool { return params[i].ID < params[j].ID })
|
||||
|
||||
kind, action, subresource := classifyOperation(opName)
|
||||
if kind == "instance" && action == "suspend" {
|
||||
hasSuspend = true
|
||||
}
|
||||
if kind == "instance" && action == "resume" {
|
||||
hasResume = true
|
||||
}
|
||||
if kind == "instance" && action == "delete" {
|
||||
hasDelete = true
|
||||
}
|
||||
result = append(result, types.OperationSpec{
|
||||
Name: opName,
|
||||
ID: opInfo.SvcOperationID,
|
||||
Kind: kind,
|
||||
Action: action,
|
||||
Subresource: subresource,
|
||||
Man: strings.TrimSpace(opInfo.Man),
|
||||
Params: params,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
if result[i].Name == result[j].Name {
|
||||
return result[i].ID < result[j].ID
|
||||
}
|
||||
return result[i].Name < result[j].Name
|
||||
})
|
||||
return result, hasSuspend, hasResume, hasDelete, nil
|
||||
}
|
||||
|
||||
func classifyOperation(name string) (string, string, string) {
|
||||
n := strings.ToLower(strings.TrimSpace(name))
|
||||
if n == "create" || n == "modify" || n == "delete" || n == "suspend" || n == "resume" {
|
||||
return "instance", n, ""
|
||||
}
|
||||
if idx := strings.Index(n, "_"); idx > 0 {
|
||||
verb := n[:idx]
|
||||
sub := n[idx+1:]
|
||||
if sub != "" {
|
||||
return "subresource", verb, sub
|
||||
}
|
||||
}
|
||||
return "action", n, ""
|
||||
}
|
||||
|
||||
func normalizeValueList(values []interface{}) []string {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]string, 0, len(values))
|
||||
for _, v := range values {
|
||||
out = append(out, fmt.Sprintf("%v", v))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func normalizeDefault(value interface{}) interface{} {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
switch t := value.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(t)
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user