[REV] Add rest api to download file for export app in api project.

This commit is contained in:
Zhang Jiajun 2018-05-15 11:03:03 +08:00
parent 2483b97d6a
commit b1409e44b8
9 changed files with 143 additions and 22 deletions

View File

@ -134,6 +134,8 @@ type SourcesInterface interface {
type AppInterface interface {
ExportApp(w http.ResponseWriter, r *http.Request)
Download(w http.ResponseWriter, r *http.Request)
Upload(w http.ResponseWriter, r *http.Request)
ImportApp(w http.ResponseWriter, r *http.Request)
BackupApp(w http.ResponseWriter, r *http.Request)
RecoverApp(w http.ResponseWriter, r *http.Request)

View File

@ -217,8 +217,13 @@ func (v2 *V2) appRouter() chi.Router {
r := chi.NewRouter()
r.Post("/export", controller.GetManager().ExportApp)
r.Get("/export/{eventId}", controller.GetManager().ExportApp)
r.Get("/download/{format}/{fileName}", controller.GetManager().Download)
r.Get("/upload/rainbond-app/{fileName}", controller.GetManager().Upload)
r.Post("/import", controller.GetManager().ImportApp)
r.Get("/import/{eventId}", controller.GetManager().ExportApp)
r.Post("/backup", controller.GetManager().BackupApp)
r.Post("/recover", controller.GetManager().RecoverApp)
return r

View File

@ -11,6 +11,7 @@ import (
dbmodel "github.com/goodrain/rainbond/db/model"
"strings"
"github.com/go-chi/chi"
"os"
)
type AppStruct struct {}
@ -63,6 +64,26 @@ func (a *AppStruct) ExportApp(w http.ResponseWriter, r *http.Request) {
}
func (a *AppStruct) Download(w http.ResponseWriter, r *http.Request) {
format := r.FormValue("format")
fileName := r.FormValue("fileName")
tarFile := fmt.Sprintf("%s/%s/%s", handler.GetAppHandler().GetStaticDir(), format, fileName)
_, err := os.Stat(tarFile)
// return status code 502 if the file not exists.
if !os.IsExist(err) {
httputil.ReturnError(r, w, 502, fmt.Sprintf("Not found export app tar file: %s", tarFile))
return
}
http.ServeFile(w, r, tarFile)
}
func (a *AppStruct) Upload(w http.ResponseWriter, r *http.Request) {
//TODO
}
func (a *AppStruct) ImportApp(w http.ResponseWriter, r *http.Request) {
//TODO
}

View File

@ -18,12 +18,18 @@ import (
)
type AppAction struct {
MQClient pb.TaskQueueClient
MQClient pb.TaskQueueClient
staticDir string
}
func (a *AppAction) GetStaticDir() string {
return a.staticDir
}
func CreateAppManager(mqClient pb.TaskQueueClient) *AppAction {
return &AppAction{
MQClient: mqClient,
MQClient: mqClient,
staticDir: "/grdata/app",
}
}
@ -42,16 +48,18 @@ func (a *AppAction) Complete(tr *model.ExportAppStruct) error {
}
appName = unicode2zh(appName)
tr.SourceDir = fmt.Sprintf("/grdata/%s/%s-%s", tr.Body.Format, appName, tr.Body.Version)
tr.SourceDir = fmt.Sprintf("%s/%s/%s-%s", a.staticDir, tr.Body.Format, appName, tr.Body.Version)
return nil
}
func (a *AppAction) ExportApp(tr *model.ExportAppStruct) error {
// 保存元数据到组目录
if err := saveMetadata(tr); err != nil {
return util.CreateAPIHandleErrorFromDBError("Failed to export app", err)
}
// 构建MQ事件对象
mqBody, err := json.Marshal(model.BuildMQBodyFrom(tr))
if err != nil {
logrus.Error("Failed to encode json from ExportAppStruct:", err)
@ -69,6 +77,7 @@ func (a *AppAction) ExportApp(tr *model.ExportAppStruct) error {
return err
}
// 写入事件到MQ中
ctx, cancel := context.WithCancel(context.Background())
_, err = a.MQClient.Enqueue(ctx, eq)
cancel()
@ -82,9 +91,10 @@ func (a *AppAction) ExportApp(tr *model.ExportAppStruct) error {
}
func saveMetadata(tr *model.ExportAppStruct) error {
os.RemoveAll(tr.SourceDir)
// 创建应用组目录
os.MkdirAll(tr.SourceDir, 0755)
// 写入元数据到文件
err := ioutil.WriteFile(fmt.Sprintf("%s/metadata.json", tr.SourceDir), []byte(tr.Body.GroupMetadata), 0644)
if err != nil {
logrus.Error("Failed to save metadata", err)

View File

@ -22,7 +22,9 @@ import (
"net/url"
"time"
"fmt"
dbmodel "github.com/goodrain/rainbond/db/model"
"strings"
)
//ServiceGetCommon path参数
@ -1379,14 +1381,19 @@ type MQBody struct {
}
func NewAppStatusFrom(app *ExportAppStruct) *dbmodel.AppStatus {
tarFile := app.SourceDir + ".tar"
fields := strings.Split(tarFile, "/")
tarName := fields[len(fields)-1]
tarFileHref := fmt.Sprintf("/v2/app/download/%s/%s", app.Body.Format, tarName)
return &dbmodel.AppStatus{
GroupKey: app.Body.GroupKey,
Version: app.Body.Version,
Format: app.Body.Format,
EventID: app.Body.EventID,
SourceDir: app.SourceDir,
Status: "exporting",
TarFile: app.SourceDir + ".tar",
TimeStamp: time.Now().Nanosecond(),
GroupKey: app.Body.GroupKey,
Version: app.Body.Version,
Format: app.Body.Format,
EventID: app.Body.EventID,
SourceDir: app.SourceDir,
Status: "exporting",
TarFile: tarFile,
TarFileHref: tarFileHref,
TimeStamp: time.Now().Nanosecond(),
}
}

View File

@ -98,6 +98,16 @@ func (i *ExportApp) Run(timeout time.Duration) error {
// 组目录命名规则将组名中unicode转为中文并去掉空格"JAVA-ETCD\\u5206\\u4eab\\u7ec4" -> "JAVA-ETCD分享组"
func (i *ExportApp) exportRainbondAPP() error {
// 如果该应用已经打包过且是最新版,则跳过打包并返回成功
if ok := i.isLatest(); ok {
return nil
}
// 删除旧应用组目录,然后重新生成该应用包
if err := i.CleanSourceDir(); err != nil {
return err
}
// 保存用应镜像和slug包
if err := i.saveApps(); err != nil {
return err
@ -118,6 +128,16 @@ func (i *ExportApp) exportRainbondAPP() error {
// 组目录命名规则将组名中unicode转为中文并去掉空格"JAVA-ETCD\\u5206\\u4eab\\u7ec4" -> "JAVA-ETCD分享组"
func (i *ExportApp) exportDockerCompose() error {
// 如果该应用已经打包过且是最新版,则跳过打包并返回成功
if ok := i.isLatest(); ok {
return nil
}
// 删除旧应用组目录,然后重新生成该应用包
if err := i.CleanSourceDir(); err != nil {
return err
}
// 保存用应镜像和slug包
if err := i.saveApps(); err != nil {
return err
@ -166,6 +186,44 @@ func (i *ExportApp) GetLogger() event.Logger {
return i.Logger
}
// isLatest 如果该应用已经打包过且是最新版则返回true
func (i *ExportApp) isLatest() bool {
md5File := fmt.Sprintf("%s/metadata.json.md5", i.SourceDir)
_, err := os.Stat(md5File)
if !os.IsExist(err) {
logrus.Debug("The export app tar file is not found.")
return false
}
err = exec.Command("sh", "-c", fmt.Sprintf("cd %s ; md5sum -c metadata.json.md5", i.SourceDir)).Run()
if err != nil {
logrus.Debug("The export app tar file is not latest.")
return false
}
return true
}
func (i *ExportApp) CleanSourceDir() error {
metaFile := fmt.Sprintf("%s/metadata.json", i.SourceDir)
data, err := ioutil.ReadFile(metaFile)
if err != nil {
logrus.Error("Failed to read metadata file: ", err)
return err
}
os.RemoveAll(i.SourceDir)
os.MkdirAll(i.SourceDir, 0755)
if err := ioutil.WriteFile(metaFile, data, 0644); err != nil {
logrus.Error("Failed to write metadata file: ", err)
return err
}
return nil
}
//parseApps get apps array from metadata.json
func (i *ExportApp) parseApps() ([]gjson.Result, error) {
i.Logger.Info("解析应用信息", map[string]string{"step": "export-app", "status": "success"})
@ -173,7 +231,7 @@ func (i *ExportApp) parseApps() ([]gjson.Result, error) {
data, err := ioutil.ReadFile(fmt.Sprintf("%s/metadata.json", i.SourceDir))
if err != nil {
i.Logger.Error("导出应用失败,没有找到应用信息", map[string]string{"step": "read-metadata", "status": "failure"})
logrus.Error("Failed to export rainbond app: ", err)
logrus.Error("Failed to read metadata file: ", err)
return nil, err
}
@ -579,11 +637,18 @@ func (i *ExportApp) updateStatus(status string) error {
app.Status = status
app.TimeStamp = time.Now().Nanosecond()
if db.GetManager().AppDao().UpdateModel(app); err != nil {
if err := db.GetManager().AppDao().UpdateModel(app); err != nil {
err = errors.New(fmt.Sprintf("Failed to update app %s: %v", i.GroupKey, err))
return err
}
// 生成MD5值并写入到文件以便在下次收到该请求时决定是否该重新打包该应用
metadataFile := fmt.Sprintf("%s/metadata.json", i.SourceDir)
if err := exec.Command("sh", "-c", fmt.Sprintf("md5sum %s > %s.md5", metadataFile, metadataFile)).Run(); err != nil {
err = errors.New(fmt.Sprintf("Failed to create md5 file: %v", err))
return err
}
return nil
}

View File

@ -52,6 +52,7 @@ type AppDao interface {
DelDao
Get(groupKey, version string) (interface{}, error)
GetByEventId(eventId string) (interface{}, error)
DeleteModelByEventId(eventId string) error
}
//LicenseDao LicenseDao

View File

@ -1,14 +1,15 @@
package model
type AppStatus struct {
EventID string `gorm:"column:event_id;size:32;primary_key" json:"event_id"`
GroupKey string `gorm:"column:group_key;size:64" json:"group_key"`
Version string `gorm:"column:version;size:32" json:"version"`
Format string `gorm:"column:format;size:32" json:"format"` // only rainbond-app/docker-compose
SourceDir string `gorm:"column:source_dir;size:255" json:"source_dir"`
Status string `gorm:"column:status;size:32" json:"status"` // only exporting/importing/failed/success
TarFile string `gorm:"column:tar_file;size:255" json:"tar_file"`
TimeStamp int `gorm:"column:timestamp" json:"timestamp"`
EventID string `gorm:"column:event_id;size:32;primary_key" json:"event_id"`
GroupKey string `gorm:"column:group_key;size:64" json:"group_key"`
Version string `gorm:"column:version;size:32" json:"version"`
Format string `gorm:"column:format;size:32" json:"format"` // only rainbond-app/docker-compose
SourceDir string `gorm:"column:source_dir;size:255" json:"source_dir"`
Status string `gorm:"column:status;size:32" json:"status"` // only exporting/importing/failed/success
TarFile string `gorm:"column:tar_file;size:255" json:"tar_file"`
TarFileHref string `gorm:"column:tar_file_href;size:255" json:"tar_file_href"`
TimeStamp int `gorm:"column:timestamp" json:"timestamp"`
}
//TableName 表名

View File

@ -55,6 +55,15 @@ func (a *AppDaoImpl) DeleteModel(groupKey string, arg ...interface{}) error {
return a.DB.Where("group_key = ? and version = ?", groupKey, version).Delete(&app).Error
}
func (a *AppDaoImpl) DeleteModelByEventId(eventId string) error {
var app model.AppStatus
if ok := a.DB.Where("event_id = ?", eventId).Find(&app).RecordNotFound(); ok {
return nil
}
return a.DB.Where("event_id = ?", eventId).Delete(&app).Error
}
func (a *AppDaoImpl) Get(groupKey, version string) (interface{}, error) {
var app model.AppStatus
err := a.DB.Where("group_key = ? and version = ?", groupKey, version).First(&app).Error