From b857175b05c688b138cef9a319c7dd571f35f214 Mon Sep 17 00:00:00 2001 From: Zhang Jiajun Date: Thu, 10 May 2018 17:45:23 +0800 Subject: [PATCH] [ADD] Add new feature to export app with docker-compose format. --- api/controller/app.go | 33 ++++++++++++++- api/handler/app.go | 66 +++++++++++++++++++++++++----- api/handler/handler.go | 6 +-- api/model/model.go | 18 ++++++++- builder/exector/export_app.go | 34 +++++++++++++++- db/dao/dao.go | 7 ++++ db/db.go | 1 + db/mysql/dao/app.go | 76 +++++++++++++++++++++++++++++++++++ db/mysql/dao_impl.go | 7 ++++ 9 files changed, 232 insertions(+), 16 deletions(-) create mode 100644 db/mysql/dao/app.go diff --git a/api/controller/app.go b/api/controller/app.go index 5503a56bc..2ac685417 100644 --- a/api/controller/app.go +++ b/api/controller/app.go @@ -7,6 +7,7 @@ import ( "github.com/goodrain/rainbond/api/handler" httputil "github.com/goodrain/rainbond/util/http" "github.com/goodrain/rainbond/api/model" + "github.com/goodrain/rainbond/db" ) type AppStruct struct {} @@ -18,12 +19,22 @@ func (a *AppStruct) ExportApp(w http.ResponseWriter, r *http.Request) { return } + if err := handler.GetAppHandler().Complete(&tr); err != nil { + return + } + err := handler.GetAppHandler().ExportApp(&tr) if err != nil { httputil.ReturnError(r, w, 500, fmt.Sprintf("Failed to export app: %v", err)) return } + app := model.NewAppStatusFrom(&tr) + if err := db.GetManager().AppDao().AddModel(app); err != nil { + httputil.ReturnError(r, w, 500, fmt.Sprintf("Failed to export app %s: %v", app.GroupKey, err)) + return + } + httputil.ReturnSuccess(r, w, nil) return } @@ -32,8 +43,28 @@ func (a *AppStruct) ImportApp(w http.ResponseWriter, r *http.Request) { //TODO } +// TODO 目前与ExportApp函数一致,以后可能会合并 func (a *AppStruct) ExportRunnableApp(w http.ResponseWriter, r *http.Request) { - //TODO + var tr model.ExportAppStruct + ok := httputil.ValidatorRequestStructAndErrorResponse(r, w, &tr.Body, nil) + if !ok { + return + } + + err := handler.GetAppHandler().ExportRunnableApp(&tr) + if err != nil { + httputil.ReturnError(r, w, 500, fmt.Sprintf("Failed to export runnable app: %v", err)) + return + } + + app := model.NewAppStatusFrom(&tr) + if err := db.GetManager().AppDao().AddModel(app); err != nil { + httputil.ReturnError(r, w, 500, fmt.Sprintf("Failed to export runnable app %s: %v", app.GroupKey, err)) + return + } + + httputil.ReturnSuccess(r, w, nil) + return } func (a *AppStruct) BackupApp(w http.ResponseWriter, r *http.Request) { diff --git a/api/handler/app.go b/api/handler/app.go index 33d38df7f..413f99053 100644 --- a/api/handler/app.go +++ b/api/handler/app.go @@ -21,13 +21,64 @@ type AppAction struct { MQClient pb.TaskQueueClient } -func CreateAppManager(mqClient pb.TaskQueueClient) AppAction { - return AppAction{ +func CreateAppManager(mqClient pb.TaskQueueClient) *AppAction { + return &AppAction{ MQClient: mqClient, } } +func (a *AppAction) Complete(tr *model.ExportAppStruct) error { + appName := gjson.Get(tr.Body.GroupMetadata, ".group_name").String() + if appName == "" { + err := errors.New("Failed to get group name form metadata.") + logrus.Error(err) + return err + } + + tr.Body.GroupName = appName + appName = unicode2zh(appName) + tr.SourceDir = fmt.Sprintf("/grdata/export-app/%s-%s", 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) + } + + mqBody, err := json.Marshal(BuildExportAppBody(tr)) + if err != nil { + logrus.Error("Failed to encode json from ExportAppStruct:", err) + return err + } + + ts := &db.BuildTaskStruct{ + TaskType: "export_app", + TaskBody: mqBody, + } + + eq, err := db.BuildTaskBuild(ts) + if err != nil { + logrus.Error("Failed to BuildTaskBuild for ExportApp:", err) + return err + } + + ctx, cancel := context.WithCancel(context.Background()) + _, err = a.MQClient.Enqueue(ctx, eq) + cancel() + if err != nil { + logrus.Error("Failed to Enqueue MQ for ExportApp:", err) + return err + } + logrus.Debugf("equeue mq build plugin from image success") + + return nil +} + + +// TODO 与ExportApp函数唯一不同的是导出目录,以后有可能合并 +func (a *AppAction) ExportRunnableApp(tr *model.ExportAppStruct) error { appName := gjson.Get(tr.Body.GroupMetadata, ".group_name").String() if appName == "" { err := errors.New("Failed to get group name form metadata.") @@ -37,7 +88,7 @@ func (a *AppAction) ExportApp(tr *model.ExportAppStruct) error { appName = unicode2zh(appName) - tr.SourceDir = fmt.Sprintf("/grdata/export-app/%s-%s", appName, tr.Body.Version) + tr.SourceDir = fmt.Sprintf("/grdata/export-runnable-app/%s-%s", appName, tr.Body.Version) if err := saveMetadata(tr); err != nil { return util.CreateAPIHandleErrorFromDBError("Failed to export app", err) @@ -89,13 +140,10 @@ type ExportAppBody struct { } func saveMetadata(tr *model.ExportAppStruct) error { - err := os.MkdirAll(tr.SourceDir, os.ModePerm) - if err != nil { - logrus.Error("Failed to save metadata", err) - return err - } + os.RemoveAll(tr.SourceDir) + os.MkdirAll(tr.SourceDir, 0755) - err = ioutil.WriteFile(fmt.Sprintf("%s/metadata.json", tr.SourceDir), []byte(tr.Body.GroupMetadata), 0644) + 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) return err diff --git a/api/handler/handler.go b/api/handler/handler.go index 1c6387ad9..af86efea2 100644 --- a/api/handler/handler.go +++ b/api/handler/handler.go @@ -89,8 +89,6 @@ func GetServiceManager() ServiceHandler { var defaultPluginHandler PluginHandler -var defaultAppHandler AppAction - //GetPluginManager get manager func GetPluginManager() PluginHandler { return defaultPluginHandler @@ -131,6 +129,8 @@ func GetEventHandler() EventHandler { return defaultEventHandler } -func GetAppHandler() AppAction { +var defaultAppHandler *AppAction + +func GetAppHandler() *AppAction { return defaultAppHandler } \ No newline at end of file diff --git a/api/model/model.go b/api/model/model.go index c882106d3..c1dc8df54 100644 --- a/api/model/model.go +++ b/api/model/model.go @@ -23,6 +23,7 @@ import ( "time" dbmodel "github.com/goodrain/rainbond/db/model" + "github.com/goodrain/rainbond/db/mysql/dao" ) //ServiceGetCommon path参数 @@ -1354,8 +1355,23 @@ type ExportAppStruct struct { Body struct { EventID string `json:"event_id"` GroupKey string `json:"group_key"` + GroupName string `json:"group_name"` Version string `json:"version"` - Format string `json:"format"` + Format string `json:"format"` // only rainbond-app/docker-compose GroupMetadata string `json:"group_metadata"` } } + +func NewAppStatusFrom(exportApp *ExportAppStruct) *dao.AppStatus { + return &dao.AppStatus{ + GroupKey: exportApp.Body.GroupKey, + GroupName: exportApp.Body.GroupName, // TODO 以后可能会去掉 + Version: exportApp.Body.Version, + Format: exportApp.Body.Format, + EventID: exportApp.Body.EventID, + SourceDir: exportApp.SourceDir, + Status: "exporting", + TarFile: exportApp.SourceDir + ".tar", + TimeStamp: time.Now().Nanosecond(), + } +} diff --git a/builder/exector/export_app.go b/builder/exector/export_app.go index ed91628ea..30f75e80d 100644 --- a/builder/exector/export_app.go +++ b/builder/exector/export_app.go @@ -36,6 +36,8 @@ import ( "path" "strconv" "strings" + "github.com/goodrain/rainbond/db/mysql/dao" + "github.com/goodrain/rainbond/db" ) //ExportApp Export app to specified format(rainbond-app or dockercompose) @@ -70,7 +72,7 @@ func NewExportApp(in []byte) TaskWorker { func (i *ExportApp) Run(timeout time.Duration) error { if i.Format == "rainbond-app" { return i.exportRainbondAPP() - } else if i.Format == "dockercompose" { + } else if i.Format == "docker-compose" { return i.exportDockerCompose() } return nil @@ -88,6 +90,11 @@ func (i *ExportApp) exportRainbondAPP() error { return err } + // 更新应用状态 + if err := i.updateStatus(); err != nil { + return err + } + return nil } @@ -118,6 +125,11 @@ func (i *ExportApp) exportDockerCompose() error { return err } + // 更新应用状态 + if err := i.updateStatus(); err != nil { + return err + } + return nil } @@ -463,7 +475,7 @@ func (i *ExportApp) generateTarFile() error { // /grdata/export-app/myapp-1.0 -> myapp-1.0 baseName := path.Base(i.SourceDir) // 打包整个目录为tar包 - err := exec.Command(fmt.Sprintf("cd %s ; tar -cf %s.tar %s", dirName, baseName, baseName)).Run() + err := exec.Command(fmt.Sprintf("cd %s ; rm -rf %s.tar ; tar -cf %s.tar %s", dirName, baseName, baseName, baseName)).Run() if err != nil { i.Logger.Error("打包应用失败", map[string]string{"step": "export-app", "status": "failure"}) logrus.Errorf("Failed to create tar file for group key %s: %v", i.GroupKey, err) @@ -475,6 +487,24 @@ func (i *ExportApp) generateTarFile() error { return nil } +func (i *ExportApp) updateStatus() error { + res, err := db.GetManager().AppDao().Get(i.GroupKey, i.Version) + if err != nil { + err = errors.New(fmt.Sprintf("Failed to get app %s from db: %v", i.GroupKey, err)) + return err + } + + app := res.(*dao.AppStatus) + app.Status = "success" + + if db.GetManager().AppDao().UpdateModel(app); err != nil { + err = errors.New(fmt.Sprintf("Failed to update app %s: %v", i.GroupKey, err)) + return err + } + + return nil +} + // 只保留"/"后面的部分,并去掉不合法字符 func buildToLinuxFileName(fileName string) string { if fileName == "" { diff --git a/db/dao/dao.go b/db/dao/dao.go index f01205178..31dfa36de 100644 --- a/db/dao/dao.go +++ b/db/dao/dao.go @@ -42,6 +42,13 @@ type TenantDao interface { GetTenantIDsByNames(names []string) ([]string, error) } +//TenantDao tenant dao +type AppDao interface { + Dao + DelDao + Get(groupKey, version string) (interface{}, error) +} + //LicenseDao LicenseDao type LicenseDao interface { Dao diff --git a/db/db.go b/db/db.go index b37bb558a..5c29e72a6 100644 --- a/db/db.go +++ b/db/db.go @@ -33,6 +33,7 @@ type Manager interface { CloseManager() error Begin() *gorm.DB LicenseDao() dao.LicenseDao + AppDao() dao.AppDao TenantDao() dao.TenantDao TenantDaoTransactions(db *gorm.DB) dao.TenantDao EventLogDao() dao.EventLogDao diff --git a/db/mysql/dao/app.go b/db/mysql/dao/app.go new file mode 100644 index 000000000..5477c57e8 --- /dev/null +++ b/db/mysql/dao/app.go @@ -0,0 +1,76 @@ +package dao + +import ( + "github.com/jinzhu/gorm" + "github.com/goodrain/rainbond/db/model" + "github.com/pkg/errors" +) + +type AppStatus struct { + GroupKey string `gorm:"column:group_key;size:64;primary_key"` + GroupName string `gorm:"column:group_name;size:64"` + Version string `gorm:"column:version;size:32"` + Format string `gorm:"column:format;size:32"` // only rainbond-app/docker-compose + EventID string `gorm:"column:event_id;size:32"` + SourceDir string `gorm:"column:source_dir;size:255"` + Status string `gorm:"column:status;size:32"` // only exporting/importing/failed/success + TarFile string `gorm:"column:tar_file;size:255"` + TimeStamp int `gorm:"column:timestamp"` +} + +//TableName 表名 +func (t *AppStatus) TableName() string { + return "app_status" +} + +type AppDaoImpl struct { + DB *gorm.DB +} + +func (a *AppDaoImpl) AddModel(mo model.Interface) error { + app, ok := mo.(*AppStatus) + if !ok { + return errors.New("Failed to convert interface to AppStatus") + } + + var old AppStatus + if ok := a.DB.Where("group_key = ? and version = ?", app.GroupKey, app.Version).Find(&old).RecordNotFound(); ok { + if err := a.DB.Create(app).Error; err != nil { + return err + } + } + + return nil +} + +func (a *AppDaoImpl) UpdateModel(mo model.Interface) error { + app, ok := mo.(*AppStatus) + if !ok { + return errors.New("Failed to convert interface to AppStatus") + } + + return a.DB.Table(app.TableName()). + Where("group_key = ? and version = ?", app.GroupKey, app.Version). + Update(app).Error +} + +func (a *AppDaoImpl) DeleteModel(groupKey string, arg ...interface{}) error { + if len(arg) < 1 { + return errors.New("Must define version for delete AppStatus in mysql.") + } + + version, ok := arg[0].(string) + if !ok { + return errors.New("Failed to convert interface to string") + } + + var app AppStatus + return a.DB.Where("group_key = ? and version = ?", groupKey, version).Delete(&app).Error +} + +func (a *AppDaoImpl) Get(groupKey, version string) (interface{}, error) { + var app AppStatus + err := a.DB.Where("group_key = ? and version = ?", groupKey, version).First(&app).Error + + return &app, err +} diff --git a/db/mysql/dao_impl.go b/db/mysql/dao_impl.go index 574d99ae7..e34112f1b 100644 --- a/db/mysql/dao_impl.go +++ b/db/mysql/dao_impl.go @@ -437,3 +437,10 @@ func (m *Manager) RegionProcotolsDaoTransactions(db *gorm.DB) dao.RegionProcotol DB: db, } } + +//AppDao 应用导入导出数据 +func (m *Manager) AppDao() dao.AppDao { + return &mysqldao.AppDaoImpl{ + DB: m.db, + } +}