blob: 2b150a6e9f7450e460900423af0fdc7e475fe57f [file] [log] [blame]
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +04001package installer
2
3import (
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +04004 "fmt"
gioefa0ed42024-06-13 12:31:43 +04005 "strings"
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +04006
7 "cuelang.org/go/cue"
8 "cuelang.org/go/cue/cuecontext"
giof15b9da2024-09-19 06:59:16 +04009 "cuelang.org/go/cue/format"
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040010)
11
12type Kind int
13
14const (
gio4ece99c2024-07-18 11:05:50 +040015 KindBoolean Kind = 0
16 KindInt = 7
17 KindString = 1
18 KindStruct = 2
19 KindNetwork = 3
20 KindMultiNetwork = 10
21 KindAuth = 5
22 KindSSHKey = 6
23 KindNumber = 4
24 KindArrayString = 8
25 KindPort = 9
gio36b23b32024-08-25 12:20:54 +040026 KindVPNAuthKey = 11
giof6ad2982024-08-23 17:42:49 +040027 KindCluster = 12
gio6481c902025-05-20 16:16:30 +040028 KindPassword = 13
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040029)
30
gio44f621b2024-04-29 09:44:38 +040031type Field struct {
32 Name string
33 Schema Schema
34}
35
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040036type Schema interface {
gio44f621b2024-04-29 09:44:38 +040037 Name() string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040038 Kind() Kind
gio44f621b2024-04-29 09:44:38 +040039 Fields() []Field
40 Advanced() bool
gio36b23b32024-08-25 12:20:54 +040041 Meta() map[string]string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040042}
43
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040044var AuthSchema Schema = structSchema{
gio44f621b2024-04-29 09:44:38 +040045 name: "Auth",
46 fields: []Field{
gio36b23b32024-08-25 12:20:54 +040047 Field{"enabled", basicSchema{"Enabled", KindBoolean, false, nil}},
48 Field{"groups", basicSchema{"Groups", KindString, false, nil}},
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040049 },
gio44f621b2024-04-29 09:44:38 +040050 advanced: false,
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040051}
52
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040053var SSHKeySchema Schema = structSchema{
gio44f621b2024-04-29 09:44:38 +040054 name: "SSH Key",
55 fields: []Field{
gio36b23b32024-08-25 12:20:54 +040056 Field{"public", basicSchema{"Public Key", KindString, false, nil}},
57 Field{"private", basicSchema{"Private Key", KindString, false, nil}},
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040058 },
gio44f621b2024-04-29 09:44:38 +040059 advanced: true,
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040060}
61
giof6ad2982024-08-23 17:42:49 +040062const clusterSchema = `
63#Cluster: {
64 name: string
65 kubeconfig: string
66 ingressClassName: string
67}
giof15b9da2024-09-19 06:59:16 +040068value: #Cluster
gio8f290322024-09-21 15:37:45 +040069
70#Schema: %s
71value: #Schema
giof6ad2982024-08-23 17:42:49 +040072`
73
74func isCluster(v cue.Value) bool {
75 if v.Value().Kind() != cue.StructKind {
76 return false
77 }
giof15b9da2024-09-19 06:59:16 +040078 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
79 if err != nil {
80 return false
81 }
82 s := fmt.Sprintf(clusterSchema, string(vb))
giof6ad2982024-08-23 17:42:49 +040083 c := cuecontext.New()
84 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +040085 if err := u.Err(); err != nil {
86 return false
87 }
giof6ad2982024-08-23 17:42:49 +040088 if err := u.Validate(); err != nil {
89 return false
90 }
giof15b9da2024-09-19 06:59:16 +040091 if err := u.Eval().Err(); err != nil {
92 return false
giof6ad2982024-08-23 17:42:49 +040093 }
giof15b9da2024-09-19 06:59:16 +040094 return true
giof6ad2982024-08-23 17:42:49 +040095}
96
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040097const networkSchema = `
98#Network: {
gio8f290322024-09-21 15:37:45 +040099 name: string
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400100 ingressClass: string
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400101 certificateIssuer: string | *""
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400102 domain: string
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400103 allocatePortAddr: string
gioefa0ed42024-06-13 12:31:43 +0400104 reservePortAddr: string
giocdfa3722024-06-13 20:10:14 +0400105 deallocatePortAddr: string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400106}
giof15b9da2024-09-19 06:59:16 +0400107value: #Network
gio8f290322024-09-21 15:37:45 +0400108
109#Schema: %s
110value: #Schema
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400111`
112
113func isNetwork(v cue.Value) bool {
114 if v.Value().Kind() != cue.StructKind {
115 return false
116 }
giof15b9da2024-09-19 06:59:16 +0400117 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
118 if err != nil {
119 return false
120 }
121 s := fmt.Sprintf(networkSchema, string(vb))
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400122 c := cuecontext.New()
123 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400124 if err := u.Err(); err != nil {
125 return false
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400126 }
giof15b9da2024-09-19 06:59:16 +0400127 if err := u.Validate(); err != nil {
128 return false
129 }
130 if err := u.Eval().Err(); err != nil {
131 return false
132 }
133 return true
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400134}
135
gio4ece99c2024-07-18 11:05:50 +0400136const multiNetworkSchema = `
137#Network: {
138 name: string
139 ingressClass: string
140 certificateIssuer: string | *""
141 domain: string
142 allocatePortAddr: string
143 reservePortAddr: string
144 deallocatePortAddr: string
145}
gio4ece99c2024-07-18 11:05:50 +0400146#Networks: [...#Network]
giof15b9da2024-09-19 06:59:16 +0400147value: #Networks
gio8f290322024-09-21 15:37:45 +0400148
149#Schema: %s
150value: #Schema
gio4ece99c2024-07-18 11:05:50 +0400151`
152
153func isMultiNetwork(v cue.Value) bool {
154 if v.Value().IncompleteKind() != cue.ListKind {
155 return false
156 }
giof15b9da2024-09-19 06:59:16 +0400157 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
158 if err != nil {
159 return false
160 }
161 s := fmt.Sprintf(multiNetworkSchema, string(vb))
gio4ece99c2024-07-18 11:05:50 +0400162 c := cuecontext.New()
163 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400164 if err := u.Err(); err != nil {
165 return false
gio4ece99c2024-07-18 11:05:50 +0400166 }
giof15b9da2024-09-19 06:59:16 +0400167 if err := u.Validate(); err != nil {
168 return false
169 }
170 if err := u.Eval().Err(); err != nil {
171 return false
172 }
173 return true
gio4ece99c2024-07-18 11:05:50 +0400174}
175
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400176const authSchema = `
177#Auth: {
178 enabled: bool | false
179 groups: string | *""
180}
giof15b9da2024-09-19 06:59:16 +0400181value: #Auth
gio8f290322024-09-21 15:37:45 +0400182
183#Schema: %s
184value: #Schema
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400185`
186
187func isAuth(v cue.Value) bool {
188 if v.Value().Kind() != cue.StructKind {
189 return false
190 }
giof15b9da2024-09-19 06:59:16 +0400191 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
192 if err != nil {
193 return false
194 }
195 s := fmt.Sprintf(authSchema, string(vb))
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400196 c := cuecontext.New()
197 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400198 if err := u.Err(); err != nil {
199 return false
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400200 }
giof15b9da2024-09-19 06:59:16 +0400201 if err := u.Validate(); err != nil {
202 return false
203 }
204 if err := u.Eval().Err(); err != nil {
205 return false
206 }
207 return true
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400208}
209
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400210const sshKeySchema = `
211#SSHKey: {
212 public: string
213 private: string
214}
giof15b9da2024-09-19 06:59:16 +0400215value: #SSHKey
gio8f290322024-09-21 15:37:45 +0400216
217#Schema: %s
218value: #Schema
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400219`
220
221func isSSHKey(v cue.Value) bool {
222 if v.Value().Kind() != cue.StructKind {
223 return false
224 }
giof15b9da2024-09-19 06:59:16 +0400225 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
226 if err != nil {
227 return false
228 }
229 s := fmt.Sprintf(sshKeySchema, string(vb))
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400230 c := cuecontext.New()
231 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400232 if err := u.Err(); err != nil {
233 return false
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400234 }
giof15b9da2024-09-19 06:59:16 +0400235 if err := u.Validate(); err != nil {
236 return false
237 }
238 if err := u.Eval().Err(); err != nil {
239 return false
240 }
241 return true
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400242}
243
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400244type basicSchema struct {
gio44f621b2024-04-29 09:44:38 +0400245 name string
246 kind Kind
247 advanced bool
gio36b23b32024-08-25 12:20:54 +0400248 meta map[string]string
gio44f621b2024-04-29 09:44:38 +0400249}
250
251func (s basicSchema) Name() string {
252 return s.name
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400253}
254
255func (s basicSchema) Kind() Kind {
256 return s.kind
257}
258
gio44f621b2024-04-29 09:44:38 +0400259func (s basicSchema) Fields() []Field {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400260 return nil
261}
262
gio44f621b2024-04-29 09:44:38 +0400263func (s basicSchema) Advanced() bool {
264 return s.advanced
265}
266
gio36b23b32024-08-25 12:20:54 +0400267func (s basicSchema) Meta() map[string]string {
268 return s.meta
269}
270
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400271type structSchema struct {
gio44f621b2024-04-29 09:44:38 +0400272 name string
273 fields []Field
274 advanced bool
275}
276
277func (s structSchema) Name() string {
278 return s.name
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400279}
280
281func (s structSchema) Kind() Kind {
282 return KindStruct
283}
284
gio44f621b2024-04-29 09:44:38 +0400285func (s structSchema) Fields() []Field {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400286 return s.fields
287}
288
gio44f621b2024-04-29 09:44:38 +0400289func (s structSchema) Advanced() bool {
290 return s.advanced
291}
292
gio36b23b32024-08-25 12:20:54 +0400293func (s structSchema) Meta() map[string]string {
294 return map[string]string{}
295}
296
gio44f621b2024-04-29 09:44:38 +0400297func NewCueSchema(name string, v cue.Value) (Schema, error) {
298 nameAttr := v.Attribute("name")
299 if nameAttr.Err() == nil {
300 name = nameAttr.Contents()
301 }
gioefa0ed42024-06-13 12:31:43 +0400302 role := ""
303 roleAttr := v.Attribute("role")
304 if roleAttr.Err() == nil {
305 role = strings.ToLower(roleAttr.Contents())
306 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400307 switch v.IncompleteKind() {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400308 case cue.StringKind:
gio6481c902025-05-20 16:16:30 +0400309 if role == "password" {
310 // TODO(gio): implement configurable requirements such as min-length, ...
311 return basicSchema{name, KindPassword, false, nil}, nil
312 } else if role == "vpnauthkey" {
gio36b23b32024-08-25 12:20:54 +0400313 meta := map[string]string{}
gio7fbd4ad2024-08-27 10:06:39 +0400314 usernameFieldAttr := v.Attribute("usernameField")
315 if usernameFieldAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400316 meta["usernameField"] = usernameFieldAttr.Contents()
gio7fbd4ad2024-08-27 10:06:39 +0400317 }
318 usernameAttr := v.Attribute("username")
319 if usernameAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400320 meta["username"] = usernameAttr.Contents()
gio7fbd4ad2024-08-27 10:06:39 +0400321 }
322 if len(meta) != 1 {
323 return nil, fmt.Errorf("invalid vpn auth key field meta: %+v", meta)
324 }
gio29f6b872024-09-08 16:14:58 +0400325 enabledFieldAttr := v.Attribute("enabledField")
326 if enabledFieldAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400327 meta["enabledField"] = enabledFieldAttr.Contents()
gio29f6b872024-09-08 16:14:58 +0400328 }
gio36b23b32024-08-25 12:20:54 +0400329 return basicSchema{name, KindVPNAuthKey, true, meta}, nil
330 } else {
331 return basicSchema{name, KindString, false, nil}, nil
332 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400333 case cue.BoolKind:
gio36b23b32024-08-25 12:20:54 +0400334 return basicSchema{name, KindBoolean, false, nil}, nil
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400335 case cue.NumberKind:
gio36b23b32024-08-25 12:20:54 +0400336 return basicSchema{name, KindNumber, false, nil}, nil
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400337 case cue.IntKind:
gioefa0ed42024-06-13 12:31:43 +0400338 if role == "port" {
gio36b23b32024-08-25 12:20:54 +0400339 return basicSchema{name, KindPort, true, nil}, nil
gioefa0ed42024-06-13 12:31:43 +0400340 } else {
gio36b23b32024-08-25 12:20:54 +0400341 return basicSchema{name, KindInt, false, nil}, nil
gioefa0ed42024-06-13 12:31:43 +0400342 }
gioe72b54f2024-04-22 10:44:41 +0400343 case cue.ListKind:
gio4ece99c2024-07-18 11:05:50 +0400344 if isMultiNetwork(v) {
gio36b23b32024-08-25 12:20:54 +0400345 return basicSchema{name, KindMultiNetwork, false, nil}, nil
gio4ece99c2024-07-18 11:05:50 +0400346 }
gio36b23b32024-08-25 12:20:54 +0400347 return basicSchema{name, KindArrayString, false, nil}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400348 case cue.StructKind:
349 if isNetwork(v) {
gio36b23b32024-08-25 12:20:54 +0400350 return basicSchema{name, KindNetwork, false, nil}, nil
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400351 } else if isAuth(v) {
gio36b23b32024-08-25 12:20:54 +0400352 return basicSchema{name, KindAuth, false, nil}, nil
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400353 } else if isSSHKey(v) {
gio36b23b32024-08-25 12:20:54 +0400354 return basicSchema{name, KindSSHKey, true, nil}, nil
giof6ad2982024-08-23 17:42:49 +0400355 } else if isCluster(v) {
356 return basicSchema{name, KindCluster, false, nil}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400357 }
gio44f621b2024-04-29 09:44:38 +0400358 s := structSchema{name, make([]Field, 0), false}
giof6ad2982024-08-23 17:42:49 +0400359 f, err := v.Fields(cue.All())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400360 if err != nil {
361 return nil, err
362 }
363 for f.Next() {
gio44f621b2024-04-29 09:44:38 +0400364 scm, err := NewCueSchema(f.Selector().String(), f.Value())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400365 if err != nil {
366 return nil, err
367 }
giof6ad2982024-08-23 17:42:49 +0400368 s.fields = append(s.fields, Field{cleanFieldName(f.Selector().String()), scm})
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400369 }
370 return s, nil
371 default:
giof15b9da2024-09-19 06:59:16 +0400372 return nil, fmt.Errorf("SHOULD NOT REACH! field: %s, value: %s", name, v)
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400373 }
374}
giof6ad2982024-08-23 17:42:49 +0400375
giofc441e32024-11-11 16:26:14 +0400376func ExtractDefaultValues(v cue.Value) (any, error) {
377 switch v.IncompleteKind() {
378 case cue.StringKind:
gio842db3f2025-05-30 11:57:20 +0400379 if d, ok := v.Default(); ok || d.IsConcrete() {
giofc441e32024-11-11 16:26:14 +0400380 return d.String()
381 }
382 case cue.BoolKind:
383 if d, ok := v.Default(); ok {
384 return d.Bool()
385 }
386 case cue.NumberKind:
387 // TODO(gio): handle numbers
388 return nil, fmt.Errorf("implement: %s", v)
389 case cue.IntKind:
gio838bcb82025-05-15 19:39:04 +0400390 if d, ok := v.Default(); ok || d.IsConcrete() {
giofc441e32024-11-11 16:26:14 +0400391 return d.Int64()
392 }
393 case cue.ListKind:
394 // TODO(gio): handle lists
395 return nil, nil
396 case cue.StructKind:
397 // TODO(gio): Such fields might have default values as well?
398 if isNetwork(v) {
399 return nil, nil
400 } else if isAuth(v) {
401 return nil, nil
giofc441e32024-11-11 16:26:14 +0400402 } else if isCluster(v) {
403 return nil, nil
404 }
405 ret := map[string]any{}
406 f, err := v.Fields(cue.All())
407 if err != nil {
408 return nil, err
409 }
410 for f.Next() {
411 fv, err := ExtractDefaultValues(f.Value())
412 if err != nil {
413 return nil, err
414 }
gio842db3f2025-05-30 11:57:20 +0400415 if fv != nil {
416 ret[f.Selector().String()] = fv
417 }
418 }
419 if len(ret) == 0 {
420 return nil, nil
giofc441e32024-11-11 16:26:14 +0400421 }
422 return ret, nil
423 default:
424 return nil, fmt.Errorf("SHOULD NOT REACH! value: %s", v)
425 }
426 return nil, nil
427}
428
giof6ad2982024-08-23 17:42:49 +0400429func cleanFieldName(name string) string {
430 return strings.ReplaceAll(strings.ReplaceAll(name, "?", ""), "!", "")
431}