Compare commits
72 Commits
bdc3e19718
...
kd-live
| Author | SHA1 | Date | |
|---|---|---|---|
| 31d77efe84 | |||
| 3ace1be27a | |||
| 3fccae1ef5 | |||
| 3dc121bc71 | |||
| 706339a386 | |||
| 849973c449 | |||
| 759dcaf00b | |||
| d1a192159b | |||
| db4769687c | |||
| 7a5696961f | |||
| 7418d847b7 | |||
| 4d9a25dd6b | |||
| b057730ad5 | |||
| 9bd09509c8 | |||
| d526a923ca | |||
| 1b57e9f87e | |||
| 30412bce70 | |||
| 4667d351a8 | |||
| 20dc204cb1 | |||
| 571f0d76df | |||
| 025e583f12 | |||
| 15dd1e544c | |||
| b3ed139431 | |||
| 45ab15d345 | |||
| c0a0f88220 | |||
| 02db65e06f | |||
| ed4ca6cfed | |||
| a8821b694b | |||
| 7c54fda9a2 | |||
| 5429d3d90f | |||
| 77174ccef4 | |||
| cf46888b6a | |||
| 642d53fbba | |||
| 6a35d7b1fe | |||
| 361ff4bb3a | |||
| d04dd5b05d | |||
| 942875071f | |||
| d3442be5dc | |||
| bff9548dab | |||
| 06bc095ea4 | |||
| e5b7a7b02b | |||
| da47d7c587 | |||
| 3f2c82251b | |||
| d464812cf8 | |||
| fdb0a7baa5 | |||
| fa8b78efed | |||
| cf033b6107 | |||
| 9b0aa4d640 | |||
| 96a0b43e3f | |||
| cacbc1008a | |||
| 6185d055ef | |||
| 6d319f2fa1 | |||
| b2aae5a38e | |||
| 01b4782e78 | |||
| 52491aff17 | |||
| 72e94ccfc3 | |||
| 8a1bcdc840 | |||
| d17c53c79c | |||
| 8bb1e4d0a2 | |||
| 9590de2e00 | |||
| d48afc61af | |||
| 58897522a7 | |||
| 1b6c4edaff | |||
| 06b390815c | |||
| 95bc4d8db3 | |||
| 3d3020f827 | |||
| 6a316afd90 | |||
| 9947835ba1 | |||
| bbe340f559 | |||
| 30ff0a4b27 | |||
| c98023cc8b | |||
| 3278bca32f |
@ -15,6 +15,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -116,10 +117,13 @@ type houstonClient struct {
|
|||||||
timestamp string
|
timestamp string
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
config clientConfig
|
config clientConfig
|
||||||
|
version string
|
||||||
|
standalone bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshal[T any](val *T, src map[string]string) {
|
func unmarshal[T any](val *T, src map[string]string) {
|
||||||
argval := reflect.ValueOf(val)
|
argval := reflect.ValueOf(val)
|
||||||
|
logger.Println("operation receive :", argval.Type().Name(), src)
|
||||||
for i := 0; i < argval.Elem().Type().NumField(); i++ {
|
for i := 0; i < argval.Elem().Type().NumField(); i++ {
|
||||||
if !argval.Elem().Type().Field(i).IsExported() {
|
if !argval.Elem().Type().Field(i).IsExported() {
|
||||||
continue
|
continue
|
||||||
@ -128,6 +132,9 @@ 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)
|
||||||
}
|
}
|
||||||
@ -157,8 +164,33 @@ func gatherDeployedPrograms(storageRoot, 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,
|
||||||
@ -169,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,
|
||||||
@ -177,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,
|
||||||
@ -184,7 +216,7 @@ func (hc *houstonClient) makeOperationQueryRequest() *protos.OperationQueryReque
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient() (HoustonClient, error) {
|
func NewClient(standalone bool) (HoustonClient, error) {
|
||||||
clientConfig, err := loadClientConfig()
|
clientConfig, err := loadClientConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -233,12 +265,19 @@ func NewClient() (HoustonClient, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ver, _ := os.ReadFile("@version")
|
||||||
|
if len(ver) == 0 {
|
||||||
|
ver = []byte("0.0.0")
|
||||||
|
}
|
||||||
|
|
||||||
hc := &houstonClient{
|
hc := &houstonClient{
|
||||||
config: clientConfig,
|
config: clientConfig,
|
||||||
clientChan: make(chan *grpc.ClientConn),
|
clientChan: make(chan *grpc.ClientConn),
|
||||||
extraMetrics: unsafe.Pointer(&map[string]float32{}),
|
extraMetrics: unsafe.Pointer(&map[string]float32{}),
|
||||||
deploys: deploys,
|
deploys: deploys,
|
||||||
timestamp: exefi.ModTime().String(),
|
timestamp: exefi.ModTime().String(),
|
||||||
|
version: string(ver),
|
||||||
|
standalone: standalone,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
@ -273,15 +312,22 @@ func NewClient() (HoustonClient, error) {
|
|||||||
var newprocs []*procmeta
|
var newprocs []*procmeta
|
||||||
for _, proc := range hc.childProcs {
|
for _, proc := range hc.childProcs {
|
||||||
if proc.cmd == exited {
|
if proc.cmd == exited {
|
||||||
if proc.state == protos.ProcessState_Running {
|
if proc.state == protos.ProcessState_Running || proc.state == protos.ProcessState_Restart {
|
||||||
go func(cmd *exec.Cmd) {
|
go func(proc *procmeta) {
|
||||||
if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
if err := proc.cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
||||||
cmd.Process.Signal(os.Kill)
|
proc.cmd.Process.Signal(os.Kill)
|
||||||
}
|
}
|
||||||
cmd.Wait()
|
proc.cmd.Wait()
|
||||||
cmd.Process.Release()
|
proc.cmd.Process.Release()
|
||||||
logger.Println("abnormal termination of process :", cmd.Args)
|
|
||||||
}(proc.cmd)
|
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)
|
||||||
@ -407,6 +453,8 @@ func (hc *houstonClient) Start() {
|
|||||||
|
|
||||||
var client *grpc.ClientConn
|
var client *grpc.ClientConn
|
||||||
reconnCount := 0
|
reconnCount := 0
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-hc.ctx.Done():
|
case <-hc.ctx.Done():
|
||||||
|
|||||||
@ -21,16 +21,22 @@ import (
|
|||||||
"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 {
|
||||||
@ -258,6 +264,12 @@ func (hc *houstonClient) prepareUpdateSelf(req *shared.DeployRequest) (srcdir st
|
|||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// houston version 파일
|
||||||
|
err = os.WriteFile(path.Join(path.Dir(fname), "@version"), []byte(req.Version), 0644)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
selfname, _ := os.Executable()
|
selfname, _ := os.Executable()
|
||||||
srcreplacer := path.Join(path.Dir(fname), "replacer") + path.Ext(selfname)
|
srcreplacer := path.Join(path.Dir(fname), "replacer") + path.Ext(selfname)
|
||||||
replacer = "./" + filepath.ToSlash("replacer"+path.Ext(selfname))
|
replacer = "./" + filepath.ToSlash("replacer"+path.Ext(selfname))
|
||||||
@ -277,7 +289,6 @@ func (hc *houstonClient) deploy(req *shared.DeployRequest) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Println("start downloading", req.Url)
|
|
||||||
// verpath에 배포 시작
|
// verpath에 배포 시작
|
||||||
fname, err := download(root, hc.makeDownloadUrl(req.Url), req.AccessToken)
|
fname, err := download(root, hc.makeDownloadUrl(req.Url), req.AccessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -103,11 +102,12 @@ func zipLogFiles(storageRoot string, req *shared.UploadRequest, start, except st
|
|||||||
defer w.Close()
|
defer w.Close()
|
||||||
|
|
||||||
oldestFile := ""
|
oldestFile := ""
|
||||||
for _, file := range matches {
|
for i, file := range matches {
|
||||||
if file == root {
|
if file == root {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(except) > 0 && file >= except {
|
if len(except) > 0 && file >= except {
|
||||||
|
matches = matches[:i]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if len(start) > 0 && file < start {
|
if len(start) > 0 && file < start {
|
||||||
@ -165,33 +165,17 @@ func zipLogFiles(storageRoot string, req *shared.UploadRequest, start, except st
|
|||||||
}
|
}
|
||||||
|
|
||||||
func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) *procmeta {
|
func prepareProcessLaunch(storageRoot string, req *shared.StartProcessRequest) *procmeta {
|
||||||
re := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`)
|
if len(req.Args) == 0 {
|
||||||
argsTemp := re.FindAllString(req.Args, -1)
|
|
||||||
var args []string
|
|
||||||
for _, arg := range argsTemp {
|
|
||||||
if strings.HasPrefix(arg, `"`) && len(args) > 0 {
|
|
||||||
lastarg := args[len(args)-1]
|
|
||||||
if strings.HasSuffix(lastarg, "=") {
|
|
||||||
args[len(args)-1] = lastarg + arg
|
|
||||||
} else {
|
|
||||||
args = append(args, arg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
args = append(args, arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) == 0 {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
verpath := path.Join(storageRoot, req.Name, req.Version)
|
verpath := path.Join(storageRoot, req.Name, req.Version)
|
||||||
fi, err := os.Stat(verpath)
|
fi, err := os.Stat(verpath)
|
||||||
|
|
||||||
if err == nil && fi.IsDir() {
|
if err == nil && fi.IsDir() {
|
||||||
args[0] = "./" + path.Clean(strings.TrimPrefix(args[0], "/"))
|
req.Args[0] = "./" + path.Clean(strings.TrimPrefix(req.Args[0], "/"))
|
||||||
os.Chmod(path.Join(verpath, args[0]), 0777)
|
os.Chmod(path.Join(verpath, req.Args[0]), 0777)
|
||||||
|
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
cmd := exec.Command(req.Args[0], req.Args[1:]...)
|
||||||
cmd.Dir = verpath
|
cmd.Dir = verpath
|
||||||
stdin, _ := cmd.StdinPipe()
|
stdin, _ := cmd.StdinPipe()
|
||||||
|
|
||||||
@ -452,54 +436,24 @@ func (hc *houstonClient) stopChildProcess(req *shared.StopProcessRequest, op pro
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hc *houstonClient) restartChildProcess(req *shared.RestartProcessRequest, op protos.OperationClient) error {
|
func (hc *houstonClient) restartChildProcess(req *shared.RestartProcessRequest, op protos.OperationClient) error {
|
||||||
if req.Version == "latest" {
|
|
||||||
// 최신 버전을 찾음
|
|
||||||
latest, err := shared.FindLastestVersion(hc.config.StorageRoot, req.Name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Version = latest
|
|
||||||
}
|
|
||||||
|
|
||||||
var restarts []*procmeta
|
|
||||||
for _, proc := range hc.childProcs {
|
for _, proc := range hc.childProcs {
|
||||||
if proc.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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proc.state = protos.ProcessState_Restart
|
||||||
|
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
|
||||||
|
hc.exitChan <- proc.cmd
|
||||||
|
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(restarts) == 0 {
|
|
||||||
return errNoRunningProcess
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, proc := range restarts {
|
|
||||||
if err := proc.cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
|
||||||
proc.cmd.Process.Signal(os.Kill)
|
|
||||||
}
|
|
||||||
proc.state = protos.ProcessState_Stopping
|
|
||||||
}
|
|
||||||
|
|
||||||
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
|
|
||||||
|
|
||||||
for _, proc := range restarts {
|
|
||||||
proc.cmd.Wait()
|
|
||||||
proc.cmd.Process.Release()
|
|
||||||
proc.state = protos.ProcessState_Stopped
|
|
||||||
}
|
|
||||||
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
|
|
||||||
|
|
||||||
for _, proc := range restarts {
|
|
||||||
if err := hc.launch(proc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -527,11 +481,7 @@ func (hc *houstonClient) uploadFiles(req *shared.UploadRequest) error {
|
|||||||
// 전체 파일을 대상으로
|
// 전체 파일을 대상으로
|
||||||
zipFile, srcFiles, err := zipLogFiles(hc.config.StorageRoot, req, "", "")
|
zipFile, srcFiles, err := zipLogFiles(hc.config.StorageRoot, req, "", "")
|
||||||
if err == nil && len(zipFile) > 0 && len(srcFiles) > 0 {
|
if err == nil && len(zipFile) > 0 && len(srcFiles) > 0 {
|
||||||
if err = hc.uploadZipLogFile(zipFile, req.Name, req.Version); err == nil {
|
if err = hc.uploadZipLogFile(zipFile, req.Name, req.Version); err != nil {
|
||||||
for _, oldf := range srcFiles {
|
|
||||||
os.Remove(oldf)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.Println("uploadZipLogFile failed :", err)
|
logger.Println("uploadZipLogFile failed :", err)
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
|||||||
3
go.mod
3
go.mod
@ -1,6 +1,6 @@
|
|||||||
module repositories.action2quare.com/ayo/houston
|
module repositories.action2quare.com/ayo/houston
|
||||||
|
|
||||||
go 1.19
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/text v0.10.0
|
golang.org/x/text v0.10.0
|
||||||
@ -13,6 +13,5 @@ require (
|
|||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
golang.org/x/net v0.11.0 // indirect
|
golang.org/x/net v0.11.0 // indirect
|
||||||
golang.org/x/sys v0.9.0 // indirect
|
golang.org/x/sys v0.9.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230525234025-438c736192d0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
14
go.sum
14
go.sum
@ -3,34 +3,20 @@ 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.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.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
|
||||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||||
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/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
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/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
|
||||||
google.golang.org/genproto v0.0.0-20230525234025-438c736192d0 h1:x1vNwUhVOcsYoKyEGCZBH694SBmmBjA2EfauFVEI2+M=
|
|
||||||
google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
|
||||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
|
||||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
|
||||||
google.golang.org/grpc v1.56.0 h1:+y7Bs8rtMd07LeXmL3NxcTLn7mUkbKZqEpPhMNkwJEE=
|
google.golang.org/grpc v1.56.0 h1:+y7Bs8rtMd07LeXmL3NxcTLn7mUkbKZqEpPhMNkwJEE=
|
||||||
google.golang.org/grpc v1.56.0/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
google.golang.org/grpc v1.56.0/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||||
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=
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230613162208-4ebf231bbe72 h1:2qaxct6dumM2JjguSp5nofeWZwWsozKSJWC36q9Q0qc=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230613162208-4ebf231bbe72/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
|
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230621052811-06ef97f11d22 h1:DImSGNxZrc+Q4WlS1OKMsLAScEfDYLX4XMJdjAaVnXc=
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20230621052811-06ef97f11d22 h1:DImSGNxZrc+Q4WlS1OKMsLAScEfDYLX4XMJdjAaVnXc=
|
||||||
repositories.action2quare.com/ayo/gocommon v0.0.0-20230621052811-06ef97f11d22/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
|
repositories.action2quare.com/ayo/gocommon v0.0.0-20230621052811-06ef97f11d22/go.mod h1:ng62uGMGXyQSeuxePG5gJAMtip4Rnspu5Tu7hgvaXns=
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
nohup /home/opdev/houston -client -logfile &
|
nohup /home/opdev/houston -client -logfile > /dev/null &
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@ -18,7 +18,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if *runAsClient {
|
if *runAsClient {
|
||||||
hc, err := client.NewClient()
|
hc, err := client.NewClient(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal(err)
|
logger.Fatal(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -8,14 +8,24 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func copy(src, dst string) error {
|
func copy(src, dst string, stdlog *log.Logger) error {
|
||||||
fi, err := os.Stat(src)
|
fi, err := os.Stat(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if fi.IsDir() {
|
||||||
|
entries, _ := os.ReadDir(src)
|
||||||
|
for _, ent := range entries {
|
||||||
|
if err := copy(path.Join(src, ent.Name()), path.Join(dst, ent.Name()), stdlog); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
inmode := fi.Mode()
|
inmode := fi.Mode()
|
||||||
|
|
||||||
in, err := os.Open(src)
|
in, err := os.Open(src)
|
||||||
@ -45,6 +55,7 @@ func copy(src, dst string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stdlog.Println("file copied :", src, dst)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,45 +70,34 @@ func main() {
|
|||||||
// args[3:] : 다시 시작할 때 넘겨줄 arguments(프로세스 이름 포함)
|
// args[3:] : 다시 시작할 때 넘겨줄 arguments(프로세스 이름 포함)
|
||||||
stdlog.Println(args)
|
stdlog.Println(args)
|
||||||
|
|
||||||
pid, err := strconv.Atoi(args[1])
|
for {
|
||||||
if err != nil {
|
stdlog.Println("wait for terminating of", args[3])
|
||||||
stdlog.Fatal(err)
|
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)
|
||||||
}
|
}
|
||||||
proc, err := os.FindProcess(pid)
|
|
||||||
if err != nil {
|
|
||||||
stdlog.Fatal(err)
|
|
||||||
}
|
|
||||||
proc.Wait()
|
|
||||||
|
|
||||||
selfext, _ := os.Executable()
|
|
||||||
selfext = path.Base(selfext)
|
|
||||||
nextArgs := args[4:]
|
nextArgs := args[4:]
|
||||||
entries, _ := os.ReadDir(args[2])
|
if bt, _ := os.ReadFile("@args"); len(bt) > 0 {
|
||||||
for _, ent := range entries {
|
var tempArgs []string
|
||||||
if ent.Name() == selfext {
|
if json.Unmarshal(bt, &tempArgs) == nil {
|
||||||
continue
|
nextArgs = tempArgs
|
||||||
}
|
|
||||||
|
|
||||||
if ent.IsDir() {
|
|
||||||
if err := os.MkdirAll(ent.Name(), 0775); err != nil {
|
|
||||||
stdlog.Fatal(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ent.Name() == "@args" {
|
|
||||||
var tempArgs []string
|
|
||||||
argfile, _ := os.Open(path.Join(args[2], ent.Name()))
|
|
||||||
dec := json.NewDecoder(argfile)
|
|
||||||
if dec.Decode(&tempArgs) == nil {
|
|
||||||
nextArgs = tempArgs
|
|
||||||
}
|
|
||||||
} else if err := copy(path.Join(args[2], ent.Name()), ent.Name()); err != nil {
|
|
||||||
stdlog.Println("copy failed :", path.Join(args[2], ent.Name()), ent.Name())
|
|
||||||
stdlog.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
os.Remove("@args")
|
||||||
|
|
||||||
err = os.RemoveAll(args[2])
|
err := os.RemoveAll(args[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stdlog.Println("os.RemoveAll failed :", args[2], err)
|
stdlog.Println("os.RemoveAll failed :", args[2], err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,15 @@ package server
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||||
@ -20,9 +23,25 @@ import (
|
|||||||
현재 접속 중인 Agent 목록을 보여줍니다.
|
현재 접속 중인 Agent 목록을 보여줍니다.
|
||||||
- http method : GET
|
- http method : GET
|
||||||
*/
|
*/
|
||||||
|
const (
|
||||||
|
sub_folder_name_deploys = string("_deploys")
|
||||||
|
sub_folder_name_downloads = string("_downloads")
|
||||||
|
)
|
||||||
|
|
||||||
func (h *houstonHandler) GetAgents(w http.ResponseWriter, r *http.Request) {
|
func (h *houstonHandler) GetAgents(w http.ResponseWriter, r *http.Request) {
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
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) {
|
||||||
@ -34,13 +53,31 @@ func (h *houstonHandler) GetDeploySources(w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
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(h.deployPath, 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,9 +121,13 @@ func (h *houstonHandler) UploadDeploySource(w http.ResponseWriter, r *http.Reque
|
|||||||
var filename string
|
var filename string
|
||||||
|
|
||||||
if version == "config" {
|
if version == "config" {
|
||||||
filename = path.Join(h.deployPath, name, version, "config.json")
|
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 {
|
} else {
|
||||||
// deploys 폴더는 파일시스템 서비스이므로 다운로드 가능
|
|
||||||
filename = path.Join(h.deployPath, name, version, name+ext)
|
filename = path.Join(h.deployPath, name, version, name+ext)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +168,47 @@ func (h *houstonHandler) DeleteDeploySource(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *houstonHandler) findLastestConfigFile(name string) (string, error) {
|
||||||
|
logger.Println("findLastestConfigFile :", name)
|
||||||
|
configFiles, err := os.ReadDir(path.Join(h.deployPath, name, "config"))
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Println("findLastestConfigFile failed :", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cf fs.FileInfo
|
||||||
|
for _, file := range configFiles {
|
||||||
|
if file.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(file.Name(), "config.") {
|
||||||
|
test, err := file.Info()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cf == nil {
|
||||||
|
cf = test
|
||||||
|
} else if test.ModTime().After(cf.ModTime()) {
|
||||||
|
cf = test
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cf != nil {
|
||||||
|
logger.Println("findLastestConfigFile cf found :", cf.Name())
|
||||||
|
return path.Join(sub_folder_name_deploys, name, "config", cf.Name()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Println("findLastestConfigFile cf NOT found")
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
|
func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
|
||||||
// <form action="/houston" method="post" enctype="multipart/form-data">
|
// <form action="/houston" method="post" enctype="multipart/form-data">
|
||||||
// <input type="text" name="name">
|
// <input type="text" name="name">
|
||||||
@ -167,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()
|
||||||
@ -178,16 +264,18 @@ func (h *houstonHandler) Deploy(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
configPath := ""
|
configPath, err := h.findLastestConfigFile(name)
|
||||||
if _, err := os.Stat(path.Join(h.deployPath, name, "config", "config.json")); err == nil || errors.Is(err, fs.ErrExist) {
|
if err != nil {
|
||||||
configPath = path.Join("deploys", name, "config", "config.json")
|
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("deploys", name, version, latestFilename),
|
Url: path.Join(sub_folder_name_deploys, name, version, latestFilename),
|
||||||
Config: configPath,
|
Config: configPath,
|
||||||
},
|
},
|
||||||
targets,
|
targets,
|
||||||
@ -237,7 +325,7 @@ func (h *houstonHandler) StartProcess(w http.ResponseWriter, r *http.Request) {
|
|||||||
// </form>
|
// </form>
|
||||||
name := r.FormValue("name")
|
name := r.FormValue("name")
|
||||||
version := r.FormValue("version")
|
version := r.FormValue("version")
|
||||||
args := r.FormValue("args")
|
argsline := r.FormValue("args")
|
||||||
traws := r.FormValue("targets")
|
traws := r.FormValue("targets")
|
||||||
|
|
||||||
var targets []string
|
var targets []string
|
||||||
@ -254,6 +342,27 @@ func (h *houstonHandler) StartProcess(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
re := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`)
|
||||||
|
argsTemp := re.FindAllString(argsline, -1)
|
||||||
|
var args []string
|
||||||
|
for _, arg := range argsTemp {
|
||||||
|
if strings.HasPrefix(arg, `"`) && len(args) > 0 {
|
||||||
|
lastarg := args[len(args)-1]
|
||||||
|
if strings.HasSuffix(lastarg, "=") {
|
||||||
|
args[len(args)-1] = lastarg + arg
|
||||||
|
} else {
|
||||||
|
args = append(args, arg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args = append(args, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
h.Operation().StartProcess(MakeStartProcessRequest(shared.StartProcessRequest{
|
h.Operation().StartProcess(MakeStartProcessRequest(shared.StartProcessRequest{
|
||||||
Name: name,
|
Name: name,
|
||||||
Version: version,
|
Version: version,
|
||||||
@ -297,6 +406,51 @@ func (h *houstonHandler) StopProcess(w http.ResponseWriter, r *http.Request) {
|
|||||||
}, targets))
|
}, targets))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *houstonHandler) RestartProcess(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// <form action="/houston" method="post" enctype="multipart/form-data">
|
||||||
|
// <input type="text" name="name">
|
||||||
|
// <input type="text" name="target">
|
||||||
|
// <input type="text" name="pid">
|
||||||
|
// <input type="text" name="config">
|
||||||
|
// </form>
|
||||||
|
pidstr := r.FormValue("pid")
|
||||||
|
target := r.FormValue("target")
|
||||||
|
name := r.FormValue("name")
|
||||||
|
if len(target) == 0 || len(pidstr) == 0 || len(name) == 0 {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deployConfig := false
|
||||||
|
configstr := r.FormValue("config")
|
||||||
|
if len(configstr) > 0 {
|
||||||
|
deployConfig, _ = strconv.ParseBool(configstr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pid, err := strconv.ParseInt(pidstr, 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println(err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var configPath string
|
||||||
|
if deployConfig {
|
||||||
|
configPath, err = h.findLastestConfigFile(name)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println(err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Operation().RestartProcess(MakeRestartRequest(shared.RestartProcessRequest{
|
||||||
|
Name: name,
|
||||||
|
Pid: int32(pid),
|
||||||
|
Config: configPath,
|
||||||
|
}, []string{target}))
|
||||||
|
}
|
||||||
|
|
||||||
func (h *houstonHandler) UploadLogs(w http.ResponseWriter, r *http.Request) {
|
func (h *houstonHandler) UploadLogs(w http.ResponseWriter, r *http.Request) {
|
||||||
// <form action="/houston" method="post" enctype="multipart/form-data">
|
// <form action="/houston" method="post" enctype="multipart/form-data">
|
||||||
// <input type="text" name="name">
|
// <input type="text" name="name">
|
||||||
@ -357,7 +511,7 @@ func (h *houstonHandler) GetLogFileLinks(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
var out []string
|
var out []string
|
||||||
for _, lf := range logfiles {
|
for _, lf := range logfiles {
|
||||||
out = append(out, path.Join("downloads", name, version, lf.Name()))
|
out = append(out, path.Join(sub_folder_name_downloads, name, version, lf.Name()))
|
||||||
}
|
}
|
||||||
|
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -10,13 +11,10 @@ import (
|
|||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"repositories.action2quare.com/ayo/gocommon/flagx"
|
||||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
defaultMaxMemory = 32 << 10 // 32 KB
|
|
||||||
)
|
|
||||||
|
|
||||||
type HoustonServerWithHandler interface {
|
type HoustonServerWithHandler interface {
|
||||||
HoustonServer
|
HoustonServer
|
||||||
RegisterHandlers(serveMux *http.ServeMux, prefix string) error
|
RegisterHandlers(serveMux *http.ServeMux, prefix string) error
|
||||||
@ -45,9 +43,10 @@ func NewHoustonHandler() HoustonServerWithHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string) error {
|
func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string) error {
|
||||||
storagePath := loadServerConfig().StorageRoot
|
config := loadServerConfig()
|
||||||
h.deployPath = path.Join(storagePath, "deploys")
|
storagePath := config.StorageRoot
|
||||||
h.downloadPath = path.Join(storagePath, "downloads")
|
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 {
|
if err := os.MkdirAll(h.deployPath, 0775); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -65,10 +64,10 @@ func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string
|
|||||||
serveMux.Handle(prefix, h)
|
serveMux.Handle(prefix, h)
|
||||||
|
|
||||||
fsx := http.FileServer(http.Dir(h.deployPath))
|
fsx := http.FileServer(http.Dir(h.deployPath))
|
||||||
serveMux.Handle(fmt.Sprintf("%s/deploys/", prefix), http.StripPrefix(fmt.Sprintf("%s/deploys/", prefix), fsx))
|
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))
|
ufsx := http.FileServer(http.Dir(h.downloadPath))
|
||||||
serveMux.Handle(fmt.Sprintf("%s/downloads/", prefix), http.StripPrefix(fmt.Sprintf("%s/downloads/", prefix), ufsx))
|
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) {
|
serveMux.HandleFunc(fmt.Sprintf("%s/upload", prefix), func(w http.ResponseWriter, r *http.Request) {
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -103,6 +102,8 @@ func (h *houstonHandler) RegisterHandlers(serveMux *http.ServeMux, prefix string
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var noauth = flagx.Bool("noauth", false, "")
|
||||||
|
|
||||||
func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
defer func() {
|
defer func() {
|
||||||
s := recover()
|
s := recover()
|
||||||
@ -117,6 +118,38 @@ func (h *houstonHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
r.Body.Close()
|
r.Body.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
var userinfo map[string]any
|
||||||
|
if !*noauth {
|
||||||
|
authheader := r.Header.Get("Authorization")
|
||||||
|
if len(authheader) == 0 {
|
||||||
|
logger.Println("Authorization header is not valid :", authheader)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", "https://graph.microsoft.com/oidc/userinfo", nil)
|
||||||
|
req.Header.Add("Authorization", authheader)
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Println("graph microsoft api call failed :", err)
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
raw, _ := io.ReadAll(resp.Body)
|
||||||
|
if err = json.Unmarshal(raw, &userinfo); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, expired := userinfo["error"]; expired {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var operation string
|
var operation string
|
||||||
if r.Method == "POST" {
|
if r.Method == "POST" {
|
||||||
operation = r.FormValue("operation")
|
operation = r.FormValue("operation")
|
||||||
@ -137,10 +170,6 @@ 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),
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"repositories.action2quare.com/ayo/houston/shared"
|
"repositories.action2quare.com/ayo/houston/shared"
|
||||||
@ -150,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()
|
||||||
}
|
}
|
||||||
@ -158,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
|
||||||
@ -328,26 +333,18 @@ func (os *operationServer) RestartProcess(d RestartProcessRequest) {
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
if len(d.hostnames) > 0 {
|
if len(d.hostnames) != 1 {
|
||||||
// hostname만 재시작
|
return
|
||||||
var final []*hostWithChan
|
|
||||||
conv := make(map[string]bool)
|
|
||||||
for _, hn := range d.hostnames {
|
|
||||||
conv[hn] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, t := range targets {
|
|
||||||
if _, ok := conv[t.Hostname]; ok {
|
|
||||||
final = append(final, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
targets = final
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hostname만 재시작
|
||||||
for _, t := range targets {
|
for _, t := range targets {
|
||||||
t.opChan <- &opdef{
|
if t.Hostname == d.hostnames[0] {
|
||||||
operation: shared.Restart,
|
t.opChan <- &opdef{
|
||||||
args: d,
|
operation: shared.Restart,
|
||||||
|
args: d,
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||||
|
"repositories.action2quare.com/ayo/houston/client"
|
||||||
"repositories.action2quare.com/ayo/houston/shared"
|
"repositories.action2quare.com/ayo/houston/shared"
|
||||||
"repositories.action2quare.com/ayo/houston/shared/protos"
|
"repositories.action2quare.com/ayo/houston/shared/protos"
|
||||||
|
|
||||||
@ -23,6 +25,7 @@ type HoustonServer interface {
|
|||||||
type serverConfig struct {
|
type serverConfig struct {
|
||||||
GrpcPort int `json:"grpc_port"`
|
GrpcPort int `json:"grpc_port"`
|
||||||
StorageRoot string `json:"storage_path"`
|
StorageRoot string `json:"storage_path"`
|
||||||
|
RunAsClient bool `json:"run_as_client"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeployRequest struct {
|
type DeployRequest struct {
|
||||||
@ -173,15 +176,34 @@ func (hs *houstonServer) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := hs.rpcServer.Serve(lis); err != nil {
|
closeCount := int32(0)
|
||||||
return err
|
var hc client.HoustonClient
|
||||||
|
if loadServerConfig().RunAsClient {
|
||||||
|
hc, err = client.NewClient(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
hc.Start()
|
||||||
|
if atomic.AddInt32(&closeCount, 1) == 1 {
|
||||||
|
hs.Stop()
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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 {
|
||||||
|
|||||||
@ -35,7 +35,7 @@ type WithdrawRequest struct {
|
|||||||
type StartProcessRequest struct {
|
type StartProcessRequest struct {
|
||||||
Name string
|
Name string
|
||||||
Version string
|
Version string
|
||||||
Args string
|
Args []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type StopProcessRequest struct {
|
type StopProcessRequest struct {
|
||||||
@ -45,8 +45,9 @@ 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 {
|
||||||
|
|||||||
@ -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,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -422,20 +425,21 @@ var file_protos_operation_proto_rawDesc = []byte{
|
|||||||
0x37, 0x0a, 0x09, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
|
0x37, 0x0a, 0x09, 0x41, 0x72, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
|
||||||
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
|
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
|
||||||
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76,
|
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76,
|
||||||
0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x41, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x63,
|
0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x2a, 0x4e, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x63,
|
||||||
0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x70,
|
0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x70,
|
||||||
0x70, 0x65, 0x64, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x69, 0x6e,
|
0x70, 0x65, 0x64, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x69, 0x6e,
|
||||||
0x67, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x02,
|
0x67, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x02,
|
||||||
0x12, 0x09, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x03, 0x32, 0x78, 0x0a, 0x09, 0x4f,
|
0x12, 0x0b, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x10, 0x03, 0x12, 0x09, 0x0a,
|
||||||
0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72,
|
0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x04, 0x32, 0x78, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72,
|
||||||
0x79, 0x12, 0x16, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65,
|
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x16,
|
||||||
0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x4f, 0x70, 0x65, 0x72,
|
0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52,
|
||||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
|
||||||
0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x2b, 0x0a, 0x07, 0x52, 0x65, 0x66, 0x72,
|
0x6f, 0x6e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||||
0x65, 0x73, 0x68, 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, 0x06, 0x2e, 0x45, 0x6d,
|
0x12, 0x16, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x65, 0x72,
|
||||||
0x70, 0x74, 0x79, 0x22, 0x00, 0x42, 0x0f, 0x5a, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f,
|
0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x06, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
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 (
|
||||||
|
|||||||
Reference in New Issue
Block a user