blob: ada4e953ae0c1b6e6fb8e5ae88700e2d0fe042e9 [file] [log] [blame]
gio3af43942024-04-16 08:13:50 +04001package installer
2
3import (
4 "fmt"
gio09a3e5b2024-04-26 14:11:06 +04005 "html/template"
gio36b23b32024-08-25 12:20:54 +04006 "strings"
gio3af43942024-04-16 08:13:50 +04007)
8
giof6ad2982024-08-23 17:42:49 +04009const defaultClusterName = "default"
10
gio3af43942024-04-16 08:13:50 +040011type Release struct {
gio3cdee592024-04-17 10:15:56 +040012 AppInstanceId string `json:"appInstanceId"`
13 Namespace string `json:"namespace"`
14 RepoAddr string `json:"repoAddr"`
15 AppDir string `json:"appDir"`
giof8843412024-05-22 16:38:05 +040016 ImageRegistry string `json:"imageRegistry,omitempty"`
gio3af43942024-04-16 08:13:50 +040017}
18
19type Network struct {
giocdfa3722024-06-13 20:10:14 +040020 Name string `json:"name,omitempty"`
21 IngressClass string `json:"ingressClass,omitempty"`
22 CertificateIssuer string `json:"certificateIssuer,omitempty"`
23 Domain string `json:"domain,omitempty"`
24 AllocatePortAddr string `json:"allocatePortAddr,omitempty"`
25 ReservePortAddr string `json:"reservePortAddr,omitempty"`
26 DeallocatePortAddr string `json:"deallocatePortAddr,omitempty"`
gio3af43942024-04-16 08:13:50 +040027}
28
gioe72b54f2024-04-22 10:44:41 +040029type InfraAppInstanceConfig struct {
30 Id string `json:"id"`
31 AppId string `json:"appId"`
32 Infra InfraConfig `json:"infra"`
33 Release Release `json:"release"`
34 Values map[string]any `json:"values"`
35 Input map[string]any `json:"input"`
gio09a3e5b2024-04-26 14:11:06 +040036 URL string `json:"url"`
37 Help []HelpDocument `json:"help"`
38 Icon template.HTML `json:"icon"`
gioe72b54f2024-04-22 10:44:41 +040039}
40
gio3cdee592024-04-17 10:15:56 +040041type AppInstanceConfig struct {
gio3af43942024-04-16 08:13:50 +040042 Id string `json:"id"`
43 AppId string `json:"appId"`
gioe72b54f2024-04-22 10:44:41 +040044 Env EnvConfig `json:"env"`
gio3cdee592024-04-17 10:15:56 +040045 Release Release `json:"release"`
46 Values map[string]any `json:"values"`
47 Input map[string]any `json:"input"`
gio09a3e5b2024-04-26 14:11:06 +040048 URL string `json:"url"`
Davit Tabidze56f86a42024-04-09 19:15:25 +040049 Help []HelpDocument `json:"help"`
gio09a3e5b2024-04-26 14:11:06 +040050 Icon string `json:"icon"`
gio3af43942024-04-16 08:13:50 +040051}
52
gio3cdee592024-04-17 10:15:56 +040053func (a AppInstanceConfig) InputToValues(schema Schema) map[string]any {
54 ret, err := derivedToConfig(a.Input, schema)
gio3af43942024-04-16 08:13:50 +040055 if err != nil {
gio3cdee592024-04-17 10:15:56 +040056 panic(err)
gio3af43942024-04-16 08:13:50 +040057 }
58 return ret
59}
60
gio36b23b32024-08-25 12:20:54 +040061func getField(v any, f string) any {
62 for _, i := range strings.Split(f, ".") {
63 vm := v.(map[string]any)
64 v = vm[i]
65 }
66 return v
67}
68
69func deriveValues(
70 root any,
71 values any,
72 schema Schema,
73 networks []Network,
giof6ad2982024-08-23 17:42:49 +040074 clusters []Cluster,
gio864b4332024-09-05 13:56:47 +040075 vpnKeyGen VPNAPIClient,
gio36b23b32024-08-25 12:20:54 +040076) (map[string]any, error) {
gio3af43942024-04-16 08:13:50 +040077 ret := make(map[string]any)
gio44f621b2024-04-29 09:44:38 +040078 for _, f := range schema.Fields() {
79 k := f.Name
80 def := f.Schema
gio3af43942024-04-16 08:13:50 +040081 // TODO(gio): validate that it is map
82 v, ok := values.(map[string]any)[k]
83 // TODO(gio): if missing use default value
84 if !ok {
85 if def.Kind() == KindSSHKey {
86 key, err := NewECDSASSHKeyPair("tmp")
87 if err != nil {
88 return nil, err
89 }
90 ret[k] = map[string]string{
91 "public": string(key.RawAuthorizedKey()),
92 "private": string(key.RawPrivateKey()),
93 }
94 }
gio36b23b32024-08-25 12:20:54 +040095 if def.Kind() == KindVPNAuthKey {
gio29f6b872024-09-08 16:14:58 +040096 enabled := true
97 if v, ok := def.Meta()["enabledField"]; ok {
98 // TODO(gio): Improve getField
99 enabled, ok = getField(root, v).(bool)
100 if !ok {
giof6ad2982024-08-23 17:42:49 +0400101 enabled = false
102 // TODO(gio): validate that enabled field exists in the schema
103 // return nil, fmt.Errorf("could not resolve enabled: %+v %s %+v", def.Meta(), v, root)
gio29f6b872024-09-08 16:14:58 +0400104 }
105 }
106 if !enabled {
107 continue
108 }
gio7fbd4ad2024-08-27 10:06:39 +0400109 var username string
110 if v, ok := def.Meta()["username"]; ok {
111 username = v
112 } else if v, ok := def.Meta()["usernameField"]; ok {
113 // TODO(gio): Improve getField
114 username, ok = getField(root, v).(string)
115 if !ok {
116 return nil, fmt.Errorf("could not resolve username: %+v %s %+v", def.Meta(), v, root)
117 }
118 }
gio864b4332024-09-05 13:56:47 +0400119 authKey, err := vpnKeyGen.GenerateAuthKey(username)
gio36b23b32024-08-25 12:20:54 +0400120 if err != nil {
121 return nil, err
122 }
123 ret[k] = authKey
124 }
gio3af43942024-04-16 08:13:50 +0400125 continue
126 }
127 switch def.Kind() {
128 case KindBoolean:
129 ret[k] = v
130 case KindString:
131 ret[k] = v
132 case KindInt:
133 ret[k] = v
gioefa0ed42024-06-13 12:31:43 +0400134 case KindPort:
135 ret[k] = v
gio36b23b32024-08-25 12:20:54 +0400136 case KindVPNAuthKey:
137 ret[k] = v
gioe72b54f2024-04-22 10:44:41 +0400138 case KindArrayString:
139 a, ok := v.([]string)
140 if !ok {
141 return nil, fmt.Errorf("expected string array")
142 }
143 ret[k] = a
gio3af43942024-04-16 08:13:50 +0400144 case KindNetwork:
gio4ece99c2024-07-18 11:05:50 +0400145 name, ok := v.(string)
146 if !ok {
147 return nil, fmt.Errorf("not a string")
148 }
149 n, err := findNetwork(networks, name)
gio3af43942024-04-16 08:13:50 +0400150 if err != nil {
151 return nil, err
152 }
153 ret[k] = n
gio4ece99c2024-07-18 11:05:50 +0400154 case KindMultiNetwork:
155 vv, ok := v.([]any)
156 if !ok {
157 return nil, fmt.Errorf("not an array")
158 }
159 picked := []Network{}
160 for _, nn := range vv {
161 name, ok := nn.(string)
162 if !ok {
163 return nil, fmt.Errorf("not a string")
164 }
165 n, err := findNetwork(networks, name)
166 if err != nil {
167 return nil, err
168 }
169 picked = append(picked, n)
170 }
171 ret[k] = picked
giof6ad2982024-08-23 17:42:49 +0400172 case KindCluster:
173 name, ok := v.(string)
174 if !ok {
175 // TODO(gio): validate that value has cluster schema
176 ret[k] = v
177 } else {
178 c, err := findCluster(clusters, name)
179 if err != nil {
180 return nil, err
181 }
182 if c == nil {
183 delete(ret, k)
184 } else {
185 ret[k] = c
186 }
187 }
gio3af43942024-04-16 08:13:50 +0400188 case KindAuth:
giof6ad2982024-08-23 17:42:49 +0400189 r, err := deriveValues(root, v, AuthSchema, networks, clusters, vpnKeyGen)
gio3af43942024-04-16 08:13:50 +0400190 if err != nil {
191 return nil, err
192 }
193 ret[k] = r
194 case KindSSHKey:
giof6ad2982024-08-23 17:42:49 +0400195 r, err := deriveValues(root, v, SSHKeySchema, networks, clusters, vpnKeyGen)
gio3af43942024-04-16 08:13:50 +0400196 if err != nil {
197 return nil, err
198 }
199 ret[k] = r
200 case KindStruct:
giof6ad2982024-08-23 17:42:49 +0400201 r, err := deriveValues(root, v, def, networks, clusters, vpnKeyGen)
gio3af43942024-04-16 08:13:50 +0400202 if err != nil {
203 return nil, err
204 }
205 ret[k] = r
206 default:
207 return nil, fmt.Errorf("Should not reach!")
208 }
209 }
210 return ret, nil
211}
212
213func derivedToConfig(derived map[string]any, schema Schema) (map[string]any, error) {
214 ret := make(map[string]any)
gio44f621b2024-04-29 09:44:38 +0400215 for _, f := range schema.Fields() {
216 k := f.Name
217 def := f.Schema
gio3af43942024-04-16 08:13:50 +0400218 v, ok := derived[k]
219 // TODO(gio): if missing use default value
220 if !ok {
gio488554f2024-10-02 16:41:26 +0400221 if def.Kind() == KindCluster {
222 ret[k] = "default"
223 }
gio3af43942024-04-16 08:13:50 +0400224 continue
225 }
226 switch def.Kind() {
227 case KindBoolean:
228 ret[k] = v
229 case KindString:
230 ret[k] = v
231 case KindInt:
232 ret[k] = v
gioefa0ed42024-06-13 12:31:43 +0400233 case KindPort:
234 ret[k] = v
gio36b23b32024-08-25 12:20:54 +0400235 case KindVPNAuthKey:
236 ret[k] = v
gioe72b54f2024-04-22 10:44:41 +0400237 case KindArrayString:
238 a, ok := v.([]string)
239 if !ok {
240 return nil, fmt.Errorf("expected string array")
241 }
242 ret[k] = a
gio3af43942024-04-16 08:13:50 +0400243 case KindNetwork:
244 vm, ok := v.(map[string]any)
245 if !ok {
246 return nil, fmt.Errorf("expected map")
247 }
248 name, ok := vm["name"]
249 if !ok {
250 return nil, fmt.Errorf("expected network name")
251 }
252 ret[k] = name
gio4ece99c2024-07-18 11:05:50 +0400253 case KindMultiNetwork:
254 nl, ok := v.([]any)
255 if !ok {
256 return nil, fmt.Errorf("expected map")
257 }
258 names := []string{}
259 for _, n := range nl {
260 i, ok := n.(map[string]any)
261 if !ok {
262 return nil, fmt.Errorf("expected map")
263 }
264 name, ok := i["name"]
265 if !ok {
266 return nil, fmt.Errorf("expected network name")
267 }
268 names = append(names, name.(string))
269 }
270 ret[k] = names
gio3af43942024-04-16 08:13:50 +0400271 case KindAuth:
272 vm, ok := v.(map[string]any)
273 if !ok {
274 return nil, fmt.Errorf("expected map")
275 }
276 r, err := derivedToConfig(vm, AuthSchema)
277 if err != nil {
278 return nil, err
279 }
280 ret[k] = r
281 case KindSSHKey:
282 vm, ok := v.(map[string]any)
283 if !ok {
284 return nil, fmt.Errorf("expected map")
285 }
286 r, err := derivedToConfig(vm, SSHKeySchema)
287 if err != nil {
288 return nil, err
289 }
290 ret[k] = r
291 case KindStruct:
292 vm, ok := v.(map[string]any)
293 if !ok {
294 return nil, fmt.Errorf("expected map")
295 }
296 r, err := derivedToConfig(vm, def)
297 if err != nil {
298 return nil, err
299 }
300 ret[k] = r
giof6ad2982024-08-23 17:42:49 +0400301 case KindCluster:
302 vm, ok := v.(map[string]any)
303 if !ok {
304 return nil, fmt.Errorf("expected map")
305 }
306 name, ok := vm["name"]
307 if !ok {
308 return nil, fmt.Errorf("expected cluster name")
309 }
310 ret[k] = name
gio3af43942024-04-16 08:13:50 +0400311 default:
312 return nil, fmt.Errorf("Should not reach!")
313 }
314 }
315 return ret, nil
316}
317
318func findNetwork(networks []Network, name string) (Network, error) {
319 for _, n := range networks {
320 if n.Name == name {
321 return n, nil
322 }
323 }
324 return Network{}, fmt.Errorf("Network not found: %s", name)
325}
giof6ad2982024-08-23 17:42:49 +0400326
327func findCluster(clusters []Cluster, name string) (*Cluster, error) {
328 if name == defaultClusterName {
329 return nil, nil
330 }
331 for _, c := range clusters {
332 if c.Name == name {
333 return &c, nil
334 }
335 }
336 return nil, fmt.Errorf("Cluster not found: %s", name)
337}