Compare commits

..

1 Commits

Author SHA1 Message Date
b01f32bc24 kd live는 go 1.18 2023-06-21 18:27:35 +09:00
13 changed files with 190 additions and 407 deletions

View File

@ -15,7 +15,6 @@ import (
"reflect" "reflect"
"sort" "sort"
"strconv" "strconv"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"syscall" "syscall"
@ -117,13 +116,10 @@ type houstonClient struct {
timestamp string timestamp string
wg sync.WaitGroup wg sync.WaitGroup
config clientConfig config clientConfig
version string
standalone bool
} }
func unmarshal[T any](val *T, src map[string]string) { func unmarshal[T any](val *T, src map[string]string) {
argval := reflect.ValueOf(val) argval := reflect.ValueOf(val)
logger.Println("operation receive :", argval.Type().Name(), src)
for i := 0; i < argval.Elem().Type().NumField(); i++ { for i := 0; i < argval.Elem().Type().NumField(); i++ {
if !argval.Elem().Type().Field(i).IsExported() { if !argval.Elem().Type().Field(i).IsExported() {
continue continue
@ -132,9 +128,6 @@ func unmarshal[T any](val *T, src map[string]string) {
if argval.Elem().Field(i).CanInt() { if argval.Elem().Field(i).CanInt() {
num, _ := strconv.ParseInt(arg, 10, 0) num, _ := strconv.ParseInt(arg, 10, 0)
argval.Elem().Field(i).SetInt(num) argval.Elem().Field(i).SetInt(num)
} else if argval.Elem().Field(i).Kind() == reflect.Array || argval.Elem().Field(i).Kind() == reflect.Slice {
conv := strings.Split(arg, "\n")
argval.Elem().Field(i).Set(reflect.ValueOf(conv))
} else { } else {
argval.Elem().Field(i).SetString(arg) argval.Elem().Field(i).SetString(arg)
} }
@ -164,33 +157,8 @@ func gatherDeployedPrograms(storageRoot, name string) []*protos.VersionAndArgs {
} }
func (hc *houstonClient) makeOperationQueryRequest() *protos.OperationQueryRequest { func (hc *houstonClient) makeOperationQueryRequest() *protos.OperationQueryRequest {
var procs []*protos.ProcessDescription hn, _ := os.Hostname()
var deploys []*protos.DeployedVersions procs := make([]*protos.ProcessDescription, 0, len(hc.childProcs))
var selfname string
var selfargs []string
if hc.standalone {
selfname = path.Base(os.Args[0])
selfargs = os.Args[1:]
} else {
selfname = "houston"
selfargs = []string{}
}
procs = append(procs, &protos.ProcessDescription{
Name: selfname,
Args: selfargs,
Version: hc.version,
State: protos.ProcessState_Running,
Pid: int32(os.Getpid()),
})
deploys = append(deploys, &protos.DeployedVersions{
Name: selfname,
Versions: []*protos.VersionAndArgs{
{Version: hc.version, Args: selfargs},
},
})
for _, child := range hc.childProcs { for _, child := range hc.childProcs {
procs = append(procs, &protos.ProcessDescription{ procs = append(procs, &protos.ProcessDescription{
Name: child.name, Name: child.name,
@ -201,6 +169,7 @@ func (hc *houstonClient) makeOperationQueryRequest() *protos.OperationQueryReque
}) })
} }
var deploys []*protos.DeployedVersions
for name, prog := range hc.deploys { for name, prog := range hc.deploys {
deploys = append(deploys, &protos.DeployedVersions{ deploys = append(deploys, &protos.DeployedVersions{
Name: name, Name: name,
@ -208,7 +177,6 @@ func (hc *houstonClient) makeOperationQueryRequest() *protos.OperationQueryReque
}) })
} }
hn, _ := os.Hostname()
return &protos.OperationQueryRequest{ return &protos.OperationQueryRequest{
Hostname: hn, Hostname: hn,
Procs: procs, Procs: procs,
@ -216,7 +184,7 @@ func (hc *houstonClient) makeOperationQueryRequest() *protos.OperationQueryReque
} }
} }
func NewClient(standalone bool) (HoustonClient, error) { func NewClient() (HoustonClient, error) {
clientConfig, err := loadClientConfig() clientConfig, err := loadClientConfig()
if err != nil { if err != nil {
return nil, err return nil, err
@ -265,19 +233,12 @@ func NewClient(standalone bool) (HoustonClient, error) {
} }
} }
ver, _ := os.ReadFile("@version")
if len(ver) == 0 {
ver = []byte("0.0.0")
}
hc := &houstonClient{ hc := &houstonClient{
config: clientConfig, config: clientConfig,
clientChan: make(chan *grpc.ClientConn), clientChan: make(chan *grpc.ClientConn),
extraMetrics: unsafe.Pointer(&map[string]float32{}), extraMetrics: unsafe.Pointer(&map[string]float32{}),
deploys: deploys, deploys: deploys,
timestamp: exefi.ModTime().String(), timestamp: exefi.ModTime().String(),
version: string(ver),
standalone: standalone,
} }
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -312,22 +273,15 @@ func NewClient(standalone bool) (HoustonClient, error) {
var newprocs []*procmeta var newprocs []*procmeta
for _, proc := range hc.childProcs { for _, proc := range hc.childProcs {
if proc.cmd == exited { if proc.cmd == exited {
if proc.state == protos.ProcessState_Running || proc.state == protos.ProcessState_Restart { if proc.state == protos.ProcessState_Running {
go func(proc *procmeta) { go func(cmd *exec.Cmd) {
if err := proc.cmd.Process.Signal(syscall.SIGTERM); err != nil { if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
proc.cmd.Process.Signal(os.Kill) cmd.Process.Signal(os.Kill)
} }
proc.cmd.Wait() cmd.Wait()
proc.cmd.Process.Release() cmd.Process.Release()
logger.Println("abnormal termination of process :", cmd.Args)
if proc.state == protos.ProcessState_Restart { }(proc.cmd)
hc.startChildProcess(&shared.StartProcessRequest{
Version: proc.version,
Name: proc.name,
Args: proc.cmd.Args,
}, op)
}
}(proc)
} }
} else { } else {
newprocs = append(newprocs, proc) newprocs = append(newprocs, proc)
@ -453,8 +407,6 @@ func (hc *houstonClient) Start() {
var client *grpc.ClientConn var client *grpc.ClientConn
reconnCount := 0 reconnCount := 0
time.Sleep(time.Second)
for { for {
select { select {
case <-hc.ctx.Done(): case <-hc.ctx.Done():

View File

@ -21,22 +21,16 @@ import (
"golang.org/x/text/transform" "golang.org/x/text/transform"
) )
func download(dir string, urlpath string, accessToken string) (target string, err error) { func download(dir string, urlpath string, accessToken string) (string, error) {
logger.Println("start downloading", dir, urlpath)
defer func() {
if err != nil {
logger.Println("downloading failed :", err)
} else {
logger.Println("downloading succeeded")
}
}()
parsed, err := url.Parse(urlpath) parsed, err := url.Parse(urlpath)
if err != nil { if err != nil {
return "", err return "", err
} }
req, _ := http.NewRequest("GET", urlpath, nil) req, _ := http.NewRequest("GET", urlpath, nil)
if len(accessToken) > 0 {
req.Header.Add("Authorization", accessToken)
}
req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.51") req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.51")
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
@ -264,12 +258,6 @@ func (hc *houstonClient) prepareUpdateSelf(req *shared.DeployRequest) (srcdir st
return "", "", err return "", "", err
} }
// houston version 파일
err = os.WriteFile(path.Join(path.Dir(fname), "@version"), []byte(req.Version), 0644)
if err != nil {
return "", "", err
}
selfname, _ := os.Executable() selfname, _ := os.Executable()
srcreplacer := path.Join(path.Dir(fname), "replacer") + path.Ext(selfname) srcreplacer := path.Join(path.Dir(fname), "replacer") + path.Ext(selfname)
replacer = "./" + filepath.ToSlash("replacer"+path.Ext(selfname)) replacer = "./" + filepath.ToSlash("replacer"+path.Ext(selfname))
@ -289,6 +277,7 @@ func (hc *houstonClient) deploy(req *shared.DeployRequest) error {
return err return err
} }
logger.Println("start downloading", req.Url)
// verpath에 배포 시작 // verpath에 배포 시작
fname, err := download(root, hc.makeDownloadUrl(req.Url), req.AccessToken) fname, err := download(root, hc.makeDownloadUrl(req.Url), req.AccessToken)
if err != nil { if err != nil {

View File

@ -12,6 +12,7 @@ import (
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"regexp"
"runtime/debug" "runtime/debug"
"strings" "strings"
"syscall" "syscall"
@ -102,12 +103,11 @@ func zipLogFiles(storageRoot string, req *shared.UploadRequest, start, except st
defer w.Close() defer w.Close()
oldestFile := "" oldestFile := ""
for i, file := range matches { for _, file := range matches {
if file == root { if file == root {
continue continue
} }
if len(except) > 0 && file >= except { if len(except) > 0 && file >= except {
matches = matches[:i]
break break
} }
if len(start) > 0 && file < start { if len(start) > 0 && file < start {
@ -165,17 +165,33 @@ func zipLogFiles(storageRoot string, req *shared.UploadRequest, start, except st
} }
func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) *procmeta { func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) *procmeta {
if len(req.Args) == 0 { re := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`)
argsTemp := re.FindAllString(req.Args, -1)
var args []string
for _, arg := range argsTemp {
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 {
return nil return nil
} }
verpath := path.Join(storageRoot, req.Name, req.Version) verpath := path.Join(storageRoot, req.Name, req.Version)
fi, err := os.Stat(verpath) fi, err := os.Stat(verpath)
if err == nil && fi.IsDir() { if err == nil && fi.IsDir() {
req.Args[0] = "./" + path.Clean(strings.TrimPrefix(req.Args[0], "/")) args[0] = "./" + path.Clean(strings.TrimPrefix(args[0], "/"))
os.Chmod(path.Join(verpath, req.Args[0]), 0777) os.Chmod(path.Join(verpath, args[0]), 0777)
cmd := exec.Command(req.Args[0], req.Args[1:]...) cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = verpath cmd.Dir = verpath
stdin, _ := cmd.StdinPipe() stdin, _ := cmd.StdinPipe()
@ -436,24 +452,54 @@ func (hc *houstonClient) stopChildProcess(req *shared.StopProcessRequest, op pro
} }
func (hc *houstonClient) restartChildProcess(req *shared.RestartProcessRequest, op protos.OperationClient) error { func (hc *houstonClient) restartChildProcess(req *shared.RestartProcessRequest, op protos.OperationClient) error {
if req.Version == "latest" {
// 최신 버전을 찾음
latest, err := shared.FindLastestVersion(hc.config.StorageRoot, req.Name)
if err != nil {
return err
}
req.Version = latest
}
var restarts []*procmeta
for _, proc := range hc.childProcs { for _, proc := range hc.childProcs {
if proc.cmd.Process.Pid == int(req.Pid) { if proc.name == req.Name {
if len(req.Config) > 0 { if len(req.Version) == 0 {
// config.json를 먼저 다운로드 시도 restarts = append(restarts, proc)
root := proc.cmd.Dir } else if req.Version == proc.version {
if _, err := download(root, hc.makeDownloadUrl(req.Config), ""); err != nil { restarts = append(restarts, proc)
return err
}
} }
proc.state = protos.ProcessState_Restart
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
hc.exitChan <- proc.cmd
break
} }
} }
if len(restarts) == 0 {
return errNoRunningProcess
}
for _, proc := range restarts {
if err := proc.cmd.Process.Signal(syscall.SIGTERM); err != nil {
proc.cmd.Process.Signal(os.Kill)
}
proc.state = protos.ProcessState_Stopping
}
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
for _, proc := range restarts {
proc.cmd.Wait()
proc.cmd.Process.Release()
proc.state = protos.ProcessState_Stopped
}
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
for _, proc := range restarts {
if err := hc.launch(proc); err != nil {
return err
}
}
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
return nil return nil
} }
@ -481,7 +527,11 @@ func (hc *houstonClient) uploadFiles(req *shared.UploadRequest) error {
// 전체 파일을 대상으로 // 전체 파일을 대상으로
zipFile, srcFiles, err := zipLogFiles(hc.config.StorageRoot, req, "", "") zipFile, srcFiles, err := zipLogFiles(hc.config.StorageRoot, req, "", "")
if err == nil && len(zipFile) > 0 && len(srcFiles) > 0 { if err == nil && len(zipFile) > 0 && len(srcFiles) > 0 {
if err = hc.uploadZipLogFile(zipFile, req.Name, req.Version); err != nil { if err = hc.uploadZipLogFile(zipFile, req.Name, req.Version); err == nil {
for _, oldf := range srcFiles {
os.Remove(oldf)
}
} else {
logger.Println("uploadZipLogFile failed :", err) logger.Println("uploadZipLogFile failed :", err)
} }
} else if err != nil { } else if err != nil {

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/sh
nohup /home/opdev/houston -client -logfile > /dev/null & nohup /home/opdev/houston -client -logfile &

View File

@ -18,7 +18,7 @@ func main() {
} }
if *runAsClient { if *runAsClient {
hc, err := client.NewClient(true) hc, err := client.NewClient()
if err != nil { if err != nil {
logger.Fatal(err) logger.Fatal(err)
return return

View File

@ -27,8 +27,7 @@ enum ProcessState {
Stopped = 0; Stopped = 0;
Stopping = 1; Stopping = 1;
Running = 2; Running = 2;
Restart = 3; Error = 3;
Error = 4;
} }
message ProcessDescription { message ProcessDescription {

View File

@ -8,24 +8,14 @@ import (
"os" "os"
"os/exec" "os/exec"
"path" "path"
"time" "strconv"
) )
func copy(src, dst string, stdlog *log.Logger) error { func copy(src, dst string) error {
fi, err := os.Stat(src) fi, err := os.Stat(src)
if err != nil { if err != nil {
return err return err
} }
if fi.IsDir() {
entries, _ := os.ReadDir(src)
for _, ent := range entries {
if err := copy(path.Join(src, ent.Name()), path.Join(dst, ent.Name()), stdlog); err != nil {
return err
}
}
return nil
}
inmode := fi.Mode() inmode := fi.Mode()
in, err := os.Open(src) in, err := os.Open(src)
@ -55,7 +45,6 @@ func copy(src, dst string, stdlog *log.Logger) error {
return err return err
} }
stdlog.Println("file copied :", src, dst)
return nil return nil
} }
@ -70,34 +59,45 @@ func main() {
// args[3:] : 다시 시작할 때 넘겨줄 arguments(프로세스 이름 포함) // args[3:] : 다시 시작할 때 넘겨줄 arguments(프로세스 이름 포함)
stdlog.Println(args) stdlog.Println(args)
for { pid, err := strconv.Atoi(args[1])
stdlog.Println("wait for terminating of", args[3]) if err != nil {
cmd := exec.Command("ps", "-p", args[1])
if err := cmd.Run(); err != nil {
break
}
time.Sleep(time.Second)
}
stdlog.Println("target is terminated")
// replacer 제거. 내가 돌고 있으므로 복사는 안된다.
// 내가 실행되기 전에 이미 복사가 되서 나는 최신 버전임
os.Remove(path.Join(args[2], os.Args[0]))
if err := copy(args[2], "", stdlog); err != nil {
stdlog.Fatal(err) stdlog.Fatal(err)
} }
proc, err := os.FindProcess(pid)
if err != nil {
stdlog.Fatal(err)
}
proc.Wait()
selfext, _ := os.Executable()
selfext = path.Base(selfext)
nextArgs := args[4:] nextArgs := args[4:]
if bt, _ := os.ReadFile("@args"); len(bt) > 0 { entries, _ := os.ReadDir(args[2])
var tempArgs []string for _, ent := range entries {
if json.Unmarshal(bt, &tempArgs) == nil { if ent.Name() == selfext {
nextArgs = tempArgs continue
}
if ent.IsDir() {
if err := os.MkdirAll(ent.Name(), 0775); err != nil {
stdlog.Fatal(err)
}
} else {
if ent.Name() == "@args" {
var tempArgs []string
argfile, _ := os.Open(path.Join(args[2], ent.Name()))
dec := json.NewDecoder(argfile)
if dec.Decode(&tempArgs) == nil {
nextArgs = tempArgs
}
} else if err := copy(path.Join(args[2], ent.Name()), ent.Name()); err != nil {
stdlog.Println("copy failed :", path.Join(args[2], ent.Name()), ent.Name())
stdlog.Fatal(err)
}
} }
} }
os.Remove("@args")
err := os.RemoveAll(args[2]) err = os.RemoveAll(args[2])
if err != nil { if err != nil {
stdlog.Println("os.RemoveAll failed :", args[2], err) stdlog.Println("os.RemoveAll failed :", args[2], err)
} }

View File

@ -3,15 +3,12 @@ package server
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io" "io"
"io/fs" "io/fs"
"net/http" "net/http"
"os" "os"
"path" "path"
"regexp"
"strconv" "strconv"
"strings"
"time" "time"
"repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/logger"
@ -23,25 +20,9 @@ import (
현재 접속 중인 Agent 목록을 보여줍니다. 현재 접속 중인 Agent 목록을 보여줍니다.
- http method : GET - 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) { func (h *houstonHandler) GetAgents(w http.ResponseWriter, r *http.Request) {
enc := json.NewEncoder(w) enc := json.NewEncoder(w)
allHosts := h.Operation().Hosts() enc.Encode(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) { func (h *houstonHandler) GetDeploySources(w http.ResponseWriter, r *http.Request) {
@ -53,31 +34,13 @@ func (h *houstonHandler) GetDeploySources(w http.ResponseWriter, r *http.Request
} }
getVersions := func(name string) []string { getVersions := func(name string) []string {
vers, _ := os.ReadDir(path.Join(h.deployPath, name)) var out []string
mytags := readTagsFromFile(h.deployPath, name, "@tags") files, _ := os.ReadDir(path.Join(h.deployPath, name))
out := []string{ for _, fd := range files {
mytags,
}
for _, fd := range vers {
if fd.IsDir() { if fd.IsDir() {
ver := fd.Name() out = append(out, 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 return out
} }
@ -121,13 +84,9 @@ func (h *houstonHandler) UploadDeploySource(w http.ResponseWriter, r *http.Reque
var filename string var filename string
if version == "config" { if version == "config" {
filename = path.Join(h.deployPath, name, version, "config"+ext) filename = path.Join(h.deployPath, name, version, "config.json")
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 { } else {
// deploys 폴더는 파일시스템 서비스이므로 다운로드 가능
filename = path.Join(h.deployPath, name, version, name+ext) filename = path.Join(h.deployPath, name, version, name+ext)
} }
@ -168,47 +127,6 @@ func (h *houstonHandler) DeleteDeploySource(w http.ResponseWriter, r *http.Reque
} }
} }
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) { func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
// <form action="/houston" method="post" enctype="multipart/form-data"> // <form action="/houston" method="post" enctype="multipart/form-data">
// <input type="text" name="name"> // <input type="text" name="name">
@ -249,10 +167,6 @@ func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
continue continue
} }
if strings.HasPrefix(fd.Name(), "@") {
continue
}
fi, _ := fd.Info() fi, _ := fd.Info()
if fi.ModTime().After(latestTime) { if fi.ModTime().After(latestTime) {
latestFilename = fi.Name() latestFilename = fi.Name()
@ -264,18 +178,16 @@ func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
return return
} }
configPath, err := h.findLastestConfigFile(name) configPath := ""
if err != nil { if _, err := os.Stat(path.Join(h.deployPath, name, "config", "config.json")); err == nil || errors.Is(err, fs.ErrExist) {
logger.Println(err) configPath = path.Join("deploys", name, "config", "config.json")
w.WriteHeader(http.StatusBadRequest)
return
} }
h.Operation().Deploy(MakeDeployRequest( h.Operation().Deploy(MakeDeployRequest(
shared.DeployRequest{ shared.DeployRequest{
Name: name, Name: name,
Version: version, Version: version,
Url: path.Join(sub_folder_name_deploys, name, version, latestFilename), Url: path.Join("deploys", name, version, latestFilename),
Config: configPath, Config: configPath,
}, },
targets, targets,
@ -325,7 +237,7 @@ func (h *houstonHandler) StartProcess(w http.ResponseWriter, r *http.Request) {
// </form> // </form>
name := r.FormValue("name") name := r.FormValue("name")
version := r.FormValue("version") version := r.FormValue("version")
argsline := r.FormValue("args") args := r.FormValue("args")
traws := r.FormValue("targets") traws := r.FormValue("targets")
var targets []string var targets []string
@ -342,27 +254,6 @@ func (h *houstonHandler) StartProcess(w http.ResponseWriter, r *http.Request) {
return return
} }
re := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`)
argsTemp := re.FindAllString(argsline, -1)
var args []string
for _, arg := range argsTemp {
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{ h.Operation().StartProcess(MakeStartProcessRequest(shared.StartProcessRequest{
Name: name, Name: name,
Version: version, Version: version,
@ -406,51 +297,6 @@ func (h *houstonHandler) StopProcess(w http.ResponseWriter, r *http.Request) {
}, targets)) }, 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) { func (h *houstonHandler) UploadLogs(w http.ResponseWriter, r *http.Request) {
// <form action="/houston" method="post" enctype="multipart/form-data"> // <form action="/houston" method="post" enctype="multipart/form-data">
// <input type="text" name="name"> // <input type="text" name="name">
@ -511,7 +357,7 @@ func (h *houstonHandler) GetLogFileLinks(w http.ResponseWriter, r *http.Request)
var out []string var out []string
for _, lf := range logfiles { for _, lf := range logfiles {
out = append(out, path.Join(sub_folder_name_downloads, name, version, lf.Name())) out = append(out, path.Join("downloads", name, version, lf.Name()))
} }
enc := json.NewEncoder(w) enc := json.NewEncoder(w)

View File

@ -1,7 +1,6 @@
package server package server
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -11,10 +10,13 @@ import (
"runtime/debug" "runtime/debug"
"strings" "strings"
"repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/logger"
) )
const (
defaultMaxMemory = 32 << 10 // 32 KB
)
type HoustonServerWithHandler interface { type HoustonServerWithHandler interface {
HoustonServer HoustonServer
RegisterHandlers(serveMux *http.ServeMux, prefix string) error RegisterHandlers(serveMux *http.ServeMux, prefix string) error
@ -43,10 +45,9 @@ func NewHoustonHandler() HoustonServerWithHandler {
} }
func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string) error { func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string) error {
config := loadServerConfig() storagePath := loadServerConfig().StorageRoot
storagePath := config.StorageRoot h.deployPath = path.Join(storagePath, "deploys")
h.deployPath = path.Join(storagePath, sub_folder_name_deploys) h.downloadPath = path.Join(storagePath, "downloads")
h.downloadPath = path.Join(storagePath, sub_folder_name_downloads)
if err := os.MkdirAll(h.deployPath, 0775); err != nil { if err := os.MkdirAll(h.deployPath, 0775); err != nil {
return err return err
@ -64,10 +65,10 @@ func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string
serveMux.Handle(prefix, h) serveMux.Handle(prefix, h)
fsx := http.FileServer(http.Dir(h.deployPath)) fsx := http.FileServer(http.Dir(h.deployPath))
serveMux.Handle(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_deploys), http.StripPrefix(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_deploys), fsx)) serveMux.Handle(fmt.Sprintf("%s/deploys/", prefix), http.StripPrefix(fmt.Sprintf("%s/deploys/", prefix), fsx))
ufsx := http.FileServer(http.Dir(h.downloadPath)) ufsx := http.FileServer(http.Dir(h.downloadPath))
serveMux.Handle(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_downloads), http.StripPrefix(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_downloads), ufsx)) serveMux.Handle(fmt.Sprintf("%s/downloads/", prefix), http.StripPrefix(fmt.Sprintf("%s/downloads/", prefix), ufsx))
serveMux.HandleFunc(fmt.Sprintf("%s/upload", prefix), func(w http.ResponseWriter, r *http.Request) { serveMux.HandleFunc(fmt.Sprintf("%s/upload", prefix), func(w http.ResponseWriter, r *http.Request) {
defer func() { defer func() {
@ -102,8 +103,6 @@ func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string
return nil return nil
} }
var noauth = flagx.Bool("noauth", false, "")
func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer func() { defer func() {
s := recover() s := recover()
@ -118,38 +117,6 @@ func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.Body.Close() r.Body.Close()
}() }()
var userinfo map[string]any
if !*noauth {
authheader := r.Header.Get("Authorization")
if len(authheader) == 0 {
logger.Println("Authorization header is not valid :", authheader)
w.WriteHeader(http.StatusBadRequest)
return
}
req, _ := http.NewRequest("GET", "https://graph.microsoft.com/oidc/userinfo", nil)
req.Header.Add("Authorization", authheader)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
logger.Println("graph microsoft api call failed :", err)
w.WriteHeader(http.StatusBadRequest)
return
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
if err = json.Unmarshal(raw, &userinfo); err != nil {
return
}
if _, expired := userinfo["error"]; expired {
w.WriteHeader(http.StatusUnauthorized)
return
}
}
var operation string var operation string
if r.Method == "POST" { if r.Method == "POST" {
operation = r.FormValue("operation") operation = r.FormValue("operation")
@ -170,6 +137,10 @@ func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
if r.PostForm == nil {
r.ParseMultipartForm(defaultMaxMemory)
}
args := []reflect.Value{ args := []reflect.Value{
reflect.ValueOf(h), reflect.ValueOf(h),
reflect.ValueOf(w), reflect.ValueOf(w),

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"reflect" "reflect"
"strings"
"sync" "sync"
"repositories.action2quare.com/ayo/houston/shared" "repositories.action2quare.com/ayo/houston/shared"
@ -151,12 +150,6 @@ func marshal(argval reflect.Value, output map[string]string) map[string]string {
marshal(argval.Field(i), output) marshal(argval.Field(i), output)
} else if argval.Field(i).CanInt() { } else if argval.Field(i).CanInt() {
output[argval.Type().Field(i).Name] = fmt.Sprintf("%d", argval.Field(i).Int()) output[argval.Type().Field(i).Name] = fmt.Sprintf("%d", argval.Field(i).Int())
} else if argval.Field(i).Kind() == reflect.Array || argval.Field(i).Kind() == reflect.Slice {
var conv []string
for j := 0; j < argval.Field(i).Len(); j++ {
conv = append(conv, argval.Field(i).Index(j).String())
}
output[argval.Type().Field(i).Name] = strings.Join(conv, "\n")
} else { } else {
output[argval.Type().Field(i).Name] = argval.Field(i).String() output[argval.Type().Field(i).Name] = argval.Field(i).String()
} }
@ -165,6 +158,8 @@ func marshal(argval reflect.Value, output map[string]string) map[string]string {
} }
func (os *operationServer) Query(svr protos.Operation_QueryServer) error { func (os *operationServer) Query(svr protos.Operation_QueryServer) error {
// 서버는 업데이트가 있는지 확인하고 있으면 stream에 응답을 보낸다.
// 업데이트가 없으면 대기
desc, err := svr.Recv() desc, err := svr.Recv()
if err != nil { if err != nil {
return err return err
@ -333,18 +328,26 @@ func (os *operationServer) RestartProcess(d RestartProcessRequest) {
return false return false
}) })
if len(d.hostnames) != 1 { if len(d.hostnames) > 0 {
return // hostname만 재시작
var final []*hostWithChan
conv := make(map[string]bool)
for _, hn := range d.hostnames {
conv[hn] = true
}
for _, t := range targets {
if _, ok := conv[t.Hostname]; ok {
final = append(final, t)
}
}
targets = final
} }
// hostname만 재시작
for _, t := range targets { for _, t := range targets {
if t.Hostname == d.hostnames[0] { t.opChan <- &opdef{
t.opChan <- &opdef{ operation: shared.Restart,
operation: shared.Restart, args: d,
args: d,
}
return
} }
} }
} }

View File

@ -5,10 +5,8 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"sync/atomic"
"repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/houston/client"
"repositories.action2quare.com/ayo/houston/shared" "repositories.action2quare.com/ayo/houston/shared"
"repositories.action2quare.com/ayo/houston/shared/protos" "repositories.action2quare.com/ayo/houston/shared/protos"
@ -25,7 +23,6 @@ type HoustonServer interface {
type serverConfig struct { type serverConfig struct {
GrpcPort int `json:"grpc_port"` GrpcPort int `json:"grpc_port"`
StorageRoot string `json:"storage_path"` StorageRoot string `json:"storage_path"`
RunAsClient bool `json:"run_as_client"`
} }
type DeployRequest struct { type DeployRequest struct {
@ -176,34 +173,15 @@ func (hs *houstonServer) Start() error {
return err return err
} }
closeCount := int32(0) if err := hs.rpcServer.Serve(lis); err != nil {
var hc client.HoustonClient return err
if loadServerConfig().RunAsClient {
hc, err = client.NewClient(false)
if err != nil {
return err
}
go func() {
hc.Start()
if atomic.AddInt32(&closeCount, 1) == 1 {
hs.Stop()
}
}()
} }
err = hs.rpcServer.Serve(lis) return nil
if atomic.AddInt32(&closeCount, 1) == 1 {
if hc != nil {
hc.Shutdown()
}
}
return err
} }
func (hs *houstonServer) Stop() { func (hs *houstonServer) Stop() {
hs.rpcServer.Stop() hs.rpcServer.GracefulStop()
} }
func (hs *houstonServer) Operation() Operation { func (hs *houstonServer) Operation() Operation {

View File

@ -35,7 +35,7 @@ type WithdrawRequest struct {
type StartProcessRequest struct { type StartProcessRequest struct {
Name string Name string
Version string Version string
Args []string Args string
} }
type StopProcessRequest struct { type StopProcessRequest struct {
@ -45,9 +45,8 @@ type StopProcessRequest struct {
} }
type RestartProcessRequest struct { type RestartProcessRequest struct {
Name string Name string
Pid int32 Version string
Config string
} }
type UploadRequest struct { type UploadRequest struct {

View File

@ -26,8 +26,7 @@ const (
ProcessState_Stopped ProcessState = 0 ProcessState_Stopped ProcessState = 0
ProcessState_Stopping ProcessState = 1 ProcessState_Stopping ProcessState = 1
ProcessState_Running ProcessState = 2 ProcessState_Running ProcessState = 2
ProcessState_Restart ProcessState = 3 ProcessState_Error ProcessState = 3
ProcessState_Error ProcessState = 4
) )
// Enum value maps for ProcessState. // Enum value maps for ProcessState.
@ -36,15 +35,13 @@ var (
0: "Stopped", 0: "Stopped",
1: "Stopping", 1: "Stopping",
2: "Running", 2: "Running",
3: "Restart", 3: "Error",
4: "Error",
} }
ProcessState_value = map[string]int32{ ProcessState_value = map[string]int32{
"Stopped": 0, "Stopped": 0,
"Stopping": 1, "Stopping": 1,
"Running": 2, "Running": 2,
"Restart": 3, "Error": 3,
"Error": 4,
} }
) )
@ -425,21 +422,20 @@ var file_protos_operation_proto_rawDesc = []byte{
0x37, 0x0a, 0x09, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x37, 0x0a, 0x09, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x4e, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x63, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x41, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x63,
0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x70, 0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x70,
0x70, 0x65, 0x64, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x69, 0x6e, 0x70, 0x65, 0x64, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x69, 0x6e,
0x67, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x02, 0x67, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x02,
0x12, 0x0b, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x03, 0x32, 0x78, 0x0a, 0x09, 0x4f,
0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x04, 0x32, 0x78, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x16, 0x79, 0x12, 0x16, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65,
0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x4f, 0x70, 0x65, 0x72,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x6f, 0x6e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x66, 0x72,
0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x65, 0x73, 0x68, 0x12, 0x16, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51,
0x12, 0x16, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65, 0x72, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x06, 0x2e, 0x45, 0x6d,
0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x70, 0x74, 0x79, 0x22, 0x00, 0x42, 0x0f, 0x5a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f,
0x22, 0x00, 0x42, 0x0f, 0x5a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (