2018-07-11 11:49:55 +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 build
|
2018-08-23 14:06:21 +08:00
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bufio"
|
2018-08-23 18:50:34 +08:00
|
|
|
|
"encoding/json"
|
2018-08-23 14:06:21 +08:00
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
|
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
|
|
|
"github.com/goodrain/rainbond/event"
|
2018-08-23 18:50:34 +08:00
|
|
|
|
"github.com/goodrain/rainbond/util"
|
2018-08-23 14:06:21 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func slugBuilder() (Build, error) {
|
|
|
|
|
return &slugBuild{}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type slugBuild struct {
|
|
|
|
|
tgzDir string
|
|
|
|
|
buildCacheDir string
|
|
|
|
|
sourceDir string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *slugBuild) Build(re *Request) (*Response, error) {
|
2018-08-23 18:50:34 +08:00
|
|
|
|
//handle cache dir
|
|
|
|
|
if _, ok := re.BuildEnvs["NO_CACHE"]; ok {
|
|
|
|
|
os.RemoveAll(re.CacheDir)
|
|
|
|
|
}
|
2018-08-23 14:06:21 +08:00
|
|
|
|
re.Logger.Info("开始编译代码包", map[string]string{"step": "build-exector"})
|
|
|
|
|
s.tgzDir = fmt.Sprintf("/grdata/build/tenant/%s/slug/%s", re.TenantID, re.ServiceID)
|
|
|
|
|
s.buildCacheDir = fmt.Sprintf("/cache/build/%s/cache/%s", re.TenantID, re.ServiceID)
|
|
|
|
|
packageName := fmt.Sprintf("%s/%s.tgz", s.tgzDir, re.DeployVersion)
|
2018-08-23 18:50:34 +08:00
|
|
|
|
if err := s.buildInContainer(re); err != nil {
|
2018-08-23 14:06:21 +08:00
|
|
|
|
re.Logger.Error("编译代码包失败", map[string]string{"step": "build-code", "status": "failure"})
|
|
|
|
|
logrus.Error("build perl error,", err.Error())
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
fileInfo, err := os.Stat(packageName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
re.Logger.Error("构建代码包检测失败", map[string]string{"step": "build-code", "status": "failure"})
|
|
|
|
|
logrus.Error("build package check error", err.Error())
|
|
|
|
|
return nil, fmt.Errorf("build package failure")
|
|
|
|
|
}
|
|
|
|
|
if fileInfo.Size() == 0 {
|
|
|
|
|
re.Logger.Error(fmt.Sprintf("构建失败! 构建包大小为0 name:%s", packageName),
|
|
|
|
|
map[string]string{"step": "build-code", "status": "failure"})
|
|
|
|
|
return nil, fmt.Errorf("build package failure")
|
|
|
|
|
}
|
|
|
|
|
re.Logger.Info("代码构建完成", map[string]string{"step": "build-code", "status": "success"})
|
|
|
|
|
res := &Response{
|
|
|
|
|
MediumType: "slug",
|
|
|
|
|
MediumPath: s.tgzDir,
|
|
|
|
|
}
|
|
|
|
|
return res, nil
|
|
|
|
|
}
|
2018-08-23 18:50:34 +08:00
|
|
|
|
func (s *slugBuild) buildInContainer(re *Request) error {
|
|
|
|
|
dockerCmd := s.createCmd(re)
|
|
|
|
|
sourceCmd := s.createSourceCmd(re)
|
|
|
|
|
source := exec.Command(sourceCmd[0], sourceCmd[1:]...)
|
|
|
|
|
read, err := source.StdoutPipe()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer read.Close()
|
|
|
|
|
cmd := exec.Command(dockerCmd[0], dockerCmd[1:]...)
|
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
stderr, err := cmd.StderrPipe()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
cmd.Stdin = read
|
|
|
|
|
closed := make(chan struct{})
|
|
|
|
|
defer close(closed)
|
|
|
|
|
go s.readLog(stdout, re.Logger, closed)
|
|
|
|
|
go s.readLog(stderr, re.Logger, closed)
|
|
|
|
|
if err := source.Start(); err != nil {
|
|
|
|
|
if re.Logger != nil {
|
|
|
|
|
re.Logger.Error(fmt.Sprintf("builder:Packaged source error"), map[string]string{"step": "build-exector"})
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("tar source dir error, %s", err.Error())
|
|
|
|
|
}
|
|
|
|
|
err = cmd.Start()
|
|
|
|
|
if err != nil {
|
|
|
|
|
if re.Logger != nil {
|
|
|
|
|
re.Logger.Error(fmt.Sprintf("builder:%v", err), map[string]string{"step": "build-exector"})
|
|
|
|
|
}
|
|
|
|
|
logrus.Errorf("start build container error:%s", err.Error())
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = source.Wait()
|
|
|
|
|
if err != nil {
|
|
|
|
|
if re.Logger != nil {
|
|
|
|
|
re.Logger.Error(fmt.Sprintf("builder:%v", err), map[string]string{"step": "build-exector"})
|
|
|
|
|
}
|
|
|
|
|
logrus.Errorf("wait build container error:%s", err.Error())
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = cmd.Wait()
|
|
|
|
|
if err != nil {
|
|
|
|
|
if re.Logger != nil {
|
|
|
|
|
re.Logger.Error(fmt.Sprintf("builder:%v", err), map[string]string{"step": "build-exector"})
|
|
|
|
|
}
|
|
|
|
|
logrus.Errorf("wait build container error:%s", err.Error())
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
func (s *slugBuild) createSourceCmd(re *Request) []string {
|
|
|
|
|
var cmd []string
|
|
|
|
|
if re.ServerType == "svn" {
|
|
|
|
|
cmd = append(cmd, "tar", "-c", "--exclude=.svn", re.SourceDir)
|
|
|
|
|
}
|
|
|
|
|
if re.ServerType == "git" {
|
|
|
|
|
cmd = append(cmd, "tar", "-c", "--exclude=.git", re.SourceDir)
|
|
|
|
|
}
|
|
|
|
|
return cmd
|
|
|
|
|
}
|
|
|
|
|
func (s *slugBuild) createCmd(re *Request) []string {
|
|
|
|
|
var cmd []string
|
|
|
|
|
if ok, _ := util.FileExists("/var/run/docker.sock"); ok {
|
|
|
|
|
cmd = append(cmd, "docker")
|
|
|
|
|
} else {
|
|
|
|
|
// not permissions
|
|
|
|
|
cmd = append(cmd, "sudo", "-P", "docker")
|
|
|
|
|
}
|
|
|
|
|
cmd = append(cmd, "run", "-i", "--net=host", "--rm", "--name", re.ServiceID[:8]+"_"+re.DeployVersion)
|
|
|
|
|
//handle cache mount
|
|
|
|
|
cmd = append(cmd, "-v", re.CacheDir+":/tmp/cache:rw")
|
|
|
|
|
cmd = append(cmd, "-v", s.tgzDir+":/tmp/slug:rw")
|
|
|
|
|
//handle stdin and stdout
|
|
|
|
|
cmd = append(cmd, "-a", "stdin", "-a", "stdout")
|
|
|
|
|
//handle env
|
|
|
|
|
for k, v := range re.BuildEnvs {
|
|
|
|
|
if k != "" {
|
|
|
|
|
cmd = append(cmd, "-e", fmt.Sprintf(`"%s=%s"`, k, v))
|
|
|
|
|
if k == "PROC_ENV" {
|
|
|
|
|
var mapdata = make(map[string]interface{})
|
|
|
|
|
if err := json.Unmarshal(util.ToByte(v), &mapdata); err == nil {
|
|
|
|
|
if runtime, ok := mapdata["runtimes"]; ok {
|
|
|
|
|
cmd = append(cmd, "-e", fmt.Sprintf(`"%s=%s"`, "RUNTIME", runtime.(string)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cmd = append(cmd, "-e", "SLUG_VERSION="+re.DeployVersion)
|
|
|
|
|
cmd = append(cmd, "-e", "SERVICE_ID="+re.ServiceID)
|
|
|
|
|
cmd = append(cmd, "-e", "TENANT_ID="+re.TenantID)
|
|
|
|
|
cmd = append(cmd, "-e", "LANGUAGE="+re.Lang.String())
|
|
|
|
|
//handle image
|
|
|
|
|
cmd = append(cmd, "goodrain.me/builder", "local")
|
|
|
|
|
return cmd
|
|
|
|
|
}
|
2018-08-23 14:06:21 +08:00
|
|
|
|
func (s *slugBuild) ShowExec(command string, params []string, logger event.Logger) error {
|
|
|
|
|
cmd := exec.Command(command, params...)
|
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
stderr, err := cmd.StderrPipe()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2018-08-23 18:50:34 +08:00
|
|
|
|
closed := make(chan struct{})
|
|
|
|
|
defer close(closed)
|
|
|
|
|
go s.readLog(stdout, logger, closed)
|
|
|
|
|
go s.readLog(stderr, logger, closed)
|
2018-08-23 14:06:21 +08:00
|
|
|
|
err = cmd.Start()
|
|
|
|
|
if err != nil {
|
|
|
|
|
if logger != nil {
|
|
|
|
|
logger.Error(fmt.Sprintf("builder:%v", err), map[string]string{"step": "build-exector"})
|
|
|
|
|
}
|
|
|
|
|
logrus.Errorf("start build container error:%s", err.Error())
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = cmd.Wait()
|
|
|
|
|
if err != nil {
|
|
|
|
|
if logger != nil {
|
|
|
|
|
logger.Error(fmt.Sprintf("builder:%v", err), map[string]string{"step": "build-exector"})
|
|
|
|
|
}
|
|
|
|
|
logrus.Errorf("wait build container error:%s", err.Error())
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-23 18:50:34 +08:00
|
|
|
|
func (s *slugBuild) readLog(stderr io.Reader, logger event.Logger, closed chan struct{}) {
|
2018-08-23 14:06:21 +08:00
|
|
|
|
readerr := bufio.NewReader(stderr)
|
|
|
|
|
for {
|
|
|
|
|
line, _, err := readerr.ReadLine()
|
|
|
|
|
if err != nil {
|
|
|
|
|
if err != io.EOF {
|
|
|
|
|
logrus.Errorf("Read build container log error:%s", err.Error())
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if logger != nil {
|
|
|
|
|
lineStr := string(line)
|
|
|
|
|
if len(lineStr) > 0 {
|
|
|
|
|
logger.Error(fmt.Sprintf("builder:%s", lineStr), map[string]string{"step": "build-exector"})
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-23 18:50:34 +08:00
|
|
|
|
select {
|
|
|
|
|
case <-closed:
|
|
|
|
|
return
|
|
|
|
|
default:
|
|
|
|
|
}
|
2018-08-23 14:06:21 +08:00
|
|
|
|
}
|
|
|
|
|
}
|