Compare commits

...

125 Commits

Author SHA1 Message Date
31d77efe84 Merge branch 'master' into kd-live 2023-09-05 17:16:43 +09:00
3ace1be27a stdout을 dev/null로 redirect 2023-09-05 17:16:05 +09:00
3fccae1ef5 Merge branch 'master' into kd-live 2023-07-03 14:32:09 +09:00
3dc121bc71 버전에 태그가 일찍 붙는 문제 수정 2023-07-03 14:31:55 +09:00
706339a386 Merge branch 'master' into kd-live 2023-07-03 14:18:20 +09:00
849973c449 서비스 자체 태그도 넘겨줌 2023-07-03 14:18:11 +09:00
759dcaf00b Merge branch 'master' into kd-live 2023-07-03 14:12:35 +09:00
d1a192159b 서비스 태그를 하위 버전에도 붙여줌 2023-07-03 14:12:21 +09:00
db4769687c Merge branch 'master' into kd-live 2023-07-03 14:08:46 +09:00
7a5696961f 버전에 태그를 붙임 2023-07-03 14:08:28 +09:00
7418d847b7 Merge branch 'master' into kd-live 2023-07-03 11:44:00 +09:00
4d9a25dd6b 클라이언트에서는 태그를 없애고 서버에서만 관리 2023-07-03 11:34:21 +09:00
b057730ad5 Merge branch 'master' into kd-live 2023-07-03 10:54:27 +09:00
9bd09509c8 reaonly 대신 tag로 교체 2023-07-03 10:54:13 +09:00
d526a923ca Merge branch 'master' into kd-live 2023-06-30 16:48:51 +09:00
1b57e9f87e 업로드 된 로그 파일은 지우되, 최신도 같이 지워지던 문제 수정 2023-06-30 16:48:13 +09:00
30412bce70 Merge branch 'master' into kd-live 2023-06-30 16:38:13 +09:00
4667d351a8 업로드한 로그 제거 안함 2023-06-30 16:38:01 +09:00
20dc204cb1 Merge branch 'master' into kd-live 2023-06-30 15:51:47 +09:00
571f0d76df 업로드한 로그도 제거하지 말자 2023-06-30 15:51:03 +09:00
025e583f12 Merge branch 'master' into kd-live 2023-06-29 14:13:17 +09:00
15dd1e544c 하위 폴더 복사 누락 수정 2023-06-29 14:13:04 +09:00
b3ed139431 Merge branch 'master' into kd-live 2023-06-29 12:24:22 +09:00
45ab15d345 gracefulstop 말고 바로 stop 2023-06-29 12:24:13 +09:00
c0a0f88220 Merge branch 'master' into kd-live 2023-06-29 12:05:22 +09:00
02db65e06f houston 버전 관리 2023-06-29 12:05:11 +09:00
ed4ca6cfed Merge branch 'master' into kd-live 2023-06-29 11:11:35 +09:00
a8821b694b 버전 파일 경로 오류 수정 2023-06-29 11:11:23 +09:00
7c54fda9a2 Merge branch 'master' into kd-live 2023-06-29 11:00:49 +09:00
5429d3d90f houstonClient도 버전 관리 2023-06-29 11:00:26 +09:00
77174ccef4 Merge branch 'master' into kd-live 2023-06-29 10:20:09 +09:00
cf46888b6a houstonClient 종료시 houstonServer도 종료 2023-06-29 10:19:57 +09:00
642d53fbba Merge branch 'master' into kd-live 2023-06-28 18:29:20 +09:00
6a35d7b1fe 프로세스 구동중인지 검출하는 로직 변경 2023-06-28 18:29:12 +09:00
361ff4bb3a Merge branch 'master' into kd-live 2023-06-28 18:23:08 +09:00
d04dd5b05d 로그 더 추가 2023-06-28 18:22:50 +09:00
942875071f Merge branch 'master' into kd-live 2023-06-28 18:15:25 +09:00
d3442be5dc 로그 추가 2023-06-28 18:15:13 +09:00
bff9548dab Merge branch 'master' into kd-live 2023-06-28 18:01:53 +09:00
06bc095ea4 프로세스 종료 판별 로직 수정 2023-06-28 18:00:51 +09:00
e5b7a7b02b Merge branch 'master' into kd-live 2023-06-27 20:05:32 +09:00
da47d7c587 httpwriter tracker 일단 다시 제거 2023-06-27 20:05:23 +09:00
3f2c82251b Merge branch 'master' into kd-live 2023-06-27 11:55:39 +09:00
d464812cf8 config 다운로드 경로 수정 2023-06-27 11:55:24 +09:00
fdb0a7baa5 Merge branch 'master' into kd-live 2023-06-27 11:17:28 +09:00
fa8b78efed restart 로직 변경 2023-06-27 11:17:16 +09:00
cf033b6107 Merge branch 'master' into kd-live 2023-06-27 11:02:28 +09:00
9b0aa4d640 인덱스 버그 수정 2023-06-27 11:02:19 +09:00
96a0b43e3f Merge branch 'master' into kd-live 2023-06-27 10:53:27 +09:00
cacbc1008a operation argument에 []string 지원 2023-06-27 10:53:14 +09:00
6185d055ef Merge branch 'master' into kd-live 2023-06-27 10:06:42 +09:00
6d319f2fa1 logapicall 플래그 추가 2023-06-27 10:06:26 +09:00
b2aae5a38e Merge branch 'master' into kd-live 2023-06-27 09:45:10 +09:00
01b4782e78 프로세스 재시작 지원 2023-06-27 09:44:56 +09:00
52491aff17 Merge branch 'master' into kd-live 2023-06-27 00:03:55 +09:00
72e94ccfc3 noauth 처리 2023-06-27 00:03:43 +09:00
8a1bcdc840 Merge branch 'master' into kd-live 2023-06-26 23:24:59 +09:00
d17c53c79c 로그 추가 2023-06-26 23:24:49 +09:00
8bb1e4d0a2 Merge branch 'master' into kd-live 2023-06-26 22:38:56 +09:00
9590de2e00 프로세스 종료 대기 로직 수정 2023-06-26 22:38:29 +09:00
d48afc61af Merge branch 'master' into kd-live 2023-06-26 19:20:06 +09:00
58897522a7 config 파일이 없어도 괜춘 2023-06-26 19:19:58 +09:00
1b6c4edaff Merge branch 'master' into kd-live 2023-06-26 11:27:10 +09:00
06b390815c 프로세스 재시작 요청 추가 2023-06-26 11:26:57 +09:00
95bc4d8db3 Merge branch 'master' into kd-live 2023-06-22 20:39:55 +09:00
3d3020f827 폴더 다시 조정 2023-06-22 20:39:38 +09:00
6a316afd90 Merge branch 'master' into kd-live 2023-06-22 20:35:51 +09:00
9947835ba1 deploys, downloads 경로 변경 2023-06-22 20:35:35 +09:00
bbe340f559 kd live는 go 1.18 2023-06-22 20:25:17 +09:00
30ff0a4b27 deploy source에 다운로드 경로를 보내줌 2023-06-22 20:24:59 +09:00
c98023cc8b 서버와 클라이언트 동시에 구동하는 옵션 추가 2023-06-22 17:16:09 +09:00
3278bca32f 하드코딩된 폴더들 const로 뺌 2023-06-22 17:15:56 +09:00
bdc3e19718 flag를 flagx로 교체 2023-06-21 14:35:27 +09:00
61a98247d8 따옴표 문제 수정 2023-06-15 16:25:55 +09:00
3f2ea5cee3 자식 프로세스 비정상종료시 처리 2023-06-15 11:24:02 +09:00
a8548fffe2 cors cjfl 2023-06-14 15:51:54 +09:00
f58db1ec09 json 소문자로 변경 2023-06-14 15:33:44 +09:00
1c1f9f748a 안쓰는 필드 제거 2023-06-14 15:30:01 +09:00
279c9f47f0 client도 storageroot지원 2023-06-14 14:16:47 +09:00
46aedbe767 모듈 업데이트 2023-06-14 01:50:48 +09:00
96ee2a4627 자동 재접속 2023-06-14 01:50:40 +09:00
2e4b7811db replace에 다음 실행 파라미터도 넘길 수 있음 2023-06-14 01:50:25 +09:00
3add5d9355 로거 되돌림 2023-06-14 00:13:51 +09:00
20b2df1fc5 로그 정리 2023-06-13 23:54:32 +09:00
4cec01609a 로거 정리 2023-06-13 23:49:37 +09:00
43da9424f0 다운로드 링크 수정 2023-06-13 23:49:25 +09:00
c4c0f86947 로거 정리 2023-06-13 23:49:14 +09:00
838b3a2194 unzip 버그 수정 - 백슬래시 처리 추가 2023-06-13 23:48:51 +09:00
93c13ba92f houston package 생성 스크립트 추가 2023-06-13 20:13:03 +09:00
ed5b4c06e9 ignore 업데이트 2023-06-13 20:12:37 +09:00
c1847ee3e1 파일에 직접 로깅하는 로거로 교체 2023-06-13 20:03:10 +09:00
d490188bd2 템플릿 변경 2023-06-13 16:27:47 +09:00
ac355d32b4 config 구조 변경 2023-06-13 16:26:00 +09:00
26b12fad72 클라이언트 다운로드 url은 deploys로 시작하는게 맞음 2023-06-13 16:25:32 +09:00
faefd8cfca 폴더 생성 확인 2023-06-13 11:18:22 +09:00
5f1b23ed80 deploy, download 경로 지정 2023-06-13 11:04:30 +09:00
5c00ff73d7 houston server config에 storagepath 추가 2023-06-13 10:10:30 +09:00
b14ad791df config파일을 추가로 배포 2023-06-12 12:28:33 +09:00
471d07a188 config도 폴더로 저장 2023-06-12 11:50:00 +09:00
153a9e50ca config는 특별처리 2023-06-12 11:43:29 +09:00
fa5a612173 Merge branch 'master' of https://repositories.action2quare.com/ayo/houston 2023-06-11 17:14:47 +09:00
6719d9b6c1 monitor func 제거하고 grpc dial을 timeout context로 교체 2023-06-11 17:14:44 +09:00
06c7fbd32d deploys 폴더가 없으면 생성 2023-06-11 16:04:04 +09:00
1d525eb7cf handler 경로 변경 2023-06-10 16:27:46 +09:00
18e7ee0afa 테스트 코드 제거 2023-06-09 16:19:51 +09:00
5326e26a8c houston 자체 업데이트 기능 추가 - replacer 2023-06-09 16:16:26 +09:00
ae783aabaf 포트 설정 버그 수정 2023-06-09 11:56:36 +09:00
1d87ef3501 서버, 클라이언트 config 분리 2023-06-09 11:53:31 +09:00
cff643310b 회수 버그 수정 2023-05-30 16:51:23 +09:00
9b9c3eaa4d 최초 deploy후 arg가 안보이는 문제 수정 2023-05-30 16:29:14 +09:00
2f30d4cbc0 args가 빈 값으로 들어왔을 때 처리 2023-05-30 15:28:18 +09:00
3161afd091 deleteDeploySource, undeploy 추가 2023-05-30 12:12:23 +09:00
18e7cbf75e 경로 문제 수정 2023-05-28 21:21:37 +09:00
49921d44ce 실행중이지 않은 로그도 업로드 2023-05-26 17:51:28 +09:00
10555ba61a download 경로 변경 2023-05-26 17:51:08 +09:00
28d8fc4149 argument에 ./가 계속 붙는 문제 수정 2023-05-26 16:26:40 +09:00
c4c157e2f8 fileserver stripprefix 수정 2023-05-25 11:22:30 +09:00
383f846934 client server 실행인자 추가 2023-05-25 10:59:04 +09:00
53a385d018 go-ayo/common을 gocommon으로 변경 2023-05-24 15:14:04 +09:00
11ea571b6e 바인딩 주소 변경 2023-05-23 16:30:23 +09:00
566a4067ab Merge branch 'master' of https://repositories.action2quare.com/ayo/houston 2023-05-23 13:54:27 +09:00
8289018fd6 houston handler 추가 2023-05-23 13:54:25 +09:00
d1efb92f52 다운로드 링크 수정 2023-05-23 11:11:09 +09:00
38bc72b684 Merge branch 'master' of https://repositories.action2quare.com/ayo/houston 2023-05-23 10:58:28 +09:00
54063a19c4 web api 추가 2023-05-23 10:58:15 +09:00
19 changed files with 1406 additions and 560 deletions

5
.gitignore vendored
View File

@ -1,3 +1,6 @@
go-ayo/
*.log *.log
*.exe *.exe
houston
houston.zip
config.json
.vscode/

View File

@ -2,32 +2,70 @@ package client
import ( import (
"context" "context"
"encoding/json"
"errors"
"fmt"
"io" "io"
"io/fs"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"path" "path"
"path/filepath"
"reflect" "reflect"
"sort" "sort"
"strconv" "strconv"
"strings"
"sync"
"sync/atomic" "sync/atomic"
"syscall"
"time"
"unsafe" "unsafe"
"repositories.action2quare.com/ayo/go-ayo/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"
"time"
"github.com/shirou/gopsutil/v3/cpu"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
sigar "github.com/cloudfoundry/gosigar"
) )
type clientConfig struct {
GrpcAddress string `json:"grpc_server_address"`
HttpAddress string `json:"http_server_address"`
StorageRoot string `json:"storage_path"`
}
func loadClientConfig() (clientConfig, error) {
configFile, err := os.Open("config.json")
if err != nil {
return clientConfig{}, err
}
defer configFile.Close()
var config struct {
Houston *struct {
Client clientConfig `json:"client"`
} `json:"houston"`
}
dec := json.NewDecoder(configFile)
err = dec.Decode(&config)
if err != nil {
return clientConfig{}, err
}
if config.Houston == nil {
return clientConfig{}, errors.New(`"houston" object is missing in config.json`)
}
return config.Houston.Client, nil
}
type HoustonClient interface { type HoustonClient interface {
SetReportMetrics(map[string]float32) SetReportMetrics(map[string]float32)
Shutdown() Shutdown()
Start()
} }
type bufferStack struct { type bufferStack struct {
@ -68,22 +106,24 @@ type procmeta struct {
} }
type houstonClient struct { type houstonClient struct {
client *grpc.ClientConn
childProcs []*procmeta childProcs []*procmeta
extraMetrics unsafe.Pointer // map[string]float32 extraMetrics unsafe.Pointer // map[string]float32
deploys map[string][]*protos.VersionAndArgs deploys map[string][]*protos.VersionAndArgs
shutdownFunc context.CancelFunc shutdownFunc context.CancelFunc
ctx context.Context
operationChan chan *protos.OperationQueryResponse
exitChan chan *exec.Cmd exitChan chan *exec.Cmd
httpAddr string clientChan chan *grpc.ClientConn
timestamp string timestamp string
} wg sync.WaitGroup
config clientConfig
func bToMb(b uint64) uint32 { version string
return uint32(b / 1024 / 1024) standalone bool
} }
func unmarshal[T any](val *T, src map[string]string) { func unmarshal[T any](val *T, src map[string]string) {
argval := reflect.ValueOf(val) argval := reflect.ValueOf(val)
logger.Println("operation receive :", argval.Type().Name(), src)
for i := 0; i < argval.Elem().Type().NumField(); i++ { for i := 0; i < argval.Elem().Type().NumField(); i++ {
if !argval.Elem().Type().Field(i).IsExported() { if !argval.Elem().Type().Field(i).IsExported() {
continue continue
@ -92,18 +132,22 @@ func unmarshal[T any](val *T, src map[string]string) {
if argval.Elem().Field(i).CanInt() { if argval.Elem().Field(i).CanInt() {
num, _ := strconv.ParseInt(arg, 10, 0) num, _ := strconv.ParseInt(arg, 10, 0)
argval.Elem().Field(i).SetInt(num) argval.Elem().Field(i).SetInt(num)
} else if argval.Elem().Field(i).Kind() == reflect.Array || argval.Elem().Field(i).Kind() == reflect.Slice {
conv := strings.Split(arg, "\n")
argval.Elem().Field(i).Set(reflect.ValueOf(conv))
} else { } else {
argval.Elem().Field(i).SetString(arg) argval.Elem().Field(i).SetString(arg)
} }
} }
} }
func gatherDeployedPrograms(name string) []*protos.VersionAndArgs { func gatherDeployedPrograms(storageRoot, name string) []*protos.VersionAndArgs {
var rawvers []*protos.VersionAndArgs var rawvers []*protos.VersionAndArgs
if vers, err := os.ReadDir(path.Join("./", name)); err == nil { targetPath := path.Join(storageRoot, name)
if vers, err := os.ReadDir(targetPath); err == nil {
for _, ver := range vers { for _, ver := range vers {
if ver.IsDir() { if ver.IsDir() {
args := lastExecutionArgs(path.Join(name, ver.Name())) args := lastExecutionArgs(path.Join(targetPath, ver.Name()))
rawvers = append(rawvers, &protos.VersionAndArgs{ rawvers = append(rawvers, &protos.VersionAndArgs{
Version: ver.Name(), Version: ver.Name(),
Args: args, Args: args,
@ -120,8 +164,33 @@ func gatherDeployedPrograms(name string) []*protos.VersionAndArgs {
} }
func (hc *houstonClient) makeOperationQueryRequest() *protos.OperationQueryRequest { func (hc *houstonClient) makeOperationQueryRequest() *protos.OperationQueryRequest {
hn, _ := os.Hostname() var procs []*protos.ProcessDescription
procs := make([]*protos.ProcessDescription, 0, len(hc.childProcs)) var deploys []*protos.DeployedVersions
var selfname string
var selfargs []string
if hc.standalone {
selfname = path.Base(os.Args[0])
selfargs = os.Args[1:]
} else {
selfname = "houston"
selfargs = []string{}
}
procs = append(procs, &protos.ProcessDescription{
Name: selfname,
Args: selfargs,
Version: hc.version,
State: protos.ProcessState_Running,
Pid: int32(os.Getpid()),
})
deploys = append(deploys, &protos.DeployedVersions{
Name: selfname,
Versions: []*protos.VersionAndArgs{
{Version: hc.version, Args: selfargs},
},
})
for _, child := range hc.childProcs { for _, child := range hc.childProcs {
procs = append(procs, &protos.ProcessDescription{ procs = append(procs, &protos.ProcessDescription{
Name: child.name, Name: child.name,
@ -132,7 +201,6 @@ func (hc *houstonClient) makeOperationQueryRequest() *protos.OperationQueryReque
}) })
} }
var deploys []*protos.DeployedVersions
for name, prog := range hc.deploys { for name, prog := range hc.deploys {
deploys = append(deploys, &protos.DeployedVersions{ deploys = append(deploys, &protos.DeployedVersions{
Name: name, Name: name,
@ -140,6 +208,7 @@ func (hc *houstonClient) makeOperationQueryRequest() *protos.OperationQueryReque
}) })
} }
hn, _ := os.Hostname()
return &protos.OperationQueryRequest{ return &protos.OperationQueryRequest{
Hostname: hn, Hostname: hn,
Procs: procs, Procs: procs,
@ -147,12 +216,20 @@ func (hc *houstonClient) makeOperationQueryRequest() *protos.OperationQueryReque
} }
} }
func NewClient(grpcAddr string, httpAddr string) (HoustonClient, error) { func NewClient(standalone bool) (HoustonClient, error) {
client, err := grpc.Dial(grpcAddr, grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials())) clientConfig, err := loadClientConfig()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(clientConfig.GrpcAddress) == 0 {
return nil, errors.New("client.grpc_server_address is missing")
}
if len(clientConfig.HttpAddress) == 0 {
return nil, errors.New("client.http_server_address is missing")
}
exefile, err := os.Executable() exefile, err := os.Executable()
if err != nil { if err != nil {
return nil, err return nil, err
@ -163,73 +240,95 @@ func NewClient(grpcAddr string, httpAddr string) (HoustonClient, error) {
return nil, err return nil, err
} }
sp, err := os.Stat(clientConfig.StorageRoot)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
err = os.MkdirAll(clientConfig.StorageRoot, 0775)
}
} else if !sp.IsDir() {
err = errors.New(clientConfig.StorageRoot + " is not directory")
}
if err != nil {
return nil, err
}
deploys := make(map[string][]*protos.VersionAndArgs) deploys := make(map[string][]*protos.VersionAndArgs)
if dirs, err := os.ReadDir("./"); err == nil { if dirs, err := os.ReadDir(clientConfig.StorageRoot); err == nil {
for _, dir := range dirs { for _, dir := range dirs {
if dir.IsDir() { if dir.IsDir() {
flagf := path.Join(dir.Name(), "@houston") flagf := path.Join(clientConfig.StorageRoot, dir.Name(), "@houston")
if _, err := os.Stat(flagf); !os.IsNotExist(err) { if _, err := os.Stat(flagf); !os.IsNotExist(err) {
deploys[dir.Name()] = gatherDeployedPrograms(dir.Name()) deploys[dir.Name()] = gatherDeployedPrograms(clientConfig.StorageRoot, dir.Name())
} }
} }
} }
} }
ver, _ := os.ReadFile("@version")
if len(ver) == 0 {
ver = []byte("0.0.0")
}
hc := &houstonClient{ hc := &houstonClient{
client: client, config: clientConfig,
clientChan: make(chan *grpc.ClientConn),
extraMetrics: unsafe.Pointer(&map[string]float32{}), extraMetrics: unsafe.Pointer(&map[string]float32{}),
deploys: deploys, deploys: deploys,
httpAddr: httpAddr,
timestamp: exefi.ModTime().String(), timestamp: exefi.ModTime().String(),
version: string(ver),
standalone: standalone,
} }
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
go func() {
// regularly send status
sc := protos.NewMonitorClient(client)
hn, _ := os.Hostname()
mem := sigar.Mem{}
mem.Get()
metrics := &protos.Metrics{
Hostname: hn,
Total: bToMb(mem.Total),
}
for {
select {
case <-ctx.Done():
return
case <-time.After(5 * time.Second):
percent, _ := cpu.Percent(0, false)
metrics.Cpu = float32(percent[0])
metrics.Free = bToMb(mem.ActualFree)
metrics.Metrics = *(*map[string]float32)(atomic.LoadPointer(&hc.extraMetrics))
sc.Report(context.Background(), metrics, grpc.WaitForReady(true))
mem.Get()
}
}
}()
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)
go func() { go func() {
defer hc.wg.Done()
// 메인 operator // 메인 operator
op := protos.NewOperationClient(hc.client) var op protos.OperationClient
myname, _ := os.Executable()
myname = path.Base(filepath.ToSlash(myname))
if len(path.Ext(myname)) > 0 {
myname = myname[:len(myname)-len(path.Ext(myname))]
}
if myname == "__debug_bin" {
myname = "houston"
}
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
case newClient := <-hc.clientChan:
op = protos.NewOperationClient(newClient)
case exited := <-exitChan: case exited := <-exitChan:
var newprocs []*procmeta var newprocs []*procmeta
for _, proc := range hc.childProcs { for _, proc := range hc.childProcs {
if proc.cmd == exited && proc.state != protos.ProcessState_Stopped { if proc.cmd == exited {
proc.state = protos.ProcessState_Stopped if proc.state == protos.ProcessState_Running || proc.state == protos.ProcessState_Restart {
go func(proc *procmeta) {
if err := proc.cmd.Process.Signal(syscall.SIGTERM); err != nil {
proc.cmd.Process.Signal(os.Kill)
}
proc.cmd.Wait()
proc.cmd.Process.Release()
if proc.state == protos.ProcessState_Restart {
hc.startChildProcess(&shared.StartProcessRequest{
Version: proc.version,
Name: proc.name,
Args: proc.cmd.Args,
}, op)
}
}(proc)
}
} else { } else {
newprocs = append(newprocs, proc) newprocs = append(newprocs, proc)
} }
@ -242,22 +341,44 @@ func NewClient(grpcAddr string, httpAddr string) (HoustonClient, error) {
case shared.Deploy: case shared.Deploy:
var dr shared.DeployRequest var dr shared.DeployRequest
unmarshal(&dr, resp.Args) unmarshal(&dr, resp.Args)
err := hc.deploy(&dr) if dr.Name == myname {
if err == nil { if srcdir, replacer, err := hc.prepareUpdateSelf(&dr); err == nil {
prog := gatherDeployedPrograms(dr.Name) 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 {
if err := hc.deploy(&dr); err == nil {
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())
} else { } else {
logger.Println(err) logger.Println(err)
} }
}
case shared.Withdraw: case shared.Withdraw:
var wr shared.WithdrawRequest var wr shared.WithdrawRequest
unmarshal(&wr, resp.Args) unmarshal(&wr, resp.Args)
err := hc.withdraw(&wr) err := hc.withdraw(&wr)
if err == nil { if err == nil {
prog := gatherDeployedPrograms(wr.Name) prog := gatherDeployedPrograms(hc.config.StorageRoot, wr.Name)
if len(prog) == 0 {
delete(hc.deploys, wr.Name)
} else {
hc.deploys[wr.Name] = prog hc.deploys[wr.Name] = prog
}
op.Refresh(ctx, hc.makeOperationQueryRequest()) op.Refresh(ctx, hc.makeOperationQueryRequest())
} else { } else {
logger.Println(err) logger.Println(err)
@ -266,21 +387,21 @@ func NewClient(grpcAddr string, httpAddr string) (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); err != nil { if err := hc.startChildProcess(&sr, op); err != nil {
logger.Println(err) logger.Println(err)
} }
case shared.Stop: case shared.Stop:
var sr shared.StopProcessRequest var sr shared.StopProcessRequest
unmarshal(&sr, resp.Args) unmarshal(&sr, resp.Args)
if err := hc.stopChildProcess(&sr); err != nil { if err := hc.stopChildProcess(&sr, op); err != nil {
logger.Println(err) logger.Println(err)
} }
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); err != nil { if err := hc.restartChildProcess(&rr, op); err != nil {
logger.Println(err) logger.Println(err)
} }
@ -295,48 +416,98 @@ func NewClient(grpcAddr string, httpAddr string) (HoustonClient, error) {
} }
}() }()
go func() { hc.shutdownFunc = cancel
// receive from stream hc.exitChan = exitChan
for { hc.ctx = ctx
select { hc.operationChan = operationChan
case <-ctx.Done():
return
default: return hc, nil
err := hc.checkOperation(operationChan) }
if err != nil {
logger.Println("hc.checkUpdate failed :", err) func (hc *houstonClient) Start() {
// receive from stream
defer func() {
hc.wg.Wait()
for _, proc := range hc.childProcs {
if err := proc.cmd.Process.Signal(syscall.SIGTERM); err != nil {
proc.cmd.Process.Signal(os.Kill)
proc.state = protos.ProcessState_Stopping
} }
} }
for _, proc := range hc.childProcs {
proc.cmd.Wait()
proc.cmd.Process.Release()
} }
}() }()
hc.shutdownFunc = cancel interrupt := make(chan os.Signal, 1)
hc.exitChan = exitChan signal.Notify(interrupt, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
return hc, nil go func() {
c := <-interrupt
logger.Println("interrupt!!!!!!!! :", c.String())
hc.shutdownFunc()
}()
var client *grpc.ClientConn
reconnCount := 0
time.Sleep(time.Second)
for {
select {
case <-hc.ctx.Done():
return
default:
if client == nil {
if reconnCount == 0 {
logger.Println("grpc.DialContext :", hc.config.GrpcAddress)
}
reconnCount++
dialContext, cancelDial := context.WithTimeout(context.Background(), 15*time.Second)
client, _ = grpc.DialContext(dialContext, hc.config.GrpcAddress, grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials()))
cancelDial()
if client != nil {
reconnCount = 0
logger.Println("grpc.DialContext succeeded")
hc.clientChan <- client
}
}
if client != nil {
err := hc.checkOperation(client)
if err != nil {
logger.Println("hc.checkUpdate failed :", err)
client = nil
}
}
}
}
} }
func (hc *houstonClient) Shutdown() { func (hc *houstonClient) Shutdown() {
hc.shutdownFunc() hc.shutdownFunc()
} }
func (hc *houstonClient) checkOperation(opChan chan<- *protos.OperationQueryResponse) error { func (hc *houstonClient) checkOperation(client *grpc.ClientConn) error {
defer func() { defer func() {
r := recover() r := recover()
if r != nil { if r != nil {
logger.Error(r) logger.Println(r)
} }
}() }()
op := protos.NewOperationClient(hc.client) op := protos.NewOperationClient(client)
cl, err := op.Query(context.Background(), grpc.WaitForReady(true)) cl, err := op.Query(hc.ctx, grpc.WaitForReady(true))
if err != nil { if err != nil {
return err return err
} }
err = cl.Send(hc.makeOperationQueryRequest()) err = cl.Send(hc.makeOperationQueryRequest())
if err != nil { if err != nil {
cl.CloseSend() cl.CloseSend()
return err return err
@ -348,7 +519,7 @@ func (hc *houstonClient) checkOperation(opChan chan<- *protos.OperationQueryResp
cl.CloseSend() cl.CloseSend()
return err return err
} }
opChan <- update hc.operationChan <- update
} }
} }

View File

@ -6,33 +6,37 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path" "path"
"path/filepath"
"strings" "strings"
"repositories.action2quare.com/ayo/go-ayo/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"
"golang.org/x/text/encoding/korean" "golang.org/x/text/encoding/korean"
"golang.org/x/text/transform" "golang.org/x/text/transform"
) )
func download(dir string, urlpath string, accessToken string) (string, error) { func download(dir string, urlpath string, accessToken string) (target string, err error) {
logger.Println("start downloading", dir, urlpath)
defer func() {
if err != nil {
logger.Println("downloading failed :", err)
} else {
logger.Println("downloading succeeded")
}
}()
parsed, err := url.Parse(urlpath) parsed, err := url.Parse(urlpath)
if err != nil { if err != nil {
return "", err return "", err
} }
req, _ := http.NewRequest("GET", urlpath, nil) req, _ := http.NewRequest("GET", urlpath, nil)
if len(accessToken) > 0 {
req.Header.Add("Authorization", accessToken)
}
req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.51") req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.51")
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
@ -55,7 +59,7 @@ func download(dir string, urlpath string, accessToken string) (string, error) {
return "", err return "", err
} }
return out.Name(), nil return filepath.ToSlash(out.Name()), nil
} }
func unzip(fname string) error { func unzip(fname string) error {
@ -64,7 +68,10 @@ func unzip(fname string) error {
os.Remove(fname) os.Remove(fname)
return err return err
} }
defer archive.Close() defer func() {
archive.Close()
os.Remove(fname)
}()
verpath := path.Dir(fname) verpath := path.Dir(fname)
for _, f := range archive.File { for _, f := range archive.File {
@ -75,14 +82,16 @@ func unzip(fname string) error {
name = f.Name name = f.Name
} }
name = strings.ReplaceAll(name, `\`, "/")
filePath := path.Join(verpath, name) filePath := path.Join(verpath, name)
if f.FileInfo().IsDir() || strings.HasSuffix(f.FileInfo().Name(), `\`) {
if f.FileInfo().IsDir() { if err = os.MkdirAll(filePath, 0775); err != nil {
os.MkdirAll(filePath, os.ModePerm) return err
}
continue continue
} }
if err := os.MkdirAll(path.Dir(filePath), os.ModePerm); err != nil { if err := os.MkdirAll(path.Dir(filePath), 0775); err != nil {
return err return err
} }
@ -103,6 +112,7 @@ func unzip(fname string) error {
dstFile.Close() dstFile.Close()
fileInArchive.Close() fileInArchive.Close()
} }
return nil return nil
} }
@ -111,7 +121,10 @@ func untar(fname string) error {
if err != nil { if err != nil {
return err return err
} }
defer file.Close() defer func() {
file.Close()
os.Remove(fname)
}()
verpath := path.Dir(fname) verpath := path.Dir(fname)
tarReader := tar.NewReader(file) tarReader := tar.NewReader(file)
@ -126,7 +139,7 @@ func untar(fname string) error {
switch header.Typeflag { switch header.Typeflag {
case tar.TypeDir: case tar.TypeDir:
if err := os.MkdirAll(path.Join(verpath, header.Name), 0755); err != nil { if err := os.MkdirAll(path.Join(verpath, header.Name), 0775); err != nil {
return err return err
} }
case tar.TypeReg: case tar.TypeReg:
@ -143,6 +156,7 @@ func untar(fname string) error {
return errors.New("unknown type") return errors.New("unknown type")
} }
} }
return nil return nil
} }
@ -150,8 +164,9 @@ func (hc *houstonClient) prepareDeploy(name string, version string) (destPath st
// houston관리용임을 표시하기 위해 더미파일 생성 // houston관리용임을 표시하기 위해 더미파일 생성
defer func() { defer func() {
var flagf *os.File var flagf *os.File
if _, err := os.Stat(path.Join(name, "@houston")); os.IsNotExist(err) { markerPath := path.Join(hc.config.StorageRoot, name, "@houston")
flagf, err = os.Create(path.Join(name, "@houston")) if _, err := os.Stat(markerPath); os.IsNotExist(err) {
flagf, err = os.Create(markerPath)
if err != nil { if err != nil {
return return
} }
@ -160,10 +175,10 @@ func (hc *houstonClient) prepareDeploy(name string, version string) (destPath st
} }
}() }()
verpath := path.Join("./", 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, fs.FileMode(os.O_WRONLY)) err = os.MkdirAll(verpath, 0775)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -180,6 +195,93 @@ func (hc *houstonClient) prepareDeploy(name string, version string) (destPath st
return verpath, nil return verpath, nil
} }
func (hc *houstonClient) makeDownloadUrl(rel string) string {
out := rel
if !strings.HasPrefix(out, "http") {
tks := strings.SplitN(hc.config.HttpAddress, "://", 2)
out = fmt.Sprintf("%s://%s", tks[0], path.Join(tks[1], rel))
}
return out
}
func copy(src, dst string) error {
fi, err := os.Stat(src)
if err != nil {
return err
}
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
}
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)
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 = copy(srcreplacer, replacer)
if err == nil {
err = os.Chmod(replacer, 0775)
}
// replacer먼저 가져옴
return filepath.ToSlash(tempdir), replacer, err
}
func (hc *houstonClient) deploy(req *shared.DeployRequest) error { func (hc *houstonClient) deploy(req *shared.DeployRequest) error {
logger.Println("start deploying") logger.Println("start deploying")
root, err := hc.prepareDeploy(req.Name, req.Version) root, err := hc.prepareDeploy(req.Name, req.Version)
@ -187,14 +289,8 @@ func (hc *houstonClient) deploy(req *shared.DeployRequest) error {
return err return err
} }
if !strings.HasPrefix(req.Url, "http") {
tks := strings.SplitN(hc.httpAddr, "://", 2)
req.Url = fmt.Sprintf("%s://%s", tks[0], path.Join(tks[1], req.Url))
}
logger.Println("start downloading", req.Url)
// verpath에 배포 시작 // verpath에 배포 시작
fname, err := download(root, req.Url, req.AccessToken) fname, err := download(root, hc.makeDownloadUrl(req.Url), req.AccessToken)
if err != nil { if err != nil {
return err return err
} }
@ -206,23 +302,27 @@ func (hc *houstonClient) deploy(req *shared.DeployRequest) error {
err = untar(fname) err = untar(fname)
} }
if err == nil && len(req.Config) > 0 {
// config.json도 다운로드
_, err = download(root, hc.makeDownloadUrl(req.Config), req.AccessToken)
}
return err return err
} }
func (hc *houstonClient) withdraw(req *shared.WithdrawRequest) error { func (hc *houstonClient) withdraw(req *shared.WithdrawRequest) error {
fd, _ := os.Stat(path.Join("./", req.Name, req.Version)) targetPath := path.Join(hc.config.StorageRoot, req.Name, req.Version)
fd, _ := os.Stat(targetPath)
if fd != nil { if fd != nil {
if fd.IsDir() { if fd.IsDir() {
for _, running := range hc.childProcs { for _, running := range hc.childProcs {
if running.name == req.Name && running.version == req.Version { if running.name == req.Name && (len(req.Version) == 0 || running.version == req.Version) {
// 회수하려는 버전이 돌고 있다 // 회수하려는 버전이 돌고 있다
if running.state != protos.ProcessState_Stopped {
return fmt.Errorf("withdraw failed. %s@%s is still running", req.Name, req.Version) return fmt.Errorf("withdraw failed. %s@%s is still running", req.Name, req.Version)
} }
} }
}
return os.RemoveAll(path.Join("./", req.Name, req.Version)) return os.RemoveAll(targetPath)
} }
} }

View File

@ -12,13 +12,12 @@ import (
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"regexp"
"runtime/debug" "runtime/debug"
"strings"
"syscall" "syscall"
"time" "time"
"repositories.action2quare.com/ayo/go-ayo/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"
) )
@ -39,8 +38,43 @@ func lastExecutionArgs(verpath string) []string {
return out return out
} }
func (meta *procmeta) zipLogFiles(req *shared.UploadRequest, start, except string) (string, []string, error) { var errUploadZipLogFailed = errors.New("not ok")
root := path.Join(req.Name, req.Version)
func (hc *houstonClient) uploadZipLogFile(zipFile string, name string, version string) error {
zf, err := os.Open(zipFile)
if err != nil {
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(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, start, except string) (string, []string, error) {
root := path.Join(storageRoot, req.Name, req.Version)
matches, err := filepath.Glob(path.Join(root, req.Filter)) matches, err := filepath.Glob(path.Join(root, req.Filter))
if err != nil { if err != nil {
return "", nil, err return "", nil, err
@ -50,9 +84,15 @@ func (meta *procmeta) zipLogFiles(req *shared.UploadRequest, start, except strin
return "", nil, nil return "", nil, nil
} }
for i, file := range matches {
file = filepath.ToSlash(file)
matches[i] = file
}
root = path.Join(root, path.Dir(req.Filter)) root = path.Join(root, path.Dir(req.Filter))
// Create a file to write the archive to. zipFileName := path.Join(os.TempDir(), path.Base(matches[0])) + ".zip"
f, err := os.CreateTemp("", "") f, err := os.OpenFile(zipFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
@ -63,12 +103,11 @@ func (meta *procmeta) zipLogFiles(req *shared.UploadRequest, start, except strin
oldestFile := "" oldestFile := ""
for i, file := range matches { for i, file := range matches {
file = filepath.ToSlash(file)
matches[i] = file
if file == root { if file == root {
continue continue
} }
if file >= except { if len(except) > 0 && file >= except {
matches = matches[:i]
break break
} }
if len(start) > 0 && file < start { if len(start) > 0 && file < start {
@ -81,19 +120,19 @@ func (meta *procmeta) zipLogFiles(req *shared.UploadRequest, start, except 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 {
logger.Error(err) logger.Println(err)
return "", nil, err return "", nil, err
} }
src, err := os.Open(file) src, err := os.Open(file)
if err != nil { if err != nil {
logger.Error(err) logger.Println(err)
return "", nil, err return "", nil, err
} }
defer src.Close() defer src.Close()
if _, err = io.Copy(fw, src); err != nil { if _, err = io.Copy(fw, src); err != nil {
logger.Error(err) logger.Println(err)
return "", nil, err return "", nil, err
} }
} }
@ -125,15 +164,18 @@ func (meta *procmeta) zipLogFiles(req *shared.UploadRequest, start, except strin
//return nil //return nil
} }
func prepareProcessLaunch(req *shared.StartProcessRequest) *procmeta { func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) *procmeta {
re := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`) if len(req.Args) == 0 {
args := re.FindAllString(req.Args, -1) return nil
}
verpath := path.Join("./", req.Name, req.Version) verpath := path.Join(storageRoot, req.Name, req.Version)
fi, err := os.Stat(verpath) fi, err := os.Stat(verpath)
if err == nil && fi.IsDir() { if err == nil && fi.IsDir() {
cmd := exec.Command("./"+args[0], args[1:]...) req.Args[0] = "./" + path.Clean(strings.TrimPrefix(req.Args[0], "/"))
os.Chmod(path.Join(verpath, req.Args[0]), 0777)
cmd := exec.Command(req.Args[0], req.Args[1:]...)
cmd.Dir = verpath cmd.Dir = verpath
stdin, _ := cmd.StdinPipe() stdin, _ := cmd.StdinPipe()
@ -160,7 +202,7 @@ func (hc *houstonClient) launch(meta *procmeta) error {
return err return err
} }
err = os.MkdirAll(path.Join(meta.cmd.Dir, "logs"), os.ModePerm) err = os.MkdirAll(path.Join(meta.cmd.Dir, "logs"), 0775)
if err != nil { if err != nil {
return err return err
} }
@ -213,28 +255,17 @@ func (hc *houstonClient) launch(meta *procmeta) error {
startFile := uploadStartFile startFile := uploadStartFile
uploadStartFile = nextFile uploadStartFile = nextFile
go func(startFile, nextFile string) { go func(startFile, nextFile string) {
zipFile, srcFiles, err := meta.zipLogFiles(req, startFile, nextFile) zipFile, srcFiles, err := zipLogFiles(hc.config.StorageRoot, req, startFile, nextFile)
if err == nil && len(zipFile) > 0 && len(srcFiles) > 0 { if err == nil && len(zipFile) > 0 && len(srcFiles) > 0 {
zf, _ := os.Open(zipFile) if err = hc.uploadZipLogFile(zipFile, meta.name, meta.version); err == nil {
if zf != nil {
req, err := http.NewRequest("POST", hc.httpAddr+"/upload", zf)
if err != nil {
logger.Error(err)
}
req.Header.Set("Houston-Service-Name", meta.name)
req.Header.Set("Houston-Service-Version", meta.version)
req.Header.Set("Houston-Service-Filename", path.Base(srcFiles[0])+".zip")
req.Header.Set("Content-Type", "application/zip")
resp, err := http.DefaultClient.Do(req)
if err == nil {
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
for _, oldf := range srcFiles { for _, oldf := range srcFiles {
os.Remove(oldf) os.Remove(oldf)
} }
} else {
logger.Println("uploadZipLogFile failed :", err)
} }
} } else if err != nil {
} logger.Println("zipLogFiles failed :", err)
} }
}(startFile, nextFile) }(startFile, nextFile)
@ -243,9 +274,10 @@ func (hc *houstonClient) launch(meta *procmeta) error {
return return
} }
logfile.Write(bt.buf[:bt.size]) logfile.Write(bt.buf[:bt.size])
logfile.Sync()
meta.buffers.push(bt.buf) meta.buffers.push(bt.buf)
thisFileSize += bt.size thisFileSize += bt.size
if thisFileSize > 1024*1024 { if thisFileSize > 10*1024*1024 {
switchToNextFile() switchToNextFile()
} }
} }
@ -287,11 +319,13 @@ func (hc *houstonClient) launch(meta *procmeta) error {
return err return err
} }
func (hc *houstonClient) startChildProcess(req *shared.StartProcessRequest) error { var errPrepareprocessLaunchFailed = errors.New("prepareProcessLaunch failed")
func (hc *houstonClient) startChildProcess(req *shared.StartProcessRequest, op protos.OperationClient) error {
logger.Println("startChildProcess :", *req) logger.Println("startChildProcess :", *req)
if req.Version == "latest" { if req.Version == "latest" {
// 최신 버전을 찾음 // 최신 버전을 찾음
latest, err := shared.FindLastestVersion(path.Join("./", req.Name)) latest, err := shared.FindLastestVersion(hc.config.StorageRoot, req.Name)
if err != nil { if err != nil {
return err return err
} }
@ -299,26 +333,34 @@ func (hc *houstonClient) startChildProcess(req *shared.StartProcessRequest) erro
req.Version = latest req.Version = latest
} }
meta := prepareProcessLaunch(req) meta := prepareProcessLaunch(hc.config.StorageRoot, req)
if meta == nil {
return errPrepareprocessLaunchFailed
}
if err := hc.launch(meta); err != nil { if err := hc.launch(meta); err != nil {
return err return err
} }
// launch가 성공하면 args 저장. this and parent folder // launch가 성공하면 args 저장. this and parent folder
if argfile, err := os.Create(path.Join(req.Name, "@args")); err == nil { vers := hc.deploys[req.Name]
for _, ver := range vers {
if ver.Version == req.Version {
ver.Args = meta.cmd.Args
}
}
if argfile, err := os.Create(path.Join(hc.config.StorageRoot, req.Name, "@args")); err == nil {
enc := json.NewEncoder(argfile) enc := json.NewEncoder(argfile)
enc.Encode(meta.cmd.Args) enc.Encode(meta.cmd.Args)
argfile.Close() argfile.Close()
} }
if argfile, err := os.Create(path.Join(req.Name, req.Version, "@args")); err == nil { if argfile, err := os.Create(path.Join(hc.config.StorageRoot, req.Name, req.Version, "@args")); err == nil {
enc := json.NewEncoder(argfile) enc := json.NewEncoder(argfile)
enc.Encode(meta.cmd.Args) enc.Encode(meta.cmd.Args)
argfile.Close() argfile.Close()
} }
hc.childProcs = append(hc.childProcs, meta) hc.childProcs = append(hc.childProcs, meta)
op := protos.NewOperationClient(hc.client)
op.Refresh(context.Background(), hc.makeOperationQueryRequest()) op.Refresh(context.Background(), hc.makeOperationQueryRequest())
return nil return nil
@ -326,10 +368,10 @@ func (hc *houstonClient) startChildProcess(req *shared.StartProcessRequest) erro
var errNoRunningProcess = errors.New("no running processed") var errNoRunningProcess = errors.New("no running processed")
func (hc *houstonClient) stopChildProcess(req *shared.StopProcessRequest) error { func (hc *houstonClient) stopChildProcess(req *shared.StopProcessRequest, op protos.OperationClient) error {
if req.Version == "latest" { if req.Version == "latest" {
// 최신 버전을 찾음 // 최신 버전을 찾음
latest, err := shared.FindLastestVersion(path.Join("./", req.Name)) latest, err := shared.FindLastestVersion(hc.config.StorageRoot, req.Name)
if err != nil { if err != nil {
return err return err
} }
@ -370,17 +412,17 @@ func (hc *houstonClient) stopChildProcess(req *shared.StopProcessRequest) error
if len(killing) > 0 { if len(killing) > 0 {
for _, proc := range killing { for _, proc := range killing {
proc.state = protos.ProcessState_Stopping
if err := proc.cmd.Process.Signal(syscall.SIGTERM); err != nil { if err := proc.cmd.Process.Signal(syscall.SIGTERM); err != nil {
proc.cmd.Process.Signal(os.Kill) proc.cmd.Process.Signal(os.Kill)
proc.state = protos.ProcessState_Stopping
} }
} }
op := protos.NewOperationClient(hc.client)
op.Refresh(context.Background(), hc.makeOperationQueryRequest()) op.Refresh(context.Background(), hc.makeOperationQueryRequest())
for _, proc := range killing { for _, proc := range killing {
proc.cmd.Wait() proc.cmd.Wait()
proc.cmd.Process.Release()
} }
hc.childProcs = remains hc.childProcs = remains
@ -393,54 +435,24 @@ func (hc *houstonClient) stopChildProcess(req *shared.StopProcessRequest) error
return errNoRunningProcess return errNoRunningProcess
} }
func (hc *houstonClient) restartChildProcess(req *shared.RestartProcessRequest) error { func (hc *houstonClient) restartChildProcess(req *shared.RestartProcessRequest, op protos.OperationClient) error {
if req.Version == "latest" {
// 최신 버전을 찾음
latest, err := shared.FindLastestVersion(path.Join("./", req.Name))
if err != nil {
return err
}
req.Version = latest
}
var restarts []*procmeta
for _, proc := range hc.childProcs { for _, proc := range hc.childProcs {
if proc.name == req.Name { if proc.cmd.Process.Pid == int(req.Pid) {
if len(req.Version) == 0 { if len(req.Config) > 0 {
restarts = append(restarts, proc) // config.json를 먼저 다운로드 시도
} else if req.Version == proc.version { root := proc.cmd.Dir
restarts = append(restarts, proc) if _, err := download(root, hc.makeDownloadUrl(req.Config), ""); err != nil {
}
}
}
if len(restarts) == 0 {
return errNoRunningProcess
}
for _, proc := range restarts {
if err := proc.cmd.Process.Signal(syscall.SIGTERM); err != nil {
proc.cmd.Process.Signal(os.Kill)
}
proc.state = protos.ProcessState_Stopping
}
op := protos.NewOperationClient(hc.client)
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
for _, proc := range restarts {
proc.cmd.Wait()
proc.state = protos.ProcessState_Stopped
}
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
for _, proc := range restarts {
if err := hc.launch(proc); err != nil {
return err return err
} }
} }
proc.state = protos.ProcessState_Restart
op.Refresh(context.Background(), hc.makeOperationQueryRequest()) op.Refresh(context.Background(), hc.makeOperationQueryRequest())
hc.exitChan <- proc.cmd
break
}
}
return nil return nil
} }
@ -448,7 +460,7 @@ func (hc *houstonClient) restartChildProcess(req *shared.RestartProcessRequest)
func (hc *houstonClient) uploadFiles(req *shared.UploadRequest) error { func (hc *houstonClient) uploadFiles(req *shared.UploadRequest) error {
if req.Version == "latest" { if req.Version == "latest" {
// 최신 버전을 찾음 // 최신 버전을 찾음
latest, err := shared.FindLastestVersion(path.Join("./", req.Name)) latest, err := shared.FindLastestVersion(hc.config.StorageRoot, req.Name)
if err != nil { if err != nil {
return err return err
} }
@ -456,14 +468,25 @@ func (hc *houstonClient) uploadFiles(req *shared.UploadRequest) error {
req.Version = latest req.Version = latest
} }
logger.Println("uploadFiles req :", *req)
for _, child := range hc.childProcs { for _, child := range hc.childProcs {
if child.version == req.Version && child.name == req.Name { if child.version == req.Version && child.name == req.Name {
logger.Println("uploadFiles found :", child.version, child.name)
child.logUploadChan <- req child.logUploadChan <- req
break return nil
} }
} }
// TODO : 실행 중이 아닌 폴더에서도 대상을 찾는다 // 실행 중이 아닌 폴더에서도 대상을 찾는다
// deploys // 전체 파일을 대상으로
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 {
logger.Println("uploadZipLogFile failed :", err)
}
} else if err != nil {
logger.Println("zipLogFiles failed :", err)
}
return nil return nil
} }

View File

@ -1 +1,12 @@
{} {
"houston" : {
"client" : {
"grpc_server_address" : "10.5.10.100:8080",
"http_server_address" : "https://kdcc.action2quare.com/houston"
},
"server" : {
"grpc_port" : 8080,
"storage_path" : "/data"
}
}
}

25
go.mod
View File

@ -1,28 +1,17 @@
module repositories.action2quare.com/ayo/houston module repositories.action2quare.com/ayo/houston
go 1.19 go 1.18
require ( require (
github.com/cloudfoundry/gosigar v1.3.12 golang.org/x/text v0.10.0
github.com/shirou/gopsutil/v3 v3.23.4 google.golang.org/grpc v1.56.0
golang.org/x/text v0.9.0
google.golang.org/grpc v1.55.0
google.golang.org/protobuf v1.30.0 google.golang.org/protobuf v1.30.0
repositories.action2quare.com/ayo/go-ayo v0.0.0-20230521165809-09cee56768c5 repositories.action2quare.com/ayo/gocommon v0.0.0-20230621052811-06ef97f11d22
) )
require ( require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect golang.org/x/net v0.11.0 // indirect
github.com/onsi/gomega v1.18.1 // indirect golang.org/x/sys v0.9.0 // indirect
github.com/pkg/errors v0.9.1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/shoenig/go-m1cpu v0.1.5 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
) )

154
go.sum
View File

@ -1,152 +1,22 @@
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudfoundry/gosigar v1.3.12 h1:kUvHg+O7DcYKpMSQEJFpNf9kJXLwATBJh5gBwcRxGqI=
github.com/cloudfoundry/gosigar v1.3.12/go.mod h1:OKmnKziBrg7zYdKJQ4fDW+B6Aqh/1Vy1StnjqiPEI3I=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/shirou/gopsutil/v3 v3.23.4 h1:hZwmDxZs7Ewt75DV81r4pFMqbq+di2cbt9FsQBqLD2o=
github.com/shirou/gopsutil/v3 v3.23.4/go.mod h1:ZcGxyfzAMRevhUR2+cfhXDH6gQdFYE/t8j1nsU4mPI8=
github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.56.0 h1:+y7Bs8rtMd07LeXmL3NxcTLn7mUkbKZqEpPhMNkwJEE=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= google.golang.org/grpc v1.56.0/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
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.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= repositories.action2quare.com/ayo/gocommon v0.0.0-20230621052811-06ef97f11d22 h1:DImSGNxZrc+Q4WlS1OKMsLAScEfDYLX4XMJdjAaVnXc=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= repositories.action2quare.com/ayo/gocommon v0.0.0-20230621052811-06ef97f11d22/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
repositories.action2quare.com/ayo/go-ayo v0.0.0-20230521165809-09cee56768c5 h1:EobNJQwZ/TyRRtXY9XhJslN80V+sKoAztsda3x+ISE0=
repositories.action2quare.com/ayo/go-ayo v0.0.0-20230521165809-09cee56768c5/go.mod h1:3gLLbyiq4cOEraSiY6wpYWdcWRPcQKIcs0TfQ/Vwhoc=

4
houston.sh Normal file
View File

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

View File

@ -1,20 +1,16 @@
package houston_test package main
import ( import (
"fmt"
"path"
"testing" "testing"
"time"
"repositories.action2quare.com/ayo/houston/client"
) )
func TestOperationServer(t *testing.T) { func TestOperationServer(t *testing.T) {
hc, _ := client.NewClient("localhost:8080", "http://localhost/commandcenter") fmt.Println(path.Join("a", "b", "/"))
for i := 0; ; i++ { fmt.Println(path.Join("a", "b/"))
hc.SetReportMetrics(map[string]float32{ fmt.Println(path.Join("a", "b/", "/"))
"count": float32(i), fmt.Println(path.Join("a", "b/", "/", "/"))
})
time.Sleep(1300 * time.Millisecond)
}
// token, _ := getMicrosoftAuthoizationToken("30330e18-f407-4e35-a6d6-b734b9fe9ee9", "VTr8Q~VBAUAOSmFiHM~bjgszYXBm9nuGBQCk8cLq") // token, _ := getMicrosoftAuthoizationToken("30330e18-f407-4e35-a6d6-b734b9fe9ee9", "VTr8Q~VBAUAOSmFiHM~bjgszYXBm9nuGBQCk8cLq")
//go func() { //go func() {

108
main.go Normal file
View File

@ -0,0 +1,108 @@
package main
import (
"repositories.action2quare.com/ayo/gocommon/flagx"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/houston/client"
"repositories.action2quare.com/ayo/houston/server"
)
var runAsClient = flagx.Bool("client", false, "")
var runAsServer = flagx.Bool("server", false, "")
func main() {
flagx.Parse()
if !*runAsClient && !*runAsServer {
logger.Fatal("client or server flag is needed")
return
}
if *runAsClient {
hc, err := client.NewClient(true)
if err != nil {
logger.Fatal(err)
return
}
hc.Start()
} else if *runAsServer {
svr := server.NewServer()
svr.Start()
}
}
// func TestOperationServer(t *testing.T) {
// hc, err := client.NewClient("192.168.9.32:8080", "http://192.168.9.32/commandcenter")
// if err != nil {
// t.Error(err)
// return
// }
// for i := 0; ; i++ {
// hc.SetReportMetrics(map[string]float32{
// "count": float32(i),
// })
// time.Sleep(1300 * time.Millisecond)
// }
// // token, _ := getMicrosoftAuthoizationToken("30330e18-f407-4e35-a6d6-b734b9fe9ee9", "VTr8Q~VBAUAOSmFiHM~bjgszYXBm9nuGBQCk8cLq")
// //go func() {
// //time.Sleep(2 * time.Second)
// // testver := fmt.Sprintf("%d.%d.%d", time.Now().Hour(), time.Now().Minute(), time.Now().Second())
// // svr.Operation().Deploy(server.MakeDeployRequest(
// // common.DeployRequest{
// // Name: "warehouse",
// // Version: testver,
// // Url: "https://actionsquare.s3.ap-northeast-2.amazonaws.com/warehouse.zip?response-content-disposition=inline&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEK7%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDmFwLW5vcnRoZWFzdC0yIkcwRQIgeYQKZXvVQsYEZNoWzxSRVjsKHzhq5VhIHVIaLpsUpssCIQCeZn8tfVM9jIjiKp62RPwEnb9oGR8T7apbsnqnntNlJCqGAwiH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8BEAIaDDU0OTY2MjkyMDczOCIMeHddxdoH6Xfz68ZqKtoCwVyCYH45tC7aDBpkl%2FsGRPYlhUVy84h%2FVQx4Bu8hvgu3Y3fYSceAFgFWv%2FE3HpvrHD8AY42UsaHPBCd7tmlyydqnPoOr%2F5rjUCAmHXziGV7oAcO3HIbobbjO1rf3W2tQf7FSGbfPyxFdRhoObRz3sQi%2FcmYLKZWPS9UZRuWOSh2J3HHOoEdAIDq38eYxtVl1OEKxPIjfeJHTzmOOmvoOFBOzrY9HJyABcYxvmtOUvR6469Qf5r%2FTe%2BvuL1NQsYyBKwukcSxHcGbg7t%2BNeDTE%2FUS9lL7VYMEZlhfA1WSADbvAcYEu7cv7MENJ44XmAEHnC6zWIvDNqwK9FCfJrpALIJhbXqv%2FU%2Ft%2B5udZT1TXDDqp1se%2FBRLg8NyplcN4E8z6Qt%2F9pNSm1flhORHJsaPzk2ZfGeqvFvZGv1oBigwA6eJ3WCNl2hHhLkiSBg%2BvFwXA1KxxH9U8Nkl7EjDp7JmhBjqzAqPqVamph2PzNkEszr52GH69m90pjYkNTLM4nwMuGdo1f5%2BOm%2FVloBjBCh6OpTSK3XH67zEMZE0tFQ7qmqu2d69EY8Frt749G3RSNPeKptuIKxhBYF692an9nYUXiVH8OJkey0LDMbwWDaVfSZyOiYr%2FmeiVK0eRdK3C0JGwP%2BT6vUHBL1Agi5MH0dKvmlHwzvl%2BuqArgw7ZdOx%2BJsFHRD%2FqA87B5qPuvxPXkAO5qgwZfUW9MAxdh5hxcc9kNfmryYuVWD1DM%2BvRsRF2TsUqeffucajpQ7lhvN6rspDPMltD3VHFX82Hv12nqU7pHwtNLSO0D43W4JCmOJA8TFqhCkY4zCFDok0lx3x6b8w%2F4GptjvCo1c4HG9LAurTNK8HOb3XkYdmPwKOHaqMNajMsKZoohb0%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230331T060558Z&X-Amz-SignedHeaders=host&X-Amz-Expires=43199&X-Amz-Credential=ASIAX76TWSAROTUEDRGM%2F20230331%2Fap-northeast-2%2Fs3%2Faws4_request&X-Amz-Signature=aa6cc8aac808a066ea0c25e57b3a220cb6b2eb6118f6fb28974cb6e3c34e59d0",
// // // AccessToken: token,
// // },
// // []string{"mountain"},
// // ))
// // time.Sleep(2 * time.Second)
// // svr.Operation().Start(server.MakeStartRequest(
// // common.StartRequest{
// // Name: "warehouse",
// // Version: "latest",
// // Args: "biglocal.exe -port=8090 -dev",
// // },
// // []string{"mountain"},
// // ))
// // time.Sleep(25 * time.Second)
// // svr.Operation().Restart(server.MakeRestartRequest(
// // common.RestartRequest{
// // Name: "warehouse",
// // Version: "latest",
// // },
// // []string{"mountain"},
// // ))
// // time.Sleep(5 * time.Second)
// // svr.Operation().Stop(server.MakeStopRequest(
// // common.StopRequest{
// // Name: "warehouse",
// // Version: "latest",
// // Pid: 0,
// // },
// // []string{"mountain"},
// // ))
// // svr.Operation().Upload(server.MakeUploadRequest(
// // common.UploadRequest{
// // Name: "warehouse",
// // Version: "latest",
// // Url: "http://localhost",
// // Filter: "logs/*.log",
// // },
// // []string{"mountain"},
// // ))
// // time.Sleep(5 * time.Second)
// // svr.Operation().Withdraw(server.MakeWithdrawRequest(
// // common.WithdrawRequest{
// // Name: "warehouse",
// // Version: testver,
// // },
// // nil,
// // ))
// //}()
// }

24
make_houston_package.ps1 Normal file
View File

@ -0,0 +1,24 @@
# $ErrorActionPreference = 'SilentlyContinue'
del houston.zip
$Env:GOOS="linux"
$Env:GOARCH="amd64"
go build -ldflags="-s -w" .
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 ..

View File

@ -27,7 +27,8 @@ enum ProcessState {
Stopped = 0; Stopped = 0;
Stopping = 1; Stopping = 1;
Running = 2; Running = 2;
Error = 3; Restart = 3;
Error = 4;
} }
message ProcessDescription { message ProcessDescription {
@ -36,8 +37,6 @@ message ProcessDescription {
string version = 3; string version = 3;
ProcessState state = 4; ProcessState state = 4;
int32 pid = 5; int32 pid = 5;
int32 stdout_size = 6;
int32 stderr_size = 7;
} }
message OperationQueryResponse { message OperationQueryResponse {

113
replacer/main.go Normal file
View File

@ -0,0 +1,113 @@
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

@ -2,39 +2,82 @@ package server
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt"
"io" "io"
"io/fs"
"net/http" "net/http"
"os" "os"
"path" "path"
"regexp"
"strconv" "strconv"
"strings"
"time" "time"
"repositories.action2quare.com/ayo/go-ayo/logger" "repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/houston/shared" "repositories.action2quare.com/ayo/houston/shared"
) )
/*
*
현재 접속 중인 Agent 목록을 보여줍니다.
- http method : GET
*/
const (
sub_folder_name_deploys = string("_deploys")
sub_folder_name_downloads = string("_downloads")
)
func (h *houstonHandler) GetAgents(w http.ResponseWriter, r *http.Request) { func (h *houstonHandler) GetAgents(w http.ResponseWriter, r *http.Request) {
enc := json.NewEncoder(w) enc := json.NewEncoder(w)
enc.Encode(h.Operation().Hosts()) allHosts := h.Operation().Hosts()
enc.Encode(allHosts)
}
func readTagsFromFile(paths ...string) string {
raw, _ := os.ReadFile(path.Join(paths...))
if len(raw) > 0 {
tag := string(raw)
return strings.Trim(tag, "\n")
}
return ""
} }
func (h *houstonHandler) GetDeploySources(w http.ResponseWriter, r *http.Request) { func (h *houstonHandler) GetDeploySources(w http.ResponseWriter, r *http.Request) {
files, err := os.ReadDir("deploys") files, err := os.ReadDir(h.deployPath)
if err != nil { if err != nil {
logger.Error(err) logger.Println(err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
getVersions := func(name string) []string { getVersions := func(name string) []string {
var out []string vers, _ := os.ReadDir(path.Join(h.deployPath, name))
files, _ := os.ReadDir(path.Join("deploys", name)) mytags := readTagsFromFile(h.deployPath, name, "@tags")
for _, fd := range files { out := []string{
mytags,
}
for _, fd := range vers {
if fd.IsDir() { if fd.IsDir() {
out = append(out, fd.Name()) ver := fd.Name()
files, _ := os.ReadDir(path.Join(h.deployPath, name, ver))
vertags := readTagsFromFile(h.deployPath, name, ver, "@tags")
if len(files) > 0 {
for _, file := range files {
if strings.HasPrefix(file.Name(), "@") {
continue
}
downloadpath := path.Join(sub_folder_name_deploys, name, ver, file.Name())
ver = fmt.Sprintf("%s:%s", ver+mytags+vertags, downloadpath)
break
} }
} }
out = append(out, ver)
}
}
return out return out
} }
@ -49,7 +92,7 @@ func (h *houstonHandler) GetDeploySources(w http.ResponseWriter, r *http.Request
enc.Encode(out) enc.Encode(out)
} }
func (h *houstonHandler) Upload(w http.ResponseWriter, r *http.Request) { func (h *houstonHandler) UploadDeploySource(w http.ResponseWriter, r *http.Request) {
// <form action="/houston" method="post" enctype="multipart/form-data"> // <form action="/houston" method="post" enctype="multipart/form-data">
// <input type="file" name="file"> // <input type="file" name="file">
// <input type="text" name="name"> // <input type="text" name="name">
@ -58,7 +101,7 @@ func (h *houstonHandler) Upload(w http.ResponseWriter, r *http.Request) {
// </form> // </form>
file, header, err := r.FormFile("file") file, header, err := r.FormFile("file")
if err != nil { if err != nil {
logger.Error(err) logger.Println(err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
@ -66,7 +109,7 @@ func (h *houstonHandler) Upload(w http.ResponseWriter, r *http.Request) {
contents, err := io.ReadAll(file) contents, err := io.ReadAll(file)
if err != nil { if err != nil {
logger.Error(err) logger.Println(err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
@ -75,10 +118,21 @@ func (h *houstonHandler) Upload(w http.ResponseWriter, r *http.Request) {
name := r.FormValue("name") name := r.FormValue("name")
ext := path.Ext(header.Filename) ext := path.Ext(header.Filename)
// deploys 폴더는 파일시스템 서비스이므로 다운로드 가능 var filename string
filename := path.Join("deploys", name, version, name+ext)
if err = os.MkdirAll(path.Dir(filename), os.ModePerm); err != nil { if version == "config" {
logger.Error(err) filename = path.Join(h.deployPath, name, version, "config"+ext)
tags := readTagsFromFile(h.deployPath, name, version, "@tags")
if !strings.Contains(tags, "#hidden") {
tags = tags + "#hidden"
os.WriteFile(path.Join(h.deployPath, name, version, "@tags"), []byte(tags), 0644)
}
} else {
filename = path.Join(h.deployPath, name, version, name+ext)
}
if err = os.MkdirAll(path.Dir(filename), 0775); err != nil {
logger.Println(err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
@ -86,12 +140,75 @@ func (h *houstonHandler) Upload(w http.ResponseWriter, r *http.Request) {
// 파일 저장 // 파일 저장
err = os.WriteFile(filename, contents, 0644) err = os.WriteFile(filename, contents, 0644)
if err != nil { if err != nil {
logger.Error(err) logger.Println(err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
} }
} }
func (h *houstonHandler) DeleteDeploySource(w http.ResponseWriter, r *http.Request) {
// <form action="/houston" method="post" enctype="multipart/form-data">
// <input type="text" name="name">
// <input type="text" name="version">
// </form>
version := r.FormValue("version")
name := r.FormValue("name")
if len(version) == 0 || len(name) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
// deploys 폴더는 파일시스템 서비스이므로 다운로드 가능
targetpath := path.Join(h.deployPath, name, version)
if err := os.RemoveAll(targetpath); err != nil {
logger.Println("deleteDeploySource failed :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
func (h *houstonHandler) findLastestConfigFile(name string) (string, error) {
logger.Println("findLastestConfigFile :", name)
configFiles, err := os.ReadDir(path.Join(h.deployPath, name, "config"))
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return "", nil
}
logger.Println("findLastestConfigFile failed :", err)
return "", err
}
var cf fs.FileInfo
for _, file := range configFiles {
if file.IsDir() {
continue
}
if strings.HasPrefix(file.Name(), "config.") {
test, err := file.Info()
if err != nil {
return "", err
}
if cf == nil {
cf = test
} else if test.ModTime().After(cf.ModTime()) {
cf = test
}
}
}
if cf != nil {
logger.Println("findLastestConfigFile cf found :", cf.Name())
return path.Join(sub_folder_name_deploys, name, "config", cf.Name()), nil
}
logger.Println("findLastestConfigFile cf NOT found")
return "", nil
}
func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) { func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
// <form action="/houston" method="post" enctype="multipart/form-data"> // <form action="/houston" method="post" enctype="multipart/form-data">
// <input type="text" name="name"> // <input type="text" name="name">
@ -104,22 +221,24 @@ func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
traws := r.FormValue("targets") traws := r.FormValue("targets")
var targets []string var targets []string
if len(traws) > 0 {
if err := json.Unmarshal([]byte(traws), &targets); err != nil { if err := json.Unmarshal([]byte(traws), &targets); err != nil {
logger.Error(err) logger.Println(err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
}
if len(name) == 0 || len(version) == 0 || len(targets) == 0 { if len(name) == 0 || len(version) == 0 || len(targets) == 0 {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
relPath := path.Join("deploys", name, version) relPath := path.Join(h.deployPath, name, version)
files, err := os.ReadDir(relPath) files, err := os.ReadDir(relPath)
if err != nil { if err != nil {
logger.Error(err) logger.Println(err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusBadRequest)
return return
} }
@ -130,6 +249,10 @@ func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
continue continue
} }
if strings.HasPrefix(fd.Name(), "@") {
continue
}
fi, _ := fd.Info() fi, _ := fd.Info()
if fi.ModTime().After(latestTime) { if fi.ModTime().After(latestTime) {
latestFilename = fi.Name() latestFilename = fi.Name()
@ -141,11 +264,52 @@ func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
return return
} }
configPath, err := h.findLastestConfigFile(name)
if err != nil {
logger.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
h.Operation().Deploy(MakeDeployRequest( h.Operation().Deploy(MakeDeployRequest(
shared.DeployRequest{ shared.DeployRequest{
Name: name, Name: name,
Version: version, Version: version,
Url: path.Join(relPath, latestFilename), Url: path.Join(sub_folder_name_deploys, name, version, latestFilename),
Config: configPath,
},
targets,
))
}
func (h *houstonHandler) Undeploy(w http.ResponseWriter, r *http.Request) {
// <form action="/houston" method="post" enctype="multipart/form-data">
// <input type="text" name="name">
// <input type="text" name="version">
// <input type="text" name="targets">
// </form>
name := r.FormValue("name")
version := r.FormValue("version")
traws := r.FormValue("targets")
var targets []string
if len(traws) > 0 {
if err := json.Unmarshal([]byte(traws), &targets); err != nil {
logger.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
}
if len(name) == 0 || len(version) == 0 || len(targets) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
h.Operation().Withdraw(MakeWithdrawRequest(
shared.WithdrawRequest{
Name: name,
Version: version,
}, },
targets, targets,
)) ))
@ -161,17 +325,40 @@ func (h *houstonHandler) StartProcess(w http.ResponseWriter, r *http.Request) {
// </form> // </form>
name := r.FormValue("name") name := r.FormValue("name")
version := r.FormValue("version") version := r.FormValue("version")
args := r.FormValue("args") argsline := r.FormValue("args")
traws := r.FormValue("targets") traws := r.FormValue("targets")
var targets []string var targets []string
if len(traws) > 0 {
if err := json.Unmarshal([]byte(traws), &targets); err != nil { if err := json.Unmarshal([]byte(traws), &targets); err != nil {
logger.Error(err) logger.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
}
if len(name) == 0 || len(version) == 0 || len(targets) == 0 {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
if len(name) == 0 || len(version) == 0 || len(targets) == 0 { re := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`)
argsTemp := re.FindAllString(argsline, -1)
var args []string
for _, arg := range argsTemp {
if strings.HasPrefix(arg, `"`) && len(args) > 0 {
lastarg := args[len(args)-1]
if strings.HasSuffix(lastarg, "=") {
args[len(args)-1] = lastarg + arg
} else {
args = append(args, arg)
}
} else {
args = append(args, arg)
}
}
if len(args) == 0 {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
@ -202,11 +389,13 @@ func (h *houstonHandler) StopProcess(w http.ResponseWriter, r *http.Request) {
traws := r.FormValue("targets") traws := r.FormValue("targets")
var targets []string var targets []string
if len(traws) > 0 {
if err := json.Unmarshal([]byte(traws), &targets); err != nil { if err := json.Unmarshal([]byte(traws), &targets); err != nil {
logger.Error(err) logger.Println(err)
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
} }
}
pid, _ := strconv.Atoi(pidstr) pid, _ := strconv.Atoi(pidstr)
@ -216,3 +405,115 @@ func (h *houstonHandler) StopProcess(w http.ResponseWriter, r *http.Request) {
Pid: int32(pid), Pid: int32(pid),
}, targets)) }, targets))
} }
func (h *houstonHandler) RestartProcess(w http.ResponseWriter, r *http.Request) {
// <form action="/houston" method="post" enctype="multipart/form-data">
// <input type="text" name="name">
// <input type="text" name="target">
// <input type="text" name="pid">
// <input type="text" name="config">
// </form>
pidstr := r.FormValue("pid")
target := r.FormValue("target")
name := r.FormValue("name")
if len(target) == 0 || len(pidstr) == 0 || len(name) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
deployConfig := false
configstr := r.FormValue("config")
if len(configstr) > 0 {
deployConfig, _ = strconv.ParseBool(configstr)
}
pid, err := strconv.ParseInt(pidstr, 10, 0)
if err != nil {
logger.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
var configPath string
if deployConfig {
configPath, err = h.findLastestConfigFile(name)
if err != nil {
logger.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
}
h.Operation().RestartProcess(MakeRestartRequest(shared.RestartProcessRequest{
Name: name,
Pid: int32(pid),
Config: configPath,
}, []string{target}))
}
func (h *houstonHandler) UploadLogs(w http.ResponseWriter, r *http.Request) {
// <form action="/houston" method="post" enctype="multipart/form-data">
// <input type="text" name="name">
// <input type="text" name="version">
// <input type="text" name="filter">
// <input type="text" name="targets">
// <input type="submit" value="업로드">
// </form>
name := r.FormValue("name")
if len(name) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
version := r.FormValue("version") // option
traws := r.FormValue("targets")
filter := r.FormValue("filter")
if len(filter) == 0 {
filter = "logs/*"
}
var targets []string
if err := json.Unmarshal([]byte(traws), &targets); err != nil {
logger.Println(err)
w.WriteHeader(http.StatusBadRequest)
return
}
h.Operation().Upload(MakeUploadRequest(shared.UploadRequest{
Name: name,
Version: version,
Url: "upload",
Filter: filter,
}, targets))
}
func (h *houstonHandler) GetLogFileLinks(w http.ResponseWriter, r *http.Request) {
// <form action="/houston" method="post" enctype="multipart/form-data">
// <input type="text" name="name">
// <input type="text" name="version">
// </form>
name := r.FormValue("name")
version := r.FormValue("version")
if len(name) == 0 || len(version) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
if version == "latest" {
version, _ = shared.FindLastestVersion(h.downloadPath, name)
}
root := path.Join(h.downloadPath, name, version)
logfiles, err := os.ReadDir(root)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
var out []string
for _, lf := range logfiles {
out = append(out, path.Join(sub_folder_name_downloads, name, version, lf.Name()))
}
enc := json.NewEncoder(w)
enc.Encode(out)
}

View File

@ -1,18 +1,18 @@
package server package server
import ( import (
"encoding/json"
"fmt"
"io" "io"
"net/http" "net/http"
"os"
"path" "path"
"reflect" "reflect"
"runtime/debug" "runtime/debug"
"strings" "strings"
"repositories.action2quare.com/ayo/go-ayo/logger" "repositories.action2quare.com/ayo/gocommon/flagx"
) "repositories.action2quare.com/ayo/gocommon/logger"
const (
defaultMaxMemory = 32 << 10 // 32 KB
) )
type HoustonServerWithHandler interface { type HoustonServerWithHandler interface {
@ -23,6 +23,8 @@ type HoustonServerWithHandler interface {
type houstonHandler struct { type houstonHandler struct {
HoustonServer HoustonServer
methods map[string]reflect.Method methods map[string]reflect.Method
deployPath string
downloadPath string
} }
func NewHoustonHandler() HoustonServerWithHandler { func NewHoustonHandler() HoustonServerWithHandler {
@ -41,11 +43,67 @@ func NewHoustonHandler() HoustonServerWithHandler {
} }
func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string) error { func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string) error {
serveMux.Handle("/"+path.Join(prefix, "houston"), h) config := loadServerConfig()
storagePath := config.StorageRoot
h.deployPath = path.Join(storagePath, sub_folder_name_deploys)
h.downloadPath = path.Join(storagePath, sub_folder_name_downloads)
if err := os.MkdirAll(h.deployPath, 0775); err != nil {
return err
}
if err := os.MkdirAll(h.downloadPath, 0775); err != nil {
return err
}
logger.Printf("houstonHandler registed. deployPath : %s, downloadPath : %s", h.deployPath, h.downloadPath)
if len(prefix) > 0 {
prefix = "/" + prefix
}
serveMux.Handle(prefix, h)
fsx := http.FileServer(http.Dir(h.deployPath))
serveMux.Handle(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_deploys), http.StripPrefix(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_deploys), fsx))
ufsx := http.FileServer(http.Dir(h.downloadPath))
serveMux.Handle(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_downloads), http.StripPrefix(fmt.Sprintf("%s/%s/", prefix, sub_folder_name_downloads), ufsx))
serveMux.HandleFunc(fmt.Sprintf("%s/upload", prefix), func(w http.ResponseWriter, r *http.Request) {
defer func() {
s := recover()
if s != nil {
logger.Println(s)
debug.PrintStack()
}
io.Copy(io.Discard, r.Body)
r.Body.Close()
}()
name := r.Header.Get("Houston-Service-Name")
version := r.Header.Get("Houston-Service-Version")
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 {
w.WriteHeader(http.StatusInternalServerError)
}
} else {
w.WriteHeader(http.StatusInternalServerError)
}
} else {
w.WriteHeader(http.StatusInternalServerError)
}
})
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()
@ -60,7 +118,45 @@ func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.Body.Close() r.Body.Close()
}() }()
operation := r.FormValue("operation") 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")
} else {
operation = r.URL.Query().Get("operation")
}
if len(operation) == 0 { if len(operation) == 0 {
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
return return
@ -74,15 +170,14 @@ func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
if r.PostForm == nil {
r.ParseMultipartForm(defaultMaxMemory)
}
args := []reflect.Value{ args := []reflect.Value{
reflect.ValueOf(h), reflect.ValueOf(h),
reflect.ValueOf(w), reflect.ValueOf(w),
reflect.ValueOf(r), reflect.ValueOf(r),
} }
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
method.Func.Call(args) method.Func.Call(args)
} }

View File

@ -2,13 +2,11 @@ package server
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strings"
"sync" "sync"
"repositories.action2quare.com/ayo/go-ayo/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"
) )
@ -19,19 +17,17 @@ type opdef struct {
} }
type ProcessSnapshot struct { type ProcessSnapshot struct {
Name string Name string `json:"name"`
Args []string Args []string `json:"args"`
Version string Version string `json:"version"`
State protos.ProcessState State protos.ProcessState `json:"state"`
Pid int32 Pid int32 `json:"pid"`
StdoutSize int32
StderrSize int32
} }
type hostWithChan struct { type hostWithChan struct {
Hostname string Hostname string
Procs []*protos.ProcessDescription Procs []*protos.ProcessDescription `json:"procs"`
Deploys map[string][]*protos.VersionAndArgs Deploys map[string][]*protos.VersionAndArgs `json:"deploys"`
opChan chan *opdef opChan chan *opdef
} }
@ -73,9 +69,6 @@ func (sp *hostPool) regist(desc *protos.OperationQueryRequest) (string, chan *op
host = makeHostWithChan(desc).withOpChan(host.opChan) host = makeHostWithChan(desc).withOpChan(host.opChan)
} }
sp.hosts[desc.Hostname] = host sp.hosts[desc.Hostname] = host
test, _ := json.Marshal(sp.hosts)
logger.Println(string(test))
return desc.Hostname, host.opChan return desc.Hostname, host.opChan
} }
@ -88,9 +81,6 @@ func (sp *hostPool) refresh(desc *protos.OperationQueryRequest) {
host = makeHostWithChan(desc).withOpChan(host.opChan) host = makeHostWithChan(desc).withOpChan(host.opChan)
sp.hosts[desc.Hostname] = host sp.hosts[desc.Hostname] = host
} }
test, _ := json.Marshal(sp.hosts)
logger.Println(string(test))
} }
func (sp *hostPool) unregist(key string) { func (sp *hostPool) unregist(key string) {
@ -101,8 +91,8 @@ func (sp *hostPool) unregist(key string) {
} }
type hostSnapshot struct { type hostSnapshot struct {
Procs []ProcessSnapshot Procs []ProcessSnapshot `json:"procs"`
Deploys map[string][]*protos.VersionAndArgs Deploys map[string][]*protos.VersionAndArgs `json:"deploys"`
} }
func (sp *hostPool) allHosts() map[string]hostSnapshot { func (sp *hostPool) allHosts() map[string]hostSnapshot {
@ -119,8 +109,6 @@ func (sp *hostPool) allHosts() map[string]hostSnapshot {
Version: p.Version, Version: p.Version,
State: p.State, State: p.State,
Pid: p.Pid, Pid: p.Pid,
StdoutSize: p.StdoutSize,
StderrSize: p.StderrSize,
}) })
} }
out[hn] = hostSnapshot{ out[hn] = hostSnapshot{
@ -163,6 +151,12 @@ func marshal(argval reflect.Value, output map[string]string) map[string]string {
marshal(argval.Field(i), output) marshal(argval.Field(i), output)
} else if argval.Field(i).CanInt() { } else if argval.Field(i).CanInt() {
output[argval.Type().Field(i).Name] = fmt.Sprintf("%d", argval.Field(i).Int()) output[argval.Type().Field(i).Name] = fmt.Sprintf("%d", argval.Field(i).Int())
} else if argval.Field(i).Kind() == reflect.Array || argval.Field(i).Kind() == reflect.Slice {
var conv []string
for j := 0; j < argval.Field(i).Len(); j++ {
conv = append(conv, argval.Field(i).Index(j).String())
}
output[argval.Type().Field(i).Name] = strings.Join(conv, "\n")
} else { } else {
output[argval.Type().Field(i).Name] = argval.Field(i).String() output[argval.Type().Field(i).Name] = argval.Field(i).String()
} }
@ -171,8 +165,6 @@ func marshal(argval reflect.Value, output map[string]string) map[string]string {
} }
func (os *operationServer) Query(svr protos.Operation_QueryServer) error { func (os *operationServer) Query(svr protos.Operation_QueryServer) error {
// 서버는 업데이트가 있는지 확인하고 있으면 stream에 응답을 보낸다.
// 업데이트가 없으면 대기
desc, err := svr.Recv() desc, err := svr.Recv()
if err != nil { if err != nil {
return err return err
@ -341,27 +333,19 @@ func (os *operationServer) RestartProcess(d RestartProcessRequest) {
return false return false
}) })
if len(d.hostnames) > 0 { if len(d.hostnames) != 1 {
return
}
// hostname만 재시작 // hostname만 재시작
var final []*hostWithChan
conv := make(map[string]bool)
for _, hn := range d.hostnames {
conv[hn] = true
}
for _, t := range targets {
if _, ok := conv[t.Hostname]; ok {
final = append(final, t)
}
}
targets = final
}
for _, t := range targets { for _, t := range targets {
if t.Hostname == d.hostnames[0] {
t.opChan <- &opdef{ t.opChan <- &opdef{
operation: shared.Restart, operation: shared.Restart,
args: d, args: d,
} }
return
}
} }
} }

View File

@ -1,9 +1,14 @@
package server package server
import ( import (
"encoding/json"
"fmt" "fmt"
"net" "net"
"os"
"sync/atomic"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/houston/client"
"repositories.action2quare.com/ayo/houston/shared" "repositories.action2quare.com/ayo/houston/shared"
"repositories.action2quare.com/ayo/houston/shared/protos" "repositories.action2quare.com/ayo/houston/shared/protos"
@ -12,11 +17,17 @@ import (
// protoc --go_out=. --go-grpc_out=. protos/*.proto // protoc --go_out=. --go-grpc_out=. protos/*.proto
type HoustonServer interface { type HoustonServer interface {
Start(port int) error Start() error
Stop() Stop()
Operation() Operation Operation() Operation
} }
type serverConfig struct {
GrpcPort int `json:"grpc_port"`
StorageRoot string `json:"storage_path"`
RunAsClient bool `json:"run_as_client"`
}
type DeployRequest struct { type DeployRequest struct {
shared.DeployRequest shared.DeployRequest
hostnames []string hostnames []string
@ -99,8 +110,42 @@ type Operation interface {
Hosts() map[string]hostSnapshot Hosts() map[string]hostSnapshot
} }
func NewServer() HoustonServer { func loadServerConfig() serverConfig {
configFile, err := os.Open("config.json")
if err != nil {
logger.Println(err)
return serverConfig{
GrpcPort: 8080,
}
}
defer configFile.Close()
var config struct {
Houston *struct {
Server serverConfig `json:"server"`
} `json:"houston"`
}
dec := json.NewDecoder(configFile)
err = dec.Decode(&config)
if err != nil {
logger.Println(err)
return serverConfig{
GrpcPort: 8080,
}
}
if config.Houston == nil {
logger.Println(`"houston" object is missing in config.json`)
return serverConfig{
GrpcPort: 8080,
}
}
return config.Houston.Server
}
func NewServer() HoustonServer {
var opts []grpc.ServerOption var opts []grpc.ServerOption
grpcServer := grpc.NewServer(opts...) grpcServer := grpc.NewServer(opts...)
@ -113,6 +158,7 @@ func NewServer() HoustonServer {
rpcServer: grpcServer, rpcServer: grpcServer,
os: os, os: os,
ms: ms, ms: ms,
port: loadServerConfig().GrpcPort,
} }
} }
@ -120,23 +166,44 @@ type houstonServer struct {
rpcServer *grpc.Server rpcServer *grpc.Server
os *operationServer os *operationServer
ms *monitorServer ms *monitorServer
port int
} }
func (hs *houstonServer) Start(port int) error { func (hs *houstonServer) Start() error {
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port)) logger.Println("houston server is started at port", hs.port)
lis, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", hs.port))
if err != nil { if err != nil {
return err return err
} }
if err := hs.rpcServer.Serve(lis); err != nil { closeCount := int32(0)
var hc client.HoustonClient
if loadServerConfig().RunAsClient {
hc, err = client.NewClient(false)
if err != nil {
return err return err
} }
return nil go func() {
hc.Start()
if atomic.AddInt32(&closeCount, 1) == 1 {
hs.Stop()
}
}()
}
err = hs.rpcServer.Serve(lis)
if atomic.AddInt32(&closeCount, 1) == 1 {
if hc != nil {
hc.Shutdown()
}
}
return err
} }
func (hs *houstonServer) Stop() { func (hs *houstonServer) Stop() {
hs.rpcServer.GracefulStop() hs.rpcServer.Stop()
} }
func (hs *houstonServer) Operation() Operation { func (hs *houstonServer) Operation() Operation {

View File

@ -3,6 +3,7 @@ package shared
import ( import (
"io/fs" "io/fs"
"os" "os"
"path"
"strings" "strings"
) )
@ -22,6 +23,7 @@ type DeployRequest struct {
Name string Name string
Version string Version string
Url string Url string
Config string
AccessToken string AccessToken string
} }
@ -33,7 +35,7 @@ type WithdrawRequest struct {
type StartProcessRequest struct { type StartProcessRequest struct {
Name string Name string
Version string Version string
Args string Args []string
} }
type StopProcessRequest struct { type StopProcessRequest struct {
@ -44,7 +46,8 @@ type StopProcessRequest struct {
type RestartProcessRequest struct { type RestartProcessRequest struct {
Name string Name string
Version string Pid int32
Config string
} }
type UploadRequest struct { type UploadRequest struct {
@ -88,9 +91,10 @@ func CompareVersionString(lhs, rhs ParsedVersionString) int {
return len(lhs) - len(rhs) return len(lhs) - len(rhs)
} }
func FindLastestVersion(root string) (string, error) { func FindLastestVersion(storageRoot, name string) (string, error) {
// 최신 버전을 찾음 // 최신 버전을 찾음
entries, err := os.ReadDir(root) targetPath := path.Join(storageRoot, name)
entries, err := os.ReadDir(targetPath)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -26,7 +26,8 @@ const (
ProcessState_Stopped ProcessState = 0 ProcessState_Stopped ProcessState = 0
ProcessState_Stopping ProcessState = 1 ProcessState_Stopping ProcessState = 1
ProcessState_Running ProcessState = 2 ProcessState_Running ProcessState = 2
ProcessState_Error ProcessState = 3 ProcessState_Restart ProcessState = 3
ProcessState_Error ProcessState = 4
) )
// Enum value maps for ProcessState. // Enum value maps for ProcessState.
@ -35,13 +36,15 @@ var (
0: "Stopped", 0: "Stopped",
1: "Stopping", 1: "Stopping",
2: "Running", 2: "Running",
3: "Error", 3: "Restart",
4: "Error",
} }
ProcessState_value = map[string]int32{ ProcessState_value = map[string]int32{
"Stopped": 0, "Stopped": 0,
"Stopping": 1, "Stopping": 1,
"Running": 2, "Running": 2,
"Error": 3, "Restart": 3,
"Error": 4,
} }
) )
@ -255,8 +258,6 @@ type ProcessDescription struct {
Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"`
State ProcessState `protobuf:"varint,4,opt,name=state,proto3,enum=ProcessState" json:"state,omitempty"` State ProcessState `protobuf:"varint,4,opt,name=state,proto3,enum=ProcessState" json:"state,omitempty"`
Pid int32 `protobuf:"varint,5,opt,name=pid,proto3" json:"pid,omitempty"` Pid int32 `protobuf:"varint,5,opt,name=pid,proto3" json:"pid,omitempty"`
StdoutSize int32 `protobuf:"varint,6,opt,name=stdout_size,json=stdoutSize,proto3" json:"stdout_size,omitempty"`
StderrSize int32 `protobuf:"varint,7,opt,name=stderr_size,json=stderrSize,proto3" json:"stderr_size,omitempty"`
} }
func (x *ProcessDescription) Reset() { func (x *ProcessDescription) Reset() {
@ -326,20 +327,6 @@ func (x *ProcessDescription) GetPid() int32 {
return 0 return 0
} }
func (x *ProcessDescription) GetStdoutSize() int32 {
if x != nil {
return x.StdoutSize
}
return 0
}
func (x *ProcessDescription) GetStderrSize() int32 {
if x != nil {
return x.StderrSize
}
return 0
}
type OperationQueryResponse struct { type OperationQueryResponse struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -419,7 +406,7 @@ var file_protos_operation_proto_rawDesc = []byte{
0x63, 0x73, 0x12, 0x2b, 0x0a, 0x07, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x73, 0x18, 0x03, 0x20, 0x63, 0x73, 0x12, 0x2b, 0x0a, 0x07, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x73, 0x18, 0x03, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x65, 0x64, 0x56, 0x65, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x65, 0x64, 0x56, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x73, 0x22, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x73, 0x22,
0xcf, 0x01, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x44, 0x65, 0x73, 0x63, 0x72, 0x8d, 0x01, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x44, 0x65, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72,
0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x67, 0x73, 0x12, 0x18, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x67, 0x73, 0x12, 0x18,
@ -427,35 +414,32 @@ var file_protos_operation_proto_rawDesc = []byte{
0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74,
0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
0x73, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x73, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a,
0x03, 0x70, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x70, 0x69, 0x64, 0x12, 0x03, 0x70, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x70, 0x69, 0x64, 0x22,
0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0xa6, 0x01, 0x0a, 0x16, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65,
0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x64, 0x6f, 0x75, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x70,
0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f,
0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x53, 0x69, 0x7a, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x0a, 0x04, 0x61, 0x72, 0x67, 0x73,
0x65, 0x22, 0xa6, 0x01, 0x0a, 0x16, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6f, 0x6e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e,
0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x61, 0x72, 0x67, 0x73, 0x1a,
0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x0a, 0x04, 0x61, 0x72, 0x37, 0x0a, 0x09, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76,
0x65, 0x2e, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x61, 0x72, 0x67, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x4e, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x63,
0x73, 0x1a, 0x37, 0x0a, 0x09, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x70,
0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x70, 0x65, 0x64, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x69, 0x6e,
0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x67, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x02,
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x41, 0x0a, 0x0c, 0x50, 0x72, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x10, 0x03, 0x12, 0x09, 0x0a,
0x6f, 0x63, 0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x74, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x04, 0x32, 0x78, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72,
0x6f, 0x70, 0x70, 0x65, 0x64, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x16,
0x69, 0x6e, 0x67, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52,
0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x03, 0x32, 0x78, 0x0a, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x05, 0x51, 0x75, 0x6f, 0x6e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
0x65, 0x72, 0x79, 0x12, 0x16, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68,
0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x4f, 0x70, 0x12, 0x16, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65, 0x72,
0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x22, 0x00, 0x42, 0x0f, 0x5a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f,
0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x16, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x6e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x06, 0x2e,
0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x42, 0x0f, 0x5a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (