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-24 10:02:34 +08:00
|
|
|
"bytes"
|
2018-08-23 18:50:34 +08:00
|
|
|
"encoding/json"
|
2018-08-23 14:06:21 +08:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2018-08-24 11:51:15 +08:00
|
|
|
"strconv"
|
2018-08-23 14:06:21 +08:00
|
|
|
|
|
|
|
"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-24 10:02:34 +08:00
|
|
|
re.Logger.Info(util.Translation("Start compiling the source code"), map[string]string{"step": "build-exector"})
|
2018-08-23 14:06:21 +08:00
|
|
|
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-24 10:02:34 +08:00
|
|
|
re.Logger.Error(util.Translation("Compiling the source code failure"), map[string]string{"step": "build-code", "status": "failure"})
|
2018-08-23 14:06:21 +08:00
|
|
|
logrus.Error("build perl error,", err.Error())
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
fileInfo, err := os.Stat(packageName)
|
|
|
|
if err != nil {
|
2018-08-24 10:02:34 +08:00
|
|
|
re.Logger.Error(util.Translation("Check that the build result failure"), map[string]string{"step": "build-code", "status": "failure"})
|
2018-08-23 14:06:21 +08:00
|
|
|
logrus.Error("build package check error", err.Error())
|
|
|
|
return nil, fmt.Errorf("build package failure")
|
|
|
|
}
|
|
|
|
if fileInfo.Size() == 0 {
|
2018-08-24 10:02:34 +08:00
|
|
|
re.Logger.Error(util.Translation("Source build failure and result slug size is 0"),
|
2018-08-23 14:06:21 +08:00
|
|
|
map[string]string{"step": "build-code", "status": "failure"})
|
|
|
|
return nil, fmt.Errorf("build package failure")
|
|
|
|
}
|
2018-08-24 10:02:34 +08:00
|
|
|
re.Logger.Info(util.Translation("Compiling the source code SUCCESS"), map[string]string{"step": "build-code", "status": "success"})
|
2018-08-23 14:06:21 +08:00
|
|
|
res := &Response{
|
|
|
|
MediumType: "slug",
|
2018-08-24 10:02:34 +08:00
|
|
|
MediumPath: packageName,
|
2018-08-23 14:06:21 +08:00
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
}
|
2018-08-23 18:50:34 +08:00
|
|
|
func (s *slugBuild) buildInContainer(re *Request) error {
|
|
|
|
dockerCmd := s.createCmd(re)
|
2018-08-24 10:02:34 +08:00
|
|
|
logrus.Debugf("docker cmd:%s", dockerCmd)
|
2018-08-23 18:50:34 +08:00
|
|
|
sourceCmd := s.createSourceCmd(re)
|
2018-08-24 10:02:34 +08:00
|
|
|
logrus.Debugf("source cmd:%s", sourceCmd)
|
2018-08-23 18:50:34 +08:00
|
|
|
source := exec.Command(sourceCmd[0], sourceCmd[1:]...)
|
2018-08-24 10:02:34 +08:00
|
|
|
source.Dir = re.SourceDir
|
2018-08-23 18:50:34 +08:00
|
|
|
cmd := exec.Command(dockerCmd[0], dockerCmd[1:]...)
|
|
|
|
closed := make(chan struct{})
|
|
|
|
defer close(closed)
|
2018-08-24 10:02:34 +08:00
|
|
|
var b bytes.Buffer
|
|
|
|
go s.readLog(&b, re.Logger, closed)
|
|
|
|
commands, err := NewPipeCommand(source, cmd)
|
2018-08-23 18:50:34 +08:00
|
|
|
if err != nil {
|
2018-08-24 10:02:34 +08:00
|
|
|
re.Logger.Error("build failure:"+err.Error(), map[string]string{"step": "build-code", "status": "failure"})
|
|
|
|
return fmt.Errorf("build in container error:%s", err.Error())
|
2018-08-23 18:50:34 +08:00
|
|
|
}
|
2018-08-24 10:02:34 +08:00
|
|
|
go s.readLog(commands.GetFinalStdout(), re.Logger, closed)
|
|
|
|
go s.readLog(commands.GetFinalStderr(), re.Logger, closed)
|
|
|
|
return commands.Run()
|
2018-08-23 18:50:34 +08:00
|
|
|
}
|
|
|
|
func (s *slugBuild) createSourceCmd(re *Request) []string {
|
|
|
|
var cmd []string
|
|
|
|
if re.ServerType == "svn" {
|
2018-08-24 10:02:34 +08:00
|
|
|
cmd = append(cmd, "tar", "-c", "--exclude=.svn", "./")
|
2018-08-23 18:50:34 +08:00
|
|
|
}
|
|
|
|
if re.ServerType == "git" {
|
2018-08-24 10:02:34 +08:00
|
|
|
cmd = append(cmd, "tar", "-c", "--exclude=.git", "./")
|
2018-08-23 18:50:34 +08:00
|
|
|
}
|
|
|
|
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 != "" {
|
2018-08-24 11:51:15 +08:00
|
|
|
cmd = append(cmd, "-e", fmt.Sprintf("%s=%s", k, strconv.Quote(v)))
|
2018-08-23 18:50:34 +08:00
|
|
|
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
|
|
|
|
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 {
|
2018-08-24 10:02:34 +08:00
|
|
|
logger.Error(lineStr, map[string]string{"step": "build-exector"})
|
2018-08-23 14:06:21 +08:00
|
|
|
}
|
|
|
|
}
|
2018-08-23 18:50:34 +08:00
|
|
|
select {
|
|
|
|
case <-closed:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
}
|
2018-08-23 14:06:21 +08:00
|
|
|
}
|
|
|
|
}
|
2018-08-24 10:02:34 +08:00
|
|
|
|
|
|
|
//PipeCommand PipeCommand
|
|
|
|
type PipeCommand struct {
|
|
|
|
stack []*exec.Cmd
|
|
|
|
finalStdout, finalStderr io.Reader
|
|
|
|
pipestack []*io.PipeWriter
|
|
|
|
}
|
|
|
|
|
|
|
|
//NewPipeCommand new pipe commands
|
|
|
|
func NewPipeCommand(stack ...*exec.Cmd) (*PipeCommand, error) {
|
|
|
|
var errorbuffer bytes.Buffer
|
|
|
|
pipestack := make([]*io.PipeWriter, len(stack)-1)
|
|
|
|
i := 0
|
|
|
|
for ; i < len(stack)-1; i++ {
|
|
|
|
stdinpipe, stdoutpipe := io.Pipe()
|
|
|
|
stack[i].Stdout = stdoutpipe
|
|
|
|
stack[i].Stderr = &errorbuffer
|
|
|
|
stack[i+1].Stdin = stdinpipe
|
|
|
|
pipestack[i] = stdoutpipe
|
|
|
|
}
|
|
|
|
finalStdout, err := stack[i].StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
finalStderr, err := stack[i].StderrPipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
pipeCommand := &PipeCommand{
|
|
|
|
stack: stack,
|
|
|
|
pipestack: pipestack,
|
|
|
|
finalStdout: finalStdout,
|
|
|
|
finalStderr: finalStderr,
|
|
|
|
}
|
|
|
|
return pipeCommand, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//Run Run
|
|
|
|
func (p *PipeCommand) Run() error {
|
|
|
|
return call(p.stack, p.pipestack)
|
|
|
|
}
|
|
|
|
|
|
|
|
//GetFinalStdout get final command stdout reader
|
|
|
|
func (p *PipeCommand) GetFinalStdout() io.Reader {
|
|
|
|
return p.finalStdout
|
|
|
|
}
|
|
|
|
|
|
|
|
//GetFinalStderr get final command stderr reader
|
|
|
|
func (p *PipeCommand) GetFinalStderr() io.Reader {
|
|
|
|
return p.finalStderr
|
|
|
|
}
|
|
|
|
|
|
|
|
func call(stack []*exec.Cmd, pipes []*io.PipeWriter) (err error) {
|
|
|
|
if stack[0].Process == nil {
|
|
|
|
if err = stack[0].Start(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(stack) > 1 {
|
|
|
|
if err = stack[1].Start(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err == nil {
|
|
|
|
pipes[0].Close()
|
|
|
|
err = call(stack[1:], pipes[1:])
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
return stack[0].Wait()
|
|
|
|
}
|