blob: e575aa56443432b08aed9a9b6d96ab0bffd442da [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 | *""
181}
giof15b9da2024-09-19 06:59:16 +0400182value: #Auth
gio8f290322024-09-21 15:37:45 +0400183
184#Schema: %s
185value: #Schema
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400186`
187
188func isAuth(v cue.Value) bool {
189 if v.Value().Kind() != cue.StructKind {
190 return false
191 }
giof15b9da2024-09-19 06:59:16 +0400192 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
193 if err != nil {
194 return false
195 }
196 s := fmt.Sprintf(authSchema, string(vb))
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400197 c := cuecontext.New()
198 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400199 if err := u.Err(); err != nil {
200 return false
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400201 }
giof15b9da2024-09-19 06:59:16 +0400202 if err := u.Validate(); err != nil {
203 return false
204 }
205 if err := u.Eval().Err(); err != nil {
206 return false
207 }
208 return true
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400209}
210
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400211const sshKeySchema = `
212#SSHKey: {
213 public: string
214 private: string
215}
giof15b9da2024-09-19 06:59:16 +0400216value: #SSHKey
gio8f290322024-09-21 15:37:45 +0400217
218#Schema: %s
219value: #Schema
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400220`
221
222func isSSHKey(v cue.Value) bool {
223 if v.Value().Kind() != cue.StructKind {
224 return false
225 }
giof15b9da2024-09-19 06:59:16 +0400226 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
227 if err != nil {
228 return false
229 }
230 s := fmt.Sprintf(sshKeySchema, string(vb))
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400231 c := cuecontext.New()
232 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400233 if err := u.Err(); err != nil {
234 return false
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400235 }
giof15b9da2024-09-19 06:59:16 +0400236 if err := u.Validate(); err != nil {
237 return false
238 }
239 if err := u.Eval().Err(); err != nil {
240 return false
241 }
242 return true
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400243}
244
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400245type basicSchema struct {
gio44f621b2024-04-29 09:44:38 +0400246 name string
247 kind Kind
248 advanced bool
gio36b23b32024-08-25 12:20:54 +0400249 meta map[string]string
gio44f621b2024-04-29 09:44:38 +0400250}
251
252func (s basicSchema) Name() string {
253 return s.name
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400254}
255
256func (s basicSchema) Kind() Kind {
257 return s.kind
258}
259
gio44f621b2024-04-29 09:44:38 +0400260func (s basicSchema) Fields() []Field {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400261 return nil
262}
263
gio44f621b2024-04-29 09:44:38 +0400264func (s basicSchema) Advanced() bool {
265 return s.advanced
266}
267
gio36b23b32024-08-25 12:20:54 +0400268func (s basicSchema) Meta() map[string]string {
269 return s.meta
270}
271
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400272type structSchema struct {
gio44f621b2024-04-29 09:44:38 +0400273 name string
274 fields []Field
275 advanced bool
276}
277
278func (s structSchema) Name() string {
279 return s.name
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400280}
281
282func (s structSchema) Kind() Kind {
283 return KindStruct
284}
285
gio44f621b2024-04-29 09:44:38 +0400286func (s structSchema) Fields() []Field {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400287 return s.fields
288}
289
gio44f621b2024-04-29 09:44:38 +0400290func (s structSchema) Advanced() bool {
291 return s.advanced
292}
293
gio36b23b32024-08-25 12:20:54 +0400294func (s structSchema) Meta() map[string]string {
295 return map[string]string{}
296}
297
gio44f621b2024-04-29 09:44:38 +0400298func NewCueSchema(name string, v cue.Value) (Schema, error) {
299 nameAttr := v.Attribute("name")
300 if nameAttr.Err() == nil {
301 name = nameAttr.Contents()
302 }
gioefa0ed42024-06-13 12:31:43 +0400303 role := ""
304 roleAttr := v.Attribute("role")
305 if roleAttr.Err() == nil {
306 role = strings.ToLower(roleAttr.Contents())
307 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400308 switch v.IncompleteKind() {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400309 case cue.StringKind:
gio6481c902025-05-20 16:16:30 +0400310 if role == "password" {
311 // TODO(gio): implement configurable requirements such as min-length, ...
312 return basicSchema{name, KindPassword, false, nil}, nil
gioe65d9a92025-06-19 09:02:32 +0400313 } else if role == "sketch-session-id" {
314 return basicSchema{name, KindSketchSessionId, false, nil}, nil
gio6481c902025-05-20 16:16:30 +0400315 } else if role == "vpnauthkey" {
gio36b23b32024-08-25 12:20:54 +0400316 meta := map[string]string{}
gio7fbd4ad2024-08-27 10:06:39 +0400317 usernameFieldAttr := v.Attribute("usernameField")
318 if usernameFieldAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400319 meta["usernameField"] = usernameFieldAttr.Contents()
gio7fbd4ad2024-08-27 10:06:39 +0400320 }
321 usernameAttr := v.Attribute("username")
322 if usernameAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400323 meta["username"] = usernameAttr.Contents()
gio7fbd4ad2024-08-27 10:06:39 +0400324 }
325 if len(meta) != 1 {
326 return nil, fmt.Errorf("invalid vpn auth key field meta: %+v", meta)
327 }
gio29f6b872024-09-08 16:14:58 +0400328 enabledFieldAttr := v.Attribute("enabledField")
329 if enabledFieldAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400330 meta["enabledField"] = enabledFieldAttr.Contents()
gio29f6b872024-09-08 16:14:58 +0400331 }
gio36b23b32024-08-25 12:20:54 +0400332 return basicSchema{name, KindVPNAuthKey, true, meta}, nil
333 } else {
334 return basicSchema{name, KindString, false, nil}, nil
335 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400336 case cue.BoolKind:
gio36b23b32024-08-25 12:20:54 +0400337 return basicSchema{name, KindBoolean, false, nil}, nil
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400338 case cue.NumberKind:
gio36b23b32024-08-25 12:20:54 +0400339 return basicSchema{name, KindNumber, false, nil}, nil
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400340 case cue.IntKind:
gioefa0ed42024-06-13 12:31:43 +0400341 if role == "port" {
gio36b23b32024-08-25 12:20:54 +0400342 return basicSchema{name, KindPort, true, nil}, nil
gioefa0ed42024-06-13 12:31:43 +0400343 } else {
gio36b23b32024-08-25 12:20:54 +0400344 return basicSchema{name, KindInt, false, nil}, nil
gioefa0ed42024-06-13 12:31:43 +0400345 }
gioe72b54f2024-04-22 10:44:41 +0400346 case cue.ListKind:
gio4ece99c2024-07-18 11:05:50 +0400347 if isMultiNetwork(v) {
gio36b23b32024-08-25 12:20:54 +0400348 return basicSchema{name, KindMultiNetwork, false, nil}, nil
gio4ece99c2024-07-18 11:05:50 +0400349 }
gio36b23b32024-08-25 12:20:54 +0400350 return basicSchema{name, KindArrayString, false, nil}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400351 case cue.StructKind:
352 if isNetwork(v) {
gio36b23b32024-08-25 12:20:54 +0400353 return basicSchema{name, KindNetwork, false, nil}, nil
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400354 } else if isAuth(v) {
gio36b23b32024-08-25 12:20:54 +0400355 return basicSchema{name, KindAuth, false, nil}, nil
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400356 } else if isSSHKey(v) {
gio36b23b32024-08-25 12:20:54 +0400357 return basicSchema{name, KindSSHKey, true, nil}, nil
giof6ad2982024-08-23 17:42:49 +0400358 } else if isCluster(v) {
359 return basicSchema{name, KindCluster, false, nil}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400360 }
gio44f621b2024-04-29 09:44:38 +0400361 s := structSchema{name, make([]Field, 0), false}
giof6ad2982024-08-23 17:42:49 +0400362 f, err := v.Fields(cue.All())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400363 if err != nil {
364 return nil, err
365 }
366 for f.Next() {
gio44f621b2024-04-29 09:44:38 +0400367 scm, err := NewCueSchema(f.Selector().String(), f.Value())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400368 if err != nil {
369 return nil, err
370 }
giof6ad2982024-08-23 17:42:49 +0400371 s.fields = append(s.fields, Field{cleanFieldName(f.Selector().String()), scm})
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400372 }
373 return s, nil
374 default:
giof15b9da2024-09-19 06:59:16 +0400375 return nil, fmt.Errorf("SHOULD NOT REACH! field: %s, value: %s", name, v)
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400376 }
377}
giof6ad2982024-08-23 17:42:49 +0400378
giofc441e32024-11-11 16:26:14 +0400379func ExtractDefaultValues(v cue.Value) (any, error) {
380 switch v.IncompleteKind() {
381 case cue.StringKind:
gio842db3f2025-05-30 11:57:20 +0400382 if d, ok := v.Default(); ok || d.IsConcrete() {
giofc441e32024-11-11 16:26:14 +0400383 return d.String()
384 }
385 case cue.BoolKind:
386 if d, ok := v.Default(); ok {
387 return d.Bool()
388 }
389 case cue.NumberKind:
390 // TODO(gio): handle numbers
391 return nil, fmt.Errorf("implement: %s", v)
392 case cue.IntKind:
gio838bcb82025-05-15 19:39:04 +0400393 if d, ok := v.Default(); ok || d.IsConcrete() {
giofc441e32024-11-11 16:26:14 +0400394 return d.Int64()
395 }
396 case cue.ListKind:
397 // TODO(gio): handle lists
398 return nil, nil
399 case cue.StructKind:
400 // TODO(gio): Such fields might have default values as well?
401 if isNetwork(v) {
402 return nil, nil
403 } else if isAuth(v) {
404 return nil, nil
giofc441e32024-11-11 16:26:14 +0400405 } else if isCluster(v) {
406 return nil, nil
407 }
408 ret := map[string]any{}
409 f, err := v.Fields(cue.All())
410 if err != nil {
411 return nil, err
412 }
413 for f.Next() {
414 fv, err := ExtractDefaultValues(f.Value())
415 if err != nil {
416 return nil, err
417 }
gio842db3f2025-05-30 11:57:20 +0400418 if fv != nil {
419 ret[f.Selector().String()] = fv
420 }
421 }
422 if len(ret) == 0 {
423 return nil, nil
giofc441e32024-11-11 16:26:14 +0400424 }
425 return ret, nil
426 default:
427 return nil, fmt.Errorf("SHOULD NOT REACH! value: %s", v)
428 }
429 return nil, nil
430}
431
giof6ad2982024-08-23 17:42:49 +0400432func cleanFieldName(name string) string {
433 return strings.ReplaceAll(strings.ReplaceAll(name, "?", ""), "!", "")
434}