blob: a692ecf09d981ec38ef2588f624ba8035aa5c82a [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 Lekveishvilie009a5d2024-01-05 14:10:11 +040020 KindNumber = 4
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040021)
22
23type Schema interface {
24 Kind() Kind
25 Fields() map[string]Schema
26}
27
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040028var AuthSchema Schema = structSchema{
29 fields: map[string]Schema{
30 "enabled": basicSchema{KindBoolean},
31 "groups": basicSchema{KindString},
32 },
33}
34
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040035const networkSchema = `
36#Network: {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040037 name: string
38 ingressClass: string
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040039 certificateIssuer: string | *""
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040040 domain: string
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040041}
42
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040043value: { %s }
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040044`
45
46func isNetwork(v cue.Value) bool {
47 if v.Value().Kind() != cue.StructKind {
48 return false
49 }
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040050 s := fmt.Sprintf(networkSchema, fmt.Sprintf("%#v", v))
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040051 c := cuecontext.New()
52 u := c.CompileString(s)
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040053 network := u.LookupPath(cue.ParsePath("#Network"))
54 vv := u.LookupPath(cue.ParsePath("value"))
55 if err := network.Subsume(vv); err == nil {
56 return true
57 }
58 return false
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040059}
60
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040061const authSchema = `
62#Auth: {
63 enabled: bool | false
64 groups: string | *""
65}
66
67value: { %s }
68`
69
70func isAuth(v cue.Value) bool {
71 if v.Value().Kind() != cue.StructKind {
72 return false
73 }
74 s := fmt.Sprintf(authSchema, fmt.Sprintf("%#v", v))
75 c := cuecontext.New()
76 u := c.CompileString(s)
77 auth := u.LookupPath(cue.ParsePath("#Auth"))
78 vv := u.LookupPath(cue.ParsePath("value"))
79 if err := auth.Subsume(vv); err == nil {
80 return true
81 }
82 return false
83}
84
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +040085type basicSchema struct {
86 kind Kind
87}
88
89func (s basicSchema) Kind() Kind {
90 return s.kind
91}
92
93func (s basicSchema) Fields() map[string]Schema {
94 return nil
95}
96
97type structSchema struct {
98 fields map[string]Schema
99}
100
101func (s structSchema) Kind() Kind {
102 return KindStruct
103}
104
105func (s structSchema) Fields() map[string]Schema {
106 return s.fields
107}
108
109func NewCueSchema(v cue.Value) (Schema, error) {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400110 switch v.IncompleteKind() {
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400111 case cue.StringKind:
112 return basicSchema{KindString}, nil
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400113 case cue.BoolKind:
114 return basicSchema{KindBoolean}, nil
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400115 case cue.NumberKind:
116 return basicSchema{KindNumber}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400117 case cue.StructKind:
118 if isNetwork(v) {
119 return basicSchema{KindNetwork}, nil
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400120 } else if isAuth(v) {
121 return basicSchema{KindAuth}, nil
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400122 }
123 s := structSchema{make(map[string]Schema)}
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400124 f, err := v.Fields(cue.Schema())
Giorgi Lekveishvili7c427602024-01-04 00:13:55 +0400125 if err != nil {
126 return nil, err
127 }
128 for f.Next() {
129 scm, err := NewCueSchema(f.Value())
130 if err != nil {
131 return nil, err
132 }
133 s.fields[f.Selector().String()] = scm
134 }
135 return s, nil
136 default:
137 return nil, fmt.Errorf("SHOULD NOT REACH!")
138 }
139}
140
141func newSchema(schema map[string]any) (Schema, error) {
142 switch schema["type"] {
143 case "string":
144 if r, ok := schema["role"]; ok && r == "network" {
145 return basicSchema{KindNetwork}, nil
146 } else {
147 return basicSchema{KindString}, nil
148 }
149 case "object":
150 s := structSchema{make(map[string]Schema)}
151 props := schema["properties"].(map[string]any)
152 for name, schema := range props {
153 sm, _ := schema.(map[string]any)
154 scm, err := newSchema(sm)
155 if err != nil {
156 return nil, err
157 }
158 s.fields[name] = scm
159 }
160 return s, nil
161 default:
162 return nil, fmt.Errorf("SHOULD NOT REACH!")
163 }
164}
165
166func NewJSONSchema(schema string) (Schema, error) {
167 ret := make(map[string]any)
168 if err := json.NewDecoder(strings.NewReader(schema)).Decode(&ret); err != nil {
169 return nil, err
170 }
171 return newSchema(ret)
172}