package server import ( "encoding/json" "errors" "fmt" "io" "io/fs" "net/http" "os" "path" "regexp" "strconv" "strings" "time" "repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/houston/shared" ) /* * 현재 접속 중인 Agent 목록을 보여줍니다. - http method : GET */ const ( sub_folder_name_deploys = string("_deploys") sub_folder_name_downloads = string("_downloads") ) func (h *houstonHandler) GetAgents(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(w) allHosts := h.Operation().Hosts() enc.Encode(allHosts) } func readTagsFromFile(paths ...string) string { raw, _ := os.ReadFile(path.Join(paths...)) if len(raw) > 0 { tag := string(raw) return strings.Trim(tag, "\n") } return "" } func (h *houstonHandler) GetDeploySources(w http.ResponseWriter, r *http.Request) { files, err := os.ReadDir(h.deployPath) if err != nil { logger.Println(err) w.WriteHeader(http.StatusInternalServerError) return } getVersions := func(name string) []string { vers, _ := os.ReadDir(path.Join(h.deployPath, name)) mytags := readTagsFromFile(h.deployPath, name, "@tags") out := []string{ mytags, } for _, fd := range vers { if fd.IsDir() { ver := fd.Name() files, _ := os.ReadDir(path.Join(h.deployPath, name, ver)) vertags := readTagsFromFile(h.deployPath, name, ver, "@tags") if len(files) > 0 { for _, file := range files { if strings.HasPrefix(file.Name(), "@") { continue } downloadpath := path.Join(sub_folder_name_deploys, name, ver, file.Name()) ver = fmt.Sprintf("%s:%s", ver+mytags+vertags, downloadpath) break } } out = append(out, ver) } } 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) UploadDeploySource(w http.ResponseWriter, r *http.Request) { //
file, header, err := r.FormFile("file") if err != nil { logger.Println(err) w.WriteHeader(http.StatusBadRequest) return } defer file.Close() contents, err := io.ReadAll(file) if err != nil { logger.Println(err) w.WriteHeader(http.StatusInternalServerError) return } version := r.FormValue("version") name := r.FormValue("name") ext := path.Ext(header.Filename) var filename string if version == "config" { filename = path.Join(h.deployPath, name, version, "config"+ext) tags := readTagsFromFile(h.deployPath, name, version, "@tags") if !strings.Contains(tags, "#hidden") { tags = tags + "#hidden" os.WriteFile(path.Join(h.deployPath, name, version, "@tags"), []byte(tags), 0644) } } else { filename = path.Join(h.deployPath, name, version, name+ext) } if err = os.MkdirAll(path.Dir(filename), 0775); err != nil { logger.Println(err) w.WriteHeader(http.StatusInternalServerError) return } // 파일 저장 err = os.WriteFile(filename, contents, 0644) if err != nil { logger.Println(err) w.WriteHeader(http.StatusInternalServerError) return } } func (h *houstonHandler) DeleteDeploySource(w http.ResponseWriter, r *http.Request) { // version := r.FormValue("version") name := r.FormValue("name") if len(version) == 0 || len(name) == 0 { w.WriteHeader(http.StatusBadRequest) return } // deploys 폴더는 파일시스템 서비스이므로 다운로드 가능 targetpath := path.Join(h.deployPath, name, version) if err := os.RemoveAll(targetpath); err != nil { logger.Println("deleteDeploySource failed :", err) w.WriteHeader(http.StatusInternalServerError) return } } func (h *houstonHandler) findLastestConfigFile(name string) (string, error) { logger.Println("findLastestConfigFile :", name) configFiles, err := os.ReadDir(path.Join(h.deployPath, name, "config")) if err != nil { if errors.Is(err, fs.ErrNotExist) { return "", nil } logger.Println("findLastestConfigFile failed :", err) return "", err } var cf fs.FileInfo for _, file := range configFiles { if file.IsDir() { continue } if strings.HasPrefix(file.Name(), "config.") { test, err := file.Info() if err != nil { return "", err } if cf == nil { cf = test } else if test.ModTime().After(cf.ModTime()) { cf = test } } } if cf != nil { logger.Println("findLastestConfigFile cf found :", cf.Name()) return path.Join(sub_folder_name_deploys, name, "config", cf.Name()), nil } logger.Println("findLastestConfigFile cf NOT found") return "", nil } 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 len(traws) > 0 { if err := json.Unmarshal([]byte(traws), &targets); err != nil { logger.Println(err) w.WriteHeader(http.StatusBadRequest) return } } if len(name) == 0 || len(version) == 0 || len(targets) == 0 { w.WriteHeader(http.StatusBadRequest) return } relPath := path.Join(h.deployPath, name, version) files, err := os.ReadDir(relPath) if err != nil { logger.Println(err) w.WriteHeader(http.StatusBadRequest) return } latestTime := time.Time{} var latestFilename string for _, fd := range files { if fd.IsDir() { continue } if strings.HasPrefix(fd.Name(), "@") { continue } fi, _ := fd.Info() if fi.ModTime().After(latestTime) { latestFilename = fi.Name() latestTime = fi.ModTime() } } if len(latestFilename) == 0 { w.WriteHeader(http.StatusBadRequest) return } configPath, err := h.findLastestConfigFile(name) if err != nil { logger.Println(err) w.WriteHeader(http.StatusBadRequest) return } h.Operation().Deploy(MakeDeployRequest( shared.DeployRequest{ Name: name, Version: version, Url: path.Join(sub_folder_name_deploys, name, version, latestFilename), Config: configPath, }, targets, )) } func (h *houstonHandler) Undeploy(w http.ResponseWriter, r *http.Request) { // name := r.FormValue("name") version := r.FormValue("version") traws := r.FormValue("targets") var targets []string if len(traws) > 0 { if err := json.Unmarshal([]byte(traws), &targets); err != nil { logger.Println(err) w.WriteHeader(http.StatusBadRequest) return } } if len(name) == 0 || len(version) == 0 || len(targets) == 0 { w.WriteHeader(http.StatusBadRequest) return } h.Operation().Withdraw(MakeWithdrawRequest( shared.WithdrawRequest{ Name: name, Version: version, }, targets, )) } func (h *houstonHandler) StartProcess(w http.ResponseWriter, r *http.Request) { // name := r.FormValue("name") version := r.FormValue("version") argsline := r.FormValue("args") traws := r.FormValue("targets") var targets []string if len(traws) > 0 { if err := json.Unmarshal([]byte(traws), &targets); err != nil { logger.Println(err) w.WriteHeader(http.StatusBadRequest) return } } if len(name) == 0 || len(version) == 0 || len(targets) == 0 { w.WriteHeader(http.StatusBadRequest) return } re := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`) argsTemp := re.FindAllString(argsline, -1) var args []string for _, arg := range argsTemp { arg = strings.Trim(arg, "\n ") if strings.HasPrefix(arg, `"`) && len(args) > 0 { lastarg := args[len(args)-1] if strings.HasSuffix(lastarg, "=") { args[len(args)-1] = lastarg + arg } else { args = append(args, arg) } } else { args = append(args, arg) } } if len(args) == 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)) } func (h *houstonHandler) StopProcess(w http.ResponseWriter, r *http.Request) { // name := r.FormValue("name") if len(name) == 0 { w.WriteHeader(http.StatusBadRequest) return } version := r.FormValue("version") // option pidstr := r.FormValue("pid") // option traws := r.FormValue("targets") var targets []string if len(traws) > 0 { if err := json.Unmarshal([]byte(traws), &targets); err != nil { logger.Println(err) w.WriteHeader(http.StatusBadRequest) return } } pid, _ := strconv.Atoi(pidstr) h.Operation().StopProcess(MakeStopProcessRequest(shared.StopProcessRequest{ Name: name, Version: version, Pid: int32(pid), }, targets)) h.Operation().Upload(MakeUploadRequest(shared.UploadRequest{ Name: name, Version: version, Url: "upload", DeleteAfterUploaded: "true", }, targets)) } func (h *houstonHandler) RestartProcess(w http.ResponseWriter, r *http.Request) { // pidstr := r.FormValue("pid") target := r.FormValue("target") name := r.FormValue("name") if len(target) == 0 || len(pidstr) == 0 || len(name) == 0 { w.WriteHeader(http.StatusBadRequest) return } deployConfig := false configstr := r.FormValue("config") if len(configstr) > 0 { deployConfig, _ = strconv.ParseBool(configstr) } pid, err := strconv.ParseInt(pidstr, 10, 0) if err != nil { logger.Println(err) w.WriteHeader(http.StatusBadRequest) return } var configPath string if deployConfig { configPath, err = h.findLastestConfigFile(name) if err != nil { logger.Println(err) w.WriteHeader(http.StatusBadRequest) return } } h.Operation().RestartProcess(MakeRestartRequest(shared.RestartProcessRequest{ Name: name, Pid: int32(pid), Config: configPath, }, []string{target})) } func (h *houstonHandler) UploadLogs(w http.ResponseWriter, r *http.Request) { // name := r.FormValue("name") if len(name) == 0 { w.WriteHeader(http.StatusBadRequest) return } version := r.FormValue("version") // option traws := r.FormValue("targets") filter := r.FormValue("filter") if len(filter) == 0 { filter = "logs/*" } var targets []string if err := json.Unmarshal([]byte(traws), &targets); err != nil { logger.Println(err) w.WriteHeader(http.StatusBadRequest) return } h.Operation().Upload(MakeUploadRequest(shared.UploadRequest{ Name: name, Version: version, Url: "upload", Filter: filter, }, targets)) } func (h *houstonHandler) GetLogFileLinks(w http.ResponseWriter, r *http.Request) { // name := r.FormValue("name") version := r.FormValue("version") if len(name) == 0 || len(version) == 0 { w.WriteHeader(http.StatusBadRequest) return } root := path.Join(h.downloadPath, name, version) logfiles, err := os.ReadDir(root) if err != nil { w.WriteHeader(http.StatusBadRequest) return } var out []string for _, lf := range logfiles { out = append(out, path.Join(sub_folder_name_downloads, name, version, lf.Name())) } enc := json.NewEncoder(w) enc.Encode(out) } func (h *houstonHandler) GetDeployingProgress(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(h.Operation().DeplyingProgress()) }