blob: 22713f917475ccef8d766110212c2bf779d4a1f3 [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 {
186 app, err := appsRepo.Find("metallb-config-env")
187 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 Lekveishvilib4a9c982023-06-22 15:17:02 +0400191 "IngressPrivate": "10.1.0.1",
192 "Headscale": "10.1.0.2",
193 "SoftServe": "10.1.0.3",
194 "Rest": map[string]any{
195 "From": "10.1.0.100",
196 "To": "10.1.0.255",
197 },
198 }); err != nil {
199 return err
200 }
201 }
202 {
203 app, err := appsRepo.Find("ingress-private")
204 if err != nil {
205 return err
206 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400207 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400208 return err
209 }
210 }
211 {
212 app, err := appsRepo.Find("certificate-issuer-public")
213 if err != nil {
214 return err
215 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400216 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{}); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400217 return err
218 }
219 }
220 {
221 app, err := appsRepo.Find("core-auth")
222 if err != nil {
223 return err
224 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400225 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400226 "Subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
227 }); err != nil {
228 return err
229 }
230 }
231 {
232 app, err := appsRepo.Find("headscale")
233 if err != nil {
234 return err
235 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400236 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400237 "Subdomain": "headscale",
238 }); err != nil {
239 return err
240 }
241 }
242 {
243 keys, err := installer.NewSSHKeyPair()
244 if err != nil {
245 return err
246 }
247 user := fmt.Sprintf("%s-welcome", req.Name)
248 if err := ss.AddUser(user, keys.Public); err != nil {
249 return err
250 }
251 if err := ss.AddCollaborator(req.Name, user); err != nil {
252 return err
253 }
254 app, err := appsRepo.Find("welcome")
255 if err != nil {
256 return err
257 }
Giorgi Lekveishvili6e813182023-06-30 13:45:30 +0400258 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400259 "RepoAddr": ss.GetRepoAddress(req.Name),
260 "SSHPrivateKey": keys.Private,
261 }); err != nil {
262 return err
263 }
264 }
Giorgi Lekveishvili4257b902023-07-07 17:08:42 +0400265 {
266 keys, err := installer.NewSSHKeyPair()
267 if err != nil {
268 return err
269 }
270 user := fmt.Sprintf("%s-appmanager", req.Name)
271 if err := ss.AddUser(user, keys.Public); err != nil {
272 return err
273 }
274 if err := ss.AddCollaborator(req.Name, user); err != nil {
275 return err
276 }
277 app, err := appsRepo.Find("app-manager") // TODO(giolekva): configure
278 if err != nil {
279 return err
280 }
281 if err := appManager.Install(*app, nsGen, suffixGen, map[string]any{
282 "RepoAddr": ss.GetRepoAddress(req.Name),
283 "SSHPrivateKey": keys.Private,
284 }); err != nil {
285 return err
286 }
287 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400288 return nil
289}
290
291func addNewEnv(
292 repoIO installer.RepoIO,
293 req createEnvReq,
294 keys installer.KeyPair,
295 pcloudRepoPublicKey []byte,
296) error {
297 kust, err := repoIO.ReadKustomization("environments/kustomization.yaml")
298 if err != nil {
299 return err
300 }
301 kust.AddResources(req.Name)
302 tmpls, err := template.ParseFS(filesTmpls, "env-tmpl/*.yaml")
303 if err != nil {
304 return err
305 }
306 repoIP := "192.168.0.211" // TODO(giolekva): configure
307 for _, tmpl := range tmpls.Templates() {
308 dstPath := path.Join("environments", req.Name, tmpl.Name())
309 dst, err := repoIO.Writer(dstPath)
310 if err != nil {
311 return err
312 }
313 defer dst.Close()
314 if err := tmpl.Execute(dst, map[string]string{
315 "Name": req.Name,
316 "PrivateKey": base64.StdEncoding.EncodeToString([]byte(keys.Private)),
317 "PublicKey": base64.StdEncoding.EncodeToString([]byte(keys.Public)),
318 "GitHost": repoIP,
319 "KnownHosts": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s %s", repoIP, pcloudRepoPublicKey))),
320 }); err != nil {
321 return err
322 }
323 }
324 if err := repoIO.WriteKustomization("environments/kustomization.yaml", *kust); err != nil {
325 return err
326 }
327 if err := repoIO.CommitAndPush(fmt.Sprintf("%s: initialize environment", req.Name)); err != nil {
328 return err
329 }
330 return nil
331}