blob: 4eea2c794c8b80edfb9ffb932e15675f2190e967 [file] [log] [blame]
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04001package welcome
2
3import (
4 "embed"
5 "encoding/base64"
6 "encoding/json"
7 "fmt"
8 "log"
9 "net/http"
10 "path"
11 "text/template"
12
13 "github.com/labstack/echo/v4"
14
15 "github.com/giolekva/pcloud/core/installer"
16 "github.com/giolekva/pcloud/core/installer/soft"
17)
18
19//go:embed env-tmpl
20var filesTmpls embed.FS
21
22//go:embed create-env.html
23var createEnvFormHtml string
24
25type EnvServer struct {
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040026 port int
27 ss *soft.Client
28 repo installer.RepoIO
29 nsCreator installer.NamespaceCreator
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040030}
31
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040032func NewEnvServer(port int, ss *soft.Client, repo installer.RepoIO, nsCreator installer.NamespaceCreator) *EnvServer {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040033 return &EnvServer{
34 port,
35 ss,
36 repo,
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040037 nsCreator,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040038 }
39}
40
41func (s *EnvServer) Start() {
42 e := echo.New()
43 e.StaticFS("/static", echo.MustSubFS(staticAssets, "static"))
44 e.GET("/env", s.createEnvForm)
45 e.POST("/env", s.createEnv)
46 log.Fatal(e.Start(fmt.Sprintf(":%d", s.port)))
47}
48
49func (s *EnvServer) createEnvForm(c echo.Context) error {
50 return c.HTML(http.StatusOK, createEnvFormHtml)
51}
52
53type createEnvReq struct {
54 Name string `json:"name"`
55 ContactEmail string `json:"contactEmail"`
56 Domain string `json:"domain"`
57}
58
59func (s *EnvServer) createEnv(c echo.Context) error {
60 var req createEnvReq
61 if err := func() error {
62 var err error
63 f, err := c.FormParams()
64 if err != nil {
65 return err
66 }
67 if req.Name, err = getFormValue(f, "name"); err != nil {
68 return err
69 }
70 if req.Domain, err = getFormValue(f, "domain"); err != nil {
71 return err
72 }
73 if req.ContactEmail, err = getFormValue(f, "contact-email"); err != nil {
74 return err
75 }
76 return nil
77 }(); err != nil {
78 if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil {
79 return err
80 }
81 }
82 keys, err := installer.NewSSHKeyPair()
83 if err != nil {
84 return err
85 }
86 {
87 readme := fmt.Sprintf("# %s PCloud environment", req.Name)
88 if err := s.ss.AddRepository(req.Name, readme); err != nil {
89 return err
90 }
91 fluxUserName := fmt.Sprintf("flux-%s", req.Name)
92 if err := s.ss.AddUser(fluxUserName, keys.Public); err != nil {
93 return err
94 }
95 if err := s.ss.AddCollaborator(req.Name, fluxUserName); err != nil {
96 return err
97 }
98 }
99 {
100 repo, err := s.ss.GetRepo(req.Name)
101 if repo == nil {
102 return err
103 }
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400104 if err := initNewEnv(s.ss, installer.NewRepoIO(repo, s.ss.Signer), s.nsCreator, req); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400105 return err
106 }
107 }
108 {
109 repo, err := s.ss.GetRepo("pcloud")
110 if err != nil {
111 return err
112 }
113 ssPubKey, err := s.ss.GetPublicKey()
114 if err != nil {
115 return err
116 }
117 if err := addNewEnv(
118 installer.NewRepoIO(repo, s.ss.Signer),
119 req,
120 keys,
121 ssPubKey,
122 ); err != nil {
123 return err
124 }
125 }
126 return c.String(http.StatusOK, "OK")
127}
128
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400129func initNewEnv(ss *soft.Client, r installer.RepoIO, nsCreator installer.NamespaceCreator, req createEnvReq) error {
130 appManager, err := installer.NewAppManager(r, nsCreator)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400131 if err != nil {
132 return err
133 }
134 appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
135 // TODO(giolekva): env name and ip should come from pcloud repo config.yaml
136 // TODO(giolekva): private domain can be configurable as well
137 config := installer.Config{
138 Values: installer.Values{
139 PCloudEnvName: "pcloud",
140 Id: req.Name,
141 ContactEmail: req.ContactEmail,
142 Domain: req.Domain,
143 PrivateDomain: fmt.Sprintf("p.%s", req.Domain),
144 PublicIP: "46.49.35.44",
145 NamespacePrefix: fmt.Sprintf("%s-", req.Name),
146 },
147 }
148 if err := r.WriteYaml("config.yaml", config); err != nil {
149 return err
150 }
151 {
152 out, err := r.Writer("pcloud-charts.yaml")
153 if err != nil {
154 return err
155 }
156 defer out.Close()
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400157 _, err = out.Write([]byte(fmt.Sprintf(`
Giorgi Lekveishvilibf1e6e82023-07-12 11:57:24 +0400158apiVersion: source.toolkit.fluxcd.io/v1
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400159kind: GitRepository
160metadata:
161 name: pcloud
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400162 namespace: %s
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400163spec:
164 interval: 1m0s
165 url: https://github.com/giolekva/pcloud
166 ref:
167 branch: main
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400168`, req.Name)))
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400169 if err != nil {
170 return err
171 }
172 }
173 rootKust := installer.NewKustomization()
174 rootKust.AddResources("pcloud-charts.yaml", "apps")
175 if err := r.WriteKustomization("kustomization.yaml", rootKust); err != nil {
176 return err
177 }
178 appsKust := installer.NewKustomization()
179 if err := r.WriteKustomization("apps/kustomization.yaml", appsKust); err != nil {
180 return err
181 }
182 r.CommitAndPush("initialize config")
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +0400183 nsGen := installer.NewPrefixGenerator(req.Name + "-")
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400184 suffixGen := installer.NewEmptySuffixGenerator()
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400185 {
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400186 app, err := appsRepo.Find("metallb-ipaddresspool")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400187 if err != nil {
188 return err
189 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400190 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
Giorgi Lekveishvili41563dc2023-07-20 10:37:35 +0400191 "Name": fmt.Sprintf("%s-ingress-private", req.Name),
192 "From": "10.1.0.1",
193 "To": "10.1.0.1",
194 "AutoAssign": false,
195 }); err != nil {
196 return err
197 }
198 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
199 "Name": fmt.Sprintf("%s-headscale", req.Name),
200 "From": "10.1.0.2",
201 "To": "10.1.0.2",
202 "AutoAssign": false,
203 }); err != nil {
204 return err
205 }
206 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
207 "Name": fmt.Sprintf("%s-soft-serve", req.Name), // TODO(giolekva): rename to config repo
208 "From": "10.1.0.3",
209 "To": "10.1.0.3",
210 "AutoAssign": false,
211 }); err != nil {
212 return err
213 }
214 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
215 "Name": req.Name,
216 "From": "10.1.0.100",
217 "To": "10.1.0.254",
218 "AutoAssign": false,
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400219 }); err != nil {
220 return err
221 }
222 }
223 {
224 app, err := appsRepo.Find("ingress-private")
225 if err != nil {
226 return err
227 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400228 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400229 return err
230 }
231 }
232 {
233 app, err := appsRepo.Find("certificate-issuer-public")
234 if err != nil {
235 return err
236 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400237 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400238 return err
239 }
240 }
241 {
242 app, err := appsRepo.Find("core-auth")
243 if err != nil {
244 return err
245 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400246 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400247 "Subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
248 }); err != nil {
249 return err
250 }
251 }
252 {
253 app, err := appsRepo.Find("headscale")
254 if err != nil {
255 return err
256 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400257 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400258 "Subdomain": "headscale",
259 }); err != nil {
260 return err
261 }
262 }
263 {
264 keys, err := installer.NewSSHKeyPair()
265 if err != nil {
266 return err
267 }
268 user := fmt.Sprintf("%s-welcome", req.Name)
269 if err := ss.AddUser(user, keys.Public); err != nil {
270 return err
271 }
272 if err := ss.AddCollaborator(req.Name, user); err != nil {
273 return err
274 }
275 app, err := appsRepo.Find("welcome")
276 if err != nil {
277 return err
278 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400279 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400280 "RepoAddr": ss.GetRepoAddress(req.Name),
281 "SSHPrivateKey": keys.Private,
282 }); err != nil {
283 return err
284 }
285 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400286 {
287 keys, err := installer.NewSSHKeyPair()
288 if err != nil {
289 return err
290 }
291 user := fmt.Sprintf("%s-appmanager", req.Name)
292 if err := ss.AddUser(user, keys.Public); err != nil {
293 return err
294 }
295 if err := ss.AddCollaborator(req.Name, user); err != nil {
296 return err
297 }
298 app, err := appsRepo.Find("app-manager") // TODO(giolekva): configure
299 if err != nil {
300 return err
301 }
302 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
303 "RepoAddr": ss.GetRepoAddress(req.Name),
304 "SSHPrivateKey": keys.Private,
305 }); err != nil {
306 return err
307 }
308 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400309 return nil
310}
311
312func addNewEnv(
313 repoIO installer.RepoIO,
314 req createEnvReq,
315 keys installer.KeyPair,
316 pcloudRepoPublicKey []byte,
317) error {
318 kust, err := repoIO.ReadKustomization("environments/kustomization.yaml")
319 if err != nil {
320 return err
321 }
322 kust.AddResources(req.Name)
323 tmpls, err := template.ParseFS(filesTmpls, "env-tmpl/*.yaml")
324 if err != nil {
325 return err
326 }
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400327 repoIP := repoIO.Addr().Addr().String()
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400328 for _, tmpl := range tmpls.Templates() {
329 dstPath := path.Join("environments", req.Name, tmpl.Name())
330 dst, err := repoIO.Writer(dstPath)
331 if err != nil {
332 return err
333 }
334 defer dst.Close()
335 if err := tmpl.Execute(dst, map[string]string{
336 "Name": req.Name,
337 "PrivateKey": base64.StdEncoding.EncodeToString([]byte(keys.Private)),
338 "PublicKey": base64.StdEncoding.EncodeToString([]byte(keys.Public)),
339 "GitHost": repoIP,
340 "KnownHosts": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s %s", repoIP, pcloudRepoPublicKey))),
341 }); err != nil {
342 return err
343 }
344 }
345 if err := repoIO.WriteKustomization("environments/kustomization.yaml", *kust); err != nil {
346 return err
347 }
348 if err := repoIO.CommitAndPush(fmt.Sprintf("%s: initialize environment", req.Name)); err != nil {
349 return err
350 }
351 return nil
352}