|
1 | 1 | package validations |
2 | | - |
3 | 2 | import ( |
4 | 3 | "encoding/json" |
5 | 4 | "fmt" |
6 | | - "maps" |
7 | | - "net/http" |
8 | | - "strings" |
9 | 5 |
|
10 | | - "github.com/threatwinds/go-sdk/utils" |
11 | 6 | "github.com/utmstack/UTMStack/plugins/modules-config/config" |
| 7 | + "github.com/utmstack/UTMStack/plugins/modules-config/validations/socai" |
12 | 8 | ) |
13 | 9 |
|
14 | | -// isAnthropicProvider detects if the URL is for Anthropic API |
15 | | -func isAnthropicProvider(url string) bool { |
16 | | - return strings.Contains(url, "anthropic.com") |
17 | | -} |
18 | | - |
19 | | -// SOCAIConfig holds the parsed SOC-AI configuration |
20 | | -type SOCAIConfig struct { |
21 | | - AutoAnalyze bool |
22 | | - IncidentCreation bool |
23 | | - ChangeAlertStatus bool |
24 | | - Provider string |
25 | | - URL string |
26 | | - Model string |
27 | | - AuthType string // "custom-headers", "none" |
28 | | - MaxTokens string |
29 | | - CustomHeaders map[string]string // All headers including auth (from frontend) |
30 | | -} |
31 | | - |
32 | | -var providerDefaultURLs = map[string]string{ |
33 | | - "openai": "https://api.openai.com/v1/chat/completions", |
34 | | - "anthropic": "https://api.anthropic.com/v1/messages", |
35 | | - "azure": "", |
36 | | - "gemini": "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", |
37 | | - "ollama": "http://localhost:11434/v1/chat/completions", |
38 | | - "mistral": "https://api.mistral.ai/v1/chat/completions", |
39 | | - "deepseek": "https://api.deepseek.com/chat/completions", |
40 | | - "groq": "https://api.groq.com/openai/v1/chat/completions", |
41 | | -} |
42 | 10 |
|
43 | | -var providerDisplayNames = map[string]string{ |
44 | | - "openai": "OpenAI", |
45 | | - "anthropic": "Anthropic", |
46 | | - "azure": "Azure OpenAI", |
47 | | - "gemini": "Google Gemini", |
48 | | - "ollama": "Ollama", |
49 | | - "mistral": "Mistral AI", |
50 | | - "deepseek": "DeepSeek", |
51 | | - "groq": "Groq", |
52 | | - "custom": "Custom", |
53 | | -} |
54 | 11 |
|
55 | | -func getProviderName(provider string) string { |
56 | | - if name, ok := providerDisplayNames[provider]; ok { |
57 | | - return name |
58 | | - } |
59 | | - return provider |
60 | | -} |
61 | 12 |
|
62 | 13 | func ValidateSOCAIConfig(cfg *config.ModuleGroup) error { |
63 | 14 | if cfg == nil { |
64 | 15 | return fmt.Errorf("SOC AI configuration is not provided") |
65 | 16 | } |
66 | | - |
67 | | - socai := parseSOCAIConfig(cfg) |
68 | | - providerName := getProviderName(socai.Provider) |
69 | | - |
70 | | - // Validate required fields |
71 | | - if socai.URL == "" { |
72 | | - if socai.Provider == "custom" || socai.Provider == "azure" || socai.Provider == "ollama" { |
73 | | - return fmt.Errorf("API URL is required for %s provider", providerName) |
74 | | - } |
75 | | - return fmt.Errorf("API URL could not be determined. Please verify the provider configuration.") |
76 | | - } |
77 | | - if socai.Model == "" { |
78 | | - return fmt.Errorf("Model is required for %s provider", providerName) |
79 | | - } |
80 | | - |
81 | | - // Validate authType |
82 | | - if socai.AuthType == "" { |
83 | | - socai.AuthType = "none" |
84 | | - } |
85 | | - if socai.AuthType != "custom-headers" && socai.AuthType != "none" { |
86 | | - return fmt.Errorf("Invalid authentication type '%s'. Must be 'custom-headers' or 'none'.", socai.AuthType) |
87 | | - } |
88 | | - |
89 | | - // Validate auth headers exist when needed |
90 | | - if socai.AuthType == "custom-headers" && len(socai.CustomHeaders) == 0 { |
91 | | - if socai.Provider == "ollama" { |
92 | | - // Ollama doesn't need auth |
93 | | - } else { |
94 | | - return fmt.Errorf("API Key is required for %s. Please provide your %s API Key.", providerName, providerName) |
95 | | - } |
96 | | - } |
97 | | - |
98 | | - // Anthropic requires maxTokens |
99 | | - if isAnthropicProvider(socai.URL) && socai.MaxTokens == "" { |
100 | | - return fmt.Errorf("Max Tokens is required for Anthropic. Please set a value (e.g., 4096).") |
101 | | - } |
102 | | - |
103 | | - // Test connection |
104 | | - if err := testSOCAIConnection(socai); err != nil { |
105 | | - return err |
106 | | - } |
107 | | - |
108 | | - return nil |
| 17 | + socai_config := parseSOCAIConfig(cfg) |
| 18 | + verificator := socai.NewSocaiVerification(socai_config) |
| 19 | + return verificator.Validate() |
109 | 20 | } |
110 | 21 |
|
111 | | -func parseSOCAIConfig(cfg *config.ModuleGroup) SOCAIConfig { |
112 | | - socai := SOCAIConfig{ |
| 22 | +func parseSOCAIConfig(cfg *config.ModuleGroup) socai.SOCAIConfig { |
| 23 | + socai := socai.SOCAIConfig{ |
113 | 24 | AuthType: "none", |
114 | 25 | CustomHeaders: make(map[string]string), |
115 | 26 | } |
@@ -143,80 +54,8 @@ func parseSOCAIConfig(cfg *config.ModuleGroup) SOCAIConfig { |
143 | 54 | } |
144 | 55 | } |
145 | 56 |
|
146 | | - // Resolve URL from provider if not custom |
147 | | - if socai.Provider != "" && socai.Provider != "custom" { |
148 | | - if defaultURL, ok := providerDefaultURLs[socai.Provider]; ok && defaultURL != "" { |
149 | | - socai.URL = defaultURL |
150 | | - } |
151 | | - } |
152 | | - |
153 | 57 | return socai |
154 | 58 | } |
155 | 59 |
|
156 | | -func testSOCAIConnection(socai SOCAIConfig) error { |
157 | | - providerName := getProviderName(socai.Provider) |
158 | 60 |
|
159 | | - headers := map[string]string{ |
160 | | - "Content-Type": "application/json", |
161 | | - } |
162 | 61 |
|
163 | | - // Add custom headers (includes auth headers configured by frontend) |
164 | | - if socai.AuthType == "custom-headers" { |
165 | | - maps.Copy(headers, socai.CustomHeaders) |
166 | | - } |
167 | | - |
168 | | - // Test connection with GET request (most APIs return error but validate auth) |
169 | | - _, status, err := utils.DoReq[map[string]any](socai.URL, nil, "GET", headers, false) |
170 | | - |
171 | | - switch status { |
172 | | - case http.StatusOK, http.StatusMethodNotAllowed, http.StatusBadRequest: |
173 | | - // These are acceptable - means we reached the API and auth worked |
174 | | - return nil |
175 | | - case http.StatusUnauthorized: |
176 | | - if socai.Provider == "anthropic" { |
177 | | - return fmt.Errorf("Invalid Anthropic API Key. Please verify your x-api-key is correct.") |
178 | | - } |
179 | | - if socai.Provider == "azure" { |
180 | | - return fmt.Errorf("Invalid Azure OpenAI API Key. Please verify your api-key is correct.") |
181 | | - } |
182 | | - return fmt.Errorf("Invalid API Key for %s. Please verify your API Key is correct.", providerName) |
183 | | - case http.StatusForbidden: |
184 | | - return fmt.Errorf("%s API Key does not have the required permissions (HTTP 403). Please verify the API Key has access to the chat completions endpoint.", providerName) |
185 | | - case http.StatusNotFound: |
186 | | - if socai.Provider == "azure" { |
187 | | - return fmt.Errorf("Azure OpenAI endpoint not found (HTTP 404). Please verify your Endpoint URL includes the correct resource name and deployment.") |
188 | | - } |
189 | | - if socai.Provider == "ollama" { |
190 | | - return fmt.Errorf("Ollama API not found at '%s' (HTTP 404). Please verify Ollama is running and the URL is correct.", socai.URL) |
191 | | - } |
192 | | - return fmt.Errorf("%s API endpoint not found (HTTP 404). Please verify the API URL is correct.", providerName) |
193 | | - case http.StatusRequestTimeout: |
194 | | - if socai.Provider == "ollama" { |
195 | | - return fmt.Errorf("Connection to Ollama timed out. Please verify Ollama is running at '%s' and is accessible from this server.", socai.URL) |
196 | | - } |
197 | | - return fmt.Errorf("Connection to %s timed out. Please verify the API URL is accessible from this server.", providerName) |
198 | | - case http.StatusTooManyRequests: |
199 | | - return fmt.Errorf("%s API rate limit exceeded (HTTP 429). Your API Key may have exceeded its quota. Please check your %s account billing/usage.", providerName, providerName) |
200 | | - default: |
201 | | - if err != nil { |
202 | | - errMsg := strings.ToLower(err.Error()) |
203 | | - if strings.Contains(errMsg, "no such host") || strings.Contains(errMsg, "lookup") { |
204 | | - if socai.Provider == "ollama" { |
205 | | - return fmt.Errorf("Cannot resolve Ollama server at '%s'. Please verify the hostname is correct and accessible.", socai.URL) |
206 | | - } |
207 | | - return fmt.Errorf("Cannot resolve %s API host. Please verify the API URL is correct.", providerName) |
208 | | - } |
209 | | - if strings.Contains(errMsg, "connection refused") { |
210 | | - if socai.Provider == "ollama" { |
211 | | - return fmt.Errorf("Connection refused by Ollama at '%s'. Please verify Ollama is running.", socai.URL) |
212 | | - } |
213 | | - return fmt.Errorf("Connection refused by %s. Please verify the API URL is correct.", providerName) |
214 | | - } |
215 | | - if strings.Contains(errMsg, "timeout") || strings.Contains(errMsg, "deadline") { |
216 | | - return fmt.Errorf("Connection to %s timed out. Please verify the API URL is accessible from this server.", providerName) |
217 | | - } |
218 | | - return fmt.Errorf("Cannot connect to %s. Please verify the API URL and API Key are correct.", providerName) |
219 | | - } |
220 | | - return nil // Accept other status codes as potentially valid |
221 | | - } |
222 | | -} |
0 commit comments