blob: 7f8410f28490386a968a79234ecb9459ed547c1f [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
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040028)
29
gio44f621b2024-04-29 09:44:38 +040030type Field struct {
31 Name string
32 Schema Schema
33}
34
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040035type Schema interface {
gio44f621b2024-04-29 09:44:38 +040036 Name() string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040037 Kind() Kind
gio44f621b2024-04-29 09:44:38 +040038 Fields() []Field
39 Advanced() bool
gio36b23b32024-08-25 12:20:54 +040040 Meta() map[string]string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040041}
42
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040043var AuthSchema Schema = structSchema{
gio44f621b2024-04-29 09:44:38 +040044 name: "Auth",
45 fields: []Field{
gio36b23b32024-08-25 12:20:54 +040046 Field{"enabled", basicSchema{"Enabled", KindBoolean, false, nil}},
47 Field{"groups", basicSchema{"Groups", KindString, false, nil}},
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040048 },
gio44f621b2024-04-29 09:44:38 +040049 advanced: false,
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040050}
51
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040052var SSHKeySchema Schema = structSchema{
gio44f621b2024-04-29 09:44:38 +040053 name: "SSH Key",
54 fields: []Field{
gio36b23b32024-08-25 12:20:54 +040055 Field{"public", basicSchema{"Public Key", KindString, false, nil}},
56 Field{"private", basicSchema{"Private Key", KindString, false, nil}},
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040057 },
gio44f621b2024-04-29 09:44:38 +040058 advanced: true,
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040059}
60
giof6ad2982024-08-23 17:42:49 +040061const clusterSchema = `
62#Cluster: {
63 name: string
64 kubeconfig: string
65 ingressClassName: string
66}
giof15b9da2024-09-19 06:59:16 +040067value: #Cluster
gio8f290322024-09-21 15:37:45 +040068
69#Schema: %s
70value: #Schema
giof6ad2982024-08-23 17:42:49 +040071`
72
73func isCluster(v cue.Value) bool {
74 if v.Value().Kind() != cue.StructKind {
75 return false
76 }
giof15b9da2024-09-19 06:59:16 +040077 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
78 if err != nil {
79 return false
80 }
81 s := fmt.Sprintf(clusterSchema, string(vb))
giof6ad2982024-08-23 17:42:49 +040082 c := cuecontext.New()
83 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +040084 if err := u.Err(); err != nil {
85 return false
86 }
giof6ad2982024-08-23 17:42:49 +040087 if err := u.Validate(); err != nil {
88 return false
89 }
giof15b9da2024-09-19 06:59:16 +040090 if err := u.Eval().Err(); err != nil {
91 return false
giof6ad2982024-08-23 17:42:49 +040092 }
giof15b9da2024-09-19 06:59:16 +040093 return true
giof6ad2982024-08-23 17:42:49 +040094}
95
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040096const networkSchema = `
97#Network: {
gio8f290322024-09-21 15:37:45 +040098 name: string
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040099 ingressClass: string
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400100 certificateIssuer: string | *""
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400101 domain: string
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400102 allocatePortAddr: string
gioefa0ed42024-06-13 12:31:43 +0400103 reservePortAddr: string
giocdfa3722024-06-13 20:10:14 +0400104 deallocatePortAddr: string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400105}
giof15b9da2024-09-19 06:59:16 +0400106value: #Network
gio8f290322024-09-21 15:37:45 +0400107
108#Schema: %s
109value: #Schema
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400110`
111
112func isNetwork(v cue.Value) bool {
113 if v.Value().Kind() != cue.StructKind {
114 return false
115 }
giof15b9da2024-09-19 06:59:16 +0400116 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
117 if err != nil {
118 return false
119 }
120 s := fmt.Sprintf(networkSchema, string(vb))
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400121 c := cuecontext.New()
122 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400123 if err := u.Err(); err != nil {
124 return false
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400125 }
giof15b9da2024-09-19 06:59:16 +0400126 if err := u.Validate(); err != nil {
127 return false
128 }
129 if err := u.Eval().Err(); err != nil {
130 return false
131 }
132 return true
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400133}
134
gio4ece99c2024-07-18 11:05:50 +0400135const multiNetworkSchema = `
136#Network: {
137 name: string
138 ingressClass: string
139 certificateIssuer: string | *""
140 domain: string
141 allocatePortAddr: string
142 reservePortAddr: string
143 deallocatePortAddr: string
144}
gio4ece99c2024-07-18 11:05:50 +0400145#Networks: [...#Network]
giof15b9da2024-09-19 06:59:16 +0400146value: #Networks
gio8f290322024-09-21 15:37:45 +0400147
148#Schema: %s
149value: #Schema
gio4ece99c2024-07-18 11:05:50 +0400150`
151
152func isMultiNetwork(v cue.Value) bool {
153 if v.Value().IncompleteKind() != cue.ListKind {
154 return false
155 }
giof15b9da2024-09-19 06:59:16 +0400156 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
157 if err != nil {
158 return false
159 }
160 s := fmt.Sprintf(multiNetworkSchema, string(vb))
gio4ece99c2024-07-18 11:05:50 +0400161 c := cuecontext.New()
162 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400163 if err := u.Err(); err != nil {
164 return false
gio4ece99c2024-07-18 11:05:50 +0400165 }
giof15b9da2024-09-19 06:59:16 +0400166 if err := u.Validate(); err != nil {
167 return false
168 }
169 if err := u.Eval().Err(); err != nil {
170 return false
171 }
172 return true
gio4ece99c2024-07-18 11:05:50 +0400173}
174
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400175const authSchema = `
176#Auth: {
177 enabled: bool | false
178 groups: string | *""
179}
giof15b9da2024-09-19 06:59:16 +0400180value: #Auth
gio8f290322024-09-21 15:37:45 +0400181
182#Schema: %s
183value: #Schema
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400184`
185
186func isAuth(v cue.Value) bool {
187 if v.Value().Kind() != cue.StructKind {
188 return false
189 }
giof15b9da2024-09-19 06:59:16 +0400190 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
191 if err != nil {
192 return false
193 }
194 s := fmt.Sprintf(authSchema, string(vb))
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400195 c := cuecontext.New()
196 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400197 if err := u.Err(); err != nil {
198 return false
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400199 }
giof15b9da2024-09-19 06:59:16 +0400200 if err := u.Validate(); err != nil {
201 return false
202 }
203 if err := u.Eval().Err(); err != nil {
204 return false
205 }
206 return true
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400207}
208
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400209const sshKeySchema = `
210#SSHKey: {
211 public: string
212 private: string
213}
giof15b9da2024-09-19 06:59:16 +0400214value: #SSHKey
gio8f290322024-09-21 15:37:45 +0400215
216#Schema: %s
217value: #Schema
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400218`
219
220func isSSHKey(v cue.Value) bool {
221 if v.Value().Kind() != cue.StructKind {
222 return false
223 }
giof15b9da2024-09-19 06:59:16 +0400224 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
225 if err != nil {
226 return false
227 }
228 s := fmt.Sprintf(sshKeySchema, string(vb))
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400229 c := cuecontext.New()
230 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400231 if err := u.Err(); err != nil {
232 return false
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400233 }
giof15b9da2024-09-19 06:59:16 +0400234 if err := u.Validate(); err != nil {
235 return false
236 }
237 if err := u.Eval().Err(); err != nil {
238 return false
239 }
240 return true
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400241}
242
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400243type basicSchema struct {
gio44f621b2024-04-29 09:44:38 +0400244 name string
245 kind Kind
246 advanced bool
gio36b23b32024-08-25 12:20:54 +0400247 meta map[string]string
gio44f621b2024-04-29 09:44:38 +0400248}
249
250func (s basicSchema) Name() string {
251 return s.name
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400252}
253
254func (s basicSchema) Kind() Kind {
255 return s.kind
256}
257
gio44f621b2024-04-29 09:44:38 +0400258func (s basicSchema) Fields() []Field {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400259 return nil
260}
261
gio44f621b2024-04-29 09:44:38 +0400262func (s basicSchema) Advanced() bool {
263 return s.advanced
264}
265
gio36b23b32024-08-25 12:20:54 +0400266func (s basicSchema) Meta() map[string]string {
267 return s.meta
268}
269
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400270type structSchema struct {
gio44f621b2024-04-29 09:44:38 +0400271 name string
272 fields []Field
273 advanced bool
274}
275
276func (s structSchema) Name() string {
277 return s.name
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400278}
279
280func (s structSchema) Kind() Kind {
281 return KindStruct
282}
283
gio44f621b2024-04-29 09:44:38 +0400284func (s structSchema) Fields() []Field {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400285 return s.fields
286}
287
gio44f621b2024-04-29 09:44:38 +0400288func (s structSchema) Advanced() bool {
289 return s.advanced
290}
291
gio36b23b32024-08-25 12:20:54 +0400292func (s structSchema) Meta() map[string]string {
293 return map[string]string{}
294}
295
gio44f621b2024-04-29 09:44:38 +0400296func NewCueSchema(name string, v cue.Value) (Schema, error) {
297 nameAttr := v.Attribute("name")
298 if nameAttr.Err() == nil {
299 name = nameAttr.Contents()
300 }
gioefa0ed42024-06-13 12:31:43 +0400301 role := ""
302 roleAttr := v.Attribute("role")
303 if roleAttr.Err() == nil {
304 role = strings.ToLower(roleAttr.Contents())
305 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400306 switch v.IncompleteKind() {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400307 case cue.StringKind:
gio36b23b32024-08-25 12:20:54 +0400308 if role == "vpnauthkey" {
309 meta := map[string]string{}
gio7fbd4ad2024-08-27 10:06:39 +0400310 usernameFieldAttr := v.Attribute("usernameField")
311 if usernameFieldAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400312 meta["usernameField"] = usernameFieldAttr.Contents()
gio7fbd4ad2024-08-27 10:06:39 +0400313 }
314 usernameAttr := v.Attribute("username")
315 if usernameAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400316 meta["username"] = usernameAttr.Contents()
gio7fbd4ad2024-08-27 10:06:39 +0400317 }
318 if len(meta) != 1 {
319 return nil, fmt.Errorf("invalid vpn auth key field meta: %+v", meta)
320 }
gio29f6b872024-09-08 16:14:58 +0400321 enabledFieldAttr := v.Attribute("enabledField")
322 if enabledFieldAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400323 meta["enabledField"] = enabledFieldAttr.Contents()
gio29f6b872024-09-08 16:14:58 +0400324 }
gio36b23b32024-08-25 12:20:54 +0400325 return basicSchema{name, KindVPNAuthKey, true, meta}, nil
326 } else {
327 return basicSchema{name, KindString, false, nil}, nil
328 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400329 case cue.BoolKind:
gio36b23b32024-08-25 12:20:54 +0400330 return basicSchema{name, KindBoolean, false, nil}, nil
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400331 case cue.NumberKind:
gio36b23b32024-08-25 12:20:54 +0400332 return basicSchema{name, KindNumber, false, nil}, nil
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400333 case cue.IntKind:
gioefa0ed42024-06-13 12:31:43 +0400334 if role == "port" {
gio36b23b32024-08-25 12:20:54 +0400335 return basicSchema{name, KindPort, true, nil}, nil
gioefa0ed42024-06-13 12:31:43 +0400336 } else {
gio36b23b32024-08-25 12:20:54 +0400337 return basicSchema{name, KindInt, false, nil}, nil
gioefa0ed42024-06-13 12:31:43 +0400338 }
gioe72b54f2024-04-22 10:44:41 +0400339 case cue.ListKind:
gio4ece99c2024-07-18 11:05:50 +0400340 if isMultiNetwork(v) {
gio36b23b32024-08-25 12:20:54 +0400341 return basicSchema{name, KindMultiNetwork, false, nil}, nil
gio4ece99c2024-07-18 11:05:50 +0400342 }
gio36b23b32024-08-25 12:20:54 +0400343 return basicSchema{name, KindArrayString, false, nil}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400344 case cue.StructKind:
345 if isNetwork(v) {
gio36b23b32024-08-25 12:20:54 +0400346 return basicSchema{name, KindNetwork, false, nil}, nil
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400347 } else if isAuth(v) {
gio36b23b32024-08-25 12:20:54 +0400348 return basicSchema{name, KindAuth, false, nil}, nil
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400349 } else if isSSHKey(v) {
gio36b23b32024-08-25 12:20:54 +0400350 return basicSchema{name, KindSSHKey, true, nil}, nil
giof6ad2982024-08-23 17:42:49 +0400351 } else if isCluster(v) {
352 return basicSchema{name, KindCluster, false, nil}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400353 }
gio44f621b2024-04-29 09:44:38 +0400354 s := structSchema{name, make([]Field, 0), false}
giof6ad2982024-08-23 17:42:49 +0400355 f, err := v.Fields(cue.All())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400356 if err != nil {
357 return nil, err
358 }
359 for f.Next() {
gio44f621b2024-04-29 09:44:38 +0400360 scm, err := NewCueSchema(f.Selector().String(), f.Value())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400361 if err != nil {
362 return nil, err
363 }
giof6ad2982024-08-23 17:42:49 +0400364 s.fields = append(s.fields, Field{cleanFieldName(f.Selector().String()), scm})
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400365 }
366 return s, nil
367 default:
giof15b9da2024-09-19 06:59:16 +0400368 return nil, fmt.Errorf("SHOULD NOT REACH! field: %s, value: %s", name, v)
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400369 }
370}
giof6ad2982024-08-23 17:42:49 +0400371
372func cleanFieldName(name string) string {
373 return strings.ReplaceAll(strings.ReplaceAll(name, "?", ""), "!", "")
374}