diff --git a/server/http_api.go b/server/http_api.go new file mode 100644 index 0000000..b34c3fd --- /dev/null +++ b/server/http_api.go @@ -0,0 +1,190 @@ +package server + +import ( + "encoding/json" + "go-ayo/logger" + "houston/shared" + "io" + "net/http" + "os" + "path" + "time" +) + +func (h *houstonHandler) GetAgents(w http.ResponseWriter, r *http.Request) { + enc := json.NewEncoder(w) + enc.Encode(h.Operation().Hosts()) +} + +func (h *houstonHandler) GetDeploySources(w http.ResponseWriter, r *http.Request) { + files, err := os.ReadDir("deploys") + if err != nil { + logger.Error(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + getVersions := func(name string) []string { + var out []string + files, _ := os.ReadDir(path.Join("deploys", name)) + for _, fd := range files { + if fd.IsDir() { + out = append(out, fd.Name()) + } + } + return out + } + + out := make(map[string][]string) + for _, fd := range files { + if fd.IsDir() { + out[fd.Name()] = getVersions(fd.Name()) + } + } + + enc := json.NewEncoder(w) + enc.Encode(out) +} + +func (h *houstonHandler) Upload(w http.ResponseWriter, r *http.Request) { + //
+ // + // + // + // + //
+ file, header, err := r.FormFile("file") + if err != nil { + logger.Error(err) + w.WriteHeader(http.StatusBadRequest) + return + } + defer file.Close() + + contents, err := io.ReadAll(file) + if err != nil { + logger.Error(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + version := r.FormValue("version") + name := r.FormValue("name") + ext := path.Ext(header.Filename) + + // deploys 폴더는 파일시스템 서비스이므로 다운로드 가능 + filename := path.Join("deploys", name, version, name+ext) + if err = os.MkdirAll(path.Dir(filename), os.ModePerm); err != nil { + logger.Error(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + // 파일 저장 + err = os.WriteFile(filename, contents, 0644) + if err != nil { + logger.Error(err) + w.WriteHeader(http.StatusInternalServerError) + return + } +} + +func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) { + //
+ // + // + // + // + //
+ name := r.FormValue("name") + version := r.FormValue("version") + traws := r.FormValue("targets") + + var targets []string + if err := json.Unmarshal([]byte(traws), &targets); err != nil { + logger.Error(err) + w.WriteHeader(http.StatusBadRequest) + return + } + + if len(name) == 0 || len(version) == 0 || len(targets) == 0 { + w.WriteHeader(http.StatusBadRequest) + return + } + + relPath := path.Join("deploys", name, version) + files, err := os.ReadDir(relPath) + if err != nil { + logger.Error(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + latestTime := time.Time{} + var latestFilename string + for _, fd := range files { + if fd.IsDir() { + continue + } + + fi, _ := fd.Info() + if fi.ModTime().After(latestTime) { + latestFilename = fi.Name() + latestTime = fi.ModTime() + } + } + if len(latestFilename) == 0 { + w.WriteHeader(http.StatusBadRequest) + return + } + + h.Operation().Deploy(MakeDeployRequest( + shared.DeployRequest{ + Name: name, + Version: version, + Url: path.Join(relPath, latestFilename), + }, + targets, + )) +} + +func (h *houstonHandler) StartProcess(w http.ResponseWriter, r *http.Request) { + //
+ // + // + // + // + // + //
+ name := r.FormValue("name") + version := r.FormValue("version") + args := r.FormValue("args") + traws := r.FormValue("targets") + + var targets []string + if err := json.Unmarshal([]byte(traws), &targets); err != nil { + logger.Error(err) + w.WriteHeader(http.StatusBadRequest) + return + } + + if len(name) == 0 || len(version) == 0 || len(targets) == 0 { + w.WriteHeader(http.StatusBadRequest) + return + } + + h.Operation().StartProcess(MakeStartProcessRequest(shared.StartProcessRequest{ + Name: name, + Version: version, + Args: args, // 실행 파일 포함 e.g. "biglocal.exe -port=8090 -dev", + }, targets)) + + // svr.Operation().Start(server.MakeStartRequest( + // common.StartRequest{ + // Name: "warehouse", + // Version: "latest", + // Args: "biglocal.exe -port=8090 -dev", + // }, + // []string{"mountain"}, + // )) +} diff --git a/server/http_handler.go b/server/http_handler.go new file mode 100644 index 0000000..fdd219f --- /dev/null +++ b/server/http_handler.go @@ -0,0 +1,87 @@ +package server + +import ( + "go-ayo/common" + "go-ayo/logger" + "io" + "net/http" + "reflect" + "runtime/debug" + "strings" +) + +const ( + defaultMaxMemory = 32 << 10 // 32 KB +) + +type HoustonServerWithHandler interface { + HoustonServer + RegisterHandlers(serveMux *http.ServeMux, prefix string) error +} + +type houstonHandler struct { + HoustonServer + methods map[string]reflect.Method +} + +func NewHoustonHandler() HoustonServerWithHandler { + var tmp *houstonHandler + + methods := make(map[string]reflect.Method) + tp := reflect.TypeOf(tmp) + for i := 0; i < tp.NumMethod(); i++ { + method := tp.Method(i) + methods[strings.ToLower(method.Name)] = method + } + return &houstonHandler{ + HoustonServer: NewServer(), + methods: methods, + } +} + +func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string) error { + serveMux.Handle(common.MakeHttpHandlerPattern(prefix, "houston"), h) + + return nil +} + +func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + defer func() { + s := recover() + if s != nil { + logger.Println(s) + debug.PrintStack() + } + }() + + defer func() { + io.Copy(io.Discard, r.Body) + r.Body.Close() + }() + + operation := r.FormValue("operation") + if len(operation) == 0 { + w.WriteHeader(http.StatusBadRequest) + return + } + + method, ok := h.methods[strings.ToLower(operation)] + if !ok { + // 없는 operation + logger.Println("fail to call api. operation is not valid :", operation) + w.WriteHeader(http.StatusBadRequest) + return + } + + if r.PostForm == nil { + r.ParseMultipartForm(defaultMaxMemory) + } + + args := []reflect.Value{ + reflect.ValueOf(h), + reflect.ValueOf(w), + reflect.ValueOf(r), + } + + method.Func.Call(args) +}