blob: 3656231d66ae6309b324aee6a57cf0dd73f4122d [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}
288`
289
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400290type appConfig struct {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400291 Name string `json:"name"`
292 Version string `json:"version"`
293 Description string `json:"description"`
294 Namespaces []string `json:"namespaces"`
295 Icon template.HTML `json:"icon"`
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400296}
297
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400298type Rendered struct {
299 Readme string
300 Resources map[string][]byte
301}
302
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400303type App interface {
304 Name() string
305 Description() string
306 Icon() template.HTML
307 Schema() Schema
308 Namespaces() []string
309 Render(derived Derived) (Rendered, error)
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400310}
311
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400312type cueApp struct {
313 name string
314 description string
315 icon template.HTML
316 namespace string
317 schema Schema
318 cfg *cue.Value
319}
320
321type cueAppConfig struct {
322 Name string `json:"name"`
323 Namespace string `json:"namespace"`
324 Description string `json:"description"`
325 Icon string `json:"icon"`
326}
327
328func newCueApp(config *cue.Value) (cueApp, error) {
329 if config == nil {
330 return cueApp{}, fmt.Errorf("config not provided")
331 }
332 var cfg cueAppConfig
333 if err := config.Decode(&cfg); err != nil {
334 return cueApp{}, err
335 }
336 schema, err := NewCueSchema(config.LookupPath(cue.ParsePath("input")))
337 if err != nil {
338 return cueApp{}, err
339 }
340 return cueApp{
341 name: cfg.Name,
342 description: cfg.Description,
343 icon: template.HTML(cfg.Icon),
344 namespace: cfg.Namespace,
345 schema: schema,
346 cfg: config,
347 }, nil
348}
349
350func (a cueApp) Name() string {
351 return a.name
352}
353
354func (a cueApp) Description() string {
355 return a.description
356}
357
358func (a cueApp) Icon() template.HTML {
359 return a.icon
360}
361
362func (a cueApp) Schema() Schema {
363 return a.schema
364}
365
366func (a cueApp) Namespaces() []string {
367 return []string{a.namespace}
368}
369
370func (a cueApp) Render(derived Derived) (Rendered, error) {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400371 ret := Rendered{
372 Resources: make(map[string][]byte),
373 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400374 var buf bytes.Buffer
375 if err := json.NewEncoder(&buf).Encode(derived); err != nil {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400376 return Rendered{}, err
377 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400378 ctx := a.cfg.Context()
379 d := ctx.CompileBytes(buf.Bytes())
380 res := a.cfg.Unify(d).Eval()
381 if err := res.Err(); err != nil {
382 return Rendered{}, err
383 }
384 if err := res.Validate(); err != nil {
385 return Rendered{}, err
386 }
387 readme, err := res.LookupPath(cue.ParsePath("readme")).String()
388 if err != nil {
389 return Rendered{}, err
390 }
391 ret.Readme = readme
392 output := res.LookupPath(cue.ParsePath("output"))
393 i, err := output.Fields()
394 if err != nil {
395 return Rendered{}, err
396 }
397 for i.Next() {
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400398 if contents, err := cueyaml.Encode(i.Value()); err != nil {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400399 return Rendered{}, err
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400400 } else {
401 name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
402 ret.Resources[name] = contents
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400403 }
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400404 }
405 return ret, nil
406}
407
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400408type AppRepository interface {
409 GetAll() ([]App, error)
410 Find(name string) (App, error)
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400411}
412
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400413type InMemoryAppRepository struct {
414 apps []App
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400415}
416
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400417func NewInMemoryAppRepository(apps []App) InMemoryAppRepository {
418 return InMemoryAppRepository{apps}
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400419}
420
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400421func (r InMemoryAppRepository) Find(name string) (App, error) {
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400422 for _, a := range r.apps {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400423 if a.Name() == name {
424 return a, nil
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400425 }
426 }
427 return nil, fmt.Errorf("Application not found: %s", name)
428}
giolekva8aa73e82022-07-09 11:34:39 +0400429
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400430func (r InMemoryAppRepository) GetAll() ([]App, error) {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400431 return r.apps, nil
432}
433
giolekva8aa73e82022-07-09 11:34:39 +0400434func CreateAllApps() []App {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400435 return append(
436 createApps(infraAppConfigs),
437 CreateStoreApps()...,
438 )
439}
440
441func CreateStoreApps() []App {
442 return createApps(storeAppConfigs)
443}
444
445func createApps(configs []string) []App {
Giorgi Lekveishvili186eae52024-02-15 14:21:41 +0400446 ret := make([]App, 0)
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400447 for _, cfgFile := range configs {
448 cfg, err := readCueConfigFromFile(valuesTmpls, cfgFile)
449 if err != nil {
450 panic(err)
451 }
452 if app, err := newCueApp(cfg); err != nil {
453 panic(err)
454 } else {
455 ret = append(ret, app)
456 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400457 }
458 return ret
459}
460
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400461// func CreateAppMaddy(fs embed.FS, tmpls *template.Template) App {
462// schema, err := readJSONSchemaFromFile(fs, "values-tmpl/maddy.jsonschema")
463// if err != nil {
464// panic(err)
465// }
466// return StoreApp{
467// App{
468// "maddy",
469// []string{"app-maddy"},
470// []*template.Template{
471// tmpls.Lookup("maddy.yaml"),
472// },
473// schema,
474// nil,
475// nil,
476// },
477// `<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>`,
478// "SMPT/IMAP server to communicate via email.",
479// }
480// }
Giorgi Lekveishvili2df23db2023-12-14 07:55:22 +0400481
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400482type httpAppRepository struct {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400483 apps []App
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400484}
485
486type appVersion struct {
487 Version string `json:"version"`
488 Urls []string `json:"urls"`
489}
490
491type allAppsResp struct {
492 ApiVersion string `json:"apiVersion"`
493 Entries map[string][]appVersion `json:"entries"`
494}
495
496func FetchAppsFromHTTPRepository(addr string, fs billy.Filesystem) error {
497 resp, err := http.Get(addr)
498 if err != nil {
499 return err
500 }
501 b, err := io.ReadAll(resp.Body)
502 if err != nil {
503 return err
504 }
505 var apps allAppsResp
506 if err := yaml.Unmarshal(b, &apps); err != nil {
507 return err
508 }
509 for name, conf := range apps.Entries {
510 for _, version := range conf {
511 resp, err := http.Get(version.Urls[0])
512 if err != nil {
513 return err
514 }
515 nameVersion := fmt.Sprintf("%s-%s", name, version.Version)
516 if err := fs.MkdirAll(nameVersion, 0700); err != nil {
517 return err
518 }
519 sub, err := fs.Chroot(nameVersion)
520 if err != nil {
521 return err
522 }
523 if err := extractApp(resp.Body, sub); err != nil {
524 return err
525 }
526 }
527 }
528 return nil
529}
530
531func extractApp(archive io.Reader, fs billy.Filesystem) error {
532 uncompressed, err := gzip.NewReader(archive)
533 if err != nil {
534 return err
535 }
536 tarReader := tar.NewReader(uncompressed)
537 for true {
538 header, err := tarReader.Next()
539 if err == io.EOF {
540 break
541 }
542 if err != nil {
543 return err
544 }
545 switch header.Typeflag {
546 case tar.TypeDir:
547 if err := fs.MkdirAll(header.Name, 0755); err != nil {
548 return err
549 }
550 case tar.TypeReg:
551 out, err := fs.Create(header.Name)
552 if err != nil {
553 return err
554 }
555 defer out.Close()
556 if _, err := io.Copy(out, tarReader); err != nil {
557 return err
558 }
559 default:
560 return fmt.Errorf("Uknown type: %s", header.Name)
561 }
562 }
563 return nil
564}
565
566type fsAppRepository struct {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400567 InMemoryAppRepository
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400568 fs billy.Filesystem
569}
570
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400571func NewFSAppRepository(fs billy.Filesystem) (AppRepository, error) {
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400572 all, err := fs.ReadDir(".")
573 if err != nil {
574 return nil, err
575 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400576 apps := make([]App, 0)
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400577 for _, e := range all {
578 if !e.IsDir() {
579 continue
580 }
581 appFS, err := fs.Chroot(e.Name())
582 if err != nil {
583 return nil, err
584 }
585 app, err := loadApp(appFS)
586 if err != nil {
587 log.Printf("Ignoring directory %s: %s", e.Name(), err)
588 continue
589 }
590 apps = append(apps, app)
591 }
592 return &fsAppRepository{
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400593 NewInMemoryAppRepository(apps),
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400594 fs,
595 }, nil
596}
597
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400598func loadApp(fs billy.Filesystem) (App, error) {
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400599 items, err := fs.ReadDir(".")
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400600 if err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400601 return nil, err
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400602 }
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400603 var contents bytes.Buffer
604 for _, i := range items {
605 if i.IsDir() {
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400606 continue
607 }
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400608 f, err := fs.Open(i.Name())
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400609 if err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400610 return nil, err
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400611 }
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400612 defer f.Close()
613 if _, err := io.Copy(&contents, f); err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400614 return nil, err
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400615 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400616 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400617 cfg, err := processCueConfig(contents.String())
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400618 if err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400619 return nil, err
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400620 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400621 return newCueApp(cfg)
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400622}
623
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400624func cleanName(s string) string {
625 return strings.ReplaceAll(strings.ReplaceAll(s, "\"", ""), "'", "")
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400626}
627
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400628func processCueConfig(contents string) (*cue.Value, error) {
629 ctx := cuecontext.New()
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400630 cfg := ctx.CompileString(contents + cueBaseConfig)
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400631 if err := cfg.Err(); err != nil {
632 return nil, err
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400633 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400634 if err := cfg.Validate(); err != nil {
635 return nil, err
636 }
637 return &cfg, nil
638}
639
640func readCueConfigFromFile(fs embed.FS, f string) (*cue.Value, error) {
641 contents, err := fs.ReadFile(f)
642 if err != nil {
643 return nil, err
644 }
645 return processCueConfig(string(contents))
646}
647
648func createApp(fs embed.FS, configFile string) App {
649 cfg, err := readCueConfigFromFile(fs, configFile)
650 if err != nil {
651 panic(err)
652 }
653 if app, err := newCueApp(cfg); err != nil {
654 panic(err)
655 } else {
656 return app
657 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400658}