2018-05-07 15:10:12 +08:00
|
|
|
|
// RAINBOND, Application Management Platform
|
|
|
|
|
// Copyright (C) 2014-2017 Goodrain Co., Ltd.
|
|
|
|
|
|
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
// (at your option) any later version. For any non-GPL usage of Rainbond,
|
|
|
|
|
// one or multiple Commercial Licenses authorized by Goodrain Co., Ltd.
|
|
|
|
|
// must be obtained first.
|
|
|
|
|
|
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
package exector
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"time"
|
|
|
|
|
|
2018-05-10 11:08:49 +08:00
|
|
|
|
"fmt"
|
2018-05-23 11:08:44 +08:00
|
|
|
|
"io/ioutil"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
|
2018-09-10 17:33:16 +08:00
|
|
|
|
"regexp"
|
|
|
|
|
|
2018-05-10 11:08:49 +08:00
|
|
|
|
"github.com/Sirupsen/logrus"
|
2018-05-07 15:10:12 +08:00
|
|
|
|
"github.com/docker/engine-api/client"
|
2018-09-14 14:35:30 +08:00
|
|
|
|
"github.com/goodrain/rainbond/builder"
|
2018-05-10 11:08:49 +08:00
|
|
|
|
"github.com/goodrain/rainbond/builder/sources"
|
2018-05-10 22:10:38 +08:00
|
|
|
|
"github.com/goodrain/rainbond/db"
|
2018-05-07 15:10:12 +08:00
|
|
|
|
"github.com/goodrain/rainbond/event"
|
2018-05-25 18:24:17 +08:00
|
|
|
|
"github.com/goodrain/rainbond/util"
|
2018-05-10 11:08:49 +08:00
|
|
|
|
"github.com/pkg/errors"
|
2018-05-07 15:10:12 +08:00
|
|
|
|
"github.com/tidwall/gjson"
|
2018-05-10 11:08:49 +08:00
|
|
|
|
"gopkg.in/yaml.v2"
|
2018-05-07 15:10:12 +08:00
|
|
|
|
)
|
|
|
|
|
|
2018-06-13 19:19:07 +08:00
|
|
|
|
var re = regexp.MustCompile(`\s`)
|
|
|
|
|
|
2018-05-07 15:10:12 +08:00
|
|
|
|
//ExportApp Export app to specified format(rainbond-app or dockercompose)
|
|
|
|
|
type ExportApp struct {
|
|
|
|
|
EventID string `json:"event_id"`
|
|
|
|
|
Format string `json:"format"`
|
|
|
|
|
SourceDir string `json:"source_dir"`
|
|
|
|
|
Logger event.Logger
|
|
|
|
|
DockerClient *client.Client
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
RegisterWorker("export_app", NewExportApp)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//NewExportApp create
|
2018-05-28 11:14:56 +08:00
|
|
|
|
func NewExportApp(in []byte, m *exectorManager) (TaskWorker, error) {
|
2018-05-07 15:10:12 +08:00
|
|
|
|
eventID := gjson.GetBytes(in, "event_id").String()
|
|
|
|
|
logger := event.GetManager().GetLogger(eventID)
|
|
|
|
|
return &ExportApp{
|
2018-05-11 11:15:17 +08:00
|
|
|
|
Format: gjson.GetBytes(in, "format").String(),
|
|
|
|
|
SourceDir: gjson.GetBytes(in, "source_dir").String(),
|
|
|
|
|
Logger: logger,
|
|
|
|
|
EventID: eventID,
|
2018-05-28 11:14:56 +08:00
|
|
|
|
DockerClient: m.DockerClient,
|
2018-05-23 11:08:44 +08:00
|
|
|
|
}, nil
|
2018-05-07 15:10:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Run Run
|
|
|
|
|
func (i *ExportApp) Run(timeout time.Duration) error {
|
|
|
|
|
if i.Format == "rainbond-app" {
|
2018-05-10 19:39:51 +08:00
|
|
|
|
err := i.exportRainbondAPP()
|
|
|
|
|
if err != nil {
|
|
|
|
|
i.updateStatus("failed")
|
|
|
|
|
}
|
|
|
|
|
return err
|
2018-05-10 17:45:23 +08:00
|
|
|
|
} else if i.Format == "docker-compose" {
|
2018-05-10 19:39:51 +08:00
|
|
|
|
err := i.exportDockerCompose()
|
|
|
|
|
if err != nil {
|
|
|
|
|
i.updateStatus("failed")
|
|
|
|
|
}
|
|
|
|
|
return err
|
2018-05-07 15:10:12 +08:00
|
|
|
|
}
|
2018-05-23 11:08:44 +08:00
|
|
|
|
return errors.New("Unsupported the format: " + i.Format)
|
2018-05-07 15:10:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// exportRainbondAPP export offline rainbond app
|
2018-05-10 11:08:49 +08:00
|
|
|
|
func (i *ExportApp) exportRainbondAPP() error {
|
2018-05-15 11:03:03 +08:00
|
|
|
|
if ok := i.isLatest(); ok {
|
2018-05-15 15:12:42 +08:00
|
|
|
|
i.updateStatus("success")
|
2018-05-15 11:03:03 +08:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// Delete the old application group directory and then regenerate the application package
|
2018-05-15 11:03:03 +08:00
|
|
|
|
if err := i.CleanSourceDir(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// Save application attachments
|
2018-05-10 11:08:49 +08:00
|
|
|
|
if err := i.saveApps(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2018-05-07 15:10:12 +08:00
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// Save the plugin attachments
|
2018-09-13 16:29:38 +08:00
|
|
|
|
if err := i.savePlugins(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// zip all file
|
2018-05-17 11:27:04 +08:00
|
|
|
|
if err := i.zip(); err != nil {
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
2018-05-07 15:10:12 +08:00
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// update export event status
|
2018-05-10 19:39:51 +08:00
|
|
|
|
if err := i.updateStatus("success"); err != nil {
|
2018-05-10 17:45:23 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return nil
|
2018-05-07 15:10:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// exportDockerCompose export app to docker compose app
|
2018-05-10 11:08:49 +08:00
|
|
|
|
func (i *ExportApp) exportDockerCompose() error {
|
2018-05-15 11:03:03 +08:00
|
|
|
|
if ok := i.isLatest(); ok {
|
2018-05-15 15:12:42 +08:00
|
|
|
|
i.updateStatus("success")
|
2018-05-15 11:03:03 +08:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// Delete the old application group directory and then regenerate the application package
|
2018-05-15 11:03:03 +08:00
|
|
|
|
if err := i.CleanSourceDir(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// Save application attachments
|
2018-05-10 11:08:49 +08:00
|
|
|
|
if err := i.saveApps(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2018-05-07 15:10:12 +08:00
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// Save runner image name
|
2018-05-10 11:08:49 +08:00
|
|
|
|
if err := i.exportRunnerImage(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2018-05-07 15:10:12 +08:00
|
|
|
|
|
2018-05-10 11:08:49 +08:00
|
|
|
|
// 在主目录中生成文件:docker-compose.yaml
|
2018-05-17 11:27:04 +08:00
|
|
|
|
if err := i.buildDockerComposeYaml(); err != nil {
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
2018-05-07 15:10:12 +08:00
|
|
|
|
|
2018-05-10 11:08:49 +08:00
|
|
|
|
// 生成应用启动脚本
|
2018-05-17 11:27:04 +08:00
|
|
|
|
if err := i.buildStartScript(); err != nil {
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
2018-05-07 15:10:12 +08:00
|
|
|
|
|
2018-05-10 11:08:49 +08:00
|
|
|
|
// 打包整个目录为tar包
|
2018-05-17 11:27:04 +08:00
|
|
|
|
if err := i.zip(); err != nil {
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
2018-05-07 15:10:12 +08:00
|
|
|
|
|
2018-05-10 17:45:23 +08:00
|
|
|
|
// 更新应用状态
|
2018-05-10 19:39:51 +08:00
|
|
|
|
if err := i.updateStatus("success"); err != nil {
|
2018-05-10 17:45:23 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return nil
|
2018-05-07 15:10:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Stop stop
|
|
|
|
|
func (i *ExportApp) Stop() error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Name return worker name
|
|
|
|
|
func (i *ExportApp) Name() string {
|
|
|
|
|
return "export_app"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//GetLogger GetLogger
|
|
|
|
|
func (i *ExportApp) GetLogger() event.Logger {
|
|
|
|
|
return i.Logger
|
|
|
|
|
}
|
2018-05-10 11:08:49 +08:00
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// isLatest Returns true if the application is packaged and up to date
|
2018-05-15 11:03:03 +08:00
|
|
|
|
func (i *ExportApp) isLatest() bool {
|
|
|
|
|
md5File := fmt.Sprintf("%s/metadata.json.md5", i.SourceDir)
|
2018-05-15 12:26:17 +08:00
|
|
|
|
if _, err := os.Stat(md5File); os.IsNotExist(err) {
|
|
|
|
|
logrus.Debug("The export app md5 file is not found: ", md5File)
|
2018-05-15 11:03:03 +08:00
|
|
|
|
return false
|
|
|
|
|
}
|
2018-05-15 12:26:17 +08:00
|
|
|
|
err := exec.Command("md5sum", "-c", md5File).Run()
|
2018-05-15 11:03:03 +08:00
|
|
|
|
if err != nil {
|
2018-09-11 11:59:01 +08:00
|
|
|
|
tarFile := i.SourceDir + ".tar"
|
|
|
|
|
if _, err := os.Stat(tarFile); os.IsNotExist(err) {
|
|
|
|
|
logrus.Debug("The export app tar file is not found. ")
|
|
|
|
|
return false
|
|
|
|
|
}
|
2018-05-15 11:03:03 +08:00
|
|
|
|
logrus.Debug("The export app tar file is not latest.")
|
|
|
|
|
return false
|
|
|
|
|
}
|
2018-05-15 12:43:39 +08:00
|
|
|
|
logrus.Debug("The export app tar file is latest.")
|
2018-05-15 11:03:03 +08:00
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
//CleanSourceDir clean export dir
|
2018-05-15 11:03:03 +08:00
|
|
|
|
func (i *ExportApp) CleanSourceDir() error {
|
2018-05-15 12:43:39 +08:00
|
|
|
|
logrus.Debug("Ready clean the source directory.")
|
2018-05-15 11:03:03 +08:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-10 11:08:49 +08:00
|
|
|
|
//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"})
|
|
|
|
|
|
|
|
|
|
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"})
|
2018-05-15 11:03:03 +08:00
|
|
|
|
logrus.Error("Failed to read metadata file: ", err)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
arr := gjson.GetBytes(data, "apps").Array()
|
|
|
|
|
if len(arr) < 1 {
|
|
|
|
|
i.Logger.Error("解析应用列表信息失败", map[string]string{"step": "parse-apps", "status": "failure"})
|
2018-09-10 17:33:16 +08:00
|
|
|
|
err := errors.New("Not found app in the metadata")
|
2018-05-10 20:18:14 +08:00
|
|
|
|
logrus.Error("Failed to get apps from json: ", err)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2018-05-15 11:30:31 +08:00
|
|
|
|
logrus.Debug("Successful parse apps array from metadata, count: ", len(arr))
|
2018-05-10 11:08:49 +08:00
|
|
|
|
|
|
|
|
|
return arr, nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:01:34 +08:00
|
|
|
|
//exportImage export image of app
|
2018-09-10 17:33:16 +08:00
|
|
|
|
func (i *ExportApp) exportImage(serviceDir string, app gjson.Result) error {
|
2018-05-10 18:32:14 +08:00
|
|
|
|
serviceName := app.Get("service_cname").String()
|
2018-05-10 11:08:49 +08:00
|
|
|
|
serviceName = unicode2zh(serviceName)
|
|
|
|
|
os.MkdirAll(serviceDir, 0755)
|
2018-06-08 17:37:57 +08:00
|
|
|
|
image := app.Get("share_image").String()
|
2018-05-10 11:08:49 +08:00
|
|
|
|
tarFileName := buildToLinuxFileName(image)
|
2018-09-10 15:36:20 +08:00
|
|
|
|
user := app.Get("service_image.hub_user").String()
|
|
|
|
|
pass := app.Get("service_image.hub_password").String()
|
2018-09-14 14:01:34 +08:00
|
|
|
|
// ignore runner image
|
2018-05-10 11:08:49 +08:00
|
|
|
|
if checkIsRunner(image) {
|
2018-05-11 13:45:07 +08:00
|
|
|
|
logrus.Debug("Skip the runner image: ", image)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
// docker pull image-name
|
2018-09-10 15:36:20 +08:00
|
|
|
|
_, err := sources.ImagePull(i.DockerClient, image, user, pass, i.Logger, 15)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
if err != nil {
|
2018-09-10 15:36:20 +08:00
|
|
|
|
return err
|
2018-05-10 11:08:49 +08:00
|
|
|
|
}
|
2018-09-10 17:33:16 +08:00
|
|
|
|
//change save app image name
|
|
|
|
|
imageName := sources.ImageNameWithNamespaceHandle(image)
|
|
|
|
|
saveImageName := fmt.Sprintf("%s/%s:%s", "goodrain.me", imageName.Name, imageName.Tag)
|
|
|
|
|
if err := sources.ImageTag(i.DockerClient, image, saveImageName, i.Logger, 2); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2018-05-10 11:08:49 +08:00
|
|
|
|
// save image to tar file
|
2018-09-10 17:33:16 +08:00
|
|
|
|
err = sources.ImageSave(i.DockerClient, saveImageName, fmt.Sprintf("%s/%s.image.tar", serviceDir, tarFileName), i.Logger)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
if err != nil {
|
2018-09-10 17:33:16 +08:00
|
|
|
|
i.Logger.Error(fmt.Sprintf("save image to local error:%s", image),
|
2018-05-10 11:08:49 +08:00
|
|
|
|
map[string]string{"step": "save-image", "status": "failure"})
|
2018-05-10 20:18:14 +08:00
|
|
|
|
logrus.Error("Failed to save image: ", err)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
2018-05-15 11:30:31 +08:00
|
|
|
|
logrus.Debug("Successful save image file: ", image)
|
2018-09-10 17:33:16 +08:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (i *ExportApp) exportSlug(serviceDir string, app gjson.Result) error {
|
|
|
|
|
shareSlugPath := app.Get("share_slug_path").String()
|
|
|
|
|
serviceName := app.Get("service_cname").String()
|
|
|
|
|
tarFileName := buildToLinuxFileName(shareSlugPath)
|
|
|
|
|
_, err := os.Stat(shareSlugPath)
|
|
|
|
|
if shareSlugPath != "" && err == nil {
|
|
|
|
|
logrus.Debug("The slug file was exist already, direct copy to service dir: ", shareSlugPath)
|
2018-09-14 14:01:34 +08:00
|
|
|
|
err = util.CopyFile(shareSlugPath, fmt.Sprintf("%s/%s", serviceDir, tarFileName))
|
2018-09-10 17:33:16 +08:00
|
|
|
|
if err == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2018-09-14 14:01:34 +08:00
|
|
|
|
// if local copy failure, try download it
|
2018-09-10 17:33:16 +08:00
|
|
|
|
logrus.Debugf("Failed to copy the slug file to service dir %s: %v", shareSlugPath, err)
|
|
|
|
|
}
|
2018-09-14 14:01:34 +08:00
|
|
|
|
// get slug save server (ftp) info
|
2018-09-10 17:33:16 +08:00
|
|
|
|
ftpHost := app.Get("service_slug.ftp_host").String()
|
|
|
|
|
ftpPort := app.Get("service_slug.ftp_port").String()
|
|
|
|
|
ftpUsername := app.Get("service_slug.ftp_username").String()
|
|
|
|
|
ftpPassword := app.Get("service_slug.ftp_password").String()
|
2018-05-10 11:08:49 +08:00
|
|
|
|
|
2018-09-10 17:33:16 +08:00
|
|
|
|
ftpClient, err := sources.NewSFTPClient(ftpUsername, ftpPassword, ftpHost, ftpPort)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logrus.Error("Failed to create ftp client: ", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
2018-09-14 14:01:34 +08:00
|
|
|
|
// download slug file
|
|
|
|
|
i.Logger.Info(fmt.Sprintf("Download service %s slug file", serviceName), map[string]string{"step": "get-slug", "status": "failure"})
|
2018-09-10 17:33:16 +08:00
|
|
|
|
err = ftpClient.DownloadFile(shareSlugPath, fmt.Sprintf("%s/%s", serviceDir, tarFileName), i.Logger)
|
|
|
|
|
ftpClient.Close()
|
|
|
|
|
if err != nil {
|
|
|
|
|
logrus.Errorf("Failed to download slug file for group %s: %v", i.SourceDir, err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
logrus.Debug("Successful download slug file: ", shareSlugPath)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-13 16:29:38 +08:00
|
|
|
|
func (i *ExportApp) savePlugins() error {
|
2018-09-14 14:35:30 +08:00
|
|
|
|
i.Logger.Info("Parsing plugin information", map[string]string{"step": "export-plugins", "status": "success"})
|
2018-09-13 16:29:38 +08:00
|
|
|
|
|
|
|
|
|
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 read metadata file: ", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
plugins := gjson.GetBytes(data, "plugins").Array()
|
|
|
|
|
|
|
|
|
|
for _, plugin := range plugins {
|
|
|
|
|
pluginName := plugin.Get("plugin_name").String()
|
|
|
|
|
pluginName = unicode2zh(pluginName)
|
|
|
|
|
pluginDir := fmt.Sprintf("%s/%s", i.SourceDir, pluginName)
|
|
|
|
|
os.MkdirAll(pluginDir, 0755)
|
|
|
|
|
image := plugin.Get("share_image").String()
|
|
|
|
|
tarFileName := buildToLinuxFileName(image)
|
|
|
|
|
user := plugin.Get("plugin_image.hub_user").String()
|
|
|
|
|
pass := plugin.Get("plugin_image.hub_password").String()
|
|
|
|
|
// docker pull image-name
|
|
|
|
|
_, err := sources.ImagePull(i.DockerClient, image, user, pass, i.Logger, 15)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
//change save app image name
|
|
|
|
|
imageName := sources.ImageNameWithNamespaceHandle(image)
|
|
|
|
|
saveImageName := fmt.Sprintf("%s/%s:%s", "goodrain.me", imageName.Name, imageName.Tag)
|
|
|
|
|
if err := sources.ImageTag(i.DockerClient, image, saveImageName, i.Logger, 2); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
// save image to tar file
|
|
|
|
|
err = sources.ImageSave(i.DockerClient, saveImageName, fmt.Sprintf("%s/%s.image.tar", pluginDir, tarFileName), i.Logger)
|
|
|
|
|
if err != nil {
|
|
|
|
|
i.Logger.Error(fmt.Sprintf("save plugin image to local error:%s", image),
|
|
|
|
|
map[string]string{"step": "save-plugin-image", "status": "failure"})
|
|
|
|
|
logrus.Error("Failed to save plugin image: ", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
logrus.Debug("Successful save plugin image file: ", image)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// save all app attachment
|
|
|
|
|
// dir naming rule:Convert unicode to Chinese in the component name and remove the empty,"2048\\u5e94\\u7528" -> "2048应用"
|
|
|
|
|
// Image naming rule: goodrain.me/percona-mysql:5.5_latest -> percona-mysqlTAG5.5_latest.image.tar
|
|
|
|
|
// slug naming rule: /app_publish/vzrd9po6/9d2635a7c59d4974bb4dc62f04/v1.0_20180207165207.tgz -> v1.0_20180207165207.tgz
|
2018-05-10 11:08:49 +08:00
|
|
|
|
func (i *ExportApp) saveApps() error {
|
|
|
|
|
apps, err := i.parseApps()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
i.Logger.Info("Start export app", map[string]string{"step": "export-app", "status": "success"})
|
2018-05-10 11:08:49 +08:00
|
|
|
|
|
|
|
|
|
for _, app := range apps {
|
2018-05-10 18:32:14 +08:00
|
|
|
|
serviceName := app.Get("service_cname").String()
|
2018-05-10 11:08:49 +08:00
|
|
|
|
serviceName = unicode2zh(serviceName)
|
|
|
|
|
serviceDir := fmt.Sprintf("%s/%s", i.SourceDir, serviceName)
|
|
|
|
|
os.MkdirAll(serviceDir, 0755)
|
2018-09-14 14:35:30 +08:00
|
|
|
|
logrus.Debug("Create directory for export app: ", serviceDir)
|
2018-05-10 18:32:14 +08:00
|
|
|
|
shareSlugPath := app.Get("share_slug_path").String()
|
2018-09-10 17:33:16 +08:00
|
|
|
|
shareImage := app.Get("share_image").String()
|
|
|
|
|
if shareSlugPath != "" {
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// app is slug type
|
2018-09-10 17:33:16 +08:00
|
|
|
|
if err := i.exportSlug(serviceDir, app); err != nil {
|
|
|
|
|
return err
|
2018-05-10 11:08:49 +08:00
|
|
|
|
}
|
2018-09-10 17:33:16 +08:00
|
|
|
|
continue
|
2018-05-10 11:08:49 +08:00
|
|
|
|
}
|
2018-09-10 17:33:16 +08:00
|
|
|
|
if shareImage != "" {
|
2018-05-11 11:15:17 +08:00
|
|
|
|
logrus.Infof("The service is image model deploy: %s", serviceName)
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// app is image type
|
2018-09-10 17:33:16 +08:00
|
|
|
|
if err := i.exportImage(serviceDir, app); err != nil {
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
2018-05-11 13:45:07 +08:00
|
|
|
|
continue
|
2018-05-10 11:08:49 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// unicode2zh 将unicode转为中文,并去掉空格
|
|
|
|
|
func unicode2zh(uText string) (context string) {
|
|
|
|
|
for i, char := range strings.Split(uText, `\\u`) {
|
|
|
|
|
if i < 1 {
|
|
|
|
|
context = char
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
length := len(char)
|
|
|
|
|
if length > 3 {
|
|
|
|
|
pre := char[:4]
|
|
|
|
|
zh, err := strconv.ParseInt(pre, 16, 32)
|
|
|
|
|
if err != nil {
|
|
|
|
|
context += char
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context += fmt.Sprintf("%c", zh)
|
|
|
|
|
|
|
|
|
|
if length > 4 {
|
|
|
|
|
context += char[4:]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context = strings.TrimSpace(context)
|
|
|
|
|
|
|
|
|
|
return context
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func checkIsRunner(image string) bool {
|
2018-09-19 18:51:16 +08:00
|
|
|
|
return strings.Contains(image, builder.RUNNERIMAGENAME)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (i *ExportApp) exportRunnerImage() error {
|
|
|
|
|
isExist := false
|
2018-05-11 23:49:02 +08:00
|
|
|
|
var image, tarFileName string
|
2018-05-11 13:45:07 +08:00
|
|
|
|
logrus.Debug("Ready export runner image")
|
2018-05-10 11:08:49 +08:00
|
|
|
|
apps, err := i.parseApps()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
for _, app := range apps {
|
|
|
|
|
image = app.Get("image").String()
|
2018-05-11 23:49:02 +08:00
|
|
|
|
tarFileName = buildToLinuxFileName(image)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
if checkIsRunner(image) {
|
2018-05-11 13:45:07 +08:00
|
|
|
|
logrus.Debug("Discovered runner image at service: ", app.Get("service_cname"))
|
2018-05-10 11:08:49 +08:00
|
|
|
|
isExist = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-05-15 11:30:31 +08:00
|
|
|
|
if !isExist {
|
|
|
|
|
logrus.Debug("Not discovered runner image in any service.")
|
2018-05-11 23:49:02 +08:00
|
|
|
|
return nil
|
|
|
|
|
}
|
2018-09-14 14:35:30 +08:00
|
|
|
|
_, err = sources.ImagePull(i.DockerClient, image, builder.REGISTRYUSER, builder.REGISTRYPASS, i.Logger, 20)
|
2018-05-11 23:49:02 +08:00
|
|
|
|
if err != nil {
|
2018-09-14 14:35:30 +08:00
|
|
|
|
i.Logger.Error(fmt.Sprintf("Pull image failure:%s", image),
|
2018-05-11 23:49:02 +08:00
|
|
|
|
map[string]string{"step": "pull-image", "status": "failure"})
|
|
|
|
|
logrus.Error("Failed to pull image: ", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = sources.ImageSave(i.DockerClient, image, fmt.Sprintf("%s/%s.image.tar", i.SourceDir, tarFileName), i.Logger)
|
|
|
|
|
if err != nil {
|
2018-09-14 14:35:30 +08:00
|
|
|
|
i.Logger.Error(fmt.Sprintf("Save image failure:%s", image),
|
2018-05-11 23:49:02 +08:00
|
|
|
|
map[string]string{"step": "save-image", "status": "failure"})
|
|
|
|
|
logrus.Error("Failed to save image: ", err)
|
|
|
|
|
return err
|
2018-05-10 11:08:49 +08:00
|
|
|
|
}
|
2018-05-15 11:30:31 +08:00
|
|
|
|
logrus.Debug("Successful download runner image: ", image)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
//DockerComposeYaml docker compose struct
|
2018-05-11 23:49:02 +08:00
|
|
|
|
type DockerComposeYaml struct {
|
|
|
|
|
Version string `yaml:"version"`
|
|
|
|
|
Volumes map[string]string `yaml:"volumes,omitempty"`
|
|
|
|
|
Services map[string]*Service `yaml:"services,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
//Service service
|
2018-05-10 11:08:49 +08:00
|
|
|
|
type Service struct {
|
|
|
|
|
Image string `yaml:"image"`
|
2018-05-11 23:49:02 +08:00
|
|
|
|
ContainerName string `yaml:"container_name,omitempty"`
|
|
|
|
|
Restart string `yaml:"restart,omitempty"`
|
|
|
|
|
NetworkMode string `yaml:"network_mode,omitempty"`
|
|
|
|
|
Volumes []string `yaml:"volumes,omitempty"`
|
|
|
|
|
Command string `yaml:"command,omitempty"`
|
|
|
|
|
Environment map[string]string `yaml:"environment,omitempty"`
|
2018-05-10 11:08:49 +08:00
|
|
|
|
Loggin struct {
|
2018-05-11 23:49:02 +08:00
|
|
|
|
Driver string `yaml:"driver,omitempty"`
|
2018-05-10 11:08:49 +08:00
|
|
|
|
Options struct {
|
2018-05-11 23:49:02 +08:00
|
|
|
|
MaxSize string `yaml:"max-size,omitempty"`
|
|
|
|
|
MaxFile string `yaml:"max-file,omitempty"`
|
2018-05-10 11:08:49 +08:00
|
|
|
|
}
|
2018-05-11 23:49:02 +08:00
|
|
|
|
} `yaml:"logging,omitempty"`
|
2018-05-10 11:08:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-17 11:27:04 +08:00
|
|
|
|
func (i *ExportApp) buildDockerComposeYaml() error {
|
2018-05-10 11:08:49 +08:00
|
|
|
|
// 因为在保存apps的步骤中更新了json文件,所以要重新加载
|
|
|
|
|
apps, err := i.parseApps()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
y := &DockerComposeYaml{
|
|
|
|
|
Version: "2.1",
|
2018-05-11 23:49:02 +08:00
|
|
|
|
Volumes: make(map[string]string, 5),
|
2018-05-10 11:08:49 +08:00
|
|
|
|
Services: make(map[string]*Service, 5),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i.Logger.Info("开始生成YAML文件", map[string]string{"step": "build-yaml", "status": "failure"})
|
2018-05-17 16:10:44 +08:00
|
|
|
|
logrus.Debug("Build docker compose yaml file in directory: ", i.SourceDir)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
|
|
|
|
|
for _, app := range apps {
|
2018-05-11 23:49:02 +08:00
|
|
|
|
image := app.Get("image").String()
|
2018-05-10 11:08:49 +08:00
|
|
|
|
appName := app.Get("service_cname").String()
|
2018-05-11 23:49:02 +08:00
|
|
|
|
appName = unicode2zh(appName)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
volumes := make([]string, 0, 3)
|
|
|
|
|
envs := make(map[string]string, 10)
|
|
|
|
|
|
2018-05-11 23:49:02 +08:00
|
|
|
|
// 如果该组件是镜像方式部署,需要做两件事
|
|
|
|
|
// 1. 在.volumes中创建一个volume
|
|
|
|
|
// 2. 在.services.volumes中做映射
|
|
|
|
|
for _, item := range app.Get("service_volume_map_list").Array() {
|
|
|
|
|
volumeName := item.Get("volume_name").String()
|
|
|
|
|
volumeName = buildToLinuxFileName(volumeName)
|
|
|
|
|
volumePath := item.Get("volume_path").String()
|
2018-05-10 11:08:49 +08:00
|
|
|
|
|
2018-05-11 23:49:02 +08:00
|
|
|
|
y.Volumes[volumeName] = ""
|
|
|
|
|
volumes = append(volumes, fmt.Sprintf("%s:%s", volumeName, volumePath))
|
2018-05-10 11:08:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-11 23:49:02 +08:00
|
|
|
|
// 如果该组件是源码方式部署,则挂载slug文件到runner容器内
|
|
|
|
|
if checkIsRunner(image) {
|
2018-05-15 14:06:57 +08:00
|
|
|
|
shareSlugPath := app.Get("share_slug_path").String()
|
|
|
|
|
tarFileName := buildToLinuxFileName(shareSlugPath)
|
|
|
|
|
volume := fmt.Sprintf("__GROUP_DIR__/%s/%s:/tmp/slug/slug.tgz", appName, tarFileName)
|
2018-05-11 23:49:02 +08:00
|
|
|
|
volumes = append(volumes, volume)
|
|
|
|
|
logrus.Debug("Mount the slug file to runner image: ", volume)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理环境变量
|
2018-05-10 11:08:49 +08:00
|
|
|
|
for k, v := range app.Get("service_env_map_list").Map() {
|
|
|
|
|
envs[k] = v.String()
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-11 23:49:02 +08:00
|
|
|
|
for _, item := range app.Get("service_connect_info_map_list").Array() {
|
|
|
|
|
key := item.Get("attr_name").String()
|
|
|
|
|
value := item.Get("attr_value").String()
|
|
|
|
|
envs[key] = value
|
|
|
|
|
}
|
2018-05-10 11:08:49 +08:00
|
|
|
|
|
2018-05-11 23:49:02 +08:00
|
|
|
|
// 如果该app依赖了另了个app-b,则把app-b中所有公开环境变量注入到该app
|
|
|
|
|
for _, item := range app.Get("dep_service_map_list").Array() {
|
|
|
|
|
serviceKey := item.Get("dep_service_key").String()
|
|
|
|
|
depEnvs := i.getPublicEnvByKey(serviceKey, &apps)
|
|
|
|
|
for k, v := range depEnvs {
|
|
|
|
|
envs[k] = v
|
|
|
|
|
}
|
2018-05-10 11:08:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
service := &Service{
|
|
|
|
|
Image: image,
|
|
|
|
|
ContainerName: appName,
|
|
|
|
|
Restart: "always",
|
2018-05-11 23:49:02 +08:00
|
|
|
|
NetworkMode: "host",
|
2018-05-10 11:08:49 +08:00
|
|
|
|
Volumes: volumes,
|
2018-05-11 23:49:02 +08:00
|
|
|
|
Command: app.Get("cmd").String(),
|
2018-05-10 11:08:49 +08:00
|
|
|
|
Environment: envs,
|
|
|
|
|
}
|
|
|
|
|
service.Loggin.Driver = "json-file"
|
|
|
|
|
service.Loggin.Options.MaxSize = "5m"
|
|
|
|
|
service.Loggin.Options.MaxFile = "2"
|
|
|
|
|
|
|
|
|
|
y.Services[appName] = service
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
content, err := yaml.Marshal(y)
|
|
|
|
|
if err != nil {
|
|
|
|
|
i.Logger.Error(fmt.Sprintf("生成YAML文件失败:%v", err), map[string]string{"step": "build-yaml", "status": "failure"})
|
2018-05-10 20:18:14 +08:00
|
|
|
|
logrus.Error("Failed to build yaml file: ", err)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = ioutil.WriteFile(fmt.Sprintf("%s/docker-compose.yaml", i.SourceDir), content, 0644)
|
|
|
|
|
if err != nil {
|
|
|
|
|
i.Logger.Error(fmt.Sprintf("创建YAML文件失败:%v", err), map[string]string{"step": "create-yaml", "status": "failure"})
|
2018-05-10 20:18:14 +08:00
|
|
|
|
logrus.Error("Failed to create yaml file: ", err)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-11 23:49:02 +08:00
|
|
|
|
func (i *ExportApp) getPublicEnvByKey(serviceKey string, apps *[]gjson.Result) map[string]string {
|
|
|
|
|
envs := make(map[string]string, 5)
|
|
|
|
|
for _, app := range *apps {
|
|
|
|
|
appKey := app.Get("service_key").String()
|
|
|
|
|
if appKey == serviceKey {
|
|
|
|
|
for _, item := range app.Get("service_connect_info_map_list").Array() {
|
|
|
|
|
key := item.Get("attr_name").String()
|
|
|
|
|
value := item.Get("attr_value").String()
|
|
|
|
|
envs[key] = value
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return envs
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-17 11:27:04 +08:00
|
|
|
|
func (i *ExportApp) buildStartScript() error {
|
2018-05-11 23:49:02 +08:00
|
|
|
|
if err := exec.Command("cp", "/src/export-app/run.sh", i.SourceDir).Run(); err != nil {
|
|
|
|
|
err = errors.New("Failed to generate start script to: " + i.SourceDir)
|
|
|
|
|
logrus.Error(err)
|
|
|
|
|
return err
|
2018-05-11 13:55:20 +08:00
|
|
|
|
}
|
2018-05-11 23:49:02 +08:00
|
|
|
|
|
2018-05-15 11:30:31 +08:00
|
|
|
|
logrus.Debug("Successful generate start script to: ", i.SourceDir)
|
2018-05-11 13:55:20 +08:00
|
|
|
|
return nil
|
2018-05-10 11:08:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-23 11:08:44 +08:00
|
|
|
|
//ErrorCallBack if run error will callback
|
|
|
|
|
func (i *ExportApp) ErrorCallBack(err error) {
|
2018-09-14 14:35:30 +08:00
|
|
|
|
i.updateStatus("failed")
|
2018-05-23 11:08:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-17 11:27:04 +08:00
|
|
|
|
func (i *ExportApp) zip() error {
|
2018-05-25 18:24:17 +08:00
|
|
|
|
err := util.Zip(i.SourceDir, i.SourceDir+".tar")
|
2018-05-10 11:08:49 +08:00
|
|
|
|
if err != nil {
|
2018-09-14 14:35:30 +08:00
|
|
|
|
i.Logger.Error("Export application failure:Zip failure", map[string]string{"step": "export-app", "status": "failure"})
|
2018-05-17 11:27:04 +08:00
|
|
|
|
logrus.Errorf("Failed to create tar file for group %s: %v", i.SourceDir, err)
|
2018-05-10 11:08:49 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
// create md5 file
|
2018-05-15 14:06:57 +08:00
|
|
|
|
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))
|
2018-05-15 15:12:42 +08:00
|
|
|
|
logrus.Error(err)
|
2018-05-15 14:06:57 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-14 14:35:30 +08:00
|
|
|
|
i.Logger.Info("Export application success", map[string]string{"step": "export-app", "status": "success"})
|
2018-05-15 15:12:42 +08:00
|
|
|
|
logrus.Info("Successful export app by event id: ", i.EventID)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (i *ExportApp) updateStatus(status string) error {
|
|
|
|
|
logrus.Debug("Update app status in database to: ", status)
|
2018-05-15 12:43:39 +08:00
|
|
|
|
res, err := db.GetManager().AppDao().GetByEventId(i.EventID)
|
2018-05-10 17:45:23 +08:00
|
|
|
|
if err != nil {
|
2018-05-15 15:12:42 +08:00
|
|
|
|
err = errors.New(fmt.Sprintf("Failed to get app %s from db: %v", i.EventID, err))
|
|
|
|
|
logrus.Error(err)
|
2018-05-10 17:45:23 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
2018-05-21 18:37:35 +08:00
|
|
|
|
res.Status = status
|
|
|
|
|
if err := db.GetManager().AppDao().UpdateModel(res); err != nil {
|
2018-05-15 15:12:42 +08:00
|
|
|
|
err = errors.New(fmt.Sprintf("Failed to update app %s: %v", i.EventID, err))
|
|
|
|
|
logrus.Error(err)
|
2018-05-10 17:45:23 +08:00
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-10 19:08:01 +08:00
|
|
|
|
// 只保留"/"后面的部分,并去掉不合法字符,一般用于把镜像名变为将要导出的文件名
|
2018-05-10 11:08:49 +08:00
|
|
|
|
func buildToLinuxFileName(fileName string) string {
|
|
|
|
|
if fileName == "" {
|
|
|
|
|
return fileName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
arr := strings.Split(fileName, "/")
|
2018-05-11 23:49:02 +08:00
|
|
|
|
|
|
|
|
|
if str := arr[len(arr)-1]; str == "" {
|
2018-06-10 18:23:24 +08:00
|
|
|
|
fileName = strings.Replace(fileName, "/", "---", -1)
|
2018-05-11 23:49:02 +08:00
|
|
|
|
} else {
|
|
|
|
|
fileName = str
|
|
|
|
|
}
|
2018-05-10 11:08:49 +08:00
|
|
|
|
|
2018-06-10 18:23:24 +08:00
|
|
|
|
fileName = strings.Replace(fileName, ":", "--", -1)
|
2018-06-13 19:19:07 +08:00
|
|
|
|
fileName = re.ReplaceAllString(fileName, "")
|
2018-05-10 11:08:49 +08:00
|
|
|
|
|
|
|
|
|
return fileName
|
|
|
|
|
}
|