blob: aabda98b7848de970b07b5e5cdf7f762dbb64f1f [file] [log] [blame]
gio3af43942024-04-16 08:13:50 +04001package installer
2
3import (
4 "fmt"
gio09a3e5b2024-04-26 14:11:06 +04005 "html/template"
gio36b23b32024-08-25 12:20:54 +04006 "strings"
gio3af43942024-04-16 08:13:50 +04007)
8
9type Release struct {
gio3cdee592024-04-17 10:15:56 +040010 AppInstanceId string `json:"appInstanceId"`
11 Namespace string `json:"namespace"`
12 RepoAddr string `json:"repoAddr"`
13 AppDir string `json:"appDir"`
giof8843412024-05-22 16:38:05 +040014 ImageRegistry string `json:"imageRegistry,omitempty"`
gio3af43942024-04-16 08:13:50 +040015}
16
17type Network struct {
giocdfa3722024-06-13 20:10:14 +040018 Name string `json:"name,omitempty"`
19 IngressClass string `json:"ingressClass,omitempty"`
20 CertificateIssuer string `json:"certificateIssuer,omitempty"`
21 Domain string `json:"domain,omitempty"`
22 AllocatePortAddr string `json:"allocatePortAddr,omitempty"`
23 ReservePortAddr string `json:"reservePortAddr,omitempty"`
24 DeallocatePortAddr string `json:"deallocatePortAddr,omitempty"`
gio3af43942024-04-16 08:13:50 +040025}
26
gioe72b54f2024-04-22 10:44:41 +040027type InfraAppInstanceConfig struct {
28 Id string `json:"id"`
29 AppId string `json:"appId"`
30 Infra InfraConfig `json:"infra"`
31 Release Release `json:"release"`
32 Values map[string]any `json:"values"`
33 Input map[string]any `json:"input"`
gio09a3e5b2024-04-26 14:11:06 +040034 URL string `json:"url"`
35 Help []HelpDocument `json:"help"`
36 Icon template.HTML `json:"icon"`
gioe72b54f2024-04-22 10:44:41 +040037}
38
gio3cdee592024-04-17 10:15:56 +040039type AppInstanceConfig struct {
gio3af43942024-04-16 08:13:50 +040040 Id string `json:"id"`
41 AppId string `json:"appId"`
gioe72b54f2024-04-22 10:44:41 +040042 Env EnvConfig `json:"env"`
gio3cdee592024-04-17 10:15:56 +040043 Release Release `json:"release"`
44 Values map[string]any `json:"values"`
45 Input map[string]any `json:"input"`
gio09a3e5b2024-04-26 14:11:06 +040046 URL string `json:"url"`
Davit Tabidze56f86a42024-04-09 19:15:25 +040047 Help []HelpDocument `json:"help"`
gio09a3e5b2024-04-26 14:11:06 +040048 Icon string `json:"icon"`
gio3af43942024-04-16 08:13:50 +040049}
50
gio3cdee592024-04-17 10:15:56 +040051func (a AppInstanceConfig) InputToValues(schema Schema) map[string]any {
52 ret, err := derivedToConfig(a.Input, schema)
gio3af43942024-04-16 08:13:50 +040053 if err != nil {
gio3cdee592024-04-17 10:15:56 +040054 panic(err)
gio3af43942024-04-16 08:13:50 +040055 }
56 return ret
57}
58
gio36b23b32024-08-25 12:20:54 +040059func getField(v any, f string) any {
60 for _, i := range strings.Split(f, ".") {
61 vm := v.(map[string]any)
62 v = vm[i]
63 }
64 return v
65}
66
67func deriveValues(
68 root any,
69 values any,
70 schema Schema,
71 networks []Network,
72 vpnKeyGen VPNAuthKeyGenerator,
73) (map[string]any, error) {
gio3af43942024-04-16 08:13:50 +040074 ret := make(map[string]any)
gio44f621b2024-04-29 09:44:38 +040075 for _, f := range schema.Fields() {
76 k := f.Name
77 def := f.Schema
gio3af43942024-04-16 08:13:50 +040078 // TODO(gio): validate that it is map
79 v, ok := values.(map[string]any)[k]
80 // TODO(gio): if missing use default value
81 if !ok {
82 if def.Kind() == KindSSHKey {
83 key, err := NewECDSASSHKeyPair("tmp")
84 if err != nil {
85 return nil, err
86 }
87 ret[k] = map[string]string{
88 "public": string(key.RawAuthorizedKey()),
89 "private": string(key.RawPrivateKey()),
90 }
91 }
gio36b23b32024-08-25 12:20:54 +040092 if def.Kind() == KindVPNAuthKey {
93 usernameField := def.Meta()["usernameField"]
94 // TODO(gio): Improve getField
95 username := getField(root, usernameField)
96 authKey, err := vpnKeyGen.Generate(username.(string))
97 if err != nil {
98 return nil, err
99 }
100 ret[k] = authKey
101 }
gio3af43942024-04-16 08:13:50 +0400102 continue
103 }
104 switch def.Kind() {
105 case KindBoolean:
106 ret[k] = v
107 case KindString:
108 ret[k] = v
109 case KindInt:
110 ret[k] = v
gioefa0ed42024-06-13 12:31:43 +0400111 case KindPort:
112 ret[k] = v
gio36b23b32024-08-25 12:20:54 +0400113 case KindVPNAuthKey:
114 ret[k] = v
gioe72b54f2024-04-22 10:44:41 +0400115 case KindArrayString:
116 a, ok := v.([]string)
117 if !ok {
118 return nil, fmt.Errorf("expected string array")
119 }
120 ret[k] = a
gio3af43942024-04-16 08:13:50 +0400121 case KindNetwork:
gio4ece99c2024-07-18 11:05:50 +0400122 name, ok := v.(string)
123 if !ok {
124 return nil, fmt.Errorf("not a string")
125 }
126 n, err := findNetwork(networks, name)
gio3af43942024-04-16 08:13:50 +0400127 if err != nil {
128 return nil, err
129 }
130 ret[k] = n
gio4ece99c2024-07-18 11:05:50 +0400131 case KindMultiNetwork:
132 vv, ok := v.([]any)
133 if !ok {
134 return nil, fmt.Errorf("not an array")
135 }
136 picked := []Network{}
137 for _, nn := range vv {
138 name, ok := nn.(string)
139 if !ok {
140 return nil, fmt.Errorf("not a string")
141 }
142 n, err := findNetwork(networks, name)
143 if err != nil {
144 return nil, err
145 }
146 picked = append(picked, n)
147 }
148 ret[k] = picked
gio3af43942024-04-16 08:13:50 +0400149 case KindAuth:
gio36b23b32024-08-25 12:20:54 +0400150 r, err := deriveValues(v, v, AuthSchema, networks, vpnKeyGen)
gio3af43942024-04-16 08:13:50 +0400151 if err != nil {
152 return nil, err
153 }
154 ret[k] = r
155 case KindSSHKey:
gio36b23b32024-08-25 12:20:54 +0400156 r, err := deriveValues(v, v, SSHKeySchema, networks, vpnKeyGen)
gio3af43942024-04-16 08:13:50 +0400157 if err != nil {
158 return nil, err
159 }
160 ret[k] = r
161 case KindStruct:
gio36b23b32024-08-25 12:20:54 +0400162 r, err := deriveValues(v, v, def, networks, vpnKeyGen)
gio3af43942024-04-16 08:13:50 +0400163 if err != nil {
164 return nil, err
165 }
166 ret[k] = r
167 default:
168 return nil, fmt.Errorf("Should not reach!")
169 }
170 }
171 return ret, nil
172}
173
174func derivedToConfig(derived map[string]any, schema Schema) (map[string]any, error) {
175 ret := make(map[string]any)
gio44f621b2024-04-29 09:44:38 +0400176 for _, f := range schema.Fields() {
177 k := f.Name
178 def := f.Schema
gio3af43942024-04-16 08:13:50 +0400179 v, ok := derived[k]
180 // TODO(gio): if missing use default value
181 if !ok {
182 continue
183 }
184 switch def.Kind() {
185 case KindBoolean:
186 ret[k] = v
187 case KindString:
188 ret[k] = v
189 case KindInt:
190 ret[k] = v
gioefa0ed42024-06-13 12:31:43 +0400191 case KindPort:
192 ret[k] = v
gio36b23b32024-08-25 12:20:54 +0400193 case KindVPNAuthKey:
194 ret[k] = v
gioe72b54f2024-04-22 10:44:41 +0400195 case KindArrayString:
196 a, ok := v.([]string)
197 if !ok {
198 return nil, fmt.Errorf("expected string array")
199 }
200 ret[k] = a
gio3af43942024-04-16 08:13:50 +0400201 case KindNetwork:
202 vm, ok := v.(map[string]any)
203 if !ok {
204 return nil, fmt.Errorf("expected map")
205 }
206 name, ok := vm["name"]
207 if !ok {
208 return nil, fmt.Errorf("expected network name")
209 }
210 ret[k] = name
gio4ece99c2024-07-18 11:05:50 +0400211 case KindMultiNetwork:
212 nl, ok := v.([]any)
213 if !ok {
214 return nil, fmt.Errorf("expected map")
215 }
216 names := []string{}
217 for _, n := range nl {
218 i, ok := n.(map[string]any)
219 if !ok {
220 return nil, fmt.Errorf("expected map")
221 }
222 name, ok := i["name"]
223 if !ok {
224 return nil, fmt.Errorf("expected network name")
225 }
226 names = append(names, name.(string))
227 }
228 ret[k] = names
gio3af43942024-04-16 08:13:50 +0400229 case KindAuth:
230 vm, ok := v.(map[string]any)
231 if !ok {
232 return nil, fmt.Errorf("expected map")
233 }
234 r, err := derivedToConfig(vm, AuthSchema)
235 if err != nil {
236 return nil, err
237 }
238 ret[k] = r
239 case KindSSHKey:
240 vm, ok := v.(map[string]any)
241 if !ok {
242 return nil, fmt.Errorf("expected map")
243 }
244 r, err := derivedToConfig(vm, SSHKeySchema)
245 if err != nil {
246 return nil, err
247 }
248 ret[k] = r
249 case KindStruct:
250 vm, ok := v.(map[string]any)
251 if !ok {
252 return nil, fmt.Errorf("expected map")
253 }
254 r, err := derivedToConfig(vm, def)
255 if err != nil {
256 return nil, err
257 }
258 ret[k] = r
259 default:
260 return nil, fmt.Errorf("Should not reach!")
261 }
262 }
263 return ret, nil
264}
265
266func findNetwork(networks []Network, name string) (Network, error) {
267 for _, n := range networks {
268 if n.Name == name {
269 return n, nil
270 }
271 }
272 return Network{}, fmt.Errorf("Network not found: %s", name)
273}