blob: d8b3a7479bb2a5c2460767e0317216d6f99b6a5a [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 (
gioe65d9a92025-06-19 09:02:32 +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
26 KindVPNAuthKey = 11
27 KindCluster = 12
28 KindPassword = 13
29 KindSketchSessionId = 14
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040030)
31
gio44f621b2024-04-29 09:44:38 +040032type Field struct {
33 Name string
34 Schema Schema
35}
36
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040037type Schema interface {
gio44f621b2024-04-29 09:44:38 +040038 Name() string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040039 Kind() Kind
gio44f621b2024-04-29 09:44:38 +040040 Fields() []Field
41 Advanced() bool
gio36b23b32024-08-25 12:20:54 +040042 Meta() map[string]string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040043}
44
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040045var AuthSchema Schema = structSchema{
gio44f621b2024-04-29 09:44:38 +040046 name: "Auth",
47 fields: []Field{
gio36b23b32024-08-25 12:20:54 +040048 Field{"enabled", basicSchema{"Enabled", KindBoolean, false, nil}},
49 Field{"groups", basicSchema{"Groups", KindString, false, nil}},
gio33222472025-08-04 12:02:34 +040050 Field{"noAuthPathPatterns", basicSchema{"Optional Auth Paths", KindArrayString, false, nil}},
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040051 },
gio44f621b2024-04-29 09:44:38 +040052 advanced: false,
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040053}
54
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040055var SSHKeySchema Schema = structSchema{
gio44f621b2024-04-29 09:44:38 +040056 name: "SSH Key",
57 fields: []Field{
gio36b23b32024-08-25 12:20:54 +040058 Field{"public", basicSchema{"Public Key", KindString, false, nil}},
59 Field{"private", basicSchema{"Private Key", KindString, false, nil}},
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040060 },
gio44f621b2024-04-29 09:44:38 +040061 advanced: true,
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040062}
63
giof6ad2982024-08-23 17:42:49 +040064const clusterSchema = `
65#Cluster: {
66 name: string
67 kubeconfig: string
68 ingressClassName: string
69}
giof15b9da2024-09-19 06:59:16 +040070value: #Cluster
gio8f290322024-09-21 15:37:45 +040071
72#Schema: %s
73value: #Schema
giof6ad2982024-08-23 17:42:49 +040074`
75
76func isCluster(v cue.Value) bool {
77 if v.Value().Kind() != cue.StructKind {
78 return false
79 }
giof15b9da2024-09-19 06:59:16 +040080 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
81 if err != nil {
82 return false
83 }
84 s := fmt.Sprintf(clusterSchema, string(vb))
giof6ad2982024-08-23 17:42:49 +040085 c := cuecontext.New()
86 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +040087 if err := u.Err(); err != nil {
88 return false
89 }
giof6ad2982024-08-23 17:42:49 +040090 if err := u.Validate(); err != nil {
91 return false
92 }
giof15b9da2024-09-19 06:59:16 +040093 if err := u.Eval().Err(); err != nil {
94 return false
giof6ad2982024-08-23 17:42:49 +040095 }
giof15b9da2024-09-19 06:59:16 +040096 return true
giof6ad2982024-08-23 17:42:49 +040097}
98
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040099const networkSchema = `
100#Network: {
gio8f290322024-09-21 15:37:45 +0400101 name: string
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400102 ingressClass: string
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400103 certificateIssuer: string | *""
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400104 domain: string
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400105 allocatePortAddr: string
gioefa0ed42024-06-13 12:31:43 +0400106 reservePortAddr: string
giocdfa3722024-06-13 20:10:14 +0400107 deallocatePortAddr: string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400108}
giof15b9da2024-09-19 06:59:16 +0400109value: #Network
gio8f290322024-09-21 15:37:45 +0400110
111#Schema: %s
112value: #Schema
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400113`
114
115func isNetwork(v cue.Value) bool {
116 if v.Value().Kind() != cue.StructKind {
117 return false
118 }
giof15b9da2024-09-19 06:59:16 +0400119 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
120 if err != nil {
121 return false
122 }
123 s := fmt.Sprintf(networkSchema, string(vb))
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400124 c := cuecontext.New()
125 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400126 if err := u.Err(); err != nil {
127 return false
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400128 }
giof15b9da2024-09-19 06:59:16 +0400129 if err := u.Validate(); err != nil {
130 return false
131 }
132 if err := u.Eval().Err(); err != nil {
133 return false
134 }
135 return true
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400136}
137
gio4ece99c2024-07-18 11:05:50 +0400138const multiNetworkSchema = `
139#Network: {
140 name: string
141 ingressClass: string
142 certificateIssuer: string | *""
143 domain: string
144 allocatePortAddr: string
145 reservePortAddr: string
146 deallocatePortAddr: string
147}
gio4ece99c2024-07-18 11:05:50 +0400148#Networks: [...#Network]
giof15b9da2024-09-19 06:59:16 +0400149value: #Networks
gio8f290322024-09-21 15:37:45 +0400150
151#Schema: %s
152value: #Schema
gio4ece99c2024-07-18 11:05:50 +0400153`
154
155func isMultiNetwork(v cue.Value) bool {
156 if v.Value().IncompleteKind() != cue.ListKind {
157 return false
158 }
giof15b9da2024-09-19 06:59:16 +0400159 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
160 if err != nil {
161 return false
162 }
163 s := fmt.Sprintf(multiNetworkSchema, string(vb))
gio4ece99c2024-07-18 11:05:50 +0400164 c := cuecontext.New()
165 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400166 if err := u.Err(); err != nil {
167 return false
gio4ece99c2024-07-18 11:05:50 +0400168 }
giof15b9da2024-09-19 06:59:16 +0400169 if err := u.Validate(); err != nil {
170 return false
171 }
172 if err := u.Eval().Err(); err != nil {
173 return false
174 }
175 return true
gio4ece99c2024-07-18 11:05:50 +0400176}
177
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400178const authSchema = `
179#Auth: {
180 enabled: bool | false
181 groups: string | *""
gio28356152025-07-24 17:20:56 +0400182 noAuthPathPatterns: [...string] | *[]
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400183}
giof15b9da2024-09-19 06:59:16 +0400184value: #Auth
gio8f290322024-09-21 15:37:45 +0400185
186#Schema: %s
187value: #Schema
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400188`
189
190func isAuth(v cue.Value) bool {
191 if v.Value().Kind() != cue.StructKind {
192 return false
193 }
giof15b9da2024-09-19 06:59:16 +0400194 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
195 if err != nil {
196 return false
197 }
198 s := fmt.Sprintf(authSchema, string(vb))
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400199 c := cuecontext.New()
200 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400201 if err := u.Err(); err != nil {
202 return false
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400203 }
giof15b9da2024-09-19 06:59:16 +0400204 if err := u.Validate(); err != nil {
205 return false
206 }
207 if err := u.Eval().Err(); err != nil {
208 return false
209 }
210 return true
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400211}
212
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400213const sshKeySchema = `
214#SSHKey: {
215 public: string
216 private: string
217}
giof15b9da2024-09-19 06:59:16 +0400218value: #SSHKey
gio8f290322024-09-21 15:37:45 +0400219
220#Schema: %s
221value: #Schema
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400222`
223
224func isSSHKey(v cue.Value) bool {
225 if v.Value().Kind() != cue.StructKind {
226 return false
227 }
giof15b9da2024-09-19 06:59:16 +0400228 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
229 if err != nil {
230 return false
231 }
232 s := fmt.Sprintf(sshKeySchema, string(vb))
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400233 c := cuecontext.New()
234 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400235 if err := u.Err(); err != nil {
236 return false
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400237 }
giof15b9da2024-09-19 06:59:16 +0400238 if err := u.Validate(); err != nil {
239 return false
240 }
241 if err := u.Eval().Err(); err != nil {
242 return false
243 }
244 return true
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400245}
246
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400247type basicSchema struct {
gio44f621b2024-04-29 09:44:38 +0400248 name string
249 kind Kind
250 advanced bool
gio36b23b32024-08-25 12:20:54 +0400251 meta map[string]string
gio44f621b2024-04-29 09:44:38 +0400252}
253
254func (s basicSchema) Name() string {
255 return s.name
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400256}
257
258func (s basicSchema) Kind() Kind {
259 return s.kind
260}
261
gio44f621b2024-04-29 09:44:38 +0400262func (s basicSchema) Fields() []Field {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400263 return nil
264}
265
gio44f621b2024-04-29 09:44:38 +0400266func (s basicSchema) Advanced() bool {
267 return s.advanced
268}
269
gio36b23b32024-08-25 12:20:54 +0400270func (s basicSchema) Meta() map[string]string {
271 return s.meta
272}
273
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400274type structSchema struct {
gio44f621b2024-04-29 09:44:38 +0400275 name string
276 fields []Field
277 advanced bool
278}
279
280func (s structSchema) Name() string {
281 return s.name
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400282}
283
284func (s structSchema) Kind() Kind {
285 return KindStruct
286}
287
gio44f621b2024-04-29 09:44:38 +0400288func (s structSchema) Fields() []Field {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400289 return s.fields
290}
291
gio44f621b2024-04-29 09:44:38 +0400292func (s structSchema) Advanced() bool {
293 return s.advanced
294}
295
gio36b23b32024-08-25 12:20:54 +0400296func (s structSchema) Meta() map[string]string {
297 return map[string]string{}
298}
299
gio44f621b2024-04-29 09:44:38 +0400300func NewCueSchema(name string, v cue.Value) (Schema, error) {
301 nameAttr := v.Attribute("name")
302 if nameAttr.Err() == nil {
303 name = nameAttr.Contents()
304 }
gioefa0ed42024-06-13 12:31:43 +0400305 role := ""
306 roleAttr := v.Attribute("role")
307 if roleAttr.Err() == nil {
308 role = strings.ToLower(roleAttr.Contents())
309 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400310 switch v.IncompleteKind() {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400311 case cue.StringKind:
gio6481c902025-05-20 16:16:30 +0400312 if role == "password" {
313 // TODO(gio): implement configurable requirements such as min-length, ...
314 return basicSchema{name, KindPassword, false, nil}, nil
gioe65d9a92025-06-19 09:02:32 +0400315 } else if role == "sketch-session-id" {
316 return basicSchema{name, KindSketchSessionId, false, nil}, nil
gio6481c902025-05-20 16:16:30 +0400317 } else if role == "vpnauthkey" {
gio36b23b32024-08-25 12:20:54 +0400318 meta := map[string]string{}
gio7fbd4ad2024-08-27 10:06:39 +0400319 usernameFieldAttr := v.Attribute("usernameField")
320 if usernameFieldAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400321 meta["usernameField"] = usernameFieldAttr.Contents()
gio7fbd4ad2024-08-27 10:06:39 +0400322 }
323 usernameAttr := v.Attribute("username")
324 if usernameAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400325 meta["username"] = usernameAttr.Contents()
gio7fbd4ad2024-08-27 10:06:39 +0400326 }
327 if len(meta) != 1 {
328 return nil, fmt.Errorf("invalid vpn auth key field meta: %+v", meta)
329 }
gio29f6b872024-09-08 16:14:58 +0400330 enabledFieldAttr := v.Attribute("enabledField")
331 if enabledFieldAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400332 meta["enabledField"] = enabledFieldAttr.Contents()
gio29f6b872024-09-08 16:14:58 +0400333 }
gio36b23b32024-08-25 12:20:54 +0400334 return basicSchema{name, KindVPNAuthKey, true, meta}, nil
335 } else {
336 return basicSchema{name, KindString, false, nil}, nil
337 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400338 case cue.BoolKind:
gio36b23b32024-08-25 12:20:54 +0400339 return basicSchema{name, KindBoolean, false, nil}, nil
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400340 case cue.NumberKind:
gio36b23b32024-08-25 12:20:54 +0400341 return basicSchema{name, KindNumber, false, nil}, nil
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400342 case cue.IntKind:
gioefa0ed42024-06-13 12:31:43 +0400343 if role == "port" {
gio36b23b32024-08-25 12:20:54 +0400344 return basicSchema{name, KindPort, true, nil}, nil
gioefa0ed42024-06-13 12:31:43 +0400345 } else {
gio36b23b32024-08-25 12:20:54 +0400346 return basicSchema{name, KindInt, false, nil}, nil
gioefa0ed42024-06-13 12:31:43 +0400347 }
gioe72b54f2024-04-22 10:44:41 +0400348 case cue.ListKind:
gio4ece99c2024-07-18 11:05:50 +0400349 if isMultiNetwork(v) {
gio36b23b32024-08-25 12:20:54 +0400350 return basicSchema{name, KindMultiNetwork, false, nil}, nil
gio4ece99c2024-07-18 11:05:50 +0400351 }
gio36b23b32024-08-25 12:20:54 +0400352 return basicSchema{name, KindArrayString, false, nil}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400353 case cue.StructKind:
354 if isNetwork(v) {
gio36b23b32024-08-25 12:20:54 +0400355 return basicSchema{name, KindNetwork, false, nil}, nil
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400356 } else if isAuth(v) {
gio36b23b32024-08-25 12:20:54 +0400357 return basicSchema{name, KindAuth, false, nil}, nil
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400358 } else if isSSHKey(v) {
gio36b23b32024-08-25 12:20:54 +0400359 return basicSchema{name, KindSSHKey, true, nil}, nil
giof6ad2982024-08-23 17:42:49 +0400360 } else if isCluster(v) {
361 return basicSchema{name, KindCluster, false, nil}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400362 }
gio44f621b2024-04-29 09:44:38 +0400363 s := structSchema{name, make([]Field, 0), false}
giof6ad2982024-08-23 17:42:49 +0400364 f, err := v.Fields(cue.All())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400365 if err != nil {
366 return nil, err
367 }
368 for f.Next() {
gio44f621b2024-04-29 09:44:38 +0400369 scm, err := NewCueSchema(f.Selector().String(), f.Value())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400370 if err != nil {
371 return nil, err
372 }
giof6ad2982024-08-23 17:42:49 +0400373 s.fields = append(s.fields, Field{cleanFieldName(f.Selector().String()), scm})
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400374 }
375 return s, nil
376 default:
giof15b9da2024-09-19 06:59:16 +0400377 return nil, fmt.Errorf("SHOULD NOT REACH! field: %s, value: %s", name, v)
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400378 }
379}
giof6ad2982024-08-23 17:42:49 +0400380
giofc441e32024-11-11 16:26:14 +0400381func ExtractDefaultValues(v cue.Value) (any, error) {
382 switch v.IncompleteKind() {
383 case cue.StringKind:
gio842db3f2025-05-30 11:57:20 +0400384 if d, ok := v.Default(); ok || d.IsConcrete() {
giofc441e32024-11-11 16:26:14 +0400385 return d.String()
386 }
387 case cue.BoolKind:
388 if d, ok := v.Default(); ok {
389 return d.Bool()
390 }
391 case cue.NumberKind:
392 // TODO(gio): handle numbers
393 return nil, fmt.Errorf("implement: %s", v)
394 case cue.IntKind:
gio838bcb82025-05-15 19:39:04 +0400395 if d, ok := v.Default(); ok || d.IsConcrete() {
giofc441e32024-11-11 16:26:14 +0400396 return d.Int64()
397 }
398 case cue.ListKind:
399 // TODO(gio): handle lists
400 return nil, nil
401 case cue.StructKind:
402 // TODO(gio): Such fields might have default values as well?
403 if isNetwork(v) {
404 return nil, nil
405 } else if isAuth(v) {
406 return nil, nil
giofc441e32024-11-11 16:26:14 +0400407 } else if isCluster(v) {
408 return nil, nil
409 }
410 ret := map[string]any{}
411 f, err := v.Fields(cue.All())
412 if err != nil {
413 return nil, err
414 }
415 for f.Next() {
416 fv, err := ExtractDefaultValues(f.Value())
417 if err != nil {
418 return nil, err
419 }
gio842db3f2025-05-30 11:57:20 +0400420 if fv != nil {
421 ret[f.Selector().String()] = fv
422 }
423 }
424 if len(ret) == 0 {
425 return nil, nil
giofc441e32024-11-11 16:26:14 +0400426 }
427 return ret, nil
428 default:
429 return nil, fmt.Errorf("SHOULD NOT REACH! value: %s", v)
430 }
431 return nil, nil
432}
433
giof6ad2982024-08-23 17:42:49 +0400434func cleanFieldName(name string) string {
435 return strings.ReplaceAll(strings.ReplaceAll(name, "?", ""), "!", "")
436}