From 5326e26a8cd3bb00bc602a4aa1f1f496657fb7bb Mon Sep 17 00:00:00 2001 From: mountain Date: Fri, 9 Jun 2023 16:16:26 +0900 Subject: [PATCH] =?UTF-8?q?houston=20=EC=9E=90=EC=B2=B4=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20replacer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/client.go | 52 ++++++++++++++++++++++--- client/deploy.go | 99 +++++++++++++++++++++++++++++++++++++++++++----- houston_test.go | 2 +- main.go | 2 +- replacer/main.go | 89 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 227 insertions(+), 17 deletions(-) create mode 100644 replacer/main.go diff --git a/client/client.go b/client/client.go index c702181..a85d392 100644 --- a/client/client.go +++ b/client/client.go @@ -4,15 +4,18 @@ import ( "context" "encoding/json" "errors" + "fmt" "io" "io/fs" "os" "os/exec" "os/signal" "path" + "path/filepath" "reflect" "sort" "strconv" + "sync" "sync/atomic" "syscall" "unsafe" @@ -93,6 +96,7 @@ type houstonClient struct { exitChan chan *exec.Cmd httpAddr string timestamp string + wg sync.WaitGroup } func bToMb(b uint64) uint32 { @@ -218,7 +222,9 @@ func NewClient() (HoustonClient, error) { } ctx, cancel := context.WithCancel(context.Background()) + hc.wg.Add(1) go func() { + defer hc.wg.Done() // regularly send status sc := protos.NewMonitorClient(client) hn, _ := os.Hostname() @@ -250,9 +256,21 @@ func NewClient() (HoustonClient, error) { exitChan := make(chan *exec.Cmd, 10) operationChan := make(chan *protos.OperationQueryResponse, 10) + hc.wg.Add(1) + go func() { + defer hc.wg.Done() + // 메인 operator op := protos.NewOperationClient(hc.client) + 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 { select { @@ -276,13 +294,33 @@ func NewClient() (HoustonClient, error) { case shared.Deploy: var dr shared.DeployRequest unmarshal(&dr, resp.Args) - err := hc.deploy(&dr) - if err == nil { - prog := gatherDeployedPrograms(dr.Name) - hc.deploys[dr.Name] = prog - op.Refresh(ctx, hc.makeOperationQueryRequest()) + + if dr.Name == myname { + if srcdir, _, err := hc.prepareUpdateSelf(&dr); err == nil { + args := []string{ + fmt.Sprintf("%d", os.Getpid()), + srcdir, + filepath.ToSlash(os.Args[0]), + } + args = append(args, os.Args[1:]...) + logger.Println(args) + // cmd := exec.Command(replacer, args...) + // if err := cmd.Start(); err != nil { + // logger.Println(err) + // } else { + // hc.shutdownFunc() + // } + } else { + logger.Println(err) + } } else { - logger.Println(err) + if err := hc.deploy(&dr); err == nil { + prog := gatherDeployedPrograms(dr.Name) + hc.deploys[dr.Name] = prog + op.Refresh(ctx, hc.makeOperationQueryRequest()) + } else { + logger.Println(err) + } } case shared.Withdraw: @@ -344,6 +382,8 @@ func NewClient() (HoustonClient, error) { 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) diff --git a/client/deploy.go b/client/deploy.go index 2eed8cf..57a2c47 100644 --- a/client/deploy.go +++ b/client/deploy.go @@ -11,6 +11,7 @@ import ( "net/url" "os" "path" + "path/filepath" "strings" "repositories.action2quare.com/ayo/gocommon/logger" @@ -54,7 +55,7 @@ func download(dir string, urlpath string, accessToken string) (string, error) { return "", err } - return out.Name(), nil + return filepath.ToSlash(out.Name()), nil } func unzip(fname string) error { @@ -63,7 +64,10 @@ func unzip(fname string) error { os.Remove(fname) return err } - defer archive.Close() + defer func() { + archive.Close() + os.Remove(fname) + }() verpath := path.Dir(fname) for _, f := range archive.File { @@ -102,6 +106,7 @@ func unzip(fname string) error { dstFile.Close() fileInArchive.Close() } + return nil } @@ -110,7 +115,10 @@ func untar(fname string) error { if err != nil { return err } - defer file.Close() + defer func() { + file.Close() + os.Remove(fname) + }() verpath := path.Dir(fname) tarReader := tar.NewReader(file) @@ -142,6 +150,7 @@ func untar(fname string) error { return errors.New("unknown type") } } + return nil } @@ -179,6 +188,83 @@ func (hc *houstonClient) prepareDeploy(name string, version string) (destPath st return verpath, nil } +func (hc *houstonClient) makeDownloadUrl(req *shared.DeployRequest) string { + out := req.Url + if !strings.HasPrefix(out, "http") { + tks := strings.SplitN(hc.httpAddr, "://", 2) + out = fmt.Sprintf("%s://%s", tks[0], path.Join(tks[1], req.Url)) + } + 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), 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 + } + + selfname, _ := os.Executable() + srcreplacer := path.Join(path.Dir(fname), "replacer") + path.Ext(selfname) + replacer = "replacer" + path.Ext(selfname) + + // replacer먼저 가져옴 + return filepath.ToSlash(tempdir), filepath.ToSlash(replacer), copy(srcreplacer, replacer) +} + func (hc *houstonClient) deploy(req *shared.DeployRequest) error { logger.Println("start deploying") root, err := hc.prepareDeploy(req.Name, req.Version) @@ -186,14 +272,9 @@ func (hc *houstonClient) deploy(req *shared.DeployRequest) error { 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에 배포 시작 - fname, err := download(root, req.Url, req.AccessToken) + fname, err := download(root, hc.makeDownloadUrl(req), req.AccessToken) if err != nil { return err } diff --git a/houston_test.go b/houston_test.go index 6c70ae1..00ca811 100644 --- a/houston_test.go +++ b/houston_test.go @@ -8,7 +8,7 @@ import ( ) func TestOperationServer(t *testing.T) { - hc, err := client.NewClient("192.168.9.32:8080", "http://192.168.9.32/commandcenter") + hc, err := client.NewClient() if err != nil { t.Error(err) return diff --git a/main.go b/main.go index 5b86b08..1430f57 100644 --- a/main.go +++ b/main.go @@ -30,7 +30,7 @@ func main() { hc.Start() } else if *runAsServer { svr := server.NewServer() - svr.Start(*port) + svr.Start() } } diff --git a/replacer/main.go b/replacer/main.go new file mode 100644 index 0000000..12221e3 --- /dev/null +++ b/replacer/main.go @@ -0,0 +1,89 @@ +package main + +import ( + "errors" + "fmt" + "io" + "os" + "os/exec" + "path" + "strconv" +) + +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 main() { + args := os.Args + // args[1] : 나를 시작한 pid. pid가 종료될 때 까지 기다림 + // args[2] : target 폴더 + // args[3:] : 다시 시작할 때 넘겨줄 arguments(프로세스 이름 포함) + fmt.Println(args) + + pid, err := strconv.Atoi(args[1]) + if err != nil { + panic(err) + } + proc, err := os.FindProcess(pid) + if err != nil { + panic(err) + } + proc.Wait() + + selfext, _ := os.Executable() + + entries, _ := os.ReadDir(args[2]) + for _, ent := range entries { + if ent.Name() == selfext { + continue + } + + if ent.IsDir() { + if err := os.MkdirAll(ent.Name(), os.ModePerm); err != nil { + panic(err) + } + } else { + if err := copy(path.Join(args[2], ent.Name()), ent.Name()); err != nil { + panic(err) + } + } + } + + os.RemoveAll(args[2]) + cmd := exec.Command(args[3], args[4:]...) + cmd.Start() +}