blob: ef148428a8cadbc69ed2e9a127b68351e928d3ec [file] [log] [blame]
gio0eaf2712024-04-14 13:08:46 +04001package main
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
gio183e8342024-08-20 06:01:24 +04007 "io"
gio0eaf2712024-04-14 13:08:46 +04008 "net/http"
9 "os"
10 "os/exec"
giofc441e32024-11-11 16:26:14 +040011 "path/filepath"
giobcd25e92025-05-03 19:14:10 +040012 "strings"
gio0eaf2712024-04-14 13:08:46 +040013 "sync"
gioff0ee0f2024-10-15 23:11:54 +040014 "syscall"
gio0eaf2712024-04-14 13:08:46 +040015 "time"
16
17 "golang.org/x/crypto/ssh"
18)
19
20type Server struct {
21 l sync.Locker
22 port int
gio266c04f2024-07-03 14:18:45 +040023 appId string
giob87415c2025-05-08 22:32:11 +040024 service string
gio0eaf2712024-04-14 13:08:46 +040025 ready bool
26 cmd *exec.Cmd
27 repoAddr string
gio2b1157a2024-10-24 08:45:07 +040028 branch string
giofc441e32024-11-11 16:26:14 +040029 rootDir string
gio0eaf2712024-04-14 13:08:46 +040030 signer ssh.Signer
31 appDir string
32 runCommands []Command
33 self string
gioa60f0de2024-07-08 10:49:48 +040034 managerAddr string
gio183e8342024-08-20 06:01:24 +040035 logs *Log
gio45c31822024-10-24 10:58:02 +040036 currDir string
gio0eaf2712024-04-14 13:08:46 +040037}
38
giob87415c2025-05-08 22:32:11 +040039func NewServer(port int, appId, service, repoAddr, branch, rootDir string, signer ssh.Signer, appDir string, runCommands []Command, self string, manager string) *Server {
gio0eaf2712024-04-14 13:08:46 +040040 return &Server{
41 l: &sync.Mutex{},
42 port: port,
43 ready: false,
gio266c04f2024-07-03 14:18:45 +040044 appId: appId,
giob87415c2025-05-08 22:32:11 +040045 service: service,
gio0eaf2712024-04-14 13:08:46 +040046 repoAddr: repoAddr,
gio2b1157a2024-10-24 08:45:07 +040047 branch: branch,
giofc441e32024-11-11 16:26:14 +040048 rootDir: rootDir,
gio0eaf2712024-04-14 13:08:46 +040049 signer: signer,
50 appDir: appDir,
51 runCommands: runCommands,
52 self: self,
gioa60f0de2024-07-08 10:49:48 +040053 managerAddr: manager,
gio183e8342024-08-20 06:01:24 +040054 logs: &Log{},
gio45c31822024-10-24 10:58:02 +040055 currDir: "",
gio0eaf2712024-04-14 13:08:46 +040056 }
57}
58
59func (s *Server) Start() error {
60 http.HandleFunc("/update", s.handleUpdate)
61 http.HandleFunc("/ready", s.handleReady)
gio183e8342024-08-20 06:01:24 +040062 http.HandleFunc("/logs", s.handleLogs)
giob87415c2025-05-08 22:32:11 +040063 go s.pingManager()
gio0eaf2712024-04-14 13:08:46 +040064 if err := s.run(); err != nil {
65 return err
66 }
gio0eaf2712024-04-14 13:08:46 +040067 return http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil)
68}
69
gio183e8342024-08-20 06:01:24 +040070func (s *Server) handleLogs(w http.ResponseWriter, r *http.Request) {
71 fmt.Fprint(w, s.logs.Contents())
72}
73
gio0eaf2712024-04-14 13:08:46 +040074func (s *Server) handleReady(w http.ResponseWriter, r *http.Request) {
75 s.l.Lock()
76 defer s.l.Unlock()
77 if s.ready {
78 fmt.Fprintln(w, "ok")
79 } else {
80 http.Error(w, "not ready", http.StatusInternalServerError)
81 }
82}
83
84func (s *Server) handleUpdate(w http.ResponseWriter, r *http.Request) {
85 fmt.Println("update")
86 s.l.Lock()
87 s.ready = false
88 s.l.Unlock()
gio0eaf2712024-04-14 13:08:46 +040089 if err := s.run(); err != nil {
90 http.Error(w, err.Error(), http.StatusInternalServerError)
91 return
92 }
93 s.l.Lock()
94 s.ready = true
95 s.l.Unlock()
96}
97
98func (s *Server) run() error {
gio45c31822024-10-24 10:58:02 +040099 newDir, err := os.MkdirTemp(s.appDir, "code-*")
100 if err != nil {
101 return err
102 }
giofc441e32024-11-11 16:26:14 +0400103 if err := CloneRepositoryBranch(s.repoAddr, s.branch, s.rootDir, s.signer, newDir); err != nil {
gio0eaf2712024-04-14 13:08:46 +0400104 return err
105 }
gio183e8342024-08-20 06:01:24 +0400106 logM := io.MultiWriter(os.Stdout, s.logs)
gio0eaf2712024-04-14 13:08:46 +0400107 for i, c := range s.runCommands {
108 args := []string{c.Bin}
109 args = append(args, c.Args...)
110 cmd := &exec.Cmd{
giofc441e32024-11-11 16:26:14 +0400111 Dir: filepath.Join(newDir, s.rootDir),
giobcd25e92025-05-03 19:14:10 +0400112 Path: "/bin/sh",
113 Args: []string{"/bin/sh", "-c", strings.Join(args, " ")},
gio7fbd4ad2024-08-27 10:06:39 +0400114 Env: append(os.Environ(), c.Env...),
gio183e8342024-08-20 06:01:24 +0400115 Stdout: logM,
116 Stderr: logM,
gio0eaf2712024-04-14 13:08:46 +0400117 }
gioff0ee0f2024-10-15 23:11:54 +0400118 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
gio5e4d1a72024-10-09 15:25:29 +0400119 fmt.Printf("Running: %s %s\n", c.Bin, c.Args)
gio0eaf2712024-04-14 13:08:46 +0400120 if i < len(s.runCommands)-1 {
121 if err := cmd.Run(); err != nil {
122 return err
123 }
124 } else {
gio45c31822024-10-24 10:58:02 +0400125 if s.cmd != nil {
126 // TODO(gio): make this point configurable
127 if err := s.kill(); err != nil {
128 return err
129 }
130 if err := os.RemoveAll(s.currDir); err != nil {
131 return err
132 }
133 }
gio0eaf2712024-04-14 13:08:46 +0400134 if err := cmd.Start(); err != nil {
135 return err
136 }
137 s.cmd = cmd
138 }
139 }
gio45c31822024-10-24 10:58:02 +0400140 s.currDir = newDir
gio0eaf2712024-04-14 13:08:46 +0400141 return nil
142}
143
144type pingReq struct {
giob87415c2025-05-08 22:32:11 +0400145 Service string `json:"service"`
gio0eaf2712024-04-14 13:08:46 +0400146 Address string `json:"address"`
gio183e8342024-08-20 06:01:24 +0400147 Logs string `json:"logs"`
gio0eaf2712024-04-14 13:08:46 +0400148}
149
150func (s *Server) pingManager() {
151 defer func() {
152 go func() {
153 time.Sleep(5 * time.Second)
154 s.pingManager()
155 }()
156 }()
gioa60f0de2024-07-08 10:49:48 +0400157 buf, err := json.Marshal(pingReq{
giob87415c2025-05-08 22:32:11 +0400158 Service: s.service,
159 Address: fmt.Sprintf("http://%s:%d", s.self, s.port),
gio183e8342024-08-20 06:01:24 +0400160 Logs: s.logs.Contents(),
gioa60f0de2024-07-08 10:49:48 +0400161 })
gio0eaf2712024-04-14 13:08:46 +0400162 if err != nil {
163 return
164 }
giob87415c2025-05-08 22:32:11 +0400165 registerWorkerAddr := fmt.Sprintf("%s/api/project/%s/workers", s.managerAddr, s.appId)
166 resp, err := http.Post(registerWorkerAddr, "application/json", bytes.NewReader(buf))
167 if err != nil {
168 fmt.Println(err)
169 } else {
170 // check resp code
171 io.Copy(os.Stdout, resp.Body)
172 }
gio0eaf2712024-04-14 13:08:46 +0400173}
gio45c31822024-10-24 10:58:02 +0400174
175func (s *Server) kill() error {
176 if s.cmd == nil {
177 return nil
178 }
179
180 err := syscall.Kill(-s.cmd.Process.Pid, syscall.SIGKILL)
181 if err != nil {
182 return err
183 }
184 // NOTE(gio): No need to check err as we just killed the process
185 s.cmd.Wait()
186 s.cmd = nil
187 return nil
188}