Compare commits

40 Commits

Author SHA1 Message Date
598181e7f7 로그 제거 2024-11-15 14:23:33 +09:00
7015dc99d6 metric 복원 2024-11-14 19:46:47 +09:00
df56a542a1 로그 업로드 로직 개선 2024-11-14 10:48:30 +09:00
46dd289c28 hosuton 종료 안되는 문제 수정 2024-11-12 16:43:39 +09:00
43b5aee48b 셀프 업데이트 완료 2024-11-12 12:25:52 +09:00
592e00b98b 셀프 업데이트 작업 추가 by script 2024-11-12 11:53:18 +09:00
92432fcd83 replacer 제거 2024-11-11 20:59:12 +09:00
a844bed056 houston 업데이트를 스크립트로 하자 2024-11-11 20:50:51 +09:00
2ddbae07b2 로그 append와 업로드를 분리 2024-11-07 13:47:53 +09:00
4d6665b64a 로그 파일 업로드를 비동기로 2024-11-07 11:43:04 +09:00
99e1007012 로그 파일을 압축하지 않고 하나씩 업로드 2024-11-06 16:46:45 +09:00
9632dc7755 Merge branch 'master' of https://repositories.action2quare.com/ayo/houston 2024-09-27 17:34:30 +09:00
a60dee1645 houston server config 오버라이드 2024-09-27 17:34:29 +09:00
7fe5090efa latest 버전 옵션을 프로세스 재시작시에도 적용 2024-09-27 15:26:51 +09:00
e9370513c2 zip 파일 unzip후 정리 추가 2024-09-26 13:18:32 +09:00
387d4f3ea8 로그파일 전송 오류 수정 2024-09-26 12:01:53 +09:00
e5984b3342 에러 로그 추가 2024-09-23 22:23:59 +09:00
f174a165fe 지원 타입 및 로그 추가 2024-09-23 21:15:15 +09:00
a112f20cb8 로그 추가 2024-09-23 18:10:14 +09:00
97fc64be81 maingateApiToken추가 2024-09-23 17:46:39 +09:00
7ae391b599 로그 추가 2024-08-22 16:35:14 +09:00
f5e491325f logwirter수정 - 파일과 stdout 동시에 2024-08-22 15:53:53 +09:00
2fa02374fd 로그 폴더 생성 추가 2024-08-22 11:56:50 +09:00
380586fb73 로그파일 설정을 houston으로 이동 2024-08-22 11:28:58 +09:00
da37ed11cd 자동실행 추가 2024-08-16 15:05:05 +09:00
3ab055008c 인증 일단 막음 2024-08-13 17:30:33 +09:00
71e80d2908 모듈 업데이트 2024-08-05 10:23:42 +09:00
12a0f9d2b1 argument 수식 처리 2024-08-05 10:20:41 +09:00
016f459252 houston server에 재접속시 autorun이 다시 불리는 문제 수정 2024-07-30 11:19:25 +09:00
030fa658f5 모듈 업데이트 2024-07-29 17:50:33 +09:00
3c96921703 autorun 포맷 오류시 대응 2024-07-25 16:10:09 +09:00
6f444e0187 config file service 방식 변경 2024-07-24 13:13:46 +09:00
3eaad85453 Merge branch 'master' of https://repositories.action2quare.com/ayo/houston 2024-07-10 14:55:49 +09:00
401cfa8b84 noauth대신 authtype 파라미터 사용 2024-07-10 14:55:47 +09:00
5e4799ff55 모듈 업데이트 2024-07-08 15:12:54 +09:00
86e14fbd23 logger.UseLogFile을 houston의 자식 프로세스에도 적용 2024-07-08 15:10:32 +09:00
fb3886e2e4 하위 프로세스에 환경변수 전달 2024-06-12 14:32:32 +09:00
95d6741389 config 로딩을 gocommon사용하도록 변경 2024-06-06 13:18:50 +09:00
fdb534c5e0 프로세스 정지시 로그 자동 업로드 2024-06-05 17:26:28 +09:00
dfcb78b70c 로그파일에 호스트이름 추가 2024-06-05 17:26:17 +09:00
15 changed files with 685 additions and 673 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ houston
houston.zip houston.zip
config.json config.json
.vscode/ .vscode/
/data

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/fs" "io/fs"
"net/http"
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
@ -22,6 +23,7 @@ import (
"time" "time"
"unsafe" "unsafe"
"github.com/djherbis/times"
"repositories.action2quare.com/ayo/gocommon" "repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/flagx" "repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/logger"
@ -30,12 +32,30 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
) )
type runcommand struct { type runcommand struct {
Exec string `json:"exec"` Exec string `json:"exec"`
Args []string `json:"args"` Args []string `json:"args"`
Version string `json:"version"` Version string `json:"version"`
AutoRestart bool `json:"auto_restart"`
OutputLogFile string `json:"logfile"`
}
type easyruncommand runcommand
func (t *runcommand) UnmarshalJSON(b []byte) error {
easy := easyruncommand{
Version: "latest",
AutoRestart: true,
}
if err := json.Unmarshal(b, &easy); err != nil {
return err
}
*t = runcommand(easy)
return nil
} }
type clientConfig struct { type clientConfig struct {
@ -49,30 +69,21 @@ type clientConfig struct {
var autorun = flagx.String("autorun", "", "") var autorun = flagx.String("autorun", "", "")
func loadClientConfig() (clientConfig, error) { type outerconfig struct {
configFile, err := os.Open("config.json")
if err != nil {
return clientConfig{}, err
}
defer configFile.Close()
var config struct {
Houston *struct { Houston *struct {
Client clientConfig `json:"client"` Client clientConfig `json:"client"`
} `json:"houston"` } `json:"houston"`
} }
dec := json.NewDecoder(configFile) func loadClientConfig() (clientConfig, error) {
err = dec.Decode(&config) var oc outerconfig
err := gocommon.LoadConfig[outerconfig](&oc)
if err != nil { if err != nil {
logger.Println(err)
return clientConfig{}, err return clientConfig{}, err
} }
if config.Houston == nil { return oc.Houston.Client, nil
return clientConfig{}, errors.New(`"houston" object is missing in config.json`)
}
return config.Houston.Client, nil
} }
type HoustonClient interface { type HoustonClient interface {
@ -89,8 +100,11 @@ type procmeta struct {
args []string args []string
version string version string
verpath string verpath string
recover bool
state int32 state int32
stdin io.WriteCloser stdin io.WriteCloser
logfile string
keepLatest bool
} }
func (pm *procmeta) isState(s protos.ProcessState) bool { func (pm *procmeta) isState(s protos.ProcessState) bool {
@ -105,6 +119,12 @@ func (pm *procmeta) setState(s protos.ProcessState) {
atomic.StoreInt32(&pm.state, int32(s)) atomic.StoreInt32(&pm.state, int32(s))
} }
type uploadRequest struct {
logFile string
name string
version string
}
type houstonClient struct { type houstonClient struct {
childProcs []*procmeta childProcs []*procmeta
extraMetrics unsafe.Pointer // map[string]float32 extraMetrics unsafe.Pointer // map[string]float32
@ -114,6 +134,7 @@ type houstonClient struct {
operationChan chan *protos.OperationQueryResponse operationChan chan *protos.OperationQueryResponse
exitChan chan *exec.Cmd exitChan chan *exec.Cmd
clientChan chan *grpc.ClientConn clientChan chan *grpc.ClientConn
uploadChan chan uploadRequest
timestamp string timestamp string
wg sync.WaitGroup wg sync.WaitGroup
config clientConfig config clientConfig
@ -123,6 +144,13 @@ type houstonClient struct {
} }
func unmarshal[T any](val *T, src map[string]string) { func unmarshal[T any](val *T, src map[string]string) {
defer func() {
r := recover()
if r != nil {
logger.Error(r)
}
}()
argval := reflect.ValueOf(val) argval := reflect.ValueOf(val)
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() {
@ -135,6 +163,9 @@ func unmarshal[T any](val *T, src map[string]string) {
} else if argval.Elem().Field(i).Kind() == reflect.Array || argval.Elem().Field(i).Kind() == reflect.Slice { } else if argval.Elem().Field(i).Kind() == reflect.Array || argval.Elem().Field(i).Kind() == reflect.Slice {
conv := strings.Split(arg, "\n") conv := strings.Split(arg, "\n")
argval.Elem().Field(i).Set(reflect.ValueOf(conv)) argval.Elem().Field(i).Set(reflect.ValueOf(conv))
} else if argval.Elem().Field(i).Kind() == reflect.Bool {
bv, _ := strconv.ParseBool(arg)
argval.Elem().Field(i).SetBool(bv)
} else { } else {
argval.Elem().Field(i).SetString(arg) argval.Elem().Field(i).SetString(arg)
} }
@ -296,6 +327,7 @@ func NewClient(standalone bool) (HoustonClient, error) {
timestamp: exefi.ModTime().String(), timestamp: exefi.ModTime().String(),
version: string(ver), version: string(ver),
standalone: standalone, standalone: standalone,
uploadChan: make(chan uploadRequest, 100),
siblingProcIndex: make(map[string]uint64), siblingProcIndex: make(map[string]uint64),
} }
@ -304,6 +336,7 @@ func NewClient(standalone bool) (HoustonClient, error) {
exitChan := make(chan *exec.Cmd, 10) exitChan := make(chan *exec.Cmd, 10)
operationChan := make(chan *protos.OperationQueryResponse, 10) operationChan := make(chan *protos.OperationQueryResponse, 10)
hc.wg.Add(1) hc.wg.Add(1)
ignoreRecover := int32(0)
// autorun 처리 // autorun 처리
go func() { go func() {
@ -327,6 +360,7 @@ func NewClient(standalone bool) (HoustonClient, error) {
case newClient := <-hc.clientChan: case newClient := <-hc.clientChan:
op = protos.NewOperationClient(newClient) op = protos.NewOperationClient(newClient)
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
case exited := <-exitChan: case exited := <-exitChan:
var newprocs []*procmeta var newprocs []*procmeta
@ -341,11 +375,19 @@ func NewClient(standalone bool) (HoustonClient, error) {
proc.cmd.Process.Release() proc.cmd.Process.Release()
if proc.isState(protos.ProcessState_Restart) { if proc.isState(protos.ProcessState_Restart) {
hc.startChildProcess(&shared.StartProcessRequest{ if proc.keepLatest {
proc.version = "latest"
}
if err := hc.startChildProcess(&shared.StartProcessRequest{
Version: proc.version, Version: proc.version,
Name: proc.name, Name: proc.name,
Args: proc.args, Args: proc.args,
}, op) }); err != nil {
logger.ErrorWithCallStack(err)
} else {
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
}
} }
}(proc) }(proc)
} }
@ -357,28 +399,14 @@ func NewClient(standalone bool) (HoustonClient, error) {
op.Refresh(ctx, hc.makeOperationQueryRequest()) op.Refresh(ctx, hc.makeOperationQueryRequest())
case resp := <-operationChan: case resp := <-operationChan:
logger.Println("houton query operation :", resp.Operation)
switch shared.Operation(resp.Operation) { switch shared.Operation(resp.Operation) {
case shared.Deploy: case shared.Deploy:
var dr shared.DeployRequest var dr shared.DeployRequest
unmarshal(&dr, resp.Args) unmarshal(&dr, resp.Args)
if dr.Name == myname { logger.Println("args :", dr)
if srcdir, replacer, err := hc.prepareUpdateSelf(&dr); err == nil {
args := []string{
fmt.Sprintf("%d", os.Getpid()),
srcdir,
filepath.ToSlash(os.Args[0]),
}
args = append(args, os.Args[1:]...)
cmd := exec.Command(replacer, args...)
if err := cmd.Start(); err != nil {
logger.Println(err)
} else {
hc.shutdownFunc()
}
} else {
logger.Println(err)
}
} else {
hn, _ := os.Hostname() hn, _ := os.Hostname()
if err := hc.deploy(&dr, func(dp *protos.DeployingProgress) { if err := hc.deploy(&dr, func(dp *protos.DeployingProgress) {
@ -387,6 +415,12 @@ func NewClient(standalone bool) (HoustonClient, error) {
dp.Version = dr.Version dp.Version = dr.Version
op.ReportDeployingProgress(ctx, dp) op.ReportDeployingProgress(ctx, dp)
}); err == nil { }); err == nil {
if dr.Name == "houston" {
// houston_update_dir 다운로드가 완료되었으므로 종료
// 종료되고나면 스크립트가 알아서 재 실행
hc.Shutdown()
return
}
prog := gatherDeployedPrograms(hc.config.StorageRoot, dr.Name) prog := gatherDeployedPrograms(hc.config.StorageRoot, dr.Name)
hc.deploys[dr.Name] = prog hc.deploys[dr.Name] = prog
op.Refresh(ctx, hc.makeOperationQueryRequest()) op.Refresh(ctx, hc.makeOperationQueryRequest())
@ -411,11 +445,12 @@ func NewClient(standalone bool) (HoustonClient, error) {
Total: 0, Total: 0,
}) })
} }
}
case shared.Withdraw: case shared.Withdraw:
var wr shared.WithdrawRequest var wr shared.WithdrawRequest
unmarshal(&wr, resp.Args) unmarshal(&wr, resp.Args)
logger.Println("args :", wr)
err := hc.withdraw(&wr) err := hc.withdraw(&wr)
if err == nil { if err == nil {
prog := gatherDeployedPrograms(hc.config.StorageRoot, wr.Name) prog := gatherDeployedPrograms(hc.config.StorageRoot, wr.Name)
@ -432,13 +467,19 @@ func NewClient(standalone bool) (HoustonClient, error) {
case shared.Start: case shared.Start:
var sr shared.StartProcessRequest var sr shared.StartProcessRequest
unmarshal(&sr, resp.Args) unmarshal(&sr, resp.Args)
if err := hc.startChildProcess(&sr, op); err != nil { logger.Println("args :", sr)
logger.Println(err)
if err := hc.startChildProcess(&sr); err != nil {
logger.ErrorWithCallStack(err)
} else {
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
} }
case shared.Stop: case shared.Stop:
var sr shared.StopProcessRequest var sr shared.StopProcessRequest
unmarshal(&sr, resp.Args) unmarshal(&sr, resp.Args)
logger.Println("args :", sr)
if err := hc.stopChildProcess(&sr, op); err != nil { if err := hc.stopChildProcess(&sr, op); err != nil {
logger.Println(err) logger.Println(err)
} }
@ -446,14 +487,9 @@ func NewClient(standalone bool) (HoustonClient, error) {
case shared.Restart: case shared.Restart:
var rr shared.RestartProcessRequest var rr shared.RestartProcessRequest
unmarshal(&rr, resp.Args) unmarshal(&rr, resp.Args)
if err := hc.restartChildProcess(&rr, op); err != nil { logger.Println("args :", rr)
logger.Println(err)
}
case shared.Upload: if err := hc.restartChildProcess(&rr, op); err != nil {
var ur shared.UploadRequest
unmarshal(&ur, resp.Args)
if err := hc.uploadFiles(&ur); err != nil {
logger.Println(err) logger.Println(err)
} }
@ -462,23 +498,50 @@ func NewClient(standalone bool) (HoustonClient, error) {
id64, _ := strconv.ParseInt(idstr, 10, 0) id64, _ := strconv.ParseInt(idstr, 10, 0)
id := int32(id64) id := int32(id64)
var found *procmeta
hc.childProcs = gocommon.ShrinkSlice(hc.childProcs, func(e *procmeta) bool { hc.childProcs = gocommon.ShrinkSlice(hc.childProcs, func(e *procmeta) bool {
if e.id == id { if e.id == id {
e.cmd.Wait() found = e
e.cmd.Process.Release()
return true return true
} }
return false return false
}) })
if found != nil {
found.cmd.Wait()
found.cmd.Process.Release()
if found.recover && atomic.LoadInt32(&ignoreRecover) == 0 {
time.Sleep(time.Second)
sr := shared.StartProcessRequest{
Name: found.name,
Version: found.version,
Args: found.args,
AutoRestart: found.recover,
OutputLogFile: found.logfile,
}
if err := hc.startChildProcess(&sr); err != nil {
logger.Println("startChildProcess failed by autorun :", err)
logger.ErrorWithCallStack(err)
} else {
logger.Println("recover success :", sr)
}
}
}
if op != nil {
op.Refresh(context.Background(), hc.makeOperationQueryRequest()) op.Refresh(context.Background(), hc.makeOperationQueryRequest())
} }
} }
} }
}
}() }()
hc.shutdownFunc = func() { hc.shutdownFunc = func() {
// child process 강제 종료 // child process 강제 종료
atomic.StoreInt32(&ignoreRecover, 1)
for _, procmeta := range hc.childProcs { for _, procmeta := range hc.childProcs {
if procmeta.cmd != nil && procmeta.cmd.Process != nil { if procmeta.cmd != nil && procmeta.cmd.Process != nil {
procmeta.cmd.Process.Signal(os.Kill) procmeta.cmd.Process.Signal(os.Kill)
@ -496,6 +559,59 @@ func NewClient(standalone bool) (HoustonClient, error) {
return hc, nil return hc, nil
} }
func uploadSafe(url, filePath, name, version string) error {
defer func() {
r := recover()
if r != nil {
logger.Error(r)
}
}()
t, err := times.Stat(filePath)
if err != nil {
return err
}
file, err := os.Open(filePath)
if err != nil {
return err
}
if file == nil {
return errors.New("upload file is missing :" + filePath)
}
defer file.Close()
// hc.config.HttpAddress+"/upload",
httpreq, err := http.NewRequest("POST", url, file)
if err != nil {
return err
}
hn, _ := os.Hostname()
// createTime := file.
httpreq.Header.Set("Houston-Service-Name", name)
httpreq.Header.Set("Houston-Service-Version", version)
httpreq.Header.Set("Houston-Service-Filename", t.BirthTime().UTC().Format(time.DateOnly)+"."+hn+path.Ext(filePath))
httpreq.Header.Set("Content-Type", "application/zip")
resp, err := http.DefaultClient.Do(httpreq)
if err != nil {
return err
}
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("upload file failed. response code : %s, %d", filePath, resp.StatusCode)
}
if err := os.Remove(filePath); err != nil {
return err
}
return nil
}
func (hc *houstonClient) Start() { func (hc *houstonClient) Start() {
// receive from stream // receive from stream
defer func() { defer func() {
@ -512,6 +628,20 @@ func (hc *houstonClient) Start() {
proc.cmd.Wait() proc.cmd.Wait()
proc.cmd.Process.Release() proc.cmd.Process.Release()
} }
close(hc.uploadChan)
}()
go func() {
// upload 고루틴
url := hc.config.HttpAddress + "/upload"
for req := range hc.uploadChan {
logger.Println("uploadSafe :", req)
err := uploadSafe(url, req.logFile, req.name, req.version)
if err != nil {
logger.Println("uploadSafe return err :", err)
}
}
}() }()
interrupt := make(chan os.Signal, 1) interrupt := make(chan os.Signal, 1)
@ -527,6 +657,40 @@ func (hc *houstonClient) Start() {
reconnCount := 0 reconnCount := 0
time.Sleep(time.Second) time.Sleep(time.Second)
if autorun != nil && len(*autorun) > 0 {
hascount := strings.Split(*autorun, "/")
var service string
count := 1
if len(hascount) > 1 {
service = hascount[0]
if len(hascount[1]) > 0 {
count, _ = strconv.Atoi(hascount[1])
}
} else {
service = *autorun
}
if cmd, ok := hc.config.Autorun[service]; ok {
// service 서비스
for i := 0; i < count; i++ {
sr := shared.StartProcessRequest{
Name: service,
Version: cmd.Version,
Args: append([]string{cmd.Exec}, cmd.Args...),
AutoRestart: cmd.AutoRestart,
OutputLogFile: cmd.OutputLogFile,
}
if err := hc.startChildProcess(&sr); err != nil {
logger.Println("startChildProcess failed by autorun :", err)
logger.ErrorWithCallStack(err)
} else {
logger.Println("autorun success :", sr)
}
}
}
}
for { for {
select { select {
case <-hc.ctx.Done(): case <-hc.ctx.Done():
@ -557,8 +721,9 @@ func (hc *houstonClient) Start() {
if client != nil { if client != nil {
err := hc.checkOperation(client) err := hc.checkOperation(client)
if err != nil { if err != nil {
if status.Convert(err).Message() != status.Convert(context.Canceled).Message() {
logger.Println("grpc.DialContext hc.checkOperation failed :", err) logger.Println("grpc.DialContext hc.checkOperation failed :", err)
}
client = nil client = nil
} }
} }
@ -590,35 +755,6 @@ func (hc *houstonClient) checkOperation(client *grpc.ClientConn) error {
return err return err
} }
if autorun != nil && len(*autorun) > 0 {
hascount := strings.Split(*autorun, "/")
var service string
count := 1
if len(hascount) > 1 {
service = hascount[0]
count, _ = strconv.Atoi(hascount[1])
} else {
service = *autorun
}
if cmd, ok := hc.config.Autorun[service]; ok {
// service 서비스
for i := 0; i < count; i++ {
sr := shared.StartProcessRequest{
Name: service,
Version: cmd.Version,
Args: append([]string{cmd.Exec}, cmd.Args...),
}
if err := hc.startChildProcess(&sr, op); err != nil {
logger.Println("startChildProcess failed by autorun :", err)
} else {
logger.Println("autorun success :", sr)
}
}
}
}
for { for {
update, err := cl.Recv() update, err := cl.Recv()
if err != nil { if err != nil {

35
client/client_test.go Normal file
View File

@ -0,0 +1,35 @@
package client
import (
"fmt"
"sync"
"testing"
"time"
)
func Test_houstonClient_Start(t *testing.T) {
tc := make(chan int, 1000)
var wg sync.WaitGroup
wg.Add(1)
go func() {
// receive
defer wg.Done()
for v := range tc {
fmt.Println(v)
time.Sleep(100 * time.Millisecond)
}
}()
go func() {
// send
for i := 0; i < 100; i++ {
tc <- i
}
close(tc)
fmt.Println("channel close called")
}()
wg.Wait()
}

View File

@ -25,6 +25,10 @@ import (
"golang.org/x/text/transform" "golang.org/x/text/transform"
) )
const (
houston_update_dir = "./houston.update"
)
func pof2(x int64, min int64) (out int64) { func pof2(x int64, min int64) (out int64) {
out = 1 out = 1
org := x org := x
@ -217,13 +221,7 @@ func (hc *houstonClient) prepareDeploy(name string, version string) (destPath st
}() }()
verpath := path.Join(hc.config.StorageRoot, name, version) verpath := path.Join(hc.config.StorageRoot, name, version)
if _, err := os.Stat(verpath); os.IsNotExist(err) { if _, err := os.Stat(verpath); !os.IsNotExist(err) {
// 없네? 만들면 된다.
err = os.MkdirAll(verpath, 0775)
if err != nil {
return "", err
}
} else {
// 있네? 재배포 가능한가? // 있네? 재배포 가능한가?
for _, child := range hc.childProcs { for _, child := range hc.childProcs {
if child.version == version && child.name == name { if child.version == version && child.name == name {
@ -282,50 +280,23 @@ func copyfile(src, dst string) error {
return nil return nil
} }
func (hc *houstonClient) prepareUpdateSelf(req *shared.DeployRequest) (srcdir string, replacer string, err error) { func (hc *houstonClient) deploy(req *shared.DeployRequest, cb func(*protos.DeployingProgress)) (err error) {
// 내가 스스로 업데이트
// 다운로드 받고 압축 푼 다음에 교체용 프로세스 시작
tempdir, err := os.MkdirTemp(os.TempDir(), "*")
if err != nil {
return "", "", err
}
fname, err := download(tempdir, hc.makeDownloadUrl(req.Url), req.AccessToken, nil)
if err != nil {
return "", "", err
}
switch path.Ext(fname) {
case ".zip":
err = unzip(fname)
case ".tar":
err = untar(fname)
}
if err != nil {
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()
srcreplacer := path.Join(path.Dir(fname), "replacer") + path.Ext(selfname)
replacer = "./" + filepath.ToSlash("replacer"+path.Ext(selfname))
err = copyfile(srcreplacer, replacer)
if err == nil {
err = os.Chmod(replacer, 0775)
}
// replacer먼저 가져옴
return filepath.ToSlash(tempdir), replacer, err
}
func (hc *houstonClient) deploy(req *shared.DeployRequest, cb func(*protos.DeployingProgress)) error {
logger.Println("start deploying") logger.Println("start deploying")
root, err := hc.prepareDeploy(req.Name, req.Version)
var root string
if req.Name == "houston" {
// houston은 버전없이 houston_update_dir폴더로 다운로드
root = houston_update_dir
// 이미 houston_update_dir가 있을 수도 있으므로 폴더채로 삭제
os.RemoveAll(houston_update_dir)
} else {
root, err = hc.prepareDeploy(req.Name, req.Version)
if err != nil {
return err
}
}
err = os.MkdirAll(root, 0775)
if err != nil { if err != nil {
return err return err
} }
@ -334,7 +305,8 @@ func (hc *houstonClient) deploy(req *shared.DeployRequest, cb func(*protos.Deplo
h := md5.New() h := md5.New()
h.Write([]byte(strings.Trim(req.Url, "/"))) h.Write([]byte(strings.Trim(req.Url, "/")))
at := hex.EncodeToString(h.Sum(nil)) at := hex.EncodeToString(h.Sum(nil))
fname, err := download(root, hc.makeDownloadUrl(req.Url), at, func(written int64, total int64) { var fname string
fname, err = download(root, hc.makeDownloadUrl(req.Url), at, func(written int64, total int64) {
prog := protos.DeployingProgress{ prog := protos.DeployingProgress{
State: "download", State: "download",
Progress: written, Progress: written,
@ -360,7 +332,7 @@ func (hc *houstonClient) deploy(req *shared.DeployRequest, cb func(*protos.Deplo
err = untar(fname) err = untar(fname)
} }
if err == nil && len(req.Config) > 0 { if err == nil && len(req.Config) > 0 && req.Name != "houston" {
// config.json도 다운로드 // config.json도 다운로드
h := md5.New() h := md5.New()
h.Write([]byte(strings.Trim(req.Config, "/"))) h.Write([]byte(strings.Trim(req.Config, "/")))

View File

@ -8,17 +8,18 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math"
"net/http"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"sort" "regexp"
"slices"
"strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
"github.com/Knetic/govaluate"
"repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/gocommon/metric" "repositories.action2quare.com/ayo/gocommon/metric"
"repositories.action2quare.com/ayo/houston/shared" "repositories.action2quare.com/ayo/houston/shared"
@ -41,64 +42,43 @@ func lastExecutionArgs(verpath string) []string {
return out return out
} }
var errUploadZipLogFailed = errors.New("not ok") func (hc *houstonClient) uploadToAppendLog(logFile string, name string, version string) {
hc.uploadChan <- uploadRequest{
func (hc *houstonClient) uploadZipLogFile(zipFile string, name string, version string) error { logFile: logFile,
zf, err := os.Open(zipFile) name: name,
if err != nil { version: version,
return err
} }
if zf == nil {
return errUploadZipLogFailed
}
defer zf.Close()
req, err := http.NewRequest("POST", hc.config.HttpAddress+"/upload", zf)
if err != nil {
logger.Println(err)
}
req.Header.Set("Houston-Service-Name", name)
req.Header.Set("Houston-Service-Version", version)
req.Header.Set("Houston-Service-Filename", path.Base(filepath.ToSlash(zipFile)))
req.Header.Set("Content-Type", "application/zip")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errUploadZipLogFailed
}
return nil
} }
func zipLogFiles(storageRoot string, req *shared.UploadRequest) (string, []string, error) { func findMatchFiles(storageRoot, name, version, filter string) (string, []string) {
root := path.Join(storageRoot, req.Name, req.Version) root := path.Join(storageRoot, name, version)
matches, err := filepath.Glob(path.Join(root, req.Filter)) matches, err := filepath.Glob(path.Join(root, filter))
if err != nil { if err != nil {
return "", nil, err return "", nil
} }
if len(matches) == 0 { if len(matches) == 0 {
return "", nil, nil return "", nil
} }
for i, file := range matches { root = path.Join(root, path.Dir(filter))
out := make([]string, 0, len(matches))
for _, file := range matches {
file = filepath.ToSlash(file) file = filepath.ToSlash(file)
matches[i] = file if file == root {
continue
} }
root = path.Join(root, path.Dir(req.Filter)) out = append(out, file)
zipFileName := path.Join(os.TempDir(), path.Base(filepath.ToSlash(matches[0]))) + ".zip" }
os.Remove(zipFileName) slices.Sort(out)
f, err := os.OpenFile(zipFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) return root, out
}
func zipCompressFiles(root string, matches []string) (string, error) {
f, err := os.CreateTemp(os.TempDir(), "*.zip")
if err != nil { if err != nil {
return "", nil, err return "", err
} }
defer f.Close() defer f.Close()
@ -107,10 +87,6 @@ func zipLogFiles(storageRoot string, req *shared.UploadRequest) (string, []strin
oldestFile := "" oldestFile := ""
for i, file := range matches { for i, file := range matches {
if file == root {
continue
}
if fi, err := os.Lstat(file); err == nil { if fi, err := os.Lstat(file); err == nil {
if (fi.Mode() & os.ModeSymlink) == os.ModeSymlink { if (fi.Mode() & os.ModeSymlink) == os.ModeSymlink {
matches[i] = "" matches[i] = ""
@ -125,21 +101,21 @@ func zipLogFiles(storageRoot string, req *shared.UploadRequest) (string, []strin
relative := file[len(root)+1:] relative := file[len(root)+1:]
fw, err := w.Create(relative) fw, err := w.Create(relative)
if err != nil { if err != nil {
return "", nil, err return "", err
} }
src, err := os.Open(file) src, err := os.Open(file)
if err != nil { if err != nil {
return "", nil, err return "", err
} }
defer src.Close() defer src.Close()
if _, err = io.Copy(fw, src); err != nil { if _, err = io.Copy(fw, src); err != nil {
return "", nil, err return "", err
} }
} }
return f.Name(), matches, nil return f.Name(), nil
} }
func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) (*procmeta, error) { func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) (*procmeta, error) {
@ -147,6 +123,7 @@ func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) (
return nil, errors.New("args is empty") return nil, errors.New("args is empty")
} }
foundVersion := req.Version
if req.Version == "latest" { if req.Version == "latest" {
entries, err := os.ReadDir(path.Join(storageRoot, req.Name)) entries, err := os.ReadDir(path.Join(storageRoot, req.Name))
if err != nil { if err != nil {
@ -172,11 +149,11 @@ func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) (
} }
if len(latestVersion) > 0 { if len(latestVersion) > 0 {
req.Version = latestVersion foundVersion = latestVersion
} }
} }
verpath := path.Join(storageRoot, req.Name, req.Version) verpath := path.Join(storageRoot, req.Name, foundVersion)
fi, err := os.Stat(verpath) fi, err := os.Stat(verpath)
if err != nil { if err != nil {
return nil, err return nil, err
@ -211,29 +188,68 @@ func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) (
cmd: cmd, cmd: cmd,
name: req.Name, name: req.Name,
args: req.Args, args: req.Args,
version: req.Version, version: foundVersion,
recover: req.AutoRestart,
verpath: verpath, verpath: verpath,
state: int32(protos.ProcessState_Stopped), state: int32(protos.ProcessState_Stopped),
stdin: stdin, stdin: stdin,
logfile: req.OutputLogFile,
keepLatest: req.Version == "latest",
}, nil }, nil
} }
return nil, errors.New("not found") return nil, errors.New("not found")
} }
func makeLogFilePrefix(meta *procmeta, index int) string { func evaluateArgs(args []string, params map[string]any) ([]string, error) {
now := time.Now().UTC() re := regexp.MustCompile(`\$\(\((.*?)\)\)`)
ext := path.Ext(meta.args[0])
nameonly := path.Base(filepath.ToSlash(meta.args[0]))
if len(ext) > 0 {
nameonly = nameonly[:len(nameonly)-len(ext)]
}
ts := now.Format("2006-01-02T15-04-05")
if index == 0 {
return path.Join(meta.verpath, "logs", fmt.Sprintf("%s_%s", nameonly, ts)) for i, input := range args {
matches := re.FindAllStringSubmatch(input, -1)
if len(matches) == 0 {
continue
} }
return path.Join(meta.verpath, "logs", fmt.Sprintf("%s_%d_%s", nameonly, index, ts)) for _, match := range matches {
if len(match) > 1 {
expression := strings.TrimSpace(match[1])
expr, err := govaluate.NewEvaluableExpression(expression)
if err != nil {
return nil, err
}
result, err := expr.Evaluate(params)
if err != nil {
return nil, err
}
// 원래 표현식을 결과로 대체
input = strings.Replace(input, match[0], fmt.Sprintf("%v", result), -1)
}
}
args[i] = input
}
return args, nil
}
func parseEnv(input []string) map[string]any {
output := make(map[string]any, len(input))
for _, envkv := range input {
kv := strings.SplitN(envkv, "=", 2)
parsed, err := strconv.ParseInt(kv[1], 10, 0)
if err == nil {
output[kv[0]] = parsed
} else {
parsed, err := strconv.ParseFloat(kv[1], 32)
if err == nil {
output[kv[0]] = parsed
} else {
output[kv[0]] = kv[1]
}
}
}
return output
} }
func (hc *houstonClient) launch(meta *procmeta) error { func (hc *houstonClient) launch(meta *procmeta) error {
@ -241,78 +257,101 @@ func (hc *houstonClient) launch(meta *procmeta) error {
if err != nil { if err != nil {
return err return err
} }
stderr, err := meta.cmd.StderrPipe()
err = os.MkdirAll(path.Join(meta.verpath, "logs"), 0775)
if err != nil { if err != nil {
return err return err
} }
stdReader := func(jobName string, r io.ReadCloser, index int) { logfolder := path.Join(meta.verpath, "logs")
err = os.MkdirAll(logfolder, 0775)
if err != nil {
return err
}
logUploader := func(localctx context.Context, logfilePath string, logChan chan []byte) {
var logFile *os.File
var logFilePath string
ext := path.Ext(logfilePath)
head := logfilePath[:len(logfilePath)-len(ext)]
if len(head) > 0 && !strings.HasSuffix(head, "/") {
head += "."
}
writeLog := func(log []byte) {
if logFile == nil {
logFilePath = head + time.Now().UTC().Format("2006-01-02.150405") + ext
logFile, _ = os.OpenFile(logFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
}
for written := 0; written < len(log); {
n, err := logFile.Write(log[written:])
if err != nil {
logger.Println("write log file failed :", logfilePath, err)
break
} else {
written += n
}
}
}
defer func() {
if logFile != nil {
logFile.Close()
logFile = nil
hc.uploadToAppendLog(logFilePath, meta.name, meta.version)
}
}()
defer func() {
for {
select {
case log := <-logChan:
writeLog(log)
default:
// logChan에 있는 모든 로그 소비
return
}
}
}()
for {
heartbeat := time.After(time.Minute)
select {
case <-localctx.Done():
return
case <-heartbeat:
heartbeat = time.After(time.Minute)
// 지금까지의 로그를 저장해서 업로드
if logFile != nil {
logFile.Close()
logFile = nil
hc.uploadToAppendLog(logFilePath, meta.name, meta.version)
}
case log := <-logChan:
writeLog(log)
}
}
}
stdReader := func(r io.ReadCloser, logfilePath string, verify func(buff []byte) bool) {
defer func() { defer func() {
reco := recover() reco := recover()
if reco != nil { if reco != nil {
logger.Println(reco) logger.Println(reco)
} }
r.Close()
}() }()
defer func() { localctx, cancel := context.WithCancel(context.Background())
overflow := index / 64 defer cancel()
offset := index % 64
key := fmt.Sprintf("%s-%d", meta.args[0], overflow)
runningFlags := hc.siblingProcIndex[key]
mask := uint64(1 << offset)
runningFlags = runningFlags ^ mask
hc.siblingProcIndex[key] = runningFlags
}()
defer r.Close() logChan := make(chan []byte, 1)
go logUploader(localctx, logfilePath, logChan)
reader := bufio.NewReader(r) reader := bufio.NewReader(r)
thisFileSize := 0
logFileIndex := 0
logFileNamePrefix := makeLogFilePrefix(meta, index)
logFileName := fmt.Sprintf("%s_%d.log", logFileNamePrefix, logFileIndex)
targetFile, err := os.Create(logFileName)
if err != nil {
logger.Println("failed to create log file :", logFileName)
return
}
exef, _ := os.Executable()
var linkPath string
if index == 0 {
linkPath = path.Join(path.Dir(exef), path.Dir(logFileName), meta.name+".log")
} else {
linkPath = path.Join(path.Dir(exef), path.Dir(logFileName), fmt.Sprintf("%s_%d.log", meta.name, index))
}
os.Remove(linkPath)
os.Symlink(path.Base(filepath.ToSlash(targetFile.Name())), linkPath)
defer func() {
if targetFile != nil {
targetFile.Close()
}
}()
readingMetric := false
var metricBuffer []byte
defer func() {
logger.Println("stdReader is terminated :", meta.name)
if meta.isState(protos.ProcessState_Running) {
hc.operationChan <- &protos.OperationQueryResponse{
Operation: string(shared.Exception),
Args: map[string]string{
"id": fmt.Sprintf("%d", meta.id),
},
}
}
}()
metricExporter := metric.NewPrometheusExport(hc.config.MetricNamespace)
defer metricExporter.Shutdown()
for { for {
buff, err := reader.ReadBytes('\n') buff, err := reader.ReadBytes('\n')
if err != nil { if err != nil {
@ -320,6 +359,28 @@ func (hc *houstonClient) launch(meta *procmeta) error {
break break
} }
if verify(buff) {
if len(buff) > 0 {
logChan <- buff
}
}
}
}
var evalfile string
if len(meta.logfile) > 0 {
evalfile = path.Join(logfolder, meta.logfile)
} else {
evalfile = logfolder + "/"
}
go func() {
metricExporter := metric.NewPrometheusExport(hc.config.MetricNamespace)
defer metricExporter.Shutdown()
var metricBuffer []byte
readingMetric := false
stdReader(stdout, evalfile+".log", func(buff []byte) bool {
if readingMetric { if readingMetric {
metricBuffer = append(metricBuffer, buff...) metricBuffer = append(metricBuffer, buff...)
} else if buff[0] == metric.METRIC_HEAD_INLINE { } else if buff[0] == metric.METRIC_HEAD_INLINE {
@ -336,7 +397,7 @@ func (hc *houstonClient) launch(meta *procmeta) error {
var desc metric.MetricDescription var desc metric.MetricDescription
if err := json.Unmarshal(metricBuffer, &desc); err != nil { if err := json.Unmarshal(metricBuffer, &desc); err != nil {
logger.Println("unmarshal metric failed :", err, string(metricBuffer)) logger.Println("unmarshal metric failed :", err, string(metricBuffer))
continue return false
} }
if desc.ConstLabels == nil { if desc.ConstLabels == nil {
@ -347,8 +408,7 @@ func (hc *houstonClient) launch(meta *procmeta) error {
desc.ConstLabels[k] = v desc.ConstLabels[k] = v
} }
desc.ConstLabels["job"] = jobName desc.ConstLabels["job"] = meta.name
metricExporter.RegisterMetric(&desc) metricExporter.RegisterMetric(&desc)
} else { } else {
key, val := metric.ReadMetricValue(metricBuffer) key, val := metric.ReadMetricValue(metricBuffer)
@ -358,74 +418,36 @@ func (hc *houstonClient) launch(meta *procmeta) error {
metricBuffer = metricBuffer[:0] metricBuffer = metricBuffer[:0]
} }
continue return false
} }
return true
})
logger.Println("stdReader is terminated :", meta.name)
for written := 0; written < len(buff); { if meta.isState(protos.ProcessState_Running) {
n, err := targetFile.Write(buff) // state는 running인데 종료됐으면 exception처리
if err != nil { hc.operationChan <- &protos.OperationQueryResponse{
logger.Println("write log file failed :", logFileName, err) Operation: string(shared.Exception),
break Args: map[string]string{
} else { "id": fmt.Sprintf("%d", meta.id),
written += n },
thisFileSize += n
} }
} }
}()
if thisFileSize > 5*1024*1024 { go stdReader(stderr, evalfile+".err", func([]byte) bool { return true })
logFileIndex++
logFileName = fmt.Sprintf("%s_%d.log", logFileNamePrefix, logFileIndex)
nextTargetFile, err := os.Create(logFileName)
if err != nil {
logger.Println("failed to create log file :", logFileName)
} else {
targetFile.Close()
targetFile = nextTargetFile
os.Remove(linkPath)
os.Symlink(path.Base(filepath.ToSlash(targetFile.Name())), linkPath)
thisFileSize = 0
}
}
}
}
index := 0
for overflow := 0; ; overflow++ {
key := fmt.Sprintf("%s-%d", meta.args[0], overflow)
runningFlags := hc.siblingProcIndex[key]
if runningFlags == math.MaxUint64 {
index += 64
} else {
for si := 0; si < 64; si++ {
mask := uint64(1 << si)
if runningFlags&mask == 0 {
index += si
runningFlags |= mask
break
}
}
hc.siblingProcIndex[key] = runningFlags
break
}
}
go stdReader(meta.name, stdout, index)
logger.Println("startChildProcess :", meta.cmd.Args) logger.Println("startChildProcess :", meta.cmd.Args)
meta.cmd.Env = append(meta.cmd.Env, fmt.Sprintf("HOUSTON_SIBLIING_INDEX=%d", index))
err = meta.cmd.Start() err = meta.cmd.Start()
if err == nil { if err == nil {
logger.Println("process index, pid =", index, meta.cmd.Process.Pid)
set_affinity(meta.cmd.Process.Pid, index)
meta.setState(protos.ProcessState_Running) meta.setState(protos.ProcessState_Running)
} }
return err return err
} }
var errPrepareprocessLaunchFailed = errors.New("prepareProcessLaunch failed") func (hc *houstonClient) startChildProcess(req *shared.StartProcessRequest) error {
func (hc *houstonClient) startChildProcess(req *shared.StartProcessRequest, op protos.OperationClient) error {
meta, err := prepareProcessLaunch(hc.config.StorageRoot, req) meta, err := prepareProcessLaunch(hc.config.StorageRoot, req)
if err != nil { if err != nil {
return err return err
@ -455,8 +477,6 @@ func (hc *houstonClient) startChildProcess(req *shared.StartProcessRequest, op p
} }
hc.childProcs = append(hc.childProcs, meta) hc.childProcs = append(hc.childProcs, meta)
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
return nil return nil
} }
@ -525,54 +545,3 @@ func (hc *houstonClient) restartChildProcess(req *shared.RestartProcessRequest,
return nil return nil
} }
func (hc *houstonClient) uploadFiles(req *shared.UploadRequest) error {
logger.Println("uploadFiles req :", *req)
for _, child := range hc.childProcs {
if child.version == req.Version && child.name == req.Name {
logger.Println("uploadFiles found :", child.version, child.name)
go func() {
zipFile, srcFiles, err := zipLogFiles(hc.config.StorageRoot, req)
if err == nil && len(zipFile) > 0 && len(srcFiles) > 0 {
if err = hc.uploadZipLogFile(zipFile, child.name, child.version); err == nil {
// 마지막거 빼고 삭제
sort.StringSlice(srcFiles).Sort()
for i := 0; i < len(srcFiles)-1; i++ {
if len(srcFiles[i]) > 0 {
os.Remove(srcFiles[i])
}
}
} else {
logger.Println("uploadZipLogFile failed :", err)
}
} else if err != nil {
logger.Println("zipLogFiles failed :", err)
}
}()
return nil
}
}
// 실행 중이 아닌 폴더에서도 대상을 찾는다
// 전체 파일을 대상으로
zipFile, srcFiles, err := zipLogFiles(hc.config.StorageRoot, req)
if err == nil && len(zipFile) > 0 && len(srcFiles) > 0 {
if err = hc.uploadZipLogFile(zipFile, req.Name, req.Version); err == nil {
// 마지막거 빼고 삭제
sort.StringSlice(srcFiles).Sort()
for i := 0; i < len(srcFiles)-1; i++ {
if len(srcFiles[i]) > 0 {
os.Remove(srcFiles[i])
}
}
} else {
logger.Println("uploadZipLogFile failed :", err)
}
} else if err != nil {
logger.Println("zipLogFiles failed :", err)
}
return nil
}

20
go.mod
View File

@ -3,15 +3,17 @@ module repositories.action2quare.com/ayo/houston
go 1.19 go 1.19
require ( require (
github.com/Knetic/govaluate v3.0.0+incompatible
github.com/djherbis/times v1.6.0
github.com/go-kit/log v0.2.1 github.com/go-kit/log v0.2.1
github.com/prometheus/client_golang v1.17.0 github.com/prometheus/client_golang v1.17.0
github.com/prometheus/common v0.44.0 github.com/prometheus/common v0.44.0
github.com/prometheus/node_exporter v1.6.1 github.com/prometheus/node_exporter v1.6.1
golang.org/x/sys v0.15.0 golang.org/x/sys v0.16.0
golang.org/x/text v0.10.0 golang.org/x/text v0.14.0
google.golang.org/grpc v1.56.0 google.golang.org/grpc v1.60.1
google.golang.org/protobuf v1.31.0 google.golang.org/protobuf v1.32.0
repositories.action2quare.com/ayo/gocommon v0.0.0-20240215030631-0c5ddac9f55f repositories.action2quare.com/ayo/gocommon v0.0.0-20240729084947-8e3d6c28f024
) )
require ( require (
@ -61,10 +63,10 @@ require (
go.mongodb.org/mongo-driver v1.11.6 // indirect go.mongodb.org/mongo-driver v1.11.6 // indirect
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.10.0 // indirect golang.org/x/crypto v0.18.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.11.0 // indirect golang.org/x/net v0.20.0 // indirect
golang.org/x/sync v0.3.0 // indirect golang.org/x/sync v0.6.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac // indirect
howett.net/plist v1.0.0 // indirect howett.net/plist v1.0.0 // indirect
) )

41
go.sum
View File

@ -1,3 +1,5 @@
github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU= github.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU=
github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
@ -20,6 +22,8 @@ github.com/dennwc/ioctl v1.0.0 h1:DsWAAjIxRqNcLn9x6mwfuf2pet3iB7aK90K4tF16rLg=
github.com/dennwc/ioctl v1.0.0/go.mod h1:ellh2YB5ldny99SBU/VX7Nq0xiZbHphf1DrtHxxjMk0= github.com/dennwc/ioctl v1.0.0/go.mod h1:ellh2YB5ldny99SBU/VX7Nq0xiZbHphf1DrtHxxjMk0=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/ema/qdisc v0.0.0-20230120214811-5b708f463de3 h1:Jrl8sD8wO34+EE1dV2vhOXrqFAZa/FILDnZRaV28+cw= github.com/ema/qdisc v0.0.0-20230120214811-5b708f463de3 h1:Jrl8sD8wO34+EE1dV2vhOXrqFAZa/FILDnZRaV28+cw=
github.com/ema/qdisc v0.0.0-20230120214811-5b708f463de3/go.mod h1:FhIc0fLYi7f+lK5maMsesDqwYojIOh3VfRs8EVd5YJQ= github.com/ema/qdisc v0.0.0-20230120214811-5b708f463de3/go.mod h1:FhIc0fLYi7f+lK5maMsesDqwYojIOh3VfRs8EVd5YJQ=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
@ -129,39 +133,40 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac h1:nUQEQmH/csSvFECKYRv6HWEyypysidKl2I6Qpsglq/0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA=
google.golang.org/grpc v1.56.0 h1:+y7Bs8rtMd07LeXmL3NxcTLn7mUkbKZqEpPhMNkwJEE= google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU=
google.golang.org/grpc v1.56.0/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@ -173,5 +178,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
repositories.action2quare.com/ayo/gocommon v0.0.0-20240215030631-0c5ddac9f55f h1:ZIDd5JCXlPCVj2656Bqm/yiI0cZzgIa51IOs5AljEAA= repositories.action2quare.com/ayo/gocommon v0.0.0-20240729084947-8e3d6c28f024 h1:WdvW4BJHoBwXqNsfEgOAZai7L9iHqRCZ7PZL0cwOULE=
repositories.action2quare.com/ayo/gocommon v0.0.0-20240215030631-0c5ddac9f55f/go.mod h1:Gb418rT96M3K7L/XMPzp8IJj4UXVunq7dZzrxsMBz/8= repositories.action2quare.com/ayo/gocommon v0.0.0-20240729084947-8e3d6c28f024/go.mod h1:XA8+hQtUNh956T+kAbJKkUtMl5HUWj83knvdBvvPS5s=

View File

@ -1,4 +1,37 @@
#!/bin/sh #!/bin/bash
nohup /home/opdev/houston -client -logfile > /dev/null & HOUSTON_PATH="./houston"
UPDATE_DIR="houston.update"
run_houston() {
if [ -f "$HOUSTON_PATH" ]; then
"$HOUSTON_PATH" "$@"
return $?
else
echo "houston 실행 파일이 없습니다."
return 1
fi
}
while true; do
echo "현재 버전의 houston을 실행합니다."
run_houston "$@"
# houston.update 폴더가 존재하는지 확인
if [ -d "$UPDATE_DIR" ]; then
echo "새로운 업데이트 폴더 발견. 업데이트를 진행합니다."
# houston.update 폴더 내의 모든 파일을 현재 폴더로 복사
cp -R "$UPDATE_DIR"/* .
# 실행 권한 부여 (필요한 경우)
chmod +x "$HOUSTON_PATH"
# 업데이트 폴더 삭제
rm -rf "$UPDATE_DIR"
echo "업데이트 완료 및 업데이트 폴더 삭제. houston을 다시 시작합니다."
else
echo "업데이트 폴더가 없습니다. 스크립트 실행을 종료합니다."
break
fi
done
echo "houston 실행 및 업데이트 스크립트 종료"
exit $?

View File

@ -8,6 +8,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"repositories.action2quare.com/ayo/gocommon/flagx" "repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/houston/client" "repositories.action2quare.com/ayo/houston/client"
"net/http" "net/http"
@ -23,7 +24,11 @@ func main() {
http.Handle("/metrics", promhttp.Handler()) http.Handle("/metrics", promhttp.Handler())
server := &http.Server{Addr: ":9100", Handler: nil} server := &http.Server{Addr: ":9100", Handler: nil}
go server.ListenAndServe()
go func() {
logger.Println("listen /metrics")
server.ListenAndServe()
}()
hc.Start() hc.Start()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

View File

@ -10,19 +10,7 @@ go mod tidy
go build -ldflags="-s -w" -tags=client . go build -ldflags="-s -w" -tags=client .
cp houston .\replacer\houston
cp config.json .\replacer\config.json
cd replacer
go build -ldflags="-s -w" .
Compress-Archive -Path replacer -DestinationPath houston.zip -Force
Compress-Archive -Path config.json -Update -DestinationPath houston.zip Compress-Archive -Path config.json -Update -DestinationPath houston.zip
Compress-Archive -Path houston -Update -DestinationPath houston.zip Compress-Archive -Path houston -Update -DestinationPath houston.zip
del houston
del config.json
del replacer
mv houston.zip ..\houston.zip
cd ..

View File

@ -1,113 +0,0 @@
package main
import (
"encoding/json"
"errors"
"io"
"log"
"os"
"os/exec"
"path"
"time"
)
func copy(src, dst string, stdlog *log.Logger) error {
fi, err := os.Stat(src)
if err != nil {
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()
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
copied, err := io.Copy(out, in)
if err != nil {
return err
}
if copied < fi.Size() {
return errors.New("copy not completed")
}
if err := out.Sync(); err != nil {
return err
}
if err := out.Chmod(inmode); err != nil {
return err
}
stdlog.Println("file copied :", src, dst)
return nil
}
func main() {
logfile, _ := os.OpenFile("replacer.log", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
defer logfile.Close()
stdlog := log.New(logfile, "", log.LstdFlags)
args := os.Args
// args[1] : 나를 시작한 pid. pid가 종료될 때 까지 기다림
// args[2] : target 폴더
// args[3:] : 다시 시작할 때 넘겨줄 arguments(프로세스 이름 포함)
stdlog.Println(args)
for {
stdlog.Println("wait for terminating of", args[3])
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)
}
nextArgs := args[4:]
if bt, _ := os.ReadFile("@args"); len(bt) > 0 {
var tempArgs []string
if json.Unmarshal(bt, &tempArgs) == nil {
nextArgs = tempArgs
}
}
os.Remove("@args")
err := os.RemoveAll(args[2])
if err != nil {
stdlog.Println("os.RemoveAll failed :", args[2], err)
}
err = os.Chmod(args[3], 0775)
if err != nil {
stdlog.Println("os.Chmod failed :", err)
}
stdlog.Println("exec.Command :", args)
cmd := exec.Command(args[3], nextArgs...)
cmd.Start()
}

View File

@ -100,6 +100,7 @@ func (h *houstonHandler) UploadDeploySource(w http.ResponseWriter, r *http.Reque
// <input type="submit" value="업로드"> // <input type="submit" value="업로드">
// </form> // </form>
file, header, err := r.FormFile("file") file, header, err := r.FormFile("file")
if err != nil { if err != nil {
logger.Println(err) logger.Println(err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
@ -161,6 +162,7 @@ func (h *houstonHandler) DeleteDeploySource(w http.ResponseWriter, r *http.Reque
// deploys 폴더는 파일시스템 서비스이므로 다운로드 가능 // deploys 폴더는 파일시스템 서비스이므로 다운로드 가능
targetpath := path.Join(h.deployPath, name, version) targetpath := path.Join(h.deployPath, name, version)
if err := os.RemoveAll(targetpath); err != nil { if err := os.RemoveAll(targetpath); err != nil {
logger.Println("deleteDeploySource failed :", err) logger.Println("deleteDeploySource failed :", err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
@ -169,7 +171,6 @@ func (h *houstonHandler) DeleteDeploySource(w http.ResponseWriter, r *http.Reque
} }
func (h *houstonHandler) findLastestConfigFile(name string) (string, error) { func (h *houstonHandler) findLastestConfigFile(name string) (string, error) {
logger.Println("findLastestConfigFile :", name)
configFiles, err := os.ReadDir(path.Join(h.deployPath, name, "config")) configFiles, err := os.ReadDir(path.Join(h.deployPath, name, "config"))
if err != nil { if err != nil {
if errors.Is(err, fs.ErrNotExist) { if errors.Is(err, fs.ErrNotExist) {
@ -200,12 +201,9 @@ func (h *houstonHandler) findLastestConfigFile(name string) (string, error) {
} }
if cf != nil { if cf != nil {
logger.Println("findLastestConfigFile cf found :", cf.Name())
return path.Join(sub_folder_name_deploys, name, "config", cf.Name()), nil return path.Join(sub_folder_name_deploys, name, "config", cf.Name()), nil
} }
logger.Println("findLastestConfigFile cf NOT found")
return "", nil return "", nil
} }
@ -264,12 +262,16 @@ func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
return return
} }
configPath, err := h.findLastestConfigFile(name) var configPath string
if name != "houston" {
// houston은 config를 포함하여 배포
configPath, err = h.findLastestConfigFile(name)
if err != nil { if err != nil {
logger.Println(err) logger.Println(err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
}
h.Operation().Deploy(MakeDeployRequest( h.Operation().Deploy(MakeDeployRequest(
shared.DeployRequest{ shared.DeployRequest{
@ -405,6 +407,13 @@ func (h *houstonHandler) StopProcess(w http.ResponseWriter, r *http.Request) {
Version: version, Version: version,
Pid: int32(pid), Pid: int32(pid),
}, targets)) }, 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) { func (h *houstonHandler) RestartProcess(w http.ResponseWriter, r *http.Request) {
@ -417,6 +426,7 @@ func (h *houstonHandler) RestartProcess(w http.ResponseWriter, r *http.Request)
pidstr := r.FormValue("pid") pidstr := r.FormValue("pid")
target := r.FormValue("target") target := r.FormValue("target")
name := r.FormValue("name") name := r.FormValue("name")
if len(target) == 0 || len(pidstr) == 0 || len(name) == 0 { if len(target) == 0 || len(pidstr) == 0 || len(name) == 0 {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return

View File

@ -3,7 +3,6 @@ package server
import ( import (
"crypto/md5" "crypto/md5"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -14,13 +13,13 @@ import (
"runtime/debug" "runtime/debug"
"strings" "strings"
"repositories.action2quare.com/ayo/gocommon/flagx" "repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/logger"
) )
type HoustonServerWithHandler interface { type HoustonServerWithHandler interface {
HoustonServer HoustonServer
RegisterHandlers(serveMux *http.ServeMux, prefix string) error RegisterHandlers(serveMux gocommon.ServerMuxInterface, prefix string) error
} }
type houstonHandler struct { type houstonHandler struct {
@ -28,9 +27,10 @@ type houstonHandler struct {
methods map[string]reflect.Method methods map[string]reflect.Method
deployPath string deployPath string
downloadPath string downloadPath string
maingateApiToken string
} }
func NewHoustonHandler() HoustonServerWithHandler { func NewHoustonHandler(apiToken string) HoustonServerWithHandler {
var tmp *houstonHandler var tmp *houstonHandler
methods := make(map[string]reflect.Method) methods := make(map[string]reflect.Method)
@ -42,10 +42,11 @@ func NewHoustonHandler() HoustonServerWithHandler {
return &houstonHandler{ return &houstonHandler{
HoustonServer: NewServer(), HoustonServer: NewServer(),
methods: methods, methods: methods,
maingateApiToken: apiToken,
} }
} }
func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string) error { func (h *houstonHandler) RegisterHandlers(serveMux gocommon.ServerMuxInterface, prefix string) error {
config := loadServerConfig() config := loadServerConfig()
storagePath := config.StorageRoot storagePath := config.StorageRoot
h.deployPath = path.Join(storagePath, sub_folder_name_deploys) h.deployPath = path.Join(storagePath, sub_folder_name_deploys)
@ -93,24 +94,27 @@ func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string
// config는 접근하기 편하게 단축 경로 제공 // config는 접근하기 편하게 단축 경로 제공
serveMux.HandleFunc("/config/", func(w http.ResponseWriter, r *http.Request) { serveMux.HandleFunc("/config/", func(w http.ResponseWriter, r *http.Request) {
logger.Println("config url.path :", r.URL.Path) logger.Println("config url.path :", r.URL.Path)
h := md5.New() testhash := md5.New()
h.Write([]byte(r.URL.Path)) testhash.Write([]byte(r.URL.Path))
at := hex.EncodeToString(h.Sum(nil)) at := hex.EncodeToString(testhash.Sum(nil))
hash := r.Header.Get("As-X-UrlHash") hash := r.Header.Get("As-X-UrlHash")
logger.Println("config at = hash :", at, hash) logger.Println("config at = hash :", at, hash)
if at == hash { if at == hash {
urlpath := strings.TrimPrefix(r.URL.Path, "/config/") urlpath := strings.TrimPrefix(r.URL.Path, "/config/")
dir := path.Dir(urlpath) dir := path.Dir(urlpath)
file := path.Base(urlpath) file := path.Base(urlpath)
dest := fmt.Sprintf("%s/config/%s", dir, file) sourceFile := path.Join(h.deployPath, dir, "config", file)
logger.Println("config dest :", dest) logger.Println("config dest :", sourceFile)
r2 := new(http.Request) bt, err := os.ReadFile(sourceFile)
*r2 = *r if err != nil && !os.IsExist(err) {
r2.URL = new(url.URL) logger.Println("config file is missing :", sourceFile)
*r2.URL = *r.URL w.WriteHeader(http.StatusNotFound)
r2.URL.Path = dest } else {
r2.URL.RawPath = dest if _, err = w.Write(bt); err != nil {
fsx.ServeHTTP(w, r2) logger.Println("config write failed :", err)
w.WriteHeader(http.StatusInternalServerError)
}
}
} else { } else {
http.NotFound(w, r) http.NotFound(w, r)
} }
@ -123,8 +127,7 @@ func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string
defer func() { defer func() {
s := recover() s := recover()
if s != nil { if s != nil {
logger.Println(s) logger.Error(s)
debug.PrintStack()
} }
io.Copy(io.Discard, r.Body) io.Copy(io.Discard, r.Body)
r.Body.Close() r.Body.Close()
@ -135,10 +138,12 @@ func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string
filename := r.Header.Get("Houston-Service-Filename") filename := r.Header.Get("Houston-Service-Filename")
dir := path.Join(h.downloadPath, name, version) dir := path.Join(h.downloadPath, name, version)
if err := os.MkdirAll(dir, 0775); err == nil { if err := os.MkdirAll(dir, 0775); err == nil {
file, _ := os.Create(path.Join(dir, filename)) filepath := path.Join(dir, filename)
if file != nil { // filepath가 이미 있으면 append
defer file.Close() localfile, _ := os.OpenFile(filepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
if _, err = io.Copy(file, r.Body); err != nil { if localfile != nil {
defer localfile.Close()
if _, err = io.Copy(localfile, r.Body); err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
} }
} else { } else {
@ -152,8 +157,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()
@ -168,38 +171,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")

View File

@ -1,11 +1,11 @@
package server package server
import ( import (
"encoding/json"
"fmt" "fmt"
"net" "net"
"os"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/gocommon/logger"
"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"
@ -108,24 +108,18 @@ type Operation interface {
DeplyingProgress() []deployingProgress DeplyingProgress() []deployingProgress
} }
func loadServerConfig() serverConfig { type outerconfig struct {
configFile, err := os.Open("config.json") Houston struct {
if err != nil {
logger.Println(err)
return serverConfig{
GrpcPort: 8080,
}
}
defer configFile.Close()
var config struct {
Houston *struct {
Server serverConfig `json:"server"` Server serverConfig `json:"server"`
} `json:"houston"` } `json:"houston"`
} }
dec := json.NewDecoder(configFile) var storagePath = flagx.String("hs_storage", "", "")
err = dec.Decode(&config) var grpcPort = flagx.Int("hs_grpc_port", 0, "")
func loadServerConfig() serverConfig {
var oc outerconfig
err := gocommon.LoadConfig[outerconfig](&oc)
if err != nil { if err != nil {
logger.Println(err) logger.Println(err)
return serverConfig{ return serverConfig{
@ -133,14 +127,16 @@ func loadServerConfig() serverConfig {
} }
} }
if config.Houston == nil { if len(*storagePath) > 0 {
logger.Println(`"houston" object is missing in config.json`) // override
return serverConfig{ oc.Houston.Server.StorageRoot = *storagePath
GrpcPort: 8080,
}
} }
return config.Houston.Server if *grpcPort != 0 {
oc.Houston.Server.GrpcPort = *grpcPort
}
return oc.Houston.Server
} }
func NewServer() HoustonServer { func NewServer() HoustonServer {

View File

@ -30,6 +30,8 @@ type StartProcessRequest struct {
Name string Name string
Version string Version string
Args []string Args []string
AutoRestart bool
OutputLogFile string
} }
type StopProcessRequest struct { type StopProcessRequest struct {