From d55d28436b489c2ed023dd9037a665fa17a0a113 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Mon, 18 May 2015 10:05:58 -0700 Subject: [PATCH] adding code for a drone build agent --- Dockerfile | 19 +++++ Makefile | 2 + cmd/drone-agent/Dockerfile | 9 +++ cmd/drone-agent/main.go | 151 +++++++++++++++++++++++++++++++++++++ cmd/drone-agent/updater.go | 75 ++++++++++++++++++ cmd/drone-build/Dockerfile | 2 +- cmd/drone-server/drone.go | 7 ++ 7 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 Dockerfile create mode 100644 cmd/drone-agent/Dockerfile create mode 100644 cmd/drone-agent/main.go create mode 100644 cmd/drone-agent/updater.go diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..4eb996aa --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +# Docker image for Drone's slack notification plugin +# +# docker build --rm=true -t drone/drone-server . + +FROM golang:1.4.2 +ENV DRONE_SERVER_PORT :80 + +ADD . /gopath/src/github.com/drone/drone/ +WORKDIR /gopath/src/github.com/drone/drone + +RUN apt-get update \ + && apt-get install -y libsqlite3-dev \ + && git clone git://github.com/gin-gonic/gin.git $GOPATH/src/github.com/gin-gonic/gin \ + && go get -u github.com/jteeuwen/go-bindata/... \ + && make bindata deps \ + && make build + +EXPOSE 80 +ENTRYPOINT ["/gopath/src/github.com/drone/drone/bin/drone"] \ No newline at end of file diff --git a/Makefile b/Makefile index 882484ac..05fdead7 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,8 @@ test: build: go build -o bin/drone -ldflags "-X main.revision $(SHA) -X main.version $(VERSION).$(SHA)" github.com/drone/drone/cmd/drone-server + go build -o bin/drone -ldflags "-X main.revision $(SHA) -X main.version $(VERSION).$(SHA)" github.com/drone/drone/cmd/drone-agent + clean: find . -name "*.out" -delete diff --git a/cmd/drone-agent/Dockerfile b/cmd/drone-agent/Dockerfile new file mode 100644 index 00000000..db3abd9e --- /dev/null +++ b/cmd/drone-agent/Dockerfile @@ -0,0 +1,9 @@ +# Docker image for the Drone build agent +# +# CGO_ENABLED=0 go build -a -tags netgo +# docker build --rm=true -t drone/drone-agent . + +FROM gliderlabs/alpine:3.1 +RUN apk-install ca-certificates +ADD drone-agent /bin/ +ENTRYPOINT ["/bin/drone-agent"] diff --git a/cmd/drone-agent/main.go b/cmd/drone-agent/main.go new file mode 100644 index 00000000..db21ae54 --- /dev/null +++ b/cmd/drone-agent/main.go @@ -0,0 +1,151 @@ +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io" + "net/http" + "net/url" + "os" + "time" + + "github.com/drone/drone/pkg/queue" + runner "github.com/drone/drone/pkg/runner/builtin" + + "github.com/gin-gonic/gin" + "github.com/samalba/dockerclient" +) + +var ( + // commit sha for the current build, set by + // the compile process. + version string + revision string +) + +var ( + // Defult docker host address + DefaultHost = "unix:///var/run/docker.sock" + + // Docker host address from environment variable + DockerHost = os.Getenv("DOCKER_HOST") +) + +var ( + addr string + token string +) + +func main() { + flag.StringVar(&addr, "addr", "http://localhost:8080", "") + flag.StringVar(&token, "token", "", "") + flag.Parse() + + if len(DockerHost) == 0 { + DockerHost = DefaultHost + } + + go func() { + for { + w, err := pull() + if err != nil { + time.Sleep(30 * time.Second) + continue + } + runner_ := runner.Runner{&updater{addr, token}} + runner_.Run(w) + } + }() + + s := gin.New() + s.GET("/stream/:id", stream) + s.GET("/ping", ping) + s.GET("/about", about) + s.Run(":1999") +} + +// ping handler returns a simple response to the +// caller indicating the server is running. This +// can be used for heartbeats. +func ping(c *gin.Context) { + c.String(200, "PONG") +} + +// about handler returns the version and revision +// information for this server. +func about(c *gin.Context) { + out := struct { + Version string + Revision string + }{version, revision} + c.JSON(200, out) +} + +// stream handler is a proxy that streams the Docker +// stdout and stderr for a running build to the caller. +func stream(c *gin.Context) { + client, err := dockerclient.NewDockerClient(DockerHost, nil) + if err != nil { + c.Fail(500, err) + return + } + cname := fmt.Sprintf("drone-%s", c.Params.ByName("id")) + + // finds the container by name + info, err := client.InspectContainer(cname) + if err != nil { + c.Fail(404, err) + return + } + + // verify the container is running. if not we'll + // do an exponential backoff and attempt to wait + if !info.State.Running { + for i := 0; ; i++ { + time.Sleep(1 * time.Second) + info, err = client.InspectContainer(info.Id) + if err != nil { + c.Fail(404, err) + return + } + if info.State.Running { + break + } + if i == 5 { + c.Fail(404, dockerclient.ErrNotFound) + return + } + } + } + + logs := &dockerclient.LogOptions{ + Follow: true, + Stdout: true, + Stderr: true, + } + + // directly streams the build output from the Docker + // daemon to the request. + rc, err := client.ContainerLogs(info.Id, logs) + if err != nil { + c.Fail(500, err) + return + } + io.Copy(c.Writer, rc) +} + +func pull() (*queue.Work, error) { + url_, _ := url.Parse(addr) + url_.Path = "/api/queue/pull" + var body bytes.Buffer + resp, err := http.Post(url_.String(), "application/json", &body) + if err != nil { + return nil, err + } + defer resp.Body.Close() + work := &queue.Work{} + err = json.NewDecoder(resp.Body).Decode(work) + return work, err +} diff --git a/cmd/drone-agent/updater.go b/cmd/drone-agent/updater.go new file mode 100644 index 00000000..9d0110c3 --- /dev/null +++ b/cmd/drone-agent/updater.go @@ -0,0 +1,75 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + + //logs "github.com/Sirupsen/logrus" + common "github.com/drone/drone/pkg/types" +) + +type updater struct { + addr string + token string +} + +func (u *updater) SetCommit(user *common.User, r *common.Repo, c *common.Commit) error { + url_, err := url.Parse(addr) + if err != nil { + return err + } + url_.Path = fmt.Sprintf("/api/queue/push/%s/%v", r.FullName, c.Sequence) + var body bytes.Buffer + json.NewEncoder(&body).Encode(c) + resp, err := http.Post(url_.String(), "application/json", &body) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return fmt.Errorf("Error pushing task state. Code %d", resp.StatusCode) + } + return nil +} + +func (u *updater) SetBuild(r *common.Repo, c *common.Commit, b *common.Build) error { + url_, err := url.Parse(u.addr) + if err != nil { + return err + } + + url_.Path = fmt.Sprintf("/api/queue/push/%s", r.FullName) + var body bytes.Buffer + json.NewEncoder(&body).Encode(b) + resp, err := http.Post(url_.String(), "application/json", &body) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return fmt.Errorf("Error pushing build state. Code %d", resp.StatusCode) + } + return nil +} + +func (u *updater) SetLogs(r *common.Repo, c *common.Commit, b *common.Build, rc io.ReadCloser) error { + url_, err := url.Parse(u.addr) + if err != nil { + return err + } + + url_.Path = fmt.Sprintf("/api/queue/push/%s/%v/%v/logs", r.FullName, c.Sequence, b.Sequence) + resp, err := http.Post(url_.String(), "application/json", rc) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return fmt.Errorf("Error pushing build logs. Code %d", resp.StatusCode) + } + return nil +} diff --git a/cmd/drone-build/Dockerfile b/cmd/drone-build/Dockerfile index dcc086b7..7170f8b3 100644 --- a/cmd/drone-build/Dockerfile +++ b/cmd/drone-build/Dockerfile @@ -1,4 +1,4 @@ -# Docker image for Drone's slack notification plugin +# Docker image for the Drone build runner # # CGO_ENABLED=0 go build -a -tags netgo # docker build --rm=true -t drone/drone-build . diff --git a/cmd/drone-server/drone.go b/cmd/drone-server/drone.go index b96c6783..9128003a 100644 --- a/cmd/drone-server/drone.go +++ b/cmd/drone-server/drone.go @@ -21,6 +21,13 @@ import ( _ "net/http/pprof" ) +var ( + // commit sha for the current build, set by + // the compile process. + version string + revision string +) + var conf = flag.String("config", "drone.toml", "") func main() {