Files
houston/client/deploy.go

331 lines
7.1 KiB
Go
Raw Normal View History

2023-05-21 23:37:54 +09:00
package client
import (
"archive/tar"
"archive/zip"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
2023-05-21 23:37:54 +09:00
"strings"
2023-06-14 00:13:51 +09:00
"repositories.action2quare.com/ayo/gocommon/logger"
2023-05-22 02:13:03 +09:00
"repositories.action2quare.com/ayo/houston/shared"
2023-05-21 23:37:54 +09:00
"golang.org/x/text/encoding/korean"
"golang.org/x/text/transform"
)
2023-06-26 23:24:49 +09:00
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")
}
}()
2023-05-21 23:37:54 +09:00
parsed, err := url.Parse(urlpath)
if err != nil {
return "", err
}
req, _ := http.NewRequest("GET", urlpath, nil)
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 filepath.ToSlash(out.Name()), nil
2023-05-21 23:37:54 +09:00
}
func unzip(fname string) error {
archive, err := zip.OpenReader(fname)
if err != nil {
os.Remove(fname)
return err
}
defer func() {
archive.Close()
os.Remove(fname)
}()
2023-05-21 23:37:54 +09:00
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
}
name = strings.ReplaceAll(name, `\`, "/")
2023-05-21 23:37:54 +09:00
filePath := path.Join(verpath, name)
if f.FileInfo().IsDir() || strings.HasSuffix(f.FileInfo().Name(), `\`) {
if err = os.MkdirAll(filePath, 0775); err != nil {
return err
}
2023-05-21 23:37:54 +09:00
continue
}
if err := os.MkdirAll(path.Dir(filePath), 0775); err != nil {
2023-05-21 23:37:54 +09:00
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()
}
2023-05-21 23:37:54 +09:00
return nil
}
func untar(fname string) error {
file, err := os.Open(fname)
if err != nil {
return err
}
defer func() {
file.Close()
os.Remove(fname)
}()
2023-05-21 23:37:54 +09:00
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), 0775); err != nil {
2023-05-21 23:37:54 +09:00
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")
}
}
2023-05-21 23:37:54 +09:00
return nil
}
func (hc *houstonClient) prepareDeploy(name string, version string) (destPath string, err error) {
// houston관리용임을 표시하기 위해 더미파일 생성
defer func() {
var flagf *os.File
2023-06-14 14:16:47 +09:00
markerPath := path.Join(hc.config.StorageRoot, name, "@houston")
if _, err := os.Stat(markerPath); os.IsNotExist(err) {
flagf, err = os.Create(markerPath)
2023-05-21 23:37:54 +09:00
if err != nil {
return
}
defer flagf.Close()
flagf.Write([]byte(hc.timestamp))
}
}()
2023-06-14 14:16:47 +09:00
verpath := path.Join(hc.config.StorageRoot, name, version)
2023-05-21 23:37:54 +09:00
if _, err := os.Stat(verpath); os.IsNotExist(err) {
// 없네? 만들면 된다.
err = os.MkdirAll(verpath, 0775)
2023-05-21 23:37:54 +09:00
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
}
2023-06-12 12:28:33 +09:00
func (hc *houstonClient) makeDownloadUrl(rel string) string {
out := rel
if !strings.HasPrefix(out, "http") {
2023-06-14 01:50:40 +09:00
tks := strings.SplitN(hc.config.HttpAddress, "://", 2)
2023-06-12 12:28:33 +09:00
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
}
2023-06-12 12:28:33 +09:00
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
}
2023-06-29 11:00:26 +09:00
// houston version 파일
2023-06-29 11:11:23 +09:00
err = os.WriteFile(path.Join(path.Dir(fname), "@version"), []byte(req.Version), 0644)
2023-06-29 11:00:26 +09:00
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
}
2023-05-21 23:37:54 +09:00
func (hc *houstonClient) deploy(req *shared.DeployRequest) error {
2023-06-14 00:13:51 +09:00
logger.Println("start deploying")
2023-05-21 23:37:54 +09:00
root, err := hc.prepareDeploy(req.Name, req.Version)
if err != nil {
return err
}
// verpath에 배포 시작
2023-06-12 12:28:33 +09:00
fname, err := download(root, hc.makeDownloadUrl(req.Url), req.AccessToken)
2023-05-21 23:37:54 +09:00
if err != nil {
return err
}
switch path.Ext(fname) {
case ".zip":
err = unzip(fname)
case ".tar":
err = untar(fname)
}
2023-06-14 14:16:47 +09:00
if err == nil && len(req.Config) > 0 {
2023-06-12 12:28:33 +09:00
// config.json도 다운로드
_, err = download(root, hc.makeDownloadUrl(req.Config), req.AccessToken)
}
2023-05-21 23:37:54 +09:00
return err
}
func (hc *houstonClient) withdraw(req *shared.WithdrawRequest) error {
2023-06-14 14:16:47 +09:00
targetPath := path.Join(hc.config.StorageRoot, req.Name, req.Version)
fd, _ := os.Stat(targetPath)
2023-05-21 23:37:54 +09:00
if fd != nil {
if fd.IsDir() {
for _, running := range hc.childProcs {
2023-05-30 16:51:23 +09:00
if running.name == req.Name && (len(req.Version) == 0 || running.version == req.Version) {
2023-05-21 23:37:54 +09:00
// 회수하려는 버전이 돌고 있다
2023-05-30 16:51:23 +09:00
return fmt.Errorf("withdraw failed. %s@%s is still running", req.Name, req.Version)
2023-05-21 23:37:54 +09:00
}
}
2023-06-14 14:16:47 +09:00
return os.RemoveAll(targetPath)
2023-05-21 23:37:54 +09:00
}
}
return fmt.Errorf("withdraw failed. %s@%s is not deployed", req.Name, req.Version)
}