blob: 8c3d9bf95bd3bd8353e53db2ddcc160d6b5dcb7b [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"
gio0eaf2712024-04-14 13:08:46 +040012 "sync"
gioff0ee0f2024-10-15 23:11:54 +040013 "syscall"
gio0eaf2712024-04-14 13:08:46 +040014 "time"
15
16 "golang.org/x/crypto/ssh"
17)
18
19type Server struct {
20 l sync.Locker
21 port int
gio266c04f2024-07-03 14:18:45 +040022 appId string
gio0eaf2712024-04-14 13:08:46 +040023 ready bool
24 cmd *exec.Cmd
25 repoAddr string
gio2b1157a2024-10-24 08:45:07 +040026 branch string
giofc441e32024-11-11 16:26:14 +040027 rootDir string
gio0eaf2712024-04-14 13:08:46 +040028 signer ssh.Signer
29 appDir string
30 runCommands []Command
31 self string
gioa60f0de2024-07-08 10:49:48 +040032 managerAddr string
gio183e8342024-08-20 06:01:24 +040033 logs *Log
gio45c31822024-10-24 10:58:02 +040034 currDir string
gio0eaf2712024-04-14 13:08:46 +040035}
36
giofc441e32024-11-11 16:26:14 +040037func NewServer(port int, appId string, repoAddr, branch, rootDir string, signer ssh.Signer, appDir string, runCommands []Command, self string, manager string) *Server {
gio0eaf2712024-04-14 13:08:46 +040038 return &Server{
39 l: &sync.Mutex{},
40 port: port,
41 ready: false,
gio266c04f2024-07-03 14:18:45 +040042 appId: appId,
gio0eaf2712024-04-14 13:08:46 +040043 repoAddr: repoAddr,
gio2b1157a2024-10-24 08:45:07 +040044 branch: branch,
giofc441e32024-11-11 16:26:14 +040045 rootDir: rootDir,
gio0eaf2712024-04-14 13:08:46 +040046 signer: signer,
47 appDir: appDir,
48 runCommands: runCommands,
49 self: self,
gioa60f0de2024-07-08 10:49:48 +040050 managerAddr: manager,
gio183e8342024-08-20 06:01:24 +040051 logs: &Log{},
gio45c31822024-10-24 10:58:02 +040052 currDir: "",
gio0eaf2712024-04-14 13:08:46 +040053 }
54}
55
56func (s *Server) Start() error {
57 http.HandleFunc("/update", s.handleUpdate)
58 http.HandleFunc("/ready", s.handleReady)
gio183e8342024-08-20 06:01:24 +040059 http.HandleFunc("/logs", s.handleLogs)
gio0eaf2712024-04-14 13:08:46 +040060 if err := s.run(); err != nil {
61 return err
62 }
63 go s.pingManager()
64 return http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil)
65}
66
gio183e8342024-08-20 06:01:24 +040067func (s *Server) handleLogs(w http.ResponseWriter, r *http.Request) {
68 fmt.Fprint(w, s.logs.Contents())
69}
70
gio0eaf2712024-04-14 13:08:46 +040071func (s *Server) handleReady(w http.ResponseWriter, r *http.Request) {
72 s.l.Lock()
73 defer s.l.Unlock()
74 if s.ready {
75 fmt.Fprintln(w, "ok")
76 } else {
77 http.Error(w, "not ready", http.StatusInternalServerError)
78 }
79}
80
81func (s *Server) handleUpdate(w http.ResponseWriter, r *http.Request) {
82 fmt.Println("update")
83 s.l.Lock()
84 s.ready = false
85 s.l.Unlock()
gio0eaf2712024-04-14 13:08:46 +040086 if err := s.run(); err != nil {
87 http.Error(w, err.Error(), http.StatusInternalServerError)
88 return
89 }
90 s.l.Lock()
91 s.ready = true
92 s.l.Unlock()
93}
94
95func (s *Server) run() error {
gio45c31822024-10-24 10:58:02 +040096 newDir, err := os.MkdirTemp(s.appDir, "code-*")
97 if err != nil {
98 return err
99 }
giofc441e32024-11-11 16:26:14 +0400100 if err := CloneRepositoryBranch(s.repoAddr, s.branch, s.rootDir, s.signer, newDir); err != nil {
gio0eaf2712024-04-14 13:08:46 +0400101 return err
102 }
gio183e8342024-08-20 06:01:24 +0400103 logM := io.MultiWriter(os.Stdout, s.logs)
gio0eaf2712024-04-14 13:08:46 +0400104 for i, c := range s.runCommands {
105 args := []string{c.Bin}
106 args = append(args, c.Args...)
107 cmd := &exec.Cmd{
giofc441e32024-11-11 16:26:14 +0400108 Dir: filepath.Join(newDir, s.rootDir),
gio0eaf2712024-04-14 13:08:46 +0400109 Path: c.Bin,
110 Args: args,
gio7fbd4ad2024-08-27 10:06:39 +0400111 Env: append(os.Environ(), c.Env...),
gio183e8342024-08-20 06:01:24 +0400112 Stdout: logM,
113 Stderr: logM,
gio0eaf2712024-04-14 13:08:46 +0400114 }
gioff0ee0f2024-10-15 23:11:54 +0400115 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
gio5e4d1a72024-10-09 15:25:29 +0400116 fmt.Printf("Running: %s %s\n", c.Bin, c.Args)
gio0eaf2712024-04-14 13:08:46 +0400117 if i < len(s.runCommands)-1 {
118 if err := cmd.Run(); err != nil {
119 return err
120 }
121 } else {
gio45c31822024-10-24 10:58:02 +0400122 if s.cmd != nil {
123 // TODO(gio): make this point configurable
124 if err := s.kill(); err != nil {
125 return err
126 }
127 if err := os.RemoveAll(s.currDir); err != nil {
128 return err
129 }
130 }
gio0eaf2712024-04-14 13:08:46 +0400131 if err := cmd.Start(); err != nil {
132 return err
133 }
134 s.cmd = cmd
135 }
136 }
gio45c31822024-10-24 10:58:02 +0400137 s.currDir = newDir
gio0eaf2712024-04-14 13:08:46 +0400138 return nil
139}
140
141type pingReq struct {
142 Address string `json:"address"`
gio183e8342024-08-20 06:01:24 +0400143 Logs string `json:"logs"`
gio0eaf2712024-04-14 13:08:46 +0400144}
145
146func (s *Server) pingManager() {
gioc555e0a2025-04-17 11:03:45 +0400147 // TODO(gio): do we need runnert -> manager communication?
148 return
gio0eaf2712024-04-14 13:08:46 +0400149 defer func() {
150 go func() {
151 time.Sleep(5 * time.Second)
152 s.pingManager()
153 }()
154 }()
gioa60f0de2024-07-08 10:49:48 +0400155 buf, err := json.Marshal(pingReq{
156 Address: fmt.Sprintf("%s:%d", s.self, s.port),
gio183e8342024-08-20 06:01:24 +0400157 Logs: s.logs.Contents(),
gioa60f0de2024-07-08 10:49:48 +0400158 })
gio0eaf2712024-04-14 13:08:46 +0400159 if err != nil {
160 return
161 }
gioa60f0de2024-07-08 10:49:48 +0400162 registerWorkerAddr := fmt.Sprintf("%s/api/apps/%s/workers", s.managerAddr, s.appId)
163 http.Post(registerWorkerAddr, "application/json", bytes.NewReader(buf))
gio0eaf2712024-04-14 13:08:46 +0400164}
gio45c31822024-10-24 10:58:02 +0400165
166func (s *Server) kill() error {
167 if s.cmd == nil {
168 return nil
169 }
170
171 err := syscall.Kill(-s.cmd.Process.Pid, syscall.SIGKILL)
172 if err != nil {
173 return err
174 }
175 // NOTE(gio): No need to check err as we just killed the process
176 s.cmd.Wait()
177 s.cmd = nil
178 return nil
179}