blob: 8457fe2a7a52604c1774da6825019b56023b0167 [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 Lekveishvili08af67a2024-01-18 08:53:05 +040037}
38
39var infraAppConfigs = []string{
40 "values-tmpl/appmanager.cue",
41 "values-tmpl/cert-manager.cue",
42 "values-tmpl/certificate-issuer-private.cue",
43 "values-tmpl/certificate-issuer-public.cue",
44 "values-tmpl/config-repo.cue",
45 "values-tmpl/core-auth.cue",
46 "values-tmpl/csi-driver-smb.cue",
47 "values-tmpl/dns-zone-manager.cue",
48 "values-tmpl/env-manager.cue",
49 "values-tmpl/fluxcd-reconciler.cue",
50 "values-tmpl/headscale-controller.cue",
51 "values-tmpl/headscale-user.cue",
52 "values-tmpl/headscale.cue",
53 "values-tmpl/ingress-public.cue",
54 "values-tmpl/metallb-ipaddresspool.cue",
55 "values-tmpl/private-network.cue",
56 "values-tmpl/resource-renderer-controller.cue",
57 "values-tmpl/welcome.cue",
DTabidze0d802592024-03-19 17:42:45 +040058 "values-tmpl/memberships.cue",
Giorgi Lekveishvili925f0de2024-03-14 18:51:56 +040059 "values-tmpl/hydra-maester.cue",
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040060}
61
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040062// TODO(gio): import
63const cueBaseConfig = `
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040064name: string | *""
65description: string | *""
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040066readme: string | *""
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +040067icon: string | *""
68namespace: string | *""
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040069
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +040070#Auth: {
71 enabled: bool | *false // TODO(gio): enabled by default?
72 groups: string | *"" // TODO(gio): []string
73}
74
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040075#Network: {
76 name: string
77 ingressClass: string
78 certificateIssuer: string | *""
79 domain: string
80}
81
Giorgi Lekveishvili67383962024-03-22 19:27:34 +040082networks: {
83 public: #Network & {
84 name: "Public"
85 ingressClass: "\(global.pcloudEnvName)-ingress-public"
86 certificateIssuer: "\(global.id)-public"
87 domain: global.domain
88 }
89 private: #Network & {
90 name: "Private"
91 ingressClass: "\(global.id)-ingress-private"
92 domain: global.privateDomain
93 }
94}
95
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +040096#Image: {
97 registry: string | *"docker.io"
98 repository: string
99 name: string
100 tag: string
101 pullPolicy: string | *"IfNotPresent"
102 imageName: "\(repository)/\(name)"
103 fullName: "\(registry)/\(imageName)"
104 fullNameWithTag: "\(fullName):\(tag)"
105}
106
107#Chart: {
108 chart: string
109 sourceRef: #SourceRef
110}
111
112#SourceRef: {
113 kind: "GitRepository" | "HelmRepository"
114 name: string
115 namespace: string // TODO(gio): default global.id
116}
117
118#Global: {
119 id: string | *""
120 pcloudEnvName: string | *""
121 domain: string | *""
122 privateDomain: string | *""
123 namespacePrefix: string | *""
124 ...
125}
126
127#Release: {
128 namespace: string
129}
130
131global: #Global
132release: #Release
133
134_ingressPrivate: "\(global.id)-ingress-private"
135_ingressPublic: "\(global.pcloudEnvName)-ingress-public"
136_issuerPrivate: "\(global.id)-private"
137_issuerPublic: "\(global.id)-public"
138
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400139_IngressWithAuthProxy: {
140 inp: {
141 auth: #Auth
142 network: #Network
143 subdomain: string
144 serviceName: string
145 port: { name: string } | { number: int & > 0 }
146 }
147
148 _domain: "\(inp.subdomain).\(inp.network.domain)"
149 _authProxyHTTPPortName: "http"
150
151 out: {
152 images: {
153 authProxy: #Image & {
154 repository: "giolekva"
155 name: "auth-proxy"
156 tag: "latest"
157 pullPolicy: "Always"
158 }
159 }
160 charts: {
161 ingress: #Chart & {
162 chart: "charts/ingress"
163 sourceRef: {
164 kind: "GitRepository"
165 name: "pcloud"
166 namespace: global.id
167 }
168 }
169 authProxy: #Chart & {
170 chart: "charts/auth-proxy"
171 sourceRef: {
172 kind: "GitRepository"
173 name: "pcloud"
174 namespace: global.id
175 }
176 }
177 }
178 helm: {
179 if inp.auth.enabled {
180 "auth-proxy": {
181 chart: charts.authProxy
182 values: {
183 image: {
184 repository: images.authProxy.fullName
185 tag: images.authProxy.tag
186 pullPolicy: images.authProxy.pullPolicy
187 }
188 upstream: "\(inp.serviceName).\(release.namespace).svc.cluster.local"
189 whoAmIAddr: "https://accounts.\(global.domain)/sessions/whoami"
190 loginAddr: "https://accounts-ui.\(global.domain)/login"
Giorgi Lekveishvili329af572024-03-25 20:14:41 +0400191 membershipAddr: "http://memberships-api.\(global.id)-core-auth-memberships.svc.cluster.local/api/user"
Giorgi Lekveishvili67383962024-03-22 19:27:34 +0400192 groups: inp.auth.groups
193 portName: _authProxyHTTPPortName
194 }
195 }
196 }
197 ingress: {
198 chart: charts.ingress
199 values: {
200 domain: _domain
201 ingressClassName: inp.network.ingressClass
202 certificateIssuer: inp.network.certificateIssuer
203 service: {
204 if inp.auth.enabled {
205 name: "auth-proxy"
206 port: name: _authProxyHTTPPortName
207 }
208 if !inp.auth.enabled {
209 name: inp.serviceName
210 if inp.port.name != _|_ {
211 port: name: inp.port.name
212 }
213 if inp.port.number != _|_ {
214 port: number: inp.port.number
215 }
216 }
217 }
218 }
219 }
220 }
221 }
222}
223
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400224images: {
225 for key, value in images {
226 "\(key)": #Image & value
227 }
228}
229
230charts: {
231 for key, value in charts {
232 "\(key)": #Chart & value
233 }
234}
235
236#ResourceReference: {
237 name: string
238 namespace: string
239}
240
241#Helm: {
242 name: string
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400243 dependsOn: [...#ResourceReference] | *[]
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400244 ...
245}
246
Giorgi Lekveishvili0ba5e402024-03-20 15:56:30 +0400247helmValidate: {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400248 for key, value in helm {
249 "\(key)": #Helm & value & {
250 name: key
251 }
252 }
253}
254
255#HelmRelease: {
256 _name: string
257 _chart: #Chart
258 _values: _
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400259 _dependencies: [...#ResourceReference] | *[]
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400260
261 apiVersion: "helm.toolkit.fluxcd.io/v2beta1"
262 kind: "HelmRelease"
263 metadata: {
264 name: _name
265 namespace: release.namespace
266 }
267 spec: {
268 interval: "1m0s"
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400269 dependsOn: _dependencies
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400270 chart: {
271 spec: _chart
272 }
273 values: _values
274 }
275}
276
277output: {
Giorgi Lekveishvili0ba5e402024-03-20 15:56:30 +0400278 for name, r in helmValidate {
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400279 "\(name)": #HelmRelease & {
280 _name: name
281 _chart: r.chart
282 _values: r.values
283 _dependencies: r.dependsOn
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400284 }
285 }
286}
287`
288
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400289type appConfig struct {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400290 Name string `json:"name"`
291 Version string `json:"version"`
292 Description string `json:"description"`
293 Namespaces []string `json:"namespaces"`
294 Icon template.HTML `json:"icon"`
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400295}
296
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400297type Rendered struct {
298 Readme string
299 Resources map[string][]byte
300}
301
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400302type App interface {
303 Name() string
304 Description() string
305 Icon() template.HTML
306 Schema() Schema
307 Namespaces() []string
308 Render(derived Derived) (Rendered, error)
Giorgi Lekveishvilie009a5d2024-01-05 14:10:11 +0400309}
310
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400311type cueApp struct {
312 name string
313 description string
314 icon template.HTML
315 namespace string
316 schema Schema
317 cfg *cue.Value
318}
319
320type cueAppConfig struct {
321 Name string `json:"name"`
322 Namespace string `json:"namespace"`
323 Description string `json:"description"`
324 Icon string `json:"icon"`
325}
326
327func newCueApp(config *cue.Value) (cueApp, error) {
328 if config == nil {
329 return cueApp{}, fmt.Errorf("config not provided")
330 }
331 var cfg cueAppConfig
332 if err := config.Decode(&cfg); err != nil {
333 return cueApp{}, err
334 }
335 schema, err := NewCueSchema(config.LookupPath(cue.ParsePath("input")))
336 if err != nil {
337 return cueApp{}, err
338 }
339 return cueApp{
340 name: cfg.Name,
341 description: cfg.Description,
342 icon: template.HTML(cfg.Icon),
343 namespace: cfg.Namespace,
344 schema: schema,
345 cfg: config,
346 }, nil
347}
348
349func (a cueApp) Name() string {
350 return a.name
351}
352
353func (a cueApp) Description() string {
354 return a.description
355}
356
357func (a cueApp) Icon() template.HTML {
358 return a.icon
359}
360
361func (a cueApp) Schema() Schema {
362 return a.schema
363}
364
365func (a cueApp) Namespaces() []string {
366 return []string{a.namespace}
367}
368
369func (a cueApp) Render(derived Derived) (Rendered, error) {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400370 ret := Rendered{
371 Resources: make(map[string][]byte),
372 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400373 var buf bytes.Buffer
374 if err := json.NewEncoder(&buf).Encode(derived); err != nil {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400375 return Rendered{}, err
376 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400377 ctx := a.cfg.Context()
378 d := ctx.CompileBytes(buf.Bytes())
379 res := a.cfg.Unify(d).Eval()
380 if err := res.Err(); err != nil {
381 return Rendered{}, err
382 }
383 if err := res.Validate(); err != nil {
384 return Rendered{}, err
385 }
386 readme, err := res.LookupPath(cue.ParsePath("readme")).String()
387 if err != nil {
388 return Rendered{}, err
389 }
390 ret.Readme = readme
391 output := res.LookupPath(cue.ParsePath("output"))
392 i, err := output.Fields()
393 if err != nil {
394 return Rendered{}, err
395 }
396 for i.Next() {
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400397 if contents, err := cueyaml.Encode(i.Value()); err != nil {
Giorgi Lekveishvili9b52ab92024-01-05 13:12:48 +0400398 return Rendered{}, err
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400399 } else {
400 name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
401 ret.Resources[name] = contents
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400402 }
Giorgi Lekveishvili3f697b12024-01-04 00:56:06 +0400403 }
404 return ret, nil
405}
406
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400407type AppRepository interface {
408 GetAll() ([]App, error)
409 Find(name string) (App, error)
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400410}
411
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400412type InMemoryAppRepository struct {
413 apps []App
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400414}
415
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400416func NewInMemoryAppRepository(apps []App) InMemoryAppRepository {
417 return InMemoryAppRepository{apps}
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400418}
419
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400420func (r InMemoryAppRepository) Find(name string) (App, error) {
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400421 for _, a := range r.apps {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400422 if a.Name() == name {
423 return a, nil
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +0400424 }
425 }
426 return nil, fmt.Errorf("Application not found: %s", name)
427}
giolekva8aa73e82022-07-09 11:34:39 +0400428
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400429func (r InMemoryAppRepository) GetAll() ([]App, error) {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400430 return r.apps, nil
431}
432
giolekva8aa73e82022-07-09 11:34:39 +0400433func CreateAllApps() []App {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400434 return append(
435 createApps(infraAppConfigs),
436 CreateStoreApps()...,
437 )
438}
439
440func CreateStoreApps() []App {
441 return createApps(storeAppConfigs)
442}
443
444func createApps(configs []string) []App {
Giorgi Lekveishvili186eae52024-02-15 14:21:41 +0400445 ret := make([]App, 0)
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400446 for _, cfgFile := range configs {
447 cfg, err := readCueConfigFromFile(valuesTmpls, cfgFile)
448 if err != nil {
449 panic(err)
450 }
451 if app, err := newCueApp(cfg); err != nil {
452 panic(err)
453 } else {
454 ret = append(ret, app)
455 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400456 }
457 return ret
458}
459
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400460// func CreateAppMaddy(fs embed.FS, tmpls *template.Template) App {
461// schema, err := readJSONSchemaFromFile(fs, "values-tmpl/maddy.jsonschema")
462// if err != nil {
463// panic(err)
464// }
465// return StoreApp{
466// App{
467// "maddy",
468// []string{"app-maddy"},
469// []*template.Template{
470// tmpls.Lookup("maddy.yaml"),
471// },
472// schema,
473// nil,
474// nil,
475// },
476// `<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>`,
477// "SMPT/IMAP server to communicate via email.",
478// }
479// }
Giorgi Lekveishvili2df23db2023-12-14 07:55:22 +0400480
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400481type httpAppRepository struct {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400482 apps []App
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400483}
484
485type appVersion struct {
486 Version string `json:"version"`
487 Urls []string `json:"urls"`
488}
489
490type allAppsResp struct {
491 ApiVersion string `json:"apiVersion"`
492 Entries map[string][]appVersion `json:"entries"`
493}
494
495func FetchAppsFromHTTPRepository(addr string, fs billy.Filesystem) error {
496 resp, err := http.Get(addr)
497 if err != nil {
498 return err
499 }
500 b, err := io.ReadAll(resp.Body)
501 if err != nil {
502 return err
503 }
504 var apps allAppsResp
505 if err := yaml.Unmarshal(b, &apps); err != nil {
506 return err
507 }
508 for name, conf := range apps.Entries {
509 for _, version := range conf {
510 resp, err := http.Get(version.Urls[0])
511 if err != nil {
512 return err
513 }
514 nameVersion := fmt.Sprintf("%s-%s", name, version.Version)
515 if err := fs.MkdirAll(nameVersion, 0700); err != nil {
516 return err
517 }
518 sub, err := fs.Chroot(nameVersion)
519 if err != nil {
520 return err
521 }
522 if err := extractApp(resp.Body, sub); err != nil {
523 return err
524 }
525 }
526 }
527 return nil
528}
529
530func extractApp(archive io.Reader, fs billy.Filesystem) error {
531 uncompressed, err := gzip.NewReader(archive)
532 if err != nil {
533 return err
534 }
535 tarReader := tar.NewReader(uncompressed)
536 for true {
537 header, err := tarReader.Next()
538 if err == io.EOF {
539 break
540 }
541 if err != nil {
542 return err
543 }
544 switch header.Typeflag {
545 case tar.TypeDir:
546 if err := fs.MkdirAll(header.Name, 0755); err != nil {
547 return err
548 }
549 case tar.TypeReg:
550 out, err := fs.Create(header.Name)
551 if err != nil {
552 return err
553 }
554 defer out.Close()
555 if _, err := io.Copy(out, tarReader); err != nil {
556 return err
557 }
558 default:
559 return fmt.Errorf("Uknown type: %s", header.Name)
560 }
561 }
562 return nil
563}
564
565type fsAppRepository struct {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400566 InMemoryAppRepository
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400567 fs billy.Filesystem
568}
569
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400570func NewFSAppRepository(fs billy.Filesystem) (AppRepository, error) {
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400571 all, err := fs.ReadDir(".")
572 if err != nil {
573 return nil, err
574 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400575 apps := make([]App, 0)
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400576 for _, e := range all {
577 if !e.IsDir() {
578 continue
579 }
580 appFS, err := fs.Chroot(e.Name())
581 if err != nil {
582 return nil, err
583 }
584 app, err := loadApp(appFS)
585 if err != nil {
586 log.Printf("Ignoring directory %s: %s", e.Name(), err)
587 continue
588 }
589 apps = append(apps, app)
590 }
591 return &fsAppRepository{
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400592 NewInMemoryAppRepository(apps),
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400593 fs,
594 }, nil
595}
596
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400597func loadApp(fs billy.Filesystem) (App, error) {
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400598 items, err := fs.ReadDir(".")
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400599 if err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400600 return nil, err
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400601 }
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400602 var contents bytes.Buffer
603 for _, i := range items {
604 if i.IsDir() {
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400605 continue
606 }
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400607 f, err := fs.Open(i.Name())
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400608 if err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400609 return nil, err
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400610 }
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400611 defer f.Close()
612 if _, err := io.Copy(&contents, f); err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400613 return nil, err
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400614 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400615 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400616 cfg, err := processCueConfig(contents.String())
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400617 if err != nil {
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400618 return nil, err
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400619 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400620 return newCueApp(cfg)
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400621}
622
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400623func cleanName(s string) string {
624 return strings.ReplaceAll(strings.ReplaceAll(s, "\"", ""), "'", "")
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400625}
626
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400627func processCueConfig(contents string) (*cue.Value, error) {
628 ctx := cuecontext.New()
Giorgi Lekveishvilia09fad72024-03-21 15:24:35 +0400629 cfg := ctx.CompileString(contents + cueBaseConfig)
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400630 if err := cfg.Err(); err != nil {
631 return nil, err
Giorgi Lekveishvilief21c132024-01-17 18:57:58 +0400632 }
Giorgi Lekveishvili08af67a2024-01-18 08:53:05 +0400633 if err := cfg.Validate(); err != nil {
634 return nil, err
635 }
636 return &cfg, nil
637}
638
639func readCueConfigFromFile(fs embed.FS, f string) (*cue.Value, error) {
640 contents, err := fs.ReadFile(f)
641 if err != nil {
642 return nil, err
643 }
644 return processCueConfig(string(contents))
645}
646
647func createApp(fs embed.FS, configFile string) App {
648 cfg, err := readCueConfigFromFile(fs, configFile)
649 if err != nil {
650 panic(err)
651 }
652 if app, err := newCueApp(cfg); err != nil {
653 panic(err)
654 } else {
655 return app
656 }
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400657}