package core import ( "context" "encoding/json" "fmt" "strings" "time" ) // RunInstanceOperationUniversal runs an available operation (modify/suspend/delete/resume) if possible. func (c *UniversalClient) RunInstanceOperationUniversal(ctx context.Context, instanceUid string, action string, params map[int]string) error { state, err := c.GetInstanceState(ctx, instanceUid) if err != nil { return err } var opId int for _, op := range state.AvailableOperations { if strings.EqualFold(op.Operation, action) { opId = op.SvcOperationId break } } if opId == 0 { return fmt.Errorf("action %s not available for instance %s", action, instanceUid) } payload := map[string]interface{}{ "instanceUid": instanceUid, "svcOperationId": opId, "operation": action, } opUid, err := c.postIgnoreResponse(ctx, "/instanceOperations", payload, true) if err != nil { return fmt.Errorf("failed to create %s operation: %w", action, err) } if opUid == "" { return fmt.Errorf("failed to get operation UID for %s", action) } for paramId, value := range params { pPayload := genericParamReq{ InstanceOperationUid: opUid, SvcOperationCfsParamId: paramId, ParamValue: value, } _, _, err := c.doRequest(ctx, "POST", "/instanceOperationCfsParams", pPayload) if err != nil { return fmt.Errorf("failed to set param %d: %w", paramId, err) } } _, _, err = c.doRequest(ctx, "POST", fmt.Sprintf("/instanceOperations/%s/run", opUid), map[string]interface{}{}) if err != nil { return err } return c.waitForOperationFinish(ctx, opUid, defaultOperationTimeout) } const defaultOperationTimeout = 30 * time.Minute type operationStatusResponse struct { InstanceOperation struct { DtFinish *string `json:"dtFinish"` IsSuccessful *bool `json:"isSuccessful"` ErrorLog *string `json:"errorLog"` IsInProgress bool `json:"isInProgress"` IsPending bool `json:"isPending"` } `json:"instanceOperation"` } func (c *UniversalClient) waitForOperationFinish(ctx context.Context, opUid string, timeout time.Duration) error { deadline := time.Now().Add(timeout) ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for { select { case <-ctx.Done(): return fmt.Errorf("operation %s cancelled", opUid) case <-ticker.C: if time.Now().After(deadline) { return fmt.Errorf("timeout waiting for operation %s to finish", opUid) } respBody, _, err := c.doRequest(ctx, "GET", fmt.Sprintf("/instanceOperations/%s", opUid), nil) if err != nil { return fmt.Errorf("failed to check operation %s status: %w", opUid, err) } var status operationStatusResponse if err := json.Unmarshal(respBody, &status); err != nil { return fmt.Errorf("failed to parse operation %s status: %w", opUid, err) } if status.InstanceOperation.DtFinish != nil && strings.TrimSpace(*status.InstanceOperation.DtFinish) != "" { if status.InstanceOperation.IsSuccessful != nil && !*status.InstanceOperation.IsSuccessful { if status.InstanceOperation.ErrorLog != nil && strings.TrimSpace(*status.InstanceOperation.ErrorLog) != "" { return fmt.Errorf("operation %s failed: %s", opUid, *status.InstanceOperation.ErrorLog) } return fmt.Errorf("operation %s failed", opUid) } return nil } } } } func (c *UniversalClient) postIgnoreResponse(ctx context.Context, path string, payload interface{}, returnLocation bool) (string, error) { respBody, headers, err := c.doRequest(ctx, "POST", path, payload) if err != nil { return "", err } if returnLocation { if loc := headers.Get("Location"); loc != "" { return extractUIDFromLocation(loc), nil } } var justId string if err := json.Unmarshal(respBody, &justId); err == nil && justId != "" { return justId, nil } return "", nil } func extractUIDFromLocation(loc string) string { if loc == "" { return "" } return strings.TrimPrefix(loc, "./") }