woodpecker/runner/builtin/runner.go
2015-05-05 01:04:20 -07:00

223 lines
4.9 KiB
Go

package builtin
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"time"
"github.com/drone/drone/common"
"github.com/drone/drone/queue"
"github.com/samalba/dockerclient"
log "github.com/Sirupsen/logrus"
)
var (
// Defult docker host address
DefaultHost = "unix:///var/run/docker.sock"
// Docker host address from environment variable
DockerHost = os.Getenv("DOCKER_HOST")
)
func init() {
// if the environment doesn't specify a DOCKER_HOST
// we should use the default Docker socket.
if len(DockerHost) == 0 {
DockerHost = DefaultHost
}
}
type Runner struct {
Updater
}
func (r *Runner) Run(w *queue.Work) error {
var workers []*worker
var client dockerclient.Client
defer func() {
recover()
// ensures that all containers have been removed
// from the host machine.
for _, worker := range workers {
worker.Remove()
}
// if any part of the build fails and leaves
// behind orphan sub-builds we need to cleanup
// after ourselves.
if w.Build.State == common.StateRunning {
// if any tasks are running or pending
// we should mark them as complete.
for _, t := range w.Build.Tasks {
if t.State == common.StateRunning {
t.State = common.StateError
t.Finished = time.Now().UTC().Unix()
t.Duration = t.Finished - t.Started
}
if t.State == common.StatePending {
t.State = common.StateError
t.Started = time.Now().UTC().Unix()
t.Finished = time.Now().UTC().Unix()
t.Duration = 0
}
r.SetTask(w.Repo, w.Build, t)
}
// mark the build as complete (with error)
w.Build.State = common.StateError
w.Build.Duration = w.Build.Finished - w.Build.Started
w.Build.Finished = time.Now().UTC().Unix()
r.SetBuild(w.Repo, w.Build)
}
}()
// marks the build as running
w.Build.Started = time.Now().UTC().Unix()
w.Build.State = common.StateRunning
err := r.SetBuild(w.Repo, w.Build)
if err != nil {
return err
}
// create the Docker client. In this version of Drone (alpha)
// we do not spread builds across clients, but this can and
// (probably) will change in the future.
client, err = dockerclient.NewDockerClient(DockerHost, nil)
if err != nil {
return err
}
// loop through and execute the build and
// clone steps for each build task.
for _, task := range w.Build.Tasks {
// marks the task as running
task.State = common.StateRunning
task.Started = time.Now().UTC().Unix()
err = r.SetTask(w.Repo, w.Build, task)
if err != nil {
return err
}
work := &work{
Repo: w.Repo,
Build: w.Build,
Keys: w.Keys,
Yaml: w.Yaml,
Task: task,
}
in, err := json.Marshal(work)
if err != nil {
return err
}
worker := newWorkerTimeout(client, w.Repo.Timeout+10) // 10 minute buffer
workers = append(workers, worker)
cname := cname(w.Repo.FullName, w.Build.Number, task.Number)
state, err := worker.Build(cname, in)
switch {
case err == ErrTimeout:
task.State = common.StateKilled
case err != nil:
task.State = common.StateError
case state != 0:
task.ExitCode = state
task.State = common.StateFailure
default:
task.State = common.StateSuccess
}
// send the logs to the datastore
rc, err := worker.Logs()
if err != nil {
return err
}
err = r.SetLogs(w.Repo, w.Build, task, rc)
if err != nil {
return err
}
// update the task in the datastore
task.Finished = time.Now().UTC().Unix()
task.Duration = task.Finished - task.Started
err = r.SetTask(w.Repo, w.Build, task)
if err != nil {
return err
}
}
// update the build state if any of the sub-tasks
// had a non-success status
w.Build.State = common.StateSuccess
for _, task := range w.Build.Tasks {
if task.State != common.StateSuccess {
w.Build.State = task.State
break
}
}
err = r.SetBuild(w.Repo, w.Build)
if err != nil {
return err
}
// loop through and execute the notifications and
// the destroy all containers afterward.
for i, task := range w.Build.Tasks {
work := &work{
Repo: w.Repo,
Build: w.Build,
Keys: w.Keys,
Yaml: w.Yaml,
Task: task,
}
in, err := json.Marshal(work)
if err != nil {
return err
}
workers[i].Notify(in)
break
}
return nil
}
func (r *Runner) Cancel(repo string, build, task int) error {
client, err := dockerclient.NewDockerClient(DockerHost, nil)
if err != nil {
return err
}
return client.StopContainer(cname(repo, build, task), 30)
}
func (r *Runner) Logs(repo string, build, task int) (io.ReadCloser, error) {
client, err := dockerclient.NewDockerClient(DockerHost, nil)
if err != nil {
return nil, err
}
return client.ContainerLogs(cname(repo, build, task), logOptsTail)
}
func cname(repo string, number, task int) string {
s := fmt.Sprintf("%s/%d/%d", repo, number, task)
h := sha1.New()
h.Write([]byte(s))
hash := hex.EncodeToString(h.Sum(nil))[:10]
return fmt.Sprintf("drone-%s", hash)
}
func (r *Runner) Poll(q queue.Queue) {
for {
w := q.Pull()
q.Ack(w)
err := r.Run(w)
if err != nil {
log.Error(err)
}
}
}