blob: 9dd2abc227ea363edcb2b715f36e25a98b8bccab [file] [log] [blame]
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04001package welcome
2
3import (
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +04004 _ "embed"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04005 "encoding/json"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +04006 "errors"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +04007 "fmt"
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +04008 htemplate "html/template"
9 "io"
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +040010 "io/fs"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040011 "log"
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040012 "net"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040013 "net/http"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040014
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040015 "github.com/gorilla/mux"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040016
17 "github.com/giolekva/pcloud/core/installer"
18 "github.com/giolekva/pcloud/core/installer/soft"
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040019 "github.com/giolekva/pcloud/core/installer/tasks"
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040020)
21
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040022//go:embed create-env.html
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040023var createEnvFormHtml []byte
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040024
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040025//go:embed env-created.html
26var envCreatedHtml string
27
28type Status string
29
30const (
31 StatusActive Status = "ACTIVE"
32 StatusAccepted Status = "ACCEPTED"
33)
34
35// TODO(giolekva): add CreatedAt and ValidUntil
36type invitation struct {
37 Token string `json:"token"`
38 Status Status `json:"status"`
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040039}
40
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040041type EnvServer struct {
42 port int
43 ss *soft.Client
44 repo installer.RepoIO
45 nsCreator installer.NamespaceCreator
46 nameGenerator installer.NameGenerator
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040047 tasks map[string]tasks.Task
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040048}
49
50func NewEnvServer(port int, ss *soft.Client, repo installer.RepoIO, nsCreator installer.NamespaceCreator, nameGenerator installer.NameGenerator) *EnvServer {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040051 return &EnvServer{
52 port,
53 ss,
54 repo,
Giorgi Lekveishvili7fb28bf2023-06-24 19:51:16 +040055 nsCreator,
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040056 nameGenerator,
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040057 make(map[string]tasks.Task),
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040058 }
59}
60
61func (s *EnvServer) Start() {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040062 r := mux.NewRouter()
63 r.PathPrefix("/static/").Handler(http.FileServer(http.FS(staticAssets)))
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040064 r.Path("/env/{key}").Methods("GET").HandlerFunc(s.monitorTask)
Giorgi Lekveishvili123a3672023-12-04 13:01:29 +040065 r.Path("/").Methods("GET").HandlerFunc(s.createEnvForm)
66 r.Path("/").Methods("POST").HandlerFunc(s.createEnv)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040067 r.Path("/create-invitation").Methods("GET").HandlerFunc(s.createInvitation)
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040068 http.Handle("/", r)
69 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040070}
71
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040072func (s *EnvServer) monitorTask(w http.ResponseWriter, r *http.Request) {
73 vars := mux.Vars(r)
74 fmt.Printf("%+v %+v\n", s.tasks, vars)
75 t, ok := s.tasks[vars["key"]]
76 if !ok {
77 http.Error(w, "Task not found", http.StatusBadRequest)
78 return
79 }
80 tmpl, err := htemplate.New("response").Parse(envCreatedHtml)
81 if err != nil {
82 http.Error(w, err.Error(), http.StatusInternalServerError)
83 return
84 }
85 if err := tmpl.Execute(w, map[string]any{
86 "Root": t,
87 }); err != nil {
88 http.Error(w, err.Error(), http.StatusInternalServerError)
89 return
90 }
91}
92
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040093func (s *EnvServer) createEnvForm(w http.ResponseWriter, r *http.Request) {
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +040094 if _, err := w.Write(createEnvFormHtml); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040095 http.Error(w, err.Error(), http.StatusInternalServerError)
96 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +040097}
98
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +040099func (s *EnvServer) createInvitation(w http.ResponseWriter, r *http.Request) {
100 invitations, err := s.readInvitations()
101 if err != nil {
102 http.Error(w, err.Error(), http.StatusInternalServerError)
103 return
104 }
105 token, err := installer.NewFixedLengthRandomNameGenerator(100).Generate() // TODO(giolekva): use cryptographic tokens
106 if err != nil {
107 http.Error(w, err.Error(), http.StatusInternalServerError)
108 return
109
110 }
111 invitations = append(invitations, invitation{token, StatusActive})
112 if err := s.writeInvitations(invitations); err != nil {
113 http.Error(w, err.Error(), http.StatusInternalServerError)
114 return
115 }
116 if _, err := w.Write([]byte("OK")); err != nil {
117 http.Error(w, err.Error(), http.StatusInternalServerError)
118 return
119 }
120}
121
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400122type createEnvReq struct {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400123 Name string
124 ContactEmail string `json:"contactEmail"`
125 Domain string `json:"domain"`
126 AdminPublicKey string `json:"adminPublicKey"`
127 SecretToken string `json:"secretToken"`
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400128}
129
130func (s *EnvServer) readInvitations() ([]invitation, error) {
131 r, err := s.repo.Reader("invitations")
132 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400133 if errors.Is(err, fs.ErrNotExist) {
134 return make([]invitation, 0), nil
135 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400136 return nil, err
137 }
138 defer r.Close()
139 dec := json.NewDecoder(r)
140 invitations := make([]invitation, 0)
141 for {
142 var i invitation
143 if err := dec.Decode(&i); err == io.EOF {
144 break
145 }
146 invitations = append(invitations, i)
147 }
148 return invitations, nil
149}
150
151func (s *EnvServer) writeInvitations(invitations []invitation) error {
152 w, err := s.repo.Writer("invitations")
153 if err != nil {
154 return err
155 }
156 defer w.Close()
157 enc := json.NewEncoder(w)
158 for _, i := range invitations {
159 if err := enc.Encode(i); err != nil {
160 return err
161 }
162 }
163 return s.repo.CommitAndPush("Generated new invitation")
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400164}
165
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400166func extractRequest(r *http.Request) (createEnvReq, error) {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400167 var req createEnvReq
168 if err := func() error {
169 var err error
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400170 if err = r.ParseForm(); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400171 return err
172 }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400173 if req.SecretToken, err = getFormValue(r.PostForm, "secret-token"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400174 return err
175 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400176 if req.Domain, err = getFormValue(r.PostForm, "domain"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400177 return err
178 }
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400179 if req.ContactEmail, err = getFormValue(r.PostForm, "contact-email"); err != nil {
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400180 return err
181 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400182 if req.AdminPublicKey, err = getFormValue(r.PostForm, "admin-public-key"); err != nil {
183 return err
184 }
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400185 return nil
186 }(); err != nil {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400187 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400188 return createEnvReq{}, err
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400189 }
190 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400191 return req, nil
192}
193
194func (s *EnvServer) acceptInvitation(token string) error {
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400195 invitations, err := s.readInvitations()
196 if err != nil {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400197 return err
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400198 }
199 found := false
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400200 for i := range invitations {
201 if invitations[i].Token == token && invitations[i].Status == StatusActive {
202 invitations[i].Status = StatusAccepted
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400203 found = true
204 break
205 }
206 }
207 if !found {
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400208 return fmt.Errorf("Invitation not found")
209 }
210 return s.writeInvitations(invitations)
211}
212
213func (s *EnvServer) createEnv(w http.ResponseWriter, r *http.Request) {
214 req, err := extractRequest(r)
215 if err != nil {
216 http.Error(w, err.Error(), http.StatusInternalServerError)
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400217 return
218 }
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400219 var env installer.EnvConfig
220 cr, err := s.repo.Reader("config.yaml")
221 if err != nil {
222 http.Error(w, err.Error(), http.StatusInternalServerError)
223 return
224 }
225 defer cr.Close()
226 if err := installer.ReadYaml(cr, &env); err != nil {
227 http.Error(w, err.Error(), http.StatusInternalServerError)
228 return
229 }
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400230 // if err := s.acceptInvitation(req.SecretToken); err != nil {
231 // http.Error(w, err.Error(), http.StatusInternalServerError)
232 // return
233 // }
Giorgi Lekveishvili081f18f2023-11-07 14:58:10 +0400234 if name, err := s.nameGenerator.Generate(); err != nil {
235 http.Error(w, err.Error(), http.StatusInternalServerError)
236 return
237 } else {
238 req.Name = name
239 }
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400240 t := tasks.NewCreateEnvTask(
241 tasks.Env{
242 PCloudEnvName: env.Name,
243 Name: req.Name,
244 ContactEmail: req.ContactEmail,
245 Domain: req.Domain,
246 AdminPublicKey: req.AdminPublicKey,
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400247 },
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400248 []net.IP{
249 net.ParseIP("135.181.48.180"),
250 net.ParseIP("65.108.39.172"),
Giorgi Lekveishvili724885f2023-11-29 16:18:42 +0400251 },
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400252 s.nsCreator,
253 s.repo,
254 )
255 s.tasks["foo"] = t
Giorgi Lekveishvili77ee2dc2023-12-11 16:51:10 +0400256 go t.Start()
Giorgi Lekveishvili46743d42023-12-10 15:47:23 +0400257 http.Redirect(w, r, "/env/foo", http.StatusSeeOther)
Giorgi Lekveishvilib4a9c982023-06-22 15:17:02 +0400258}