still a wip. container that is launched to run a build
This commit is contained in:
parent
7762ecac90
commit
9fef3a23d2
6 changed files with 735 additions and 0 deletions
18
cmd/drone-build/Dockerfile
Normal file
18
cmd/drone-build/Dockerfile
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Docker image for Drone's git-clone plugin
|
||||||
|
#
|
||||||
|
# docker build --rm=true -t drone/drone-build .
|
||||||
|
|
||||||
|
FROM library/golang:1.4
|
||||||
|
|
||||||
|
# copy the local package files to the container's workspace.
|
||||||
|
#ADD . /go/src/github.com/drone/drone-build/
|
||||||
|
|
||||||
|
# build the program inside the container.
|
||||||
|
#RUN go get github.com/drone/drone-build/... && \
|
||||||
|
# go install github.com/drone/drone-build
|
||||||
|
|
||||||
|
|
||||||
|
ADD drone-build /go/bin/
|
||||||
|
|
||||||
|
# run the git-clone plugin when the container starts
|
||||||
|
ENTRYPOINT ["/go/bin/drone-build"]
|
203
cmd/drone-build/client.go
Normal file
203
cmd/drone-build/client.go
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/samalba/dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrTimeout = errors.New("Timeout")
|
||||||
|
ErrLogging = errors.New("Logs not available")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// options to fetch the stdout and stderr logs
|
||||||
|
logOpts = &dockerclient.LogOptions{
|
||||||
|
Stdout: true,
|
||||||
|
Stderr: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// options to fetch the stdout and stderr logs
|
||||||
|
// by tailing the output.
|
||||||
|
logOptsTail = &dockerclient.LogOptions{
|
||||||
|
Follow: true,
|
||||||
|
Stdout: true,
|
||||||
|
Stderr: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// client is a wrapper around the default Docker client
|
||||||
|
// that tracks all created containers ensures some default
|
||||||
|
// configurations are in place.
|
||||||
|
type client struct {
|
||||||
|
dockerclient.Client
|
||||||
|
info *dockerclient.ContainerInfo
|
||||||
|
names []string // names of created containers
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClient(docker dockerclient.Client) (*client, error) {
|
||||||
|
|
||||||
|
// creates an ambassador container
|
||||||
|
conf := &dockerclient.ContainerConfig{}
|
||||||
|
conf.HostConfig = dockerclient.HostConfig{}
|
||||||
|
conf.Entrypoint = []string{"/bin/sleep"}
|
||||||
|
conf.Cmd = []string{"86400"}
|
||||||
|
conf.Image = "busybox"
|
||||||
|
conf.Volumes = map[string]struct{}{}
|
||||||
|
conf.Volumes["/drone"] = struct{}{}
|
||||||
|
info, err := daemon(docker, conf, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &client{Client: docker, info: info}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateContainer creates a container and internally
|
||||||
|
// caches its container id.
|
||||||
|
func (c *client) CreateContainer(conf *dockerclient.ContainerConfig, name string) (string, error) {
|
||||||
|
conf.Env = append(conf.Env, "affinity:container=="+c.info.Id)
|
||||||
|
id, err := c.Client.CreateContainer(conf, name)
|
||||||
|
if err == nil {
|
||||||
|
c.names = append(c.names, id)
|
||||||
|
}
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartContainer starts a container and links to an
|
||||||
|
// ambassador container sharing the build machiens volume.
|
||||||
|
func (c *client) StartContainer(id string, conf *dockerclient.HostConfig) error {
|
||||||
|
conf.VolumesFrom = append(conf.VolumesFrom, c.info.Id)
|
||||||
|
if len(conf.NetworkMode) == 0 {
|
||||||
|
conf.NetworkMode = "container:" + c.info.Id
|
||||||
|
}
|
||||||
|
return c.Client.StartContainer(id, conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy will terminate and destroy all containers that
|
||||||
|
// were created by this client.
|
||||||
|
func (c *client) Destroy() error {
|
||||||
|
for _, id := range c.names {
|
||||||
|
c.Client.KillContainer(id, "9")
|
||||||
|
c.Client.RemoveContainer(id, true, true)
|
||||||
|
}
|
||||||
|
c.Client.KillContainer(c.info.Id, "9")
|
||||||
|
return c.Client.RemoveContainer(c.info.Id, true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(client dockerclient.Client, conf *dockerclient.ContainerConfig, pull bool) (*dockerclient.ContainerInfo, error) {
|
||||||
|
// force-pull the image if specified.
|
||||||
|
// TEMPORARY while we are in beta mode we should always re-pull drone plugins
|
||||||
|
if pull { //|| strings.HasPrefix(conf.Image, "plugins/") {
|
||||||
|
client.PullImage(conf.Image, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempts to create the contianer
|
||||||
|
id, err := client.CreateContainer(conf, "")
|
||||||
|
if err != nil {
|
||||||
|
// and pull the image and re-create if that fails
|
||||||
|
client.PullImage(conf.Image, nil)
|
||||||
|
id, err = client.CreateContainer(conf, "")
|
||||||
|
// make sure the container is removed in
|
||||||
|
// the event of a creation error.
|
||||||
|
if len(id) != 0 {
|
||||||
|
client.RemoveContainer(id, true, true)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensures the container is always stopped
|
||||||
|
// and ready to be removed.
|
||||||
|
defer func() {
|
||||||
|
client.StopContainer(id, 5)
|
||||||
|
client.KillContainer(id, "9")
|
||||||
|
}()
|
||||||
|
|
||||||
|
// fetches the container information.
|
||||||
|
info, err := client.InspectContainer(id)
|
||||||
|
if err != nil {
|
||||||
|
client.RemoveContainer(id, true, true)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// channel listening for errors while the
|
||||||
|
// container is running async.
|
||||||
|
errc := make(chan error, 1)
|
||||||
|
infoc := make(chan *dockerclient.ContainerInfo, 1)
|
||||||
|
go func() {
|
||||||
|
|
||||||
|
// starts the container
|
||||||
|
err := client.StartContainer(id, &conf.HostConfig)
|
||||||
|
if err != nil {
|
||||||
|
errc <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// blocks and waits for the container to finish
|
||||||
|
// by streaming the logs (to /dev/null). Ideally
|
||||||
|
// we could use the `wait` function instead
|
||||||
|
rc, err := client.ContainerLogs(id, logOptsTail)
|
||||||
|
if err != nil {
|
||||||
|
errc <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
StdCopy(os.Stdout, os.Stdout, rc)
|
||||||
|
|
||||||
|
// fetches the container information
|
||||||
|
info, err := client.InspectContainer(id)
|
||||||
|
if err != nil {
|
||||||
|
errc <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
infoc <- info
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case info := <-infoc:
|
||||||
|
return info, nil
|
||||||
|
case err := <-errc:
|
||||||
|
return info, err
|
||||||
|
// TODO checkout net.Context and cancel
|
||||||
|
// case <-time.After(timeout):
|
||||||
|
// return info, ErrTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func daemon(client dockerclient.Client, conf *dockerclient.ContainerConfig, pull bool) (*dockerclient.ContainerInfo, error) {
|
||||||
|
// force-pull the image
|
||||||
|
if pull {
|
||||||
|
client.PullImage(conf.Image, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempts to create the contianer
|
||||||
|
id, err := client.CreateContainer(conf, "")
|
||||||
|
if err != nil {
|
||||||
|
// and pull the image and re-create if that fails
|
||||||
|
client.PullImage(conf.Image, nil)
|
||||||
|
id, err = client.CreateContainer(conf, "")
|
||||||
|
// make sure the container is removed in
|
||||||
|
// the event of a creation error.
|
||||||
|
if len(id) != 0 {
|
||||||
|
client.RemoveContainer(id, true, true)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetches the container information
|
||||||
|
info, err := client.InspectContainer(id)
|
||||||
|
if err != nil {
|
||||||
|
client.RemoveContainer(id, true, true)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// starts the container
|
||||||
|
err = client.StartContainer(id, &conf.HostConfig)
|
||||||
|
return info, err
|
||||||
|
}
|
124
cmd/drone-build/copy.go
Normal file
124
cmd/drone-build/copy.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StdWriterPrefixLen = 8
|
||||||
|
StdWriterFdIndex = 0
|
||||||
|
StdWriterSizeIndex = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
type StdType [StdWriterPrefixLen]byte
|
||||||
|
|
||||||
|
var (
|
||||||
|
Stdin StdType = StdType{0: 0}
|
||||||
|
Stdout StdType = StdType{0: 1}
|
||||||
|
Stderr StdType = StdType{0: 2}
|
||||||
|
)
|
||||||
|
|
||||||
|
type StdWriter struct {
|
||||||
|
io.Writer
|
||||||
|
prefix StdType
|
||||||
|
sizeBuf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrInvalidStdHeader = errors.New("Unrecognized input header")
|
||||||
|
|
||||||
|
// StdCopy is a modified version of io.Copy.
|
||||||
|
//
|
||||||
|
// StdCopy will demultiplex `src`, assuming that it contains two streams,
|
||||||
|
// previously multiplexed together using a StdWriter instance.
|
||||||
|
// As it reads from `src`, StdCopy will write to `dstout` and `dsterr`.
|
||||||
|
//
|
||||||
|
// StdCopy will read until it hits EOF on `src`. It will then return a nil error.
|
||||||
|
// In other words: if `err` is non nil, it indicates a real underlying error.
|
||||||
|
//
|
||||||
|
// `written` will hold the total number of bytes written to `dstout` and `dsterr`.
|
||||||
|
func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) {
|
||||||
|
var (
|
||||||
|
buf = make([]byte, 32*1024+StdWriterPrefixLen+1)
|
||||||
|
bufLen = len(buf)
|
||||||
|
nr, nw int
|
||||||
|
er, ew error
|
||||||
|
out io.Writer
|
||||||
|
frameSize int
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Make sure we have at least a full header
|
||||||
|
for nr < StdWriterPrefixLen {
|
||||||
|
var nr2 int
|
||||||
|
nr2, er = src.Read(buf[nr:])
|
||||||
|
nr += nr2
|
||||||
|
if er == io.EOF {
|
||||||
|
if nr < StdWriterPrefixLen {
|
||||||
|
return written, nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if er != nil {
|
||||||
|
return 0, er
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the first byte to know where to write
|
||||||
|
switch buf[StdWriterFdIndex] {
|
||||||
|
case 0:
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
// Write on stdout
|
||||||
|
out = dstout
|
||||||
|
case 2:
|
||||||
|
// Write on stderr
|
||||||
|
out = dsterr
|
||||||
|
default:
|
||||||
|
return 0, ErrInvalidStdHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the size of the frame
|
||||||
|
frameSize = int(binary.BigEndian.Uint32(buf[StdWriterSizeIndex : StdWriterSizeIndex+4]))
|
||||||
|
|
||||||
|
// Check if the buffer is big enough to read the frame.
|
||||||
|
// Extend it if necessary.
|
||||||
|
if frameSize+StdWriterPrefixLen > bufLen {
|
||||||
|
buf = append(buf, make([]byte, frameSize+StdWriterPrefixLen-bufLen+1)...)
|
||||||
|
bufLen = len(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// While the amount of bytes read is less than the size of the frame + header, we keep reading
|
||||||
|
for nr < frameSize+StdWriterPrefixLen {
|
||||||
|
var nr2 int
|
||||||
|
nr2, er = src.Read(buf[nr:])
|
||||||
|
nr += nr2
|
||||||
|
if er == io.EOF {
|
||||||
|
if nr < frameSize+StdWriterPrefixLen {
|
||||||
|
return written, nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if er != nil {
|
||||||
|
return 0, er
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the retrieved frame (without header)
|
||||||
|
nw, ew = out.Write(buf[StdWriterPrefixLen : frameSize+StdWriterPrefixLen])
|
||||||
|
if ew != nil {
|
||||||
|
return 0, ew
|
||||||
|
}
|
||||||
|
// If the frame has not been fully written: error
|
||||||
|
if nw != frameSize {
|
||||||
|
return 0, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
written += int64(nw)
|
||||||
|
|
||||||
|
// Move the rest of the buffer to the beginning
|
||||||
|
copy(buf, buf[frameSize+StdWriterPrefixLen:])
|
||||||
|
// Move the index
|
||||||
|
nr -= frameSize + StdWriterPrefixLen
|
||||||
|
}
|
||||||
|
}
|
156
cmd/drone-build/main.go
Normal file
156
cmd/drone-build/main.go
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/drone/drone/common"
|
||||||
|
"github.com/samalba/dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
clone = flag.Bool("clone", false, "")
|
||||||
|
build = flag.Bool("build", false, "")
|
||||||
|
publish = flag.Bool("publish", false, "")
|
||||||
|
deploy = flag.Bool("deploy", false, "")
|
||||||
|
notify = flag.Bool("notify", false, "")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
ctx, err := parseContext()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error launching build container.", err)
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
createClone(ctx)
|
||||||
|
|
||||||
|
// creates the Docker client, connecting to the
|
||||||
|
// linked Docker daemon
|
||||||
|
docker, err := dockerclient.NewDockerClient("unix:///var/run/docker.sock", nil)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error connecting to build server.", err)
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a wrapper Docker client that uses an ambassador
|
||||||
|
// container to create a pod-like environment.
|
||||||
|
client, err := newClient(docker)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error starting build server pod", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
ctx.client = client
|
||||||
|
defer client.Destroy()
|
||||||
|
|
||||||
|
// performs some initial parsing and pre-processing steps
|
||||||
|
// prior to executing our build tasks.
|
||||||
|
err = setup(ctx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error processing .drone.yml file.", err)
|
||||||
|
client.Destroy()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var execs []execFunc
|
||||||
|
if *clone {
|
||||||
|
execs = append(execs, execClone)
|
||||||
|
}
|
||||||
|
if *build {
|
||||||
|
execs = append(execs, execSetup)
|
||||||
|
execs = append(execs, execCompose)
|
||||||
|
execs = append(execs, execBuild)
|
||||||
|
}
|
||||||
|
if *publish {
|
||||||
|
execs = append(execs, execPublish)
|
||||||
|
}
|
||||||
|
if *deploy {
|
||||||
|
execs = append(execs, execDeploy)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through and execute each step.
|
||||||
|
for i, exec_ := range execs {
|
||||||
|
code, err := exec_(ctx)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("00%d Error executing build\n", i+1)
|
||||||
|
fmt.Println(err)
|
||||||
|
code = 255
|
||||||
|
}
|
||||||
|
if code != 0 {
|
||||||
|
ctx.Build.ExitCode = code
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally execute notification steps.
|
||||||
|
if *notify {
|
||||||
|
execNotify(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Destroy()
|
||||||
|
os.Exit(ctx.Build.ExitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createClone(c *Context) error {
|
||||||
|
c.Clone = &common.Clone{
|
||||||
|
Netrc: c.Netrc,
|
||||||
|
Keypair: c.Keys,
|
||||||
|
Remote: c.Repo.Clone,
|
||||||
|
Origin: c.Repo.Clone,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Clone.Origin = c.Repo.Clone
|
||||||
|
c.Clone.Remote = c.Repo.Clone
|
||||||
|
c.Clone.Sha = c.Commit.Sha
|
||||||
|
c.Clone.Ref = c.Commit.Ref
|
||||||
|
c.Clone.Branch = c.Commit.Branch
|
||||||
|
// TODO move this to the main app (github package)
|
||||||
|
if strings.HasPrefix(c.Clone.Branch, "refs/heads/") {
|
||||||
|
c.Clone.Branch = c.Clone.Branch[11:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO we should also pass the SourceSha, SourceBranch, etc
|
||||||
|
// to the clone object for merge requests from bitbucket, gitlab, et al
|
||||||
|
// if len(c.Commit.PullRequest) != 0 {
|
||||||
|
// }
|
||||||
|
|
||||||
|
url_, err := url.Parse(c.Repo.Link)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Clone.Dir = filepath.Join("/drone/src", url_.Host, c.Repo.FullName)
|
||||||
|
|
||||||
|
// attempt to extract the clone path. i'm not a huge fan of
|
||||||
|
// this, by the way, but for now we'll keep it.
|
||||||
|
// TODO consider moving this to a transform?
|
||||||
|
pathv, ok := c.Conf.Clone.Config["path"]
|
||||||
|
if ok {
|
||||||
|
path, ok := pathv.(string)
|
||||||
|
if ok {
|
||||||
|
c.Clone.Dir = filepath.Join("/drone/src", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseContext() (*Context, error) {
|
||||||
|
c := &Context{}
|
||||||
|
for i, arg := range os.Args {
|
||||||
|
if arg == "--" && len(os.Args) != i+1 {
|
||||||
|
buf := bytes.NewBufferString(os.Args[i+1])
|
||||||
|
err := json.NewDecoder(buf).Decode(c)
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := json.NewDecoder(os.Stdin).Decode(c)
|
||||||
|
return c, err
|
||||||
|
}
|
142
cmd/drone-build/run.go
Normal file
142
cmd/drone-build/run.go
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/drone/drone/common"
|
||||||
|
"github.com/drone/drone/parser"
|
||||||
|
"github.com/drone/drone/parser/inject"
|
||||||
|
"github.com/samalba/dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
// Links *common.Link
|
||||||
|
Clone *common.Clone `json:"clone"`
|
||||||
|
Repo *common.Repo `json:"repo"`
|
||||||
|
Commit *common.Commit `json:"commit"`
|
||||||
|
Build *common.Build `json:"build"`
|
||||||
|
Keys *common.Keypair `json:"keys"`
|
||||||
|
Netrc *common.Netrc `json:"netrc"`
|
||||||
|
Yaml []byte `json:"yaml"`
|
||||||
|
Conf *common.Config `json:"-"`
|
||||||
|
infos []*dockerclient.ContainerInfo
|
||||||
|
client dockerclient.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup(c *Context) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// inject the matrix parameters into the yaml
|
||||||
|
injected := inject.Inject(string(c.Yaml), c.Build.Environment)
|
||||||
|
c.Conf, err = parser.ParseSingle(injected, parser.DefaultOpts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// and append the matrix parameters as environment
|
||||||
|
// variables for the build
|
||||||
|
for k, v := range c.Build.Environment {
|
||||||
|
env := k + "=" + v
|
||||||
|
c.Conf.Build.Environment = append(c.Conf.Build.Environment, env)
|
||||||
|
}
|
||||||
|
|
||||||
|
// and append drone, jenkins, travis and other
|
||||||
|
// environment variables that may be of use.
|
||||||
|
for k, v := range toEnv(c) {
|
||||||
|
env := k + "=" + v
|
||||||
|
c.Conf.Build.Environment = append(c.Conf.Build.Environment, env)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type execFunc func(c *Context) (int, error)
|
||||||
|
|
||||||
|
func execClone(c *Context) (int, error) {
|
||||||
|
conf := toContainerConfig(c.Conf.Clone)
|
||||||
|
conf.Cmd = toCommand(c, c.Conf.Clone)
|
||||||
|
info, err := run(c.client, conf, c.Conf.Clone.Pull)
|
||||||
|
if err != nil {
|
||||||
|
return 255, err
|
||||||
|
}
|
||||||
|
return info.State.ExitCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func execBuild(c *Context) (int, error) {
|
||||||
|
conf := toContainerConfig(c.Conf.Build)
|
||||||
|
conf.Entrypoint = []string{"/bin/bash", "-e"}
|
||||||
|
conf.Cmd = []string{"/drone/bin/build.sh"}
|
||||||
|
info, err := run(c.client, conf, c.Conf.Build.Pull)
|
||||||
|
if err != nil {
|
||||||
|
return 255, err
|
||||||
|
}
|
||||||
|
return info.State.ExitCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func execSetup(c *Context) (int, error) {
|
||||||
|
conf := toContainerConfig(c.Conf.Setup)
|
||||||
|
conf.Cmd = toCommand(c, c.Conf.Setup)
|
||||||
|
info, err := run(c.client, conf, c.Conf.Setup.Pull)
|
||||||
|
if err != nil {
|
||||||
|
return 255, err
|
||||||
|
}
|
||||||
|
return info.State.ExitCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func execDeploy(c *Context) (int, error) {
|
||||||
|
return runSteps(c, c.Conf.Deploy)
|
||||||
|
}
|
||||||
|
|
||||||
|
func execPublish(c *Context) (int, error) {
|
||||||
|
return runSteps(c, c.Conf.Publish)
|
||||||
|
}
|
||||||
|
|
||||||
|
func execNotify(c *Context) (int, error) {
|
||||||
|
return runSteps(c, c.Conf.Notify)
|
||||||
|
}
|
||||||
|
|
||||||
|
func execCompose(c *Context) (int, error) {
|
||||||
|
for _, step := range c.Conf.Compose {
|
||||||
|
conf := toContainerConfig(step)
|
||||||
|
_, err := daemon(c.client, conf, step.Pull)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trace(s string) string {
|
||||||
|
cmd := fmt.Sprintf("$ %s\n", s)
|
||||||
|
encoded := base64.StdEncoding.EncodeToString([]byte(cmd))
|
||||||
|
return fmt.Sprintf("echo %s | base64 --decode\n", encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newline(s string) string {
|
||||||
|
return fmt.Sprintf("%s\n", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runSteps(c *Context, steps map[string]*common.Step) (int, error) {
|
||||||
|
for _, step := range steps {
|
||||||
|
|
||||||
|
// verify the step matches the branch
|
||||||
|
// and other specifications
|
||||||
|
if step.Condition == nil ||
|
||||||
|
!step.Condition.MatchOwner(c.Repo.Owner) ||
|
||||||
|
!step.Condition.MatchBranch(c.Clone.Branch) ||
|
||||||
|
!step.Condition.MatchMatrix(c.Build.Environment) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := toContainerConfig(step)
|
||||||
|
conf.Cmd = toCommand(c, step)
|
||||||
|
info, err := run(c.client, conf, step.Pull)
|
||||||
|
if err != nil {
|
||||||
|
return 255, err
|
||||||
|
} else if info.State.ExitCode != 0 {
|
||||||
|
return info.State.ExitCode, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
92
cmd/drone-build/util.go
Normal file
92
cmd/drone-build/util.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/drone/drone/common"
|
||||||
|
"github.com/samalba/dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
// helper function that converts the build step to
|
||||||
|
// a containerConfig for use with the dockerclient
|
||||||
|
func toContainerConfig(step *common.Step) *dockerclient.ContainerConfig {
|
||||||
|
config := &dockerclient.ContainerConfig{
|
||||||
|
Image: step.Image,
|
||||||
|
Env: step.Environment,
|
||||||
|
Cmd: step.Command,
|
||||||
|
Entrypoint: step.Entrypoint,
|
||||||
|
WorkingDir: step.WorkingDir,
|
||||||
|
HostConfig: dockerclient.HostConfig{
|
||||||
|
Privileged: step.Privileged,
|
||||||
|
NetworkMode: step.NetworkMode,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Volumes = map[string]struct{}{}
|
||||||
|
for _, path := range step.Volumes {
|
||||||
|
if strings.Index(path, ":") == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.Split(path, ":")
|
||||||
|
config.Volumes[parts[1]] = struct{}{}
|
||||||
|
config.HostConfig.Binds = append(config.HostConfig.Binds, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to inject drone-specific environment
|
||||||
|
// variables into the container.
|
||||||
|
func toEnv(c *Context) map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"CI": "true",
|
||||||
|
"BUILD_DIR": c.Clone.Dir,
|
||||||
|
"BUILD_ID": strconv.Itoa(c.Commit.Sequence),
|
||||||
|
"BUILD_NUMBER": strconv.Itoa(c.Commit.Sequence),
|
||||||
|
"JOB_NAME": c.Repo.FullName,
|
||||||
|
"WORKSPACE": c.Clone.Dir,
|
||||||
|
"GIT_BRANCH": c.Clone.Branch,
|
||||||
|
"GIT_COMMIT": c.Clone.Sha,
|
||||||
|
|
||||||
|
"DRONE": "true",
|
||||||
|
"DRONE_REPO": c.Repo.FullName,
|
||||||
|
"DRONE_BUILD": strconv.Itoa(c.Commit.Sequence),
|
||||||
|
"DRONE_BRANCH": c.Clone.Branch,
|
||||||
|
"DRONE_COMMIT": c.Clone.Sha,
|
||||||
|
"DRONE_DIR": c.Clone.Dir,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to encode the build step to
|
||||||
|
// a json string. Primarily used for plugins, which
|
||||||
|
// expect a json encoded string in stdin or arg[1].
|
||||||
|
func toCommand(c *Context, step *common.Step) []string {
|
||||||
|
p := payload{
|
||||||
|
c.Repo,
|
||||||
|
c.Commit,
|
||||||
|
c.Build,
|
||||||
|
c.Clone,
|
||||||
|
step.Config,
|
||||||
|
}
|
||||||
|
return []string{p.Encode()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// payload represents the payload of a plugin
|
||||||
|
// that is serialized and sent to the plugin in JSON
|
||||||
|
// format via stdin or arg[1].
|
||||||
|
type payload struct {
|
||||||
|
Repo *common.Repo `json:"repo"`
|
||||||
|
Commit *common.Commit `json:"commit"`
|
||||||
|
Build *common.Build `json:"build"`
|
||||||
|
Clone *common.Clone `json:"clone"`
|
||||||
|
|
||||||
|
Config map[string]interface{} `json:"vargs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode encodes the payload in JSON format.
|
||||||
|
func (p *payload) Encode() string {
|
||||||
|
out, _ := json.Marshal(p)
|
||||||
|
return string(out)
|
||||||
|
}
|
Loading…
Reference in a new issue