From 98dcff54bbba98efecdfd3b5dcd1e9b88fc22b3c Mon Sep 17 00:00:00 2001 From: shengxiang Date: Thu, 3 Sep 2015 16:34:16 +0800 Subject: [PATCH] try to do add handler --- gosuv.go | 66 +++++++++++++++++++++++++++++++--- server.go | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 4 deletions(-) diff --git a/gosuv.go b/gosuv.go index 23b323a..8fd56fd 100644 --- a/gosuv.go +++ b/gosuv.go @@ -10,6 +10,8 @@ import ( "time" "github.com/codegangsta/cli" + "github.com/franela/goreq" + "github.com/qiniu/log" ) func MkdirIfNoExists(dir string) error { @@ -43,8 +45,14 @@ func wrapAction(f func(*cli.Context)) func(*cli.Context) { //host := c.GlobalString("host") //port := c.GlobalInt("port") //ServeAddr(host, port) - go exec.Command(os.Args[0], "serv").Run() - time.Sleep(time.Millisecond * 500) + _, err := goreq.Request{ + Method: "GET", + Uri: buildURI(c, "/api/version"), + }.Do() + if err != nil { + go exec.Command(os.Args[0], "serv").Run() + time.Sleep(time.Millisecond * 500) + } f(c) } } @@ -61,7 +69,52 @@ func StatusAction(ctx *cli.Context) { func AddAction(ctx *cli.Context) { name := ctx.String("name") + dir, _ := os.Getwd() + if len(ctx.Args()) < 1 { + log.Fatal("need at least one args") + } + if name == "" { + name = ctx.Args()[0] + } + log.Println(ctx.Args().Tail()) + log.Println([]string(ctx.Args())) + log.Println(ctx.Args().Tail()) + log.Println(ctx.StringSlice("env")) + log.Println("Dir:", dir) + cmdName := ctx.Args().First() + log.Println("cmd name:", cmdName) + cmdPath, err := exec.LookPath(cmdName) + if err != nil { + log.Fatal(err) + } fmt.Printf("program: %s has been added\n", strconv.Quote(name)) + p := &ProgramInfo{ + Name: name, + Dir: dir, + Command: append([]string{cmdPath}, ctx.Args().Tail()...), + Environ: ctx.StringSlice("env"), + } + res, err := goreq.Request{ + Method: "POST", + Uri: buildURI(ctx, "/api/programs"), + Body: p, + }.Do() + if err != nil { + log.Fatal(err) + } + var jres JSONResponse + if res.StatusCode != http.StatusOK { + log.Fatal(res.Body.ToString()) + } + if err = res.Body.FromJsonTo(&jres); err != nil { + log.Fatal(err) + } + fmt.Println(jres.Message) +} + +func buildURI(ctx *cli.Context, uri string) string { + return fmt.Sprintf("http://%s:%d%s", + ctx.GlobalString("host"), ctx.GlobalInt("port"), uri) } func StopAction(ctx *cli.Context) { @@ -71,7 +124,8 @@ func ShutdownAction(ctx *cli.Context) { res, err := chttp("POST", fmt.Sprintf("http://%s:%d/api/shutdown", ctx.GlobalString("host"), ctx.GlobalInt("port"))) if err != nil { - panic(err) + log.Println("Already shutdown") + return } fmt.Println(res.Message) } @@ -128,8 +182,12 @@ func init() { Name: "name, n", Usage: "program name", }, + cli.StringSliceFlag{ + Name: "env, e", + Usage: "Specify environ", + }, }, - Action: AddAction, + Action: wrapAction(AddAction), }, { Name: "stop", diff --git a/server.go b/server.go index 3369ff0..fbd6fdb 100644 --- a/server.go +++ b/server.go @@ -5,9 +5,13 @@ import ( "fmt" "net/http" "os" + "os/exec" + "path/filepath" "time" + "github.com/codeskyblue/kproc" "github.com/gorilla/mux" + "github.com/qiniu/log" ) type JSONResponse struct { @@ -15,6 +19,17 @@ type JSONResponse struct { Message string `json:"message"` } +type ProgramInfo struct { + Name string `json:"name"` + Command []string `json:"command"` + Dir string `json:"dir"` + Environ []string `json:"environ"` +} + +var programTable struct { + table map[string]*Program +} + func renderJSON(w http.ResponseWriter, v interface{}) { w.Header().Add("Content-Type", "json") json.NewEncoder(w).Encode(v) @@ -27,6 +42,95 @@ func versionHandler(w http.ResponseWriter, r *http.Request) { }) } +func addHandler(w http.ResponseWriter, r *http.Request) { + pinfo := new(ProgramInfo) + err := json.NewDecoder(r.Body).Decode(pinfo) + if err != nil { + http.Error(w, err.Error(), 502) + return + } + log.Printf("add: %#v", pinfo) + + // init cmd + cmd := exec.Command(pinfo.Command[0], pinfo.Command[1:]...) + cmd.Dir = pinfo.Dir + cmd.Env = append(os.Environ(), pinfo.Environ...) + program := NewProgram(cmd, pinfo) + + // set output + logFd, err := program.createLog() + if err != nil { + http.Error(w, err.Error(), 503) + return + } + cmd.Stdout = logFd + cmd.Stderr = logFd + + if err = program.Start(); err != nil { + http.Error(w, err.Error(), 503) + return + } + program.Status = ST_RUNNING + + // wait func finish + go func() { + finish := false + ch := GoFunc(program.Wait) + for !finish { + select { + case err := <-ch: + if err != nil { + log.Warnf("program finish: %v", err) + } + finish = true + case sig := <-program.Sig: + program.Terminate(sig) + } + } + }() + renderJSON(w, &JSONResponse{ + Code: 200, + Message: "program add success", + }) +} + +func GoFunc(f func() error) chan error { + ch := make(chan error) + go func() { + ch <- f() + }() + return ch +} + +const ( + ST_PENDING = "pending" + ST_RUNNING = "running" + ST_STOPPED = "stopped" +) + +type Program struct { + *kproc.Process + Status string `json:"state"` + Sig chan os.Signal + info *ProgramInfo +} + +func NewProgram(cmd *exec.Cmd, info *ProgramInfo) *Program { + return &Program{ + Process: kproc.ProcCommand(cmd), + Status: ST_PENDING, + Sig: make(chan os.Signal), + info: info, + } +} + +func (p *Program) createLog() (*os.File, error) { + logDir := os.ExpandEnv("$HOME/.gosuv/logs") + os.MkdirAll(logDir, 0755) // just do it, err ignore it + logFile := filepath.Join(logDir, p.info.Name+".output.log") + return os.Create(logFile) +} + func shutdownHandler(w http.ResponseWriter, r *http.Request) { go func() { time.Sleep(50 * time.Millisecond) @@ -42,5 +146,6 @@ func ServeAddr(host string, port int) error { r := mux.NewRouter() r.HandleFunc("/api/version", versionHandler) r.Methods("POST").Path("/api/shutdown").HandlerFunc(shutdownHandler) + r.Methods("POST").Path("/api/programs").HandlerFunc(addHandler) return http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), r) }