blob: cf792ef1cd02145ec11e407aa79fff2589519a3b [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"
gio6481c902025-05-20 16:16:30 +04007
8 "github.com/sethvargo/go-password/password"
gio3af43942024-04-16 08:13:50 +04009)
10
giof6ad2982024-08-23 17:42:49 +040011const defaultClusterName = "default"
12
gio3af43942024-04-16 08:13:50 +040013type Release struct {
gio3cdee592024-04-17 10:15:56 +040014 AppInstanceId string `json:"appInstanceId"`
15 Namespace string `json:"namespace"`
16 RepoAddr string `json:"repoAddr"`
17 AppDir string `json:"appDir"`
giof8843412024-05-22 16:38:05 +040018 ImageRegistry string `json:"imageRegistry,omitempty"`
gio3af43942024-04-16 08:13:50 +040019}
20
21type Network struct {
giocdfa3722024-06-13 20:10:14 +040022 Name string `json:"name,omitempty"`
23 IngressClass string `json:"ingressClass,omitempty"`
24 CertificateIssuer string `json:"certificateIssuer,omitempty"`
25 Domain string `json:"domain,omitempty"`
26 AllocatePortAddr string `json:"allocatePortAddr,omitempty"`
27 ReservePortAddr string `json:"reservePortAddr,omitempty"`
28 DeallocatePortAddr string `json:"deallocatePortAddr,omitempty"`
gio3af43942024-04-16 08:13:50 +040029}
30
gioe72b54f2024-04-22 10:44:41 +040031type InfraAppInstanceConfig struct {
32 Id string `json:"id"`
33 AppId string `json:"appId"`
34 Infra InfraConfig `json:"infra"`
35 Release Release `json:"release"`
36 Values map[string]any `json:"values"`
37 Input map[string]any `json:"input"`
gio09a3e5b2024-04-26 14:11:06 +040038 URL string `json:"url"`
39 Help []HelpDocument `json:"help"`
40 Icon template.HTML `json:"icon"`
gioe72b54f2024-04-22 10:44:41 +040041}
42
gio3cdee592024-04-17 10:15:56 +040043type AppInstanceConfig struct {
gio3af43942024-04-16 08:13:50 +040044 Id string `json:"id"`
45 AppId string `json:"appId"`
gioe72b54f2024-04-22 10:44:41 +040046 Env EnvConfig `json:"env"`
gio3cdee592024-04-17 10:15:56 +040047 Release Release `json:"release"`
48 Values map[string]any `json:"values"`
49 Input map[string]any `json:"input"`
gio09a3e5b2024-04-26 14:11:06 +040050 URL string `json:"url"`
Davit Tabidze56f86a42024-04-09 19:15:25 +040051 Help []HelpDocument `json:"help"`
gio09a3e5b2024-04-26 14:11:06 +040052 Icon string `json:"icon"`
gio3af43942024-04-16 08:13:50 +040053}
54
gio3cdee592024-04-17 10:15:56 +040055func (a AppInstanceConfig) InputToValues(schema Schema) map[string]any {
56 ret, err := derivedToConfig(a.Input, schema)
gio3af43942024-04-16 08:13:50 +040057 if err != nil {
gio3cdee592024-04-17 10:15:56 +040058 panic(err)
gio3af43942024-04-16 08:13:50 +040059 }
60 return ret
61}
62
gio36b23b32024-08-25 12:20:54 +040063func getField(v any, f string) any {
64 for _, i := range strings.Split(f, ".") {
65 vm := v.(map[string]any)
66 v = vm[i]
67 }
68 return v
69}
70
71func deriveValues(
72 root any,
73 values any,
74 schema Schema,
75 networks []Network,
giof6ad2982024-08-23 17:42:49 +040076 clusters []Cluster,
gio864b4332024-09-05 13:56:47 +040077 vpnKeyGen VPNAPIClient,
gio36b23b32024-08-25 12:20:54 +040078) (map[string]any, error) {
gio3af43942024-04-16 08:13:50 +040079 ret := make(map[string]any)
gio44f621b2024-04-29 09:44:38 +040080 for _, f := range schema.Fields() {
81 k := f.Name
82 def := f.Schema
gio3af43942024-04-16 08:13:50 +040083 // TODO(gio): validate that it is map
84 v, ok := values.(map[string]any)[k]
85 // TODO(gio): if missing use default value
86 if !ok {
87 if def.Kind() == KindSSHKey {
88 key, err := NewECDSASSHKeyPair("tmp")
89 if err != nil {
90 return nil, err
91 }
92 ret[k] = map[string]string{
93 "public": string(key.RawAuthorizedKey()),
94 "private": string(key.RawPrivateKey()),
95 }
96 }
gio6481c902025-05-20 16:16:30 +040097 if def.Kind() == KindPassword {
98 psswd, err := GeneratePassword()
99 if err != nil {
100 return nil, err
101 }
102 ret[k] = psswd
103 }
gio36b23b32024-08-25 12:20:54 +0400104 if def.Kind() == KindVPNAuthKey {
gio29f6b872024-09-08 16:14:58 +0400105 enabled := true
106 if v, ok := def.Meta()["enabledField"]; ok {
107 // TODO(gio): Improve getField
108 enabled, ok = getField(root, v).(bool)
109 if !ok {
giof6ad2982024-08-23 17:42:49 +0400110 enabled = false
111 // TODO(gio): validate that enabled field exists in the schema
112 // return nil, fmt.Errorf("could not resolve enabled: %+v %s %+v", def.Meta(), v, root)
gio29f6b872024-09-08 16:14:58 +0400113 }
114 }
115 if !enabled {
116 continue
117 }
gio7fbd4ad2024-08-27 10:06:39 +0400118 var username string
119 if v, ok := def.Meta()["username"]; ok {
120 username = v
121 } else if v, ok := def.Meta()["usernameField"]; ok {
122 // TODO(gio): Improve getField
123 username, ok = getField(root, v).(string)
124 if !ok {
125 return nil, fmt.Errorf("could not resolve username: %+v %s %+v", def.Meta(), v, root)
126 }
127 }
gio864b4332024-09-05 13:56:47 +0400128 authKey, err := vpnKeyGen.GenerateAuthKey(username)
gio36b23b32024-08-25 12:20:54 +0400129 if err != nil {
130 return nil, err
131 }
132 ret[k] = authKey
133 }
gio3af43942024-04-16 08:13:50 +0400134 continue
135 }
136 switch def.Kind() {
137 case KindBoolean:
138 ret[k] = v
139 case KindString:
140 ret[k] = v
141 case KindInt:
142 ret[k] = v
gioefa0ed42024-06-13 12:31:43 +0400143 case KindPort:
144 ret[k] = v
gio36b23b32024-08-25 12:20:54 +0400145 case KindVPNAuthKey:
146 ret[k] = v
gio6481c902025-05-20 16:16:30 +0400147 case KindPassword:
148 ret[k] = v
gioe72b54f2024-04-22 10:44:41 +0400149 case KindArrayString:
150 a, ok := v.([]string)
151 if !ok {
152 return nil, fmt.Errorf("expected string array")
153 }
154 ret[k] = a
gio3af43942024-04-16 08:13:50 +0400155 case KindNetwork:
gio4ece99c2024-07-18 11:05:50 +0400156 name, ok := v.(string)
157 if !ok {
158 return nil, fmt.Errorf("not a string")
159 }
160 n, err := findNetwork(networks, name)
gio3af43942024-04-16 08:13:50 +0400161 if err != nil {
162 return nil, err
163 }
164 ret[k] = n
gio4ece99c2024-07-18 11:05:50 +0400165 case KindMultiNetwork:
166 vv, ok := v.([]any)
167 if !ok {
168 return nil, fmt.Errorf("not an array")
169 }
170 picked := []Network{}
171 for _, nn := range vv {
172 name, ok := nn.(string)
173 if !ok {
174 return nil, fmt.Errorf("not a string")
175 }
176 n, err := findNetwork(networks, name)
177 if err != nil {
178 return nil, err
179 }
180 picked = append(picked, n)
181 }
182 ret[k] = picked
giof6ad2982024-08-23 17:42:49 +0400183 case KindCluster:
184 name, ok := v.(string)
185 if !ok {
186 // TODO(gio): validate that value has cluster schema
187 ret[k] = v
188 } else {
189 c, err := findCluster(clusters, name)
190 if err != nil {
191 return nil, err
192 }
193 if c == nil {
194 delete(ret, k)
195 } else {
196 ret[k] = c
197 }
198 }
gio3af43942024-04-16 08:13:50 +0400199 case KindAuth:
giof6ad2982024-08-23 17:42:49 +0400200 r, err := deriveValues(root, v, AuthSchema, networks, clusters, vpnKeyGen)
gio3af43942024-04-16 08:13:50 +0400201 if err != nil {
202 return nil, err
203 }
204 ret[k] = r
205 case KindSSHKey:
giof6ad2982024-08-23 17:42:49 +0400206 r, err := deriveValues(root, v, SSHKeySchema, networks, clusters, vpnKeyGen)
gio3af43942024-04-16 08:13:50 +0400207 if err != nil {
208 return nil, err
209 }
210 ret[k] = r
211 case KindStruct:
giof6ad2982024-08-23 17:42:49 +0400212 r, err := deriveValues(root, v, def, networks, clusters, vpnKeyGen)
gio3af43942024-04-16 08:13:50 +0400213 if err != nil {
214 return nil, err
215 }
216 ret[k] = r
217 default:
218 return nil, fmt.Errorf("Should not reach!")
219 }
220 }
221 return ret, nil
222}
223
224func derivedToConfig(derived map[string]any, schema Schema) (map[string]any, error) {
225 ret := make(map[string]any)
gio44f621b2024-04-29 09:44:38 +0400226 for _, f := range schema.Fields() {
227 k := f.Name
228 def := f.Schema
gio3af43942024-04-16 08:13:50 +0400229 v, ok := derived[k]
230 // TODO(gio): if missing use default value
231 if !ok {
gio488554f2024-10-02 16:41:26 +0400232 if def.Kind() == KindCluster {
233 ret[k] = "default"
234 }
gio3af43942024-04-16 08:13:50 +0400235 continue
236 }
237 switch def.Kind() {
238 case KindBoolean:
239 ret[k] = v
240 case KindString:
241 ret[k] = v
242 case KindInt:
243 ret[k] = v
gioefa0ed42024-06-13 12:31:43 +0400244 case KindPort:
245 ret[k] = v
gio36b23b32024-08-25 12:20:54 +0400246 case KindVPNAuthKey:
247 ret[k] = v
gio6481c902025-05-20 16:16:30 +0400248 case KindPassword:
249 ret[k] = v
gioe72b54f2024-04-22 10:44:41 +0400250 case KindArrayString:
251 a, ok := v.([]string)
252 if !ok {
253 return nil, fmt.Errorf("expected string array")
254 }
255 ret[k] = a
gio3af43942024-04-16 08:13:50 +0400256 case KindNetwork:
257 vm, ok := v.(map[string]any)
258 if !ok {
259 return nil, fmt.Errorf("expected map")
260 }
261 name, ok := vm["name"]
262 if !ok {
263 return nil, fmt.Errorf("expected network name")
264 }
265 ret[k] = name
gio4ece99c2024-07-18 11:05:50 +0400266 case KindMultiNetwork:
267 nl, ok := v.([]any)
268 if !ok {
269 return nil, fmt.Errorf("expected map")
270 }
271 names := []string{}
272 for _, n := range nl {
273 i, ok := n.(map[string]any)
274 if !ok {
275 return nil, fmt.Errorf("expected map")
276 }
277 name, ok := i["name"]
278 if !ok {
279 return nil, fmt.Errorf("expected network name")
280 }
281 names = append(names, name.(string))
282 }
283 ret[k] = names
gio3af43942024-04-16 08:13:50 +0400284 case KindAuth:
285 vm, ok := v.(map[string]any)
286 if !ok {
287 return nil, fmt.Errorf("expected map")
288 }
289 r, err := derivedToConfig(vm, AuthSchema)
290 if err != nil {
291 return nil, err
292 }
293 ret[k] = r
294 case KindSSHKey:
295 vm, ok := v.(map[string]any)
296 if !ok {
297 return nil, fmt.Errorf("expected map")
298 }
299 r, err := derivedToConfig(vm, SSHKeySchema)
300 if err != nil {
301 return nil, err
302 }
303 ret[k] = r
304 case KindStruct:
305 vm, ok := v.(map[string]any)
306 if !ok {
307 return nil, fmt.Errorf("expected map")
308 }
309 r, err := derivedToConfig(vm, def)
310 if err != nil {
311 return nil, err
312 }
313 ret[k] = r
giof6ad2982024-08-23 17:42:49 +0400314 case KindCluster:
315 vm, ok := v.(map[string]any)
316 if !ok {
317 return nil, fmt.Errorf("expected map")
318 }
319 name, ok := vm["name"]
320 if !ok {
321 return nil, fmt.Errorf("expected cluster name")
322 }
323 ret[k] = name
gio3af43942024-04-16 08:13:50 +0400324 default:
325 return nil, fmt.Errorf("Should not reach!")
326 }
327 }
328 return ret, nil
329}
330
331func findNetwork(networks []Network, name string) (Network, error) {
332 for _, n := range networks {
333 if n.Name == name {
334 return n, nil
335 }
336 }
337 return Network{}, fmt.Errorf("Network not found: %s", name)
338}
giof6ad2982024-08-23 17:42:49 +0400339
340func findCluster(clusters []Cluster, name string) (*Cluster, error) {
341 if name == defaultClusterName {
342 return nil, nil
343 }
344 for _, c := range clusters {
345 if c.Name == name {
346 return &c, nil
347 }
348 }
349 return nil, fmt.Errorf("Cluster not found: %s", name)
350}
gio6481c902025-05-20 16:16:30 +0400351
352func GeneratePassword() (string, error) {
353 return password.Generate(20, 5, 0, false, true)
354}