blob: 6312e8bf4b7fa64441a4e478faa6fc79c9e26440 [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"
5 "compress/gzip"
giolekva8aa73e82022-07-09 11:34:39 +04006 "embed"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +04007 "encoding/json"
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +04008 "fmt"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +04009 htemplate "html/template"
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040010 "io"
giolekva8aa73e82022-07-09 11:34:39 +040011 "log"
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040012 "net/http"
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040013 "strings"
giolekva8aa73e82022-07-09 11:34:39 +040014 "text/template"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040015
16 "github.com/Masterminds/sprig/v3"
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040017 "github.com/go-git/go-billy/v5"
18 "sigs.k8s.io/yaml"
giolekva8aa73e82022-07-09 11:34:39 +040019)
giolekva050609f2021-12-29 15:51:40 +040020
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040021//go:embed values-tmpl
22var valuesTmpls embed.FS
23
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +040024type Named interface {
25 Nam() string
26}
27
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040028type appConfig struct {
29 Name string `json:"name"`
30 Version string `json:"version"`
31 Description string `json:"description"`
32 Namespaces []string `json:"namespaces"`
33 Icon htemplate.HTML `json:"icon"`
34}
35
giolekva050609f2021-12-29 15:51:40 +040036type App struct {
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040037 Name string
38 Namespaces []string
39 Templates []*template.Template
40 Schema string
41 Readme *template.Template
giolekva050609f2021-12-29 15:51:40 +040042}
43
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040044func (a App) ConfigSchema() map[string]any {
45 ret := make(map[string]any)
46 if err := json.NewDecoder(strings.NewReader(a.Schema)).Decode(&ret); err != nil {
47 panic(err) // TODO(giolekva): prevalidate
48 }
49 return ret
50}
51
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +040052type StoreApp struct {
53 App
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +040054 Icon htemplate.HTML
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +040055 ShortDescription string
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040056}
57
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +040058func (a App) Nam() string {
59 return a.Name
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040060}
61
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +040062func (a StoreApp) Nam() string {
63 return a.Name
64}
65
66type AppRepository[A Named] interface {
67 GetAll() ([]A, error)
68 Find(name string) (*A, error)
69}
70
71type InMemoryAppRepository[A Named] struct {
72 apps []A
73}
74
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +040075func NewInMemoryAppRepository[A Named](apps []A) InMemoryAppRepository[A] {
76 return InMemoryAppRepository[A]{
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040077 apps,
78 }
79}
80
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +040081func (r InMemoryAppRepository[A]) Find(name string) (*A, error) {
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040082 for _, a := range r.apps {
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +040083 if a.Nam() == name {
Giorgi Lekveishvilibd6be7f2023-05-26 15:51:28 +040084 return &a, nil
85 }
86 }
87 return nil, fmt.Errorf("Application not found: %s", name)
88}
giolekva8aa73e82022-07-09 11:34:39 +040089
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +040090func (r InMemoryAppRepository[A]) GetAll() ([]A, error) {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +040091 return r.apps, nil
92}
93
giolekva8aa73e82022-07-09 11:34:39 +040094func CreateAllApps() []App {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040095 tmpls, err := template.New("root").Funcs(template.FuncMap(sprig.FuncMap())).ParseFS(valuesTmpls, "values-tmpl/*")
giolekva8aa73e82022-07-09 11:34:39 +040096 if err != nil {
97 log.Fatal(err)
98 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +040099 ret := []App{
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400100 CreateAppIngressPrivate(valuesTmpls, tmpls),
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400101 CreateCertificateIssuerPublic(valuesTmpls, tmpls),
102 CreateCertificateIssuerPrivate(valuesTmpls, tmpls),
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400103 CreateAppCoreAuth(valuesTmpls, tmpls),
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400104 CreateAppHeadscale(valuesTmpls, tmpls),
Giorgi Lekveishvili39913692023-12-05 08:58:08 +0400105 CreateAppHeadscaleUser(valuesTmpls, tmpls),
Giorgi Lekveishvili4fc29432023-07-20 10:03:28 +0400106 CreateMetallbIPAddressPool(valuesTmpls, tmpls),
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400107 CreateEnvManager(valuesTmpls, tmpls),
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400108 CreateWelcome(valuesTmpls, tmpls),
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400109 CreateAppManager(valuesTmpls, tmpls),
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400110 CreateIngressPublic(valuesTmpls, tmpls),
111 CreateCertManager(valuesTmpls, tmpls),
112 CreateCertManagerWebhookGandi(valuesTmpls, tmpls),
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400113 CreateCSIDriverSMB(valuesTmpls, tmpls),
114 CreateResourceRendererController(valuesTmpls, tmpls),
115 CreateHeadscaleController(valuesTmpls, tmpls),
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400116 CreateDNSZoneManager(valuesTmpls, tmpls),
Giorgi Lekveishvili2df23db2023-12-14 07:55:22 +0400117 CreateFluxcdReconciler(valuesTmpls, tmpls),
giolekvaef76a3e2022-01-10 12:22:28 +0400118 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400119 for _, a := range CreateStoreApps() {
120 ret = append(ret, a.App)
121 }
122 return ret
123}
124
125func CreateStoreApps() []StoreApp {
126 tmpls, err := template.New("root").Funcs(template.FuncMap(sprig.FuncMap())).ParseFS(valuesTmpls, "values-tmpl/*")
127 if err != nil {
128 log.Fatal(err)
129 }
130 return []StoreApp{
131 CreateAppVaultwarden(valuesTmpls, tmpls),
132 CreateAppMatrix(valuesTmpls, tmpls),
133 CreateAppPihole(valuesTmpls, tmpls),
Giorgi Lekveishvili3fd5e4c2023-12-19 22:09:40 +0400134 CreateAppPenpot(valuesTmpls, tmpls),
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400135 CreateAppMaddy(valuesTmpls, tmpls),
136 CreateAppQBittorrent(valuesTmpls, tmpls),
137 CreateAppJellyfin(valuesTmpls, tmpls),
Giorgi Lekveishvili672af5d2023-07-12 11:57:51 +0400138 CreateAppSoftServe(valuesTmpls, tmpls),
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400139 CreateAppRpuppy(valuesTmpls, tmpls),
140 }
giolekvaef76a3e2022-01-10 12:22:28 +0400141}
142
Giorgi Lekveishvili4d2784d2023-06-01 14:27:32 +0400143// TODO(gio): service account needs permission to create/update secret
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400144func CreateAppIngressPrivate(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +0400145 schema, err := fs.ReadFile("values-tmpl/private-network.jsonschema")
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400146 if err != nil {
147 panic(err)
148 }
giolekva050609f2021-12-29 15:51:40 +0400149 return App{
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +0400150 "private-network",
151 []string{"ingress-private"}, // TODO(gio): rename to private network
giolekva050609f2021-12-29 15:51:40 +0400152 []*template.Template{
giolekva050609f2021-12-29 15:51:40 +0400153 tmpls.Lookup("ingress-private.yaml"),
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +0400154 tmpls.Lookup("tailscale-proxy.yaml"),
giolekva050609f2021-12-29 15:51:40 +0400155 },
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400156 string(schema),
Giorgi Lekveishvili2dbce6c2023-12-05 15:16:27 +0400157 tmpls.Lookup("private-network.md"),
giolekva050609f2021-12-29 15:51:40 +0400158 }
159}
160
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400161func CreateCertificateIssuerPrivate(fs embed.FS, tmpls *template.Template) App {
162 schema, err := fs.ReadFile("values-tmpl/certificate-issuer-private.jsonschema")
163 if err != nil {
164 panic(err)
165 }
166 return App{
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400167 "certificate-issuer-private",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400168 []string{},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400169 []*template.Template{
170 tmpls.Lookup("certificate-issuer-private.yaml"),
171 },
172 string(schema),
173 tmpls.Lookup("certificate-issuer-private.md"),
174 }
175}
176
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400177func CreateCertificateIssuerPublic(fs embed.FS, tmpls *template.Template) App {
178 schema, err := fs.ReadFile("values-tmpl/certificate-issuer-public.jsonschema")
179 if err != nil {
180 panic(err)
181 }
182 return App{
183 "certificate-issuer-public",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400184 []string{},
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400185 []*template.Template{
186 tmpls.Lookup("certificate-issuer-public.yaml"),
187 },
188 string(schema),
189 tmpls.Lookup("certificate-issuer-public.md"),
190 }
191}
192
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400193func CreateAppCoreAuth(fs embed.FS, tmpls *template.Template) App {
194 schema, err := fs.ReadFile("values-tmpl/core-auth.jsonschema")
195 if err != nil {
196 panic(err)
197 }
giolekva050609f2021-12-29 15:51:40 +0400198 return App{
199 "core-auth",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400200 []string{"core-auth"},
giolekva050609f2021-12-29 15:51:40 +0400201 []*template.Template{
202 tmpls.Lookup("core-auth-storage.yaml"),
203 tmpls.Lookup("core-auth.yaml"),
204 },
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400205 string(schema),
Giorgi Lekveishvili3ca1f3f2023-05-30 14:33:02 +0400206 tmpls.Lookup("core-auth.md"),
giolekva050609f2021-12-29 15:51:40 +0400207 }
208}
209
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400210func CreateAppVaultwarden(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400211 schema, err := fs.ReadFile("values-tmpl/vaultwarden.jsonschema")
212 if err != nil {
213 panic(err)
214 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400215 return StoreApp{
216 App: App{
217 "vaultwarden",
218 []string{"app-vaultwarden"},
219 []*template.Template{
220 tmpls.Lookup("vaultwarden.yaml"),
221 },
222 string(schema),
223 tmpls.Lookup("vaultwarden.md"),
giolekva050609f2021-12-29 15:51:40 +0400224 },
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400225 Icon: `<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="M35.38 25.63V9.37H24v28.87a34.93 34.93 0 0 0 5.41-3.48q6-4.66 6-9.14Zm4.87-19.5v19.5A11.58 11.58 0 0 1 39.4 30a16.22 16.22 0 0 1-2.11 3.81a23.52 23.52 0 0 1-3 3.24a34.87 34.87 0 0 1-3.22 2.62c-1 .69-2 1.35-3.07 2s-1.82 1-2.27 1.26l-1.08.51a1.53 1.53 0 0 1-1.32 0l-1.08-.51c-.45-.22-1.21-.64-2.27-1.26s-2.09-1.27-3.07-2A34.87 34.87 0 0 1 13.7 37a23.52 23.52 0 0 1-3-3.24A16.22 16.22 0 0 1 8.6 30a11.58 11.58 0 0 1-.85-4.32V6.13A1.64 1.64 0 0 1 9.38 4.5h29.24a1.64 1.64 0 0 1 1.63 1.63Z"/></svg>`,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400226 ShortDescription: "Open source implementation of Bitwarden password manager. Can be used with official client applications.",
giolekva050609f2021-12-29 15:51:40 +0400227 }
228}
229
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400230func CreateAppMatrix(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400231 schema, err := fs.ReadFile("values-tmpl/matrix.jsonschema")
232 if err != nil {
233 panic(err)
234 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400235 return StoreApp{
236 App{
237 "matrix",
238 []string{"app-matrix"},
239 []*template.Template{
240 tmpls.Lookup("matrix-storage.yaml"),
241 tmpls.Lookup("matrix.yaml"),
242 },
243 string(schema),
244 nil,
giolekva050609f2021-12-29 15:51:40 +0400245 },
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400246 `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 24 24"><path fill="currentColor" d="M.632.55v22.9H2.28V24H0V0h2.28v.55zm7.043 7.26v1.157h.033a3.312 3.312 0 0 1 1.117-1.024c.433-.245.936-.365 1.5-.365c.54 0 1.033.107 1.481.314c.448.208.785.582 1.02 1.108c.254-.374.6-.706 1.034-.992c.434-.287.95-.43 1.546-.43c.453 0 .872.056 1.26.167c.388.11.716.286.993.53c.276.245.489.559.646.951c.152.392.23.863.23 1.417v5.728h-2.349V11.52c0-.286-.01-.559-.032-.812a1.755 1.755 0 0 0-.18-.66a1.106 1.106 0 0 0-.438-.448c-.194-.11-.457-.166-.785-.166c-.332 0-.6.064-.803.189a1.38 1.38 0 0 0-.48.499a1.946 1.946 0 0 0-.231.696a5.56 5.56 0 0 0-.06.785v4.768h-2.35v-4.8c0-.254-.004-.503-.018-.752a2.074 2.074 0 0 0-.143-.688a1.052 1.052 0 0 0-.415-.503c-.194-.125-.476-.19-.854-.19c-.111 0-.259.024-.439.074c-.18.051-.36.143-.53.282a1.637 1.637 0 0 0-.439.595c-.12.259-.18.6-.18 1.02v4.966H5.46V7.81zm15.693 15.64V.55H21.72V0H24v24h-2.28v-.55z"/></svg>`,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400247 "An open network for secure, decentralised communication",
giolekva050609f2021-12-29 15:51:40 +0400248 }
249}
250
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400251func CreateAppPihole(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400252 schema, err := fs.ReadFile("values-tmpl/pihole.jsonschema")
253 if err != nil {
254 panic(err)
255 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400256 return StoreApp{
257 App{
258 "pihole",
259 []string{"app-pihole"},
260 []*template.Template{
261 tmpls.Lookup("pihole.yaml"),
262 },
263 string(schema),
264 tmpls.Lookup("pihole.md"),
giolekva050609f2021-12-29 15:51:40 +0400265 },
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400266 // "simple-icons:pihole",
267 `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 24 24"><path fill="currentColor" d="M4.344 0c.238 4.792 3.256 7.056 6.252 7.376c.165-1.692-4.319-5.6-4.319-5.6c-.008-.011.009-.025.019-.014c0 0 4.648 4.01 5.423 5.645c2.762-.15 5.196-1.947 5-4.912c0 0-4.12-.613-5 4.618C11.48 2.753 8.993 0 4.344 0zM12 7.682v.002a3.68 3.68 0 0 0-2.591 1.077L4.94 13.227a3.683 3.683 0 0 0-.86 1.356a3.31 3.31 0 0 0-.237 1.255A3.681 3.681 0 0 0 4.92 18.45l4.464 4.466a3.69 3.69 0 0 0 2.251 1.06l.002.001c.093.01.187.015.28.017l-.1-.008c.06.003.117.009.177.009l-.077-.001L12 24l-.004-.005a3.68 3.68 0 0 0 2.61-1.077l4.469-4.465a3.683 3.683 0 0 0 1.006-1.888l.012-.063a3.682 3.682 0 0 0 .057-.541l.003-.061c0-.017.003-.05.004-.06h-.002a3.683 3.683 0 0 0-1.077-2.607l-4.466-4.468a3.694 3.694 0 0 0-1.564-.927l-.07-.02a3.43 3.43 0 0 0-.946-.133L12 7.682zm3.165 3.357c.023 1.748-1.33 3.078-1.33 4.806c.164 2.227 1.733 3.207 3.266 3.146c-.035.003-.068.007-.104.009c-1.847.135-3.209-1.326-5.002-1.326c-2.23.164-3.21 1.736-3.147 3.27l-.008-.104c-.133-1.847 1.328-3.21 1.328-5.002c-.173-2.32-1.867-3.284-3.46-3.132c.1-.011.203-.021.31-.027c1.847-.133 3.209 1.328 5.002 1.328c2.082-.155 3.074-1.536 3.145-2.968zM4.344 0c.238 4.792 3.256 7.056 6.252 7.376c.165-1.692-4.319-5.6-4.319-5.6c-.008-.011.009-.025.019-.014c0 0 4.648 4.01 5.423 5.645c2.762-.15 5.196-1.947 5-4.912c0 0-4.12-.613-5 4.618C11.48 2.753 8.993 0 4.344 0zM12 7.682v.002a3.68 3.68 0 0 0-2.591 1.077L4.94 13.227a3.683 3.683 0 0 0-.86 1.356a3.31 3.31 0 0 0-.237 1.255A3.681 3.681 0 0 0 4.92 18.45l4.464 4.466a3.69 3.69 0 0 0 2.251 1.06l.002.001c.093.01.187.015.28.017l-.1-.008c.06.003.117.009.177.009l-.077-.001L12 24l-.004-.005a3.68 3.68 0 0 0 2.61-1.077l4.469-4.465a3.683 3.683 0 0 0 1.006-1.888l.012-.063a3.682 3.682 0 0 0 .057-.541l.003-.061c0-.017.003-.05.004-.06h-.002a3.683 3.683 0 0 0-1.077-2.607l-4.466-4.468a3.694 3.694 0 0 0-1.564-.927l-.07-.02a3.43 3.43 0 0 0-.946-.133L12 7.682zm3.165 3.357c.023 1.748-1.33 3.078-1.33 4.806c.164 2.227 1.733 3.207 3.266 3.146c-.035.003-.068.007-.104.009c-1.847.135-3.209-1.326-5.002-1.326c-2.23.164-3.21 1.736-3.147 3.27l-.008-.104c-.133-1.847 1.328-3.21 1.328-5.002c-.173-2.32-1.867-3.284-3.46-3.132c.1-.011.203-.021.31-.027c1.847-.133 3.209 1.328 5.002 1.328c2.082-.155 3.074-1.536 3.145-2.968z"/></svg>`,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400268 "Pi-hole is a Linux network-level advertisement and Internet tracker blocking application which acts as a DNS sinkhole and optionally a DHCP server, intended for use on a private network.",
giolekva050609f2021-12-29 15:51:40 +0400269 }
270}
271
Giorgi Lekveishvili3fd5e4c2023-12-19 22:09:40 +0400272func CreateAppPenpot(fs embed.FS, tmpls *template.Template) StoreApp {
273 schema, err := fs.ReadFile("values-tmpl/penpot.jsonschema")
274 if err != nil {
275 panic(err)
276 }
277 return StoreApp{
278 App{
279 "penpot",
280 []string{"app-penpot"},
281 []*template.Template{
282 tmpls.Lookup("penpot.yaml"),
283 },
284 string(schema),
285 tmpls.Lookup("penpot.md"),
286 },
287 // "simple-icons:pihole",
288 `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M7.654 0L5.13 3.554v2.01L2.934 6.608l-.02-.009v13.109l8.563 4.045L12 24l.523-.247l8.563-4.045V6.6l-.017.008l-2.196-1.045V3.555l-.077-.108L16.349.001l-2.524 3.554v.004L11.989.973l-1.823 2.566l-.065-.091zm.447 2.065l.976 1.374H6.232l.964-1.358zm8.694 0l.976 1.374h-2.845l.965-1.358zm-4.36.971l.976 1.375h-2.845l.965-1.359zM5.962 4.132h1.35v4.544l-1.35-.638Zm2.042 0h1.343v5.506l-1.343-.635zm6.652 0h1.35V9l-1.35.637zm2.042 0h1.343v3.905l-1.343.634zm-6.402.972h1.35v5.62l-1.35-.638zm2.042 0h1.343v4.993l-1.343.634zm6.534 1.493l1.188.486l-1.188.561zM5.13 6.6v1.047l-1.187-.561ZM3.96 8.251l7.517 3.55v10.795l-7.516-3.55zm16.08 0v10.794l-7.517 3.55V11.802z"/></svg>`,
289 "Penpot is the first Open Source design and prototyping platform meant for cross-domain teams. Non dependent on operating systems, Penpot is web based and works with open standards (SVG). Penpot invites designers all over the world to fall in love with open source while getting developers excited about the design process in return.",
290 }
291}
292
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400293func CreateAppMaddy(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400294 schema, err := fs.ReadFile("values-tmpl/maddy.jsonschema")
295 if err != nil {
296 panic(err)
297 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400298 return StoreApp{
299 App{
300 "maddy",
301 []string{"app-maddy"},
302 []*template.Template{
303 tmpls.Lookup("maddy.yaml"),
304 },
305 string(schema),
306 nil,
giolekva050609f2021-12-29 15:51:40 +0400307 },
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400308 `<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>`,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400309 "SMPT/IMAP server to communicate via email.",
giolekva050609f2021-12-29 15:51:40 +0400310 }
311}
giolekvaef76a3e2022-01-10 12:22:28 +0400312
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400313func CreateAppQBittorrent(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400314 schema, err := fs.ReadFile("values-tmpl/qbittorrent.jsonschema")
315 if err != nil {
316 panic(err)
317 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400318 return StoreApp{
319 App{
320 "qbittorrent",
321 []string{"app-qbittorrent"},
322 []*template.Template{
323 tmpls.Lookup("qbittorrent.yaml"),
324 },
325 string(schema),
326 tmpls.Lookup("qbittorrent.md"),
giolekvaef76a3e2022-01-10 12:22:28 +0400327 },
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400328 `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 48 48"><circle cx="24" cy="24" r="21.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M26.651 22.364a5.034 5.034 0 0 1 5.035-5.035h0a5.034 5.034 0 0 1 5.034 5.035v3.272a5.034 5.034 0 0 1-5.034 5.035h0a5.034 5.034 0 0 1-5.035-5.035m0 5.035V10.533m-5.302 15.103a5.034 5.034 0 0 1-5.035 5.035h0a5.034 5.034 0 0 1-5.034-5.035v-3.272a5.034 5.034 0 0 1 5.034-5.035h0a5.034 5.034 0 0 1 5.035 5.035m0-5.035v20.138"/></svg>`,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400329 "qBittorrent is a cross-platform free and open-source BitTorrent client written in native C++. It relies on Boost, Qt 6 toolkit and the libtorrent-rasterbar library, with an optional search engine written in Python.",
giolekvaef76a3e2022-01-10 12:22:28 +0400330 }
331}
332
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400333func CreateAppJellyfin(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400334 schema, err := fs.ReadFile("values-tmpl/jellyfin.jsonschema")
335 if err != nil {
336 panic(err)
337 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400338 return StoreApp{
339 App{
340 "jellyfin",
341 []string{"app-jellyfin"},
342 []*template.Template{
343 tmpls.Lookup("jellyfin.yaml"),
344 },
345 string(schema),
346 nil,
giolekvaef76a3e2022-01-10 12:22:28 +0400347 },
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400348 `<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="M24 20c-1.62 0-6.85 9.48-6.06 11.08s11.33 1.59 12.12 0S25.63 20 24 20Z"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M24 5.5c-4.89 0-20.66 28.58-18.25 33.4s34.13 4.77 36.51 0S28.9 5.5 24 5.5Zm12 29.21c-1.56 3.13-22.35 3.17-23.93 0S20.8 12.83 24 12.83s13.52 18.76 12 21.88Z"/></svg>`,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400349 "Jellyfin is a free and open-source media server and suite of multimedia applications designed to organize, manage, and share digital media files to networked devices.",
giolekvaef76a3e2022-01-10 12:22:28 +0400350 }
351}
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400352
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400353func CreateAppRpuppy(fs embed.FS, tmpls *template.Template) StoreApp {
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400354 schema, err := fs.ReadFile("values-tmpl/rpuppy.jsonschema")
355 if err != nil {
356 panic(err)
357 }
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400358 return StoreApp{
359 App{
360 "rpuppy",
361 []string{"app-rpuppy"},
362 []*template.Template{
363 tmpls.Lookup("rpuppy.yaml"),
364 },
365 string(schema),
366 tmpls.Lookup("rpuppy.md"),
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400367 },
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400368 `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 256 256"><path fill="currentColor" d="M100 140a8 8 0 1 1-8-8a8 8 0 0 1 8 8Zm64 8a8 8 0 1 0-8-8a8 8 0 0 0 8 8Zm64.94-9.11a12.12 12.12 0 0 1-5 1.11a11.83 11.83 0 0 1-9.35-4.62l-2.59-3.29V184a36 36 0 0 1-36 36H80a36 36 0 0 1-36-36v-51.91l-2.53 3.27A11.88 11.88 0 0 1 32.1 140a12.08 12.08 0 0 1-5-1.11a11.82 11.82 0 0 1-6.84-13.14l16.42-88a12 12 0 0 1 14.7-9.43h.16L104.58 44h46.84l53.08-15.6h.16a12 12 0 0 1 14.7 9.43l16.42 88a11.81 11.81 0 0 1-6.84 13.06ZM97.25 50.18L49.34 36.1a4.18 4.18 0 0 0-.92-.1a4 4 0 0 0-3.92 3.26l-16.42 88a4 4 0 0 0 7.08 3.22ZM204 121.75L150 52h-44l-54 69.75V184a28 28 0 0 0 28 28h44v-18.34l-14.83-14.83a4 4 0 0 1 5.66-5.66L128 186.34l13.17-13.17a4 4 0 0 1 5.66 5.66L132 193.66V212h44a28 28 0 0 0 28-28Zm23.92 5.48l-16.42-88a4 4 0 0 0-4.84-3.16l-47.91 14.11l62.11 80.28a4 4 0 0 0 7.06-3.23Z"/></svg>`,
Giorgi Lekveishvili27b2b572023-06-30 10:44:45 +0400369 "Delights users with randomly generate puppy pictures. Can be configured to be reachable only from private network or publicly.",
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400370 }
371}
372
Giorgi Lekveishvili672af5d2023-07-12 11:57:51 +0400373func CreateAppSoftServe(fs embed.FS, tmpls *template.Template) StoreApp {
374 schema, err := fs.ReadFile("values-tmpl/soft-serve.jsonschema")
375 if err != nil {
376 panic(err)
377 }
378 return StoreApp{
379 App{
380 "soft-serve",
381 []string{"app-soft-serve"},
382 []*template.Template{
383 tmpls.Lookup("soft-serve.yaml"),
384 },
385 string(schema),
386 tmpls.Lookup("soft-serve.md"),
387 },
388 `<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 48 48"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="4"><path stroke-linejoin="round" d="M15.34 22.5L21 37l3 6l3-6l5.66-14.5"/><path d="M19 32h10"/><path stroke-linejoin="round" d="M24 3c-6 0-8 6-8 6s-6 2-6 7s5 7 5 7s3.5-2 9-2s9 2 9 2s5-2 5-7s-6-7-6-7s-2-6-8-6Z"/></g></svg>`,
389 "A tasty, self-hostable Git server for the command line. 🍦",
390 }
391}
392
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400393func CreateAppHeadscale(fs embed.FS, tmpls *template.Template) App {
394 schema, err := fs.ReadFile("values-tmpl/headscale.jsonschema")
395 if err != nil {
396 panic(err)
397 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400398 return App{
399 "headscale",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400400 []string{"app-headscale"},
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400401 []*template.Template{
402 tmpls.Lookup("headscale.yaml"),
403 },
Giorgi Lekveishvili7efe22f2023-05-30 13:01:53 +0400404 string(schema),
Giorgi Lekveishvili3a907052023-05-30 13:33:32 +0400405 tmpls.Lookup("headscale.md"),
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400406 }
407}
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400408
Giorgi Lekveishvili39913692023-12-05 08:58:08 +0400409func CreateAppHeadscaleUser(fs embed.FS, tmpls *template.Template) App {
410 schema, err := fs.ReadFile("values-tmpl/headscale-user.jsonschema")
411 if err != nil {
412 panic(err)
413 }
414 return App{
415 "headscale-user",
416 []string{"app-headscale"},
417 []*template.Template{
418 tmpls.Lookup("headscale-user.yaml"),
419 },
420 string(schema),
421 tmpls.Lookup("headscale-user.md"),
422 }
423}
424
Giorgi Lekveishvili4fc29432023-07-20 10:03:28 +0400425func CreateMetallbIPAddressPool(fs embed.FS, tmpls *template.Template) App {
426 schema, err := fs.ReadFile("values-tmpl/metallb-ipaddresspool.jsonschema")
427 if err != nil {
428 panic(err)
429 }
430 return App{
431 "metallb-ipaddresspool",
432 []string{"metallb-ipaddresspool"},
433 []*template.Template{
434 tmpls.Lookup("metallb-ipaddresspool.yaml"),
435 },
436 string(schema),
437 tmpls.Lookup("metallb-ipaddresspool.md"),
438 }
439}
440
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400441func CreateEnvManager(fs embed.FS, tmpls *template.Template) App {
442 schema, err := fs.ReadFile("values-tmpl/env-manager.jsonschema")
443 if err != nil {
444 panic(err)
445 }
446 return App{
447 "env-manager",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400448 []string{"env-manager"},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400449 []*template.Template{
450 tmpls.Lookup("env-manager.yaml"),
451 },
452 string(schema),
453 tmpls.Lookup("env-manager.md"),
454 }
455}
456
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400457func CreateWelcome(fs embed.FS, tmpls *template.Template) App {
458 schema, err := fs.ReadFile("values-tmpl/welcome.jsonschema")
459 if err != nil {
460 panic(err)
461 }
462 return App{
463 "welcome",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400464 []string{"app-welcome"},
Giorgi Lekveishvili12850ee2023-06-22 13:11:17 +0400465 []*template.Template{
466 tmpls.Lookup("welcome.yaml"),
467 },
468 string(schema),
469 tmpls.Lookup("welcome.md"),
470 }
471}
472
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400473func CreateAppManager(fs embed.FS, tmpls *template.Template) App {
474 schema, err := fs.ReadFile("values-tmpl/appmanager.jsonschema")
475 if err != nil {
476 panic(err)
477 }
478 return App{
479 "app-manager",
480 []string{"core-appmanager"},
481 []*template.Template{
482 tmpls.Lookup("appmanager.yaml"),
483 },
484 string(schema),
485 tmpls.Lookup("appmanager.md"),
486 }
487}
488
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400489func CreateIngressPublic(fs embed.FS, tmpls *template.Template) App {
490 schema, err := fs.ReadFile("values-tmpl/ingress-public.jsonschema")
491 if err != nil {
492 panic(err)
493 }
494 return App{
495 "ingress-public",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400496 []string{"ingress-public"},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400497 []*template.Template{
498 tmpls.Lookup("ingress-public.yaml"),
499 },
500 string(schema),
501 tmpls.Lookup("ingress-public.md"),
502 }
503}
504
505func CreateCertManager(fs embed.FS, tmpls *template.Template) App {
506 schema, err := fs.ReadFile("values-tmpl/cert-manager.jsonschema")
507 if err != nil {
508 panic(err)
509 }
510 return App{
511 "cert-manager",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400512 []string{"cert-manager"},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400513 []*template.Template{
514 tmpls.Lookup("cert-manager.yaml"),
515 },
516 string(schema),
517 tmpls.Lookup("cert-manager.md"),
518 }
519}
520
521func CreateCertManagerWebhookGandi(fs embed.FS, tmpls *template.Template) App {
Giorgi Lekveishvili5c2c0b92023-12-07 17:35:40 +0400522 schema, err := fs.ReadFile("values-tmpl/cert-manager-webhook-pcloud.jsonschema")
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400523 if err != nil {
524 panic(err)
525 }
526 return App{
Giorgi Lekveishvili5c2c0b92023-12-07 17:35:40 +0400527 "cert-manager-webhook-pcloud",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400528 []string{},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400529 []*template.Template{
Giorgi Lekveishvili5c2c0b92023-12-07 17:35:40 +0400530 tmpls.Lookup("cert-manager-webhook-pcloud.yaml"),
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400531 },
532 string(schema),
Giorgi Lekveishvili5c2c0b92023-12-07 17:35:40 +0400533 tmpls.Lookup("cert-manager-webhook-pcloud.md"),
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400534 }
535}
536
537func CreateCSIDriverSMB(fs embed.FS, tmpls *template.Template) App {
538 schema, err := fs.ReadFile("values-tmpl/csi-driver-smb.jsonschema")
539 if err != nil {
540 panic(err)
541 }
542 return App{
543 "csi-driver-smb",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400544 []string{"csi-driver-smb"},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400545 []*template.Template{
546 tmpls.Lookup("csi-driver-smb.yaml"),
547 },
548 string(schema),
549 tmpls.Lookup("csi-driver-smb.md"),
550 }
551}
552
553func CreateResourceRendererController(fs embed.FS, tmpls *template.Template) App {
554 schema, err := fs.ReadFile("values-tmpl/resource-renderer-controller.jsonschema")
555 if err != nil {
556 panic(err)
557 }
558 return App{
559 "resource-renderer-controller",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400560 []string{"rr-controller"},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400561 []*template.Template{
562 tmpls.Lookup("resource-renderer-controller.yaml"),
563 },
564 string(schema),
565 tmpls.Lookup("resource-renderer-controller.md"),
566 }
567}
568
569func CreateHeadscaleController(fs embed.FS, tmpls *template.Template) App {
570 schema, err := fs.ReadFile("values-tmpl/headscale-controller.jsonschema")
571 if err != nil {
572 panic(err)
573 }
574 return App{
575 "headscale-controller",
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400576 []string{"headscale-controller"},
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400577 []*template.Template{
578 tmpls.Lookup("headscale-controller.yaml"),
579 },
580 string(schema),
581 tmpls.Lookup("headscale-controller.md"),
582 }
583}
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400584
Giorgi Lekveishvili106a9352023-12-04 11:20:11 +0400585func CreateDNSZoneManager(fs embed.FS, tmpls *template.Template) App {
586 schema, err := fs.ReadFile("values-tmpl/dns-zone-controller.jsonschema")
587 if err != nil {
588 panic(err)
589 }
590 return App{
591 "dns-zone-manager",
592 []string{"dns-zone-manager"},
593 []*template.Template{
594 tmpls.Lookup("dns-zone-storage.yaml"),
595 tmpls.Lookup("coredns.yaml"),
596 tmpls.Lookup("dns-zone-controller.yaml"),
597 },
598 string(schema),
599 tmpls.Lookup("dns-zone-controller.md"),
600 }
601}
602
Giorgi Lekveishvili2df23db2023-12-14 07:55:22 +0400603func CreateFluxcdReconciler(fs embed.FS, tmpls *template.Template) App {
604 schema, err := fs.ReadFile("values-tmpl/fluxcd-reconciler.jsonschema")
605 if err != nil {
606 panic(err)
607 }
608 return App{
609 "fluxcd-reconciler",
610 []string{"fluxcd-reconciler"},
611 []*template.Template{
612 tmpls.Lookup("fluxcd-reconciler.yaml"),
613 },
614 string(schema),
615 tmpls.Lookup("fluxcd-reconciler.md"),
616 }
617}
618
Giorgi Lekveishvili743fb432023-11-08 17:19:40 +0400619type httpAppRepository struct {
620 apps []StoreApp
621}
622
623type appVersion struct {
624 Version string `json:"version"`
625 Urls []string `json:"urls"`
626}
627
628type allAppsResp struct {
629 ApiVersion string `json:"apiVersion"`
630 Entries map[string][]appVersion `json:"entries"`
631}
632
633func FetchAppsFromHTTPRepository(addr string, fs billy.Filesystem) error {
634 resp, err := http.Get(addr)
635 if err != nil {
636 return err
637 }
638 b, err := io.ReadAll(resp.Body)
639 if err != nil {
640 return err
641 }
642 var apps allAppsResp
643 if err := yaml.Unmarshal(b, &apps); err != nil {
644 return err
645 }
646 for name, conf := range apps.Entries {
647 for _, version := range conf {
648 resp, err := http.Get(version.Urls[0])
649 if err != nil {
650 return err
651 }
652 nameVersion := fmt.Sprintf("%s-%s", name, version.Version)
653 if err := fs.MkdirAll(nameVersion, 0700); err != nil {
654 return err
655 }
656 sub, err := fs.Chroot(nameVersion)
657 if err != nil {
658 return err
659 }
660 if err := extractApp(resp.Body, sub); err != nil {
661 return err
662 }
663 }
664 }
665 return nil
666}
667
668func extractApp(archive io.Reader, fs billy.Filesystem) error {
669 uncompressed, err := gzip.NewReader(archive)
670 if err != nil {
671 return err
672 }
673 tarReader := tar.NewReader(uncompressed)
674 for true {
675 header, err := tarReader.Next()
676 if err == io.EOF {
677 break
678 }
679 if err != nil {
680 return err
681 }
682 switch header.Typeflag {
683 case tar.TypeDir:
684 if err := fs.MkdirAll(header.Name, 0755); err != nil {
685 return err
686 }
687 case tar.TypeReg:
688 out, err := fs.Create(header.Name)
689 if err != nil {
690 return err
691 }
692 defer out.Close()
693 if _, err := io.Copy(out, tarReader); err != nil {
694 return err
695 }
696 default:
697 return fmt.Errorf("Uknown type: %s", header.Name)
698 }
699 }
700 return nil
701}
702
703type fsAppRepository struct {
704 InMemoryAppRepository[StoreApp]
705 fs billy.Filesystem
706}
707
708func NewFSAppRepository(fs billy.Filesystem) (AppRepository[StoreApp], error) {
709 all, err := fs.ReadDir(".")
710 if err != nil {
711 return nil, err
712 }
713 apps := make([]StoreApp, 0)
714 for _, e := range all {
715 if !e.IsDir() {
716 continue
717 }
718 appFS, err := fs.Chroot(e.Name())
719 if err != nil {
720 return nil, err
721 }
722 app, err := loadApp(appFS)
723 if err != nil {
724 log.Printf("Ignoring directory %s: %s", e.Name(), err)
725 continue
726 }
727 apps = append(apps, app)
728 }
729 return &fsAppRepository{
730 NewInMemoryAppRepository[StoreApp](apps),
731 fs,
732 }, nil
733}
734
735func loadApp(fs billy.Filesystem) (StoreApp, error) {
736 cfg, err := fs.Open("Chart.yaml")
737 if err != nil {
738 return StoreApp{}, err
739 }
740 defer cfg.Close()
741 b, err := io.ReadAll(cfg)
742 if err != nil {
743 return StoreApp{}, err
744 }
745 var appCfg appConfig
746 if err := yaml.Unmarshal(b, &appCfg); err != nil {
747 return StoreApp{}, err
748 }
749 rb, err := fs.Open("README.md")
750 if err != nil {
751 return StoreApp{}, err
752 }
753 defer rb.Close()
754 readme, err := io.ReadAll(rb)
755 if err != nil {
756 return StoreApp{}, err
757 }
758 readmeTmpl, err := template.New("README.md").Parse(string(readme))
759 if err != nil {
760 return StoreApp{}, err
761 }
762 sb, err := fs.Open("schema.json")
763 if err != nil {
764 return StoreApp{}, err
765 }
766 defer sb.Close()
767 schema, err := io.ReadAll(sb)
768 if err != nil {
769 return StoreApp{}, err
770 }
771 tFiles, err := fs.ReadDir("templates")
772 if err != nil {
773 return StoreApp{}, err
774 }
775 tmpls := make([]*template.Template, 0)
776 for _, t := range tFiles {
777 if !strings.HasSuffix(t.Name(), ".yaml") {
778 continue
779 }
780 inp, err := fs.Open(fs.Join("templates", t.Name()))
781 if err != nil {
782 return StoreApp{}, err
783 }
784 b, err := io.ReadAll(inp)
785 if err != nil {
786 return StoreApp{}, err
787 }
788 tmpl, err := template.New(t.Name()).Parse(string(b))
789 if err != nil {
790 return StoreApp{}, err
791 }
792 tmpls = append(tmpls, tmpl)
793 }
794 return StoreApp{
795 App: App{
796 Name: appCfg.Name,
797 Readme: readmeTmpl,
798 Schema: string(schema),
799 Namespaces: appCfg.Namespaces,
800 Templates: tmpls,
801 },
802 ShortDescription: appCfg.Description,
803 Icon: appCfg.Icon,
804 }, nil
805}