blob: b96cc138b08c788a1ebf3a8afa0057a245c27566 [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
16 KindString = 1
17 KindStruct = 2
18 KindNetwork = 3
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040019 KindAuth = 5
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040020 KindSSHKey = 6
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040021 KindNumber = 4
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040022)
23
24type Schema interface {
25 Kind() Kind
26 Fields() map[string]Schema
27}
28
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040029var AuthSchema Schema = structSchema{
30 fields: map[string]Schema{
31 "enabled": basicSchema{KindBoolean},
32 "groups": basicSchema{KindString},
33 },
34}
35
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040036var SSHKeySchema Schema = structSchema{
37 fields: map[string]Schema{
38 "public": basicSchema{KindString},
39 "private": basicSchema{KindString},
40 },
41}
42
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040043const networkSchema = `
44#Network: {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040045 name: string
46 ingressClass: string
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040047 certificateIssuer: string | *""
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040048 domain: string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040049}
50
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040051value: { %s }
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040052`
53
54func isNetwork(v cue.Value) bool {
55 if v.Value().Kind() != cue.StructKind {
56 return false
57 }
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040058 s := fmt.Sprintf(networkSchema, fmt.Sprintf("%#v", v))
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040059 c := cuecontext.New()
60 u := c.CompileString(s)
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040061 network := u.LookupPath(cue.ParsePath("#Network"))
62 vv := u.LookupPath(cue.ParsePath("value"))
63 if err := network.Subsume(vv); err == nil {
64 return true
65 }
66 return false
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040067}
68
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040069const authSchema = `
70#Auth: {
71 enabled: bool | false
72 groups: string | *""
73}
74
75value: { %s }
76`
77
78func isAuth(v cue.Value) bool {
79 if v.Value().Kind() != cue.StructKind {
80 return false
81 }
82 s := fmt.Sprintf(authSchema, fmt.Sprintf("%#v", v))
83 c := cuecontext.New()
84 u := c.CompileString(s)
85 auth := u.LookupPath(cue.ParsePath("#Auth"))
86 vv := u.LookupPath(cue.ParsePath("value"))
87 if err := auth.Subsume(vv); err == nil {
88 return true
89 }
90 return false
91}
92
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +040093const sshKeySchema = `
94#SSHKey: {
95 public: string
96 private: string
97}
98
99value: { %s }
100`
101
102func isSSHKey(v cue.Value) bool {
103 if v.Value().Kind() != cue.StructKind {
104 return false
105 }
106 s := fmt.Sprintf(sshKeySchema, fmt.Sprintf("%#v", v))
107 c := cuecontext.New()
108 u := c.CompileString(s)
109 sshKey := u.LookupPath(cue.ParsePath("#SSHKey"))
110 vv := u.LookupPath(cue.ParsePath("value"))
111 if err := sshKey.Subsume(vv); err == nil {
112 return true
113 }
114 return false
115}
116
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400117type basicSchema struct {
118 kind Kind
119}
120
121func (s basicSchema) Kind() Kind {
122 return s.kind
123}
124
125func (s basicSchema) Fields() map[string]Schema {
126 return nil
127}
128
129type structSchema struct {
130 fields map[string]Schema
131}
132
133func (s structSchema) Kind() Kind {
134 return KindStruct
135}
136
137func (s structSchema) Fields() map[string]Schema {
138 return s.fields
139}
140
141func NewCueSchema(v cue.Value) (Schema, error) {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400142 switch v.IncompleteKind() {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400143 case cue.StringKind:
144 return basicSchema{KindString}, nil
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400145 case cue.BoolKind:
146 return basicSchema{KindBoolean}, nil
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400147 case cue.NumberKind:
148 return basicSchema{KindNumber}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400149 case cue.StructKind:
150 if isNetwork(v) {
151 return basicSchema{KindNetwork}, nil
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400152 } else if isAuth(v) {
153 return basicSchema{KindAuth}, nil
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400154 } else if isSSHKey(v) {
155 return basicSchema{KindSSHKey}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400156 }
157 s := structSchema{make(map[string]Schema)}
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400158 f, err := v.Fields(cue.Schema())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400159 if err != nil {
160 return nil, err
161 }
162 for f.Next() {
163 scm, err := NewCueSchema(f.Value())
164 if err != nil {
165 return nil, err
166 }
167 s.fields[f.Selector().String()] = scm
168 }
169 return s, nil
170 default:
171 return nil, fmt.Errorf("SHOULD NOT REACH!")
172 }
173}
174
175func newSchema(schema map[string]any) (Schema, error) {
176 switch schema["type"] {
177 case "string":
178 if r, ok := schema["role"]; ok && r == "network" {
179 return basicSchema{KindNetwork}, nil
180 } else {
181 return basicSchema{KindString}, nil
182 }
183 case "object":
184 s := structSchema{make(map[string]Schema)}
185 props := schema["properties"].(map[string]any)
186 for name, schema := range props {
187 sm, _ := schema.(map[string]any)
188 scm, err := newSchema(sm)
189 if err != nil {
190 return nil, err
191 }
192 s.fields[name] = scm
193 }
194 return s, nil
195 default:
196 return nil, fmt.Errorf("SHOULD NOT REACH!")
197 }
198}
199
200func NewJSONSchema(schema string) (Schema, error) {
201 ret := make(map[string]any)
202 if err := json.NewDecoder(strings.NewReader(schema)).Decode(&ret); err != nil {
203 return nil, err
204 }
205 return newSchema(ret)
206}