experimental branch. playing around with boltdb

This commit is contained in:
Brad Rydzewski
2015-04-07 01:20:55 -07:00
parent 1f8d65bb80
commit d9fd23a6df
284 changed files with 1076 additions and 22907 deletions

View File

@@ -1,549 +0,0 @@
package build
import (
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/drone/drone/shared/build/buildfile"
"github.com/drone/drone/shared/build/docker"
"github.com/drone/drone/shared/build/dockerfile"
"github.com/drone/drone/shared/build/log"
"github.com/drone/drone/shared/build/proxy"
"github.com/drone/drone/shared/build/repo"
"github.com/drone/drone/shared/build/script"
)
// 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.
}
func New(dockerClient *docker.Client) *Builder {
return &Builder{
dockerClient: dockerClient,
}
}
// 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
// Privileged indicates the build should be executed in privileged
// mode. The default is false.
Privileged bool
// 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
dockerClient *docker.Client
}
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
}
}
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 fmt.Errorf("Error: Unable to copy repository. %s", err)
}
}
// start all services required for the build
// that will get linked to the container.
for _, service := range b.Build.Services {
// Parse the name of the Docker image
// And then construct a fully qualified image name
owner, name, tag := parseImageName(service)
cname := fmt.Sprintf("%s/%s:%s", owner, name, tag)
// Get the image info
img, err := b.dockerClient.Images.Inspect(cname)
if err != nil {
// Get the image if it doesn't exist
if err := b.dockerClient.Images.Pull(cname); err != nil {
return fmt.Errorf("Error: Unable to pull image %s", cname)
}
img, err = b.dockerClient.Images.Inspect(cname)
if err != nil {
return fmt.Errorf("Error: Invalid or unknown image %s", cname)
}
}
// debugging
log.Infof("starting service container %s", cname)
// Run the contianer
run, err := b.dockerClient.Containers.RunDaemonPorts(cname, img.Config.ExposedPorts)
if err != nil {
return err
}
// Get the container info
info, err := b.dockerClient.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.
b.dockerClient.Containers.Stop(run.ID, 10)
b.dockerClient.Containers.Remove(run.ID)
return err
}
// Add the running service to the list
b.services = append(b.services, info)
}
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 or it's :latest tag
if _, err := b.dockerClient.Images.Inspect(b.Build.Image); err == docker.ErrNotFound || strings.HasSuffix(b.Build.Image, ":latest") {
// download the image if it doesn't exist
if err := b.dockerClient.Images.Pull(b.Build.Image); err != nil {
return fmt.Errorf("Error: Unable to pull image %s. %s", b.Build.Image, err)
}
} else if err != nil {
log.Errf("failed to inspect image %s", b.Build.Image)
}
// create the Docker image
id := createUID()
if err := b.dockerClient.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 = b.dockerClient.Images.Inspect(id)
if err != nil {
// if we have problems with the image make sure
// we remove it before we exit
log.Errf("failed to verify build image %s", 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 {
defer b.dockerClient.CloseIdleConnections()
// stop and destroy the container
if b.container != nil {
// debugging
log.Info("removing build container")
// stop the container, ignore error message
b.dockerClient.Containers.Stop(b.container.ID, 15)
// remove the container, ignore error message
if err := b.dockerClient.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
b.dockerClient.Containers.Stop(container.ID, 15)
// remove the service container, ignore the error
if err := b.dockerClient.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 := b.dockerClient.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{
Hostname: script.DockerHostname(b.Build.Docker),
Image: b.image.ID,
AttachStdin: false,
AttachStdout: true,
AttachStderr: true,
}
// configure if Docker should run in privileged mode
host := docker.HostConfig{
Privileged: (b.Privileged && len(b.Repo.PR) == 0),
}
if host.Privileged {
host.NetworkMode = script.DockerNetworkMode(b.Build.Docker)
}
// debugging
log.Noticef("starting build %s", b.Build.Name)
// link service containers
for i, service := range b.services {
// convert name of the image to a slug
_, name, _ := parseImageName(b.Build.Services[i])
// link the service container to our
// build container.
host.Links = append(host.Links, service.Name[1:]+":"+name)
}
// where are temp files going to go?
tmpPath := "/tmp/drone"
if len(os.Getenv("DRONE_TMP")) > 0 {
tmpPath = os.Getenv("DRONE_TMP")
}
log.Infof("temp directory is %s", tmpPath)
if err := os.MkdirAll(tmpPath, 0777); err != nil {
return fmt.Errorf("Failed to create temp directory at %s: %s", tmpPath, err)
}
// link cached volumes
conf.Volumes = make(map[string]struct{})
for _, volume := range b.Build.Cache {
name := filepath.Clean(b.Repo.Name)
volume := filepath.Clean(volume)
// with Docker, volumes must be an absolute path. If an absolute
// path is not provided, then assume it is for the repository
// working directory.
if strings.HasPrefix(volume, "/") == false {
volume = filepath.Join(b.Repo.Dir, volume)
}
// local cache path on the host machine
// this path is going to be really long
hostpath := filepath.Join(tmpPath, name, volume)
// check if the volume is created
if _, err := os.Stat(hostpath); err != nil {
// if does not exist then create
os.MkdirAll(hostpath, 0777)
}
host.Binds = append(host.Binds, hostpath+":"+volume)
conf.Volumes[volume] = struct{}{}
// debugging
log.Infof("mounting volume %s:%s", hostpath, volume)
}
// create the container from the image
run, err := b.dockerClient.Containers.Create(&conf)
if err != nil {
return fmt.Errorf("Error: Failed to create build container. %s", err)
}
// cache instance of docker.Run
b.container = run
// attach to the container
go func() {
b.dockerClient.Containers.Attach(run.ID, &writer{b.Stdout, 0})
}()
// start the container
if err := b.dockerClient.Containers.Start(run.ID, &host); err != nil {
b.BuildState.ExitCode = 1
b.BuildState.Finished = time.Now().UTC().Unix()
return fmt.Errorf("Error: Failed to start build container. %s", err)
}
// wait for the container to stop
wait, err := b.dockerClient.Containers.Wait(run.ID)
if err != nil {
b.BuildState.ExitCode = 1
b.BuildState.Finished = time.Now().UTC().Unix()
return fmt.Errorf("Error: Failed to wait for build container. %s", err)
}
// set completion time
b.BuildState.Finished = time.Now().UTC().Unix()
// get the exit code if possible
b.BuildState.ExitCode = wait.StatusCode
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 image
// is the "ubuntu" user, since all build images
// inherit from the ubuntu cloud ISO
dockerfile.WriteUser("ubuntu")
dockerfile.WriteEnv("LANG", "en_US.UTF-8")
dockerfile.WriteEnv("LANGUAGE", "en_US:en")
dockerfile.WriteEnv("LOGNAME", "ubuntu")
dockerfile.WriteEnv("HOME", "/home/ubuntu")
dockerfile.WriteRun("sudo chown -R ubuntu:ubuntu /var/cache/drone")
dockerfile.WriteRun("sudo chown -R ubuntu:ubuntu /usr/local/bin/drone")
default:
// all other images are assumed to use
// the root user.
dockerfile.WriteUser("root")
dockerfile.WriteEnv("LOGNAME", "root")
dockerfile.WriteEnv("HOME", "/root")
}
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()
// add environment variables for user env
f.WriteEnv("TERM", "xterm")
f.WriteEnv("GOPATH", "/var/cache/drone")
f.WriteEnv("SHELL", "/bin/bash")
// add environment variables about the build
f.WriteEnv("CI", "true")
f.WriteEnv("DRONE", "true")
f.WriteEnv("DRONE_REMOTE", b.Repo.Path)
f.WriteEnv("DRONE_BRANCH", b.Repo.Branch)
f.WriteEnv("DRONE_COMMIT", b.Repo.Commit)
f.WriteEnv("DRONE_PR", b.Repo.PR)
f.WriteEnv("DRONE_BUILD_DIR", b.Repo.Dir)
// add environment variables for code coverage
// systems, like coveralls.
f.WriteEnv("CI_NAME", "DRONE")
f.WriteEnv("CI_BUILD_URL", "")
f.WriteEnv("CI_REMOTE", b.Repo.Path)
f.WriteEnv("CI_BRANCH", b.Repo.Branch)
f.WriteEnv("CI_PULL_REQUEST", b.Repo.PR)
// add /etc/hosts entries
for _, mapping := range b.Build.Hosts {
f.WriteHost(mapping)
}
if len(b.Key) != 0 {
f.WriteFile("$HOME/.ssh/id_rsa", b.Key, 600)
}
// 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, b.Repo)
} 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)
}

View File

@@ -1,586 +0,0 @@
package build
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"testing"
"github.com/drone/drone/shared/build/buildfile"
"github.com/drone/drone/shared/build/docker"
"github.com/drone/drone/shared/build/proxy"
"github.com/drone/drone/shared/build/repo"
"github.com/drone/drone/shared/build/script"
)
var (
// mux is the HTTP request multiplexer used with the test server.
mux *http.ServeMux
// server is a test HTTP server used to provide mock API responses.
server *httptest.Server
// docker client
client *docker.Client
)
// setup a mock docker client for testing purposes. This will use
// a test http server that can return mock responses to the docker client.
func setup() {
mux = http.NewServeMux()
server = httptest.NewServer(mux)
url, _ := url.Parse(server.URL)
url.Scheme = "tcp"
os.Setenv("DOCKER_HOST", url.String())
client = docker.New()
}
func teardown() {
server.Close()
}
// TestSetup will test our ability to successfully create a Docker
// image for the build.
func TestSetup(t *testing.T) {
setup()
defer teardown()
// Handles a request to inspect the Go 1.2 image
// This will return a dummy image ID, so that the system knows
// the build image exists, and doens't need to be downloaded.
mux.HandleFunc("/v1.9/images/bradrydzewski/go:1.2/json", func(w http.ResponseWriter, r *http.Request) {
body := `[{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }]`
w.Write([]byte(body))
})
// Handles a request to create the build image, with the build
// script injected. This will return a dummy stream.
mux.HandleFunc("/v1.9/build", func(w http.ResponseWriter, r *http.Request) {
body := `{"stream":"Step 1..."}`
w.Write([]byte(body))
})
// Handles a request to inspect the newly created build image. Note
// that we are doing a "wildcard" url match here, since the name of
// the image will be random. This will return a dummy image ID
// to confirm the build image was created successfully.
mux.HandleFunc("/v1.9/images/", func(w http.ResponseWriter, r *http.Request) {
body := `{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }`
w.Write([]byte(body))
})
b := Builder{}
b.Repo = &repo.Repo{}
b.Repo.Path = "git://github.com/drone/drone.git"
b.Build = &script.Build{}
b.Build.Image = "go1.2"
b.dockerClient = client
if err := b.setup(); err != nil {
t.Errorf("Expected success, got %s", err)
}
// verify the Image is being correctly set
if b.image == nil {
t.Errorf("Expected image not nil")
}
expectedID := "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c"
if b.image.ID != expectedID {
t.Errorf("Expected image.ID %s, got %s", expectedID, b.image.ID)
}
}
// TestSetupEmptyImage will test our ability to handle a nil or
// blank Docker build image. We expect this to return an error.
func TestSetupEmptyImage(t *testing.T) {
b := Builder{Build: &script.Build{}}
var got, want = b.setup(), "Error: missing Docker image"
if got == nil || got.Error() != want {
t.Errorf("Expected error %s, got %s", want, got)
}
}
// TestSetupErrorInspectImage will test our ability to handle a
// failure when inspecting an image (i.e. bradrydzewski/mysql:latest),
// which should trigger a `docker pull`.
func TestSetupErrorInspectImage(t *testing.T) {
t.Skip()
}
// TestSetupErrorPullImage will test our ability to handle a
// failure when pulling an image (i.e. bradrydzewski/mysql:latest)
func TestSetupErrorPullImage(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
})
}
// TestSetupErrorRunDaemonPorts will test our ability to handle a
// failure when starting a service (i.e. mysql) as a daemon.
func TestSetupErrorRunDaemonPorts(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) {
data := []byte(`{"config": { "ExposedPorts": { "6379/tcp": {}}}}`)
w.Write(data)
})
mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
})
b := Builder{}
b.Repo = &repo.Repo{}
b.Repo.Path = "git://github.com/drone/drone.git"
b.Build = &script.Build{}
b.Build.Image = "go1.2"
b.Build.Services = append(b.Build.Services, "mysql")
b.dockerClient = client
var got, want = b.setup(), docker.ErrBadRequest
if got == nil || got != want {
t.Errorf("Expected error %s, got %s", want, got)
}
}
// TestSetupErrorServiceInspect will test our ability to handle a
// failure when a service (i.e. mysql) is started successfully,
// but cannot be queried post-start with the Docker remote API.
func TestSetupErrorServiceInspect(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) {
data := []byte(`{"config": { "ExposedPorts": { "6379/tcp": {}}}}`)
w.Write(data)
})
mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) {
body := `{ "Id":"e90e34656806", "Warnings":[] }`
w.Write([]byte(body))
})
mux.HandleFunc("/v1.9/containers/e90e34656806/start", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
})
mux.HandleFunc("/v1.9/containers/e90e34656806/json", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
})
b := Builder{}
b.Repo = &repo.Repo{}
b.Repo.Path = "git://github.com/drone/drone.git"
b.Build = &script.Build{}
b.Build.Image = "go1.2"
b.Build.Services = append(b.Build.Services, "mysql")
b.dockerClient = client
var got, want = b.setup(), docker.ErrBadRequest
if got == nil || got != want {
t.Errorf("Expected error %s, got %s", want, got)
}
}
// TestSetupErrorImagePull will test our ability to handle a
// failure when a the build image cannot be pulled from the index.
func TestSetupErrorImagePull(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
})
mux.HandleFunc("/v1.9/images/create?fromImage=bradrydzewski/mysql&tag=5.5", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
})
b := Builder{}
b.Repo = &repo.Repo{}
b.Repo.Path = "git://github.com/drone/drone.git"
b.Build = &script.Build{}
b.Build.Image = "go1.2"
b.Build.Services = append(b.Build.Services, "mysql")
b.dockerClient = client
var got, want = b.setup(), fmt.Errorf("Error: Unable to pull image bradrydzewski/mysql:5.5")
if got == nil || got.Error() != want.Error() {
t.Errorf("Expected error %s, got %s", want, got)
}
}
// TestSetupErrorUpdate will test our ability to handle a
// failure when the build image cannot be updated
func TestSetupErrorUpdate(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v1.9/images/create", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
})
b := Builder{}
b.Repo = &repo.Repo{}
b.Repo.Path = "git://github.com/drone/drone.git"
b.Build = &script.Build{}
b.Build.Image = "bradrydzewski/go:latest"
b.dockerClient = client
var got, want = b.setup(), fmt.Errorf("Error: Unable to pull image bradrydzewski/go:latest. %s", docker.ErrBadRequest)
if got == nil || got.Error() != want.Error() {
t.Errorf("Expected error %s, got %s", want, got)
}
}
// TestSetupErrorBuild will test our ability to handle a failure
// when creating a Docker image with the injected build script,
// ssh keys, etc.
func TestSetupErrorBuild(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v1.9/images/bradrydzewski/go:1.2/json", func(w http.ResponseWriter, r *http.Request) {
body := `[{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }]`
w.Write([]byte(body))
})
mux.HandleFunc("/v1.9/build", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
})
b := Builder{}
b.Repo = &repo.Repo{}
b.Repo.Path = "git://github.com/drone/drone.git"
b.Build = &script.Build{}
b.Build.Image = "go1.2"
b.dockerClient = client
var got, want = b.setup(), docker.ErrBadRequest
if got == nil || got != want {
t.Errorf("Expected error %s, got %s", want, got)
}
}
// TestSetupErrorBuildInspect will test our ability to handle a failure
// when we successfully create a Docker image with the injected build script,
// ssh keys, etc, however, we cannot inspect it post-creation using
// the Docker remote API.
func TestSetupErrorBuildInspect(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v1.9/images/bradrydzewski/go:1.2/json", func(w http.ResponseWriter, r *http.Request) {
body := `[{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }]`
w.Write([]byte(body))
})
mux.HandleFunc("/v1.9/build", func(w http.ResponseWriter, r *http.Request) {
body := `{"stream":"Step 1..."}`
w.Write([]byte(body))
})
mux.HandleFunc("/v1.9/images/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
})
b := Builder{}
b.Repo = &repo.Repo{}
b.Repo.Path = "git://github.com/drone/drone.git"
b.Build = &script.Build{}
b.Build.Image = "go1.2"
b.dockerClient = client
var got, want = b.setup(), docker.ErrBadRequest
if got == nil || got != want {
t.Errorf("Expected error %s, got %s", want, got)
}
}
// TestTeardown will test our ability to sucessfully teardown a
// Docker-based build environment.
func TestTeardown(t *testing.T) {
setup()
defer teardown()
var (
containerStopped = false
containerRemoved = false
serviceStopped = false
serviceRemoved = false
imageRemoved = false
)
mux.HandleFunc("/v1.9/containers/7bf9ce0ffb/stop", func(w http.ResponseWriter, r *http.Request) {
containerStopped = true
w.WriteHeader(http.StatusOK)
})
mux.HandleFunc("/v1.9/containers/7bf9ce0ffb", func(w http.ResponseWriter, r *http.Request) {
containerRemoved = true
w.WriteHeader(http.StatusOK)
})
mux.HandleFunc("/v1.9/containers/ec62dcc736/stop", func(w http.ResponseWriter, r *http.Request) {
serviceStopped = true
w.WriteHeader(http.StatusOK)
})
mux.HandleFunc("/v1.9/containers/ec62dcc736", func(w http.ResponseWriter, r *http.Request) {
serviceRemoved = true
w.WriteHeader(http.StatusOK)
})
mux.HandleFunc("/v1.9/images/c3ab8ff137", func(w http.ResponseWriter, r *http.Request) {
imageRemoved = true
w.Write([]byte(`[{"Untagged":"c3ab8ff137"},{"Deleted":"c3ab8ff137"}]`))
})
b := Builder{}
b.dockerClient = client
b.services = append(b.services, &docker.Container{ID: "ec62dcc736"})
b.container = &docker.Run{ID: "7bf9ce0ffb"}
b.image = &docker.Image{ID: "c3ab8ff137"}
b.Build = &script.Build{Services: []string{"mysql"}}
b.teardown()
if !containerStopped {
t.Errorf("Expected Docker container was stopped")
}
if !containerRemoved {
t.Errorf("Expected Docker container was removed")
}
if !serviceStopped {
t.Errorf("Expected Docker mysql container was stopped")
}
if !serviceRemoved {
t.Errorf("Expected Docker mysql container was removed")
}
if !imageRemoved {
t.Errorf("Expected Docker image was removed")
}
}
func TestRun(t *testing.T) {
t.Skip()
}
func TestRunPrivileged(t *testing.T) {
setup()
defer teardown()
var conf = docker.HostConfig{}
mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) {
body := `{ "Id":"e90e34656806", "Warnings":[] }`
w.Write([]byte(body))
})
mux.HandleFunc("/v1.9/containers/e90e34656806/start", func(w http.ResponseWriter, r *http.Request) {
json.NewDecoder(r.Body).Decode(&conf)
w.WriteHeader(http.StatusBadRequest)
})
b := Builder{}
b.BuildState = &BuildState{}
b.dockerClient = client
b.Stdout = new(bytes.Buffer)
b.image = &docker.Image{ID: "c3ab8ff137"}
b.Build = &script.Build{}
b.Repo = &repo.Repo{}
b.run()
if conf.Privileged != false {
t.Errorf("Expected container NOT started in Privileged mode")
}
// now lets set priviliged mode
b.Privileged = true
b.run()
if conf.Privileged != true {
t.Errorf("Expected container IS started in Privileged mode")
}
// now lets set priviliged mode but for a pull request
b.Privileged = true
b.Repo.PR = "55"
b.run()
if conf.Privileged != false {
t.Errorf("Expected container NOT started in Privileged mode when PR")
}
}
func TestRunErrorCreate(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
})
b := Builder{}
b.BuildState = &BuildState{}
b.dockerClient = client
b.Stdout = new(bytes.Buffer)
b.image = &docker.Image{ID: "c3ab8ff137"}
b.Build = &script.Build{}
b.Repo = &repo.Repo{}
if err := b.run(); err == nil || err.Error() != "Error: Failed to create build container. Bad Request" {
t.Errorf("Expected error when trying to create build container")
}
}
func TestRunErrorStart(t *testing.T) {
setup()
defer teardown()
var (
containerCreated = false
containerStarted = false
)
mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) {
containerCreated = true
body := `{ "Id":"e90e34656806", "Warnings":[] }`
w.Write([]byte(body))
})
mux.HandleFunc("/v1.9/containers/e90e34656806/start", func(w http.ResponseWriter, r *http.Request) {
containerStarted = true
w.WriteHeader(http.StatusBadRequest)
})
b := Builder{}
b.BuildState = &BuildState{}
b.dockerClient = client
b.Stdout = new(bytes.Buffer)
b.image = &docker.Image{ID: "c3ab8ff137"}
b.Build = &script.Build{}
b.Repo = &repo.Repo{}
if err := b.run(); err == nil || err.Error() != "Error: Failed to start build container. Bad Request" {
t.Errorf("Expected error when trying to start build container")
}
if !containerCreated {
t.Errorf("Expected Docker endpoint was invoked to create container")
}
if !containerStarted {
t.Errorf("Expected Docker endpoint was invoked to start container")
}
if b.container == nil || b.container.ID != "e90e34656806" {
t.Errorf("Expected build container was created with ID e90e34656806")
}
}
func TestRunErrorWait(t *testing.T) {
t.Skip()
}
func TestWriteProxyScript(t *testing.T) {
// temporary directory to store file
dir, _ := ioutil.TempDir("", "drone-test-")
defer os.RemoveAll(dir)
// fake service container that we'll assume was part of the yaml
// and should be attached to the build container.
c := docker.Container{
NetworkSettings: &docker.NetworkSettings{
IPAddress: "172.1.4.5",
Ports: map[docker.Port][]docker.PortBinding{
docker.NewPort("tcp", "3306"): nil,
},
},
}
// this should generate the following proxy file
p := proxy.Proxy{}
p.Set("3306", "172.1.4.5")
want := p.String()
b := Builder{}
b.services = append(b.services, &c)
b.writeProxyScript(dir)
// persist a dummy proxy script to disk
got, err := ioutil.ReadFile(filepath.Join(dir, "proxy.sh"))
if err != nil {
t.Errorf("Expected proxy.sh file saved to disk")
}
if string(got) != want {
t.Errorf("Expected proxy.sh value saved as %s, got %s", want, got)
}
}
func TestWriteBuildScript(t *testing.T) {
// temporary directory to store file
dir, _ := ioutil.TempDir("", "drone-test-")
defer os.RemoveAll(dir)
b := Builder{}
b.Build = &script.Build{
Hosts: []string{"127.0.0.1"}}
b.Key = []byte("ssh-rsa AAA...")
b.Repo = &repo.Repo{
Path: "git://github.com/drone/drone.git",
Branch: "master",
Commit: "e7e046b35",
PR: "123",
Dir: "/var/cache/drone/github.com/drone/drone"}
b.writeBuildScript(dir)
// persist a dummy build script to disk
script, err := ioutil.ReadFile(filepath.Join(dir, "drone"))
if err != nil {
t.Errorf("Expected id_rsa file saved to disk")
}
f := buildfile.New()
f.WriteEnv("TERM", "xterm")
f.WriteEnv("GOPATH", "/var/cache/drone")
f.WriteEnv("SHELL", "/bin/bash")
f.WriteEnv("CI", "true")
f.WriteEnv("DRONE", "true")
f.WriteEnv("DRONE_REMOTE", "git://github.com/drone/drone.git")
f.WriteEnv("DRONE_BRANCH", "master")
f.WriteEnv("DRONE_COMMIT", "e7e046b35")
f.WriteEnv("DRONE_PR", "123")
f.WriteEnv("DRONE_BUILD_DIR", "/var/cache/drone/github.com/drone/drone")
f.WriteEnv("CI_NAME", "DRONE")
f.WriteEnv("CI_BUILD_URL", "")
f.WriteEnv("CI_REMOTE", "git://github.com/drone/drone.git")
f.WriteEnv("CI_BRANCH", "master")
f.WriteEnv("CI_PULL_REQUEST", "123")
f.WriteHost("127.0.0.1")
f.WriteFile("$HOME/.ssh/id_rsa", []byte("ssh-rsa AAA..."), 600)
f.WriteCmd("git clone --depth=0 --recursive git://github.com/drone/drone.git /var/cache/drone/github.com/drone/drone")
f.WriteCmd("git fetch origin +refs/pull/123/head:refs/remotes/origin/pr/123")
f.WriteCmd("git checkout -qf -b pr/123 origin/pr/123")
if string(script) != f.String() {
t.Errorf("Expected build script value saved as %s, got %s", f.String(), script)
}
}

View File

@@ -1,92 +0,0 @@
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=%q\n", key, value))
}
// WriteHost adds an entry to the /etc/hosts file.
func (b *Buildfile) WriteHost(mapping string) {
b.WriteCmdSilent(fmt.Sprintf("[ -f /usr/bin/sudo ] || echo %q | tee -a /etc/hosts", mapping))
b.WriteCmdSilent(fmt.Sprintf("[ -f /usr/bin/sudo ] && echo %q | sudo tee -a /etc/hosts", mapping))
}
// WriteFile add files as part of the script.
func (b *Buildfile) WriteFile(path string, file []byte, i int) {
b.WriteString(fmt.Sprintf("echo '%s' | tee %s > /dev/null\n", string(file), path))
b.WriteCmdSilent(fmt.Sprintf("chmod %d %s", i, path))
}
// every build script starts with the following
// code at the start.
var base = `
#!/bin/bash
set +e
# 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
if [ ! -d $HOME/.ssh ]; then
mkdir -p $HOME/.ssh
fi
chmod 0700 $HOME/.ssh
echo 'StrictHostKeyChecking no' | tee $HOME/.ssh/config > /dev/null
# 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 ##############################
`

View File

@@ -1,56 +0,0 @@
package buildfile
import (
"testing"
)
func TestWrite(t *testing.T) {
var f = New()
var got, want = f.String(), base
if got != want {
t.Errorf("Exepected New() returned %s, got %s", want, got)
}
f = &Buildfile{}
f.WriteCmd("echo hi")
got, want = f.String(), "echo '#DRONE:6563686f206869'\necho hi\n"
if got != want {
t.Errorf("Exepected WriteCmd returned %s, got %s", want, got)
}
f = &Buildfile{}
f.WriteCmdSilent("echo hi")
got, want = f.String(), "echo hi\n"
if got != want {
t.Errorf("Exepected WriteCmdSilent returned %s, got %s", want, got)
}
f = &Buildfile{}
f.WriteComment("this is a comment")
got, want = f.String(), "#this is a comment\n"
if got != want {
t.Errorf("Exepected WriteComment returned %s, got %s", want, got)
}
f = &Buildfile{}
f.WriteEnv("FOO", "BAR")
got, want = f.String(), "export FOO=\"BAR\"\n"
if got != want {
t.Errorf("Exepected WriteEnv returned %s, got %s", want, got)
}
f = &Buildfile{}
f.WriteHost("127.0.0.1")
got, want = f.String(), "[ -f /usr/bin/sudo ] || echo \"127.0.0.1\" | tee -a /etc/hosts\n[ -f /usr/bin/sudo ] && echo \"127.0.0.1\" | sudo tee -a /etc/hosts\n"
if got != want {
t.Errorf("Exepected WriteHost returned %s, got %s", want, got)
}
f = &Buildfile{}
f.WriteFile("$HOME/.ssh/id_rsa", []byte("ssh-rsa AAA..."), 600)
got, want = f.String(), "echo 'ssh-rsa AAA...' | tee $HOME/.ssh/id_rsa > /dev/null\nchmod 600 $HOME/.ssh/id_rsa\n"
if got != want {
t.Errorf("Exepected WriteFile returned \n%s, \ngot\n%s", want, got)
}
}

View File

@@ -1,391 +0,0 @@
package docker
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"os"
"strings"
"time"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/utils"
)
const (
APIVERSION = 1.9
DEFAULTHTTPPORT = 2375
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 {
return NewHost("")
}
func NewHost(uri string) *Client {
var cli, _ = NewHostCert(uri, nil, nil)
return cli
}
func NewHostCertFile(uri, cert, key string) (*Client, error) {
if len(key) == 0 || len(cert) == 0 {
return NewHostCert(uri, nil, nil)
}
certfile, err := ioutil.ReadFile(cert)
if err != nil {
return nil, err
}
keyfile, err := ioutil.ReadFile(key)
if err != nil {
return nil, err
}
return NewHostCert(uri, certfile, keyfile)
}
func NewHostCert(uri string, cert, key []byte) (*Client, error) {
var host = GetHost(uri)
var proto, addr = SplitProtoAddr(host)
var cli = new(Client)
cli.proto = proto
cli.addr = addr
cli.scheme = "http"
cli.Images = &ImageService{cli}
cli.Containers = &ContainerService{cli}
// if no certificate is provided returns the
// client with no TLS configured.
if cert == nil || key == nil || len(cert) == 0 || len(key) == 0 {
cli.trans = &http.Transport{
Dial: func(dial_network, dial_addr string) (net.Conn, error) {
return net.DialTimeout(cli.proto, cli.addr, 32*time.Second)
},
}
return cli, nil
}
// loads the key value pair in pem format
pem, err := tls.X509KeyPair(cert, key)
if err != nil {
return nil, err
}
// setup the client TLS and store the certificate.
// also skip verification since we are (typically)
// going to be using certs for IP addresses.
cli.scheme = "https"
cli.tls = new(tls.Config)
cli.tls.InsecureSkipVerify = true
cli.tls.Certificates = []tls.Certificate{pem}
// disable compression for local socket communication.
if cli.proto == DEFAULTPROTOCOL {
cli.trans.DisableCompression = true
}
// creates a transport that uses the custom tls configuration
// to securely connect to remote Docker clients.
cli.trans = &http.Transport{
TLSClientConfig: cli.tls,
Dial: func(dial_network, dial_addr string) (net.Conn, error) {
return net.DialTimeout(cli.proto, cli.addr, 32*time.Second)
},
}
return cli, nil
}
// GetHost returns the Docker Host address in order to
// connect to the Docker Daemon. It implements a very
// simple set of fallthrough logic to determine which
// address to use.
func GetHost(host string) string {
// if a default value was provided this
// shoudl be used
if len(host) != 0 {
return host
}
// else attempt to use the DOCKER_HOST
// environment variable
var env = os.Getenv("DOCKER_HOST")
if len(env) != 0 {
return env
}
// else check to see if the default unix
// socket exists and return
_, err := os.Stat(DEFAULTUNIXSOCKET)
if err == nil {
return fmt.Sprintf("%s://%s", DEFAULTPROTOCOL, DEFAULTUNIXSOCKET)
}
// else return the standard TCP address
return fmt.Sprintf("tcp://0.0.0.0:%d", DEFAULTHTTPPORT)
}
// SplitProtoAddr is a helper function that splits
// a host into Protocol and Address.
func SplitProtoAddr(host string) (string, string) {
var parts = strings.Split(host, "://")
var proto, addr string
switch {
case len(parts) == 2:
proto = parts[0]
addr = parts[1]
default:
proto = "tcp"
addr = parts[0]
}
return proto, addr
}
type Client struct {
tls *tls.Config
trans *http.Transport
scheme string
proto string
addr string
Images *ImageService
Containers *ContainerService
}
var (
// Returned if the specified resource does not exist.
ErrNotFound = errors.New("Not Found")
// Return if something going wrong
ErrInternalServer = errors.New("Internal Server Error")
// 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.URL.Host = c.addr
req.URL.Scheme = "http"
if c.tls != nil {
req.URL.Scheme = "https"
}
resp, err := c.HTTPClient().Do(req)
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 500:
return ErrInternalServer
case 404:
return ErrNotFound
case 403:
return ErrForbidden
case 401:
return ErrNotAuthorized
case 400:
return ErrBadRequest
}
// Decode the JSON response
if out != nil {
return json.NewDecoder(resp.Body).Decode(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 := c.Dial()
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 = stdcopy.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.URL.Host = c.addr
req.URL.Scheme = "http"
if c.tls != nil {
req.URL.Scheme = "https"
}
resp, err := c.HTTPClient().Do(req)
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 500:
return ErrInternalServer
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 {
io.Copy(ioutil.Discard, resp.Body)
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
}
func (c *Client) HTTPClient() *http.Client {
if c.trans != nil {
return &http.Client{Transport: c.trans}
}
return &http.Client{
// WARN Leak Transport's Pooling Connection
Transport: &http.Transport{
Dial: func(dial_network, dial_addr string) (net.Conn, error) {
return net.DialTimeout(c.proto, c.addr, 32*time.Second)
},
},
}
}
func (c *Client) Dial() (net.Conn, error) {
if c.tls != nil && c.proto != "unix" {
return tls.Dial(c.proto, c.addr, c.tls)
}
return net.Dial(c.proto, c.addr)
}
func (c *Client) CloseIdleConnections() {
if c.trans != nil {
c.trans.CloseIdleConnections()
}
}

View File

@@ -1,32 +0,0 @@
package docker
import (
"os"
"testing"
)
func TestHostFromEnv(t *testing.T) {
os.Setenv("DOCKER_HOST", "tcp://1.1.1.1:2375")
defer os.Setenv("DOCKER_HOST", "")
client := New()
if client.proto != "tcp" {
t.Fail()
}
if client.addr != "1.1.1.1:2375" {
t.Fail()
}
}
func TestInvalidHostFromEnv(t *testing.T) {
os.Setenv("DOCKER_HOST", "tcp:1.1.1.1:2375") // missing tcp:// prefix
defer os.Setenv("DOCKER_HOST", "")
client := New()
if client.addr == "1.1.1.1:2375" {
t.Fail()
}
}

View File

@@ -1,146 +0,0 @@
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 map[Port]struct{}) (*Run, error) {
// setup configuration
config := Config{Image: image}
config.ExposedPorts = ports
// host configuration
host := HostConfig{}
host.PortBindings = make(map[Port][]PortBinding)
// loop through and add ports
for port, _ := range ports {
host.PortBindings[port] = []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)
}

View File

@@ -1,124 +0,0 @@
package docker
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"time"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/parsers"
)
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", image), nil, nil)
}
func (c *ImageService) Pull(image string) error {
name, tag := parsers.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 /*os.Stdout*/, nil, headers)
}

View File

@@ -1,167 +0,0 @@
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
NetworkMode 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
}

View File

@@ -1,44 +0,0 @@
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))
}

View File

@@ -1,63 +0,0 @@
package dockerfile
import (
"testing"
)
func TestWrite(t *testing.T) {
var f = New("ubuntu")
var got, want = f.String(), "FROM ubuntu\n"
if got != want {
t.Errorf("Exepected New() returned %s, got %s", want, got)
}
f = &Dockerfile{}
f.WriteAdd("src", "target")
got, want = f.String(), "ADD src target\n"
if got != want {
t.Errorf("Exepected WriteAdd returned %s, got %s", want, got)
}
f = &Dockerfile{}
f.WriteFrom("ubuntu")
got, want = f.String(), "FROM ubuntu\n"
if got != want {
t.Errorf("Exepected WriteFrom returned %s, got %s", want, got)
}
f = &Dockerfile{}
f.WriteRun("whoami")
got, want = f.String(), "RUN whoami\n"
if got != want {
t.Errorf("Exepected WriteRun returned %s, got %s", want, got)
}
f = &Dockerfile{}
f.WriteUser("root")
got, want = f.String(), "USER root\n"
if got != want {
t.Errorf("Exepected WriteUser returned %s, got %s", want, got)
}
f = &Dockerfile{}
f.WriteEnv("FOO", "BAR")
got, want = f.String(), "ENV FOO BAR\n"
if got != want {
t.Errorf("Exepected WriteEnv returned %s, got %s", want, got)
}
f = &Dockerfile{}
f.WriteWorkdir("/home/ubuntu")
got, want = f.String(), "WORKDIR /home/ubuntu\n"
if got != want {
t.Errorf("Exepected WriteWorkdir returned %s, got %s", want, got)
}
f = &Dockerfile{}
f.WriteEntrypoint("/root")
got, want = f.String(), "ENTRYPOINT /root\n"
if got != want {
t.Errorf("Exepected WriteEntrypoint returned %s, got %s", want, got)
}
}

View File

@@ -1,39 +0,0 @@
package git
const (
DefaultGitDepth = 50
)
// Git stores the configuration details for
// executing Git commands.
type Git struct {
// Depth options instructs git to create a shallow
// clone with a history truncated to the specified
// number of revisions.
Depth *int `yaml:"depth,omitempty"`
// The name of a directory to clone into.
Path *string `yaml:"path,omitempty"`
}
// GitDepth returns GitDefaultDepth
// when Git.Depth is empty.
// GitDepth returns Git.Depth
// when it is not empty.
func GitDepth(g *Git) int {
if g == nil || g.Depth == nil {
return DefaultGitDepth
}
return *g.Depth
}
// GitPath returns the given default path
// when Git.Path is empty.
// GitPath returns Git.Path
// when it is not empty.
func GitPath(g *Git, defaultPath string) string {
if g == nil || g.Path == nil {
return defaultPath
}
return *g.Path
}

View File

@@ -1,40 +0,0 @@
package git
import (
"testing"
)
func TestGitDepth(t *testing.T) {
var g *Git
var expected int
expected = DefaultGitDepth
g = nil
if actual := GitDepth(g); actual != expected {
t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual)
}
expected = DefaultGitDepth
g = &Git{}
if actual := GitDepth(g); actual != expected {
t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual)
}
expected = DefaultGitDepth
g = &Git{Depth: nil}
if actual := GitDepth(g); actual != expected {
t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual)
}
expected = 0
g = &Git{Depth: &expected}
if actual := GitDepth(g); actual != expected {
t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual)
}
expected = 1
g = &Git{Depth: &expected}
if actual := GitDepth(g); actual != expected {
t.Errorf("The result is invalid. [expected: %d][actual: %d]", expected, actual)
}
}

View File

@@ -1,239 +0,0 @@
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.5",
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.3"},
"go1": {Tag: "bradrydzewski/go:1.0"},
"go1.1": {Tag: "bradrydzewski/go:1.1"},
"go1.2": {Tag: "bradrydzewski/go:1.2"},
"go1.3": {Tag: "bradrydzewski/go:1.3"},
// 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"},
}

View File

@@ -1,105 +0,0 @@
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...))
}

View File

@@ -1,54 +0,0 @@
package proxy
import (
"bytes"
"fmt"
)
// bash header plus an embedded perl script that can be used
// as an alternative to socat to proxy tcp traffic.
const header = `#!/bin/bash
set +e
`
// TODO(bradrydzewski) probably going to remove this
//echo H4sICGKv1VQAA3NvY2F0LnBsAH1SXUvDQBB8Tn7FipUmkpr6gWBKgyIiBdGixVeJ6RZP00u4S6wi8be7t3exFsWEhNzO7M7MXba34kar+FHIuEJV+I1GmNwkyV2Zv2A9Wq+xwJzWfk/IqqlhDM+lkEEf+tHp2e3lfTj6Rj5hGc/Op4Oryd3s4joJ9nbDaFGqF6Air/gVU0M2nyua1Dug76pUZmrvkDSW79ATpUZTWIsPUomrkQF3NLt7WGaVY2tUr6g6OqNJMrm+mHFT4HtXZZ4VZ6yXQn+4x3c/csCUxVNgF1S8RcrdsfcNS+gapWdWw6HPYY2/QUoRAqdOVX/1JAqEYD+ED9+j0MDm2A8EXU+eyQeF2ZxJnlgQ4ijjcRfFYp5pzwuBkvfGQiSa51jRYTiCwmVZ4z/h6Zoiqi4Q73v0Xd4Ib6ohT95IaD38AVhtB6yP5cN1tMa25fym2DpTLNtQWnqwoL+O80t8q6GRBWoN+EaHoGFjhP1uf2/Fv6zHZrFA9aMpm69bBql+16YUOF4ER8OTYxfRCjBnpUSNHSl03lu/9b8ACaSZylQDAAA= | base64 -d | gunzip > /tmp/socat && chmod +x /tmp/socat
// this command string will check if the socat utility
// exists, and if it does, will proxy connections to
// the external IP address.
const command = "command -v socat >/dev/null && socat TCP-LISTEN:%s,fork TCP:%s:%s &\n"
// alternative command that acts as a "polyfill" for socat
// in the event that it isn't installed on the server
const polyfill = "[ -x /tmp/socat ] && /tmp/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))
// TODO(bradrydzewski) probably going to remove this
//buf.WriteString(fmt.Sprintf(polyfill, 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())
}

View File

@@ -1,27 +0,0 @@
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")
b := p.Bytes()
expected := header + "command -v socat >/dev/null && socat TCP-LISTEN:8080,fork TCP:172.1.4.5:8080 &\n"
if string(b) != expected {
t.Errorf("Invalid proxy got:\n%s\nwant:\n%s", string(b), expected)
}
// test creating a proxy script when there
// are no proxy addresses added to the map
p = Proxy{}
b = p.Bytes()
if string(b) != header {
t.Errorf("Invalid empty proxy file. Expected\n%s", header)
}
}

View File

@@ -1,127 +0,0 @@
package repo
import (
"fmt"
"strings"
)
type Repo struct {
// The name of the Repository. This should be the
// canonical name, for example, github.com/drone/drone.
Name string
// 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
// (optional) The depth of the `git clone` command.
Depth int
}
// 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, "gitlab@"):
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, "gitlab@"):
return true
case strings.HasPrefix(r.Path, "ssh://gitlab@"):
return true
case strings.HasPrefix(r.Path, "https://github"):
return true
case strings.HasPrefix(r.Path, "http://github"):
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{}
if len(r.PR) > 0 {
// If a specific PR is provided then we need to clone it.
cmds = append(cmds, fmt.Sprintf("git clone --depth=%d --recursive %s %s", r.Depth, r.Path, r.Dir))
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 -b pr/%s origin/pr/%s", r.PR, r.PR))
} else {
// Otherwise just clone the branch.
cmds = append(cmds, fmt.Sprintf("git clone --depth=%d --recursive --branch=%s %s %s", r.Depth, branch, r.Path, r.Dir))
// If a specific commit is provided then we'll need to check it out.
if len(r.Commit) > 0 {
cmds = append(cmds, fmt.Sprintf("git checkout -qf %s", r.Commit))
}
}
return cmds
}

View File

@@ -1,54 +0,0 @@
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)
}
}
}

View File

@@ -1,39 +0,0 @@
package script
const (
DefaultDockerNetworkMode = "bridge"
)
// Docker stores the configuration details for
// configuring docker container.
type Docker struct {
// NetworkMode (also known as `--net` option)
// Could be set only if Docker is running in privileged mode
NetworkMode *string `yaml:"net,omitempty"`
// Hostname (also known as `--hostname` option)
// Could be set only if Docker is running in privileged mode
Hostname *string `yaml:"hostname,omitempty"`
}
// DockerNetworkMode returns DefaultNetworkMode
// when Docker.NetworkMode is empty.
// DockerNetworkMode returns Docker.NetworkMode
// when it is not empty.
func DockerNetworkMode(d *Docker) string {
if d == nil || d.NetworkMode == nil {
return DefaultDockerNetworkMode
}
return *d.NetworkMode
}
// DockerNetworkMode returns empty string
// when Docker.NetworkMode is empty.
// DockerNetworkMode returns Docker.NetworkMode
// when it is not empty.
func DockerHostname(d *Docker) string {
if d == nil || d.Hostname == nil {
return ""
}
return *d.Hostname
}

View File

@@ -1,69 +0,0 @@
package script
import (
"testing"
)
func TestDockerNetworkMode(t *testing.T) {
var d *Docker
var expected string
expected = DefaultDockerNetworkMode
d = nil
if actual := DockerNetworkMode(d); actual != expected {
t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual)
}
expected = DefaultDockerNetworkMode
d = &Docker{}
if actual := DockerNetworkMode(d); actual != expected {
t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual)
}
expected = DefaultDockerNetworkMode
d = &Docker{NetworkMode: nil}
if actual := DockerNetworkMode(d); actual != expected {
t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual)
}
expected = "bridge"
d = &Docker{NetworkMode: &expected}
if actual := DockerNetworkMode(d); actual != expected {
t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual)
}
expected = "host"
d = &Docker{NetworkMode: &expected}
if actual := DockerNetworkMode(d); actual != expected {
t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual)
}
}
func TestDockerHostname(t *testing.T) {
var d *Docker
var expected string
expected = ""
d = nil
if actual := DockerHostname(d); actual != expected {
t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual)
}
expected = ""
d = &Docker{}
if actual := DockerHostname(d); actual != expected {
t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual)
}
expected = ""
d = &Docker{Hostname: nil}
if actual := DockerHostname(d); actual != expected {
t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual)
}
expected = "host"
d = &Docker{Hostname: &expected}
if actual := DockerHostname(d); actual != expected {
t.Errorf("The result is invalid. [expected: %s][actual: %s]", expected, actual)
}
}

View File

@@ -1,23 +0,0 @@
package script
import (
"sort"
"strings"
)
func Inject(script string, params map[string]string) string {
if params == nil {
return script
}
keys := []string{}
for k, _ := range params {
keys = append(keys, k)
}
sort.Sort(sort.Reverse(sort.StringSlice(keys)))
injected := script
for _, k := range keys {
v := params[k]
injected = strings.Replace(injected, "$$"+k, v, -1)
}
return injected
}

View File

@@ -1,32 +0,0 @@
package script
import (
"github.com/franela/goblin"
"testing"
)
func Test_Inject(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Inject params", func() {
g.It("Should replace vars with $$", func() {
s := "echo $$FOO $BAR"
m := map[string]string{}
m["FOO"] = "BAZ"
g.Assert("echo BAZ $BAR").Equal(Inject(s, m))
})
g.It("Should not replace vars with single $", func() {
s := "echo $FOO $BAR"
m := map[string]string{}
m["FOO"] = "BAZ"
g.Assert(s).Equal(Inject(s, m))
})
g.It("Should not replace vars in nil map", func() {
s := "echo $$FOO $BAR"
g.Assert(s).Equal(Inject(s, nil))
})
})
}

View File

@@ -1,159 +0,0 @@
package script
import (
"io/ioutil"
"strings"
"gopkg.in/yaml.v1"
"github.com/drone/drone/plugin/deploy"
"github.com/drone/drone/plugin/notify"
"github.com/drone/drone/plugin/publish"
"github.com/drone/drone/shared/build/buildfile"
"github.com/drone/drone/shared/build/git"
"github.com/drone/drone/shared/build/repo"
)
func ParseBuild(data string) (*Build, error) {
build := Build{}
// parse the build configuration file
err := yaml.Unmarshal([]byte(data), &build)
return &build, err
}
func ParseBuildFile(filename string, params map[string]string) (*Build, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return ParseBuild(Inject(string(data), params))
}
// 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
// Hosts specifies the custom IP address and
// hostname mappings.
Hosts []string
// Cache lists a set of directories that should
// persisted between builds.
Cache []string
// Services specifies external services, such as
// database or messaging queues, that should be
// linked to the build environment.
Services []string
// White-list of Branches that are built.
Branches []string
Deploy *deploy.Deploy `yaml:"deploy,omitempty"`
Publish *publish.Publish `yaml:"publish,omitempty"`
Notifications *notify.Notification `yaml:"notify,omitempty"`
// Git specified git-specific parameters, such as
// the clone depth and path
Git *git.Git `yaml:"git,omitempty"`
// Docker container parameters, such as
// NetworkMode and UserName
Docker *Docker `yaml:"docker,omitempty"`
}
// Write adds all the steps to the build script, including
// build commands, deploy and publish commands.
func (b *Build) Write(f *buildfile.Buildfile, r *repo.Repo) {
// append build commands
b.WriteBuild(f)
// write publish commands
if b.Publish != nil {
b.Publish.Write(f, r)
}
// write deployment commands
if b.Deploy != nil {
b.Deploy.Write(f, r)
}
// write exit value
f.WriteCmd("exit 0")
}
// 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.SplitN(env, "=", 2)
if len(parts) != 2 {
continue
}
f.WriteEnv(parts[0], parts[1])
}
// append build commands
for _, cmd := range b.Script {
f.WriteCmd(cmd)
}
}
func (b *Build) MatchBranch(branch string) bool {
if len(b.Branches) == 0 {
return true
}
for _, item := range b.Branches {
if item == branch {
return true
}
}
return false
}
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
}

View File

@@ -1,83 +0,0 @@
package build
import (
"crypto/rand"
"crypto/sha1"
"fmt"
"io"
"strings"
)
// 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
}
// list of service aliases and their full, canonical names
var defaultServices = map[string]string{
"cassandra": "relateiq/cassandra:latest",
"couchdb": "bradrydzewski/couchdb:1.5",
"elasticsearch": "bradrydzewski/elasticsearch:0.90",
"memcached": "bradrydzewski/memcached",
"mongodb": "bradrydzewski/mongodb:2.4",
"mysql": "bradrydzewski/mysql:5.5",
"neo4j": "bradrydzewski/neo4j:1.9",
"postgres": "bradrydzewski/postgres:9.1",
"redis": "bradrydzewski/redis:2.8",
"rabbitmq": "bradrydzewski/rabbitmq:3.2",
"riak": "guillermo/riak:latest",
"zookeeper": "jplock/zookeeper:3.4.5",
}
// parseImageName parses a Docker image name, in the format owner/name:tag,
// and returns each segment.
//
// If the owner is blank, it is assumed to be an official drone image,
// and will be prefixed with the appropriate owner name.
//
// If the tag is empty, it is assumed to be the latest version.
func parseImageName(image string) (owner, name, tag string) {
owner = "bradrydzewski" // this will eventually change to drone
name = image
tag = "latest"
// first we check to see if the image name is an alias
// for a known service.
//
// TODO I'm not a huge fan of this code here. Maybe it
// should get handled when the yaml is parsed, and
// convert the image and service names in the yaml
// to fully qualified names?
if cname, ok := defaultServices[image]; ok {
name = cname
}
parts := strings.Split(name, "/")
if len(parts) == 2 {
owner = parts[0]
name = parts[1]
}
parts = strings.Split(name, ":")
if len(parts) == 2 {
name = parts[0]
tag = parts[1]
}
return
}

View File

@@ -1,36 +0,0 @@
package build
import "testing"
func TestParseImageName(t *testing.T) {
images := []struct {
owner string
name string
tag string
cname string
}{
// full image name with all 3 sections present
{"johnsmith", "redis", "2.8", "johnsmith/redis:2.8"},
// image name with no tag specified
{"johnsmith", "redis", "latest", "johnsmith/redis"},
// image name with no owner specified
{"bradrydzewski", "redis", "2.8", "redis:2.8"},
// image name with ownly name specified
{"bradrydzewski", "redis2", "latest", "redis2"},
// image name that is a known alias
{"relateiq", "cassandra", "latest", "cassandra"},
}
for _, img := range images {
owner, name, tag := parseImageName(img.cname)
if owner != img.owner {
t.Errorf("Expected image %s with owner %s, got %s", img.cname, img.owner, owner)
}
if name != img.name {
t.Errorf("Expected image %s with name %s, got %s", img.cname, img.name, name)
}
if tag != img.tag {
t.Errorf("Expected image %s with tag %s, got %s", img.cname, img.tag, tag)
}
}
}

View File

@@ -1,72 +0,0 @@
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:")
// default limit to use when streaming build output.
DefaultLimit = 2000000
)
// custom writer to intercept the build
// output
type writer struct {
io.Writer
length int
}
// 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) {
// ensure we haven't exceeded the limit
if w.length > DefaultLimit {
w.Writer.Write([]byte("Truncating build output ..."))
return len(p), nil
}
// track the number of bytes written to the
// buffer so that we can limit it.
w.length += len(p)
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))
}

View File

@@ -1,27 +0,0 @@
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, 0}
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())
}
}

View File

@@ -1,109 +0,0 @@
package httputil
import (
"net/http"
"strings"
)
// IsHttps is a helper function that evaluates the http.Request
// and returns True if the Request uses HTTPS. It is able to detect,
// using the X-Forwarded-Proto, if the original request was HTTPS and
// routed through a reverse proxy with SSL termination.
func IsHttps(r *http.Request) bool {
switch {
case r.URL.Scheme == "https":
return true
case r.TLS != nil:
return true
case strings.HasPrefix(r.Proto, "HTTPS"):
return true
case r.Header.Get("X-Forwarded-Proto") == "https":
return true
default:
return false
}
}
// GetScheme is a helper function that evaluates the http.Request
// and returns the scheme, HTTP or HTTPS. It is able to detect,
// using the X-Forwarded-Proto, if the original request was HTTPS
// and routed through a reverse proxy with SSL termination.
func GetScheme(r *http.Request) string {
switch {
case r.URL.Scheme == "https":
return "https"
case r.TLS != nil:
return "https"
case strings.HasPrefix(r.Proto, "HTTPS"):
return "https"
case r.Header.Get("X-Forwarded-Proto") == "https":
return "https"
default:
return "http"
}
}
// GetHost is a helper function that evaluates the http.Request
// and returns the hostname. It is able to detect, using the
// X-Forarded-For header, the original hostname when routed
// through a reverse proxy.
func GetHost(r *http.Request) string {
switch {
case len(r.Host) != 0:
return r.Host
case len(r.URL.Host) != 0:
return r.URL.Host
case len(r.Header.Get("X-Forwarded-For")) != 0:
return r.Header.Get("X-Forwarded-For")
case len(r.Header.Get("X-Host")) != 0:
return r.Header.Get("X-Host")
case len(r.Header.Get("XFF")) != 0:
return r.Header.Get("XFF")
default:
return "localhost:8080"
}
}
// GetURL is a helper function that evaluates the http.Request
// and returns the URL as a string. Only the scheme + hostname
// are included; the path is excluded.
func GetURL(r *http.Request) string {
return GetScheme(r) + "://" + GetHost(r)
}
// GetCookie retrieves and verifies the cookie value.
func GetCookie(r *http.Request, name string) (value string) {
cookie, err := r.Cookie(name)
if err != nil {
return
}
value = cookie.Value
return
}
// SetCookie writes the cookie value.
func SetCookie(w http.ResponseWriter, r *http.Request, name, value string) {
cookie := http.Cookie{
Name: name,
Value: value,
Path: "/",
Domain: r.URL.Host,
HttpOnly: true,
Secure: IsHttps(r),
}
http.SetCookie(w, &cookie)
}
// DelCookie deletes a cookie.
func DelCookie(w http.ResponseWriter, r *http.Request, name string) {
cookie := http.Cookie{
Name: name,
Value: "deleted",
Path: "/",
Domain: r.URL.Host,
MaxAge: -1,
}
http.SetCookie(w, &cookie)
}

View File

@@ -1,52 +0,0 @@
package model
import (
"encoding/xml"
"time"
)
type CCProjects struct {
XMLName xml.Name `xml:"Projects"`
Project *CCProject `xml:"Project"`
}
type CCProject struct {
XMLName xml.Name `xml:"Project"`
Name string `xml:"name,attr"`
Activity string `xml:"activity,attr"`
LastBuildStatus string `xml:"lastBuildStatus,attr"`
LastBuildLabel string `xml:"lastBuildLabel,attr"`
LastBuildTime string `xml:"lastBuildTime,attr"`
WebURL string `xml:"webUrl,attr"`
}
func NewCC(r *Repo, c *Commit, url string) *CCProjects {
proj := &CCProject{
Name: r.Owner + "/" + r.Name,
WebURL: url,
Activity: "Building",
LastBuildStatus: "Unknown",
LastBuildLabel: "Unknown",
}
// if the build is not currently running then
// we can return the latest build status.
if c.Status != StatusStarted &&
c.Status != StatusEnqueue {
proj.Activity = "Sleeping"
proj.LastBuildStatus = c.Status
proj.LastBuildTime = time.Unix(c.Started, 0).Format(time.RFC3339)
proj.LastBuildLabel = c.ShaShort()
}
// If the build is not running, and not successful,
// then set to Failure. Not sure CCTray will support
// our custom failure types (ie Killed)
if c.Status != StatusStarted &&
c.Status != StatusEnqueue &&
c.Status != StatusSuccess {
proj.LastBuildStatus = StatusFailure
}
return &CCProjects{Project: proj}
}

View File

@@ -1,69 +0,0 @@
package model
import (
"time"
)
type Commit struct {
ID int64 `meddler:"commit_id,pk" json:"id"`
RepoID int64 `meddler:"repo_id" json:"-"`
Status string `meddler:"commit_status" json:"status"`
Started int64 `meddler:"commit_started" json:"started_at"`
Finished int64 `meddler:"commit_finished" json:"finished_at"`
Duration int64 `meddler:"commit_duration" json:"duration"`
Sha string `meddler:"commit_sha" json:"sha"`
Branch string `meddler:"commit_branch" json:"branch"`
PullRequest string `meddler:"commit_pr" json:"pull_request"`
Author string `meddler:"commit_author" json:"author"`
Gravatar string `meddler:"commit_gravatar" json:"gravatar"`
Timestamp string `meddler:"commit_timestamp" json:"timestamp"`
Message string `meddler:"commit_message" json:"message"`
Config string `meddler:"commit_yaml" json:"-"`
Created int64 `meddler:"commit_created" json:"created_at"`
Updated int64 `meddler:"commit_updated" json:"updated_at"`
}
// SetAuthor sets the author's email address and calculate the Gravatar hash.
func (c *Commit) SetAuthor(email string) {
c.Author = email
c.Gravatar = CreateGravatar(email)
}
// Returns the Short (--short) Commit Hash.
func (c *Commit) ShaShort() string {
if len(c.Sha) > 8 {
return c.Sha[:8]
} else {
return c.Sha
}
}
// Returns the Started Date as an ISO8601
// formatted string.
func (c *Commit) FinishedString() string {
return time.Unix(c.Finished, 0).Format("2006-01-02T15:04:05Z")
}
type CommitRepo struct {
Remote string `meddler:"repo_remote" json:"remote"`
Host string `meddler:"repo_host" json:"host"`
Owner string `meddler:"repo_owner" json:"owner"`
Name string `meddler:"repo_name" json:"name"`
CommitID int64 `meddler:"commit_id,pk" json:"-"`
RepoID int64 `meddler:"repo_id" json:"-"`
Status string `meddler:"commit_status" json:"status"`
Started int64 `meddler:"commit_started" json:"started_at"`
Finished int64 `meddler:"commit_finished" json:"finished_at"`
Duration int64 `meddler:"commit_duration" json:"duration"`
Sha string `meddler:"commit_sha" json:"sha"`
Branch string `meddler:"commit_branch" json:"branch"`
PullRequest string `meddler:"commit_pr" json:"pull_request"`
Author string `meddler:"commit_author" json:"author"`
Gravatar string `meddler:"commit_gravatar" json:"gravatar"`
Timestamp string `meddler:"commit_timestamp" json:"timestamp"`
Message string `meddler:"commit_message" json:"message"`
Config string `meddler:"commit_yaml" json:"-"`
Created int64 `meddler:"commit_created" json:"created_at"`
Updated int64 `meddler:"commit_updated" json:"updated_at"`
}

View File

@@ -1,15 +0,0 @@
package model
// Hook represents a subset of commit meta-data provided
// by post-commit and pull request hooks.
type Hook struct {
Owner string
Repo string
Sha string
Branch string
PullRequest string
Author string
Gravatar string
Timestamp string
Message string
}

View File

@@ -1,13 +0,0 @@
package model
// Login represents a standard subset of user meta-data
// provided by OAuth login services.
type Login struct {
ID int64
Login string
Access string
Secret string
Name string
Email string
Expiry int64
}

View File

@@ -1,13 +0,0 @@
package model
type Perm struct {
ID int64 `meddler:"perm_id,pk" json:"-"`
UserID int64 `meddler:"user_id" json:"-"`
RepoID int64 `meddler:"repo_id" json:"-"`
Read bool `meddler:"perm_read" json:"read"`
Write bool `meddler:"perm_write" json:"write"`
Admin bool `meddler:"perm_admin" json:"admin"`
Guest bool `meddler:"-" json:"guest"`
Created int64 `meddler:"perm_created" json:"-"`
Updated int64 `meddler:"perm_updated" json:"-"`
}

View File

@@ -1,21 +0,0 @@
package model
const (
RemoteGithub = "github.com"
RemoteGitlab = "gitlab.com"
RemoteGithubEnterprise = "enterprise.github.com"
RemoteBitbucket = "bitbucket.org"
RemoteStash = "stash.atlassian.com"
RemoteGogs = "gogs"
)
type Remote struct {
ID int64 `meddler:"remote_id,pk" json:"id"`
Type string `meddler:"remote_type" json:"type"`
Host string `meddler:"remote_host" json:"host"`
URL string `meddler:"remote_url" json:"url"`
API string `meddler:"remote_api" json:"api"`
Client string `meddler:"remote_client" json:"client"`
Secret string `meddler:"remote_secret" json:"secret"`
Open bool `meddler:"remote_open" json:"open"`
}

View File

@@ -1,66 +0,0 @@
package model
import (
"gopkg.in/yaml.v1"
)
var (
DefaultBranch = "master"
// default build timeout, in seconds
DefaultTimeout int64 = 7200
)
// RepoParams represents a set of private key value parameters
// for each Repository.
type RepoParams map[string]string
type Repo struct {
ID int64 `meddler:"repo_id,pk" json:"-"`
UserID int64 `meddler:"user_id" json:"-"`
Token string `meddler:"repo_token" json:"-"`
Remote string `meddler:"repo_remote" json:"remote"`
Host string `meddler:"repo_host" json:"host"`
Owner string `meddler:"repo_owner" json:"owner"`
Name string `meddler:"repo_name" json:"name"`
URL string `meddler:"repo_url" json:"url"`
CloneURL string `meddler:"repo_clone_url" json:"clone_url"`
GitURL string `meddler:"repo_git_url" json:"git_url"`
SSHURL string `meddler:"repo_ssh_url" json:"ssh_url"`
Active bool `meddler:"repo_active" json:"active"`
Private bool `meddler:"repo_private" json:"private"`
Privileged bool `meddler:"repo_privileged" json:"privileged"`
PostCommit bool `meddler:"repo_post_commit" json:"post_commits"`
PullRequest bool `meddler:"repo_pull_request" json:"pull_requests"`
PublicKey string `meddler:"repo_public_key" json:"-"`
PrivateKey string `meddler:"repo_private_key" json:"-"`
Params string `meddler:"repo_params" json:"-"`
Timeout int64 `meddler:"repo_timeout" json:"timeout"`
Created int64 `meddler:"repo_created" json:"created_at"`
Updated int64 `meddler:"repo_updated" json:"updated_at"`
// Role defines the user's role relative to this repository.
// Note that this data is stored separately in the datastore,
// and must be joined to populate.
Role *Perm `meddler:"-" json:"role,omitempty"`
}
func NewRepo(remote, owner, name string) (*Repo, error) {
repo := Repo{}
repo.Remote = remote
repo.Owner = owner
repo.Name = name
repo.Active = false
repo.PostCommit = true
repo.PullRequest = true
repo.Timeout = DefaultTimeout
return &repo, nil
}
func (r *Repo) ParamMap() (map[string]string, error) {
out := map[string]string{}
err := yaml.Unmarshal([]byte(r.Params), out)
return out, err
}

View File

@@ -1,26 +0,0 @@
package model
import (
"fmt"
)
type Request struct {
Host string `json:"-"`
User *User `json:"-"`
Repo *Repo `json:"repo"`
Commit *Commit `json:"commit"`
Prior *Commit `json:"prior_commit"`
}
// URL returns the link to the commit in
// string format.
func (r *Request) URL() string {
return fmt.Sprintf("%s/%s/%s/%s/%s/%s",
r.Host,
r.Repo.Host,
r.Repo.Owner,
r.Repo.Name,
r.Commit.Branch,
r.Commit.Sha,
)
}

View File

@@ -1,11 +0,0 @@
package model
const (
StatusNone = "None"
StatusEnqueue = "Pending"
StatusStarted = "Started"
StatusSuccess = "Success"
StatusFailure = "Failure"
StatusError = "Error"
StatusKilled = "Killed"
)

View File

@@ -1,7 +0,0 @@
package model
type Token struct {
AccessToken string
RefreshToken string
Expiry int64
}

View File

@@ -1,56 +0,0 @@
package model
import (
"time"
)
type User struct {
ID int64 `meddler:"user_id,pk" json:"-"`
Remote string `meddler:"user_remote" json:"remote"`
Login string `meddler:"user_login" json:"login"`
Access string `meddler:"user_access" json:"-"`
Secret string `meddler:"user_secret" json:"-"`
Name string `meddler:"user_name" json:"name"`
Email string `meddler:"user_email" json:"email,omitempty"`
Gravatar string `meddler:"user_gravatar" json:"gravatar"`
Token string `meddler:"user_token" json:"-"`
Admin bool `meddler:"user_admin" json:"admin"`
Active bool `meddler:"user_active" json:"active"`
Syncing bool `meddler:"user_syncing" json:"syncing"`
Created int64 `meddler:"user_created" json:"created_at"`
Updated int64 `meddler:"user_updated" json:"updated_at"`
Synced int64 `meddler:"user_synced" json:"synced_at"`
TokenExpiry int64 `meddler:"user_access_expires,zeroisnull" json:"-"`
}
func NewUser(remote, login, email string) *User {
user := User{}
user.Token = GenerateToken()
user.Login = login
user.Remote = remote
user.Active = true
user.SetEmail(email)
return &user
}
// SetEmail sets the email address and calculate the Gravatar hash.
func (u *User) SetEmail(email string) {
u.Email = email
u.Gravatar = CreateGravatar(email)
}
func (u *User) IsStale() bool {
switch {
case u.Synced == 0:
return true
// refresh every 24 hours
case u.Synced+DefaultExpires < time.Now().Unix():
return true
default:
return false
}
}
// by default, let's expire the user
// cache after 72 hours
var DefaultExpires = int64(time.Hour.Seconds() * 72)

View File

@@ -1,48 +0,0 @@
package model
import (
"crypto/md5"
"crypto/rand"
"fmt"
"io"
"strings"
)
// standard characters allowed in token string.
var chars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
// default token length
var length = 40
// GenerateToken generates random strings good for use in URIs to
// identify unique objects.
func GenerateToken() string {
b := make([]byte, length)
r := make([]byte, length+(length/4)) // storage for random bytes.
clen := byte(len(chars))
maxrb := byte(256 - (256 % len(chars)))
i := 0
for {
io.ReadFull(rand.Reader, r)
for _, c := range r {
if c >= maxrb {
// Skip this number to avoid modulo bias.
continue
}
b[i] = chars[c%clen]
i++
if i == length {
return string(b)
}
}
}
}
// helper function to create a Gravatar Hash
// for the given Email address.
func CreateGravatar(email string) string {
email = strings.ToLower(strings.TrimSpace(email))
hash := md5.New()
hash.Write([]byte(email))
return fmt.Sprintf("%x", hash.Sum(nil))
}

View File

@@ -1,19 +0,0 @@
package model
import (
"testing"
)
func Test_CreateGravatar(t *testing.T) {
var got, want = CreateGravatar("dr_cooper@caltech.edu"), "2b77ba83e2216ddcd11fe8c24b70c2a3"
if got != want {
t.Errorf("Got gravatar hash %s, want %s", got, want)
}
}
func Test_GenerateToken(t *testing.T) {
token := GenerateToken()
if len(token) != length {
t.Errorf("Want token length %d, got %d", length, len(token))
}
}

View File

@@ -1,39 +0,0 @@
package sshutil
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"code.google.com/p/go.crypto/ssh"
)
const (
RSA_BITS = 2048 // Default number of bits in an RSA key
RSA_BITS_MIN = 768 // Minimum number of bits in an RSA key
)
// helper function to generate an RSA Private Key.
func GeneratePrivateKey() (*rsa.PrivateKey, error) {
return rsa.GenerateKey(rand.Reader, RSA_BITS)
}
// helper function that marshalls an RSA Public Key to an SSH
// .authorized_keys format
func MarshalPublicKey(pubkey *rsa.PublicKey) string {
pk, err := ssh.NewPublicKey(pubkey)
if err != nil {
return ""
}
return string(ssh.MarshalAuthorizedKey(pk))
}
// helper function that marshalls an RSA Private Key to
// a PEM encoded file.
func MarshalPrivateKey(privkey *rsa.PrivateKey) string {
privateKeyMarshaled := x509.MarshalPKCS1PrivateKey(privkey)
privateKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Headers: nil, Bytes: privateKeyMarshaled})
return string(privateKeyPEM)
}