blob: 5a70519f33a19744cdc25cbc82b692b620b7a722 [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"
9)
10
11type Kind int
12
13const (
gio4ece99c2024-07-18 11:05:50 +040014 KindBoolean Kind = 0
15 KindInt = 7
16 KindString = 1
17 KindStruct = 2
18 KindNetwork = 3
19 KindMultiNetwork = 10
20 KindAuth = 5
21 KindSSHKey = 6
22 KindNumber = 4
23 KindArrayString = 8
24 KindPort = 9
gio36b23b32024-08-25 12:20:54 +040025 KindVPNAuthKey = 11
giof6ad2982024-08-23 17:42:49 +040026 KindCluster = 12
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040027)
28
gio44f621b2024-04-29 09:44:38 +040029type Field struct {
30 Name string
31 Schema Schema
32}
33
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040034type Schema interface {
gio44f621b2024-04-29 09:44:38 +040035 Name() string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040036 Kind() Kind
gio44f621b2024-04-29 09:44:38 +040037 Fields() []Field
38 Advanced() bool
gio36b23b32024-08-25 12:20:54 +040039 Meta() map[string]string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040040}
41
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040042var AuthSchema Schema = structSchema{
gio44f621b2024-04-29 09:44:38 +040043 name: "Auth",
44 fields: []Field{
gio36b23b32024-08-25 12:20:54 +040045 Field{"enabled", basicSchema{"Enabled", KindBoolean, false, nil}},
46 Field{"groups", basicSchema{"Groups", KindString, false, nil}},
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040047 },
gio44f621b2024-04-29 09:44:38 +040048 advanced: false,
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040049}
50
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040051var SSHKeySchema Schema = structSchema{
gio44f621b2024-04-29 09:44:38 +040052 name: "SSH Key",
53 fields: []Field{
gio36b23b32024-08-25 12:20:54 +040054 Field{"public", basicSchema{"Public Key", KindString, false, nil}},
55 Field{"private", basicSchema{"Private Key", KindString, false, nil}},
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040056 },
gio44f621b2024-04-29 09:44:38 +040057 advanced: true,
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040058}
59
giof6ad2982024-08-23 17:42:49 +040060const clusterSchema = `
61#Cluster: {
62 name: string
63 kubeconfig: string
64 ingressClassName: string
65}
66
67value: { %s }
68`
69
70func isCluster(v cue.Value) bool {
71 if v.Value().Kind() != cue.StructKind {
72 return false
73 }
74 s := fmt.Sprintf(clusterSchema, fmt.Sprintf("%#v", v))
75 c := cuecontext.New()
76 u := c.CompileString(s)
77 if err := u.Validate(); err != nil {
78 return false
79 }
80 cluster := u.LookupPath(cue.ParsePath("#Cluster"))
81 vv := u.LookupPath(cue.ParsePath("value"))
82 if err := cluster.Subsume(vv); err == nil {
83 return true
84 }
85 return false
86}
87
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040088const networkSchema = `
89#Network: {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040090 name: string
91 ingressClass: string
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040092 certificateIssuer: string | *""
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040093 domain: string
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040094 allocatePortAddr: string
gioefa0ed42024-06-13 12:31:43 +040095 reservePortAddr: string
giocdfa3722024-06-13 20:10:14 +040096 deallocatePortAddr: string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040097}
98
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040099value: { %s }
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400100`
101
102func isNetwork(v cue.Value) bool {
103 if v.Value().Kind() != cue.StructKind {
104 return false
105 }
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400106 s := fmt.Sprintf(networkSchema, fmt.Sprintf("%#v", v))
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400107 c := cuecontext.New()
108 u := c.CompileString(s)
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400109 network := u.LookupPath(cue.ParsePath("#Network"))
110 vv := u.LookupPath(cue.ParsePath("value"))
111 if err := network.Subsume(vv); err == nil {
112 return true
113 }
114 return false
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400115}
116
gio4ece99c2024-07-18 11:05:50 +0400117const multiNetworkSchema = `
118#Network: {
119 name: string
120 ingressClass: string
121 certificateIssuer: string | *""
122 domain: string
123 allocatePortAddr: string
124 reservePortAddr: string
125 deallocatePortAddr: string
126}
127
128#Networks: [...#Network]
129
130value: %s
131`
132
133func isMultiNetwork(v cue.Value) bool {
134 if v.Value().IncompleteKind() != cue.ListKind {
135 return false
136 }
137 s := fmt.Sprintf(multiNetworkSchema, fmt.Sprintf("%#v", v))
138 c := cuecontext.New()
139 u := c.CompileString(s)
140 networks := u.LookupPath(cue.ParsePath("#Networks"))
141 vv := u.LookupPath(cue.ParsePath("value"))
142 if err := networks.Subsume(vv); err == nil {
143 return true
144 }
145 return false
146}
147
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400148const authSchema = `
149#Auth: {
150 enabled: bool | false
151 groups: string | *""
152}
153
154value: { %s }
155`
156
157func isAuth(v cue.Value) bool {
158 if v.Value().Kind() != cue.StructKind {
159 return false
160 }
161 s := fmt.Sprintf(authSchema, fmt.Sprintf("%#v", v))
162 c := cuecontext.New()
163 u := c.CompileString(s)
164 auth := u.LookupPath(cue.ParsePath("#Auth"))
165 vv := u.LookupPath(cue.ParsePath("value"))
166 if err := auth.Subsume(vv); err == nil {
167 return true
168 }
169 return false
170}
171
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400172const sshKeySchema = `
173#SSHKey: {
174 public: string
175 private: string
176}
177
178value: { %s }
179`
180
181func isSSHKey(v cue.Value) bool {
182 if v.Value().Kind() != cue.StructKind {
183 return false
184 }
185 s := fmt.Sprintf(sshKeySchema, fmt.Sprintf("%#v", v))
186 c := cuecontext.New()
187 u := c.CompileString(s)
188 sshKey := u.LookupPath(cue.ParsePath("#SSHKey"))
189 vv := u.LookupPath(cue.ParsePath("value"))
190 if err := sshKey.Subsume(vv); err == nil {
191 return true
192 }
193 return false
194}
195
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400196type basicSchema struct {
gio44f621b2024-04-29 09:44:38 +0400197 name string
198 kind Kind
199 advanced bool
gio36b23b32024-08-25 12:20:54 +0400200 meta map[string]string
gio44f621b2024-04-29 09:44:38 +0400201}
202
203func (s basicSchema) Name() string {
204 return s.name
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400205}
206
207func (s basicSchema) Kind() Kind {
208 return s.kind
209}
210
gio44f621b2024-04-29 09:44:38 +0400211func (s basicSchema) Fields() []Field {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400212 return nil
213}
214
gio44f621b2024-04-29 09:44:38 +0400215func (s basicSchema) Advanced() bool {
216 return s.advanced
217}
218
gio36b23b32024-08-25 12:20:54 +0400219func (s basicSchema) Meta() map[string]string {
220 return s.meta
221}
222
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400223type structSchema struct {
gio44f621b2024-04-29 09:44:38 +0400224 name string
225 fields []Field
226 advanced bool
227}
228
229func (s structSchema) Name() string {
230 return s.name
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400231}
232
233func (s structSchema) Kind() Kind {
234 return KindStruct
235}
236
gio44f621b2024-04-29 09:44:38 +0400237func (s structSchema) Fields() []Field {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400238 return s.fields
239}
240
gio44f621b2024-04-29 09:44:38 +0400241func (s structSchema) Advanced() bool {
242 return s.advanced
243}
244
gio36b23b32024-08-25 12:20:54 +0400245func (s structSchema) Meta() map[string]string {
246 return map[string]string{}
247}
248
gio44f621b2024-04-29 09:44:38 +0400249func NewCueSchema(name string, v cue.Value) (Schema, error) {
250 nameAttr := v.Attribute("name")
251 if nameAttr.Err() == nil {
252 name = nameAttr.Contents()
253 }
gioefa0ed42024-06-13 12:31:43 +0400254 role := ""
255 roleAttr := v.Attribute("role")
256 if roleAttr.Err() == nil {
257 role = strings.ToLower(roleAttr.Contents())
258 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400259 switch v.IncompleteKind() {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400260 case cue.StringKind:
gio36b23b32024-08-25 12:20:54 +0400261 if role == "vpnauthkey" {
262 meta := map[string]string{}
gio7fbd4ad2024-08-27 10:06:39 +0400263 usernameFieldAttr := v.Attribute("usernameField")
264 if usernameFieldAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400265 meta["usernameField"] = usernameFieldAttr.Contents()
gio7fbd4ad2024-08-27 10:06:39 +0400266 }
267 usernameAttr := v.Attribute("username")
268 if usernameAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400269 meta["username"] = usernameAttr.Contents()
gio7fbd4ad2024-08-27 10:06:39 +0400270 }
271 if len(meta) != 1 {
272 return nil, fmt.Errorf("invalid vpn auth key field meta: %+v", meta)
273 }
gio29f6b872024-09-08 16:14:58 +0400274 enabledFieldAttr := v.Attribute("enabledField")
275 if enabledFieldAttr.Err() == nil {
giof6ad2982024-08-23 17:42:49 +0400276 meta["enabledField"] = enabledFieldAttr.Contents()
gio29f6b872024-09-08 16:14:58 +0400277 }
gio36b23b32024-08-25 12:20:54 +0400278 return basicSchema{name, KindVPNAuthKey, true, meta}, nil
279 } else {
280 return basicSchema{name, KindString, false, nil}, nil
281 }
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400282 case cue.BoolKind:
gio36b23b32024-08-25 12:20:54 +0400283 return basicSchema{name, KindBoolean, false, nil}, nil
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400284 case cue.NumberKind:
gio36b23b32024-08-25 12:20:54 +0400285 return basicSchema{name, KindNumber, false, nil}, nil
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400286 case cue.IntKind:
gioefa0ed42024-06-13 12:31:43 +0400287 if role == "port" {
gio36b23b32024-08-25 12:20:54 +0400288 return basicSchema{name, KindPort, true, nil}, nil
gioefa0ed42024-06-13 12:31:43 +0400289 } else {
gio36b23b32024-08-25 12:20:54 +0400290 return basicSchema{name, KindInt, false, nil}, nil
gioefa0ed42024-06-13 12:31:43 +0400291 }
gioe72b54f2024-04-22 10:44:41 +0400292 case cue.ListKind:
gio4ece99c2024-07-18 11:05:50 +0400293 if isMultiNetwork(v) {
gio36b23b32024-08-25 12:20:54 +0400294 return basicSchema{name, KindMultiNetwork, false, nil}, nil
gio4ece99c2024-07-18 11:05:50 +0400295 }
gio36b23b32024-08-25 12:20:54 +0400296 return basicSchema{name, KindArrayString, false, nil}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400297 case cue.StructKind:
298 if isNetwork(v) {
gio36b23b32024-08-25 12:20:54 +0400299 return basicSchema{name, KindNetwork, false, nil}, nil
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400300 } else if isAuth(v) {
gio36b23b32024-08-25 12:20:54 +0400301 return basicSchema{name, KindAuth, false, nil}, nil
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400302 } else if isSSHKey(v) {
gio36b23b32024-08-25 12:20:54 +0400303 return basicSchema{name, KindSSHKey, true, nil}, nil
giof6ad2982024-08-23 17:42:49 +0400304 } else if isCluster(v) {
305 return basicSchema{name, KindCluster, false, nil}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400306 }
gio44f621b2024-04-29 09:44:38 +0400307 s := structSchema{name, make([]Field, 0), false}
giof6ad2982024-08-23 17:42:49 +0400308 f, err := v.Fields(cue.All())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400309 if err != nil {
310 return nil, err
311 }
312 for f.Next() {
gio44f621b2024-04-29 09:44:38 +0400313 scm, err := NewCueSchema(f.Selector().String(), f.Value())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400314 if err != nil {
315 return nil, err
316 }
giof6ad2982024-08-23 17:42:49 +0400317 s.fields = append(s.fields, Field{cleanFieldName(f.Selector().String()), scm})
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400318 }
319 return s, nil
320 default:
321 return nil, fmt.Errorf("SHOULD NOT REACH!")
322 }
323}
giof6ad2982024-08-23 17:42:49 +0400324
325func cleanFieldName(name string) string {
326 return strings.ReplaceAll(strings.ReplaceAll(name, "?", ""), "!", "")
327}