houston package 독립
This commit is contained in:
329
client/client.go
Normal file
329
client/client.go
Normal file
@ -0,0 +1,329 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go-ayo/common/logger"
|
||||
"houston/shared"
|
||||
"houston/shared/protos"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
sigar "github.com/cloudfoundry/gosigar"
|
||||
)
|
||||
|
||||
type HoustonClient interface {
|
||||
SetReportMetrics(map[string]float32)
|
||||
Shutdown()
|
||||
}
|
||||
|
||||
type procmeta struct {
|
||||
cmd *exec.Cmd
|
||||
name string
|
||||
version string
|
||||
state protos.ProcessState
|
||||
stdin io.WriteCloser
|
||||
stdPrefix string
|
||||
stdoutSize int32
|
||||
stderrSize int32
|
||||
}
|
||||
|
||||
type houstonClient struct {
|
||||
client *grpc.ClientConn
|
||||
childProcs []*procmeta
|
||||
extraMetrics unsafe.Pointer // map[string]float32
|
||||
deploys map[string][]*protos.VersionAndArgs
|
||||
shutdownFunc context.CancelFunc
|
||||
exitChan chan *exec.Cmd
|
||||
httpAddr string
|
||||
timestamp string
|
||||
}
|
||||
|
||||
func bToMb(b uint64) uint32 {
|
||||
return uint32(b / 1024 / 1024)
|
||||
}
|
||||
|
||||
func unmarshal[T any](val *T, src map[string]string) {
|
||||
argval := reflect.ValueOf(val)
|
||||
for i := 0; i < argval.Elem().Type().NumField(); i++ {
|
||||
if !argval.Elem().Type().Field(i).IsExported() {
|
||||
continue
|
||||
}
|
||||
arg := src[argval.Elem().Type().Field(i).Name]
|
||||
if argval.Elem().Field(i).CanInt() {
|
||||
num, _ := strconv.ParseInt(arg, 10, 0)
|
||||
argval.Elem().Field(i).SetInt(num)
|
||||
} else {
|
||||
argval.Elem().Field(i).SetString(arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func gatherDeployedPrograms(name string) []*protos.VersionAndArgs {
|
||||
var rawvers []*protos.VersionAndArgs
|
||||
if vers, err := os.ReadDir(path.Join("./", name)); err == nil {
|
||||
for _, ver := range vers {
|
||||
if ver.IsDir() {
|
||||
args := lastExecutionArgs(path.Join(name, ver.Name()))
|
||||
rawvers = append(rawvers, &protos.VersionAndArgs{
|
||||
Version: ver.Name(),
|
||||
Args: args,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Slice(rawvers, func(i, j int) bool {
|
||||
leftParsed := parseVersionString(rawvers[i].Version)
|
||||
rightParsed := parseVersionString(rawvers[j].Version)
|
||||
return compareVersionString(leftParsed, rightParsed) < 0
|
||||
})
|
||||
return rawvers
|
||||
}
|
||||
|
||||
func (hc *houstonClient) makeOperationQueryRequest() *protos.OperationQueryRequest {
|
||||
hn, _ := os.Hostname()
|
||||
procs := make([]*protos.ProcessDescription, 0, len(hc.childProcs))
|
||||
for _, child := range hc.childProcs {
|
||||
procs = append(procs, &protos.ProcessDescription{
|
||||
Name: child.name,
|
||||
Args: child.cmd.Args,
|
||||
Version: child.version,
|
||||
State: child.state,
|
||||
Pid: int32(child.cmd.Process.Pid),
|
||||
StdoutSize: atomic.LoadInt32(&child.stdoutSize),
|
||||
StderrSize: atomic.LoadInt32(&child.stderrSize),
|
||||
})
|
||||
}
|
||||
|
||||
var deploys []*protos.DeployedVersions
|
||||
for name, prog := range hc.deploys {
|
||||
deploys = append(deploys, &protos.DeployedVersions{
|
||||
Name: name,
|
||||
Versions: prog,
|
||||
})
|
||||
}
|
||||
|
||||
return &protos.OperationQueryRequest{
|
||||
Hostname: hn,
|
||||
Procs: procs,
|
||||
Deploys: deploys,
|
||||
}
|
||||
}
|
||||
|
||||
func NewClient(grpcAddr string, httpAddr string) (HoustonClient, error) {
|
||||
client, err := grpc.Dial(grpcAddr, grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exefile, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exefi, err := os.Stat(exefile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deploys := make(map[string][]*protos.VersionAndArgs)
|
||||
if dirs, err := os.ReadDir("./"); err == nil {
|
||||
for _, dir := range dirs {
|
||||
if dir.IsDir() {
|
||||
flagf := path.Join(dir.Name(), "@houston")
|
||||
if _, err := os.Stat(flagf); !os.IsNotExist(err) {
|
||||
deploys[dir.Name()] = gatherDeployedPrograms(dir.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hc := &houstonClient{
|
||||
client: client,
|
||||
extraMetrics: unsafe.Pointer(&map[string]float32{}),
|
||||
deploys: deploys,
|
||||
httpAddr: httpAddr,
|
||||
timestamp: exefi.ModTime().String(),
|
||||
}
|
||||
|
||||
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)
|
||||
operationChan := make(chan *protos.OperationQueryResponse, 10)
|
||||
go func() {
|
||||
// 메인 operator
|
||||
op := protos.NewOperationClient(hc.client)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
case exited := <-exitChan:
|
||||
for _, proc := range hc.childProcs {
|
||||
if proc.cmd == exited && proc.state != protos.ProcessState_Stopped {
|
||||
proc.state = protos.ProcessState_Stopped
|
||||
op.Refresh(ctx, hc.makeOperationQueryRequest())
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
case resp := <-operationChan:
|
||||
switch shared.Operation(resp.Operation) {
|
||||
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())
|
||||
} else {
|
||||
logger.Println(err)
|
||||
}
|
||||
|
||||
case shared.Withdraw:
|
||||
var wr shared.WithdrawRequest
|
||||
unmarshal(&wr, resp.Args)
|
||||
err := hc.withdraw(&wr)
|
||||
if err == nil {
|
||||
prog := gatherDeployedPrograms(wr.Name)
|
||||
hc.deploys[wr.Name] = prog
|
||||
op.Refresh(ctx, hc.makeOperationQueryRequest())
|
||||
} else {
|
||||
logger.Println(err)
|
||||
}
|
||||
|
||||
case shared.Start:
|
||||
var sr shared.StartProcessRequest
|
||||
unmarshal(&sr, resp.Args)
|
||||
if err := hc.startChildProcess(&sr); err != nil {
|
||||
logger.Println(err)
|
||||
}
|
||||
|
||||
case shared.Stop:
|
||||
var sr shared.StopProcessRequest
|
||||
unmarshal(&sr, resp.Args)
|
||||
if err := hc.stopChildProcess(&sr); err != nil {
|
||||
logger.Println(err)
|
||||
}
|
||||
|
||||
case shared.Restart:
|
||||
var rr shared.RestartProcessRequest
|
||||
unmarshal(&rr, resp.Args)
|
||||
if err := hc.restartChildProcess(&rr); err != nil {
|
||||
logger.Println(err)
|
||||
}
|
||||
|
||||
case shared.Upload:
|
||||
var ur shared.UploadRequest
|
||||
unmarshal(&ur, resp.Args)
|
||||
if err := hc.uploadFiles(&ur); err != nil {
|
||||
logger.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// receive from stream
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
default:
|
||||
err := hc.checkOperation(operationChan)
|
||||
if err != nil {
|
||||
logger.Println("hc.checkUpdate failed :", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
hc.shutdownFunc = cancel
|
||||
hc.exitChan = exitChan
|
||||
|
||||
return hc, nil
|
||||
}
|
||||
|
||||
func (hc *houstonClient) Shutdown() {
|
||||
hc.shutdownFunc()
|
||||
}
|
||||
|
||||
func (hc *houstonClient) checkOperation(opChan chan<- *protos.OperationQueryResponse) error {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
logger.Error(r)
|
||||
}
|
||||
}()
|
||||
|
||||
op := protos.NewOperationClient(hc.client)
|
||||
cl, err := op.Query(context.Background(), grpc.WaitForReady(true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cl.Send(hc.makeOperationQueryRequest())
|
||||
|
||||
if err != nil {
|
||||
cl.CloseSend()
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
update, err := cl.Recv()
|
||||
if err != nil {
|
||||
cl.CloseSend()
|
||||
return err
|
||||
}
|
||||
opChan <- update
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *houstonClient) SetReportMetrics(extra map[string]float32) {
|
||||
atomic.StorePointer(&hc.extraMetrics, unsafe.Pointer(&extra))
|
||||
}
|
||||
228
client/deploy.go
Normal file
228
client/deploy.go
Normal file
@ -0,0 +1,228 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go-ayo/common/logger"
|
||||
"houston/shared"
|
||||
"houston/shared/protos"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/encoding/korean"
|
||||
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
func download(dir string, urlpath string, accessToken string) (string, error) {
|
||||
parsed, err := url.Parse(urlpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
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")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("download failed : %d %s", resp.StatusCode, parsed.Path)
|
||||
}
|
||||
|
||||
out, err := os.Create(path.Join(dir, path.Base(parsed.Path)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return out.Name(), nil
|
||||
}
|
||||
|
||||
func unzip(fname string) error {
|
||||
archive, err := zip.OpenReader(fname)
|
||||
if err != nil {
|
||||
os.Remove(fname)
|
||||
return err
|
||||
}
|
||||
defer archive.Close()
|
||||
|
||||
verpath := path.Dir(fname)
|
||||
for _, f := range archive.File {
|
||||
var name string
|
||||
if f.NonUTF8 {
|
||||
name, _, _ = transform.String(korean.EUCKR.NewDecoder(), f.Name)
|
||||
} else {
|
||||
name = f.Name
|
||||
}
|
||||
|
||||
filePath := path.Join(verpath, name)
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
os.MkdirAll(filePath, os.ModePerm)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Dir(filePath), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileInArchive, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(dstFile, fileInArchive); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstFile.Close()
|
||||
fileInArchive.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func untar(fname string) error {
|
||||
file, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
verpath := path.Dir(fname)
|
||||
tarReader := tar.NewReader(file)
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err := os.MkdirAll(path.Join(verpath, header.Name), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeReg:
|
||||
fileWriter, err := os.Create(path.Join(verpath, header.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileWriter.Close()
|
||||
|
||||
if _, err := io.Copy(fileWriter, tarReader); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return errors.New("unknown type")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hc *houstonClient) prepareDeploy(name string, version string) (destPath string, err error) {
|
||||
// houston관리용임을 표시하기 위해 더미파일 생성
|
||||
defer func() {
|
||||
var flagf *os.File
|
||||
if _, err := os.Stat(path.Join(name, "@houston")); os.IsNotExist(err) {
|
||||
flagf, err = os.Create(path.Join(name, "@houston"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer flagf.Close()
|
||||
flagf.Write([]byte(hc.timestamp))
|
||||
}
|
||||
}()
|
||||
|
||||
verpath := path.Join("./", name, version)
|
||||
if _, err := os.Stat(verpath); os.IsNotExist(err) {
|
||||
// 없네? 만들면 된다.
|
||||
err = os.MkdirAll(verpath, fs.FileMode(os.O_WRONLY))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
// 있네? 재배포 가능한가?
|
||||
for _, child := range hc.childProcs {
|
||||
if child.version == version && child.name == name {
|
||||
// 이미 실행 중인 버전이다. 실패
|
||||
return "", fmt.Errorf("%s %s is already running. deploy is failed", name, version)
|
||||
}
|
||||
}
|
||||
// 재배포 가능
|
||||
}
|
||||
return verpath, nil
|
||||
}
|
||||
|
||||
func (hc *houstonClient) deploy(req *shared.DeployRequest) error {
|
||||
logger.Println("start deploying")
|
||||
root, err := hc.prepareDeploy(req.Name, req.Version)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch path.Ext(fname) {
|
||||
case ".zip":
|
||||
err = unzip(fname)
|
||||
case ".tar":
|
||||
err = untar(fname)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (hc *houstonClient) withdraw(req *shared.WithdrawRequest) error {
|
||||
fd, _ := os.Stat(path.Join("./", req.Name, req.Version))
|
||||
if fd != nil {
|
||||
if fd.IsDir() {
|
||||
for _, running := range hc.childProcs {
|
||||
if running.name == req.Name && 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 os.RemoveAll(path.Join("./", req.Name, req.Version))
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("withdraw failed. %s@%s is not deployed", req.Name, req.Version)
|
||||
}
|
||||
449
client/operation.go
Normal file
449
client/operation.go
Normal file
@ -0,0 +1,449 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go-ayo/common/logger"
|
||||
"houston/shared"
|
||||
"houston/shared/protos"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type parsedVersionString = []string
|
||||
|
||||
func parseVersionString(ver string) parsedVersionString {
|
||||
return strings.Split(ver, ".")
|
||||
}
|
||||
|
||||
func lastExecutionArgs(verpath string) []string {
|
||||
argf, err := os.Open(path.Join(verpath, "@args"))
|
||||
if os.IsNotExist(err) {
|
||||
argf, err = os.Open(path.Clean(path.Join(verpath, "..", "@args")))
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
defer argf.Close()
|
||||
|
||||
var out []string
|
||||
dec := json.NewDecoder(argf)
|
||||
dec.Decode(&out)
|
||||
return out
|
||||
}
|
||||
|
||||
func compareVersionString(lhs, rhs parsedVersionString) int {
|
||||
minlen := len(lhs)
|
||||
if minlen > len(rhs) {
|
||||
minlen = len(rhs)
|
||||
}
|
||||
|
||||
for i := 0; i < minlen; i++ {
|
||||
if len(lhs[i]) < len(rhs[i]) {
|
||||
return -1
|
||||
}
|
||||
|
||||
if len(lhs[i]) > len(rhs[i]) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if lhs[i] < rhs[i] {
|
||||
return -1
|
||||
}
|
||||
|
||||
if lhs[i] > rhs[i] {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
return len(lhs) - len(rhs)
|
||||
}
|
||||
|
||||
func findLastestVersion(root string) (string, error) {
|
||||
// 최신 버전을 찾음
|
||||
entries, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(entries) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
latest := parseVersionString(entries[0].Name())
|
||||
for i := 1; i < len(entries); i++ {
|
||||
next := parseVersionString(entries[i].Name())
|
||||
if compareVersionString(latest, next) < 0 {
|
||||
latest = next
|
||||
}
|
||||
}
|
||||
return strings.Join(latest, "."), nil
|
||||
}
|
||||
|
||||
func (meta *procmeta) launch(args []string, exitChan chan<- *exec.Cmd) error {
|
||||
exepath := args[0]
|
||||
verpath := path.Dir(exepath)
|
||||
args[0] = path.Base(exepath)
|
||||
|
||||
cmd := exec.Command("./"+args[0], args[1:]...)
|
||||
cmd.Dir = verpath
|
||||
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(path.Join(cmd.Dir, "logs"), os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
ext := path.Ext(cmd.Args[0])
|
||||
nameonly := path.Base(cmd.Args[0])
|
||||
if len(ext) > 0 {
|
||||
nameonly = nameonly[:len(nameonly)-len(ext)]
|
||||
}
|
||||
ts := now.Format("2006-01-02T15-04-05")
|
||||
stdPrefix := path.Join(cmd.Dir, "logs", fmt.Sprintf("%s_%s", nameonly, ts))
|
||||
errfile, err := os.Create(stdPrefix + ".stderr.log")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outfile, err := os.Create(stdPrefix + ".stdout.log")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
recover()
|
||||
stdout.Close()
|
||||
errfile.Close()
|
||||
}()
|
||||
|
||||
buff := make([]byte, 1024)
|
||||
for {
|
||||
size, err := stderr.Read(buff)
|
||||
if err != nil {
|
||||
exitChan <- cmd
|
||||
errfile.Close()
|
||||
break
|
||||
}
|
||||
errfile.Write(buff[:size])
|
||||
new := atomic.AddInt32(&meta.stderrSize, int32(size))
|
||||
logger.Println("stderrSize :", new)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
recover()
|
||||
stderr.Close()
|
||||
outfile.Close()
|
||||
}()
|
||||
|
||||
buff := make([]byte, 1024)
|
||||
for {
|
||||
size, err := stdout.Read(buff)
|
||||
if err != nil {
|
||||
exitChan <- cmd
|
||||
break
|
||||
}
|
||||
outfile.Write(buff[:size])
|
||||
new := atomic.AddInt32(&meta.stdoutSize, int32(size))
|
||||
logger.Println("stdoutSize :", new)
|
||||
}
|
||||
}()
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
meta.cmd = cmd
|
||||
meta.stdin = stdin
|
||||
meta.stdPrefix = stdPrefix
|
||||
meta.state = protos.ProcessState_Running
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hc *houstonClient) startChildProcess(req *shared.StartProcessRequest) error {
|
||||
if req.Version == "latest" {
|
||||
// 최신 버전을 찾음
|
||||
latest, err := findLastestVersion(path.Join("./", req.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Version = latest
|
||||
}
|
||||
|
||||
meta := &procmeta{
|
||||
name: req.Name,
|
||||
version: req.Version,
|
||||
state: protos.ProcessState_Error,
|
||||
}
|
||||
|
||||
verpath := path.Join("./", req.Name, req.Version)
|
||||
fi, err := os.Stat(verpath)
|
||||
if err == nil && fi.IsDir() {
|
||||
// Define regular expression
|
||||
re := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`)
|
||||
|
||||
// Split input string into array of strings
|
||||
result := re.FindAllString(req.Args, -1)
|
||||
for i := range result {
|
||||
result[i] = strings.Trim(result[i], "\"'")
|
||||
}
|
||||
result[0] = path.Join(verpath, result[0])
|
||||
err := meta.launch(result, hc.exitChan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// launch가 성공하면 args 저장. this and parent folder
|
||||
if argfile, err := os.Create(path.Join(req.Name, "@args")); err == nil {
|
||||
enc := json.NewEncoder(argfile)
|
||||
enc.Encode(result)
|
||||
argfile.Close()
|
||||
}
|
||||
if argfile, err := os.Create(path.Join(verpath, "@args")); err == nil {
|
||||
enc := json.NewEncoder(argfile)
|
||||
enc.Encode(result)
|
||||
argfile.Close()
|
||||
}
|
||||
|
||||
hc.childProcs = append(hc.childProcs, meta)
|
||||
|
||||
op := protos.NewOperationClient(hc.client)
|
||||
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var errNoRunningProcess = errors.New("no running processed")
|
||||
|
||||
func (hc *houstonClient) stopChildProcess(req *shared.StopProcessRequest) error {
|
||||
if req.Version == "latest" {
|
||||
// 최신 버전을 찾음
|
||||
latest, err := findLastestVersion(path.Join("./", req.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Version = latest
|
||||
}
|
||||
|
||||
var remains []*procmeta
|
||||
var killing []*procmeta
|
||||
for _, proc := range hc.childProcs {
|
||||
if proc.state != protos.ProcessState_Running {
|
||||
continue
|
||||
}
|
||||
|
||||
if req.Pid != 0 {
|
||||
if req.Pid == int32(proc.cmd.Process.Pid) {
|
||||
// 해당 pid만 제거
|
||||
killing = append(killing, proc)
|
||||
} else {
|
||||
remains = append(remains, proc)
|
||||
}
|
||||
} else if proc.name == req.Name {
|
||||
if len(req.Version) == 0 {
|
||||
// program 다 정지
|
||||
killing = append(killing, proc)
|
||||
} else if req.Version == proc.version {
|
||||
// program의 특정 버전만 정지
|
||||
killing = append(killing, proc)
|
||||
} else {
|
||||
// 해당 사항 없음
|
||||
remains = append(remains, proc)
|
||||
}
|
||||
} else {
|
||||
// 해당 사항 없음
|
||||
remains = append(remains, proc)
|
||||
}
|
||||
}
|
||||
|
||||
if len(killing) > 0 {
|
||||
for _, proc := range killing {
|
||||
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 killing {
|
||||
proc.cmd.Wait()
|
||||
}
|
||||
|
||||
hc.childProcs = remains
|
||||
|
||||
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return errNoRunningProcess
|
||||
}
|
||||
|
||||
func (hc *houstonClient) restartChildProcess(req *shared.RestartProcessRequest) error {
|
||||
if req.Version == "latest" {
|
||||
// 최신 버전을 찾음
|
||||
latest, err := findLastestVersion(path.Join("./", req.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Version = latest
|
||||
}
|
||||
|
||||
var restarts []*procmeta
|
||||
for _, proc := range hc.childProcs {
|
||||
if proc.name == req.Name {
|
||||
if len(req.Version) == 0 {
|
||||
restarts = append(restarts, proc)
|
||||
} else if req.Version == proc.version {
|
||||
restarts = append(restarts, proc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
args := proc.cmd.Args
|
||||
args[0] = path.Join(proc.cmd.Dir, args[0])
|
||||
|
||||
if err := proc.launch(args, hc.exitChan); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
op.Refresh(context.Background(), hc.makeOperationQueryRequest())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hc *houstonClient) uploadFiles(req *shared.UploadRequest) error {
|
||||
if req.Version == "latest" {
|
||||
// 최신 버전을 찾음
|
||||
latest, err := findLastestVersion(path.Join("./", req.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Version = latest
|
||||
}
|
||||
|
||||
root := path.Join(req.Name, req.Version)
|
||||
matches, err := filepath.Glob(path.Join(root, req.Filter))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(matches) == 0 {
|
||||
resp, err := http.Post(req.Url, "application/zip", bytes.NewBuffer([]byte{}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a file to write the archive to.
|
||||
f, err := os.CreateTemp("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func(f *os.File) {
|
||||
defer func() {
|
||||
tempname := f.Name()
|
||||
f.Close()
|
||||
|
||||
resp, _ := http.Post(req.Url, "application/zip", f)
|
||||
if resp != nil && resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
os.Remove(tempname)
|
||||
if del, err := strconv.ParseBool(req.DeleteAfterUploaded); del && err == nil {
|
||||
for _, f := range matches {
|
||||
os.Remove(f)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Create a new zip archive.
|
||||
w := zip.NewWriter(f)
|
||||
defer w.Close()
|
||||
|
||||
for _, file := range matches {
|
||||
relative := file[len(root)+1:]
|
||||
fw, err := w.Create(relative)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
src, err := os.Open(file)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
if _, err = io.Copy(fw, src); err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}(f)
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user