mirror of
https://github.com/drone/drone-cli.git
synced 2024-11-23 09:21:56 +01:00
new builder package
This commit is contained in:
parent
4e57f9106c
commit
e0b10ec19e
124
builder/ambassador/ambassador.go
Normal file
124
builder/ambassador/ambassador.go
Normal file
@ -0,0 +1,124 @@
|
||||
package ambassador
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
var errNop = errors.New("Operation not supported")
|
||||
|
||||
// Ambassador is a wrapper around the Docker client that
|
||||
// provides a shared volume and network for all containers.
|
||||
type Ambassador struct {
|
||||
name string
|
||||
client dockerclient.Client
|
||||
}
|
||||
|
||||
// CreateContainer creates a container.
|
||||
func (c *Ambassador) CreateContainer(config *dockerclient.ContainerConfig, name string) (string, error) {
|
||||
return c.client.CreateContainer(config, name)
|
||||
}
|
||||
|
||||
// InspectContainer returns container details.
|
||||
func (c *Ambassador) InspectContainer(id string) (*dockerclient.ContainerInfo, error) {
|
||||
return c.client.InspectContainer(id)
|
||||
}
|
||||
|
||||
// ContainerLogs returns an io.ReadCloser for reading the
|
||||
// container logs.
|
||||
func (c *Ambassador) ContainerLogs(id string, options *dockerclient.LogOptions) (io.ReadCloser, error) {
|
||||
return c.client.ContainerLogs(id, options)
|
||||
}
|
||||
|
||||
// StartContainer starts a container. The ambassador volume
|
||||
// is automatically linked. The ambassador network is linked
|
||||
// iff a network mode is not already specified.
|
||||
func (c *Ambassador) StartContainer(id string, config *dockerclient.HostConfig) error {
|
||||
config.VolumesFrom = append(config.VolumesFrom, "container:"+c.name)
|
||||
if len(config.NetworkMode) == 0 {
|
||||
config.NetworkMode = "container:" + c.name
|
||||
}
|
||||
return c.client.StartContainer(id, config)
|
||||
}
|
||||
|
||||
// StopContainer stops a container.
|
||||
func (c *Ambassador) StopContainer(id string, timeout int) error {
|
||||
return c.client.StopContainer(id, timeout)
|
||||
}
|
||||
|
||||
// PullImage pulls an image.
|
||||
func (c *Ambassador) PullImage(name string, auth *dockerclient.AuthConfig) error {
|
||||
return c.client.PullImage(name, auth)
|
||||
}
|
||||
|
||||
// RemoveContainer removes a container.
|
||||
func (c *Ambassador) RemoveContainer(id string, force, volumes bool) error {
|
||||
return c.client.RemoveContainer(id, force, volumes)
|
||||
}
|
||||
|
||||
// KillContainer kills a running container.
|
||||
func (c *Ambassador) KillContainer(id, signal string) error {
|
||||
return c.client.KillContainer(id, signal)
|
||||
}
|
||||
|
||||
//
|
||||
// methods below are not implemented
|
||||
//
|
||||
|
||||
// Info returns a no-op error
|
||||
func (c *Ambassador) Info() (*dockerclient.Info, error) {
|
||||
return nil, errNop
|
||||
}
|
||||
|
||||
// ListContainers returns a no-op error
|
||||
func (c *Ambassador) ListContainers(all bool, size bool, filters string) ([]dockerclient.Container, error) {
|
||||
return nil, errNop
|
||||
}
|
||||
|
||||
// RestartContainer returns a no-op error
|
||||
func (c *Ambassador) RestartContainer(id string, timeout int) error {
|
||||
return errNop
|
||||
}
|
||||
|
||||
// StartMonitorEvents returns a no-op error
|
||||
func (c *Ambassador) StartMonitorEvents(cb dockerclient.Callback, ec chan error, args ...interface{}) {
|
||||
|
||||
}
|
||||
|
||||
// StopAllMonitorEvents returns a no-op error
|
||||
func (c *Ambassador) StopAllMonitorEvents() {
|
||||
|
||||
}
|
||||
|
||||
// Version returns a no-op error
|
||||
func (c *Ambassador) Version() (*dockerclient.Version, error) {
|
||||
return nil, errNop
|
||||
}
|
||||
|
||||
// ListImages returns a no-op error
|
||||
func (c *Ambassador) ListImages() ([]*dockerclient.Image, error) {
|
||||
return nil, errNop
|
||||
}
|
||||
|
||||
// RemoveImage returns a no-op error
|
||||
func (c *Ambassador) RemoveImage(name string) error {
|
||||
return errNop
|
||||
}
|
||||
|
||||
// PauseContainer returns a no-op error
|
||||
func (c *Ambassador) PauseContainer(name string) error {
|
||||
return errNop
|
||||
}
|
||||
|
||||
// UnpauseContainer returns a no-op error
|
||||
func (c *Ambassador) UnpauseContainer(name string) error {
|
||||
return errNop
|
||||
}
|
||||
|
||||
// Exec returns a no-op error
|
||||
func (c *Ambassador) Exec(config *dockerclient.ExecConfig) (string, error) {
|
||||
var empty string
|
||||
return empty, errNop
|
||||
}
|
35
builder/build.go
Normal file
35
builder/build.go
Normal file
@ -0,0 +1,35 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/drone/drone-cli/common"
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
// Build represents a build request.
|
||||
type Build struct {
|
||||
Repo *common.Repo
|
||||
Commit *common.Repo
|
||||
Config *common.Config
|
||||
Clone *common.Clone
|
||||
|
||||
Client dockerclient.Client
|
||||
}
|
||||
|
||||
// BuildPayload represents the payload of a plugin
|
||||
// that is serialized and sent to the plugin in JSON
|
||||
// format via stdin or arg[1].
|
||||
type BuildPayload struct {
|
||||
Repo *common.Repo `json:"repo"`
|
||||
Commit *common.Repo `json:"commit"`
|
||||
Clone *common.Clone `json:"clone"`
|
||||
|
||||
Config map[string]interface{} `json:"vargs"`
|
||||
}
|
||||
|
||||
// Encode encodes the payload in JSON format.
|
||||
func (b *BuildPayload) Encode() string {
|
||||
out, _ := json.Marshal(b)
|
||||
return string(out)
|
||||
}
|
@ -1,120 +1,118 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
type Builder struct {
|
||||
handlers []Handler
|
||||
}
|
||||
|
||||
"github.com/drone/drone-cli/common/uuid"
|
||||
)
|
||||
// Handle adds a build step handler to be processed
|
||||
// when running the build.
|
||||
func (b *Builder) Handle(h Handler) {
|
||||
b.handlers = append(b.handlers, h)
|
||||
}
|
||||
|
||||
const (
|
||||
ImageInit = "drone/drone-init"
|
||||
ImageClone = "drone/drone-clone-git"
|
||||
)
|
||||
|
||||
func Run(req *Request, resp ResponseWriter) error {
|
||||
|
||||
var containers []*Container
|
||||
|
||||
defer func() {
|
||||
for i := len(containers) - 1; i >= 0; i-- {
|
||||
container := containers[i]
|
||||
container.Stop()
|
||||
container.Kill()
|
||||
container.Remove()
|
||||
}
|
||||
}()
|
||||
|
||||
// temporary name for the build container
|
||||
//name := fmt.Sprintf("build-init-%s", createUID())
|
||||
net := req.Config.Docker.Net
|
||||
uid := uuid.CreateUUID()
|
||||
cmd := []string{req.Encode()}
|
||||
|
||||
// init container
|
||||
containers = append(containers, &Container{
|
||||
Name: fmt.Sprintf("drone-%s-init", uid),
|
||||
Image: ImageInit,
|
||||
Volumes: []string{"/drone"},
|
||||
Cmd: cmd,
|
||||
})
|
||||
|
||||
// clone container
|
||||
containers = append(containers, &Container{
|
||||
Name: fmt.Sprintf("drone-%s-clone", uid),
|
||||
Image: ImageClone,
|
||||
VolumesFrom: []string{containers[0].Name},
|
||||
Cmd: cmd,
|
||||
})
|
||||
|
||||
// attached service containers
|
||||
for i, service := range req.Config.Services {
|
||||
containers = append(containers, &Container{
|
||||
Name: fmt.Sprintf("drone-%s-service-%v", uid, i),
|
||||
Image: service,
|
||||
Env: req.Config.Env,
|
||||
NetworkMode: net,
|
||||
Detached: true,
|
||||
})
|
||||
|
||||
if i == 0 && len(net) == 0 {
|
||||
net = fmt.Sprintf("container:drone-%s-service-%v", uid, i)
|
||||
}
|
||||
}
|
||||
|
||||
// build container
|
||||
containers = append(containers, &Container{
|
||||
Name: fmt.Sprintf("drone-%s-build", uid),
|
||||
Image: req.Config.Image,
|
||||
Env: req.Config.Env,
|
||||
Cmd: []string{"/drone/bin/build.sh"},
|
||||
Entrypoint: []string{"/bin/bash"},
|
||||
WorkingDir: req.Clone.Dir,
|
||||
NetworkMode: net,
|
||||
Privileged: req.Config.Docker.Privileged,
|
||||
VolumesFrom: []string{containers[0].Name},
|
||||
})
|
||||
|
||||
//
|
||||
// create the notify, publish, deploy containers
|
||||
//
|
||||
|
||||
// loop through and create containers
|
||||
for _, container := range containers {
|
||||
container.SetClient(req.Client)
|
||||
if err := container.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// loop through and start containers
|
||||
for _, container := range containers {
|
||||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if container.Detached { // if a detached (daemon) just continue
|
||||
continue
|
||||
}
|
||||
r, err := container.Logs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
io.Copy(resp, r)
|
||||
r.Close()
|
||||
info, err := container.Inspect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.State.Running != false {
|
||||
fmt.Println("ERROR: container still running")
|
||||
}
|
||||
|
||||
resp.WriteExitCode(info.State.ExitCode)
|
||||
if info.State.ExitCode != 0 {
|
||||
// Build runs all build step handlers.
|
||||
func (b *Builder) Build(r *Result) (err error) {
|
||||
for _, h := range b.handlers {
|
||||
err = h.Build(r)
|
||||
if err != nil || r.exitCode != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cancel cancels any running build processes and
|
||||
// removes and build containers.
|
||||
func (b *Builder) Cancel() {
|
||||
for _, h := range b.handlers {
|
||||
h.Cancel() // TODO use channel to signal cancel
|
||||
}
|
||||
}
|
||||
|
||||
// type Builder struct {
|
||||
// containers []*Container
|
||||
// client dockerclient.Client
|
||||
// }
|
||||
//
|
||||
// // New creates a new builder
|
||||
// func New(client dockerclient.Client) *Builder {
|
||||
// return &Builder{client: client}
|
||||
// }
|
||||
//
|
||||
// func (r *Builder) Add(c *Container) {
|
||||
// r.containers = append(r.containers, c)
|
||||
// }
|
||||
//
|
||||
// func (r *Builder) Cancel() {
|
||||
// for _, c := range r.containers {
|
||||
// // TODO cancel using environment
|
||||
// if c != nil {
|
||||
// // TODO remove
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func (b *Builder) Build(res *Result) error {
|
||||
// for _, c := range b.containers {
|
||||
// err := b.run(c, res)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// if res.ExitCode() != 0 {
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// // helper function to run a single build container.
|
||||
// func (b *Builder) run(c *Container, res *Result) error {
|
||||
// // create container
|
||||
// // err: failed creating
|
||||
// // start continer
|
||||
// // err: failed starting container
|
||||
// if c.Detached == false {
|
||||
// return nil
|
||||
// }
|
||||
// // get log reader
|
||||
// // err: failed getting logs
|
||||
//
|
||||
// // copy logs to writer
|
||||
// // err: failed copying logs
|
||||
//
|
||||
// // write response
|
||||
// return nil
|
||||
// }
|
||||
|
||||
/*
|
||||
func NewBuilder(build *Build) *Builder {
|
||||
b := New(build.Client)
|
||||
for _, step := range build.Config.Compose {
|
||||
b.Add(fromCompose(build, &step))
|
||||
}
|
||||
b.Add(fromSetup(build, &build.Config.Build))
|
||||
b.Add(fromPlugin(build, &build.Config.Clone))
|
||||
b.Add(fromBuild(build, &build.Config.Build))
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func NewDeployer(build *Build) *Builder {
|
||||
b := New(build.Client)
|
||||
for _, step := range build.Config.Publish {
|
||||
b.Add(fromPlugin(build, &step))
|
||||
}
|
||||
for _, step := range build.Config.Deploy {
|
||||
b.Add(fromPlugin(build, &step))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func NewNotifier(build *Build) *Builder {
|
||||
b := New(build.Client)
|
||||
for _, step := range build.Config.Notify {
|
||||
b.Add(fromPlugin(build, &step))
|
||||
}
|
||||
return b
|
||||
}
|
||||
*/
|
||||
|
@ -1,33 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v1"
|
||||
)
|
||||
|
||||
func Inject(raw string, params map[string]string) string {
|
||||
if params == nil {
|
||||
return raw
|
||||
}
|
||||
keys := []string{}
|
||||
for k, _ := range params {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(keys)))
|
||||
injected := raw
|
||||
for _, k := range keys {
|
||||
v := params[k]
|
||||
injected = strings.Replace(injected, "$$"+k, v, -1)
|
||||
}
|
||||
return injected
|
||||
}
|
||||
|
||||
func InjectSafe(raw string, params map[string]string) string {
|
||||
before, _ := Parse(raw)
|
||||
after, _ := Parse(Inject(raw, params))
|
||||
after.Script = before.Script
|
||||
scrubbed, _ := yaml.Marshal(after)
|
||||
return string(scrubbed)
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
package config
|
||||
|
||||
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))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_InjectSafe(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Safely Inject params", func() {
|
||||
|
||||
m := map[string]string{}
|
||||
m["TOKEN"] = "FOO"
|
||||
m["SECRET"] = "BAR"
|
||||
c, _ := Parse(InjectSafe(yml, m))
|
||||
|
||||
g.It("Should replace vars in notify section", func() {
|
||||
g.Assert(c.Deploy["my_service"].(map[interface{}]interface{})["token"]).Equal("FOO")
|
||||
g.Assert(c.Deploy["my_service"].(map[interface{}]interface{})["secret"]).Equal("BAR")
|
||||
})
|
||||
|
||||
g.It("Should not replace vars in script section", func() {
|
||||
g.Assert(c.Script[0]).Equal("echo $$TOKEN")
|
||||
g.Assert(c.Script[1]).Equal("echo $$SECRET")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var yml = `
|
||||
image: foo
|
||||
script:
|
||||
- echo $$TOKEN
|
||||
- echo $$SECRET
|
||||
deploy:
|
||||
my_service:
|
||||
token: $$TOKEN
|
||||
secret: $$SECRET
|
||||
`
|
@ -1,87 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/drone/drone-cli/common"
|
||||
"gopkg.in/yaml.v1"
|
||||
)
|
||||
|
||||
const (
|
||||
LimitAxis = 10
|
||||
LimitPerms = 25
|
||||
)
|
||||
|
||||
func Parse(raw string) (*common.Config, error) {
|
||||
config := common.Config{}
|
||||
err := yaml.Unmarshal([]byte(raw), &config)
|
||||
return &config, err
|
||||
}
|
||||
|
||||
func ParseMatrix(raw string) ([]*common.Config, error) {
|
||||
var matrix []*common.Config
|
||||
|
||||
config, err := Parse(raw)
|
||||
if err != nil {
|
||||
return matrix, err
|
||||
}
|
||||
|
||||
// if not a matrix build return an array
|
||||
// with just the single axis.
|
||||
if len(config.Matrix) == 0 {
|
||||
matrix = append(matrix, config)
|
||||
return matrix, nil
|
||||
}
|
||||
|
||||
// calculate number of permutations and
|
||||
// extract the list of keys.
|
||||
var perm int
|
||||
var keys []string
|
||||
for k, v := range config.Matrix {
|
||||
perm *= len(v)
|
||||
if perm == 0 {
|
||||
perm = len(v)
|
||||
}
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
// for each axis calculate the values the uniqe
|
||||
// set of values that should be used.
|
||||
for p := 0; p < perm; p++ {
|
||||
axis := map[string]string{}
|
||||
decr := perm
|
||||
for i, key := range keys {
|
||||
vals := config.Matrix[key]
|
||||
decr = decr / len(vals)
|
||||
item := p / decr % len(vals)
|
||||
axis[key] = vals[item]
|
||||
|
||||
// enforce a maximum number of axis
|
||||
// in the build matrix.
|
||||
if i > LimitAxis {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
config, err = Parse(Inject(raw, axis))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matrix = append(matrix, config)
|
||||
|
||||
// each axis value should also be added
|
||||
// as an environment variable
|
||||
for key, val := range axis {
|
||||
env := fmt.Sprintf("%s=%s", key, val)
|
||||
config.Env = append(config.Env, env)
|
||||
}
|
||||
|
||||
// enforce a maximum number of permutations
|
||||
// in the build matrix.
|
||||
if p > LimitPerms {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return matrix, nil
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Parse(t *testing.T) {
|
||||
confs, err := ParseMatrix(matrix)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(confs) != 24 {
|
||||
t.Errorf("Expected 24 permutations in matrix, got %d", len(confs))
|
||||
}
|
||||
|
||||
unique := map[string]bool{}
|
||||
for _, config := range confs {
|
||||
unique[config.Image] = true
|
||||
}
|
||||
|
||||
if len(unique) != 24 {
|
||||
t.Errorf("Expected 24 unique permutations in matrix, got %d", len(unique))
|
||||
}
|
||||
}
|
||||
|
||||
var matrix = `
|
||||
image: $$python_version $$redis_version $$django_version $$go_version
|
||||
matrix:
|
||||
python_version:
|
||||
- 3.2
|
||||
- 3.3
|
||||
redis_version:
|
||||
- 2.6
|
||||
- 2.8
|
||||
django_version:
|
||||
- 1.7
|
||||
- 1.7.1
|
||||
- 1.7.2
|
||||
go_version:
|
||||
- go1
|
||||
- go1.2
|
||||
`
|
@ -1 +0,0 @@
|
||||
package config
|
@ -1,39 +1,117 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/drone/drone-cli/common"
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
// Container represents a Docker Container used
|
||||
// to execute a build step.
|
||||
type Container struct {
|
||||
Name string
|
||||
ID string
|
||||
Image string
|
||||
Pull bool
|
||||
Detached bool
|
||||
Privileged bool
|
||||
Env []string
|
||||
Cmd []string
|
||||
Environment []string
|
||||
Entrypoint []string
|
||||
WorkingDir string
|
||||
NetworkMode string
|
||||
Command []string
|
||||
Volumes []string
|
||||
VolumesFrom []string
|
||||
Links []string
|
||||
|
||||
client dockerclient.Client
|
||||
info *dockerclient.ContainerInfo
|
||||
WorkingDir string
|
||||
NetworkMode string
|
||||
}
|
||||
|
||||
func (c *Container) SetClient(client dockerclient.Client) {
|
||||
c.client = client
|
||||
// helper function to create a container from a step.
|
||||
func fromStep(step *common.Step) *Container {
|
||||
return &Container{
|
||||
Image: step.Name,
|
||||
Pull: step.Pull,
|
||||
Privileged: step.Privileged,
|
||||
Volumes: step.Volumes,
|
||||
WorkingDir: step.WorkingDir,
|
||||
NetworkMode: step.NetworkMode,
|
||||
Entrypoint: step.Entrypoint,
|
||||
Environment: step.Environment,
|
||||
Command: step.Command,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) Create() error {
|
||||
config := dockerclient.ContainerConfig{
|
||||
// helper function to create a container from a build
|
||||
// step. The build task will invoke a shell script
|
||||
// at an expected path.
|
||||
func fromBuild(build *Build, step *common.Step) *Container {
|
||||
c := fromStep(step)
|
||||
c.Entrypoint = []string{"/bin/bash"}
|
||||
c.Command = []string{"/drone/bin/build.sh"}
|
||||
return c
|
||||
}
|
||||
|
||||
// helper function to create a container from a setup
|
||||
// step. This is a special container. It is used to
|
||||
// bootstrap the environment, create build directories,
|
||||
// and generate the build script.
|
||||
//
|
||||
// see https://github.com/drone-plugins/drone-build
|
||||
func fromSetup(build *Build, step *common.Step) *Container {
|
||||
c := fromStep(step)
|
||||
c.Image = "plugins/drone-build"
|
||||
c.Entrypoint = []string{"/go/bin/drone-build"}
|
||||
c.Command = toCommand(build, step)
|
||||
return c
|
||||
}
|
||||
|
||||
// helper function to create a container from any plugin
|
||||
// step, including notification, deployment and publish steps.
|
||||
// It is used to create the plugin payload (JSON) and pass
|
||||
// to the container as arg[1]
|
||||
func fromPlugin(build *Build, step *common.Step) *Container {
|
||||
c := fromStep(step)
|
||||
c.Entrypoint = []string{}
|
||||
c.Command = toCommand(build, step)
|
||||
return c
|
||||
}
|
||||
|
||||
// helper function to create a container from a compose
|
||||
// step. This creates the container almost verbatim. It only
|
||||
// adds a --detached flag to the container. This instructure
|
||||
// the build not to block and wait for this container to
|
||||
// finish execution.
|
||||
func fromCompose(build *Build, step *common.Step) *Container {
|
||||
c := fromStep(step)
|
||||
c.Detached = true
|
||||
return c
|
||||
}
|
||||
|
||||
// helper function to encode the container arguments
|
||||
// in a json string. Primarily used for plugins, which
|
||||
// expect a json encoded string in stdin or arg[1].
|
||||
func toCommand(build *Build, step *common.Step) []string {
|
||||
payload := BuildPayload{
|
||||
build.Repo,
|
||||
build.Commit,
|
||||
build.Clone,
|
||||
step.Config,
|
||||
}
|
||||
return []string{payload.Encode()}
|
||||
}
|
||||
|
||||
// helper function that converts the container to
|
||||
// a hostConfig for use with the dockerclient
|
||||
func (c *Container) toHostConfig() *dockerclient.HostConfig {
|
||||
return &dockerclient.HostConfig{
|
||||
Privileged: c.Privileged,
|
||||
NetworkMode: c.NetworkMode,
|
||||
}
|
||||
}
|
||||
|
||||
// helper function that converts the container to
|
||||
// a containerConfig for use with the dockerclient
|
||||
func (c *Container) toContainerConfig() *dockerclient.ContainerConfig {
|
||||
config := &dockerclient.ContainerConfig{
|
||||
Image: c.Image,
|
||||
Env: c.Env,
|
||||
Cmd: c.Cmd,
|
||||
Env: c.Environment,
|
||||
Cmd: c.Command,
|
||||
Entrypoint: c.Entrypoint,
|
||||
WorkingDir: c.WorkingDir,
|
||||
}
|
||||
@ -45,56 +123,5 @@ func (c *Container) Create() error {
|
||||
}
|
||||
}
|
||||
|
||||
id, err := c.client.CreateContainer(&config, c.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.info, err = c.client.InspectContainer(id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Container) Start() error {
|
||||
config := dockerclient.HostConfig{
|
||||
Privileged: c.Privileged,
|
||||
NetworkMode: c.NetworkMode,
|
||||
VolumesFrom: c.VolumesFrom,
|
||||
}
|
||||
return c.client.StartContainer(c.info.Id, &config)
|
||||
}
|
||||
|
||||
func (c *Container) Stop() error {
|
||||
return c.client.StopContainer(c.info.Id, 10)
|
||||
}
|
||||
|
||||
func (c *Container) Kill() error {
|
||||
return c.client.KillContainer(c.info.Id, "SIGKILL")
|
||||
}
|
||||
|
||||
func (c *Container) Remove() error {
|
||||
return c.client.RemoveContainer(c.info.Id, true, true)
|
||||
}
|
||||
|
||||
func (c *Container) Wait() error {
|
||||
src, err := c.Logs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
_, err = io.Copy(ioutil.Discard, src)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) Logs() (io.ReadCloser, error) {
|
||||
opts := dockerclient.LogOptions{
|
||||
Follow: true,
|
||||
Stderr: true,
|
||||
Stdout: true,
|
||||
Tail: 10000,
|
||||
Timestamps: true,
|
||||
}
|
||||
return c.client.ContainerLogs(c.info.Id, &opts)
|
||||
}
|
||||
|
||||
func (c *Container) Inspect() (*dockerclient.ContainerInfo, error) {
|
||||
return c.client.InspectContainer(c.info.Id)
|
||||
return config
|
||||
}
|
||||
|
88
builder/handler.go
Normal file
88
builder/handler.go
Normal file
@ -0,0 +1,88 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
// Handler defines an interface that can be implemented by
|
||||
// objects that should be run during the build process.
|
||||
// to run as part of a build.
|
||||
type Handler interface {
|
||||
Build(*Result) error
|
||||
Cancel()
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
id string
|
||||
name string
|
||||
detach bool
|
||||
client dockerclient.Client
|
||||
host *dockerclient.HostConfig
|
||||
config *dockerclient.ContainerConfig
|
||||
}
|
||||
|
||||
func (h *handler) Build(res *Result) error {
|
||||
id, err := h.client.CreateContainer(h.config, h.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.id = id
|
||||
err = h.client.StartContainer(h.id, h.host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if h.detach {
|
||||
return nil
|
||||
}
|
||||
logs := &dockerclient.LogOptions{
|
||||
Follow: true,
|
||||
Stderr: true,
|
||||
Stdout: true,
|
||||
Timestamps: true,
|
||||
}
|
||||
rc, err := h.client.ContainerLogs(h.id, logs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
io.Copy(res, rc)
|
||||
info, err := h.client.InspectContainer(h.id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res.WriteExitCode(info.State.ExitCode)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handler) Cancel() {
|
||||
h.client.StopContainer(h.id, 5)
|
||||
h.client.KillContainer(h.id, "SIGKILL")
|
||||
h.client.RemoveContainer(h.id, true, false)
|
||||
}
|
||||
|
||||
// BatchHandler returns a handler that runs a build
|
||||
// task in batch mode. It will block until the task
|
||||
// comples, writing stdout to the result.
|
||||
//
|
||||
// If the task fails the exit status code is written
|
||||
// to the result as well.
|
||||
func BatchHandler(client dockerclient.Client, c *Container) Handler {
|
||||
return &handler{
|
||||
client: client,
|
||||
host: c.toHostConfig(),
|
||||
config: c.toContainerConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
// DetachedHandler returns a handler that runs a build
|
||||
// task in detached mode. It will start the container async,
|
||||
// and immediately exit.
|
||||
func DetachedHandler(client dockerclient.Client, c *Container) Handler {
|
||||
return &handler{
|
||||
detach: true,
|
||||
client: client,
|
||||
host: c.toHostConfig(),
|
||||
config: c.toContainerConfig(),
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/drone/drone-cli/common"
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
// A Request represents a build request.
|
||||
type Request struct {
|
||||
Repo *common.Repo `json:"repo"`
|
||||
Commit *common.Repo `json:"commit"`
|
||||
Config *common.Config `json:"config"`
|
||||
Clone *common.Clone `json:"clone"`
|
||||
|
||||
Client dockerclient.Client `json:"-"`
|
||||
}
|
||||
|
||||
func (r *Request) Encode() string {
|
||||
out, _ := json.Marshal(r)
|
||||
return string(out)
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type ResponseWriter interface {
|
||||
// Write writes the build stdout and stderr to the response.
|
||||
Write([]byte) (int, error)
|
||||
|
||||
// WriteExitCode writes the build exit status to the response.
|
||||
WriteExitCode(int)
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Writer io.Writer
|
||||
ExitCode int
|
||||
}
|
||||
|
||||
func (r *Response) Write(p []byte) (n int, err error) {
|
||||
return r.Writer.Write(p)
|
||||
}
|
||||
|
||||
func (r *Response) WriteExitCode(code int) {
|
||||
r.ExitCode = code
|
||||
}
|
24
builder/result.go
Normal file
24
builder/result.go
Normal file
@ -0,0 +1,24 @@
|
||||
package builder
|
||||
|
||||
import "io"
|
||||
|
||||
// Result represents the result from a build request.
|
||||
type Result struct {
|
||||
writer io.Writer
|
||||
exitCode int
|
||||
}
|
||||
|
||||
// Write writes the build stdout and stderr to the result.
|
||||
func (r *Result) Write(p []byte) (n int, err error) {
|
||||
return r.writer.Write(p)
|
||||
}
|
||||
|
||||
// WriteExitCode writes the build exit status to the result.
|
||||
func (r *Result) WriteExitCode(code int) {
|
||||
r.exitCode = code
|
||||
}
|
||||
|
||||
// ExitCode returns the build exit status.
|
||||
func (r *Result) ExitCode() int {
|
||||
return r.exitCode
|
||||
}
|
@ -1,15 +1,17 @@
|
||||
package common
|
||||
|
||||
// A step represents a step in the build process, including
|
||||
// Step represents a step in the build process, including
|
||||
// the execution environment and parameters.
|
||||
type Step struct {
|
||||
Name string
|
||||
Image string
|
||||
Environment []string
|
||||
Volumes []string
|
||||
Hostname string
|
||||
Pull bool
|
||||
Privileged bool
|
||||
Net string
|
||||
Environment []string
|
||||
Entrypoint []string
|
||||
Command []string
|
||||
Volumes []string
|
||||
WorkingDir string
|
||||
NetworkMode string
|
||||
|
||||
// Config represents the unique configuration details
|
||||
// for each plugin.
|
||||
|
49
runner/builder.go
Normal file
49
runner/builder.go
Normal file
@ -0,0 +1,49 @@
|
||||
package runner
|
||||
|
||||
/*
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
type Runner struct {
|
||||
client dockerclient.Client
|
||||
containers []*Container
|
||||
}
|
||||
|
||||
func (r *Runner) Run(req *Request, resp ResponseWriter) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Setup()
|
||||
|
||||
Clone()
|
||||
Build()
|
||||
Deploy()
|
||||
Notify()
|
||||
|
||||
Teardown()
|
||||
|
||||
func (r *Runner) Logs(w io.Writer) {
|
||||
for _, c := range r.containers {
|
||||
if c.Detached {
|
||||
continue
|
||||
}
|
||||
r, err := c.Logs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
io.Copy(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) Kill() {
|
||||
for _, c := range r.containers {
|
||||
c.Stop()
|
||||
c.Kill()
|
||||
c.Remove()
|
||||
}
|
||||
}
|
||||
*/
|
40
runner/notifier.go
Normal file
40
runner/notifier.go
Normal file
@ -0,0 +1,40 @@
|
||||
package runner
|
||||
|
||||
/*
|
||||
func Notify(req *Request, resp ResponseWriter) error {
|
||||
|
||||
var containers []*Container
|
||||
|
||||
defer func() {
|
||||
for i := len(containers) - 1; i >= 0; i-- {
|
||||
container := containers[i]
|
||||
container.Stop()
|
||||
container.Kill()
|
||||
container.Remove()
|
||||
}
|
||||
}()
|
||||
|
||||
// attached service containers
|
||||
for _, notification := range req.Config.Notify {
|
||||
containers = append(containers, &Container{
|
||||
Image: notification.Image,
|
||||
Env: notification.Environment,
|
||||
Cmd: EncodeParams(req, notification.Config),
|
||||
})
|
||||
}
|
||||
|
||||
// loop through and run containers
|
||||
for _, container := range containers {
|
||||
container.SetClient(req.Client)
|
||||
if err := container.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
container.Wait()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
*/
|
Loading…
Reference in New Issue
Block a user