528 lines
13 KiB
Go
528 lines
13 KiB
Go
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) {
|
|
// <form action="/houston" method="post" enctype="multipart/form-data">
|
|
// <input type="file" name="file">
|
|
// <input type="text" name="name">
|
|
// <input type="text" name="version">
|
|
// <input type="submit" value="업로드">
|
|
// </form>
|
|
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) {
|
|
// <form action="/houston" method="post" enctype="multipart/form-data">
|
|
// <input type="text" name="name">
|
|
// <input type="text" name="version">
|
|
// </form>
|
|
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) {
|
|
// <form action="/houston" method="post" enctype="multipart/form-data">
|
|
// <input type="text" name="name">
|
|
// <input type="text" name="version">
|
|
// <input type="text" name="targets">
|
|
// <input type="submit" value="업로드">
|
|
// </form>
|
|
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) {
|
|
// <form action="/houston" method="post" enctype="multipart/form-data">
|
|
// <input type="text" name="name">
|
|
// <input type="text" name="version">
|
|
// <input type="text" name="targets">
|
|
// </form>
|
|
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) {
|
|
// <form action="/houston" method="post" enctype="multipart/form-data">
|
|
// <input type="text" name="name">
|
|
// <input type="text" name="version">
|
|
// <input type="text" name="args">
|
|
// <input type="text" name="targets">
|
|
// <input type="submit" value="업로드">
|
|
// </form>
|
|
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) {
|
|
// <form action="/houston" method="post" enctype="multipart/form-data">
|
|
// <input type="text" name="name">
|
|
// <input type="text" name="version">
|
|
// <input type="text" name="args">
|
|
// <input type="text" name="targets">
|
|
// <input type="submit" value="업로드">
|
|
// </form>
|
|
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) {
|
|
// <form action="/houston" method="post" enctype="multipart/form-data">
|
|
// <input type="text" name="name">
|
|
// <input type="text" name="target">
|
|
// <input type="text" name="pid">
|
|
// <input type="text" name="config">
|
|
// </form>
|
|
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) {
|
|
// <form action="/houston" method="post" enctype="multipart/form-data">
|
|
// <input type="text" name="name">
|
|
// <input type="text" name="version">
|
|
// <input type="text" name="filter">
|
|
// <input type="text" name="targets">
|
|
// <input type="submit" value="업로드">
|
|
// </form>
|
|
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) {
|
|
// <form action="/houston" method="post" enctype="multipart/form-data">
|
|
// <input type="text" name="name">
|
|
// <input type="text" name="version">
|
|
// </form>
|
|
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())
|
|
}
|