blob: d99f02b8d35808272c4036ec87c7c58493420e8b [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,
gio864b4332024-09-05 13:56:47 +040072 vpnKeyGen VPNAPIClient,
gio36b23b32024-08-25 12:20:54 +040073) (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 {
gio7fbd4ad2024-08-27 10:06:39 +040093 var username string
94 if v, ok := def.Meta()["username"]; ok {
95 username = v
96 } else if v, ok := def.Meta()["usernameField"]; ok {
97 // TODO(gio): Improve getField
98 username, ok = getField(root, v).(string)
99 if !ok {
100 return nil, fmt.Errorf("could not resolve username: %+v %s %+v", def.Meta(), v, root)
101 }
102 }
gio864b4332024-09-05 13:56:47 +0400103 authKey, err := vpnKeyGen.GenerateAuthKey(username)
gio36b23b32024-08-25 12:20:54 +0400104 if err != nil {
105 return nil, err
106 }
107 ret[k] = authKey
108 }
gio3af43942024-04-16 08:13:50 +0400109 continue
110 }
111 switch def.Kind() {
112 case KindBoolean:
113 ret[k] = v
114 case KindString:
115 ret[k] = v
116 case KindInt:
117 ret[k] = v
gioefa0ed42024-06-13 12:31:43 +0400118 case KindPort:
119 ret[k] = v
gio36b23b32024-08-25 12:20:54 +0400120 case KindVPNAuthKey:
121 ret[k] = v
gioe72b54f2024-04-22 10:44:41 +0400122 case KindArrayString:
123 a, ok := v.([]string)
124 if !ok {
125 return nil, fmt.Errorf("expected string array")
126 }
127 ret[k] = a
gio3af43942024-04-16 08:13:50 +0400128 case KindNetwork:
gio4ece99c2024-07-18 11:05:50 +0400129 name, ok := v.(string)
130 if !ok {
131 return nil, fmt.Errorf("not a string")
132 }
133 n, err := findNetwork(networks, name)
gio3af43942024-04-16 08:13:50 +0400134 if err != nil {
135 return nil, err
136 }
137 ret[k] = n
gio4ece99c2024-07-18 11:05:50 +0400138 case KindMultiNetwork:
139 vv, ok := v.([]any)
140 if !ok {
141 return nil, fmt.Errorf("not an array")
142 }
143 picked := []Network{}
144 for _, nn := range vv {
145 name, ok := nn.(string)
146 if !ok {
147 return nil, fmt.Errorf("not a string")
148 }
149 n, err := findNetwork(networks, name)
150 if err != nil {
151 return nil, err
152 }
153 picked = append(picked, n)
154 }
155 ret[k] = picked
gio3af43942024-04-16 08:13:50 +0400156 case KindAuth:
gio7fbd4ad2024-08-27 10:06:39 +0400157 r, err := deriveValues(root, v, AuthSchema, networks, vpnKeyGen)
gio3af43942024-04-16 08:13:50 +0400158 if err != nil {
159 return nil, err
160 }
161 ret[k] = r
162 case KindSSHKey:
gio7fbd4ad2024-08-27 10:06:39 +0400163 r, err := deriveValues(root, v, SSHKeySchema, networks, vpnKeyGen)
gio3af43942024-04-16 08:13:50 +0400164 if err != nil {
165 return nil, err
166 }
167 ret[k] = r
168 case KindStruct:
gio7fbd4ad2024-08-27 10:06:39 +0400169 r, err := deriveValues(root, v, def, networks, vpnKeyGen)
gio3af43942024-04-16 08:13:50 +0400170 if err != nil {
171 return nil, err
172 }
173 ret[k] = r
174 default:
175 return nil, fmt.Errorf("Should not reach!")
176 }
177 }
178 return ret, nil
179}
180
181func derivedToConfig(derived map[string]any, schema Schema) (map[string]any, error) {
182 ret := make(map[string]any)
gio44f621b2024-04-29 09:44:38 +0400183 for _, f := range schema.Fields() {
184 k := f.Name
185 def := f.Schema
gio3af43942024-04-16 08:13:50 +0400186 v, ok := derived[k]
187 // TODO(gio): if missing use default value
188 if !ok {
189 continue
190 }
191 switch def.Kind() {
192 case KindBoolean:
193 ret[k] = v
194 case KindString:
195 ret[k] = v
196 case KindInt:
197 ret[k] = v
gioefa0ed42024-06-13 12:31:43 +0400198 case KindPort:
199 ret[k] = v
gio36b23b32024-08-25 12:20:54 +0400200 case KindVPNAuthKey:
201 ret[k] = v
gioe72b54f2024-04-22 10:44:41 +0400202 case KindArrayString:
203 a, ok := v.([]string)
204 if !ok {
205 return nil, fmt.Errorf("expected string array")
206 }
207 ret[k] = a
gio3af43942024-04-16 08:13:50 +0400208 case KindNetwork:
209 vm, ok := v.(map[string]any)
210 if !ok {
211 return nil, fmt.Errorf("expected map")
212 }
213 name, ok := vm["name"]
214 if !ok {
215 return nil, fmt.Errorf("expected network name")
216 }
217 ret[k] = name
gio4ece99c2024-07-18 11:05:50 +0400218 case KindMultiNetwork:
219 nl, ok := v.([]any)
220 if !ok {
221 return nil, fmt.Errorf("expected map")
222 }
223 names := []string{}
224 for _, n := range nl {
225 i, ok := n.(map[string]any)
226 if !ok {
227 return nil, fmt.Errorf("expected map")
228 }
229 name, ok := i["name"]
230 if !ok {
231 return nil, fmt.Errorf("expected network name")
232 }
233 names = append(names, name.(string))
234 }
235 ret[k] = names
gio3af43942024-04-16 08:13:50 +0400236 case KindAuth:
237 vm, ok := v.(map[string]any)
238 if !ok {
239 return nil, fmt.Errorf("expected map")
240 }
241 r, err := derivedToConfig(vm, AuthSchema)
242 if err != nil {
243 return nil, err
244 }
245 ret[k] = r
246 case KindSSHKey:
247 vm, ok := v.(map[string]any)
248 if !ok {
249 return nil, fmt.Errorf("expected map")
250 }
251 r, err := derivedToConfig(vm, SSHKeySchema)
252 if err != nil {
253 return nil, err
254 }
255 ret[k] = r
256 case KindStruct:
257 vm, ok := v.(map[string]any)
258 if !ok {
259 return nil, fmt.Errorf("expected map")
260 }
261 r, err := derivedToConfig(vm, def)
262 if err != nil {
263 return nil, err
264 }
265 ret[k] = r
266 default:
267 return nil, fmt.Errorf("Should not reach!")
268 }
269 }
270 return ret, nil
271}
272
273func findNetwork(networks []Network, name string) (Network, error) {
274 for _, n := range networks {
275 if n.Name == name {
276 return n, nil
277 }
278 }
279 return Network{}, fmt.Errorf("Network not found: %s", name)
280}