blob: 7e5e08d3111495db5c57f540d9baca57347ab229 [file] [log] [blame]
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +04001package installer
2
3import (
4 "encoding/json"
5 "fmt"
6 "strings"
7
8 "cuelang.org/go/cue"
9 "cuelang.org/go/cue/cuecontext"
10)
11
12type Kind int
13
14const (
15 KindBoolean Kind = 0
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040016 KindInt = 7
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040017 KindString = 1
18 KindStruct = 2
19 KindNetwork = 3
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040020 KindAuth = 5
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040021 KindSSHKey = 6
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040022 KindNumber = 4
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040023)
24
25type Schema interface {
26 Kind() Kind
27 Fields() map[string]Schema
28}
29
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040030var AuthSchema Schema = structSchema{
31 fields: map[string]Schema{
32 "enabled": basicSchema{KindBoolean},
33 "groups": basicSchema{KindString},
34 },
35}
36
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040037var SSHKeySchema Schema = structSchema{
38 fields: map[string]Schema{
39 "public": basicSchema{KindString},
40 "private": basicSchema{KindString},
41 },
42}
43
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040044const networkSchema = `
45#Network: {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040046 name: string
47 ingressClass: string
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040048 certificateIssuer: string | *""
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040049 domain: string
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040050 allocatePortAddr: string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040051}
52
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040053value: { %s }
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040054`
55
56func isNetwork(v cue.Value) bool {
57 if v.Value().Kind() != cue.StructKind {
58 return false
59 }
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040060 s := fmt.Sprintf(networkSchema, fmt.Sprintf("%#v", v))
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040061 c := cuecontext.New()
62 u := c.CompileString(s)
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040063 network := u.LookupPath(cue.ParsePath("#Network"))
64 vv := u.LookupPath(cue.ParsePath("value"))
65 if err := network.Subsume(vv); err == nil {
66 return true
67 }
68 return false
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040069}
70
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040071const authSchema = `
72#Auth: {
73 enabled: bool | false
74 groups: string | *""
75}
76
77value: { %s }
78`
79
80func isAuth(v cue.Value) bool {
81 if v.Value().Kind() != cue.StructKind {
82 return false
83 }
84 s := fmt.Sprintf(authSchema, fmt.Sprintf("%#v", v))
85 c := cuecontext.New()
86 u := c.CompileString(s)
87 auth := u.LookupPath(cue.ParsePath("#Auth"))
88 vv := u.LookupPath(cue.ParsePath("value"))
89 if err := auth.Subsume(vv); err == nil {
90 return true
91 }
92 return false
93}
94
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040095const sshKeySchema = `
96#SSHKey: {
97 public: string
98 private: string
99}
100
101value: { %s }
102`
103
104func isSSHKey(v cue.Value) bool {
105 if v.Value().Kind() != cue.StructKind {
106 return false
107 }
108 s := fmt.Sprintf(sshKeySchema, fmt.Sprintf("%#v", v))
109 c := cuecontext.New()
110 u := c.CompileString(s)
111 sshKey := u.LookupPath(cue.ParsePath("#SSHKey"))
112 vv := u.LookupPath(cue.ParsePath("value"))
113 if err := sshKey.Subsume(vv); err == nil {
114 return true
115 }
116 return false
117}
118
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400119type basicSchema struct {
120 kind Kind
121}
122
123func (s basicSchema) Kind() Kind {
124 return s.kind
125}
126
127func (s basicSchema) Fields() map[string]Schema {
128 return nil
129}
130
131type structSchema struct {
132 fields map[string]Schema
133}
134
135func (s structSchema) Kind() Kind {
136 return KindStruct
137}
138
139func (s structSchema) Fields() map[string]Schema {
140 return s.fields
141}
142
143func NewCueSchema(v cue.Value) (Schema, error) {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400144 switch v.IncompleteKind() {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400145 case cue.StringKind:
146 return basicSchema{KindString}, nil
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400147 case cue.BoolKind:
148 return basicSchema{KindBoolean}, nil
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400149 case cue.NumberKind:
150 return basicSchema{KindNumber}, nil
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400151 case cue.IntKind:
152 return basicSchema{KindInt}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400153 case cue.StructKind:
154 if isNetwork(v) {
155 return basicSchema{KindNetwork}, nil
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400156 } else if isAuth(v) {
157 return basicSchema{KindAuth}, nil
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400158 } else if isSSHKey(v) {
159 return basicSchema{KindSSHKey}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400160 }
161 s := structSchema{make(map[string]Schema)}
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400162 f, err := v.Fields(cue.Schema())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400163 if err != nil {
164 return nil, err
165 }
166 for f.Next() {
167 scm, err := NewCueSchema(f.Value())
168 if err != nil {
169 return nil, err
170 }
171 s.fields[f.Selector().String()] = scm
172 }
173 return s, nil
174 default:
175 return nil, fmt.Errorf("SHOULD NOT REACH!")
176 }
177}
178
179func newSchema(schema map[string]any) (Schema, error) {
180 switch schema["type"] {
181 case "string":
182 if r, ok := schema["role"]; ok && r == "network" {
183 return basicSchema{KindNetwork}, nil
184 } else {
185 return basicSchema{KindString}, nil
186 }
187 case "object":
188 s := structSchema{make(map[string]Schema)}
189 props := schema["properties"].(map[string]any)
190 for name, schema := range props {
191 sm, _ := schema.(map[string]any)
192 scm, err := newSchema(sm)
193 if err != nil {
194 return nil, err
195 }
196 s.fields[name] = scm
197 }
198 return s, nil
199 default:
200 return nil, fmt.Errorf("SHOULD NOT REACH!")
201 }
202}
203
204func NewJSONSchema(schema string) (Schema, error) {
205 ret := make(map[string]any)
206 if err := json.NewDecoder(strings.NewReader(schema)).Decode(&ret); err != nil {
207 return nil, err
208 }
209 return newSchema(ret)
210}