[ADD] Add new feature to export app with docker-compose format.

This commit is contained in:
Zhang Jiajun 2018-05-10 17:45:23 +08:00
parent 597957d4fc
commit b857175b05
9 changed files with 232 additions and 16 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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
}

View File

@ -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(),
}
}

View File

@ -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 == "" {

View File

@ -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

View File

@ -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

76
db/mysql/dao/app.go Normal file
View File

@ -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
}

View File

@ -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,
}
}