Compare commits
34 Commits
5e4799ff55
...
sbl
| Author | SHA1 | Date | |
|---|---|---|---|
| 598181e7f7 | |||
| 7015dc99d6 | |||
| df56a542a1 | |||
| 46dd289c28 | |||
| 43b5aee48b | |||
| 592e00b98b | |||
| 92432fcd83 | |||
| a844bed056 | |||
| 2ddbae07b2 | |||
| 4d6665b64a | |||
| 99e1007012 | |||
| 9632dc7755 | |||
| a60dee1645 | |||
| 7fe5090efa | |||
| e9370513c2 | |||
| 387d4f3ea8 | |||
| e5984b3342 | |||
| f174a165fe | |||
| a112f20cb8 | |||
| 97fc64be81 | |||
| 7ae391b599 | |||
| f5e491325f | |||
| 2fa02374fd | |||
| 380586fb73 | |||
| da37ed11cd | |||
| 3ab055008c | |||
| 71e80d2908 | |||
| 12a0f9d2b1 | |||
| 016f459252 | |||
| 030fa658f5 | |||
| 3c96921703 | |||
| 6f444e0187 | |||
| 3eaad85453 | |||
| 401cfa8b84 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@ houston
|
||||
houston.zip
|
||||
config.json
|
||||
.vscode/
|
||||
/data
|
||||
|
||||
270
client/client.go
270
client/client.go
@ -2,10 +2,12 @@ package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
@ -21,6 +23,7 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/djherbis/times"
|
||||
"repositories.action2quare.com/ayo/gocommon"
|
||||
"repositories.action2quare.com/ayo/gocommon/flagx"
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
@ -29,12 +32,30 @@ import (
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type runcommand struct {
|
||||
Exec string `json:"exec"`
|
||||
Args []string `json:"args"`
|
||||
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 {
|
||||
@ -79,8 +100,11 @@ type procmeta struct {
|
||||
args []string
|
||||
version string
|
||||
verpath string
|
||||
recover bool
|
||||
state int32
|
||||
stdin io.WriteCloser
|
||||
logfile string
|
||||
keepLatest bool
|
||||
}
|
||||
|
||||
func (pm *procmeta) isState(s protos.ProcessState) bool {
|
||||
@ -95,6 +119,12 @@ func (pm *procmeta) setState(s protos.ProcessState) {
|
||||
atomic.StoreInt32(&pm.state, int32(s))
|
||||
}
|
||||
|
||||
type uploadRequest struct {
|
||||
logFile string
|
||||
name string
|
||||
version string
|
||||
}
|
||||
|
||||
type houstonClient struct {
|
||||
childProcs []*procmeta
|
||||
extraMetrics unsafe.Pointer // map[string]float32
|
||||
@ -104,6 +134,7 @@ type houstonClient struct {
|
||||
operationChan chan *protos.OperationQueryResponse
|
||||
exitChan chan *exec.Cmd
|
||||
clientChan chan *grpc.ClientConn
|
||||
uploadChan chan uploadRequest
|
||||
timestamp string
|
||||
wg sync.WaitGroup
|
||||
config clientConfig
|
||||
@ -113,6 +144,13 @@ type houstonClient struct {
|
||||
}
|
||||
|
||||
func unmarshal[T any](val *T, src map[string]string) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
logger.Error(r)
|
||||
}
|
||||
}()
|
||||
|
||||
argval := reflect.ValueOf(val)
|
||||
for i := 0; i < argval.Elem().Type().NumField(); i++ {
|
||||
if !argval.Elem().Type().Field(i).IsExported() {
|
||||
@ -125,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 {
|
||||
conv := strings.Split(arg, "\n")
|
||||
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 {
|
||||
argval.Elem().Field(i).SetString(arg)
|
||||
}
|
||||
@ -286,6 +327,7 @@ func NewClient(standalone bool) (HoustonClient, error) {
|
||||
timestamp: exefi.ModTime().String(),
|
||||
version: string(ver),
|
||||
standalone: standalone,
|
||||
uploadChan: make(chan uploadRequest, 100),
|
||||
siblingProcIndex: make(map[string]uint64),
|
||||
}
|
||||
|
||||
@ -294,6 +336,7 @@ func NewClient(standalone bool) (HoustonClient, error) {
|
||||
exitChan := make(chan *exec.Cmd, 10)
|
||||
operationChan := make(chan *protos.OperationQueryResponse, 10)
|
||||
hc.wg.Add(1)
|
||||
ignoreRecover := int32(0)
|
||||
|
||||
// autorun 처리
|
||||
go func() {
|
||||
@ -317,6 +360,7 @@ func NewClient(standalone bool) (HoustonClient, error) {
|
||||
|
||||
case newClient := <-hc.clientChan:
|
||||
op = protos.NewOperationClient(newClient)
|
||||
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
|
||||
|
||||
case exited := <-exitChan:
|
||||
var newprocs []*procmeta
|
||||
@ -331,11 +375,19 @@ func NewClient(standalone bool) (HoustonClient, error) {
|
||||
proc.cmd.Process.Release()
|
||||
|
||||
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,
|
||||
Name: proc.name,
|
||||
Args: proc.args,
|
||||
}, op)
|
||||
}); err != nil {
|
||||
logger.ErrorWithCallStack(err)
|
||||
} else {
|
||||
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
|
||||
}
|
||||
}
|
||||
}(proc)
|
||||
}
|
||||
@ -347,28 +399,14 @@ func NewClient(standalone bool) (HoustonClient, error) {
|
||||
op.Refresh(ctx, hc.makeOperationQueryRequest())
|
||||
|
||||
case resp := <-operationChan:
|
||||
logger.Println("houton query operation :", resp.Operation)
|
||||
|
||||
switch shared.Operation(resp.Operation) {
|
||||
case shared.Deploy:
|
||||
var dr shared.DeployRequest
|
||||
unmarshal(&dr, resp.Args)
|
||||
if dr.Name == myname {
|
||||
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 {
|
||||
logger.Println("args :", dr)
|
||||
|
||||
hn, _ := os.Hostname()
|
||||
|
||||
if err := hc.deploy(&dr, func(dp *protos.DeployingProgress) {
|
||||
@ -377,6 +415,12 @@ func NewClient(standalone bool) (HoustonClient, error) {
|
||||
dp.Version = dr.Version
|
||||
op.ReportDeployingProgress(ctx, dp)
|
||||
}); err == nil {
|
||||
if dr.Name == "houston" {
|
||||
// houston_update_dir 다운로드가 완료되었으므로 종료
|
||||
// 종료되고나면 스크립트가 알아서 재 실행
|
||||
hc.Shutdown()
|
||||
return
|
||||
}
|
||||
prog := gatherDeployedPrograms(hc.config.StorageRoot, dr.Name)
|
||||
hc.deploys[dr.Name] = prog
|
||||
op.Refresh(ctx, hc.makeOperationQueryRequest())
|
||||
@ -401,11 +445,12 @@ func NewClient(standalone bool) (HoustonClient, error) {
|
||||
Total: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
case shared.Withdraw:
|
||||
var wr shared.WithdrawRequest
|
||||
unmarshal(&wr, resp.Args)
|
||||
logger.Println("args :", wr)
|
||||
|
||||
err := hc.withdraw(&wr)
|
||||
if err == nil {
|
||||
prog := gatherDeployedPrograms(hc.config.StorageRoot, wr.Name)
|
||||
@ -422,13 +467,19 @@ func NewClient(standalone bool) (HoustonClient, error) {
|
||||
case shared.Start:
|
||||
var sr shared.StartProcessRequest
|
||||
unmarshal(&sr, resp.Args)
|
||||
if err := hc.startChildProcess(&sr, op); err != nil {
|
||||
logger.Println(err)
|
||||
logger.Println("args :", sr)
|
||||
|
||||
if err := hc.startChildProcess(&sr); err != nil {
|
||||
logger.ErrorWithCallStack(err)
|
||||
} else {
|
||||
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
|
||||
}
|
||||
|
||||
case shared.Stop:
|
||||
var sr shared.StopProcessRequest
|
||||
unmarshal(&sr, resp.Args)
|
||||
logger.Println("args :", sr)
|
||||
|
||||
if err := hc.stopChildProcess(&sr, op); err != nil {
|
||||
logger.Println(err)
|
||||
}
|
||||
@ -436,14 +487,9 @@ func NewClient(standalone bool) (HoustonClient, error) {
|
||||
case shared.Restart:
|
||||
var rr shared.RestartProcessRequest
|
||||
unmarshal(&rr, resp.Args)
|
||||
if err := hc.restartChildProcess(&rr, op); err != nil {
|
||||
logger.Println(err)
|
||||
}
|
||||
logger.Println("args :", rr)
|
||||
|
||||
case shared.Upload:
|
||||
var ur shared.UploadRequest
|
||||
unmarshal(&ur, resp.Args)
|
||||
if err := hc.uploadFiles(&ur); err != nil {
|
||||
if err := hc.restartChildProcess(&rr, op); err != nil {
|
||||
logger.Println(err)
|
||||
}
|
||||
|
||||
@ -452,23 +498,50 @@ func NewClient(standalone bool) (HoustonClient, error) {
|
||||
id64, _ := strconv.ParseInt(idstr, 10, 0)
|
||||
id := int32(id64)
|
||||
|
||||
var found *procmeta
|
||||
hc.childProcs = gocommon.ShrinkSlice(hc.childProcs, func(e *procmeta) bool {
|
||||
if e.id == id {
|
||||
e.cmd.Wait()
|
||||
e.cmd.Process.Release()
|
||||
found = e
|
||||
return true
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
hc.shutdownFunc = func() {
|
||||
// child process 강제 종료
|
||||
atomic.StoreInt32(&ignoreRecover, 1)
|
||||
|
||||
for _, procmeta := range hc.childProcs {
|
||||
if procmeta.cmd != nil && procmeta.cmd.Process != nil {
|
||||
procmeta.cmd.Process.Signal(os.Kill)
|
||||
@ -486,6 +559,59 @@ func NewClient(standalone bool) (HoustonClient, error) {
|
||||
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() {
|
||||
// receive from stream
|
||||
defer func() {
|
||||
@ -502,6 +628,20 @@ func (hc *houstonClient) Start() {
|
||||
proc.cmd.Wait()
|
||||
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)
|
||||
@ -517,6 +657,40 @@ func (hc *houstonClient) Start() {
|
||||
reconnCount := 0
|
||||
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 {
|
||||
select {
|
||||
case <-hc.ctx.Done():
|
||||
@ -547,8 +721,9 @@ func (hc *houstonClient) Start() {
|
||||
if client != nil {
|
||||
err := hc.checkOperation(client)
|
||||
if err != nil {
|
||||
if status.Convert(err).Message() != status.Convert(context.Canceled).Message() {
|
||||
logger.Println("grpc.DialContext hc.checkOperation failed :", err)
|
||||
|
||||
}
|
||||
client = nil
|
||||
}
|
||||
}
|
||||
@ -580,35 +755,6 @@ func (hc *houstonClient) checkOperation(client *grpc.ClientConn) error {
|
||||
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 {
|
||||
update, err := cl.Recv()
|
||||
if err != nil {
|
||||
|
||||
35
client/client_test.go
Normal file
35
client/client_test.go
Normal 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()
|
||||
}
|
||||
@ -25,6 +25,10 @@ import (
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
const (
|
||||
houston_update_dir = "./houston.update"
|
||||
)
|
||||
|
||||
func pof2(x int64, min int64) (out int64) {
|
||||
out = 1
|
||||
org := x
|
||||
@ -217,13 +221,7 @@ func (hc *houstonClient) prepareDeploy(name string, version string) (destPath st
|
||||
}()
|
||||
|
||||
verpath := path.Join(hc.config.StorageRoot, name, version)
|
||||
if _, err := os.Stat(verpath); os.IsNotExist(err) {
|
||||
// 없네? 만들면 된다.
|
||||
err = os.MkdirAll(verpath, 0775)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
if _, err := os.Stat(verpath); !os.IsNotExist(err) {
|
||||
// 있네? 재배포 가능한가?
|
||||
for _, child := range hc.childProcs {
|
||||
if child.version == version && child.name == name {
|
||||
@ -282,50 +280,23 @@ func copyfile(src, dst string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hc *houstonClient) prepareUpdateSelf(req *shared.DeployRequest) (srcdir string, replacer string, 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 {
|
||||
func (hc *houstonClient) deploy(req *shared.DeployRequest, cb func(*protos.DeployingProgress)) (err error) {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -334,7 +305,8 @@ func (hc *houstonClient) deploy(req *shared.DeployRequest, cb func(*protos.Deplo
|
||||
h := md5.New()
|
||||
h.Write([]byte(strings.Trim(req.Url, "/")))
|
||||
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{
|
||||
State: "download",
|
||||
Progress: written,
|
||||
@ -360,7 +332,7 @@ func (hc *houstonClient) deploy(req *shared.DeployRequest, cb func(*protos.Deplo
|
||||
err = untar(fname)
|
||||
}
|
||||
|
||||
if err == nil && len(req.Config) > 0 {
|
||||
if err == nil && len(req.Config) > 0 && req.Name != "houston" {
|
||||
// config.json도 다운로드
|
||||
h := md5.New()
|
||||
h.Write([]byte(strings.Trim(req.Config, "/")))
|
||||
|
||||
@ -8,17 +8,18 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Knetic/govaluate"
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
"repositories.action2quare.com/ayo/gocommon/metric"
|
||||
"repositories.action2quare.com/ayo/houston/shared"
|
||||
@ -41,65 +42,43 @@ func lastExecutionArgs(verpath string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
var errUploadZipLogFailed = errors.New("not ok")
|
||||
|
||||
func (hc *houstonClient) uploadZipLogFile(zipFile string, name string, version string) error {
|
||||
zf, err := os.Open(zipFile)
|
||||
if err != nil {
|
||||
return err
|
||||
func (hc *houstonClient) uploadToAppendLog(logFile string, name string, version string) {
|
||||
hc.uploadChan <- uploadRequest{
|
||||
logFile: logFile,
|
||||
name: name,
|
||||
version: version,
|
||||
}
|
||||
|
||||
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) {
|
||||
root := path.Join(storageRoot, req.Name, req.Version)
|
||||
matches, err := filepath.Glob(path.Join(root, req.Filter))
|
||||
func findMatchFiles(storageRoot, name, version, filter string) (string, []string) {
|
||||
root := path.Join(storageRoot, name, version)
|
||||
matches, err := filepath.Glob(path.Join(root, filter))
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", nil
|
||||
}
|
||||
|
||||
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)
|
||||
matches[i] = file
|
||||
if file == root {
|
||||
continue
|
||||
}
|
||||
|
||||
root = path.Join(root, path.Dir(req.Filter))
|
||||
hostname, _ := os.Hostname()
|
||||
zipFileName := path.Join(os.TempDir(), hostname+"_"+path.Base(filepath.ToSlash(matches[0]))) + ".zip"
|
||||
os.Remove(zipFileName)
|
||||
f, err := os.OpenFile(zipFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||
out = append(out, file)
|
||||
}
|
||||
slices.Sort(out)
|
||||
return root, out
|
||||
}
|
||||
|
||||
func zipCompressFiles(root string, matches []string) (string, error) {
|
||||
f, err := os.CreateTemp(os.TempDir(), "*.zip")
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
@ -108,10 +87,6 @@ func zipLogFiles(storageRoot string, req *shared.UploadRequest) (string, []strin
|
||||
|
||||
oldestFile := ""
|
||||
for i, file := range matches {
|
||||
if file == root {
|
||||
continue
|
||||
}
|
||||
|
||||
if fi, err := os.Lstat(file); err == nil {
|
||||
if (fi.Mode() & os.ModeSymlink) == os.ModeSymlink {
|
||||
matches[i] = ""
|
||||
@ -126,21 +101,21 @@ func zipLogFiles(storageRoot string, req *shared.UploadRequest) (string, []strin
|
||||
relative := file[len(root)+1:]
|
||||
fw, err := w.Create(relative)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
src, err := os.Open(file)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return "", err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
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) {
|
||||
@ -148,6 +123,7 @@ func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) (
|
||||
return nil, errors.New("args is empty")
|
||||
}
|
||||
|
||||
foundVersion := req.Version
|
||||
if req.Version == "latest" {
|
||||
entries, err := os.ReadDir(path.Join(storageRoot, req.Name))
|
||||
if err != nil {
|
||||
@ -173,11 +149,11 @@ func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) (
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -212,29 +188,68 @@ func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) (
|
||||
cmd: cmd,
|
||||
name: req.Name,
|
||||
args: req.Args,
|
||||
version: req.Version,
|
||||
version: foundVersion,
|
||||
recover: req.AutoRestart,
|
||||
verpath: verpath,
|
||||
state: int32(protos.ProcessState_Stopped),
|
||||
stdin: stdin,
|
||||
logfile: req.OutputLogFile,
|
||||
keepLatest: req.Version == "latest",
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
func makeLogFilePrefix(meta *procmeta, index int) string {
|
||||
now := time.Now().UTC()
|
||||
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 {
|
||||
func evaluateArgs(args []string, params map[string]any) ([]string, error) {
|
||||
re := regexp.MustCompile(`\$\(\((.*?)\)\)`)
|
||||
|
||||
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 {
|
||||
@ -242,113 +257,101 @@ func (hc *houstonClient) launch(meta *procmeta) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(path.Join(meta.verpath, "logs"), 0775)
|
||||
stderr, err := meta.cmd.StderrPipe()
|
||||
if err != nil {
|
||||
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() {
|
||||
reco := recover()
|
||||
if reco != nil {
|
||||
logger.Println(reco)
|
||||
}
|
||||
r.Close()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
overflow := index / 64
|
||||
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
|
||||
}()
|
||||
localctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
defer r.Close()
|
||||
logChan := make(chan []byte, 1)
|
||||
go logUploader(localctx, logfilePath, logChan)
|
||||
|
||||
reader := bufio.NewReader(r)
|
||||
thisFileSize := 0
|
||||
logFileIndex := 0
|
||||
|
||||
var logWriter func([]byte)
|
||||
if *logger.UseLogFile {
|
||||
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()
|
||||
}
|
||||
}()
|
||||
|
||||
logWriter = func(buff []byte) {
|
||||
for written := 0; written < len(buff); {
|
||||
n, err := targetFile.Write(buff)
|
||||
if err != nil {
|
||||
logger.Println("write log file failed :", logFileName, err)
|
||||
break
|
||||
} else {
|
||||
written += n
|
||||
thisFileSize += n
|
||||
}
|
||||
}
|
||||
|
||||
if thisFileSize > 5*1024*1024 {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logWriter = func(buff []byte) {
|
||||
os.Stdout.Write(buff)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
buff, err := reader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
@ -356,6 +359,28 @@ func (hc *houstonClient) launch(meta *procmeta) error {
|
||||
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 {
|
||||
metricBuffer = append(metricBuffer, buff...)
|
||||
} else if buff[0] == metric.METRIC_HEAD_INLINE {
|
||||
@ -372,7 +397,7 @@ func (hc *houstonClient) launch(meta *procmeta) error {
|
||||
var desc metric.MetricDescription
|
||||
if err := json.Unmarshal(metricBuffer, &desc); err != nil {
|
||||
logger.Println("unmarshal metric failed :", err, string(metricBuffer))
|
||||
continue
|
||||
return false
|
||||
}
|
||||
|
||||
if desc.ConstLabels == nil {
|
||||
@ -383,8 +408,7 @@ func (hc *houstonClient) launch(meta *procmeta) error {
|
||||
desc.ConstLabels[k] = v
|
||||
}
|
||||
|
||||
desc.ConstLabels["job"] = jobName
|
||||
|
||||
desc.ConstLabels["job"] = meta.name
|
||||
metricExporter.RegisterMetric(&desc)
|
||||
} else {
|
||||
key, val := metric.ReadMetricValue(metricBuffer)
|
||||
@ -394,48 +418,36 @@ func (hc *houstonClient) launch(meta *procmeta) error {
|
||||
metricBuffer = metricBuffer[:0]
|
||||
}
|
||||
|
||||
continue
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
logger.Println("stdReader is terminated :", meta.name)
|
||||
|
||||
logWriter(buff)
|
||||
if meta.isState(protos.ProcessState_Running) {
|
||||
// state는 running인데 종료됐으면 exception처리
|
||||
hc.operationChan <- &protos.OperationQueryResponse{
|
||||
Operation: string(shared.Exception),
|
||||
Args: map[string]string{
|
||||
"id": fmt.Sprintf("%d", meta.id),
|
||||
},
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
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)
|
||||
go stdReader(stderr, evalfile+".err", func([]byte) bool { return true })
|
||||
|
||||
logger.Println("startChildProcess :", meta.cmd.Args)
|
||||
meta.cmd.Env = append(os.Environ(), fmt.Sprintf("HOUSTON_SIBLIING_INDEX=%d", index))
|
||||
|
||||
err = meta.cmd.Start()
|
||||
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)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (hc *houstonClient) startChildProcess(req *shared.StartProcessRequest, op protos.OperationClient) error {
|
||||
func (hc *houstonClient) startChildProcess(req *shared.StartProcessRequest) error {
|
||||
meta, err := prepareProcessLaunch(hc.config.StorageRoot, req)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -465,8 +477,6 @@ func (hc *houstonClient) startChildProcess(req *shared.StartProcessRequest, op p
|
||||
}
|
||||
|
||||
hc.childProcs = append(hc.childProcs, meta)
|
||||
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -535,61 +545,3 @@ func (hc *houstonClient) restartChildProcess(req *shared.RestartProcessRequest,
|
||||
|
||||
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 {
|
||||
// 마지막거 빼고 삭제
|
||||
if req.DeleteAfterUploaded == "true" {
|
||||
for i := 0; i < len(srcFiles)-1; i++ {
|
||||
os.Remove(srcFiles[i])
|
||||
}
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
4
go.mod
4
go.mod
@ -3,6 +3,8 @@ module repositories.action2quare.com/ayo/houston
|
||||
go 1.19
|
||||
|
||||
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/prometheus/client_golang v1.17.0
|
||||
github.com/prometheus/common v0.44.0
|
||||
@ -11,7 +13,7 @@ require (
|
||||
golang.org/x/text v0.14.0
|
||||
google.golang.org/grpc v1.60.1
|
||||
google.golang.org/protobuf v1.32.0
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20240708060921-18d284a4ea85
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20240729084947-8e3d6c28f024
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
9
go.sum
9
go.sum
@ -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/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
||||
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/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/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/go.mod h1:FhIc0fLYi7f+lK5maMsesDqwYojIOh3VfRs8EVd5YJQ=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
@ -144,6 +148,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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-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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
@ -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=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20240708060921-18d284a4ea85 h1:+mUz2LcDkv406BsXpGJRCWdeWB2zqMHunkM0sLtRhI4=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20240708060921-18d284a4ea85/go.mod h1:XA8+hQtUNh956T+kAbJKkUtMl5HUWj83knvdBvvPS5s=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20240729084947-8e3d6c28f024 h1:WdvW4BJHoBwXqNsfEgOAZai7L9iHqRCZ7PZL0cwOULE=
|
||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20240729084947-8e3d6c28f024/go.mod h1:XA8+hQtUNh956T+kAbJKkUtMl5HUWj83knvdBvvPS5s=
|
||||
|
||||
37
houston.sh
37
houston.sh
@ -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 $?
|
||||
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"repositories.action2quare.com/ayo/gocommon/flagx"
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
"repositories.action2quare.com/ayo/houston/client"
|
||||
|
||||
"net/http"
|
||||
@ -23,7 +24,11 @@ func main() {
|
||||
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
server := &http.Server{Addr: ":9100", Handler: nil}
|
||||
go server.ListenAndServe()
|
||||
|
||||
go func() {
|
||||
logger.Println("listen /metrics")
|
||||
server.ListenAndServe()
|
||||
}()
|
||||
|
||||
hc.Start()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
|
||||
@ -10,19 +10,7 @@ go mod tidy
|
||||
|
||||
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 houston -Update -DestinationPath houston.zip
|
||||
|
||||
del houston
|
||||
del config.json
|
||||
del replacer
|
||||
|
||||
mv houston.zip ..\houston.zip
|
||||
cd ..
|
||||
|
||||
113
replacer/main.go
113
replacer/main.go
@ -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()
|
||||
}
|
||||
@ -100,6 +100,7 @@ func (h *houstonHandler) UploadDeploySource(w http.ResponseWriter, r *http.Reque
|
||||
// <input type="submit" value="업로드">
|
||||
// </form>
|
||||
file, header, err := r.FormFile("file")
|
||||
|
||||
if err != nil {
|
||||
logger.Println(err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
@ -161,6 +162,7 @@ func (h *houstonHandler) DeleteDeploySource(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
// deploys 폴더는 파일시스템 서비스이므로 다운로드 가능
|
||||
targetpath := path.Join(h.deployPath, name, version)
|
||||
|
||||
if err := os.RemoveAll(targetpath); err != nil {
|
||||
logger.Println("deleteDeploySource failed :", err)
|
||||
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) {
|
||||
logger.Println("findLastestConfigFile :", name)
|
||||
configFiles, err := os.ReadDir(path.Join(h.deployPath, name, "config"))
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
@ -200,12 +201,9 @@ func (h *houstonHandler) findLastestConfigFile(name string) (string, error) {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -264,12 +262,16 @@ func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
configPath, err := h.findLastestConfigFile(name)
|
||||
var configPath string
|
||||
if name != "houston" {
|
||||
// houston은 config를 포함하여 배포
|
||||
configPath, err = h.findLastestConfigFile(name)
|
||||
if err != nil {
|
||||
logger.Println(err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
h.Operation().Deploy(MakeDeployRequest(
|
||||
shared.DeployRequest{
|
||||
@ -424,6 +426,7 @@ func (h *houstonHandler) RestartProcess(w http.ResponseWriter, r *http.Request)
|
||||
pidstr := r.FormValue("pid")
|
||||
target := r.FormValue("target")
|
||||
name := r.FormValue("name")
|
||||
|
||||
if len(target) == 0 || len(pidstr) == 0 || len(name) == 0 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
|
||||
@ -3,7 +3,6 @@ package server
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@ -14,13 +13,13 @@ import (
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"repositories.action2quare.com/ayo/gocommon/flagx"
|
||||
"repositories.action2quare.com/ayo/gocommon"
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
)
|
||||
|
||||
type HoustonServerWithHandler interface {
|
||||
HoustonServer
|
||||
RegisterHandlers(serveMux *http.ServeMux, prefix string) error
|
||||
RegisterHandlers(serveMux gocommon.ServerMuxInterface, prefix string) error
|
||||
}
|
||||
|
||||
type houstonHandler struct {
|
||||
@ -28,9 +27,10 @@ type houstonHandler struct {
|
||||
methods map[string]reflect.Method
|
||||
deployPath string
|
||||
downloadPath string
|
||||
maingateApiToken string
|
||||
}
|
||||
|
||||
func NewHoustonHandler() HoustonServerWithHandler {
|
||||
func NewHoustonHandler(apiToken string) HoustonServerWithHandler {
|
||||
var tmp *houstonHandler
|
||||
|
||||
methods := make(map[string]reflect.Method)
|
||||
@ -42,10 +42,11 @@ func NewHoustonHandler() HoustonServerWithHandler {
|
||||
return &houstonHandler{
|
||||
HoustonServer: NewServer(),
|
||||
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()
|
||||
storagePath := config.StorageRoot
|
||||
h.deployPath = path.Join(storagePath, sub_folder_name_deploys)
|
||||
@ -93,24 +94,27 @@ func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string
|
||||
// config는 접근하기 편하게 단축 경로 제공
|
||||
serveMux.HandleFunc("/config/", func(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Println("config url.path :", r.URL.Path)
|
||||
h := md5.New()
|
||||
h.Write([]byte(r.URL.Path))
|
||||
at := hex.EncodeToString(h.Sum(nil))
|
||||
testhash := md5.New()
|
||||
testhash.Write([]byte(r.URL.Path))
|
||||
at := hex.EncodeToString(testhash.Sum(nil))
|
||||
hash := r.Header.Get("As-X-UrlHash")
|
||||
logger.Println("config at = hash :", at, hash)
|
||||
if at == hash {
|
||||
urlpath := strings.TrimPrefix(r.URL.Path, "/config/")
|
||||
dir := path.Dir(urlpath)
|
||||
file := path.Base(urlpath)
|
||||
dest := fmt.Sprintf("%s/config/%s", dir, file)
|
||||
logger.Println("config dest :", dest)
|
||||
r2 := new(http.Request)
|
||||
*r2 = *r
|
||||
r2.URL = new(url.URL)
|
||||
*r2.URL = *r.URL
|
||||
r2.URL.Path = dest
|
||||
r2.URL.RawPath = dest
|
||||
fsx.ServeHTTP(w, r2)
|
||||
sourceFile := path.Join(h.deployPath, dir, "config", file)
|
||||
logger.Println("config dest :", sourceFile)
|
||||
bt, err := os.ReadFile(sourceFile)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
logger.Println("config file is missing :", sourceFile)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
} else {
|
||||
if _, err = w.Write(bt); err != nil {
|
||||
logger.Println("config write failed :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
@ -123,8 +127,7 @@ func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string
|
||||
defer func() {
|
||||
s := recover()
|
||||
if s != nil {
|
||||
logger.Println(s)
|
||||
debug.PrintStack()
|
||||
logger.Error(s)
|
||||
}
|
||||
io.Copy(io.Discard, r.Body)
|
||||
r.Body.Close()
|
||||
@ -135,10 +138,12 @@ func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string
|
||||
filename := r.Header.Get("Houston-Service-Filename")
|
||||
dir := path.Join(h.downloadPath, name, version)
|
||||
if err := os.MkdirAll(dir, 0775); err == nil {
|
||||
file, _ := os.Create(path.Join(dir, filename))
|
||||
if file != nil {
|
||||
defer file.Close()
|
||||
if _, err = io.Copy(file, r.Body); err != nil {
|
||||
filepath := path.Join(dir, filename)
|
||||
// filepath가 이미 있으면 append
|
||||
localfile, _ := os.OpenFile(filepath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
|
||||
if localfile != nil {
|
||||
defer localfile.Close()
|
||||
if _, err = io.Copy(localfile, r.Body); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
@ -152,8 +157,6 @@ func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string
|
||||
return nil
|
||||
}
|
||||
|
||||
var noauth = flagx.Bool("noauth", false, "")
|
||||
|
||||
func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
s := recover()
|
||||
@ -168,38 +171,6 @@ func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
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
|
||||
if r.Method == "POST" {
|
||||
operation = r.FormValue("operation")
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"net"
|
||||
|
||||
"repositories.action2quare.com/ayo/gocommon"
|
||||
"repositories.action2quare.com/ayo/gocommon/flagx"
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
"repositories.action2quare.com/ayo/houston/shared"
|
||||
"repositories.action2quare.com/ayo/houston/shared/protos"
|
||||
@ -108,11 +109,14 @@ type Operation interface {
|
||||
}
|
||||
|
||||
type outerconfig struct {
|
||||
Houston *struct {
|
||||
Houston struct {
|
||||
Server serverConfig `json:"server"`
|
||||
} `json:"houston"`
|
||||
}
|
||||
|
||||
var storagePath = flagx.String("hs_storage", "", "")
|
||||
var grpcPort = flagx.Int("hs_grpc_port", 0, "")
|
||||
|
||||
func loadServerConfig() serverConfig {
|
||||
var oc outerconfig
|
||||
err := gocommon.LoadConfig[outerconfig](&oc)
|
||||
@ -123,6 +127,15 @@ func loadServerConfig() serverConfig {
|
||||
}
|
||||
}
|
||||
|
||||
if len(*storagePath) > 0 {
|
||||
// override
|
||||
oc.Houston.Server.StorageRoot = *storagePath
|
||||
}
|
||||
|
||||
if *grpcPort != 0 {
|
||||
oc.Houston.Server.GrpcPort = *grpcPort
|
||||
}
|
||||
|
||||
return oc.Houston.Server
|
||||
}
|
||||
|
||||
|
||||
@ -30,6 +30,8 @@ type StartProcessRequest struct {
|
||||
Name string
|
||||
Version string
|
||||
Args []string
|
||||
AutoRestart bool
|
||||
OutputLogFile string
|
||||
}
|
||||
|
||||
type StopProcessRequest struct {
|
||||
|
||||
Reference in New Issue
Block a user