blob: e8718309f90ab9618ec737fb777472d690e2491e [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 {
26 port int
27 ss *soft.Client
28 repo installer.RepoIO
29}
30
31func NewEnvServer(port int, ss *soft.Client, repo installer.RepoIO) *EnvServer {
32 return &EnvServer{
33 port,
34 ss,
35 repo,
36 }
37}
38
39func (s *EnvServer) Start() {
40 e := echo.New()
41 e.StaticFS("/static", echo.MustSubFS(staticAssets, "static"))
42 e.GET("/env", s.createEnvForm)
43 e.POST("/env", s.createEnv)
44 log.Fatal(e.Start(fmt.Sprintf(":%d", s.port)))
45}
46
47func (s *EnvServer) createEnvForm(c echo.Context) error {
48 return c.HTML(http.StatusOK, createEnvFormHtml)
49}
50
51type createEnvReq struct {
52 Name string `json:"name"`
53 ContactEmail string `json:"contactEmail"`
54 Domain string `json:"domain"`
55}
56
57func (s *EnvServer) createEnv(c echo.Context) error {
58 var req createEnvReq
59 if err := func() error {
60 var err error
61 f, err := c.FormParams()
62 if err != nil {
63 return err
64 }
65 if req.Name, err = getFormValue(f, "name"); err != nil {
66 return err
67 }
68 if req.Domain, err = getFormValue(f, "domain"); err != nil {
69 return err
70 }
71 if req.ContactEmail, err = getFormValue(f, "contact-email"); err != nil {
72 return err
73 }
74 return nil
75 }(); err != nil {
76 if err := json.NewDecoder(c.Request().Body).Decode(&req); err != nil {
77 return err
78 }
79 }
80 keys, err := installer.NewSSHKeyPair()
81 if err != nil {
82 return err
83 }
84 {
85 readme := fmt.Sprintf("# %s PCloud environment", req.Name)
86 if err := s.ss.AddRepository(req.Name, readme); err != nil {
87 return err
88 }
89 fluxUserName := fmt.Sprintf("flux-%s", req.Name)
90 if err := s.ss.AddUser(fluxUserName, keys.Public); err != nil {
91 return err
92 }
93 if err := s.ss.AddCollaborator(req.Name, fluxUserName); err != nil {
94 return err
95 }
96 }
97 {
98 repo, err := s.ss.GetRepo(req.Name)
99 if repo == nil {
100 return err
101 }
102 if err := initNewEnv(s.ss, installer.NewRepoIO(repo, s.ss.Signer), req); err != nil {
103 return err
104 }
105 }
106 {
107 repo, err := s.ss.GetRepo("pcloud")
108 if err != nil {
109 return err
110 }
111 ssPubKey, err := s.ss.GetPublicKey()
112 if err != nil {
113 return err
114 }
115 if err := addNewEnv(
116 installer.NewRepoIO(repo, s.ss.Signer),
117 req,
118 keys,
119 ssPubKey,
120 ); err != nil {
121 return err
122 }
123 }
124 return c.String(http.StatusOK, "OK")
125}
126
127func initNewEnv(ss *soft.Client, r installer.RepoIO, req createEnvReq) error {
128 appManager, err := installer.NewAppManager(r)
129 if err != nil {
130 return err
131 }
132 appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
133 // TODO(giolekva): env name and ip should come from pcloud repo config.yaml
134 // TODO(giolekva): private domain can be configurable as well
135 config := installer.Config{
136 Values: installer.Values{
137 PCloudEnvName: "pcloud",
138 Id: req.Name,
139 ContactEmail: req.ContactEmail,
140 Domain: req.Domain,
141 PrivateDomain: fmt.Sprintf("p.%s", req.Domain),
142 PublicIP: "46.49.35.44",
143 NamespacePrefix: fmt.Sprintf("%s-", req.Name),
144 },
145 }
146 if err := r.WriteYaml("config.yaml", config); err != nil {
147 return err
148 }
149 {
150 out, err := r.Writer("pcloud-charts.yaml")
151 if err != nil {
152 return err
153 }
154 defer out.Close()
155 _, err = out.Write([]byte(`
156apiVersion: source.toolkit.fluxcd.io/v1beta2
157kind: GitRepository
158metadata:
159 name: pcloud
160 namespace: lekva
161spec:
162 interval: 1m0s
163 url: https://github.com/giolekva/pcloud
164 ref:
165 branch: main
166`))
167 if err != nil {
168 return err
169 }
170 }
171 rootKust := installer.NewKustomization()
172 rootKust.AddResources("pcloud-charts.yaml", "apps")
173 if err := r.WriteKustomization("kustomization.yaml", rootKust); err != nil {
174 return err
175 }
176 appsKust := installer.NewKustomization()
177 if err := r.WriteKustomization("apps/kustomization.yaml", appsKust); err != nil {
178 return err
179 }
180 r.CommitAndPush("initialize config")
181 {
182 app, err := appsRepo.Find("metallb-config-env")
183 if err != nil {
184 return err
185 }
186 if err := appManager.Install(*app, map[string]any{
187 "IngressPrivate": "10.1.0.1",
188 "Headscale": "10.1.0.2",
189 "SoftServe": "10.1.0.3",
190 "Rest": map[string]any{
191 "From": "10.1.0.100",
192 "To": "10.1.0.255",
193 },
194 }); err != nil {
195 return err
196 }
197 }
198 {
199 app, err := appsRepo.Find("ingress-private")
200 if err != nil {
201 return err
202 }
203 if err := appManager.Install(*app, map[string]any{}); err != nil {
204 return err
205 }
206 }
207 {
208 app, err := appsRepo.Find("certificate-issuer-public")
209 if err != nil {
210 return err
211 }
212 if err := appManager.Install(*app, map[string]any{}); err != nil {
213 return err
214 }
215 }
216 {
217 app, err := appsRepo.Find("core-auth")
218 if err != nil {
219 return err
220 }
221 if err := appManager.Install(*app, map[string]any{
222 "Subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
223 }); err != nil {
224 return err
225 }
226 }
227 {
228 app, err := appsRepo.Find("headscale")
229 if err != nil {
230 return err
231 }
232 if err := appManager.Install(*app, map[string]any{
233 "Subdomain": "headscale",
234 }); err != nil {
235 return err
236 }
237 }
238 {
239 keys, err := installer.NewSSHKeyPair()
240 if err != nil {
241 return err
242 }
243 user := fmt.Sprintf("%s-welcome", req.Name)
244 if err := ss.AddUser(user, keys.Public); err != nil {
245 return err
246 }
247 if err := ss.AddCollaborator(req.Name, user); err != nil {
248 return err
249 }
250 app, err := appsRepo.Find("welcome")
251 if err != nil {
252 return err
253 }
254 if err := appManager.Install(*app, map[string]any{
255 "RepoAddr": ss.GetRepoAddress(req.Name),
256 "SSHPrivateKey": keys.Private,
257 }); err != nil {
258 return err
259 }
260 }
261 return nil
262}
263
264func addNewEnv(
265 repoIO installer.RepoIO,
266 req createEnvReq,
267 keys installer.KeyPair,
268 pcloudRepoPublicKey []byte,
269) error {
270 kust, err := repoIO.ReadKustomization("environments/kustomization.yaml")
271 if err != nil {
272 return err
273 }
274 kust.AddResources(req.Name)
275 tmpls, err := template.ParseFS(filesTmpls, "env-tmpl/*.yaml")
276 if err != nil {
277 return err
278 }
279 repoIP := "192.168.0.211" // TODO(giolekva): configure
280 for _, tmpl := range tmpls.Templates() {
281 dstPath := path.Join("environments", req.Name, tmpl.Name())
282 dst, err := repoIO.Writer(dstPath)
283 if err != nil {
284 return err
285 }
286 defer dst.Close()
287 if err := tmpl.Execute(dst, map[string]string{
288 "Name": req.Name,
289 "PrivateKey": base64.StdEncoding.EncodeToString([]byte(keys.Private)),
290 "PublicKey": base64.StdEncoding.EncodeToString([]byte(keys.Public)),
291 "GitHost": repoIP,
292 "KnownHosts": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s %s", repoIP, pcloudRepoPublicKey))),
293 }); err != nil {
294 return err
295 }
296 }
297 if err := repoIO.WriteKustomization("environments/kustomization.yaml", *kust); err != nil {
298 return err
299 }
300 if err := repoIO.CommitAndPush(fmt.Sprintf("%s: initialize environment", req.Name)); err != nil {
301 return err
302 }
303 return nil
304}