mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-10-21 20:25:57 +00:00
experimental branch. playing around with boltdb
This commit is contained in:
@@ -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)
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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 ##############################
|
||||
`
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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))
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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"},
|
||||
}
|
@@ -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...))
|
||||
}
|
@@ -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())
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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))
|
||||
})
|
||||
})
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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))
|
||||
}
|
@@ -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())
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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}
|
||||
}
|
@@ -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"`
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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:"-"`
|
||||
}
|
@@ -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"`
|
||||
}
|
@@ -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
|
||||
}
|
@@ -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,
|
||||
)
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
package model
|
||||
|
||||
const (
|
||||
StatusNone = "None"
|
||||
StatusEnqueue = "Pending"
|
||||
StatusStarted = "Started"
|
||||
StatusSuccess = "Success"
|
||||
StatusFailure = "Failure"
|
||||
StatusError = "Error"
|
||||
StatusKilled = "Killed"
|
||||
)
|
@@ -1,7 +0,0 @@
|
||||
package model
|
||||
|
||||
type Token struct {
|
||||
AccessToken string
|
||||
RefreshToken string
|
||||
Expiry int64
|
||||
}
|
@@ -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)
|
@@ -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))
|
||||
}
|
@@ -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))
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
Reference in New Issue
Block a user