blob: cec7cbf3f4490c5d0d6227fb8ba0b6ec3d0d3c45 [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"
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +04005 "encoding/json"
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +04006 "fmt"
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +04007 template "html/template"
gio3cdee592024-04-17 10:15:56 +04008 "net"
gioe72b54f2024-04-22 10:44:41 +04009 "net/netip"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040010 "strings"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040011
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040012 "cuelang.org/go/cue"
gio308105e2024-04-19 13:12:13 +040013 "cuelang.org/go/cue/build"
14 "cuelang.org/go/cue/cuecontext"
15 "cuelang.org/go/cue/load"
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040016 cueyaml "cuelang.org/go/encoding/yaml"
giolekva8aa73e82022-07-09 11:34:39 +040017)
giolekva050609f2021-12-29 15:51:40 +040018
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040019// TODO(gio): import
gioe72b54f2024-04-22 10:44:41 +040020const cueEnvAppGlobal = `
21#Global: {
22 id: string | *""
23 pcloudEnvName: string | *""
24 domain: string | *""
25 privateDomain: string | *""
26 contactEmail: string | *""
27 adminPublicKey: string | *""
28 publicIP: [...string] | *[]
29 nameserverIP: [...string] | *[]
30 namespacePrefix: string | *""
31 network: #EnvNetwork
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040032}
33
Giorgi Lekveishvili67383962024-03-22 19:27:34 +040034networks: {
35 public: #Network & {
36 name: "Public"
37 ingressClass: "\(global.pcloudEnvName)-ingress-public"
38 certificateIssuer: "\(global.id)-public"
39 domain: global.domain
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040040 allocatePortAddr: "http://port-allocator.\(global.pcloudEnvName)-ingress-public.svc.cluster.local/api/allocate"
Giorgi Lekveishvili67383962024-03-22 19:27:34 +040041 }
42 private: #Network & {
43 name: "Private"
44 ingressClass: "\(global.id)-ingress-private"
45 domain: global.privateDomain
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +040046 allocatePortAddr: "http://port-allocator.\(global.id)-ingress-private.svc.cluster.local/api/allocate"
Giorgi Lekveishvili67383962024-03-22 19:27:34 +040047 }
48}
49
gioe72b54f2024-04-22 10:44:41 +040050// TODO(gio): remove
51ingressPrivate: "\(global.id)-ingress-private"
52ingressPublic: "\(global.pcloudEnvName)-ingress-public"
53issuerPrivate: "\(global.id)-private"
54issuerPublic: "\(global.id)-public"
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040055
gio1de49582024-04-21 08:33:57 +040056#Ingress: {
57 auth: #Auth
58 network: #Network
59 subdomain: string
60 service: close({
61 name: string
62 port: close({ name: string }) | close({ number: int & > 0 })
63 })
Giorgi Lekveishvili67383962024-03-22 19:27:34 +040064
gio1de49582024-04-21 08:33:57 +040065 _domain: "\(subdomain).\(network.domain)"
Giorgi Lekveishvili67383962024-03-22 19:27:34 +040066 _authProxyHTTPPortName: "http"
67
68 out: {
69 images: {
70 authProxy: #Image & {
71 repository: "giolekva"
72 name: "auth-proxy"
73 tag: "latest"
74 pullPolicy: "Always"
75 }
76 }
77 charts: {
78 ingress: #Chart & {
79 chart: "charts/ingress"
80 sourceRef: {
81 kind: "GitRepository"
82 name: "pcloud"
83 namespace: global.id
84 }
85 }
86 authProxy: #Chart & {
87 chart: "charts/auth-proxy"
88 sourceRef: {
89 kind: "GitRepository"
90 name: "pcloud"
91 namespace: global.id
92 }
93 }
94 }
95 helm: {
gio1de49582024-04-21 08:33:57 +040096 if auth.enabled {
Giorgi Lekveishvili67383962024-03-22 19:27:34 +040097 "auth-proxy": {
98 chart: charts.authProxy
99 values: {
100 image: {
101 repository: images.authProxy.fullName
102 tag: images.authProxy.tag
103 pullPolicy: images.authProxy.pullPolicy
104 }
gio1de49582024-04-21 08:33:57 +0400105 upstream: "\(service.name).\(release.namespace).svc.cluster.local"
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400106 whoAmIAddr: "https://accounts.\(global.domain)/sessions/whoami"
107 loginAddr: "https://accounts-ui.\(global.domain)/login"
Giorgi Lekveishvili329af572024-03-25 20:14:41 +0400108 membershipAddr: "http://memberships-api.\(global.id)-core-auth-memberships.svc.cluster.local/api/user"
gio1de49582024-04-21 08:33:57 +0400109 groups: auth.groups
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400110 portName: _authProxyHTTPPortName
111 }
112 }
113 }
114 ingress: {
115 chart: charts.ingress
gio1de49582024-04-21 08:33:57 +0400116 _service: service
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400117 values: {
118 domain: _domain
gio1de49582024-04-21 08:33:57 +0400119 ingressClassName: network.ingressClass
120 certificateIssuer: network.certificateIssuer
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400121 service: {
gio1de49582024-04-21 08:33:57 +0400122 if auth.enabled {
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400123 name: "auth-proxy"
124 port: name: _authProxyHTTPPortName
125 }
gio1de49582024-04-21 08:33:57 +0400126 if !auth.enabled {
127 name: _service.name
128 if _service.port.name != _|_ {
129 port: name: _service.port.name
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400130 }
gio1de49582024-04-21 08:33:57 +0400131 if _service.port.number != _|_ {
132 port: number: _service.port.number
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400133 }
134 }
135 }
136 }
137 }
138 }
139 }
140}
141
gio1de49582024-04-21 08:33:57 +0400142ingress: {}
143
144_ingressValidate: {
145 for key, value in ingress {
146 "\(key)": #Ingress & value
147 }
148}
gioe72b54f2024-04-22 10:44:41 +0400149`
150
151const cueInfraAppGlobal = `
152#Global: {
153 pcloudEnvName: string | *""
154 publicIP: [...string] | *[]
155 namespacePrefix: string | *""
156 infraAdminPublicKey: string | *""
157}
158
159// TODO(gio): remove
160ingressPublic: "\(global.pcloudEnvName)-ingress-public"
161
162ingress: {}
163_ingressValidate: {}
164`
165
166const cueBaseConfig = `
167import (
168 "net"
169)
170
171name: string | *""
172description: string | *""
173readme: string | *""
174icon: string | *""
175namespace: string | *""
176
177help: [...#HelpDocument] | *[]
178
179#HelpDocument: {
180 title: string
181 contents: string
182 children: [...#HelpDocument] | *[]
183}
184
185url: string | *""
186
187#AppType: "infra" | "env"
188appType: #AppType | *"env"
189
190#Auth: {
191 enabled: bool | *false // TODO(gio): enabled by default?
192 groups: string | *"" // TODO(gio): []string
193}
194
195#Network: {
196 name: string
197 ingressClass: string
198 certificateIssuer: string | *""
199 domain: string
200 allocatePortAddr: string
201}
202
203#Image: {
204 registry: string | *"docker.io"
205 repository: string
206 name: string
207 tag: string
208 pullPolicy: string | *"IfNotPresent"
209 imageName: "\(repository)/\(name)"
210 fullName: "\(registry)/\(imageName)"
211 fullNameWithTag: "\(fullName):\(tag)"
212}
213
214#Chart: {
215 chart: string
216 sourceRef: #SourceRef
217}
218
219#SourceRef: {
220 kind: "GitRepository" | "HelmRepository"
221 name: string
222 namespace: string // TODO(gio): default global.id
223}
224
225#EnvNetwork: {
226 dns: net.IPv4
227 dnsInClusterIP: net.IPv4
228 ingress: net.IPv4
229 headscale: net.IPv4
230 servicesFrom: net.IPv4
231 servicesTo: net.IPv4
232}
233
234#Release: {
235 appInstanceId: string
236 namespace: string
237 repoAddr: string
238 appDir: string
239}
240
241#PortForward: {
242 allocator: string
243 protocol: "TCP" | "UDP" | *"TCP"
244 sourcePort: int
245 targetService: string
246 targetPort: int
247}
248
249portForward: [...#PortForward] | *[]
250
251global: #Global
252release: #Release
gio1de49582024-04-21 08:33:57 +0400253
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400254images: {
255 for key, value in images {
256 "\(key)": #Image & value
257 }
gio1de49582024-04-21 08:33:57 +0400258 for _, value in _ingressValidate {
259 for name, image in value.out.images {
260 "\(name)": #Image & image
261 }
262 }
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400263}
264
265charts: {
266 for key, value in charts {
267 "\(key)": #Chart & value
268 }
gio1de49582024-04-21 08:33:57 +0400269 for _, value in _ingressValidate {
270 for name, chart in value.out.charts {
271 "\(name)": #Chart & chart
272 }
273 }
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400274}
275
276#ResourceReference: {
277 name: string
278 namespace: string
279}
280
281#Helm: {
282 name: string
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400283 dependsOn: [...#ResourceReference] | *[]
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400284 ...
285}
286
gio1de49582024-04-21 08:33:57 +0400287_helmValidate: {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400288 for key, value in helm {
289 "\(key)": #Helm & value & {
290 name: key
291 }
292 }
gio1de49582024-04-21 08:33:57 +0400293 for key, value in _ingressValidate {
294 for ing, ingValue in value.out.helm {
295 // TODO(gio): support multiple ingresses
296 // "\(key)-\(ing)": #Helm & ingValue & {
297 "\(ing)": #Helm & ingValue & {
298 // name: "\(key)-\(ing)"
299 name: ing
300 }
301 }
302 }
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400303}
304
305#HelmRelease: {
306 _name: string
307 _chart: #Chart
308 _values: _
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400309 _dependencies: [...#ResourceReference] | *[]
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400310
311 apiVersion: "helm.toolkit.fluxcd.io/v2beta1"
312 kind: "HelmRelease"
313 metadata: {
314 name: _name
315 namespace: release.namespace
316 }
317 spec: {
318 interval: "1m0s"
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400319 dependsOn: _dependencies
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400320 chart: {
321 spec: _chart
322 }
323 values: _values
324 }
325}
326
327output: {
gio1de49582024-04-21 08:33:57 +0400328 for name, r in _helmValidate {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400329 "\(name)": #HelmRelease & {
330 _name: name
331 _chart: r.chart
332 _values: r.values
333 _dependencies: r.dependsOn
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400334 }
335 }
336}
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400337
338#SSHKey: {
339 public: string
340 private: string
341}
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400342`
343
gioe72b54f2024-04-22 10:44:41 +0400344type rendered struct {
gio3cdee592024-04-17 10:15:56 +0400345 Name string
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400346 Readme string
gio308105e2024-04-19 13:12:13 +0400347 Resources CueAppData
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400348 Ports []PortForward
gio308105e2024-04-19 13:12:13 +0400349 Data CueAppData
Davit Tabidze56f86a42024-04-09 19:15:25 +0400350 Help []HelpDocument
351 Url string
352 Icon string
353}
354
355type HelpDocument struct {
356 Title string
357 Contents string
358 Children []HelpDocument
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400359}
360
gioe72b54f2024-04-22 10:44:41 +0400361type EnvAppRendered struct {
362 rendered
363 Config AppInstanceConfig
364}
365
366type InfraAppRendered struct {
367 rendered
368 Config InfraAppInstanceConfig
369}
370
gio3cdee592024-04-17 10:15:56 +0400371type PortForward struct {
372 Allocator string `json:"allocator"`
373 Protocol string `json:"protocol"`
374 SourcePort int `json:"sourcePort"`
375 TargetService string `json:"targetService"`
376 TargetPort int `json:"targetPort"`
377}
378
379type AppType int
380
381const (
382 AppTypeInfra AppType = iota
383 AppTypeEnv
384)
385
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400386type App interface {
gio3cdee592024-04-17 10:15:56 +0400387 Type() AppType
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400388 Name() string
389 Description() string
390 Icon() template.HTML
391 Schema() Schema
gioef01fbb2024-04-12 16:52:59 +0400392 Namespace() string
gio3cdee592024-04-17 10:15:56 +0400393}
394
395type InfraConfig struct {
gioe72b54f2024-04-22 10:44:41 +0400396 Name string `json:"pcloudEnvName,omitempty"` // #TODO(gio): change to name
397 PublicIP []net.IP `json:"publicIP,omitempty"`
398 InfraNamespacePrefix string `json:"namespacePrefix,omitempty"`
399 InfraAdminPublicKey []byte `json:"infraAdminPublicKey,omitempty"`
gio3cdee592024-04-17 10:15:56 +0400400}
401
402type InfraApp interface {
403 App
gioe72b54f2024-04-22 10:44:41 +0400404 Render(release Release, infra InfraConfig, values map[string]any) (InfraAppRendered, error)
405}
406
407type EnvNetwork struct {
408 DNS net.IP `json:"dns,omitempty"`
409 DNSInClusterIP net.IP `json:"dnsInClusterIP,omitempty"`
410 Ingress net.IP `json:"ingress,omitempty"`
411 Headscale net.IP `json:"headscale,omitempty"`
412 ServicesFrom net.IP `json:"servicesFrom,omitempty"`
413 ServicesTo net.IP `json:"servicesTo,omitempty"`
414}
415
416func NewEnvNetwork(subnet net.IP) (EnvNetwork, error) {
417 addr, err := netip.ParseAddr(subnet.String())
418 if err != nil {
419 return EnvNetwork{}, err
420 }
421 if !addr.Is4() {
422 return EnvNetwork{}, fmt.Errorf("Expected IPv4, got %s instead", addr)
423 }
424 dns := addr.Next()
425 ingress := dns.Next()
426 headscale := ingress.Next()
427 b := addr.AsSlice()
428 if b[3] != 0 {
429 return EnvNetwork{}, fmt.Errorf("Expected last byte to be zero, got %d instead", b[3])
430 }
431 b[3] = 10
432 servicesFrom, ok := netip.AddrFromSlice(b)
433 if !ok {
434 return EnvNetwork{}, fmt.Errorf("Must not reach")
435 }
436 b[3] = 254
437 servicesTo, ok := netip.AddrFromSlice(b)
438 if !ok {
439 return EnvNetwork{}, fmt.Errorf("Must not reach")
440 }
441 b[3] = b[2]
442 b[2] = b[1]
443 b[0] = 10
444 b[1] = 44
445 dnsInClusterIP, ok := netip.AddrFromSlice(b)
446 if !ok {
447 return EnvNetwork{}, fmt.Errorf("Must not reach")
448 }
449 return EnvNetwork{
450 DNS: net.ParseIP(dns.String()),
451 DNSInClusterIP: net.ParseIP(dnsInClusterIP.String()),
452 Ingress: net.ParseIP(ingress.String()),
453 Headscale: net.ParseIP(headscale.String()),
454 ServicesFrom: net.ParseIP(servicesFrom.String()),
455 ServicesTo: net.ParseIP(servicesTo.String()),
456 }, nil
gio3cdee592024-04-17 10:15:56 +0400457}
458
459// TODO(gio): rename to EnvConfig
gioe72b54f2024-04-22 10:44:41 +0400460type EnvConfig struct {
461 Id string `json:"id,omitempty"`
462 InfraName string `json:"pcloudEnvName,omitempty"`
463 Domain string `json:"domain,omitempty"`
464 PrivateDomain string `json:"privateDomain,omitempty"`
465 ContactEmail string `json:"contactEmail,omitempty"`
466 AdminPublicKey string `json:"adminPublicKey,omitempty"`
467 PublicIP []net.IP `json:"publicIP,omitempty"`
468 NameserverIP []net.IP `json:"nameserverIP,omitempty"`
469 NamespacePrefix string `json:"namespacePrefix,omitempty"`
470 Network EnvNetwork `json:"network,omitempty"`
gio3cdee592024-04-17 10:15:56 +0400471}
472
473type EnvApp interface {
474 App
gioe72b54f2024-04-22 10:44:41 +0400475 Render(release Release, env EnvConfig, values map[string]any) (EnvAppRendered, error)
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400476}
477
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400478type cueApp struct {
479 name string
480 description string
481 icon template.HTML
482 namespace string
483 schema Schema
gio308105e2024-04-19 13:12:13 +0400484 cfg cue.Value
485 data CueAppData
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400486}
487
gio308105e2024-04-19 13:12:13 +0400488type CueAppData map[string][]byte
489
490func ParseCueAppConfig(data CueAppData) (cue.Value, error) {
491 ctx := cuecontext.New()
492 buildCtx := build.NewContext()
493 cfg := &load.Config{
494 Context: buildCtx,
495 Overlay: map[string]load.Source{},
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400496 }
gio308105e2024-04-19 13:12:13 +0400497 names := make([]string, 0)
498 for n, b := range data {
499 a := fmt.Sprintf("/%s", n)
500 names = append(names, a)
501 cfg.Overlay[a] = load.FromString("package main\n\n" + string(b))
502 }
503 instances := load.Instances(names, cfg)
504 for _, inst := range instances {
505 if inst.Err != nil {
506 return cue.Value{}, inst.Err
507 }
508 }
509 if len(instances) != 1 {
510 return cue.Value{}, fmt.Errorf("invalid")
511 }
512 ret := ctx.BuildInstance(instances[0])
513 if ret.Err() != nil {
514 return cue.Value{}, ret.Err()
515 }
516 if err := ret.Validate(); err != nil {
517 return cue.Value{}, err
518 }
519 return ret, nil
520}
521
522func newCueApp(config cue.Value, data CueAppData) (cueApp, error) {
gio3cdee592024-04-17 10:15:56 +0400523 cfg := struct {
524 Name string `json:"name"`
525 Namespace string `json:"namespace"`
526 Description string `json:"description"`
527 Icon string `json:"icon"`
528 }{}
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400529 if err := config.Decode(&cfg); err != nil {
530 return cueApp{}, err
531 }
532 schema, err := NewCueSchema(config.LookupPath(cue.ParsePath("input")))
533 if err != nil {
534 return cueApp{}, err
535 }
536 return cueApp{
537 name: cfg.Name,
538 description: cfg.Description,
539 icon: template.HTML(cfg.Icon),
540 namespace: cfg.Namespace,
541 schema: schema,
542 cfg: config,
gio308105e2024-04-19 13:12:13 +0400543 data: data,
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400544 }, nil
545}
546
gio308105e2024-04-19 13:12:13 +0400547func ParseAndCreateNewCueApp(data CueAppData) (cueApp, error) {
548 config, err := ParseCueAppConfig(data)
549 if err != nil {
550 return cueApp{}, err
551 }
552 return newCueApp(config, data)
553}
554
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400555func (a cueApp) Name() string {
556 return a.name
557}
558
559func (a cueApp) Description() string {
560 return a.description
561}
562
563func (a cueApp) Icon() template.HTML {
564 return a.icon
565}
566
567func (a cueApp) Schema() Schema {
568 return a.schema
569}
570
gioef01fbb2024-04-12 16:52:59 +0400571func (a cueApp) Namespace() string {
572 return a.namespace
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400573}
574
gioe72b54f2024-04-22 10:44:41 +0400575func (a cueApp) render(values map[string]any) (rendered, error) {
576 ret := rendered{
gio3cdee592024-04-17 10:15:56 +0400577 Name: a.Name(),
gio308105e2024-04-19 13:12:13 +0400578 Resources: make(CueAppData),
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400579 Ports: make([]PortForward, 0),
gio308105e2024-04-19 13:12:13 +0400580 Data: a.data,
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400581 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400582 var buf bytes.Buffer
gio3cdee592024-04-17 10:15:56 +0400583 if err := json.NewEncoder(&buf).Encode(values); err != nil {
gioe72b54f2024-04-22 10:44:41 +0400584 return rendered{}, err
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400585 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400586 ctx := a.cfg.Context()
587 d := ctx.CompileBytes(buf.Bytes())
588 res := a.cfg.Unify(d).Eval()
589 if err := res.Err(); err != nil {
gioe72b54f2024-04-22 10:44:41 +0400590 return rendered{}, err
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400591 }
592 if err := res.Validate(); err != nil {
gioe72b54f2024-04-22 10:44:41 +0400593 return rendered{}, err
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400594 }
gio308105e2024-04-19 13:12:13 +0400595 full, err := json.MarshalIndent(res, "", "\t")
596 if err != nil {
gioe72b54f2024-04-22 10:44:41 +0400597 return rendered{}, err
gio308105e2024-04-19 13:12:13 +0400598 }
599 ret.Data["rendered.json"] = full
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400600 readme, err := res.LookupPath(cue.ParsePath("readme")).String()
601 if err != nil {
gioe72b54f2024-04-22 10:44:41 +0400602 return rendered{}, err
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400603 }
604 ret.Readme = readme
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400605 if err := res.LookupPath(cue.ParsePath("portForward")).Decode(&ret.Ports); err != nil {
gioe72b54f2024-04-22 10:44:41 +0400606 return rendered{}, err
Giorgi Lekveishvilib59b7c22024-04-03 22:17:50 +0400607 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400608 output := res.LookupPath(cue.ParsePath("output"))
609 i, err := output.Fields()
610 if err != nil {
gioe72b54f2024-04-22 10:44:41 +0400611 return rendered{}, err
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400612 }
613 for i.Next() {
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400614 if contents, err := cueyaml.Encode(i.Value()); err != nil {
gioe72b54f2024-04-22 10:44:41 +0400615 return rendered{}, err
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400616 } else {
617 name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
618 ret.Resources[name] = contents
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400619 }
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400620 }
Davit Tabidze56f86a42024-04-09 19:15:25 +0400621 helpValue := res.LookupPath(cue.ParsePath("help"))
622 if helpValue.Exists() {
623 if err := helpValue.Decode(&ret.Help); err != nil {
gioe72b54f2024-04-22 10:44:41 +0400624 return rendered{}, err
Davit Tabidze56f86a42024-04-09 19:15:25 +0400625 }
626 }
627 url, err := res.LookupPath(cue.ParsePath("url")).String()
628 if err != nil {
gioe72b54f2024-04-22 10:44:41 +0400629 return rendered{}, err
Davit Tabidze56f86a42024-04-09 19:15:25 +0400630 }
631 ret.Url = url
632 icon, err := res.LookupPath(cue.ParsePath("icon")).String()
633 if err != nil {
gioe72b54f2024-04-22 10:44:41 +0400634 return rendered{}, err
Davit Tabidze56f86a42024-04-09 19:15:25 +0400635 }
636 ret.Icon = icon
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400637 return ret, nil
638}
639
gio3cdee592024-04-17 10:15:56 +0400640type cueEnvApp struct {
641 cueApp
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400642}
643
gio308105e2024-04-19 13:12:13 +0400644func NewCueEnvApp(data CueAppData) (EnvApp, error) {
645 app, err := ParseAndCreateNewCueApp(data)
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400646 if err != nil {
647 return nil, err
648 }
gio3cdee592024-04-17 10:15:56 +0400649 return cueEnvApp{app}, nil
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400650}
651
gio3cdee592024-04-17 10:15:56 +0400652func (a cueEnvApp) Type() AppType {
653 return AppTypeEnv
654}
655
gioe72b54f2024-04-22 10:44:41 +0400656func (a cueEnvApp) Render(release Release, env EnvConfig, values map[string]any) (EnvAppRendered, error) {
gio3cdee592024-04-17 10:15:56 +0400657 networks := CreateNetworks(env)
658 derived, err := deriveValues(values, a.Schema(), networks)
659 if err != nil {
gioe72b54f2024-04-22 10:44:41 +0400660 return EnvAppRendered{}, nil
gio3cdee592024-04-17 10:15:56 +0400661 }
662 ret, err := a.cueApp.render(map[string]any{
663 "global": env,
664 "release": release,
665 "input": derived,
666 })
667 if err != nil {
gioe72b54f2024-04-22 10:44:41 +0400668 return EnvAppRendered{}, err
gio3cdee592024-04-17 10:15:56 +0400669 }
gioe72b54f2024-04-22 10:44:41 +0400670 return EnvAppRendered{
671 rendered: ret,
672 Config: AppInstanceConfig{
673 AppId: a.Name(),
674 Env: env,
675 Release: release,
676 Values: values,
677 Input: derived,
678 Help: ret.Help,
679 Url: ret.Url,
680 },
681 }, nil
gio3cdee592024-04-17 10:15:56 +0400682}
683
684type cueInfraApp struct {
685 cueApp
686}
687
gio308105e2024-04-19 13:12:13 +0400688func NewCueInfraApp(data CueAppData) (InfraApp, error) {
689 app, err := ParseAndCreateNewCueApp(data)
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400690 if err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400691 return nil, err
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400692 }
gio3cdee592024-04-17 10:15:56 +0400693 return cueInfraApp{app}, nil
694}
695
696func (a cueInfraApp) Type() AppType {
697 return AppTypeInfra
698}
699
gioe72b54f2024-04-22 10:44:41 +0400700func (a cueInfraApp) Render(release Release, infra InfraConfig, values map[string]any) (InfraAppRendered, error) {
701 ret, err := a.cueApp.render(map[string]any{
gio3cdee592024-04-17 10:15:56 +0400702 "global": infra,
703 "release": release,
704 "input": values,
705 })
gioe72b54f2024-04-22 10:44:41 +0400706 if err != nil {
707 return InfraAppRendered{}, err
708 }
709 return InfraAppRendered{
710 rendered: ret,
711 Config: InfraAppInstanceConfig{
712 AppId: a.Name(),
713 Infra: infra,
714 Release: release,
715 Values: values,
716 Input: values,
717 },
718 }, nil
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400719}
720
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400721func cleanName(s string) string {
722 return strings.ReplaceAll(strings.ReplaceAll(s, "\"", ""), "'", "")
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400723}
gioe72b54f2024-04-22 10:44:41 +0400724
725func join[T fmt.Stringer](items []T, sep string) string {
726 var tmp []string
727 for _, i := range items {
728 tmp = append(tmp, i.String())
729 }
730 return strings.Join(tmp, ",")
731}