mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-10-21 17:25:52 +00:00
initial public commit
This commit is contained in:
471
pkg/build/build.go
Normal file
471
pkg/build/build.go
Normal file
@@ -0,0 +1,471 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
"github.com/drone/drone/pkg/build/docker"
|
||||
"github.com/drone/drone/pkg/build/dockerfile"
|
||||
"github.com/drone/drone/pkg/build/log"
|
||||
"github.com/drone/drone/pkg/build/proxy"
|
||||
"github.com/drone/drone/pkg/build/repo"
|
||||
"github.com/drone/drone/pkg/build/script"
|
||||
)
|
||||
|
||||
// instance of the Docker client
|
||||
var client = docker.New()
|
||||
|
||||
// BuildState stores information about a build
|
||||
// process including the Exit status and various
|
||||
// Runtime statistics (coming soon).
|
||||
type BuildState struct {
|
||||
Started int64
|
||||
Finished int64
|
||||
ExitCode int
|
||||
|
||||
// we may eventually include detailed resource
|
||||
// usage statistics, including including CPU time,
|
||||
// Max RAM, Max Swap, Disk space, and more.
|
||||
}
|
||||
|
||||
// Builder represents a build process being prepared
|
||||
// to run.
|
||||
type Builder struct {
|
||||
// Image specifies the Docker Image that will be
|
||||
// used to virtualize the Build process.
|
||||
Build *script.Build
|
||||
|
||||
// Source specifies the Repository path of the code
|
||||
// that we are testing.
|
||||
//
|
||||
// The source repository may be a local repository
|
||||
// on the current filesystem, or a remote repository
|
||||
// on GitHub, Bitbucket, etc.
|
||||
Repo *repo.Repo
|
||||
|
||||
// Key is an identify file, such as an RSA private key, that
|
||||
// will be copied into the environments ~/.ssh/id_rsa file.
|
||||
Key []byte
|
||||
|
||||
// Timeout is the maximum amount of to will wait for a process
|
||||
// to exit.
|
||||
//
|
||||
// The default is no timeout.
|
||||
Timeout time.Duration
|
||||
|
||||
// Stdout specifies the builds's standard output.
|
||||
//
|
||||
// If stdout is nil, Run connects the corresponding file descriptor
|
||||
// to the null device (os.DevNull).
|
||||
Stdout io.Writer
|
||||
|
||||
// BuildState contains information about an exited build,
|
||||
// available after a call to Run.
|
||||
BuildState *BuildState
|
||||
|
||||
// Docker image that was created for
|
||||
// this build.
|
||||
image *docker.Image
|
||||
|
||||
// Docker container was that created
|
||||
// for this build.
|
||||
container *docker.Run
|
||||
|
||||
// Docker containers created for the
|
||||
// specified services and linked to
|
||||
// this build.
|
||||
services []*docker.Container
|
||||
}
|
||||
|
||||
func (b *Builder) Run() error {
|
||||
// teardown will remove the Image and stop and
|
||||
// remove the service containers after the
|
||||
// build is done running.
|
||||
defer b.teardown()
|
||||
|
||||
// setup will create the Image and supporting
|
||||
// service containers.
|
||||
if err := b.setup(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make sure build state is not nil
|
||||
b.BuildState = &BuildState{}
|
||||
b.BuildState.ExitCode = 0
|
||||
b.BuildState.Started = time.Now().UTC().Unix()
|
||||
|
||||
c := make(chan error, 1)
|
||||
go func() {
|
||||
c <- b.run()
|
||||
}()
|
||||
|
||||
// wait for either a) the job to complete or b) the job to timeout
|
||||
select {
|
||||
case err := <-c:
|
||||
return err
|
||||
case <-time.After(b.Timeout):
|
||||
log.Errf("time limit exceeded for build %s", b.Build.Name)
|
||||
b.BuildState.ExitCode = 124
|
||||
b.BuildState.Finished = time.Now().UTC().Unix()
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) setup() error {
|
||||
|
||||
// temp directory to store all files required
|
||||
// to generate the Docker image.
|
||||
dir, err := ioutil.TempDir("", "drone-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// clean up after our mess.
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// make sure the image isn't empty. this would be bad
|
||||
if len(b.Build.Image) == 0 {
|
||||
log.Err("Fatal Error, No Docker Image specified")
|
||||
return fmt.Errorf("Error: missing Docker image")
|
||||
}
|
||||
|
||||
// if we're using an alias for the build name we
|
||||
// should substitute it now
|
||||
if alias, ok := builders[b.Build.Image]; ok {
|
||||
b.Build.Image = alias.Tag
|
||||
}
|
||||
|
||||
// if this is a local repository we should symlink
|
||||
// to the source code in our temp directory
|
||||
if b.Repo.IsLocal() {
|
||||
// this is where we used to use symlinks. We should
|
||||
// talk to the docker team about this, since copying
|
||||
// the entire repository is slow :(
|
||||
//
|
||||
// see https://github.com/dotcloud/docker/pull/3567
|
||||
|
||||
//src := filepath.Join(dir, "src")
|
||||
//err = os.Symlink(b.Repo.Path, src)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
src := filepath.Join(dir, "src")
|
||||
cmd := exec.Command("cp", "-a", b.Repo.Path, src)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// start all services required for the build
|
||||
// that will get linked to the container.
|
||||
for _, service := range b.Build.Services {
|
||||
image, ok := services[service]
|
||||
if !ok {
|
||||
return fmt.Errorf("Error: Invalid or unknown service %s", service)
|
||||
}
|
||||
|
||||
// debugging
|
||||
log.Infof("starting service container %s", image.Tag)
|
||||
|
||||
// Run the contianer
|
||||
run, err := client.Containers.RunDaemonPorts(image.Tag, image.Ports...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the container info
|
||||
info, err := client.Containers.Inspect(run.ID)
|
||||
if err != nil {
|
||||
// on error kill the container since it hasn't yet been
|
||||
// added to the array and would therefore not get
|
||||
// removed in the defer statement.
|
||||
client.Containers.Stop(run.ID, 10)
|
||||
client.Containers.Remove(run.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the running service to the list
|
||||
b.services = append(b.services, info)
|
||||
|
||||
}
|
||||
|
||||
if err := b.writeIdentifyFile(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := b.writeBuildScript(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := b.writeProxyScript(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := b.writeDockerfile(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// debugging
|
||||
log.Info("creating build image")
|
||||
|
||||
// check for build container (ie bradrydzewski/go:1.2)
|
||||
// and download if it doesn't already exist
|
||||
if _, err := client.Images.Inspect(b.Build.Image); err == docker.ErrNotFound {
|
||||
// download the image if it doesn't exist
|
||||
if err := client.Images.Pull(b.Build.Image); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// create the Docker image
|
||||
id := createUID()
|
||||
if err := client.Images.Build(id, dir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// debugging
|
||||
log.Infof("copying repository to %s", b.Repo.Dir)
|
||||
|
||||
// get the image details
|
||||
b.image, err = client.Images.Inspect(id)
|
||||
if err != nil {
|
||||
// if we have problems with the image make sure
|
||||
// we remove it before we exit
|
||||
client.Images.Remove(id)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// teardown is a helper function that we can use to
|
||||
// stop and remove the build container, its supporting image,
|
||||
// and the supporting service containers.
|
||||
func (b *Builder) teardown() error {
|
||||
|
||||
// stop and destroy the container
|
||||
if b.container != nil {
|
||||
|
||||
// debugging
|
||||
log.Info("removing build container")
|
||||
|
||||
// stop the container, ignore error message
|
||||
client.Containers.Stop(b.container.ID, 15)
|
||||
|
||||
// remove the container, ignore error message
|
||||
if err := client.Containers.Remove(b.container.ID); err != nil {
|
||||
log.Errf("failed to delete build container %s", b.container.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// stop and destroy the container services
|
||||
for i, container := range b.services {
|
||||
// debugging
|
||||
log.Infof("removing service container %s", b.Build.Services[i])
|
||||
|
||||
// stop the service container, ignore the error
|
||||
client.Containers.Stop(container.ID, 15)
|
||||
|
||||
// remove the service container, ignore the error
|
||||
if err := client.Containers.Remove(container.ID); err != nil {
|
||||
log.Errf("failed to delete service container %s", container.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// destroy the underlying image
|
||||
if b.image != nil {
|
||||
// debugging
|
||||
log.Info("removing build image")
|
||||
|
||||
if _, err := client.Images.Remove(b.image.ID); err != nil {
|
||||
log.Errf("failed to completely delete build image %s. %s", b.image.ID, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Builder) run() error {
|
||||
// create and run the container
|
||||
conf := docker.Config{
|
||||
Image: b.image.ID,
|
||||
AttachStdin: false,
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
}
|
||||
host := docker.HostConfig{
|
||||
Privileged: false,
|
||||
}
|
||||
|
||||
// debugging
|
||||
log.Noticef("starting build %s", b.Build.Name)
|
||||
|
||||
// link service containers
|
||||
for i, service := range b.services {
|
||||
image, ok := services[b.Build.Services[i]]
|
||||
if !ok {
|
||||
continue // THIS SHOULD NEVER HAPPEN
|
||||
}
|
||||
// link the service container to our
|
||||
// build container.
|
||||
host.Links = append(host.Links, service.Name[1:]+":"+image.Name)
|
||||
}
|
||||
|
||||
// create the container from the image
|
||||
run, err := client.Containers.Create(&conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// cache instance of docker.Run
|
||||
b.container = run
|
||||
|
||||
// attach to the container
|
||||
go func() {
|
||||
client.Containers.Attach(run.ID, &writer{b.Stdout})
|
||||
}()
|
||||
|
||||
// start the container
|
||||
if err := client.Containers.Start(run.ID, &host); err != nil {
|
||||
b.BuildState.ExitCode = 1
|
||||
b.BuildState.Finished = time.Now().UTC().Unix()
|
||||
return err
|
||||
}
|
||||
|
||||
// wait for the container to stop
|
||||
wait, err := client.Containers.Wait(run.ID)
|
||||
if err != nil {
|
||||
b.BuildState.ExitCode = 1
|
||||
b.BuildState.Finished = time.Now().UTC().Unix()
|
||||
return err
|
||||
}
|
||||
|
||||
// set completion time
|
||||
b.BuildState.Finished = time.Now().UTC().Unix()
|
||||
|
||||
// get the exit code if possible
|
||||
b.BuildState.ExitCode = wait.StatusCode //b.container.State.ExitCode
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeDockerfile is a helper function that generates a
|
||||
// Dockerfile and writes to the builds temporary directory
|
||||
// so that it can be used to create the Image.
|
||||
func (b *Builder) writeDockerfile(dir string) error {
|
||||
var dockerfile = dockerfile.New(b.Build.Image)
|
||||
dockerfile.WriteWorkdir(b.Repo.Dir)
|
||||
dockerfile.WriteAdd("drone", "/usr/local/bin/")
|
||||
|
||||
// upload source code if repository is stored
|
||||
// on the host machine
|
||||
if b.Repo.IsRemote() == false {
|
||||
dockerfile.WriteAdd("src", filepath.Join(b.Repo.Dir))
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(b.Build.Image, "bradrydzewski/"),
|
||||
strings.HasPrefix(b.Build.Image, "drone/"):
|
||||
// the default user for all official Drone imnage
|
||||
// is the "ubuntu" user, since all build images
|
||||
// inherit from the ubuntu cloud ISO
|
||||
dockerfile.WriteUser("ubuntu")
|
||||
dockerfile.WriteEnv("HOME", "/home/ubuntu")
|
||||
dockerfile.WriteEnv("LANG", "en_US.UTF-8")
|
||||
dockerfile.WriteEnv("LANGUAGE", "en_US:en")
|
||||
dockerfile.WriteEnv("LOGNAME", "ubuntu")
|
||||
dockerfile.WriteEnv("TERM", "xterm")
|
||||
dockerfile.WriteEnv("SHELL", "/bin/bash")
|
||||
dockerfile.WriteAdd("id_rsa", "/home/ubuntu/.ssh/id_rsa")
|
||||
dockerfile.WriteRun("sudo chown -R ubuntu:ubuntu /home/ubuntu/.ssh")
|
||||
dockerfile.WriteRun("sudo chown -R ubuntu:ubuntu /var/cache/drone")
|
||||
dockerfile.WriteRun("sudo chown -R ubuntu:ubuntu /usr/local/bin/drone")
|
||||
dockerfile.WriteRun("sudo chmod 600 /home/ubuntu/.ssh/id_rsa")
|
||||
default:
|
||||
// all other images are assumed to use
|
||||
// the root user.
|
||||
dockerfile.WriteUser("root")
|
||||
dockerfile.WriteEnv("HOME", "/root")
|
||||
dockerfile.WriteEnv("LANG", "en_US.UTF-8")
|
||||
dockerfile.WriteEnv("LANGUAGE", "en_US:en")
|
||||
dockerfile.WriteEnv("LOGNAME", "root")
|
||||
dockerfile.WriteEnv("TERM", "xterm")
|
||||
dockerfile.WriteEnv("SHELL", "/bin/bash")
|
||||
dockerfile.WriteEnv("GOPATH", "/var/cache/drone")
|
||||
dockerfile.WriteAdd("id_rsa", "/root/.ssh/id_rsa")
|
||||
dockerfile.WriteRun("chmod 600 /root/.ssh/id_rsa")
|
||||
}
|
||||
|
||||
dockerfile.WriteAdd("proxy.sh", "/etc/drone.d/")
|
||||
dockerfile.WriteEntrypoint("/bin/bash -e /usr/local/bin/drone")
|
||||
|
||||
// write the Dockerfile to the temporary directory
|
||||
return ioutil.WriteFile(filepath.Join(dir, "Dockerfile"), dockerfile.Bytes(), 0700)
|
||||
}
|
||||
|
||||
// writeBuildScript is a helper function that
|
||||
// will generate the build script file in the builder's
|
||||
// temp directory to be added to the Image.
|
||||
func (b *Builder) writeBuildScript(dir string) error {
|
||||
f := buildfile.New()
|
||||
|
||||
// if the repository is remote then we should
|
||||
// add the commands to the build script to
|
||||
// clone the repository
|
||||
if b.Repo.IsRemote() {
|
||||
for _, cmd := range b.Repo.Commands() {
|
||||
f.WriteCmd(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// if the commit is for merging a pull request
|
||||
// we should only execute the build commands,
|
||||
// and omit the deploy and publish commands.
|
||||
if len(b.Repo.PR) == 0 {
|
||||
b.Build.Write(f)
|
||||
} else {
|
||||
// only write the build commands
|
||||
b.Build.WriteBuild(f)
|
||||
}
|
||||
|
||||
scriptfilePath := filepath.Join(dir, "drone")
|
||||
return ioutil.WriteFile(scriptfilePath, f.Bytes(), 0700)
|
||||
}
|
||||
|
||||
// writeProxyScript is a helper function that
|
||||
// will generate the proxy.sh file in the builder's
|
||||
// temp directory to be added to the Image.
|
||||
func (b *Builder) writeProxyScript(dir string) error {
|
||||
var proxyfile = proxy.Proxy{}
|
||||
|
||||
// loop through services so that we can
|
||||
// map ip address to localhost
|
||||
for _, container := range b.services {
|
||||
// create an entry for each port
|
||||
for port, _ := range container.NetworkSettings.Ports {
|
||||
proxyfile.Set(port.Port(), container.NetworkSettings.IPAddress)
|
||||
}
|
||||
}
|
||||
|
||||
// write the proxyfile to the temp directory
|
||||
proxyfilePath := filepath.Join(dir, "proxy.sh")
|
||||
return ioutil.WriteFile(proxyfilePath, proxyfile.Bytes(), 0755)
|
||||
}
|
||||
|
||||
// writeIdentifyFile is a helper function that
|
||||
// will generate the id_rsa file in the builder's
|
||||
// temp directory to be added to the Image.
|
||||
func (b *Builder) writeIdentifyFile(dir string) error {
|
||||
keyfilePath := filepath.Join(dir, "id_rsa")
|
||||
return ioutil.WriteFile(keyfilePath, b.Key, 0700)
|
||||
}
|
72
pkg/build/buildfile/buildfile.go
Normal file
72
pkg/build/buildfile/buildfile.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package buildfile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Buildfile struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func New() *Buildfile {
|
||||
b := Buildfile{}
|
||||
b.WriteString(base)
|
||||
return &b
|
||||
}
|
||||
|
||||
// WriteCmd writes a command to the build file. The
|
||||
// command will be echoed back as a base16 encoded
|
||||
// command so that it can be parsed and appended to
|
||||
// the build output
|
||||
func (b *Buildfile) WriteCmd(command string) {
|
||||
// echo the command as an encoded value
|
||||
b.WriteString(fmt.Sprintf("echo '#DRONE:%x'\n", command))
|
||||
// and then run the command
|
||||
b.WriteString(fmt.Sprintf("%s\n", command))
|
||||
}
|
||||
|
||||
// WriteCmdSilent writes a command to the build file
|
||||
// but does not echo the command.
|
||||
func (b *Buildfile) WriteCmdSilent(command string) {
|
||||
b.WriteString(fmt.Sprintf("%s\n", command))
|
||||
}
|
||||
|
||||
// WriteComment adds a comment to the build file. This
|
||||
// is really only used internally for debugging purposes.
|
||||
func (b *Buildfile) WriteComment(comment string) {
|
||||
b.WriteString(fmt.Sprintf("#%s\n", comment))
|
||||
}
|
||||
|
||||
// WriteEnv exports the environment variable as
|
||||
// part of the script. The environment variables
|
||||
// are not echoed back to the console, and are
|
||||
// kept private by default.
|
||||
func (b *Buildfile) WriteEnv(key, value string) {
|
||||
b.WriteString(fmt.Sprintf("export %s=%s\n", key, value))
|
||||
}
|
||||
|
||||
// every build script starts with the following
|
||||
// code at the start.
|
||||
var base = `
|
||||
#!/bin/bash
|
||||
|
||||
# drone configuration files are stored in /etc/drone.d
|
||||
# execute these files prior to our build to set global
|
||||
# environment variables and initialize programs (like rbenv)
|
||||
if [ -d /etc/drone.d ]; then
|
||||
for i in /etc/drone.d/*.sh; do
|
||||
if [ -r $i ]; then
|
||||
. $i
|
||||
fi
|
||||
done
|
||||
unset i
|
||||
fi
|
||||
|
||||
# be sure to exit on error and print out
|
||||
# our bash commands, so we can which commands
|
||||
# are executing and troubleshoot failures.
|
||||
set -e
|
||||
|
||||
# user-defined commands below ##############################
|
||||
`
|
258
pkg/build/docker/client.go
Normal file
258
pkg/build/docker/client.go
Normal file
@@ -0,0 +1,258 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dotcloud/docker/pkg/term"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
APIVERSION = 1.9
|
||||
DEFAULTHTTPPORT = 4243
|
||||
DEFAULTUNIXSOCKET = "/var/run/docker.sock"
|
||||
DEFAULTPROTOCOL = "unix"
|
||||
DEFAULTTAG = "latest"
|
||||
VERSION = "0.8.0"
|
||||
)
|
||||
|
||||
// Enables verbose logging to the Terminal window
|
||||
var Logging = true
|
||||
|
||||
// New creates an instance of the Docker Client
|
||||
func New() *Client {
|
||||
c := &Client{}
|
||||
c.proto = DEFAULTPROTOCOL
|
||||
c.addr = DEFAULTUNIXSOCKET
|
||||
|
||||
// if the default socket doesn't exist then
|
||||
// we'll try to connect to the default tcp address
|
||||
if _, err := os.Stat(DEFAULTUNIXSOCKET); err != nil {
|
||||
c.proto = "tcp"
|
||||
c.addr = "0.0.0.0:4243"
|
||||
}
|
||||
|
||||
c.Images = &ImageService{c}
|
||||
c.Containers = &ContainerService{c}
|
||||
return c
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
proto string
|
||||
addr string
|
||||
|
||||
Images *ImageService
|
||||
Containers *ContainerService
|
||||
}
|
||||
|
||||
var (
|
||||
// Returned if the specified resource does not exist.
|
||||
ErrNotFound = errors.New("Not Found")
|
||||
|
||||
// Returned if the caller attempts to make a call or modify a resource
|
||||
// for which the caller is not authorized.
|
||||
//
|
||||
// The request was a valid request, the caller's authentication credentials
|
||||
// succeeded but those credentials do not grant the caller permission to
|
||||
// access the resource.
|
||||
ErrForbidden = errors.New("Forbidden")
|
||||
|
||||
// Returned if the call requires authentication and either the credentials
|
||||
// provided failed or no credentials were provided.
|
||||
ErrNotAuthorized = errors.New("Unauthorized")
|
||||
|
||||
// Returned if the caller submits a badly formed request. For example,
|
||||
// the caller can receive this return if you forget a required parameter.
|
||||
ErrBadRequest = errors.New("Bad Request")
|
||||
)
|
||||
|
||||
// helper function used to make HTTP requests to the Docker daemon.
|
||||
func (c *Client) do(method, path string, in, out interface{}) error {
|
||||
// if data input is provided, serialize to JSON
|
||||
var payload io.Reader
|
||||
if in != nil {
|
||||
buf, err := json.Marshal(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload = bytes.NewBuffer(buf)
|
||||
}
|
||||
|
||||
// create the request
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set the appropariate headers
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// dial the host server
|
||||
req.Host = c.addr
|
||||
dial, err := net.Dial(c.proto, c.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make the request
|
||||
conn := httputil.NewClientConn(dial, nil)
|
||||
resp, err := conn.Do(req)
|
||||
defer conn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read the bytes from the body (make sure we defer close the body)
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check for an http error status (ie not 200 StatusOK)
|
||||
switch resp.StatusCode {
|
||||
case 404:
|
||||
return ErrNotFound
|
||||
case 403:
|
||||
return ErrForbidden
|
||||
case 401:
|
||||
return ErrNotAuthorized
|
||||
case 400:
|
||||
return ErrBadRequest
|
||||
}
|
||||
|
||||
// Unmarshall the JSON response
|
||||
if out != nil {
|
||||
return json.Unmarshal(body, out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) hijack(method, path string, setRawTerminal bool, out io.Writer) error {
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
req.Host = c.addr
|
||||
|
||||
dial, err := net.Dial(c.proto, c.addr)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
|
||||
}
|
||||
return err
|
||||
}
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
defer clientconn.Close()
|
||||
|
||||
// Server hijacks the connection, error 'connection closed' expected
|
||||
clientconn.Do(req)
|
||||
|
||||
// Hijack the connection to read / write
|
||||
rwc, br := clientconn.Hijack()
|
||||
defer rwc.Close()
|
||||
|
||||
// launch a goroutine to copy the stream
|
||||
// of build output to the writer.
|
||||
errStdout := make(chan error, 1)
|
||||
go func() {
|
||||
var err error
|
||||
if setRawTerminal {
|
||||
_, err = io.Copy(out, br)
|
||||
} else {
|
||||
_, err = utils.StdCopy(out, out, br)
|
||||
}
|
||||
|
||||
errStdout <- err
|
||||
}()
|
||||
|
||||
// wait for a response
|
||||
if err := <-errStdout; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) stream(method, path string, in io.Reader, out io.Writer, headers http.Header) error {
|
||||
if (method == "POST" || method == "PUT") && in == nil {
|
||||
in = bytes.NewReader(nil)
|
||||
}
|
||||
|
||||
// setup the request
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set default headers
|
||||
req.Header = headers
|
||||
req.Header.Set("User-Agent", "Docker-Client/0.6.4")
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
|
||||
// dial the host server
|
||||
req.Host = c.addr
|
||||
dial, err := net.Dial(c.proto, c.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make the request
|
||||
conn := httputil.NewClientConn(dial, nil)
|
||||
resp, err := conn.Do(req)
|
||||
defer conn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make sure we defer close the body
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check for an http error status (ie not 200 StatusOK)
|
||||
switch resp.StatusCode {
|
||||
case 404:
|
||||
return ErrNotFound
|
||||
case 403:
|
||||
return ErrForbidden
|
||||
case 401:
|
||||
return ErrNotAuthorized
|
||||
case 400:
|
||||
return ErrBadRequest
|
||||
}
|
||||
|
||||
// If no output we exit now with no errors
|
||||
if out == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// copy the output stream to the writer
|
||||
if resp.Header.Get("Content-Type") == "application/json" {
|
||||
var terminalFd = os.Stdin.Fd()
|
||||
var isTerminal = term.IsTerminal(terminalFd)
|
||||
|
||||
// it may not make sense to put this code here, but it works for
|
||||
// us at the moment, and I don't feel like refactoring
|
||||
return utils.DisplayJSONMessagesStream(resp.Body, out, terminalFd, isTerminal)
|
||||
}
|
||||
// otherwise plain text
|
||||
if _, err := io.Copy(out, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
147
pkg/build/docker/container.go
Normal file
147
pkg/build/docker/container.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type ContainerService struct {
|
||||
*Client
|
||||
}
|
||||
|
||||
// List only running containers.
|
||||
func (c *ContainerService) List() ([]*Containers, error) {
|
||||
containers := []*Containers{}
|
||||
err := c.do("GET", "/containers/json?all=0", nil, &containers)
|
||||
return containers, err
|
||||
}
|
||||
|
||||
// List all containers
|
||||
func (c *ContainerService) ListAll() ([]*Containers, error) {
|
||||
containers := []*Containers{}
|
||||
err := c.do("GET", "/containers/json?all=1", nil, &containers)
|
||||
return containers, err
|
||||
}
|
||||
|
||||
// Create a Container
|
||||
func (c *ContainerService) Create(conf *Config) (*Run, error) {
|
||||
run, err := c.create(conf)
|
||||
switch {
|
||||
// if no error, exit immediately
|
||||
case err == nil:
|
||||
return run, nil
|
||||
// if error we exit, unless it is
|
||||
// a NOT FOUND error, which means we just
|
||||
// need to download the Image from the center
|
||||
// image index
|
||||
case err != nil && err != ErrNotFound:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// attempt to pull the image
|
||||
if err := c.Images.Pull(conf.Image); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// now that we have the image, re-try creation
|
||||
return c.create(conf)
|
||||
}
|
||||
|
||||
func (c *ContainerService) create(conf *Config) (*Run, error) {
|
||||
run := Run{}
|
||||
err := c.do("POST", "/containers/create", conf, &run)
|
||||
return &run, err
|
||||
}
|
||||
|
||||
// Start the container id
|
||||
func (c *ContainerService) Start(id string, conf *HostConfig) error {
|
||||
return c.do("POST", fmt.Sprintf("/containers/%s/start", id), &conf, nil)
|
||||
}
|
||||
|
||||
// Stop the container id
|
||||
func (c *ContainerService) Stop(id string, timeout int) error {
|
||||
return c.do("POST", fmt.Sprintf("/containers/%s/stop?t=%v", id, timeout), nil, nil)
|
||||
}
|
||||
|
||||
// Remove the container id from the filesystem.
|
||||
func (c *ContainerService) Remove(id string) error {
|
||||
return c.do("DELETE", fmt.Sprintf("/containers/%s", id), nil, nil)
|
||||
}
|
||||
|
||||
// Block until container id stops, then returns the exit code
|
||||
func (c *ContainerService) Wait(id string) (*Wait, error) {
|
||||
wait := Wait{}
|
||||
err := c.do("POST", fmt.Sprintf("/containers/%s/wait", id), nil, &wait)
|
||||
return &wait, err
|
||||
}
|
||||
|
||||
// Attach to the container to stream the stdout and stderr
|
||||
func (c *ContainerService) Attach(id string, out io.Writer) error {
|
||||
path := fmt.Sprintf("/containers/%s/attach?&stream=1&stdout=1&stderr=1", id)
|
||||
return c.hijack("POST", path, false, out)
|
||||
}
|
||||
|
||||
// Stop the container id
|
||||
func (c *ContainerService) Inspect(id string) (*Container, error) {
|
||||
container := Container{}
|
||||
err := c.do("GET", fmt.Sprintf("/containers/%s/json", id), nil, &container)
|
||||
return &container, err
|
||||
}
|
||||
|
||||
// Run the container
|
||||
func (c *ContainerService) Run(conf *Config, host *HostConfig, out io.Writer) (*Wait, error) {
|
||||
// create the container from the image
|
||||
run, err := c.Create(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// attach to the container
|
||||
go func() {
|
||||
c.Attach(run.ID, out)
|
||||
}()
|
||||
|
||||
// start the container
|
||||
if err := c.Start(run.ID, host); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// wait for the container to stop
|
||||
wait, err := c.Wait(run.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return wait, nil
|
||||
}
|
||||
|
||||
// Run the container as a Daemon
|
||||
func (c *ContainerService) RunDaemon(conf *Config, host *HostConfig) (*Run, error) {
|
||||
run, err := c.Create(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// start the container
|
||||
err = c.Start(run.ID, host)
|
||||
return run, err
|
||||
}
|
||||
|
||||
func (c *ContainerService) RunDaemonPorts(image string, ports ...string) (*Run, error) {
|
||||
// setup configuration
|
||||
config := Config{Image: image}
|
||||
config.ExposedPorts = make(map[Port]struct{})
|
||||
|
||||
// host configuration
|
||||
host := HostConfig{}
|
||||
host.PortBindings = make(map[Port][]PortBinding)
|
||||
|
||||
// loop through and add ports
|
||||
for _, port := range ports {
|
||||
config.ExposedPorts[Port(port+"/tcp")] = struct{}{}
|
||||
host.PortBindings[Port(port+"/tcp")] = []PortBinding{{HostIp: "127.0.0.1", HostPort: ""}}
|
||||
}
|
||||
//127.0.0.1::%s
|
||||
//map[3306/tcp:{}] map[3306/tcp:[{127.0.0.1 }]]
|
||||
return c.RunDaemon(&config, &host)
|
||||
}
|
124
pkg/build/docker/image.go
Normal file
124
pkg/build/docker/image.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
)
|
||||
|
||||
type Images struct {
|
||||
ID string `json:"Id"`
|
||||
RepoTags []string `json:",omitempty"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
ParentId string `json:",omitempty"`
|
||||
|
||||
// DEPRECATED
|
||||
Repository string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
ID string `json:"id"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Created time.Time `json:"created"`
|
||||
Container string `json:"container,omitempty"`
|
||||
ContainerConfig Config `json:"container_config,omitempty"`
|
||||
DockerVersion string `json:"docker_version,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
Config *Config `json:"config,omitempty"`
|
||||
Architecture string `json:"architecture,omitempty"`
|
||||
OS string `json:"os,omitempty"`
|
||||
Size int64
|
||||
}
|
||||
|
||||
type Delete struct {
|
||||
Deleted string `json:",omitempty"`
|
||||
Untagged string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type ImageService struct {
|
||||
*Client
|
||||
}
|
||||
|
||||
// List Images
|
||||
func (c *ImageService) List() ([]*Images, error) {
|
||||
images := []*Images{}
|
||||
err := c.do("GET", "/images/json?all=0", nil, &images)
|
||||
return images, err
|
||||
}
|
||||
|
||||
// Create an image, either by pull it from the registry or by importing it.
|
||||
func (c *ImageService) Create(image string) error {
|
||||
return c.do("POST", fmt.Sprintf("/images/create?fromImage=%s"), nil, nil)
|
||||
}
|
||||
|
||||
func (c *ImageService) Pull(image string) error {
|
||||
name, tag := utils.ParseRepositoryTag(image)
|
||||
if len(tag) == 0 {
|
||||
tag = DEFAULTTAG
|
||||
}
|
||||
return c.PullTag(name, tag)
|
||||
}
|
||||
|
||||
func (c *ImageService) PullTag(name, tag string) error {
|
||||
var out io.Writer
|
||||
if Logging {
|
||||
out = os.Stdout
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/images/create?fromImage=%s&tag=%s", name, tag)
|
||||
return c.stream("POST", path, nil, out, http.Header{})
|
||||
}
|
||||
|
||||
// Remove the image name from the filesystem
|
||||
func (c *ImageService) Remove(image string) ([]*Delete, error) {
|
||||
resp := []*Delete{}
|
||||
err := c.do("DELETE", fmt.Sprintf("/images/%s", image), nil, &resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Inspect the image
|
||||
func (c *ImageService) Inspect(name string) (*Image, error) {
|
||||
image := Image{}
|
||||
err := c.do("GET", fmt.Sprintf("/images/%s/json", name), nil, &image)
|
||||
return &image, err
|
||||
}
|
||||
|
||||
// Build the Image
|
||||
func (c *ImageService) Build(tag, dir string) error {
|
||||
|
||||
// tar the file
|
||||
context, err := archive.Tar(dir, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var body io.Reader
|
||||
body = ioutil.NopCloser(context)
|
||||
|
||||
// Upload the build context
|
||||
v := url.Values{}
|
||||
v.Set("t", tag)
|
||||
v.Set("q", "1")
|
||||
//v.Set("rm", "1")
|
||||
|
||||
// url path
|
||||
path := fmt.Sprintf("/build?%s", v.Encode())
|
||||
|
||||
// set content type to tar file
|
||||
headers := http.Header{}
|
||||
headers.Set("Content-Type", "application/tar")
|
||||
|
||||
// make the request
|
||||
return c.stream("POST", path, body, nil, headers)
|
||||
}
|
166
pkg/build/docker/structs.go
Normal file
166
pkg/build/docker/structs.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// These are structures copied from the Docker project.
|
||||
// We avoid importing the libraries due to a CGO
|
||||
// depenency on libdevmapper that we'd like to avoid.
|
||||
|
||||
type KeyValuePair struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
type HostConfig struct {
|
||||
Binds []string
|
||||
ContainerIDFile string
|
||||
LxcConf []KeyValuePair
|
||||
Privileged bool
|
||||
PortBindings map[Port][]PortBinding
|
||||
Links []string
|
||||
PublishAllPorts bool
|
||||
}
|
||||
|
||||
type Top struct {
|
||||
Titles []string
|
||||
Processes [][]string
|
||||
}
|
||||
|
||||
type Containers struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports []Port
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
Names []string
|
||||
}
|
||||
|
||||
type Run struct {
|
||||
ID string `json:"Id"`
|
||||
Warnings []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
type Wait struct {
|
||||
StatusCode int
|
||||
}
|
||||
|
||||
type State struct {
|
||||
Running bool
|
||||
Pid int
|
||||
ExitCode int
|
||||
StartedAt time.Time
|
||||
FinishedAt time.Time
|
||||
Ghost bool
|
||||
}
|
||||
|
||||
type PortBinding struct {
|
||||
HostIp string
|
||||
HostPort string
|
||||
}
|
||||
|
||||
// 80/tcp
|
||||
type Port string
|
||||
|
||||
func (p Port) Proto() string {
|
||||
parts := strings.Split(string(p), "/")
|
||||
if len(parts) == 1 {
|
||||
return "tcp"
|
||||
}
|
||||
return parts[1]
|
||||
}
|
||||
|
||||
func (p Port) Port() string {
|
||||
return strings.Split(string(p), "/")[0]
|
||||
}
|
||||
|
||||
func (p Port) Int() int {
|
||||
i, err := parsePort(p.Port())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func parsePort(rawPort string) (int, error) {
|
||||
port, err := strconv.ParseUint(rawPort, 10, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(port), nil
|
||||
}
|
||||
|
||||
func NewPort(proto, port string) Port {
|
||||
return Port(fmt.Sprintf("%s/%s", port, proto))
|
||||
}
|
||||
|
||||
type PortMapping map[string]string // Deprecated
|
||||
|
||||
type NetworkSettings struct {
|
||||
IPAddress string
|
||||
IPPrefixLen int
|
||||
Gateway string
|
||||
Bridge string
|
||||
PortMapping map[string]PortMapping // Deprecated
|
||||
Ports map[Port][]PortBinding
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Hostname string
|
||||
Domainname string
|
||||
User string
|
||||
Memory int64 // Memory limit (in bytes)
|
||||
MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap
|
||||
CpuShares int64 // CPU shares (relative weight vs. other containers)
|
||||
AttachStdin bool
|
||||
AttachStdout bool
|
||||
AttachStderr bool
|
||||
PortSpecs []string // Deprecated - Can be in the format of 8080/tcp
|
||||
ExposedPorts map[Port]struct{}
|
||||
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
|
||||
OpenStdin bool // Open stdin
|
||||
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
|
||||
Env []string
|
||||
Cmd []string
|
||||
Dns []string
|
||||
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
|
||||
Volumes map[string]struct{}
|
||||
VolumesFrom string
|
||||
WorkingDir string
|
||||
Entrypoint []string
|
||||
NetworkDisabled bool
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
ID string
|
||||
|
||||
Created time.Time
|
||||
|
||||
Path string
|
||||
Args []string
|
||||
|
||||
Config *Config
|
||||
State State
|
||||
Image string
|
||||
|
||||
NetworkSettings *NetworkSettings
|
||||
|
||||
SysInitPath string
|
||||
ResolvConfPath string
|
||||
HostnamePath string
|
||||
HostsPath string
|
||||
Name string
|
||||
Driver string
|
||||
|
||||
Volumes map[string]string
|
||||
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
|
||||
// Easier than migrating older container configs :)
|
||||
VolumesRW map[string]bool
|
||||
}
|
44
pkg/build/dockerfile/dockerfile.go
Normal file
44
pkg/build/dockerfile/dockerfile.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package dockerfile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Dockerfile struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func New(from string) *Dockerfile {
|
||||
d := Dockerfile{}
|
||||
d.WriteFrom(from)
|
||||
return &d
|
||||
}
|
||||
|
||||
func (d *Dockerfile) WriteAdd(from, to string) {
|
||||
d.WriteString(fmt.Sprintf("ADD %s %s\n", from, to))
|
||||
}
|
||||
|
||||
func (d *Dockerfile) WriteFrom(from string) {
|
||||
d.WriteString(fmt.Sprintf("FROM %s\n", from))
|
||||
}
|
||||
|
||||
func (d *Dockerfile) WriteRun(cmd string) {
|
||||
d.WriteString(fmt.Sprintf("RUN %s\n", cmd))
|
||||
}
|
||||
|
||||
func (d *Dockerfile) WriteUser(user string) {
|
||||
d.WriteString(fmt.Sprintf("USER %s\n", user))
|
||||
}
|
||||
|
||||
func (d *Dockerfile) WriteEnv(key, val string) {
|
||||
d.WriteString(fmt.Sprintf("ENV %s %s\n", key, val))
|
||||
}
|
||||
|
||||
func (d *Dockerfile) WriteWorkdir(workdir string) {
|
||||
d.WriteString(fmt.Sprintf("WORKDIR %s\n", workdir))
|
||||
}
|
||||
|
||||
func (d *Dockerfile) WriteEntrypoint(entrypoint string) {
|
||||
d.WriteString(fmt.Sprintf("ENTRYPOINT %s\n", entrypoint))
|
||||
}
|
238
pkg/build/images.go
Normal file
238
pkg/build/images.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package build
|
||||
|
||||
type image struct {
|
||||
// default ports the service will run on.
|
||||
// for example, 3306 for mysql. Note that a service
|
||||
// may expose multiple prots, for example, Riak
|
||||
// exposes 8087 and 8089.
|
||||
Ports []string
|
||||
|
||||
// tag of the docker image to pull in order
|
||||
// to run this service.
|
||||
Tag string
|
||||
|
||||
// display name of the image type
|
||||
Name string
|
||||
}
|
||||
|
||||
// List of 3rd party services (database, queue, etc) that
|
||||
// are known to work with this Build utility.
|
||||
var services = map[string]*image{
|
||||
|
||||
// neo4j
|
||||
"neo4j": {
|
||||
Ports: []string{"7474"},
|
||||
Tag: "bradrydzewski/neo4j:1.9",
|
||||
Name: "neo4j",
|
||||
},
|
||||
"neo4j:1.9": {
|
||||
Ports: []string{"7474"},
|
||||
Tag: "bradrydzewski/neo4j:1.9",
|
||||
Name: "neo4j",
|
||||
},
|
||||
|
||||
// elasticsearch servers
|
||||
"elasticsearch": {
|
||||
Ports: []string{"9200"},
|
||||
Tag: "bradrydzewski/elasticsearch:0.90",
|
||||
Name: "elasticsearch",
|
||||
},
|
||||
"elasticsearch:0.20": {
|
||||
Ports: []string{"9200"},
|
||||
Tag: "bradrydzewski/elasticsearch:0.20",
|
||||
Name: "elasticsearch",
|
||||
},
|
||||
"elasticsearch:0.90": {
|
||||
Ports: []string{"9200"},
|
||||
Tag: "bradrydzewski/elasticsearch:0.90",
|
||||
Name: "elasticsearch",
|
||||
},
|
||||
|
||||
// redis servers
|
||||
"redis": {
|
||||
Ports: []string{"6379"},
|
||||
Tag: "bradrydzewski/redis:2.8",
|
||||
Name: "redis",
|
||||
},
|
||||
"redis:2.8": {
|
||||
Ports: []string{"6379"},
|
||||
Tag: "bradrydzewski/redis:2.8",
|
||||
Name: "redis",
|
||||
},
|
||||
"redis:2.6": {
|
||||
Ports: []string{"6379"},
|
||||
Tag: "bradrydzewski/redis:2.6",
|
||||
Name: "redis",
|
||||
},
|
||||
|
||||
// mysql servers
|
||||
"mysql": {
|
||||
Tag: "bradrydzewski/mysql:5.5",
|
||||
Ports: []string{"3306"},
|
||||
Name: "mysql",
|
||||
},
|
||||
"mysql:5.5": {
|
||||
Tag: "bradrydzewski/mysql:5.5",
|
||||
Ports: []string{"3306"},
|
||||
Name: "mysql",
|
||||
},
|
||||
|
||||
// memcached
|
||||
"memcached": {
|
||||
Ports: []string{"11211"},
|
||||
Tag: "bradrydzewski/memcached",
|
||||
Name: "memcached",
|
||||
},
|
||||
|
||||
// mongodb
|
||||
"mongodb": {
|
||||
Ports: []string{"27017"},
|
||||
Tag: "bradrydzewski/mongodb:2.4",
|
||||
Name: "mongodb",
|
||||
},
|
||||
"mongodb:2.4": {
|
||||
Ports: []string{"27017"},
|
||||
Tag: "bradrydzewski/mongodb:2.4",
|
||||
Name: "mongodb",
|
||||
},
|
||||
"mongodb:2.2": {
|
||||
Ports: []string{"27017"},
|
||||
Tag: "bradrydzewski/mongodb:2.2",
|
||||
Name: "mongodb",
|
||||
},
|
||||
|
||||
// postgres
|
||||
"postgres": {
|
||||
Ports: []string{"5432"},
|
||||
Tag: "bradrydzewski/postgres:9.1",
|
||||
Name: "postgres",
|
||||
},
|
||||
"postgres:9.1": {
|
||||
Ports: []string{"5432"},
|
||||
Tag: "bradrydzewski/postgres:9.1",
|
||||
Name: "postgres",
|
||||
},
|
||||
|
||||
// couchdb
|
||||
"couchdb": {
|
||||
Ports: []string{"5984"},
|
||||
Tag: "bradrydzewski/couchdb:1.0",
|
||||
Name: "couchdb",
|
||||
},
|
||||
"couchdb:1.0": {
|
||||
Ports: []string{"5984"},
|
||||
Tag: "bradrydzewski/couchdb:1.0",
|
||||
Name: "couchdb",
|
||||
},
|
||||
"couchdb:1.4": {
|
||||
Ports: []string{"5984"},
|
||||
Tag: "bradrydzewski/couchdb:1.4",
|
||||
Name: "couchdb",
|
||||
},
|
||||
"couchdb:1.5": {
|
||||
Ports: []string{"5984"},
|
||||
Tag: "bradrydzewski/couchdb:1.5",
|
||||
Name: "couchdb",
|
||||
},
|
||||
|
||||
// rabbitmq
|
||||
"rabbitmq": {
|
||||
Ports: []string{"5672", "15672"},
|
||||
Tag: "bradrydzewski/rabbitmq:3.2",
|
||||
Name: "rabbitmq",
|
||||
},
|
||||
"rabbitmq:3.2": {
|
||||
Ports: []string{"5672", "15672"},
|
||||
Tag: "bradrydzewski/rabbitmq:3.2",
|
||||
Name: "rabbitmq",
|
||||
},
|
||||
|
||||
// experimental images from 3rd parties
|
||||
|
||||
"zookeeper": {
|
||||
Ports: []string{"2181"},
|
||||
Tag: "jplock/zookeeper:3.4.5",
|
||||
Name: "zookeeper",
|
||||
},
|
||||
|
||||
// cassandra
|
||||
"cassandra": {
|
||||
Ports: []string{"9042", "7000", "7001", "7199", "9160", "49183"},
|
||||
Tag: "relateiq/cassandra",
|
||||
Name: "cassandra",
|
||||
},
|
||||
|
||||
// riak - TESTED
|
||||
"riak": {
|
||||
Ports: []string{"8087", "8098"},
|
||||
Tag: "guillermo/riak",
|
||||
Name: "riak",
|
||||
},
|
||||
}
|
||||
|
||||
// List of official Drone build images.
|
||||
var builders = map[string]*image{
|
||||
|
||||
// Clojure build images
|
||||
"lein": {Tag: "bradrydzewski/lein"},
|
||||
|
||||
// Dart build images
|
||||
"dart": {Tag: "bradrydzewski/dart:stable"},
|
||||
"dart_stable": {Tag: "bradrydzewski/dart:stable"},
|
||||
"dart_dev": {Tag: "bradrydzewski/dart:dev"},
|
||||
|
||||
// Erlang build images
|
||||
"erlang": {Tag: "bradrydzewski/erlang:R16B02"},
|
||||
"erlangR16B": {Tag: "bradrydzewski/erlang:R16B"},
|
||||
"erlangR16B02": {Tag: "bradrydzewski/erlang:R16B02"},
|
||||
"erlangR16B01": {Tag: "bradrydzewski/erlang:R16B01"},
|
||||
|
||||
// GCC build images
|
||||
"gcc": {Tag: "bradrydzewski/gcc:4.6"},
|
||||
"gcc4.6": {Tag: "bradrydzewski/gcc:4.6"},
|
||||
"gcc4.8": {Tag: "bradrydzewski/gcc:4.8"},
|
||||
|
||||
// Golang build images
|
||||
"go": {Tag: "bradrydzewski/go:1.2"},
|
||||
"go1": {Tag: "bradrydzewski/go:1.0"},
|
||||
"go1.1": {Tag: "bradrydzewski/go:1.1"},
|
||||
"go1.2": {Tag: "bradrydzewski/go:1.2"},
|
||||
|
||||
// Haskell build images
|
||||
"haskell": {Tag: "bradrydzewski/haskell:7.4"},
|
||||
"haskell7.4": {Tag: "bradrydzewski/haskell:7.4"},
|
||||
|
||||
// Java build images
|
||||
"java": {Tag: "bradrydzewski/java:openjdk7"},
|
||||
"openjdk6": {Tag: "bradrydzewski/java:openjdk6"},
|
||||
"openjdk7": {Tag: "bradrydzewski/java:openjdk7"},
|
||||
"oraclejdk7": {Tag: "bradrydzewski/java:oraclejdk7"},
|
||||
"oraclejdk8": {Tag: "bradrydzewski/java:oraclejdk8"},
|
||||
|
||||
// Node build images
|
||||
"node": {Tag: "bradrydzewski/node:0.10"},
|
||||
"node0.10": {Tag: "bradrydzewski/node:0.10"},
|
||||
"node0.8": {Tag: "bradrydzewski/node:0.8"},
|
||||
|
||||
// PHP build images
|
||||
"php": {Tag: "bradrydzewski/php:5.5"},
|
||||
"php5.5": {Tag: "bradrydzewski/php:5.5"},
|
||||
"php5.4": {Tag: "bradrydzewski/php:5.4"},
|
||||
|
||||
// Python build images
|
||||
"python": {Tag: "bradrydzewski/python:2.7"},
|
||||
"python2.7": {Tag: "bradrydzewski/python:2.7"},
|
||||
"python3.2": {Tag: "bradrydzewski/python:3.2"},
|
||||
"python3.3": {Tag: "bradrydzewski/python:3.3"},
|
||||
"pypy": {Tag: "bradrydzewski/python:pypy"},
|
||||
|
||||
// Ruby build images
|
||||
"ruby": {Tag: "bradrydzewski/ruby:2.0.0"},
|
||||
"ruby2.0.0": {Tag: "bradrydzewski/ruby:2.0.0"},
|
||||
"ruby1.9.3": {Tag: "bradrydzewski/ruby:1.9.3"},
|
||||
|
||||
// Scala build images
|
||||
"scala": {Tag: "bradrydzewski/scala:2.10.3"},
|
||||
"scala2.10.3": {Tag: "bradrydzewski/scala:2.10.3"},
|
||||
"scala2.9.3": {Tag: "bradrydzewski/scala:2.9.3"},
|
||||
}
|
105
pkg/build/log/log.go
Normal file
105
pkg/build/log/log.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
LOG_EMERG = iota
|
||||
LOG_ALERT
|
||||
LOG_CRIT
|
||||
LOG_ERR
|
||||
LOG_WARNING
|
||||
LOG_NOTICE
|
||||
LOG_INFO
|
||||
LOG_DEBUG
|
||||
)
|
||||
|
||||
var mu sync.Mutex
|
||||
|
||||
// the default Log priority
|
||||
var priority int = LOG_DEBUG
|
||||
|
||||
// the default Log output destination
|
||||
var output io.Writer = os.Stdout
|
||||
|
||||
// the log prefix
|
||||
var prefix string
|
||||
|
||||
// the log suffix
|
||||
var suffix string = "/n"
|
||||
|
||||
// SetPriority sets the default log level.
|
||||
func SetPriority(level int) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
priority = level
|
||||
}
|
||||
|
||||
// SetOutput sets the output destination.
|
||||
func SetOutput(w io.Writer) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
output = w
|
||||
}
|
||||
|
||||
// SetPrefix sets the prefix for the log message.
|
||||
func SetPrefix(pre string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
prefix = pre
|
||||
}
|
||||
|
||||
// SetSuffix sets the suffix for the log message.
|
||||
func SetSuffix(suf string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
suffix = suf
|
||||
}
|
||||
|
||||
func Write(out string, level int) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
// append the prefix and suffix
|
||||
out = prefix + out + suffix
|
||||
|
||||
if priority >= level {
|
||||
output.Write([]byte(out))
|
||||
}
|
||||
}
|
||||
|
||||
func Debug(out string) {
|
||||
Write(out, LOG_DEBUG)
|
||||
}
|
||||
|
||||
func Debugf(format string, a ...interface{}) {
|
||||
Debug(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func Info(out string) {
|
||||
Write(out, LOG_INFO)
|
||||
}
|
||||
|
||||
func Infof(format string, a ...interface{}) {
|
||||
Info(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func Err(out string) {
|
||||
Write(out, LOG_ERR)
|
||||
}
|
||||
|
||||
func Errf(format string, a ...interface{}) {
|
||||
Err(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func Notice(out string) {
|
||||
Write(out, LOG_NOTICE)
|
||||
}
|
||||
|
||||
func Noticef(format string, a ...interface{}) {
|
||||
Notice(fmt.Sprintf(format, a...))
|
||||
}
|
41
pkg/build/proxy/proxy.go
Normal file
41
pkg/build/proxy/proxy.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// bash header
|
||||
const header = "#!/bin/bash\n"
|
||||
|
||||
// this command string will check if the socat utility
|
||||
// exists, and if it does, will proxy connections to
|
||||
// the external IP address.
|
||||
const command = "[ -x /usr/bin/socat ] && socat TCP-LISTEN:%s,fork TCP:%s:%s &\n"
|
||||
|
||||
// Proxy stores proxy configuration details mapping
|
||||
// a local port to an external IP address with the
|
||||
// same port number.
|
||||
type Proxy map[string]string
|
||||
|
||||
func (p Proxy) Set(port, ip string) {
|
||||
p[port] = ip
|
||||
}
|
||||
|
||||
// String converts the proxy configuration details
|
||||
// to a bash script.
|
||||
func (p Proxy) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(header)
|
||||
for port, ip := range p {
|
||||
buf.WriteString(fmt.Sprintf(command, port, ip, port))
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Bytes converts the proxy configuration details
|
||||
// to a bash script in byte array format.
|
||||
func (p Proxy) Bytes() []byte {
|
||||
return []byte(p.String())
|
||||
}
|
32
pkg/build/proxy/proxy_test.go
Normal file
32
pkg/build/proxy/proxy_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProxy(t *testing.T) {
|
||||
// test creating a proxy with a few different
|
||||
// addresses, and our ability to create the
|
||||
// proxy shell script.
|
||||
p := Proxy{}
|
||||
p.Set("8080", "172.1.4.5")
|
||||
p.Set("8000", "172.1.3.1")
|
||||
b := p.Bytes()
|
||||
|
||||
expected := `#!/bin/bash
|
||||
[ -x /usr/bin/socat ] && socat TCP-LISTEN:8080,fork TCP:172.1.4.5:8080 &
|
||||
[ -x /usr/bin/socat ] && socat TCP-LISTEN:8000,fork TCP:172.1.3.1:8000 &
|
||||
`
|
||||
if string(b) != expected {
|
||||
t.Errorf("Invalid proxy \n%s", expected)
|
||||
}
|
||||
|
||||
// test creating a proxy script when there
|
||||
// are no proxy addresses added to the map
|
||||
p = Proxy{}
|
||||
b = p.Bytes()
|
||||
expected = "#!/bin/bash\n"
|
||||
if string(b) != expected {
|
||||
t.Errorf("Invalid proxy \n%s", expected)
|
||||
}
|
||||
}
|
118
pkg/build/repo/repo.go
Normal file
118
pkg/build/repo/repo.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Repo struct {
|
||||
// The path of the Repoisotry. This could be
|
||||
// the remote path of a Git repository or the path of
|
||||
// of the repository on the local file system.
|
||||
//
|
||||
// A remote path must start with http://, https://,
|
||||
// git://, ssh:// or git@. Otherwise we'll assume
|
||||
// the repository is located on the local filesystem.
|
||||
Path string
|
||||
|
||||
// (optional) Specific Branch that we should checkout
|
||||
// when the Repository is cloned. If no value is
|
||||
// provided we'll assume the default, master branch.
|
||||
Branch string
|
||||
|
||||
// (optional) Specific Commit Hash that we should
|
||||
// checkout when the Repository is cloned. If no
|
||||
// value is provided we'll assume HEAD.
|
||||
Commit string
|
||||
|
||||
// (optional) Pull Request number that we should
|
||||
// checkout when the Repository is cloned.
|
||||
PR string
|
||||
|
||||
// (optional) The filesystem path that the repository
|
||||
// will be cloned into (or copied to) inside the
|
||||
// host system (Docker Container).
|
||||
Dir string
|
||||
}
|
||||
|
||||
// IsRemote returns true if the Repository is located
|
||||
// on a remote server (ie Github, Bitbucket)
|
||||
func (r *Repo) IsRemote() bool {
|
||||
switch {
|
||||
case strings.HasPrefix(r.Path, "git://"):
|
||||
return true
|
||||
case strings.HasPrefix(r.Path, "git@"):
|
||||
return true
|
||||
case strings.HasPrefix(r.Path, "http://"):
|
||||
return true
|
||||
case strings.HasPrefix(r.Path, "https://"):
|
||||
return true
|
||||
case strings.HasPrefix(r.Path, "ssh://"):
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsLocal returns true if the Repository is located
|
||||
// on the local filesystem.
|
||||
func (r *Repo) IsLocal() bool {
|
||||
return !r.IsRemote()
|
||||
}
|
||||
|
||||
// IsGit returns true if the Repository is
|
||||
// a Git repoisitory.
|
||||
func (r *Repo) IsGit() bool {
|
||||
switch {
|
||||
case strings.HasPrefix(r.Path, "git://"):
|
||||
return true
|
||||
case strings.HasPrefix(r.Path, "git@"):
|
||||
return true
|
||||
case strings.HasPrefix(r.Path, "ssh://git@"):
|
||||
return true
|
||||
case strings.HasPrefix(r.Path, "https://github.com/"):
|
||||
return true
|
||||
case strings.HasPrefix(r.Path, "http://github.com"):
|
||||
return true
|
||||
case strings.HasSuffix(r.Path, ".git"):
|
||||
return true
|
||||
}
|
||||
|
||||
// we could also ping the repository to check
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// returns commands that can be used in a Dockerfile
|
||||
// to clone the repository.
|
||||
//
|
||||
// TODO we should also enable Mercurial projects and SVN projects
|
||||
func (r *Repo) Commands() []string {
|
||||
|
||||
// get the branch. default to master
|
||||
// if no branch exists.
|
||||
branch := r.Branch
|
||||
if len(branch) == 0 {
|
||||
branch = "master"
|
||||
}
|
||||
|
||||
cmds := []string{}
|
||||
cmds = append(cmds, fmt.Sprintf("git clone --branch=%s %s %s", branch, r.Path, r.Dir))
|
||||
|
||||
switch {
|
||||
// if a specific commit is provided then we'll
|
||||
// need to clone it.
|
||||
case len(r.PR) > 0:
|
||||
|
||||
cmds = append(cmds, fmt.Sprintf("git fetch origin +refs/pull/%s/head:refs/remotes/origin/pr/%s", r.PR, r.PR))
|
||||
cmds = append(cmds, fmt.Sprintf("git checkout -qf pr/%s", r.PR))
|
||||
//cmds = append(cmds, fmt.Sprintf("git fetch origin +refs/pull/%s/merge:", r.PR))
|
||||
//cmds = append(cmds, fmt.Sprintf("git checkout -qf %s", "FETCH_HEAD"))
|
||||
// if a specific commit is provided then we'll
|
||||
// need to clone it.
|
||||
case len(r.Commit) > 0:
|
||||
cmds = append(cmds, fmt.Sprintf("git checkout -qf %s", r.Commit))
|
||||
}
|
||||
|
||||
return cmds
|
||||
}
|
54
pkg/build/repo/repo_test.go
Normal file
54
pkg/build/repo/repo_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsRemote(t *testing.T) {
|
||||
repos := []struct {
|
||||
path string
|
||||
remote bool
|
||||
}{
|
||||
{"git://github.com/foo/far", true},
|
||||
{"git://github.com/foo/far.git", true},
|
||||
{"git@github.com:foo/far", true},
|
||||
{"git@github.com:foo/far.git", true},
|
||||
{"http://github.com/foo/far.git", true},
|
||||
{"https://github.com/foo/far.git", true},
|
||||
{"ssh://baz.com/foo/far.git", true},
|
||||
{"/var/lib/src", false},
|
||||
{"/home/ubuntu/src", false},
|
||||
{"src", false},
|
||||
}
|
||||
|
||||
for _, r := range repos {
|
||||
repo := Repo{Path: r.path}
|
||||
if remote := repo.IsRemote(); remote != r.remote {
|
||||
t.Errorf("IsRemote %s was %v, expected %v", r.path, remote, r.remote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsGit(t *testing.T) {
|
||||
repos := []struct {
|
||||
path string
|
||||
remote bool
|
||||
}{
|
||||
{"git://github.com/foo/far", true},
|
||||
{"git://github.com/foo/far.git", true},
|
||||
{"git@github.com:foo/far", true},
|
||||
{"git@github.com:foo/far.git", true},
|
||||
{"http://github.com/foo/far.git", true},
|
||||
{"https://github.com/foo/far.git", true},
|
||||
{"ssh://baz.com/foo/far.git", true},
|
||||
{"svn://gcc.gnu.org/svn/gcc/branches/gccgo", false},
|
||||
{"https://code.google.com/p/go", false},
|
||||
}
|
||||
|
||||
for _, r := range repos {
|
||||
repo := Repo{Path: r.path}
|
||||
if remote := repo.IsGit(); remote != r.remote {
|
||||
t.Errorf("IsGit %s was %v, expected %v", r.path, remote, r.remote)
|
||||
}
|
||||
}
|
||||
}
|
12
pkg/build/script/deployment/appfog.go
Normal file
12
pkg/build/script/deployment/appfog.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
type AppFog struct {
|
||||
}
|
||||
|
||||
func (a *AppFog) Write(f *buildfile.Buildfile) {
|
||||
|
||||
}
|
12
pkg/build/script/deployment/cloudcontrol.go
Normal file
12
pkg/build/script/deployment/cloudcontrol.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
type CloudControl struct {
|
||||
}
|
||||
|
||||
func (c *CloudControl) Write(f *buildfile.Buildfile) {
|
||||
|
||||
}
|
12
pkg/build/script/deployment/cloudfoundry.go
Normal file
12
pkg/build/script/deployment/cloudfoundry.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
type CloudFoundry struct {
|
||||
}
|
||||
|
||||
func (c *CloudFoundry) Write(f *buildfile.Buildfile) {
|
||||
|
||||
}
|
42
pkg/build/script/deployment/deployment.go
Normal file
42
pkg/build/script/deployment/deployment.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
// Deploy stores the configuration details
|
||||
// for deploying build artifacts when
|
||||
// a Build has succeeded
|
||||
type Deploy struct {
|
||||
AppFog *AppFog `yaml:"appfog,omitempty"`
|
||||
CloudControl *CloudControl `yaml:"cloudcontrol,omitempty"`
|
||||
CloudFoundry *CloudFoundry `yaml:"cloudfoundry,omitempty"`
|
||||
EngineYard *EngineYard `yaml:"engineyard,omitempty"`
|
||||
Heroku *Heroku `yaml:"heroku,omitempty"`
|
||||
Nodejitsu *Nodejitsu `yaml:"nodejitsu,omitempty"`
|
||||
Openshift *Openshift `yaml:"openshift,omitempty"`
|
||||
}
|
||||
|
||||
func (d *Deploy) Write(f *buildfile.Buildfile) {
|
||||
if d.AppFog != nil {
|
||||
d.AppFog.Write(f)
|
||||
}
|
||||
if d.CloudControl != nil {
|
||||
d.CloudControl.Write(f)
|
||||
}
|
||||
if d.CloudFoundry != nil {
|
||||
d.CloudFoundry.Write(f)
|
||||
}
|
||||
if d.EngineYard != nil {
|
||||
d.EngineYard.Write(f)
|
||||
}
|
||||
if d.Heroku != nil {
|
||||
d.Heroku.Write(f)
|
||||
}
|
||||
if d.Nodejitsu != nil {
|
||||
d.Nodejitsu.Write(f)
|
||||
}
|
||||
if d.Openshift != nil {
|
||||
d.Openshift.Write(f)
|
||||
}
|
||||
}
|
12
pkg/build/script/deployment/engineyard.go
Normal file
12
pkg/build/script/deployment/engineyard.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
type EngineYard struct {
|
||||
}
|
||||
|
||||
func (e *EngineYard) Write(f *buildfile.Buildfile) {
|
||||
|
||||
}
|
1
pkg/build/script/deployment/git.go
Normal file
1
pkg/build/script/deployment/git.go
Normal file
@@ -0,0 +1 @@
|
||||
package deployment
|
38
pkg/build/script/deployment/heroku.go
Normal file
38
pkg/build/script/deployment/heroku.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
type Heroku struct {
|
||||
App string `yaml:"app,omitempty"`
|
||||
Force bool `yaml:"force,omitempty"`
|
||||
Branch string `yaml:"branch,omitempty"`
|
||||
}
|
||||
|
||||
func (h *Heroku) Write(f *buildfile.Buildfile) {
|
||||
// get the current commit hash
|
||||
f.WriteCmdSilent("COMMIT=$(git rev-parse HEAD)")
|
||||
|
||||
// set the git user and email based on the individual
|
||||
// that made the commit.
|
||||
f.WriteCmdSilent("git config --global user.name $(git --no-pager log -1 --pretty=format:'%an')")
|
||||
f.WriteCmdSilent("git config --global user.email $(git --no-pager log -1 --pretty=format:'%ae')")
|
||||
|
||||
// add heroku as a git remote
|
||||
f.WriteCmd(fmt.Sprintf("git remote add heroku git@heroku.com:%s.git", h.App))
|
||||
|
||||
switch h.Force {
|
||||
case true:
|
||||
// this is useful when the there are artifacts generated
|
||||
// by the build script, such as less files converted to css,
|
||||
// that need to be deployed to Heroku.
|
||||
f.WriteCmd(fmt.Sprintf("git add -A"))
|
||||
f.WriteCmd(fmt.Sprintf("git commit -m 'adding build artifacts'"))
|
||||
f.WriteCmd(fmt.Sprintf("git push heroku $COMMIT:master --force"))
|
||||
case false:
|
||||
// otherwise we just do a standard git push
|
||||
f.WriteCmd(fmt.Sprintf("git push heroku $COMMIT:master"))
|
||||
}
|
||||
}
|
12
pkg/build/script/deployment/nodejitsu.go
Normal file
12
pkg/build/script/deployment/nodejitsu.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
type Nodejitsu struct {
|
||||
}
|
||||
|
||||
func (n *Nodejitsu) Write(f *buildfile.Buildfile) {
|
||||
|
||||
}
|
12
pkg/build/script/deployment/openshift.go
Normal file
12
pkg/build/script/deployment/openshift.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
type Openshift struct {
|
||||
}
|
||||
|
||||
func (o *Openshift) Write(f *buildfile.Buildfile) {
|
||||
|
||||
}
|
1
pkg/build/script/deployment/ssh.go
Normal file
1
pkg/build/script/deployment/ssh.go
Normal file
@@ -0,0 +1 @@
|
||||
package deployment
|
85
pkg/build/script/notification/email.go
Normal file
85
pkg/build/script/notification/email.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
)
|
||||
|
||||
type Email struct {
|
||||
Recipients []string `yaml:"recipients,omitempty"`
|
||||
Success string `yaml:"on_success"`
|
||||
Failure string `yaml:"on_failure"`
|
||||
|
||||
host string // smtp host address
|
||||
port string // smtp host port
|
||||
user string // smtp username for authentication
|
||||
pass string // smtp password for authentication
|
||||
from string // smtp email address. send from this address
|
||||
}
|
||||
|
||||
// SetServer is a function that will set the SMTP
|
||||
// server location and credentials
|
||||
func (e *Email) SetServer(host, port, user, pass, from string) {
|
||||
e.host = host
|
||||
e.port = port
|
||||
e.user = user
|
||||
e.pass = pass
|
||||
e.from = from
|
||||
}
|
||||
|
||||
// Send will send an email, either success or failure,
|
||||
// based on the Commit Status.
|
||||
func (e *Email) Send(context *Context) error {
|
||||
switch {
|
||||
case context.Commit.Status == "Success" && e.Success != "never":
|
||||
return e.sendSuccess(context)
|
||||
case context.Commit.Status == "Failure" && e.Failure != "never":
|
||||
return e.sendFailure(context)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendFailure sends email notifications to the list of
|
||||
// recipients indicating the build failed.
|
||||
func (e *Email) sendFailure(context *Context) error {
|
||||
// loop through and email recipients
|
||||
/*for _, email := range e.Recipients {
|
||||
if err := mail.SendFailure(context.Repo.Slug, email, context); err != nil {
|
||||
return err
|
||||
}
|
||||
}*/
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendSuccess sends email notifications to the list of
|
||||
// recipients indicating the build was a success.
|
||||
func (e *Email) sendSuccess(context *Context) error {
|
||||
// loop through and email recipients
|
||||
/*for _, email := range e.Recipients {
|
||||
if err := mail.SendSuccess(context.Repo.Slug, email, context); err != nil {
|
||||
return err
|
||||
}
|
||||
}*/
|
||||
return nil
|
||||
}
|
||||
|
||||
// send is a simple helper function to format and
|
||||
// send an email message.
|
||||
func (e *Email) send(to, subject, body string) error {
|
||||
// Format the raw email message body
|
||||
raw := fmt.Sprintf(emailTemplate, e.from, to, subject, body)
|
||||
auth := smtp.PlainAuth("", e.user, e.pass, e.host)
|
||||
addr := fmt.Sprintf("%s:%s", e.host, e.port)
|
||||
|
||||
return smtp.SendMail(addr, auth, e.from, []string{to}, []byte(raw))
|
||||
}
|
||||
|
||||
// text-template used to generate a raw Email message
|
||||
var emailTemplate = `From: %s
|
||||
To: %s
|
||||
Subject: %s
|
||||
MIME-version: 1.0
|
||||
Content-Type: text/html; charset="UTF-8"
|
||||
|
||||
%s`
|
64
pkg/build/script/notification/hipchat.go
Normal file
64
pkg/build/script/notification/hipchat.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/andybons/hipchat"
|
||||
)
|
||||
|
||||
const (
|
||||
startedMessage = "Building %s, commit %s, author %s"
|
||||
successMessage = "<b>Success</b> %s, commit %s, author %s"
|
||||
failureMessage = "<b>Failed</b> %s, commit %s, author %s"
|
||||
)
|
||||
|
||||
type Hipchat struct {
|
||||
Room string `yaml:"room,omitempty"`
|
||||
Token string `yaml:"token,omitempty"`
|
||||
Started bool `yaml:"on_started,omitempty"`
|
||||
Success bool `yaml:"on_success,omitempty"`
|
||||
Failure bool `yaml:"on_failure,omitempty"`
|
||||
}
|
||||
|
||||
func (h *Hipchat) Send(context *Context) error {
|
||||
switch {
|
||||
case context.Commit.Status == "Started" && h.Started:
|
||||
return h.sendStarted(context)
|
||||
case context.Commit.Status == "Success" && h.Success:
|
||||
return h.sendSuccess(context)
|
||||
case context.Commit.Status == "Failure" && h.Failure:
|
||||
return h.sendFailure(context)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Hipchat) sendStarted(context *Context) error {
|
||||
msg := fmt.Sprintf(startedMessage, context.Repo.Name, context.Commit.HashShort(), context.Commit.Author)
|
||||
return h.send(hipchat.ColorYellow, hipchat.FormatHTML, msg)
|
||||
}
|
||||
|
||||
func (h *Hipchat) sendFailure(context *Context) error {
|
||||
msg := fmt.Sprintf(failureMessage, context.Repo.Name, context.Commit.HashShort(), context.Commit.Author)
|
||||
return h.send(hipchat.ColorRed, hipchat.FormatHTML, msg)
|
||||
}
|
||||
|
||||
func (h *Hipchat) sendSuccess(context *Context) error {
|
||||
msg := fmt.Sprintf(successMessage, context.Repo.Name, context.Commit.HashShort(), context.Commit.Author)
|
||||
return h.send(hipchat.ColorGreen, hipchat.FormatHTML, msg)
|
||||
}
|
||||
|
||||
// helper function to send Hipchat requests
|
||||
func (h *Hipchat) send(color, format, message string) error {
|
||||
c := hipchat.Client{AuthToken: h.Token}
|
||||
req := hipchat.MessageRequest{
|
||||
RoomId: h.Room,
|
||||
From: "Drone",
|
||||
Message: message,
|
||||
Color: color,
|
||||
MessageFormat: format,
|
||||
Notify: true,
|
||||
}
|
||||
|
||||
return c.PostMessage(req)
|
||||
}
|
1
pkg/build/script/notification/irc.go
Normal file
1
pkg/build/script/notification/irc.go
Normal file
@@ -0,0 +1 @@
|
||||
package notification
|
53
pkg/build/script/notification/notification.go
Normal file
53
pkg/build/script/notification/notification.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/model"
|
||||
)
|
||||
|
||||
// Context represents the context of an
|
||||
// in-progress build request.
|
||||
type Context struct {
|
||||
// Global settings
|
||||
Host string
|
||||
|
||||
// User that owns the repository
|
||||
User *model.User
|
||||
|
||||
// Repository being built.
|
||||
Repo *model.Repo
|
||||
|
||||
// Commit being built
|
||||
Commit *model.Commit
|
||||
}
|
||||
|
||||
type Sender interface {
|
||||
Send(context *Context) error
|
||||
}
|
||||
|
||||
// Notification stores the configuration details
|
||||
// for notifying a user, or group of users,
|
||||
// when their Build has completed.
|
||||
type Notification struct {
|
||||
Email *Email `yaml:"email,omitempty"`
|
||||
Webhook *Webhook `yaml:"webhook,omitempty"`
|
||||
Hipchat *Hipchat `yaml:"hipchat,omitempty"`
|
||||
}
|
||||
|
||||
func (n *Notification) Send(context *Context) error {
|
||||
// send email notifications
|
||||
//if n.Email != nil && n.Email.Enabled {
|
||||
// n.Email.Send(context)
|
||||
//}
|
||||
|
||||
// send email notifications
|
||||
if n.Webhook != nil {
|
||||
n.Webhook.Send(context)
|
||||
}
|
||||
|
||||
// send email notifications
|
||||
if n.Hipchat != nil {
|
||||
n.Hipchat.Send(context)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
59
pkg/build/script/notification/webhook.go
Normal file
59
pkg/build/script/notification/webhook.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/drone/drone/pkg/model"
|
||||
)
|
||||
|
||||
type Webhook struct {
|
||||
URL []string `yaml:"urls,omitempty"`
|
||||
Success bool `yaml:"on_success,omitempty"`
|
||||
Failure bool `yaml:"on_failure,omitempty"`
|
||||
}
|
||||
|
||||
func (w *Webhook) Send(context *Context) error {
|
||||
switch {
|
||||
case context.Commit.Status == "Success" && w.Success:
|
||||
return w.send(context)
|
||||
case context.Commit.Status == "Failure" && w.Failure:
|
||||
return w.send(context)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// helper function to send HTTP requests
|
||||
func (w *Webhook) send(context *Context) error {
|
||||
// data will get posted in this format
|
||||
data := struct {
|
||||
Owner *model.User `json:"owner"`
|
||||
Repo *model.Repo `json:"repository"`
|
||||
Commit *model.Commit `json:"commit"`
|
||||
}{context.User, context.Repo, context.Commit}
|
||||
|
||||
// data json encoded
|
||||
payload, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// loop through and email recipients
|
||||
for _, url := range w.URL {
|
||||
go sendJson(url, payload)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// helper fuction to sent HTTP Post requests
|
||||
// with JSON data as the payload.
|
||||
func sendJson(url string, payload []byte) {
|
||||
buf := bytes.NewBuffer(payload)
|
||||
resp, err := http.Post(url, "application/json", buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
1
pkg/build/script/notification/zapier.go
Normal file
1
pkg/build/script/notification/zapier.go
Normal file
@@ -0,0 +1 @@
|
||||
package notification
|
1
pkg/build/script/publish/bintray.go
Normal file
1
pkg/build/script/publish/bintray.go
Normal file
@@ -0,0 +1 @@
|
||||
package publish
|
1
pkg/build/script/publish/dropbox.go
Normal file
1
pkg/build/script/publish/dropbox.go
Normal file
@@ -0,0 +1 @@
|
||||
package publish
|
1
pkg/build/script/publish/gems.go
Normal file
1
pkg/build/script/publish/gems.go
Normal file
@@ -0,0 +1 @@
|
||||
package publish
|
1
pkg/build/script/publish/maven.go
Normal file
1
pkg/build/script/publish/maven.go
Normal file
@@ -0,0 +1 @@
|
||||
package publish
|
1
pkg/build/script/publish/npm.go
Normal file
1
pkg/build/script/publish/npm.go
Normal file
@@ -0,0 +1 @@
|
||||
package publish
|
1
pkg/build/script/publish/pub.go
Normal file
1
pkg/build/script/publish/pub.go
Normal file
@@ -0,0 +1 @@
|
||||
package publish
|
18
pkg/build/script/publish/publish.go
Normal file
18
pkg/build/script/publish/publish.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package publish
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
// Publish stores the configuration details
|
||||
// for publishing build artifacts when
|
||||
// a Build has succeeded
|
||||
type Publish struct {
|
||||
S3 *S3 `yaml:"s3,omitempty"`
|
||||
}
|
||||
|
||||
func (p *Publish) Write(f *buildfile.Buildfile) {
|
||||
if p.S3 != nil {
|
||||
p.S3.Write(f)
|
||||
}
|
||||
}
|
2
pkg/build/script/publish/pypi.go
Normal file
2
pkg/build/script/publish/pypi.go
Normal file
@@ -0,0 +1,2 @@
|
||||
package publish
|
||||
|
85
pkg/build/script/publish/s3.go
Normal file
85
pkg/build/script/publish/s3.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package publish
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
)
|
||||
|
||||
type S3 struct {
|
||||
Key string `yaml:"access_key,omitempty"`
|
||||
Secret string `yaml:"secret_key,omitempty"`
|
||||
Bucket string `yaml:"bucket,omitempty"`
|
||||
|
||||
// us-east-1
|
||||
// us-west-1
|
||||
// us-west-2
|
||||
// eu-west-1
|
||||
// ap-southeast-1
|
||||
// ap-southeast-2
|
||||
// ap-northeast-1
|
||||
// sa-east-1
|
||||
Region string `yaml:"region,omitempty"`
|
||||
|
||||
// Indicates the files ACL, which should be one
|
||||
// of the following:
|
||||
// private
|
||||
// public-read
|
||||
// public-read-write
|
||||
// authenticated-read
|
||||
// bucket-owner-read
|
||||
// bucket-owner-full-control
|
||||
Access string `yaml:"acl,omitempty"`
|
||||
|
||||
// Copies the files from the specified directory.
|
||||
// Regexp matching will apply to match multiple
|
||||
// files
|
||||
//
|
||||
// Examples:
|
||||
// /path/to/file
|
||||
// /path/to/*.txt
|
||||
// /path/to/*/*.txt
|
||||
// /path/to/**
|
||||
Source string `yaml:"source,omitempty"`
|
||||
Target string `yaml:"target,omitempty"`
|
||||
|
||||
// Recursive uploads
|
||||
Recursive bool `yaml:"recursive"`
|
||||
|
||||
Branch string `yaml:"branch,omitempty"`
|
||||
}
|
||||
|
||||
func (s *S3) Write(f *buildfile.Buildfile) {
|
||||
// install the AWS cli using PIP
|
||||
f.WriteCmdSilent("[ -f /usr/bin/sudo ] || pip install awscli 1> /dev/null 2> /dev/null")
|
||||
f.WriteCmdSilent("[ -f /usr/bin/sudo ] && sudo pip install awscli 1> /dev/null 2> /dev/null")
|
||||
|
||||
f.WriteEnv("AWS_ACCESS_KEY_ID", s.Key)
|
||||
f.WriteEnv("AWS_SECRET_ACCESS_KEY", s.Secret)
|
||||
|
||||
// make sure a default region is set
|
||||
if len(s.Region) == 0 {
|
||||
s.Region = "us-east-1"
|
||||
}
|
||||
|
||||
// make sure a default access is set
|
||||
// let's be conservative and assume private
|
||||
if len(s.Region) == 0 {
|
||||
s.Region = "private"
|
||||
}
|
||||
|
||||
// if the target starts with a "/" we need
|
||||
// to remove it, otherwise we might adding
|
||||
// a 3rd slash to s3://
|
||||
if strings.HasPrefix(s.Target, "/") {
|
||||
s.Target = s.Target[1:]
|
||||
}
|
||||
|
||||
switch s.Recursive {
|
||||
case true:
|
||||
f.WriteCmd(fmt.Sprintf(`aws s3 cp %s s3://%s/%s --recursive --acl %s --region %s`, s.Source, s.Bucket, s.Target, s.Access, s.Region))
|
||||
case false:
|
||||
f.WriteCmd(fmt.Sprintf(`aws s3 cp %s s3://%s/%s --acl %s --region %s`, s.Source, s.Bucket, s.Target, s.Access, s.Region))
|
||||
}
|
||||
}
|
5
pkg/build/script/report/README.md
Normal file
5
pkg/build/script/report/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
cobertura.go
|
||||
coveralls.go
|
||||
gocov.go
|
||||
junit.go
|
||||
phpunit.go
|
123
pkg/build/script/script.go
Normal file
123
pkg/build/script/script.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package script
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"launchpad.net/goyaml"
|
||||
|
||||
"github.com/drone/drone/pkg/build/buildfile"
|
||||
"github.com/drone/drone/pkg/build/script/deployment"
|
||||
"github.com/drone/drone/pkg/build/script/notification"
|
||||
"github.com/drone/drone/pkg/build/script/publish"
|
||||
)
|
||||
|
||||
func ParseBuild(data []byte) (*Build, error) {
|
||||
build := Build{}
|
||||
|
||||
// parse the build configuration file
|
||||
err := goyaml.Unmarshal(data, &build)
|
||||
return &build, err
|
||||
}
|
||||
|
||||
func ParseBuildFile(filename string) (*Build, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ParseBuild(data)
|
||||
}
|
||||
|
||||
// Build stores the configuration details for
|
||||
// building, testing and deploying code.
|
||||
type Build struct {
|
||||
// Image specifies the Docker Image that will be
|
||||
// used to virtualize the Build process.
|
||||
Image string
|
||||
|
||||
// Name specifies a user-defined label used
|
||||
// to identify the build.
|
||||
Name string
|
||||
|
||||
// Script specifies the build and test commands.
|
||||
Script []string
|
||||
|
||||
// Env specifies the environment of the build.
|
||||
Env []string
|
||||
|
||||
// Services specifies external services, such as
|
||||
// database or messaging queues, that should be
|
||||
// linked to the build environment.
|
||||
Services []string
|
||||
|
||||
Deploy *deployment.Deploy `yaml:"deploy,omitempty"`
|
||||
Publish *publish.Publish `yaml:"publish,omitempty"`
|
||||
Notifications *notification.Notification `yaml:"notify,omitempty"`
|
||||
}
|
||||
|
||||
// Write adds all the steps to the build script, including
|
||||
// build commands, deploy and publish commands.
|
||||
func (b *Build) Write(f *buildfile.Buildfile) {
|
||||
// append build commands
|
||||
b.WriteBuild(f)
|
||||
|
||||
// write publish commands
|
||||
if b.Publish != nil {
|
||||
b.Publish.Write(f)
|
||||
}
|
||||
|
||||
// write deployment commands
|
||||
if b.Deploy != nil {
|
||||
b.Deploy.Write(f)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteBuild adds only the build steps to the build script,
|
||||
// omitting publish and deploy steps. This is important for
|
||||
// pull requests, where deployment would be undesirable.
|
||||
func (b *Build) WriteBuild(f *buildfile.Buildfile) {
|
||||
// append environment variables
|
||||
for _, env := range b.Env {
|
||||
parts := strings.Split(env, "=")
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
f.WriteEnv(parts[0], parts[1])
|
||||
}
|
||||
|
||||
// append build commands
|
||||
for _, cmd := range b.Script {
|
||||
f.WriteCmd(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
type Publish interface {
|
||||
Write(f *buildfile.Buildfile)
|
||||
}
|
||||
|
||||
type Deployment interface {
|
||||
Write(f *buildfile.Buildfile)
|
||||
}
|
||||
|
||||
type Notification interface {
|
||||
Set(c Context)
|
||||
}
|
||||
|
||||
type Context interface {
|
||||
Host() string
|
||||
Owner() string
|
||||
Name() string
|
||||
|
||||
Branch() string
|
||||
Hash() string
|
||||
Status() string
|
||||
Message() string
|
||||
Author() string
|
||||
Gravatar() string
|
||||
|
||||
Duration() int64
|
||||
HumanDuration() string
|
||||
|
||||
//Settings
|
||||
}
|
28
pkg/build/util.go
Normal file
28
pkg/build/util.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// createUID is a helper function that will
|
||||
// create a random, unique identifier.
|
||||
func createUID() string {
|
||||
c := sha1.New()
|
||||
r := createRandom()
|
||||
io.WriteString(c, string(r))
|
||||
s := fmt.Sprintf("%x", c.Sum(nil))
|
||||
return "drone-" + s[0:10]
|
||||
}
|
||||
|
||||
// createRandom creates a random block of bytes
|
||||
// that we can use to generate unique identifiers.
|
||||
func createRandom() []byte {
|
||||
k := make([]byte, sha1.BlockSize)
|
||||
if _, err := io.ReadFull(rand.Reader, k); err != nil {
|
||||
return nil
|
||||
}
|
||||
return k
|
||||
}
|
57
pkg/build/writer.go
Normal file
57
pkg/build/writer.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
//"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// the prefix used to determine if this is
|
||||
// data that should be stripped from the output
|
||||
prefix = []byte("#DRONE:")
|
||||
)
|
||||
|
||||
// custom writer to intercept the build
|
||||
// output
|
||||
type writer struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// Write appends the contents of p to the buffer. It will
|
||||
// scan for DRONE special formatting codes embedded in the
|
||||
// output, and will alter the output accordingly.
|
||||
func (w *writer) Write(p []byte) (n int, err error) {
|
||||
|
||||
lines := strings.Split(string(p), "\n")
|
||||
for i, line := range lines {
|
||||
|
||||
if strings.HasPrefix(line, "#DRONE:") {
|
||||
var cmd string
|
||||
|
||||
// extract the command (base16 encoded)
|
||||
// from the output
|
||||
fmt.Sscanf(line[7:], "%x", &cmd)
|
||||
|
||||
// echo the decoded command
|
||||
cmd = fmt.Sprintf("$ %s", cmd)
|
||||
w.Writer.Write([]byte(cmd))
|
||||
|
||||
} else {
|
||||
w.Writer.Write([]byte(line))
|
||||
}
|
||||
|
||||
if i < len(lines)-1 {
|
||||
w.Writer.Write([]byte("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// WriteString appends the contents of s to the buffer.
|
||||
func (w *writer) WriteString(s string) (n int, err error) {
|
||||
return w.Write([]byte(s))
|
||||
}
|
27
pkg/build/writer_test.go
Normal file
27
pkg/build/writer_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSetupDockerfile(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// wrap the buffer so we can analyze output
|
||||
w := writer{&buf}
|
||||
|
||||
w.WriteString("#DRONE:676f206275696c64\n")
|
||||
w.WriteString("#DRONE:676f2074657374202d76\n")
|
||||
w.WriteString("PASS\n")
|
||||
w.WriteString("ok github.com/garyburd/redigo/redis 0.113s\n")
|
||||
|
||||
expected := `$ go build
|
||||
$ go test -v
|
||||
PASS
|
||||
ok github.com/garyburd/redigo/redis 0.113s
|
||||
`
|
||||
if expected != buf.String() {
|
||||
t.Errorf("Expected commands decoded and echoed correctly. got \n%s", buf.String())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user