blob: b906e7bdaa09907e9a4f8d58d240bda1b18d8b82 [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
25 signer ssh.Signer
26 appDir string
27 runCommands []Command
28 self string
gioa60f0de2024-07-08 10:49:48 +040029 managerAddr string
gio183e8342024-08-20 06:01:24 +040030 logs *Log
gio0eaf2712024-04-14 13:08:46 +040031}
32
gio266c04f2024-07-03 14:18:45 +040033func NewServer(port int, appId string, repoAddr string, signer ssh.Signer, appDir string, runCommands []Command, self string, manager string) *Server {
gio0eaf2712024-04-14 13:08:46 +040034 return &Server{
35 l: &sync.Mutex{},
36 port: port,
37 ready: false,
gio266c04f2024-07-03 14:18:45 +040038 appId: appId,
gio0eaf2712024-04-14 13:08:46 +040039 repoAddr: repoAddr,
40 signer: signer,
41 appDir: appDir,
42 runCommands: runCommands,
43 self: self,
gioa60f0de2024-07-08 10:49:48 +040044 managerAddr: manager,
gio183e8342024-08-20 06:01:24 +040045 logs: &Log{},
gio0eaf2712024-04-14 13:08:46 +040046 }
47}
48
49func (s *Server) Start() error {
50 http.HandleFunc("/update", s.handleUpdate)
51 http.HandleFunc("/ready", s.handleReady)
gio183e8342024-08-20 06:01:24 +040052 http.HandleFunc("/logs", s.handleLogs)
gio0eaf2712024-04-14 13:08:46 +040053 if err := s.run(); err != nil {
54 return err
55 }
56 go s.pingManager()
57 return http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil)
58}
59
gio183e8342024-08-20 06:01:24 +040060func (s *Server) handleLogs(w http.ResponseWriter, r *http.Request) {
61 fmt.Fprint(w, s.logs.Contents())
62}
63
gio0eaf2712024-04-14 13:08:46 +040064func (s *Server) handleReady(w http.ResponseWriter, r *http.Request) {
65 s.l.Lock()
66 defer s.l.Unlock()
67 if s.ready {
68 fmt.Fprintln(w, "ok")
69 } else {
70 http.Error(w, "not ready", http.StatusInternalServerError)
71 }
72}
73
74func (s *Server) handleUpdate(w http.ResponseWriter, r *http.Request) {
75 fmt.Println("update")
76 s.l.Lock()
77 s.ready = false
78 s.l.Unlock()
79 if s.cmd != nil {
gioff0ee0f2024-10-15 23:11:54 +040080 err := syscall.Kill(-s.cmd.Process.Pid, syscall.SIGKILL)
81 if err != nil {
82 http.Error(w, err.Error(), http.StatusInternalServerError)
83 return
84 }
gio51abcff2024-10-24 08:37:25 +040085 // NOTE(gio): No need to check err as we just killed the process
86 s.cmd.Wait()
gio0eaf2712024-04-14 13:08:46 +040087 s.cmd = nil
gio0eaf2712024-04-14 13:08:46 +040088 }
89 if err := os.RemoveAll(s.appDir); err != nil {
90 http.Error(w, err.Error(), http.StatusInternalServerError)
91 return
92 }
93 if err := s.run(); err != nil {
94 http.Error(w, err.Error(), http.StatusInternalServerError)
95 return
96 }
97 s.l.Lock()
98 s.ready = true
99 s.l.Unlock()
100}
101
102func (s *Server) run() error {
103 if err := CloneRepository(s.repoAddr, s.signer, s.appDir); err != nil {
104 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{
111 Dir: *appDir,
112 Path: c.Bin,
113 Args: 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 {
125 if err := cmd.Start(); err != nil {
126 return err
127 }
128 s.cmd = cmd
129 }
130 }
131 return nil
132}
133
134type pingReq struct {
135 Address string `json:"address"`
gio183e8342024-08-20 06:01:24 +0400136 Logs string `json:"logs"`
gio0eaf2712024-04-14 13:08:46 +0400137}
138
139func (s *Server) pingManager() {
140 defer func() {
141 go func() {
142 time.Sleep(5 * time.Second)
143 s.pingManager()
144 }()
145 }()
gioa60f0de2024-07-08 10:49:48 +0400146 buf, err := json.Marshal(pingReq{
147 Address: fmt.Sprintf("%s:%d", s.self, s.port),
gio183e8342024-08-20 06:01:24 +0400148 Logs: s.logs.Contents(),
gioa60f0de2024-07-08 10:49:48 +0400149 })
gio0eaf2712024-04-14 13:08:46 +0400150 if err != nil {
151 return
152 }
gioa60f0de2024-07-08 10:49:48 +0400153 registerWorkerAddr := fmt.Sprintf("%s/api/apps/%s/workers", s.managerAddr, s.appId)
154 http.Post(registerWorkerAddr, "application/json", bytes.NewReader(buf))
gio0eaf2712024-04-14 13:08:46 +0400155}