blob: 3c9e2374eaa2574579e2f2ced2e7e393510d1a90 [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}
67
giof15b9da2024-09-19 06:59:16 +040068value: #Cluster
giof6ad2982024-08-23 17:42:49 +040069value: { %s }
70`
71
72func isCluster(v cue.Value) bool {
73 if v.Value().Kind() != cue.StructKind {
74 return false
75 }
giof15b9da2024-09-19 06:59:16 +040076 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
77 if err != nil {
78 return false
79 }
80 s := fmt.Sprintf(clusterSchema, string(vb))
giof6ad2982024-08-23 17:42:49 +040081 c := cuecontext.New()
82 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +040083 if err := u.Err(); err != nil {
84 return false
85 }
giof6ad2982024-08-23 17:42:49 +040086 if err := u.Validate(); err != nil {
87 return false
88 }
giof15b9da2024-09-19 06:59:16 +040089 if err := u.Eval().Err(); err != nil {
90 return false
giof6ad2982024-08-23 17:42:49 +040091 }
giof15b9da2024-09-19 06:59:16 +040092 return true
giof6ad2982024-08-23 17:42:49 +040093}
94
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040095const networkSchema = `
96#Network: {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040097 name: string
98 ingressClass: string
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040099 certificateIssuer: string | *""
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400100 domain: string
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400101 allocatePortAddr: string
gioefa0ed42024-06-13 12:31:43 +0400102 reservePortAddr: string
giocdfa3722024-06-13 20:10:14 +0400103 deallocatePortAddr: string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400104}
105
giof15b9da2024-09-19 06:59:16 +0400106value: #Network
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400107value: { %s }
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400108`
109
110func isNetwork(v cue.Value) bool {
111 if v.Value().Kind() != cue.StructKind {
112 return false
113 }
giof15b9da2024-09-19 06:59:16 +0400114 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
115 if err != nil {
116 return false
117 }
118 s := fmt.Sprintf(networkSchema, string(vb))
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400119 c := cuecontext.New()
120 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400121 if err := u.Err(); err != nil {
122 return false
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400123 }
giof15b9da2024-09-19 06:59:16 +0400124 if err := u.Validate(); err != nil {
125 return false
126 }
127 if err := u.Eval().Err(); err != nil {
128 return false
129 }
130 return true
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400131}
132
gio4ece99c2024-07-18 11:05:50 +0400133const multiNetworkSchema = `
134#Network: {
135 name: string
136 ingressClass: string
137 certificateIssuer: string | *""
138 domain: string
139 allocatePortAddr: string
140 reservePortAddr: string
141 deallocatePortAddr: string
142}
143
144#Networks: [...#Network]
145
giof15b9da2024-09-19 06:59:16 +0400146value: #Networks
gio4ece99c2024-07-18 11:05:50 +0400147value: %s
148`
149
150func isMultiNetwork(v cue.Value) bool {
151 if v.Value().IncompleteKind() != cue.ListKind {
152 return false
153 }
giof15b9da2024-09-19 06:59:16 +0400154 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
155 if err != nil {
156 return false
157 }
158 s := fmt.Sprintf(multiNetworkSchema, string(vb))
gio4ece99c2024-07-18 11:05:50 +0400159 c := cuecontext.New()
160 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400161 if err := u.Err(); err != nil {
162 return false
gio4ece99c2024-07-18 11:05:50 +0400163 }
giof15b9da2024-09-19 06:59:16 +0400164 if err := u.Validate(); err != nil {
165 return false
166 }
167 if err := u.Eval().Err(); err != nil {
168 return false
169 }
170 return true
gio4ece99c2024-07-18 11:05:50 +0400171}
172
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400173const authSchema = `
174#Auth: {
175 enabled: bool | false
176 groups: string | *""
177}
178
giof15b9da2024-09-19 06:59:16 +0400179value: #Auth
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400180value: { %s }
181`
182
183func isAuth(v cue.Value) bool {
184 if v.Value().Kind() != cue.StructKind {
185 return false
186 }
giof15b9da2024-09-19 06:59:16 +0400187 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
188 if err != nil {
189 return false
190 }
191 s := fmt.Sprintf(authSchema, string(vb))
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400192 c := cuecontext.New()
193 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400194 if err := u.Err(); err != nil {
195 return false
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400196 }
giof15b9da2024-09-19 06:59:16 +0400197 if err := u.Validate(); err != nil {
198 return false
199 }
200 if err := u.Eval().Err(); err != nil {
201 return false
202 }
203 return true
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400204}
205
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400206const sshKeySchema = `
207#SSHKey: {
208 public: string
209 private: string
210}
211
giof15b9da2024-09-19 06:59:16 +0400212value: #SSHKey
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400213value: { %s }
214`
215
216func isSSHKey(v cue.Value) bool {
217 if v.Value().Kind() != cue.StructKind {
218 return false
219 }
giof15b9da2024-09-19 06:59:16 +0400220 vb, err := format.Node(v.Syntax(cue.All()), format.TabIndent(true))
221 if err != nil {
222 return false
223 }
224 s := fmt.Sprintf(sshKeySchema, string(vb))
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400225 c := cuecontext.New()
226 u := c.CompileString(s)
giof15b9da2024-09-19 06:59:16 +0400227 if err := u.Err(); err != nil {
228 return false
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400229 }
giof15b9da2024-09-19 06:59:16 +0400230 if err := u.Validate(); err != nil {
231 return false
232 }
233 if err := u.Eval().Err(); err != nil {
234 return false
235 }
236 return true
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400237}
238
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400239type basicSchema struct {
gio44f621b2024-04-29 09:44:38 +0400240 name string
241 kind Kind
242 advanced bool
gio36b23b32024-08-25 12:20:54 +0400243 meta map[string]string
gio44f621b2024-04-29 09:44:38 +0400244}
245
246func (s basicSchema) Name() string {
247 return s.name
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400248}
249
250func (s basicSchema) Kind() Kind {
251 return s.kind
252}
253
gio44f621b2024-04-29 09:44:38 +0400254func (s basicSchema) Fields() []Field {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400255 return nil
256}
257
gio44f621b2024-04-29 09:44:38 +0400258func (s basicSchema) Advanced() bool {
259 return s.advanced
260}
261
gio36b23b32024-08-25 12:20:54 +0400262func (s basicSchema) Meta() map[string]string {
263 return s.meta
264}
265
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400266type structSchema struct {
gio44f621b2024-04-29 09:44:38 +0400267 name string
268 fields []Field
269 advanced bool
270}
271
272func (s structSchema) Name() string {
273 return s.name
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400274}
275
276func (s structSchema) Kind() Kind {
277 return KindStruct
278}
279
gio44f621b2024-04-29 09:44:38 +0400280func (s structSchema) Fields() []Field {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400281 return s.fields
282}
283
gio44f621b2024-04-29 09:44:38 +0400284func (s structSchema) Advanced() bool {
285 return s.advanced
286}
287
gio36b23b32024-08-25 12:20:54 +0400288func (s structSchema) Meta() map[string]string {
289 return map[string]string{}
290}
291
gio44f621b2024-04-29 09:44:38 +0400292func NewCueSchema(name string, v cue.Value) (Schema, error) {
293 nameAttr := v.Attribute("name")
294 if nameAttr.Err() == nil {
295 name = nameAttr.Contents()
296 }
gioefa0ed42024-06-13 12:31:43 +0400297 role := ""
298 roleAttr := v.Attribute("role")
299 if roleAttr.Err() == nil {
300 role = strings.ToLower(roleAttr.Contents())
301 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400302 switch v.IncompleteKind() {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400303 case cue.StringKind:
gio36b23b32024-08-25 12:20:54 +0400304 if role == "vpnauthkey" {
305 meta := map[string]string{}
gio7fbd4ad2024-08-27 10:06:39 +0400306 usernameFieldAttr := v.Attribute("usernameField")
307 if usernameFieldAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400308 meta["usernameField"] = usernameFieldAttr.Contents()
gio7fbd4ad2024-08-27 10:06:39 +0400309 }
310 usernameAttr := v.Attribute("username")
311 if usernameAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400312 meta["username"] = usernameAttr.Contents()
gio7fbd4ad2024-08-27 10:06:39 +0400313 }
314 if len(meta) != 1 {
315 return nil, fmt.Errorf("invalid vpn auth key field meta: %+v", meta)
316 }
gio29f6b872024-09-08 16:14:58 +0400317 enabledFieldAttr := v.Attribute("enabledField")
318 if enabledFieldAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400319 meta["enabledField"] = enabledFieldAttr.Contents()
gio29f6b872024-09-08 16:14:58 +0400320 }
gio36b23b32024-08-25 12:20:54 +0400321 return basicSchema{name, KindVPNAuthKey, true, meta}, nil
322 } else {
323 return basicSchema{name, KindString, false, nil}, nil
324 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400325 case cue.BoolKind:
gio36b23b32024-08-25 12:20:54 +0400326 return basicSchema{name, KindBoolean, false, nil}, nil
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400327 case cue.NumberKind:
gio36b23b32024-08-25 12:20:54 +0400328 return basicSchema{name, KindNumber, false, nil}, nil
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400329 case cue.IntKind:
gioefa0ed42024-06-13 12:31:43 +0400330 if role == "port" {
gio36b23b32024-08-25 12:20:54 +0400331 return basicSchema{name, KindPort, true, nil}, nil
gioefa0ed42024-06-13 12:31:43 +0400332 } else {
gio36b23b32024-08-25 12:20:54 +0400333 return basicSchema{name, KindInt, false, nil}, nil
gioefa0ed42024-06-13 12:31:43 +0400334 }
gioe72b54f2024-04-22 10:44:41 +0400335 case cue.ListKind:
gio4ece99c2024-07-18 11:05:50 +0400336 if isMultiNetwork(v) {
gio36b23b32024-08-25 12:20:54 +0400337 return basicSchema{name, KindMultiNetwork, false, nil}, nil
gio4ece99c2024-07-18 11:05:50 +0400338 }
gio36b23b32024-08-25 12:20:54 +0400339 return basicSchema{name, KindArrayString, false, nil}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400340 case cue.StructKind:
341 if isNetwork(v) {
gio36b23b32024-08-25 12:20:54 +0400342 return basicSchema{name, KindNetwork, false, nil}, nil
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400343 } else if isAuth(v) {
gio36b23b32024-08-25 12:20:54 +0400344 return basicSchema{name, KindAuth, false, nil}, nil
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400345 } else if isSSHKey(v) {
gio36b23b32024-08-25 12:20:54 +0400346 return basicSchema{name, KindSSHKey, true, nil}, nil
giof6ad2982024-08-23 17:42:49 +0400347 } else if isCluster(v) {
348 return basicSchema{name, KindCluster, false, nil}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400349 }
gio44f621b2024-04-29 09:44:38 +0400350 s := structSchema{name, make([]Field, 0), false}
giof6ad2982024-08-23 17:42:49 +0400351 f, err := v.Fields(cue.All())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400352 if err != nil {
353 return nil, err
354 }
355 for f.Next() {
gio44f621b2024-04-29 09:44:38 +0400356 scm, err := NewCueSchema(f.Selector().String(), f.Value())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400357 if err != nil {
358 return nil, err
359 }
giof6ad2982024-08-23 17:42:49 +0400360 s.fields = append(s.fields, Field{cleanFieldName(f.Selector().String()), scm})
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400361 }
362 return s, nil
363 default:
giof15b9da2024-09-19 06:59:16 +0400364 return nil, fmt.Errorf("SHOULD NOT REACH! field: %s, value: %s", name, v)
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400365 }
366}
giof6ad2982024-08-23 17:42:49 +0400367
368func cleanFieldName(name string) string {
369 return strings.ReplaceAll(strings.ReplaceAll(name, "?", ""), "!", "")
370}