blob: ffc570928f59f8c6c5c11eb9b4f12b4b9682fe39 [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"
11 "sync"
gioff0ee0f2024-10-15 23:11:54 +040012 "syscall"
gio0eaf2712024-04-14 13:08:46 +040013 "time"
14
15 "golang.org/x/crypto/ssh"
16)
17
18type Server struct {
19 l sync.Locker
20 port int
gio266c04f2024-07-03 14:18:45 +040021 appId string
gio0eaf2712024-04-14 13:08:46 +040022 ready bool
23 cmd *exec.Cmd
24 repoAddr string
gio2b1157a2024-10-24 08:45:07 +040025 branch string
gio0eaf2712024-04-14 13:08:46 +040026 signer ssh.Signer
27 appDir string
28 runCommands []Command
29 self string
gioa60f0de2024-07-08 10:49:48 +040030 managerAddr string
gio183e8342024-08-20 06:01:24 +040031 logs *Log
gio0eaf2712024-04-14 13:08:46 +040032}
33
gio2b1157a2024-10-24 08:45:07 +040034func NewServer(port int, appId string, repoAddr, branch string, signer ssh.Signer, appDir string, runCommands []Command, self string, manager string) *Server {
gio0eaf2712024-04-14 13:08:46 +040035 return &Server{
36 l: &sync.Mutex{},
37 port: port,
38 ready: false,
gio266c04f2024-07-03 14:18:45 +040039 appId: appId,
gio0eaf2712024-04-14 13:08:46 +040040 repoAddr: repoAddr,
gio2b1157a2024-10-24 08:45:07 +040041 branch: branch,
gio0eaf2712024-04-14 13:08:46 +040042 signer: signer,
43 appDir: appDir,
44 runCommands: runCommands,
45 self: self,
gioa60f0de2024-07-08 10:49:48 +040046 managerAddr: manager,
gio183e8342024-08-20 06:01:24 +040047 logs: &Log{},
gio0eaf2712024-04-14 13:08:46 +040048 }
49}
50
51func (s *Server) Start() error {
52 http.HandleFunc("/update", s.handleUpdate)
53 http.HandleFunc("/ready", s.handleReady)
gio183e8342024-08-20 06:01:24 +040054 http.HandleFunc("/logs", s.handleLogs)
gio0eaf2712024-04-14 13:08:46 +040055 if err := s.run(); err != nil {
56 return err
57 }
58 go s.pingManager()
59 return http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil)
60}
61
gio183e8342024-08-20 06:01:24 +040062func (s *Server) handleLogs(w http.ResponseWriter, r *http.Request) {
63 fmt.Fprint(w, s.logs.Contents())
64}
65
gio0eaf2712024-04-14 13:08:46 +040066func (s *Server) handleReady(w http.ResponseWriter, r *http.Request) {
67 s.l.Lock()
68 defer s.l.Unlock()
69 if s.ready {
70 fmt.Fprintln(w, "ok")
71 } else {
72 http.Error(w, "not ready", http.StatusInternalServerError)
73 }
74}
75
76func (s *Server) handleUpdate(w http.ResponseWriter, r *http.Request) {
77 fmt.Println("update")
78 s.l.Lock()
79 s.ready = false
80 s.l.Unlock()
81 if s.cmd != nil {
gioff0ee0f2024-10-15 23:11:54 +040082 err := syscall.Kill(-s.cmd.Process.Pid, syscall.SIGKILL)
83 if err != nil {
84 http.Error(w, err.Error(), http.StatusInternalServerError)
85 return
86 }
gio51abcff2024-10-24 08:37:25 +040087 // NOTE(gio): No need to check err as we just killed the process
88 s.cmd.Wait()
gio0eaf2712024-04-14 13:08:46 +040089 s.cmd = nil
gio0eaf2712024-04-14 13:08:46 +040090 }
91 if err := os.RemoveAll(s.appDir); err != nil {
92 http.Error(w, err.Error(), http.StatusInternalServerError)
93 return
94 }
95 if err := s.run(); err != nil {
96 http.Error(w, err.Error(), http.StatusInternalServerError)
97 return
98 }
99 s.l.Lock()
100 s.ready = true
101 s.l.Unlock()
102}
103
104func (s *Server) run() error {
gio2b1157a2024-10-24 08:45:07 +0400105 if err := CloneRepositoryBranch(s.repoAddr, s.branch, s.signer, s.appDir); err != nil {
gio0eaf2712024-04-14 13:08:46 +0400106 return err
107 }
gio183e8342024-08-20 06:01:24 +0400108 logM := io.MultiWriter(os.Stdout, s.logs)
gio0eaf2712024-04-14 13:08:46 +0400109 for i, c := range s.runCommands {
110 args := []string{c.Bin}
111 args = append(args, c.Args...)
112 cmd := &exec.Cmd{
113 Dir: *appDir,
114 Path: c.Bin,
115 Args: args,
gio7fbd4ad2024-08-27 10:06:39 +0400116 Env: append(os.Environ(), c.Env...),
gio183e8342024-08-20 06:01:24 +0400117 Stdout: logM,
118 Stderr: logM,
gio0eaf2712024-04-14 13:08:46 +0400119 }
gioff0ee0f2024-10-15 23:11:54 +0400120 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
gio5e4d1a72024-10-09 15:25:29 +0400121 fmt.Printf("Running: %s %s\n", c.Bin, c.Args)
gio0eaf2712024-04-14 13:08:46 +0400122 if i < len(s.runCommands)-1 {
123 if err := cmd.Run(); err != nil {
124 return err
125 }
126 } else {
127 if err := cmd.Start(); err != nil {
128 return err
129 }
130 s.cmd = cmd
131 }
132 }
133 return nil
134}
135
136type pingReq struct {
137 Address string `json:"address"`
gio183e8342024-08-20 06:01:24 +0400138 Logs string `json:"logs"`
gio0eaf2712024-04-14 13:08:46 +0400139}
140
141func (s *Server) pingManager() {
142 defer func() {
143 go func() {
144 time.Sleep(5 * time.Second)
145 s.pingManager()
146 }()
147 }()
gioa60f0de2024-07-08 10:49:48 +0400148 buf, err := json.Marshal(pingReq{
149 Address: fmt.Sprintf("%s:%d", s.self, s.port),
gio183e8342024-08-20 06:01:24 +0400150 Logs: s.logs.Contents(),
gioa60f0de2024-07-08 10:49:48 +0400151 })
gio0eaf2712024-04-14 13:08:46 +0400152 if err != nil {
153 return
154 }
gioa60f0de2024-07-08 10:49:48 +0400155 registerWorkerAddr := fmt.Sprintf("%s/api/apps/%s/workers", s.managerAddr, s.appId)
156 http.Post(registerWorkerAddr, "application/json", bytes.NewReader(buf))
gio0eaf2712024-04-14 13:08:46 +0400157}