blob: 0b59ca1864d2d4faf27f549978bc8cb44846ecc3 [file] [log] [blame]
giolekva8aa73e82022-07-09 11:34:39 +04001package installer
giolekva050609f2021-12-29 15:51:40 +04002
giolekva8aa73e82022-07-09 11:34:39 +04003import (
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +04004 "bytes"
gio0eaf2712024-04-14 13:08:46 +04005 _ "embed"
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +04006 "encoding/json"
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +04007 "fmt"
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +04008 template "html/template"
gio3cdee592024-04-17 10:15:56 +04009 "net"
gioe72b54f2024-04-22 10:44:41 +040010 "net/netip"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040011 "strings"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040012
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040013 "cuelang.org/go/cue"
gio308105e2024-04-19 13:12:13 +040014 "cuelang.org/go/cue/build"
15 "cuelang.org/go/cue/cuecontext"
gioa1f29472025-05-14 13:05:05 +040016 "cuelang.org/go/cue/errors"
gio308105e2024-04-19 13:12:13 +040017 "cuelang.org/go/cue/load"
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040018 cueyaml "cuelang.org/go/encoding/yaml"
giof8843412024-05-22 16:38:05 +040019 helmv2 "github.com/fluxcd/helm-controller/api/v2"
giolekva8aa73e82022-07-09 11:34:39 +040020)
giolekva050609f2021-12-29 15:51:40 +040021
giof8843412024-05-22 16:38:05 +040022//go:embed app_configs/dodo_app.cue
23var dodoAppCue []byte
gio0eaf2712024-04-14 13:08:46 +040024
giof8843412024-05-22 16:38:05 +040025//go:embed app_configs/app_base.cue
26var cueBaseConfig []byte
gio0eaf2712024-04-14 13:08:46 +040027
giof8843412024-05-22 16:38:05 +040028//go:embed app_configs/app_global_env.cue
29var cueEnvAppGlobal []byte
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040030
giof8843412024-05-22 16:38:05 +040031//go:embed app_configs/app_global_infra.cue
32var cueInfraAppGlobal []byte
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040033
gioe72b54f2024-04-22 10:44:41 +040034type rendered struct {
giof8843412024-05-22 16:38:05 +040035 Name string
36 Readme string
giof6ad2982024-08-23 17:42:49 +040037 Cluster string
38 Namespaces []Namespace
giof8843412024-05-22 16:38:05 +040039 Resources CueAppData
40 HelmCharts HelmCharts
41 ContainerImages map[string]ContainerImage
42 Ports []PortForward
giof6ad2982024-08-23 17:42:49 +040043 ClusterProxies map[string]ClusterProxy
giof8843412024-05-22 16:38:05 +040044 Data CueAppData
45 URL string
46 Help []HelpDocument
47 Icon string
gio94904702024-07-26 16:58:34 +040048 Raw []byte
Davit Tabidze56f86a42024-04-09 19:15:25 +040049}
50
giof6ad2982024-08-23 17:42:49 +040051type Namespace struct {
52 Name string `json:"name"`
53 Kubeconfig string `json:"kubeconfig,omitempty"`
54}
55
Davit Tabidze56f86a42024-04-09 19:15:25 +040056type HelpDocument struct {
57 Title string
58 Contents string
59 Children []HelpDocument
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040060}
61
giof8843412024-05-22 16:38:05 +040062type ContainerImage struct {
63 Registry string `json:"registry"`
64 Repository string `json:"repository"`
65 Name string `json:"name"`
66 Tag string `json:"tag"`
67}
68
69type helmChartRef struct {
70 Kind string `json:"kind"`
71}
72
73type HelmCharts struct {
74 Git map[string]HelmChartGitRepo
75}
76
77type HelmChartGitRepo struct {
78 Address string `json:"address"`
79 Branch string `json:"branch"`
80 Path string `json:"path"`
81}
82
gioe72b54f2024-04-22 10:44:41 +040083type EnvAppRendered struct {
84 rendered
85 Config AppInstanceConfig
86}
87
88type InfraAppRendered struct {
89 rendered
90 Config InfraAppInstanceConfig
91}
92
giof6ad2982024-08-23 17:42:49 +040093type ClusterProxy struct {
94 From string `json:"from"`
95 To string `json:"to"`
96}
97
gio3cdee592024-04-17 10:15:56 +040098type PortForward struct {
giod78896a2025-04-10 07:42:13 +040099 Cluster string `json:"clusterName,omitempty"`
100 Network Network `json:"network"`
101 Protocol string `json:"protocol"`
102 Port int `json:"port"`
103 Service struct {
giof4344632025-04-08 20:04:35 +0400104 Name string `json:"name"`
105 Namespace string `json:"namespace,omitempty"`
106 Port int `json:"port"`
gio802311e2024-11-04 08:37:34 +0400107 } `json:"service"`
gio3cdee592024-04-17 10:15:56 +0400108}
109
110type AppType int
111
112const (
113 AppTypeInfra AppType = iota
114 AppTypeEnv
115)
116
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400117type App interface {
118 Name() string
gio44f621b2024-04-29 09:44:38 +0400119 Type() AppType
120 Slug() string
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400121 Description() string
122 Icon() template.HTML
123 Schema() Schema
gioef01fbb2024-04-12 16:52:59 +0400124 Namespace() string
gio3cdee592024-04-17 10:15:56 +0400125}
126
127type InfraConfig struct {
gioe72b54f2024-04-22 10:44:41 +0400128 Name string `json:"pcloudEnvName,omitempty"` // #TODO(gio): change to name
129 PublicIP []net.IP `json:"publicIP,omitempty"`
130 InfraNamespacePrefix string `json:"namespacePrefix,omitempty"`
131 InfraAdminPublicKey []byte `json:"infraAdminPublicKey,omitempty"`
gio3cdee592024-04-17 10:15:56 +0400132}
133
gio7841f4f2024-07-26 19:53:49 +0400134type InfraNetwork struct {
135 Name string `json:"name,omitempty"`
136 IngressClass string `json:"ingressClass,omitempty"`
137 CertificateIssuer string `json:"certificateIssuer,omitempty"`
138 AllocatePortAddr string `json:"allocatePortAddr,omitempty"`
139 ReservePortAddr string `json:"reservePortAddr,omitempty"`
140 DeallocatePortAddr string `json:"deallocatePortAddr,omitempty"`
141}
142
gio3cdee592024-04-17 10:15:56 +0400143type InfraApp interface {
144 App
gio7841f4f2024-07-26 19:53:49 +0400145 Render(release Release, infra InfraConfig, networks []InfraNetwork, values map[string]any, charts map[string]helmv2.HelmChartTemplateSpec) (InfraAppRendered, error)
gioe72b54f2024-04-22 10:44:41 +0400146}
147
148type EnvNetwork struct {
149 DNS net.IP `json:"dns,omitempty"`
150 DNSInClusterIP net.IP `json:"dnsInClusterIP,omitempty"`
151 Ingress net.IP `json:"ingress,omitempty"`
152 Headscale net.IP `json:"headscale,omitempty"`
153 ServicesFrom net.IP `json:"servicesFrom,omitempty"`
154 ServicesTo net.IP `json:"servicesTo,omitempty"`
155}
156
157func NewEnvNetwork(subnet net.IP) (EnvNetwork, error) {
158 addr, err := netip.ParseAddr(subnet.String())
159 if err != nil {
160 return EnvNetwork{}, err
161 }
162 if !addr.Is4() {
163 return EnvNetwork{}, fmt.Errorf("Expected IPv4, got %s instead", addr)
164 }
165 dns := addr.Next()
166 ingress := dns.Next()
167 headscale := ingress.Next()
168 b := addr.AsSlice()
169 if b[3] != 0 {
170 return EnvNetwork{}, fmt.Errorf("Expected last byte to be zero, got %d instead", b[3])
171 }
172 b[3] = 10
173 servicesFrom, ok := netip.AddrFromSlice(b)
174 if !ok {
175 return EnvNetwork{}, fmt.Errorf("Must not reach")
176 }
177 b[3] = 254
178 servicesTo, ok := netip.AddrFromSlice(b)
179 if !ok {
180 return EnvNetwork{}, fmt.Errorf("Must not reach")
181 }
182 b[3] = b[2]
183 b[2] = b[1]
184 b[0] = 10
185 b[1] = 44
186 dnsInClusterIP, ok := netip.AddrFromSlice(b)
187 if !ok {
188 return EnvNetwork{}, fmt.Errorf("Must not reach")
189 }
190 return EnvNetwork{
191 DNS: net.ParseIP(dns.String()),
192 DNSInClusterIP: net.ParseIP(dnsInClusterIP.String()),
193 Ingress: net.ParseIP(ingress.String()),
194 Headscale: net.ParseIP(headscale.String()),
195 ServicesFrom: net.ParseIP(servicesFrom.String()),
196 ServicesTo: net.ParseIP(servicesTo.String()),
197 }, nil
gio3cdee592024-04-17 10:15:56 +0400198}
199
gioe72b54f2024-04-22 10:44:41 +0400200type EnvConfig struct {
201 Id string `json:"id,omitempty"`
202 InfraName string `json:"pcloudEnvName,omitempty"`
203 Domain string `json:"domain,omitempty"`
204 PrivateDomain string `json:"privateDomain,omitempty"`
205 ContactEmail string `json:"contactEmail,omitempty"`
206 AdminPublicKey string `json:"adminPublicKey,omitempty"`
207 PublicIP []net.IP `json:"publicIP,omitempty"`
208 NameserverIP []net.IP `json:"nameserverIP,omitempty"`
209 NamespacePrefix string `json:"namespacePrefix,omitempty"`
210 Network EnvNetwork `json:"network,omitempty"`
gio3cdee592024-04-17 10:15:56 +0400211}
212
213type EnvApp interface {
214 App
gio36b23b32024-08-25 12:20:54 +0400215 Render(
216 release Release,
217 env EnvConfig,
218 networks []Network,
giof6ad2982024-08-23 17:42:49 +0400219 clusters []Cluster,
gio36b23b32024-08-25 12:20:54 +0400220 values map[string]any,
221 charts map[string]helmv2.HelmChartTemplateSpec,
gio864b4332024-09-05 13:56:47 +0400222 vpnKeyGen VPNAPIClient,
gio36b23b32024-08-25 12:20:54 +0400223 ) (EnvAppRendered, error)
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400224}
225
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400226type cueApp struct {
227 name string
228 description string
229 icon template.HTML
230 namespace string
231 schema Schema
gio308105e2024-04-19 13:12:13 +0400232 cfg cue.Value
233 data CueAppData
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400234}
235
gio308105e2024-04-19 13:12:13 +0400236type CueAppData map[string][]byte
237
238func ParseCueAppConfig(data CueAppData) (cue.Value, error) {
239 ctx := cuecontext.New()
240 buildCtx := build.NewContext()
241 cfg := &load.Config{
242 Context: buildCtx,
243 Overlay: map[string]load.Source{},
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400244 }
gio308105e2024-04-19 13:12:13 +0400245 names := make([]string, 0)
246 for n, b := range data {
247 a := fmt.Sprintf("/%s", n)
248 names = append(names, a)
249 cfg.Overlay[a] = load.FromString("package main\n\n" + string(b))
250 }
251 instances := load.Instances(names, cfg)
252 for _, inst := range instances {
253 if inst.Err != nil {
254 return cue.Value{}, inst.Err
255 }
256 }
257 if len(instances) != 1 {
258 return cue.Value{}, fmt.Errorf("invalid")
259 }
260 ret := ctx.BuildInstance(instances[0])
gioc81a8472024-09-24 13:06:19 +0200261 if err := ret.Err(); err != nil {
262 return cue.Value{}, err
gio308105e2024-04-19 13:12:13 +0400263 }
264 if err := ret.Validate(); err != nil {
265 return cue.Value{}, err
266 }
267 return ret, nil
268}
269
270func newCueApp(config cue.Value, data CueAppData) (cueApp, error) {
gio3cdee592024-04-17 10:15:56 +0400271 cfg := struct {
272 Name string `json:"name"`
273 Namespace string `json:"namespace"`
274 Description string `json:"description"`
275 Icon string `json:"icon"`
276 }{}
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400277 if err := config.Decode(&cfg); err != nil {
278 return cueApp{}, err
279 }
gio44f621b2024-04-29 09:44:38 +0400280 schema, err := NewCueSchema("input", config.LookupPath(cue.ParsePath("input")))
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400281 if err != nil {
282 return cueApp{}, err
283 }
284 return cueApp{
285 name: cfg.Name,
286 description: cfg.Description,
287 icon: template.HTML(cfg.Icon),
288 namespace: cfg.Namespace,
289 schema: schema,
290 cfg: config,
gio308105e2024-04-19 13:12:13 +0400291 data: data,
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400292 }, nil
293}
294
gio308105e2024-04-19 13:12:13 +0400295func ParseAndCreateNewCueApp(data CueAppData) (cueApp, error) {
296 config, err := ParseCueAppConfig(data)
297 if err != nil {
298 return cueApp{}, err
299 }
300 return newCueApp(config, data)
301}
302
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400303func (a cueApp) Name() string {
304 return a.name
305}
306
gio44f621b2024-04-29 09:44:38 +0400307func (a cueApp) Slug() string {
308 return strings.ReplaceAll(strings.ToLower(a.name), " ", "-")
309}
310
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400311func (a cueApp) Description() string {
312 return a.description
313}
314
315func (a cueApp) Icon() template.HTML {
316 return a.icon
317}
318
319func (a cueApp) Schema() Schema {
320 return a.schema
321}
322
gioef01fbb2024-04-12 16:52:59 +0400323func (a cueApp) Namespace() string {
324 return a.namespace
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400325}
326
gioe72b54f2024-04-22 10:44:41 +0400327func (a cueApp) render(values map[string]any) (rendered, error) {
328 ret := rendered{
gio44f621b2024-04-29 09:44:38 +0400329 Name: a.Slug(),
gio308105e2024-04-19 13:12:13 +0400330 Resources: make(CueAppData),
giof8843412024-05-22 16:38:05 +0400331 HelmCharts: HelmCharts{
332 Git: make(map[string]HelmChartGitRepo),
333 },
334 ContainerImages: make(map[string]ContainerImage),
335 Ports: make([]PortForward, 0),
336 Data: a.data,
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400337 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400338 var buf bytes.Buffer
gio3cdee592024-04-17 10:15:56 +0400339 if err := json.NewEncoder(&buf).Encode(values); err != nil {
gioe72b54f2024-04-22 10:44:41 +0400340 return rendered{}, err
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400341 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400342 ctx := a.cfg.Context()
343 d := ctx.CompileBytes(buf.Bytes())
344 res := a.cfg.Unify(d).Eval()
345 if err := res.Err(); err != nil {
gioa1f29472025-05-14 13:05:05 +0400346 return rendered{}, fmt.Errorf(errors.Details(err, nil))
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400347 }
348 if err := res.Validate(); err != nil {
gioe72b54f2024-04-22 10:44:41 +0400349 return rendered{}, err
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400350 }
gio308105e2024-04-19 13:12:13 +0400351 full, err := json.MarshalIndent(res, "", "\t")
352 if err != nil {
gioe72b54f2024-04-22 10:44:41 +0400353 return rendered{}, err
gio308105e2024-04-19 13:12:13 +0400354 }
gio94904702024-07-26 16:58:34 +0400355 ret.Raw = full
gio308105e2024-04-19 13:12:13 +0400356 ret.Data["rendered.json"] = full
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400357 readme, err := res.LookupPath(cue.ParsePath("readme")).String()
358 if err != nil {
gioe72b54f2024-04-22 10:44:41 +0400359 return rendered{}, err
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400360 }
361 ret.Readme = readme
giof6ad2982024-08-23 17:42:49 +0400362 res.LookupPath(cue.ParsePath("input.cluster.name")).Decode(&ret.Cluster)
363 if err := res.LookupPath(cue.ParsePath("output.clusterProxy")).Decode(&ret.ClusterProxies); err != nil {
364 return rendered{}, err
365 }
366 if err := res.LookupPath(cue.ParsePath("namespaces")).Decode(&ret.Namespaces); err != nil {
367 return rendered{}, err
368 }
gio802311e2024-11-04 08:37:34 +0400369 if err := res.LookupPath(cue.ParsePath("output.openPort")).Decode(&ret.Ports); err != nil {
gioe72b54f2024-04-22 10:44:41 +0400370 return rendered{}, err
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400371 }
gio0eaf2712024-04-14 13:08:46 +0400372 {
gio7fbd4ad2024-08-27 10:06:39 +0400373 charts := res.LookupPath(cue.ParsePath("output.charts"))
giof8843412024-05-22 16:38:05 +0400374 i, err := charts.Fields()
375 if err != nil {
376 return rendered{}, err
377 }
378 for i.Next() {
379 var chartRef helmChartRef
380 if err := i.Value().Decode(&chartRef); err != nil {
381 return rendered{}, err
382 }
383 if chartRef.Kind == "GitRepository" {
384 var chart HelmChartGitRepo
385 if err := i.Value().Decode(&chart); err != nil {
386 return rendered{}, err
387 }
388 ret.HelmCharts.Git[cleanName(i.Selector().String())] = chart
389 }
390 }
391 }
392 {
gio7fbd4ad2024-08-27 10:06:39 +0400393 images := res.LookupPath(cue.ParsePath("output.images"))
giof8843412024-05-22 16:38:05 +0400394 i, err := images.Fields()
395 if err != nil {
396 return rendered{}, err
397 }
398 for i.Next() {
399 var img ContainerImage
400 if err := i.Value().Decode(&img); err != nil {
401 return rendered{}, err
402 }
403 ret.ContainerImages[cleanName(i.Selector().String())] = img
404 }
405 }
406 {
gio7fbd4ad2024-08-27 10:06:39 +0400407 helm := res.LookupPath(cue.ParsePath("output.helm"))
408 i, err := helm.Fields()
gio0eaf2712024-04-14 13:08:46 +0400409 if err != nil {
gioe72b54f2024-04-22 10:44:41 +0400410 return rendered{}, err
gio0eaf2712024-04-14 13:08:46 +0400411 }
412 for i.Next() {
413 if contents, err := cueyaml.Encode(i.Value()); err != nil {
414 return rendered{}, err
415 } else {
416 name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
417 ret.Resources[name] = contents
418 }
419 }
420 }
421 {
422 resources := res.LookupPath(cue.ParsePath("resources"))
423 i, err := resources.Fields()
424 if err != nil {
425 return rendered{}, err
426 }
427 for i.Next() {
428 if contents, err := cueyaml.Encode(i.Value()); err != nil {
429 return rendered{}, err
430 } else {
431 name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
432 ret.Resources[name] = contents
433 }
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400434 }
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400435 }
Davit Tabidze56f86a42024-04-09 19:15:25 +0400436 helpValue := res.LookupPath(cue.ParsePath("help"))
437 if helpValue.Exists() {
438 if err := helpValue.Decode(&ret.Help); err != nil {
gioe72b54f2024-04-22 10:44:41 +0400439 return rendered{}, err
Davit Tabidze56f86a42024-04-09 19:15:25 +0400440 }
441 }
442 url, err := res.LookupPath(cue.ParsePath("url")).String()
443 if err != nil {
gioe72b54f2024-04-22 10:44:41 +0400444 return rendered{}, err
Davit Tabidze56f86a42024-04-09 19:15:25 +0400445 }
gio09a3e5b2024-04-26 14:11:06 +0400446 ret.URL = url
Davit Tabidze56f86a42024-04-09 19:15:25 +0400447 icon, err := res.LookupPath(cue.ParsePath("icon")).String()
448 if err != nil {
gioe72b54f2024-04-22 10:44:41 +0400449 return rendered{}, err
Davit Tabidze56f86a42024-04-09 19:15:25 +0400450 }
451 ret.Icon = icon
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400452 return ret, nil
453}
454
gio3cdee592024-04-17 10:15:56 +0400455type cueEnvApp struct {
456 cueApp
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400457}
458
gio308105e2024-04-19 13:12:13 +0400459func NewCueEnvApp(data CueAppData) (EnvApp, error) {
460 app, err := ParseAndCreateNewCueApp(data)
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400461 if err != nil {
462 return nil, err
463 }
gio3cdee592024-04-17 10:15:56 +0400464 return cueEnvApp{app}, nil
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400465}
466
gio0eaf2712024-04-14 13:08:46 +0400467func NewDodoApp(appCfg []byte) (EnvApp, error) {
468 return NewCueEnvApp(CueAppData{
giofc9c4ea2024-06-26 13:46:53 +0400469 "app.cue": appCfg,
470 "base.cue": []byte(cueBaseConfig),
471 "dodo.cue": dodoAppCue,
472 "env.cue": []byte(cueEnvAppGlobal),
gio0eaf2712024-04-14 13:08:46 +0400473 })
474}
475
gio3cdee592024-04-17 10:15:56 +0400476func (a cueEnvApp) Type() AppType {
477 return AppTypeEnv
478}
479
giofc441e32024-11-11 16:26:14 +0400480func merge(d map[string]any, v map[string]any) map[string]any {
481 ret := map[string]any{}
482 for k, val := range d {
483 if vv, ok := v[k]; ok && vv != nil {
484 if mv, ok := val.(map[string]any); ok {
485 // TODO(gio): check that it is actually map
giofc441e32024-11-11 16:26:14 +0400486 ret[k] = merge(mv, vv.(map[string]any))
487 } else {
488 ret[k] = vv
489 }
490 } else {
491 ret[k] = val
492 }
493 }
494 for k, v := range v {
495 if v != nil {
496 if _, ok := d[k]; !ok {
gio838bcb82025-05-15 19:39:04 +0400497 ret[k] = v
giofc441e32024-11-11 16:26:14 +0400498 }
499 }
500 }
501 return ret
502}
503
giocb34ad22024-07-11 08:01:13 +0400504func (a cueEnvApp) Render(
505 release Release,
506 env EnvConfig,
507 networks []Network,
giof6ad2982024-08-23 17:42:49 +0400508 clusters []Cluster,
giocb34ad22024-07-11 08:01:13 +0400509 values map[string]any,
510 charts map[string]helmv2.HelmChartTemplateSpec,
gio864b4332024-09-05 13:56:47 +0400511 vpnKeyGen VPNAPIClient,
giocb34ad22024-07-11 08:01:13 +0400512) (EnvAppRendered, error) {
giofc441e32024-11-11 16:26:14 +0400513 dv, err := ExtractDefaultValues(a.cueApp.cfg.LookupPath(cue.ParsePath("input")))
514 if err != nil {
515 return EnvAppRendered{}, err
516 }
517 mv := merge(dv.(map[string]any), values)
518 derived, err := deriveValues(mv, values, a.Schema(), networks, clusters, vpnKeyGen)
gio3cdee592024-04-17 10:15:56 +0400519 if err != nil {
gioefa0ed42024-06-13 12:31:43 +0400520 return EnvAppRendered{}, err
gio3cdee592024-04-17 10:15:56 +0400521 }
giof8843412024-05-22 16:38:05 +0400522 if charts == nil {
523 charts = make(map[string]helmv2.HelmChartTemplateSpec)
524 }
giof15b9da2024-09-19 06:59:16 +0400525 if clusters == nil {
526 clusters = []Cluster{}
527 }
gio3cdee592024-04-17 10:15:56 +0400528 ret, err := a.cueApp.render(map[string]any{
giof8843412024-05-22 16:38:05 +0400529 "global": env,
530 "release": release,
531 "input": derived,
532 "localCharts": charts,
gio5e49bb62024-07-20 10:43:19 +0400533 "networks": NetworkMap(networks),
giof15b9da2024-09-19 06:59:16 +0400534 "clusters": clusters,
gio3cdee592024-04-17 10:15:56 +0400535 })
536 if err != nil {
gioe72b54f2024-04-22 10:44:41 +0400537 return EnvAppRendered{}, err
gio3cdee592024-04-17 10:15:56 +0400538 }
gioe72b54f2024-04-22 10:44:41 +0400539 return EnvAppRendered{
540 rendered: ret,
541 Config: AppInstanceConfig{
gio44f621b2024-04-29 09:44:38 +0400542 AppId: a.Slug(),
gioe72b54f2024-04-22 10:44:41 +0400543 Env: env,
544 Release: release,
545 Values: values,
546 Input: derived,
gio09a3e5b2024-04-26 14:11:06 +0400547 URL: ret.URL,
gioe72b54f2024-04-22 10:44:41 +0400548 Help: ret.Help,
gio09a3e5b2024-04-26 14:11:06 +0400549 Icon: ret.Icon,
gioe72b54f2024-04-22 10:44:41 +0400550 },
551 }, nil
gio3cdee592024-04-17 10:15:56 +0400552}
553
554type cueInfraApp struct {
555 cueApp
556}
557
gio308105e2024-04-19 13:12:13 +0400558func NewCueInfraApp(data CueAppData) (InfraApp, error) {
559 app, err := ParseAndCreateNewCueApp(data)
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400560 if err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400561 return nil, err
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400562 }
gio3cdee592024-04-17 10:15:56 +0400563 return cueInfraApp{app}, nil
564}
565
566func (a cueInfraApp) Type() AppType {
567 return AppTypeInfra
568}
569
gio7841f4f2024-07-26 19:53:49 +0400570func (a cueInfraApp) Render(release Release, infra InfraConfig, networks []InfraNetwork, values map[string]any, charts map[string]helmv2.HelmChartTemplateSpec) (InfraAppRendered, error) {
giof8843412024-05-22 16:38:05 +0400571 if charts == nil {
572 charts = make(map[string]helmv2.HelmChartTemplateSpec)
573 }
gioe72b54f2024-04-22 10:44:41 +0400574 ret, err := a.cueApp.render(map[string]any{
giof8843412024-05-22 16:38:05 +0400575 "global": infra,
576 "release": release,
577 "input": values,
578 "localCharts": charts,
gio7841f4f2024-07-26 19:53:49 +0400579 "networks": InfraNetworkMap(networks),
gio3cdee592024-04-17 10:15:56 +0400580 })
gioe72b54f2024-04-22 10:44:41 +0400581 if err != nil {
582 return InfraAppRendered{}, err
583 }
584 return InfraAppRendered{
585 rendered: ret,
586 Config: InfraAppInstanceConfig{
gio44f621b2024-04-29 09:44:38 +0400587 AppId: a.Slug(),
gioe72b54f2024-04-22 10:44:41 +0400588 Infra: infra,
589 Release: release,
590 Values: values,
591 Input: values,
gio09a3e5b2024-04-26 14:11:06 +0400592 URL: ret.URL,
593 Help: ret.Help,
gioe72b54f2024-04-22 10:44:41 +0400594 },
595 }, nil
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400596}
597
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400598func cleanName(s string) string {
599 return strings.ReplaceAll(strings.ReplaceAll(s, "\"", ""), "'", "")
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400600}
gioe72b54f2024-04-22 10:44:41 +0400601
602func join[T fmt.Stringer](items []T, sep string) string {
603 var tmp []string
604 for _, i := range items {
605 tmp = append(tmp, i.String())
606 }
607 return strings.Join(tmp, ",")
608}
gio0eaf2712024-04-14 13:08:46 +0400609
gio5e49bb62024-07-20 10:43:19 +0400610func NetworkMap(networks []Network) map[string]Network {
gio0eaf2712024-04-14 13:08:46 +0400611 ret := make(map[string]Network)
612 for _, n := range networks {
613 ret[strings.ToLower(n.Name)] = n
614 }
615 return ret
616}
gio7841f4f2024-07-26 19:53:49 +0400617
618func InfraNetworkMap(networks []InfraNetwork) map[string]InfraNetwork {
619 ret := make(map[string]InfraNetwork)
620 for _, n := range networks {
621 ret[strings.ToLower(n.Name)] = n
622 }
623 return ret
624}