blob: 12a79b0d41dc6c197599a2d84b818c246574298f [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}},
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040050 },
gio44f621b2024-04-29 09:44:38 +040051 advanced: false,
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040052}
53
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040054var SSHKeySchema Schema = structSchema{
gio44f621b2024-04-29 09:44:38 +040055 name: "SSH Key",
56 fields: []Field{
gio36b23b32024-08-25 12:20:54 +040057 Field{"public", basicSchema{"Public Key", KindString, false, nil}},
58 Field{"private", basicSchema{"Private Key", KindString, false, nil}},
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040059 },
gio44f621b2024-04-29 09:44:38 +040060 advanced: true,
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040061}
62
giof6ad2982024-08-23 17:42:49 +040063const clusterSchema = `
64#Cluster: {
65 name: string
66 kubeconfig: string
67 ingressClassName: string
68}
giof15b9da2024-09-19 06:59:16 +040069value: #Cluster
gio8f290322024-09-21 15:37:45 +040070
71#Schema: %s
72value: #Schema
giof6ad2982024-08-23 17:42:49 +040073`
74
75func isCluster(v cue.Value) bool {
76 if v.Value().Kind() != cue.StructKind {
77 return false
78 }
giof15b9da2024-09-19 06:59:16 +040079 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
80 if err != nil {
81 return false
82 }
83 s := fmt.Sprintf(clusterSchema, string(vb))
giof6ad2982024-08-23 17:42:49 +040084 c := cuecontext.New()
85 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +040086 if err := u.Err(); err != nil {
87 return false
88 }
giof6ad2982024-08-23 17:42:49 +040089 if err := u.Validate(); err != nil {
90 return false
91 }
giof15b9da2024-09-19 06:59:16 +040092 if err := u.Eval().Err(); err != nil {
93 return false
giof6ad2982024-08-23 17:42:49 +040094 }
giof15b9da2024-09-19 06:59:16 +040095 return true
giof6ad2982024-08-23 17:42:49 +040096}
97
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040098const networkSchema = `
99#Network: {
gio8f290322024-09-21 15:37:45 +0400100 name: string
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400101 ingressClass: string
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400102 certificateIssuer: string | *""
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400103 domain: string
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400104 allocatePortAddr: string
gioefa0ed42024-06-13 12:31:43 +0400105 reservePortAddr: string
giocdfa3722024-06-13 20:10:14 +0400106 deallocatePortAddr: string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400107}
giof15b9da2024-09-19 06:59:16 +0400108value: #Network
gio8f290322024-09-21 15:37:45 +0400109
110#Schema: %s
111value: #Schema
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400112`
113
114func isNetwork(v cue.Value) bool {
115 if v.Value().Kind() != cue.StructKind {
116 return false
117 }
giof15b9da2024-09-19 06:59:16 +0400118 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
119 if err != nil {
120 return false
121 }
122 s := fmt.Sprintf(networkSchema, string(vb))
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400123 c := cuecontext.New()
124 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400125 if err := u.Err(); err != nil {
126 return false
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400127 }
giof15b9da2024-09-19 06:59:16 +0400128 if err := u.Validate(); err != nil {
129 return false
130 }
131 if err := u.Eval().Err(); err != nil {
132 return false
133 }
134 return true
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400135}
136
gio4ece99c2024-07-18 11:05:50 +0400137const multiNetworkSchema = `
138#Network: {
139 name: string
140 ingressClass: string
141 certificateIssuer: string | *""
142 domain: string
143 allocatePortAddr: string
144 reservePortAddr: string
145 deallocatePortAddr: string
146}
gio4ece99c2024-07-18 11:05:50 +0400147#Networks: [...#Network]
giof15b9da2024-09-19 06:59:16 +0400148value: #Networks
gio8f290322024-09-21 15:37:45 +0400149
150#Schema: %s
151value: #Schema
gio4ece99c2024-07-18 11:05:50 +0400152`
153
154func isMultiNetwork(v cue.Value) bool {
155 if v.Value().IncompleteKind() != cue.ListKind {
156 return false
157 }
giof15b9da2024-09-19 06:59:16 +0400158 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
159 if err != nil {
160 return false
161 }
162 s := fmt.Sprintf(multiNetworkSchema, string(vb))
gio4ece99c2024-07-18 11:05:50 +0400163 c := cuecontext.New()
164 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400165 if err := u.Err(); err != nil {
166 return false
gio4ece99c2024-07-18 11:05:50 +0400167 }
giof15b9da2024-09-19 06:59:16 +0400168 if err := u.Validate(); err != nil {
169 return false
170 }
171 if err := u.Eval().Err(); err != nil {
172 return false
173 }
174 return true
gio4ece99c2024-07-18 11:05:50 +0400175}
176
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400177const authSchema = `
178#Auth: {
179 enabled: bool | false
180 groups: string | *""
gio28356152025-07-24 17:20:56 +0400181 noAuthPathPatterns: [...string] | *[]
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400182}
giof15b9da2024-09-19 06:59:16 +0400183value: #Auth
gio8f290322024-09-21 15:37:45 +0400184
185#Schema: %s
186value: #Schema
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400187`
188
189func isAuth(v cue.Value) bool {
190 if v.Value().Kind() != cue.StructKind {
191 return false
192 }
giof15b9da2024-09-19 06:59:16 +0400193 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
194 if err != nil {
195 return false
196 }
197 s := fmt.Sprintf(authSchema, string(vb))
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400198 c := cuecontext.New()
199 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400200 if err := u.Err(); err != nil {
201 return false
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400202 }
giof15b9da2024-09-19 06:59:16 +0400203 if err := u.Validate(); err != nil {
204 return false
205 }
206 if err := u.Eval().Err(); err != nil {
207 return false
208 }
209 return true
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400210}
211
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400212const sshKeySchema = `
213#SSHKey: {
214 public: string
215 private: string
216}
giof15b9da2024-09-19 06:59:16 +0400217value: #SSHKey
gio8f290322024-09-21 15:37:45 +0400218
219#Schema: %s
220value: #Schema
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400221`
222
223func isSSHKey(v cue.Value) bool {
224 if v.Value().Kind() != cue.StructKind {
225 return false
226 }
giof15b9da2024-09-19 06:59:16 +0400227 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
228 if err != nil {
229 return false
230 }
231 s := fmt.Sprintf(sshKeySchema, string(vb))
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400232 c := cuecontext.New()
233 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400234 if err := u.Err(); err != nil {
235 return false
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400236 }
giof15b9da2024-09-19 06:59:16 +0400237 if err := u.Validate(); err != nil {
238 return false
239 }
240 if err := u.Eval().Err(); err != nil {
241 return false
242 }
243 return true
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400244}
245
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400246type basicSchema struct {
gio44f621b2024-04-29 09:44:38 +0400247 name string
248 kind Kind
249 advanced bool
gio36b23b32024-08-25 12:20:54 +0400250 meta map[string]string
gio44f621b2024-04-29 09:44:38 +0400251}
252
253func (s basicSchema) Name() string {
254 return s.name
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400255}
256
257func (s basicSchema) Kind() Kind {
258 return s.kind
259}
260
gio44f621b2024-04-29 09:44:38 +0400261func (s basicSchema) Fields() []Field {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400262 return nil
263}
264
gio44f621b2024-04-29 09:44:38 +0400265func (s basicSchema) Advanced() bool {
266 return s.advanced
267}
268
gio36b23b32024-08-25 12:20:54 +0400269func (s basicSchema) Meta() map[string]string {
270 return s.meta
271}
272
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400273type structSchema struct {
gio44f621b2024-04-29 09:44:38 +0400274 name string
275 fields []Field
276 advanced bool
277}
278
279func (s structSchema) Name() string {
280 return s.name
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400281}
282
283func (s structSchema) Kind() Kind {
284 return KindStruct
285}
286
gio44f621b2024-04-29 09:44:38 +0400287func (s structSchema) Fields() []Field {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400288 return s.fields
289}
290
gio44f621b2024-04-29 09:44:38 +0400291func (s structSchema) Advanced() bool {
292 return s.advanced
293}
294
gio36b23b32024-08-25 12:20:54 +0400295func (s structSchema) Meta() map[string]string {
296 return map[string]string{}
297}
298
gio44f621b2024-04-29 09:44:38 +0400299func NewCueSchema(name string, v cue.Value) (Schema, error) {
300 nameAttr := v.Attribute("name")
301 if nameAttr.Err() == nil {
302 name = nameAttr.Contents()
303 }
gioefa0ed42024-06-13 12:31:43 +0400304 role := ""
305 roleAttr := v.Attribute("role")
306 if roleAttr.Err() == nil {
307 role = strings.ToLower(roleAttr.Contents())
308 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400309 switch v.IncompleteKind() {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400310 case cue.StringKind:
gio6481c902025-05-20 16:16:30 +0400311 if role == "password" {
312 // TODO(gio): implement configurable requirements such as min-length, ...
313 return basicSchema{name, KindPassword, false, nil}, nil
gioe65d9a92025-06-19 09:02:32 +0400314 } else if role == "sketch-session-id" {
315 return basicSchema{name, KindSketchSessionId, false, nil}, nil
gio6481c902025-05-20 16:16:30 +0400316 } else if role == "vpnauthkey" {
gio36b23b32024-08-25 12:20:54 +0400317 meta := map[string]string{}
gio7fbd4ad2024-08-27 10:06:39 +0400318 usernameFieldAttr := v.Attribute("usernameField")
319 if usernameFieldAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400320 meta["usernameField"] = usernameFieldAttr.Contents()
gio7fbd4ad2024-08-27 10:06:39 +0400321 }
322 usernameAttr := v.Attribute("username")
323 if usernameAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400324 meta["username"] = usernameAttr.Contents()
gio7fbd4ad2024-08-27 10:06:39 +0400325 }
326 if len(meta) != 1 {
327 return nil, fmt.Errorf("invalid vpn auth key field meta: %+v", meta)
328 }
gio29f6b872024-09-08 16:14:58 +0400329 enabledFieldAttr := v.Attribute("enabledField")
330 if enabledFieldAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400331 meta["enabledField"] = enabledFieldAttr.Contents()
gio29f6b872024-09-08 16:14:58 +0400332 }
gio36b23b32024-08-25 12:20:54 +0400333 return basicSchema{name, KindVPNAuthKey, true, meta}, nil
334 } else {
335 return basicSchema{name, KindString, false, nil}, nil
336 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400337 case cue.BoolKind:
gio36b23b32024-08-25 12:20:54 +0400338 return basicSchema{name, KindBoolean, false, nil}, nil
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400339 case cue.NumberKind:
gio36b23b32024-08-25 12:20:54 +0400340 return basicSchema{name, KindNumber, false, nil}, nil
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400341 case cue.IntKind:
gioefa0ed42024-06-13 12:31:43 +0400342 if role == "port" {
gio36b23b32024-08-25 12:20:54 +0400343 return basicSchema{name, KindPort, true, nil}, nil
gioefa0ed42024-06-13 12:31:43 +0400344 } else {
gio36b23b32024-08-25 12:20:54 +0400345 return basicSchema{name, KindInt, false, nil}, nil
gioefa0ed42024-06-13 12:31:43 +0400346 }
gioe72b54f2024-04-22 10:44:41 +0400347 case cue.ListKind:
gio4ece99c2024-07-18 11:05:50 +0400348 if isMultiNetwork(v) {
gio36b23b32024-08-25 12:20:54 +0400349 return basicSchema{name, KindMultiNetwork, false, nil}, nil
gio4ece99c2024-07-18 11:05:50 +0400350 }
gio36b23b32024-08-25 12:20:54 +0400351 return basicSchema{name, KindArrayString, false, nil}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400352 case cue.StructKind:
353 if isNetwork(v) {
gio36b23b32024-08-25 12:20:54 +0400354 return basicSchema{name, KindNetwork, false, nil}, nil
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400355 } else if isAuth(v) {
gio36b23b32024-08-25 12:20:54 +0400356 return basicSchema{name, KindAuth, false, nil}, nil
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400357 } else if isSSHKey(v) {
gio36b23b32024-08-25 12:20:54 +0400358 return basicSchema{name, KindSSHKey, true, nil}, nil
giof6ad2982024-08-23 17:42:49 +0400359 } else if isCluster(v) {
360 return basicSchema{name, KindCluster, false, nil}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400361 }
gio44f621b2024-04-29 09:44:38 +0400362 s := structSchema{name, make([]Field, 0), false}
giof6ad2982024-08-23 17:42:49 +0400363 f, err := v.Fields(cue.All())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400364 if err != nil {
365 return nil, err
366 }
367 for f.Next() {
gio44f621b2024-04-29 09:44:38 +0400368 scm, err := NewCueSchema(f.Selector().String(), f.Value())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400369 if err != nil {
370 return nil, err
371 }
giof6ad2982024-08-23 17:42:49 +0400372 s.fields = append(s.fields, Field{cleanFieldName(f.Selector().String()), scm})
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400373 }
374 return s, nil
375 default:
giof15b9da2024-09-19 06:59:16 +0400376 return nil, fmt.Errorf("SHOULD NOT REACH! field: %s, value: %s", name, v)
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400377 }
378}
giof6ad2982024-08-23 17:42:49 +0400379
giofc441e32024-11-11 16:26:14 +0400380func ExtractDefaultValues(v cue.Value) (any, error) {
381 switch v.IncompleteKind() {
382 case cue.StringKind:
gio842db3f2025-05-30 11:57:20 +0400383 if d, ok := v.Default(); ok || d.IsConcrete() {
giofc441e32024-11-11 16:26:14 +0400384 return d.String()
385 }
386 case cue.BoolKind:
387 if d, ok := v.Default(); ok {
388 return d.Bool()
389 }
390 case cue.NumberKind:
391 // TODO(gio): handle numbers
392 return nil, fmt.Errorf("implement: %s", v)
393 case cue.IntKind:
gio838bcb82025-05-15 19:39:04 +0400394 if d, ok := v.Default(); ok || d.IsConcrete() {
giofc441e32024-11-11 16:26:14 +0400395 return d.Int64()
396 }
397 case cue.ListKind:
398 // TODO(gio): handle lists
399 return nil, nil
400 case cue.StructKind:
401 // TODO(gio): Such fields might have default values as well?
402 if isNetwork(v) {
403 return nil, nil
404 } else if isAuth(v) {
405 return nil, nil
giofc441e32024-11-11 16:26:14 +0400406 } else if isCluster(v) {
407 return nil, nil
408 }
409 ret := map[string]any{}
410 f, err := v.Fields(cue.All())
411 if err != nil {
412 return nil, err
413 }
414 for f.Next() {
415 fv, err := ExtractDefaultValues(f.Value())
416 if err != nil {
417 return nil, err
418 }
gio842db3f2025-05-30 11:57:20 +0400419 if fv != nil {
420 ret[f.Selector().String()] = fv
421 }
422 }
423 if len(ret) == 0 {
424 return nil, nil
giofc441e32024-11-11 16:26:14 +0400425 }
426 return ret, nil
427 default:
428 return nil, fmt.Errorf("SHOULD NOT REACH! value: %s", v)
429 }
430 return nil, nil
431}
432
giof6ad2982024-08-23 17:42:49 +0400433func cleanFieldName(name string) string {
434 return strings.ReplaceAll(strings.ReplaceAll(name, "?", ""), "!", "")
435}