package client import ( "archive/tar" "archive/zip" "errors" "fmt" "io" "io/fs" "net/http" "net/url" "os" "path" "strings" "repositories.action2quare.com/ayo/gocommon/logger" "repositories.action2quare.com/ayo/houston/shared" "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 && (len(req.Version) == 0 || running.version == req.Version) { // 회수하려는 버전이 돌고 있다 return fmt.Errorf("withdraw failed. %s@%s is still running", req.Name, req.Version) } } return os.RemoveAll(path.Join(req.Name, req.Version)) } } return fmt.Errorf("withdraw failed. %s@%s is not deployed", req.Name, req.Version) }