blob: 64c4078e460b398614af329edbecf2988c4a5dff [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 Lekveishvili743fb432023-11-08 17:19:40 +04004 "archive/tar"
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +04005 "bytes"
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +04006 "compress/gzip"
giolekva8aa73e82022-07-09 11:34:39 +04007 "embed"
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +04008 "encoding/json"
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +04009 "fmt"
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040010 template "html/template"
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040011 "io"
giolekva8aa73e82022-07-09 11:34:39 +040012 "log"
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040013 "net/http"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040014 "strings"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040015
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +040016 "cuelang.org/go/cue"
17 "cuelang.org/go/cue/cuecontext"
18 cueyaml "cuelang.org/go/encoding/yaml"
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040019 "github.com/go-git/go-billy/v5"
20 "sigs.k8s.io/yaml"
giolekva8aa73e82022-07-09 11:34:39 +040021)
giolekva050609f2021-12-29 15:51:40 +040022
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040023//go:embed values-tmpl
24var valuesTmpls embed.FS
25
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040026var storeAppConfigs = []string{
27 "values-tmpl/jellyfin.cue",
28 // "values-tmpl/maddy.cue",
29 "values-tmpl/matrix.cue",
30 "values-tmpl/penpot.cue",
31 "values-tmpl/pihole.cue",
32 "values-tmpl/qbittorrent.cue",
33 "values-tmpl/rpuppy.cue",
34 "values-tmpl/soft-serve.cue",
35 "values-tmpl/vaultwarden.cue",
DTabidze09935812024-03-13 13:47:39 +040036 "values-tmpl/url-shortener.cue",
Giorgi Lekveishviliee15ee22024-03-28 12:35:10 +040037 "values-tmpl/gerrit.cue",
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040038}
39
40var infraAppConfigs = []string{
41 "values-tmpl/appmanager.cue",
42 "values-tmpl/cert-manager.cue",
43 "values-tmpl/certificate-issuer-private.cue",
44 "values-tmpl/certificate-issuer-public.cue",
45 "values-tmpl/config-repo.cue",
46 "values-tmpl/core-auth.cue",
47 "values-tmpl/csi-driver-smb.cue",
48 "values-tmpl/dns-zone-manager.cue",
49 "values-tmpl/env-manager.cue",
50 "values-tmpl/fluxcd-reconciler.cue",
51 "values-tmpl/headscale-controller.cue",
52 "values-tmpl/headscale-user.cue",
53 "values-tmpl/headscale.cue",
54 "values-tmpl/ingress-public.cue",
55 "values-tmpl/metallb-ipaddresspool.cue",
56 "values-tmpl/private-network.cue",
57 "values-tmpl/resource-renderer-controller.cue",
58 "values-tmpl/welcome.cue",
DTabidze0d802592024-03-19 17:42:45 +040059 "values-tmpl/memberships.cue",
Giorgi Lekveishvili925f0de2024-03-14 18:51:56 +040060 "values-tmpl/hydra-maester.cue",
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040061}
62
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040063// TODO(gio): import
64const cueBaseConfig = `
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040065name: string | *""
66description: string | *""
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040067readme: string | *""
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040068icon: string | *""
69namespace: string | *""
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040070
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040071#Auth: {
72 enabled: bool | *false // TODO(gio): enabled by default?
73 groups: string | *"" // TODO(gio): []string
74}
75
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040076#Network: {
77 name: string
78 ingressClass: string
79 certificateIssuer: string | *""
80 domain: string
81}
82
Giorgi Lekveishvili67383962024-03-22 19:27:34 +040083networks: {
84 public: #Network & {
85 name: "Public"
86 ingressClass: "\(global.pcloudEnvName)-ingress-public"
87 certificateIssuer: "\(global.id)-public"
88 domain: global.domain
89 }
90 private: #Network & {
91 name: "Private"
92 ingressClass: "\(global.id)-ingress-private"
93 domain: global.privateDomain
94 }
95}
96
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040097#Image: {
98 registry: string | *"docker.io"
99 repository: string
100 name: string
101 tag: string
102 pullPolicy: string | *"IfNotPresent"
103 imageName: "\(repository)/\(name)"
104 fullName: "\(registry)/\(imageName)"
105 fullNameWithTag: "\(fullName):\(tag)"
106}
107
108#Chart: {
109 chart: string
110 sourceRef: #SourceRef
111}
112
113#SourceRef: {
114 kind: "GitRepository" | "HelmRepository"
115 name: string
116 namespace: string // TODO(gio): default global.id
117}
118
119#Global: {
120 id: string | *""
121 pcloudEnvName: string | *""
122 domain: string | *""
123 privateDomain: string | *""
124 namespacePrefix: string | *""
125 ...
126}
127
128#Release: {
129 namespace: string
130}
131
132global: #Global
133release: #Release
134
135_ingressPrivate: "\(global.id)-ingress-private"
136_ingressPublic: "\(global.pcloudEnvName)-ingress-public"
137_issuerPrivate: "\(global.id)-private"
138_issuerPublic: "\(global.id)-public"
139
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400140_IngressWithAuthProxy: {
141 inp: {
142 auth: #Auth
143 network: #Network
144 subdomain: string
145 serviceName: string
146 port: { name: string } | { number: int & > 0 }
147 }
148
149 _domain: "\(inp.subdomain).\(inp.network.domain)"
150 _authProxyHTTPPortName: "http"
151
152 out: {
153 images: {
154 authProxy: #Image & {
155 repository: "giolekva"
156 name: "auth-proxy"
157 tag: "latest"
158 pullPolicy: "Always"
159 }
160 }
161 charts: {
162 ingress: #Chart & {
163 chart: "charts/ingress"
164 sourceRef: {
165 kind: "GitRepository"
166 name: "pcloud"
167 namespace: global.id
168 }
169 }
170 authProxy: #Chart & {
171 chart: "charts/auth-proxy"
172 sourceRef: {
173 kind: "GitRepository"
174 name: "pcloud"
175 namespace: global.id
176 }
177 }
178 }
179 helm: {
180 if inp.auth.enabled {
181 "auth-proxy": {
182 chart: charts.authProxy
183 values: {
184 image: {
185 repository: images.authProxy.fullName
186 tag: images.authProxy.tag
187 pullPolicy: images.authProxy.pullPolicy
188 }
189 upstream: "\(inp.serviceName).\(release.namespace).svc.cluster.local"
190 whoAmIAddr: "https://accounts.\(global.domain)/sessions/whoami"
191 loginAddr: "https://accounts-ui.\(global.domain)/login"
Giorgi Lekveishvili329af572024-03-25 20:14:41 +0400192 membershipAddr: "http://memberships-api.\(global.id)-core-auth-memberships.svc.cluster.local/api/user"
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400193 groups: inp.auth.groups
194 portName: _authProxyHTTPPortName
195 }
196 }
197 }
198 ingress: {
199 chart: charts.ingress
200 values: {
201 domain: _domain
202 ingressClassName: inp.network.ingressClass
203 certificateIssuer: inp.network.certificateIssuer
204 service: {
205 if inp.auth.enabled {
206 name: "auth-proxy"
207 port: name: _authProxyHTTPPortName
208 }
209 if !inp.auth.enabled {
210 name: inp.serviceName
211 if inp.port.name != _|_ {
212 port: name: inp.port.name
213 }
214 if inp.port.number != _|_ {
215 port: number: inp.port.number
216 }
217 }
218 }
219 }
220 }
221 }
222 }
223}
224
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400225images: {
226 for key, value in images {
227 "\(key)": #Image & value
228 }
229}
230
231charts: {
232 for key, value in charts {
233 "\(key)": #Chart & value
234 }
235}
236
237#ResourceReference: {
238 name: string
239 namespace: string
240}
241
242#Helm: {
243 name: string
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400244 dependsOn: [...#ResourceReference] | *[]
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400245 ...
246}
247
Giorgi Lekveishvili0ba5e402024-03-20 15:56:30 +0400248helmValidate: {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400249 for key, value in helm {
250 "\(key)": #Helm & value & {
251 name: key
252 }
253 }
254}
255
256#HelmRelease: {
257 _name: string
258 _chart: #Chart
259 _values: _
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400260 _dependencies: [...#ResourceReference] | *[]
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400261
262 apiVersion: "helm.toolkit.fluxcd.io/v2beta1"
263 kind: "HelmRelease"
264 metadata: {
265 name: _name
266 namespace: release.namespace
267 }
268 spec: {
269 interval: "1m0s"
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400270 dependsOn: _dependencies
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400271 chart: {
272 spec: _chart
273 }
274 values: _values
275 }
276}
277
278output: {
Giorgi Lekveishvili0ba5e402024-03-20 15:56:30 +0400279 for name, r in helmValidate {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400280 "\(name)": #HelmRelease & {
281 _name: name
282 _chart: r.chart
283 _values: r.values
284 _dependencies: r.dependsOn
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400285 }
286 }
287}
Giorgi Lekveishvilib6a58062024-04-02 16:49:19 +0400288
289#SSHKey: {
290 public: string
291 private: string
292}
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400293`
294
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400295type appConfig struct {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400296 Name string `json:"name"`
297 Version string `json:"version"`
298 Description string `json:"description"`
299 Namespaces []string `json:"namespaces"`
300 Icon template.HTML `json:"icon"`
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400301}
302
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400303type Rendered struct {
304 Readme string
305 Resources map[string][]byte
306}
307
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400308type App interface {
309 Name() string
310 Description() string
311 Icon() template.HTML
312 Schema() Schema
313 Namespaces() []string
314 Render(derived Derived) (Rendered, error)
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400315}
316
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400317type cueApp struct {
318 name string
319 description string
320 icon template.HTML
321 namespace string
322 schema Schema
323 cfg *cue.Value
324}
325
326type cueAppConfig struct {
327 Name string `json:"name"`
328 Namespace string `json:"namespace"`
329 Description string `json:"description"`
330 Icon string `json:"icon"`
331}
332
333func newCueApp(config *cue.Value) (cueApp, error) {
334 if config == nil {
335 return cueApp{}, fmt.Errorf("config not provided")
336 }
337 var cfg cueAppConfig
338 if err := config.Decode(&cfg); err != nil {
339 return cueApp{}, err
340 }
341 schema, err := NewCueSchema(config.LookupPath(cue.ParsePath("input")))
342 if err != nil {
343 return cueApp{}, err
344 }
345 return cueApp{
346 name: cfg.Name,
347 description: cfg.Description,
348 icon: template.HTML(cfg.Icon),
349 namespace: cfg.Namespace,
350 schema: schema,
351 cfg: config,
352 }, nil
353}
354
355func (a cueApp) Name() string {
356 return a.name
357}
358
359func (a cueApp) Description() string {
360 return a.description
361}
362
363func (a cueApp) Icon() template.HTML {
364 return a.icon
365}
366
367func (a cueApp) Schema() Schema {
368 return a.schema
369}
370
371func (a cueApp) Namespaces() []string {
372 return []string{a.namespace}
373}
374
375func (a cueApp) Render(derived Derived) (Rendered, error) {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400376 ret := Rendered{
377 Resources: make(map[string][]byte),
378 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400379 var buf bytes.Buffer
380 if err := json.NewEncoder(&buf).Encode(derived); err != nil {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400381 return Rendered{}, err
382 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400383 ctx := a.cfg.Context()
384 d := ctx.CompileBytes(buf.Bytes())
385 res := a.cfg.Unify(d).Eval()
386 if err := res.Err(); err != nil {
387 return Rendered{}, err
388 }
389 if err := res.Validate(); err != nil {
390 return Rendered{}, err
391 }
392 readme, err := res.LookupPath(cue.ParsePath("readme")).String()
393 if err != nil {
394 return Rendered{}, err
395 }
396 ret.Readme = readme
397 output := res.LookupPath(cue.ParsePath("output"))
398 i, err := output.Fields()
399 if err != nil {
400 return Rendered{}, err
401 }
402 for i.Next() {
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400403 if contents, err := cueyaml.Encode(i.Value()); err != nil {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400404 return Rendered{}, err
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400405 } else {
406 name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
407 ret.Resources[name] = contents
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400408 }
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400409 }
410 return ret, nil
411}
412
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400413type AppRepository interface {
414 GetAll() ([]App, error)
415 Find(name string) (App, error)
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400416}
417
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400418type InMemoryAppRepository struct {
419 apps []App
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400420}
421
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400422func NewInMemoryAppRepository(apps []App) InMemoryAppRepository {
423 return InMemoryAppRepository{apps}
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400424}
425
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400426func (r InMemoryAppRepository) Find(name string) (App, error) {
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400427 for _, a := range r.apps {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400428 if a.Name() == name {
429 return a, nil
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400430 }
431 }
432 return nil, fmt.Errorf("Application not found: %s", name)
433}
giolekva8aa73e82022-07-09 11:34:39 +0400434
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400435func (r InMemoryAppRepository) GetAll() ([]App, error) {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400436 return r.apps, nil
437}
438
giolekva8aa73e82022-07-09 11:34:39 +0400439func CreateAllApps() []App {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400440 return append(
441 createApps(infraAppConfigs),
442 CreateStoreApps()...,
443 )
444}
445
446func CreateStoreApps() []App {
447 return createApps(storeAppConfigs)
448}
449
450func createApps(configs []string) []App {
Giorgi Lekveishvili186eae52024-02-15 14:21:41 +0400451 ret := make([]App, 0)
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400452 for _, cfgFile := range configs {
453 cfg, err := readCueConfigFromFile(valuesTmpls, cfgFile)
454 if err != nil {
455 panic(err)
456 }
457 if app, err := newCueApp(cfg); err != nil {
458 panic(err)
459 } else {
460 ret = append(ret, app)
461 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400462 }
463 return ret
464}
465
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400466// func CreateAppMaddy(fs embed.FS, tmpls *template.Template) App {
467// schema, err := readJSONSchemaFromFile(fs, "values-tmpl/maddy.jsonschema")
468// if err != nil {
469// panic(err)
470// }
471// return StoreApp{
472// App{
473// "maddy",
474// []string{"app-maddy"},
475// []*template.Template{
476// tmpls.Lookup("maddy.yaml"),
477// },
478// schema,
479// nil,
480// nil,
481// },
482// `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 48 48"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M9.5 13c13.687 13.574 14.825 13.09 29 0"/><rect width="37" height="31" x="5.5" y="8.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" rx="2"/></svg>`,
483// "SMPT/IMAP server to communicate via email.",
484// }
485// }
Giorgi Lekveishvili2df23db2023-12-14 07:55:22 +0400486
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400487type httpAppRepository struct {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400488 apps []App
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400489}
490
491type appVersion struct {
492 Version string `json:"version"`
493 Urls []string `json:"urls"`
494}
495
496type allAppsResp struct {
497 ApiVersion string `json:"apiVersion"`
498 Entries map[string][]appVersion `json:"entries"`
499}
500
501func FetchAppsFromHTTPRepository(addr string, fs billy.Filesystem) error {
502 resp, err := http.Get(addr)
503 if err != nil {
504 return err
505 }
506 b, err := io.ReadAll(resp.Body)
507 if err != nil {
508 return err
509 }
510 var apps allAppsResp
511 if err := yaml.Unmarshal(b, &apps); err != nil {
512 return err
513 }
514 for name, conf := range apps.Entries {
515 for _, version := range conf {
516 resp, err := http.Get(version.Urls[0])
517 if err != nil {
518 return err
519 }
520 nameVersion := fmt.Sprintf("%s-%s", name, version.Version)
521 if err := fs.MkdirAll(nameVersion, 0700); err != nil {
522 return err
523 }
524 sub, err := fs.Chroot(nameVersion)
525 if err != nil {
526 return err
527 }
528 if err := extractApp(resp.Body, sub); err != nil {
529 return err
530 }
531 }
532 }
533 return nil
534}
535
536func extractApp(archive io.Reader, fs billy.Filesystem) error {
537 uncompressed, err := gzip.NewReader(archive)
538 if err != nil {
539 return err
540 }
541 tarReader := tar.NewReader(uncompressed)
542 for true {
543 header, err := tarReader.Next()
544 if err == io.EOF {
545 break
546 }
547 if err != nil {
548 return err
549 }
550 switch header.Typeflag {
551 case tar.TypeDir:
552 if err := fs.MkdirAll(header.Name, 0755); err != nil {
553 return err
554 }
555 case tar.TypeReg:
556 out, err := fs.Create(header.Name)
557 if err != nil {
558 return err
559 }
560 defer out.Close()
561 if _, err := io.Copy(out, tarReader); err != nil {
562 return err
563 }
564 default:
565 return fmt.Errorf("Uknown type: %s", header.Name)
566 }
567 }
568 return nil
569}
570
571type fsAppRepository struct {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400572 InMemoryAppRepository
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400573 fs billy.Filesystem
574}
575
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400576func NewFSAppRepository(fs billy.Filesystem) (AppRepository, error) {
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400577 all, err := fs.ReadDir(".")
578 if err != nil {
579 return nil, err
580 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400581 apps := make([]App, 0)
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400582 for _, e := range all {
583 if !e.IsDir() {
584 continue
585 }
586 appFS, err := fs.Chroot(e.Name())
587 if err != nil {
588 return nil, err
589 }
590 app, err := loadApp(appFS)
591 if err != nil {
592 log.Printf("Ignoring directory %s: %s", e.Name(), err)
593 continue
594 }
595 apps = append(apps, app)
596 }
597 return &fsAppRepository{
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400598 NewInMemoryAppRepository(apps),
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400599 fs,
600 }, nil
601}
602
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400603func loadApp(fs billy.Filesystem) (App, error) {
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400604 items, err := fs.ReadDir(".")
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400605 if err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400606 return nil, err
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400607 }
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400608 var contents bytes.Buffer
609 for _, i := range items {
610 if i.IsDir() {
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400611 continue
612 }
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400613 f, err := fs.Open(i.Name())
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400614 if err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400615 return nil, err
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400616 }
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400617 defer f.Close()
618 if _, err := io.Copy(&contents, f); err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400619 return nil, err
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400620 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400621 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400622 cfg, err := processCueConfig(contents.String())
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400623 if err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400624 return nil, err
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400625 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400626 return newCueApp(cfg)
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400627}
628
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400629func cleanName(s string) string {
630 return strings.ReplaceAll(strings.ReplaceAll(s, "\"", ""), "'", "")
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400631}
632
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400633func processCueConfig(contents string) (*cue.Value, error) {
634 ctx := cuecontext.New()
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400635 cfg := ctx.CompileString(contents + cueBaseConfig)
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400636 if err := cfg.Err(); err != nil {
637 return nil, err
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400638 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400639 if err := cfg.Validate(); err != nil {
640 return nil, err
641 }
642 return &cfg, nil
643}
644
645func readCueConfigFromFile(fs embed.FS, f string) (*cue.Value, error) {
646 contents, err := fs.ReadFile(f)
647 if err != nil {
648 return nil, err
649 }
650 return processCueConfig(string(contents))
651}
652
653func createApp(fs embed.FS, configFile string) App {
654 cfg, err := readCueConfigFromFile(fs, configFile)
655 if err != nil {
656 panic(err)
657 }
658 if app, err := newCueApp(cfg); err != nil {
659 panic(err)
660 } else {
661 return app
662 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400663}