mirror of
https://github.com/harness/drone.git
synced 2025-05-08 03:50:40 +08:00
add yaml parser, build execution code. not yet hooked up
This commit is contained in:
parent
cd18645784
commit
5816f1156e
153
builder/build.go
Normal file
153
builder/build.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/drone/drone/common"
|
||||||
|
"github.com/samalba/dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
// B is a type passed to build nodes. B implements an io.Writer
|
||||||
|
// and will accumulate build output during execution.
|
||||||
|
type B struct {
|
||||||
|
sync.Mutex
|
||||||
|
|
||||||
|
Repo *common.Repo
|
||||||
|
Build *common.Build
|
||||||
|
Task *common.Task
|
||||||
|
Clone *common.Clone
|
||||||
|
|
||||||
|
client dockerclient.Client
|
||||||
|
|
||||||
|
writer io.Writer
|
||||||
|
|
||||||
|
exitCode int
|
||||||
|
|
||||||
|
start time.Time // Time build started
|
||||||
|
duration time.Duration
|
||||||
|
timerOn bool
|
||||||
|
|
||||||
|
containers []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewB returns a new Build context.
|
||||||
|
func NewB(client dockerclient.Client, w io.Writer) *B {
|
||||||
|
return &B{
|
||||||
|
client: client,
|
||||||
|
writer: w,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run creates and runs a Docker container.
|
||||||
|
func (b *B) Run(conf *dockerclient.ContainerConfig) (string, error) {
|
||||||
|
b.Lock()
|
||||||
|
defer b.Unlock()
|
||||||
|
|
||||||
|
name, err := b.client.CreateContainer(conf, "")
|
||||||
|
if err != nil {
|
||||||
|
// on error try to pull the Docker image.
|
||||||
|
// note that this may not be the cause of
|
||||||
|
// the error, but we'll try just in case.
|
||||||
|
b.client.PullImage(conf.Image, nil)
|
||||||
|
|
||||||
|
// then try to re-create
|
||||||
|
name, err = b.client.CreateContainer(conf, "")
|
||||||
|
if err != nil {
|
||||||
|
return name, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.containers = append(b.containers, name)
|
||||||
|
err = b.client.StartContainer(name, &conf.HostConfig)
|
||||||
|
if err != nil {
|
||||||
|
return name, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspect inspects the running Docker container and returns
|
||||||
|
// the contianer runtime information and state.
|
||||||
|
func (b *B) Inspect(name string) (*dockerclient.ContainerInfo, error) {
|
||||||
|
return b.client.InspectContainer(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops and removes the named Docker container.
|
||||||
|
func (b *B) Remove(name string) {
|
||||||
|
b.client.StopContainer(name, 5)
|
||||||
|
b.client.KillContainer(name, "9")
|
||||||
|
b.client.RemoveContainer(name, true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAll stops and removes all Docker containers that were
|
||||||
|
// created and started during the build process.
|
||||||
|
func (b *B) RemoveAll() {
|
||||||
|
b.Lock()
|
||||||
|
defer b.Unlock()
|
||||||
|
|
||||||
|
for i := len(b.containers) - 1; i >= 0; i-- {
|
||||||
|
b.Remove(b.containers[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logs returns an io.ReadCloser for reading the build stream of
|
||||||
|
// the named Docker container.
|
||||||
|
func (b *B) Logs(name string) (io.ReadCloser, error) {
|
||||||
|
opts := dockerclient.LogOptions{
|
||||||
|
Follow: true,
|
||||||
|
Stderr: true,
|
||||||
|
Stdout: true,
|
||||||
|
Timestamps: false,
|
||||||
|
}
|
||||||
|
return b.client.ContainerLogs(name, &opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartTimer starts timing a build. This function is called automatically
|
||||||
|
// before a build starts, but it can also used to resume timing after
|
||||||
|
// a call to StopTimer.
|
||||||
|
func (b *B) StartTimer() {
|
||||||
|
b.Lock()
|
||||||
|
defer b.Unlock()
|
||||||
|
|
||||||
|
if !b.timerOn {
|
||||||
|
b.start = time.Now()
|
||||||
|
b.timerOn = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopTimer stops timing a build. This can be used to pause the timer
|
||||||
|
// while performing complex initialization that you don't want to measure.
|
||||||
|
func (b *B) StopTimer() {
|
||||||
|
b.Lock()
|
||||||
|
defer b.Unlock()
|
||||||
|
|
||||||
|
if b.timerOn {
|
||||||
|
b.duration += time.Now().Sub(b.start)
|
||||||
|
b.timerOn = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes the build stdout and stderr to the result.
|
||||||
|
func (b *B) Write(p []byte) (n int, err error) {
|
||||||
|
return b.writer.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit writes the function as having failed but continues execution.
|
||||||
|
func (b *B) Exit(code int) {
|
||||||
|
b.Lock()
|
||||||
|
defer b.Unlock()
|
||||||
|
|
||||||
|
if code != 0 { // never override non-zero exit
|
||||||
|
b.exitCode = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitCode reports the build exit code. A non-zero value indicates
|
||||||
|
// the build exited with errors.
|
||||||
|
func (b *B) ExitCode() int {
|
||||||
|
b.Lock()
|
||||||
|
defer b.Unlock()
|
||||||
|
|
||||||
|
return b.exitCode
|
||||||
|
}
|
81
builder/builder.go
Normal file
81
builder/builder.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import "github.com/drone/drone/common"
|
||||||
|
|
||||||
|
// Builder represents a build execution tree.
|
||||||
|
type Builder struct {
|
||||||
|
builds Node
|
||||||
|
deploy Node
|
||||||
|
notify Node
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the build, deploy and notify nodes
|
||||||
|
// in the build tree.
|
||||||
|
func (b *Builder) Run(build *B) error {
|
||||||
|
var err error
|
||||||
|
err = b.RunBuild(build)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = b.RunDeploy(build)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.RunNotify(build)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunBuild runs only the build node.
|
||||||
|
func (b *Builder) RunBuild(build *B) error {
|
||||||
|
return b.builds.Run(build)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunDeploy runs only the deploy node.
|
||||||
|
func (b *Builder) RunDeploy(build *B) error {
|
||||||
|
return b.notify.Run(build)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunNotify runs on the notify node.
|
||||||
|
func (b *Builder) RunNotify(build *B) error {
|
||||||
|
return b.notify.Run(build)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) HasDeploy() bool {
|
||||||
|
return len(b.deploy.(serialNode)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) HasNotify() bool {
|
||||||
|
return len(b.notify.(serialNode)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads a build configuration file.
|
||||||
|
func Load(conf *common.Config) *Builder {
|
||||||
|
var (
|
||||||
|
builds []Node
|
||||||
|
deploys []Node
|
||||||
|
notifys []Node
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, step := range conf.Compose {
|
||||||
|
builds = append(builds, &serviceNode{step}) // compose
|
||||||
|
}
|
||||||
|
builds = append(builds, &batchNode{conf.Setup}) // setup
|
||||||
|
if conf.Clone != nil {
|
||||||
|
builds = append(builds, &batchNode{conf.Clone}) // clone
|
||||||
|
}
|
||||||
|
builds = append(builds, &batchNode{conf.Build}) // build
|
||||||
|
|
||||||
|
for _, step := range conf.Publish {
|
||||||
|
deploys = append(deploys, &batchNode{step}) // publish
|
||||||
|
}
|
||||||
|
for _, step := range conf.Deploy {
|
||||||
|
deploys = append(deploys, &batchNode{step}) // deploy
|
||||||
|
}
|
||||||
|
for _, step := range conf.Notify {
|
||||||
|
notifys = append(notifys, &batchNode{step}) // notify
|
||||||
|
}
|
||||||
|
return &Builder{
|
||||||
|
serialNode(builds),
|
||||||
|
serialNode(deploys),
|
||||||
|
serialNode(notifys),
|
||||||
|
}
|
||||||
|
}
|
124
builder/copy.go
Normal file
124
builder/copy.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StdWriterPrefixLen = 8
|
||||||
|
StdWriterFdIndex = 0
|
||||||
|
StdWriterSizeIndex = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
type StdType [StdWriterPrefixLen]byte
|
||||||
|
|
||||||
|
var (
|
||||||
|
Stdin StdType = StdType{0: 0}
|
||||||
|
Stdout StdType = StdType{0: 1}
|
||||||
|
Stderr StdType = StdType{0: 2}
|
||||||
|
)
|
||||||
|
|
||||||
|
type StdWriter struct {
|
||||||
|
io.Writer
|
||||||
|
prefix StdType
|
||||||
|
sizeBuf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrInvalidStdHeader = errors.New("Unrecognized input header")
|
||||||
|
|
||||||
|
// StdCopy is a modified version of io.Copy.
|
||||||
|
//
|
||||||
|
// StdCopy will demultiplex `src`, assuming that it contains two streams,
|
||||||
|
// previously multiplexed together using a StdWriter instance.
|
||||||
|
// As it reads from `src`, StdCopy will write to `dstout` and `dsterr`.
|
||||||
|
//
|
||||||
|
// StdCopy will read until it hits EOF on `src`. It will then return a nil error.
|
||||||
|
// In other words: if `err` is non nil, it indicates a real underlying error.
|
||||||
|
//
|
||||||
|
// `written` will hold the total number of bytes written to `dstout` and `dsterr`.
|
||||||
|
func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) {
|
||||||
|
var (
|
||||||
|
buf = make([]byte, 32*1024+StdWriterPrefixLen+1)
|
||||||
|
bufLen = len(buf)
|
||||||
|
nr, nw int
|
||||||
|
er, ew error
|
||||||
|
out io.Writer
|
||||||
|
frameSize int
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Make sure we have at least a full header
|
||||||
|
for nr < StdWriterPrefixLen {
|
||||||
|
var nr2 int
|
||||||
|
nr2, er = src.Read(buf[nr:])
|
||||||
|
nr += nr2
|
||||||
|
if er == io.EOF {
|
||||||
|
if nr < StdWriterPrefixLen {
|
||||||
|
return written, nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if er != nil {
|
||||||
|
return 0, er
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the first byte to know where to write
|
||||||
|
switch buf[StdWriterFdIndex] {
|
||||||
|
case 0:
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
// Write on stdout
|
||||||
|
out = dstout
|
||||||
|
case 2:
|
||||||
|
// Write on stderr
|
||||||
|
out = dsterr
|
||||||
|
default:
|
||||||
|
return 0, ErrInvalidStdHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the size of the frame
|
||||||
|
frameSize = int(binary.BigEndian.Uint32(buf[StdWriterSizeIndex : StdWriterSizeIndex+4]))
|
||||||
|
|
||||||
|
// Check if the buffer is big enough to read the frame.
|
||||||
|
// Extend it if necessary.
|
||||||
|
if frameSize+StdWriterPrefixLen > bufLen {
|
||||||
|
buf = append(buf, make([]byte, frameSize+StdWriterPrefixLen-bufLen+1)...)
|
||||||
|
bufLen = len(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// While the amount of bytes read is less than the size of the frame + header, we keep reading
|
||||||
|
for nr < frameSize+StdWriterPrefixLen {
|
||||||
|
var nr2 int
|
||||||
|
nr2, er = src.Read(buf[nr:])
|
||||||
|
nr += nr2
|
||||||
|
if er == io.EOF {
|
||||||
|
if nr < frameSize+StdWriterPrefixLen {
|
||||||
|
return written, nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if er != nil {
|
||||||
|
return 0, er
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the retrieved frame (without header)
|
||||||
|
nw, ew = out.Write(buf[StdWriterPrefixLen : frameSize+StdWriterPrefixLen])
|
||||||
|
if ew != nil {
|
||||||
|
return 0, ew
|
||||||
|
}
|
||||||
|
// If the frame has not been fully written: error
|
||||||
|
if nw != frameSize {
|
||||||
|
return 0, io.ErrShortWrite
|
||||||
|
}
|
||||||
|
written += int64(nw)
|
||||||
|
|
||||||
|
// Move the rest of the buffer to the beginning
|
||||||
|
copy(buf, buf[frameSize+StdWriterPrefixLen:])
|
||||||
|
// Move the index
|
||||||
|
nr -= frameSize + StdWriterPrefixLen
|
||||||
|
}
|
||||||
|
}
|
110
builder/docker/ambassador.go
Normal file
110
builder/docker/ambassador.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"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 {
|
||||||
|
dockerclient.Client
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAmbassador creates an ambassador container and wraps the Docker
|
||||||
|
// client to inject the ambassador volume and network into containers.
|
||||||
|
func NewAmbassador(client dockerclient.Client) (_ *Ambassador, err error) {
|
||||||
|
amb := &Ambassador{client, ""}
|
||||||
|
|
||||||
|
conf := &dockerclient.ContainerConfig{}
|
||||||
|
host := &dockerclient.HostConfig{}
|
||||||
|
conf.Entrypoint = []string{"/bin/sleep"}
|
||||||
|
conf.Cmd = []string{"86400"}
|
||||||
|
conf.Image = "busybox"
|
||||||
|
conf.Volumes = map[string]struct{}{}
|
||||||
|
conf.Volumes["/drone"] = struct{}{}
|
||||||
|
|
||||||
|
// creates the ambassador container
|
||||||
|
amb.name, err = client.CreateContainer(conf, "")
|
||||||
|
if err != nil {
|
||||||
|
log.WithField("ambassador", conf.Image).Errorln(err)
|
||||||
|
|
||||||
|
// on failure attempts to pull the image
|
||||||
|
client.PullImage(conf.Image, nil)
|
||||||
|
|
||||||
|
// then attempts to re-create the container
|
||||||
|
amb.name, err = client.CreateContainer(conf, "")
|
||||||
|
if err != nil {
|
||||||
|
log.WithField("ambassador", conf.Image).Errorln(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = client.StartContainer(amb.name, host)
|
||||||
|
if err != nil {
|
||||||
|
log.WithField("ambassador", conf.Image).Errorln(err)
|
||||||
|
}
|
||||||
|
return amb, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy stops and deletes the ambassador container.
|
||||||
|
func (c *Ambassador) Destroy() error {
|
||||||
|
c.Client.StopContainer(c.name, 5)
|
||||||
|
c.Client.KillContainer(c.name, "9")
|
||||||
|
return c.Client.RemoveContainer(c.name, true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateContainer creates a container.
|
||||||
|
func (c *Ambassador) CreateContainer(conf *dockerclient.ContainerConfig, name string) (string, error) {
|
||||||
|
log.WithField("image", conf.Image).Infoln("create container")
|
||||||
|
|
||||||
|
// add the affinity flag for swarm
|
||||||
|
conf.Env = append(conf.Env, "affinity:container=="+c.name)
|
||||||
|
|
||||||
|
id, err := c.Client.CreateContainer(conf, name)
|
||||||
|
if err != nil {
|
||||||
|
log.WithField("image", conf.Image).Errorln(err)
|
||||||
|
}
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, conf *dockerclient.HostConfig) error {
|
||||||
|
log.WithField("container", id).Debugln("start container")
|
||||||
|
|
||||||
|
conf.VolumesFrom = append(conf.VolumesFrom, c.name)
|
||||||
|
if len(conf.NetworkMode) == 0 {
|
||||||
|
conf.NetworkMode = "container:" + c.name
|
||||||
|
}
|
||||||
|
err := c.Client.StartContainer(id, conf)
|
||||||
|
if err != nil {
|
||||||
|
log.WithField("container", id).Errorln(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopContainer stops a container.
|
||||||
|
func (c *Ambassador) StopContainer(id string, timeout int) error {
|
||||||
|
log.WithField("container", id).Debugln("stop container")
|
||||||
|
err := c.Client.StopContainer(id, timeout)
|
||||||
|
if err != nil {
|
||||||
|
log.WithField("container", id).Errorln(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PullImage pulls an image.
|
||||||
|
func (c *Ambassador) PullImage(name string, auth *dockerclient.AuthConfig) error {
|
||||||
|
log.WithField("image", name).Debugln("pull image")
|
||||||
|
err := c.Client.PullImage(name, auth)
|
||||||
|
if err != nil {
|
||||||
|
log.WithField("image", name).Errorln(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
103
builder/node.go
Normal file
103
builder/node.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/drone/drone/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Node is an element in the build execution tree.
|
||||||
|
type Node interface {
|
||||||
|
Run(*B) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// parallelNode runs a set of build nodes in parallel.
|
||||||
|
type parallelNode []Node
|
||||||
|
|
||||||
|
func (n parallelNode) Run(b *B) error {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, node := range n {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func(node Node) {
|
||||||
|
defer wg.Done()
|
||||||
|
node.Run(b)
|
||||||
|
}(node)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialNode runs a set of build nodes in sequential order.
|
||||||
|
type serialNode []Node
|
||||||
|
|
||||||
|
func (n serialNode) Run(b *B) error {
|
||||||
|
for _, node := range n {
|
||||||
|
err := node.Run(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if b.ExitCode() != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// batchNode runs a container and blocks until complete.
|
||||||
|
type batchNode struct {
|
||||||
|
step *common.Step
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *batchNode) Run(b *B) error {
|
||||||
|
|
||||||
|
// switch {
|
||||||
|
// case n.step.Condition == nil:
|
||||||
|
// case n.step.Condition.MatchBranch(b.Commit.Branch) == false:
|
||||||
|
// return nil
|
||||||
|
// case n.step.Condition.MatchOwner(b.Repo.Owner) == false:
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// creates the container conf
|
||||||
|
conf := toContainerConfig(n.step)
|
||||||
|
if n.step.Config != nil {
|
||||||
|
conf.Cmd = toCommand(b, n.step)
|
||||||
|
}
|
||||||
|
|
||||||
|
// inject environment vars
|
||||||
|
injectEnv(b, conf)
|
||||||
|
|
||||||
|
name, err := b.Run(conf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// streams the logs to the build results
|
||||||
|
rc, err := b.Logs(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
StdCopy(b, b, rc)
|
||||||
|
//io.Copy(b, rc)
|
||||||
|
|
||||||
|
// inspects the results and writes the
|
||||||
|
// build result exit code
|
||||||
|
info, err := b.Inspect(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.Exit(info.State.ExitCode)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// serviceNode runs a container, blocking, writes output, uses config section
|
||||||
|
type serviceNode struct {
|
||||||
|
step *common.Step
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *serviceNode) Run(b *B) error {
|
||||||
|
conf := toContainerConfig(n.step)
|
||||||
|
_, err := b.Run(conf)
|
||||||
|
return err
|
||||||
|
}
|
89
builder/pool/pool.go
Normal file
89
builder/pool/pool.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/samalba/dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO (bradrydzewski) ability to cancel work.
|
||||||
|
// TODO (bradrydzewski) ability to remove a worker.
|
||||||
|
|
||||||
|
type Pool struct {
|
||||||
|
sync.Mutex
|
||||||
|
clients map[dockerclient.Client]bool
|
||||||
|
clientc chan dockerclient.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Pool {
|
||||||
|
return &Pool{
|
||||||
|
clients: make(map[dockerclient.Client]bool),
|
||||||
|
clientc: make(chan dockerclient.Client, 999),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate allocates a client to the pool to
|
||||||
|
// be available to accept work.
|
||||||
|
func (p *Pool) Allocate(c dockerclient.Client) bool {
|
||||||
|
if p.IsAllocated(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Lock()
|
||||||
|
p.clients[c] = true
|
||||||
|
p.Unlock()
|
||||||
|
|
||||||
|
p.clientc <- c
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAllocated is a helper function that returns
|
||||||
|
// true if the client is currently allocated to
|
||||||
|
// the Pool.
|
||||||
|
func (p *Pool) IsAllocated(c dockerclient.Client) bool {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
_, ok := p.clients[c]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deallocate removes the worker from the pool of
|
||||||
|
// available clients. If the client is currently
|
||||||
|
// reserved and performing work it will finish,
|
||||||
|
// but no longer be given new work.
|
||||||
|
func (p *Pool) Deallocate(c dockerclient.Client) {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
delete(p.clients, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns a list of all Workers currently
|
||||||
|
// allocated to the Pool.
|
||||||
|
func (p *Pool) List() []dockerclient.Client {
|
||||||
|
p.Lock()
|
||||||
|
defer p.Unlock()
|
||||||
|
|
||||||
|
var clients []dockerclient.Client
|
||||||
|
for c := range p.clients {
|
||||||
|
clients = append(clients, c)
|
||||||
|
}
|
||||||
|
return clients
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve reserves the next available worker to
|
||||||
|
// start doing work. Once work is complete, the
|
||||||
|
// worker should be released back to the pool.
|
||||||
|
func (p *Pool) Reserve() <-chan dockerclient.Client {
|
||||||
|
return p.clientc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases the worker back to the pool
|
||||||
|
// of available workers.
|
||||||
|
func (p *Pool) Release(c dockerclient.Client) bool {
|
||||||
|
if !p.IsAllocated(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
p.clientc <- c
|
||||||
|
return true
|
||||||
|
}
|
98
builder/util.go
Normal file
98
builder/util.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/drone/drone/common"
|
||||||
|
"github.com/samalba/dockerclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
// helper function that converts the build step to
|
||||||
|
// a containerConfig for use with the dockerclient
|
||||||
|
func toContainerConfig(step *common.Step) *dockerclient.ContainerConfig {
|
||||||
|
config := &dockerclient.ContainerConfig{
|
||||||
|
Image: step.Image,
|
||||||
|
Env: step.Environment,
|
||||||
|
Cmd: step.Command,
|
||||||
|
Entrypoint: step.Entrypoint,
|
||||||
|
WorkingDir: step.WorkingDir,
|
||||||
|
HostConfig: dockerclient.HostConfig{
|
||||||
|
Privileged: step.Privileged,
|
||||||
|
NetworkMode: step.NetworkMode,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Volumes = map[string]struct{}{}
|
||||||
|
for _, path := range step.Volumes {
|
||||||
|
if strings.Index(path, ":") == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.Split(path, ":")
|
||||||
|
config.Volumes[parts[1]] = struct{}{}
|
||||||
|
config.HostConfig.Binds = append(config.HostConfig.Binds, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to inject drone-specific environment
|
||||||
|
// variables into the container.
|
||||||
|
func injectEnv(b *B, conf *dockerclient.ContainerConfig) {
|
||||||
|
var branch string
|
||||||
|
var commit string
|
||||||
|
if b.Build.Commit != nil {
|
||||||
|
branch = b.Build.Commit.Ref
|
||||||
|
commit = b.Build.Commit.Sha
|
||||||
|
} else {
|
||||||
|
branch = b.Build.PullRequest.Target.Ref
|
||||||
|
commit = b.Build.PullRequest.Target.Sha
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.Env = append(conf.Env, "DRONE=true")
|
||||||
|
conf.Env = append(conf.Env, fmt.Sprintf("DRONE_BRANCH=%s", branch))
|
||||||
|
conf.Env = append(conf.Env, fmt.Sprintf("DRONE_COMMIT=%s", commit))
|
||||||
|
|
||||||
|
// for jenkins campatibility
|
||||||
|
conf.Env = append(conf.Env, "CI=true")
|
||||||
|
conf.Env = append(conf.Env, fmt.Sprintf("WORKSPACE=%s", b.Clone.Dir))
|
||||||
|
conf.Env = append(conf.Env, fmt.Sprintf("JOB_NAME=%s/%s", b.Repo.Owner, b.Repo.Name))
|
||||||
|
conf.Env = append(conf.Env, fmt.Sprintf("BUILD_ID=%d", b.Build.Number))
|
||||||
|
conf.Env = append(conf.Env, fmt.Sprintf("BUILD_DIR=%s", b.Clone.Dir))
|
||||||
|
conf.Env = append(conf.Env, fmt.Sprintf("GIT_BRANCH=%s", branch))
|
||||||
|
conf.Env = append(conf.Env, fmt.Sprintf("GIT_COMMIT=%s", commit))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to encode the build step to
|
||||||
|
// a json string. Primarily used for plugins, which
|
||||||
|
// expect a json encoded string in stdin or arg[1].
|
||||||
|
func toCommand(b *B, step *common.Step) []string {
|
||||||
|
p := payload{
|
||||||
|
b.Repo,
|
||||||
|
b.Build,
|
||||||
|
b.Task,
|
||||||
|
b.Clone,
|
||||||
|
step.Config,
|
||||||
|
}
|
||||||
|
return []string{p.Encode()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// payload represents the payload of a plugin
|
||||||
|
// that is serialized and sent to the plugin in JSON
|
||||||
|
// format via stdin or arg[1].
|
||||||
|
type payload struct {
|
||||||
|
Repo *common.Repo `json:"repo"`
|
||||||
|
Build *common.Build `json:"build"`
|
||||||
|
Task *common.Task `json:"task"`
|
||||||
|
Clone *common.Clone `json:"clone"`
|
||||||
|
|
||||||
|
Config map[string]interface{} `json:"vargs"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode encodes the payload in JSON format.
|
||||||
|
func (p *payload) Encode() string {
|
||||||
|
out, _ := json.Marshal(p)
|
||||||
|
return string(out)
|
||||||
|
}
|
@ -74,3 +74,20 @@ type Remote struct {
|
|||||||
FullName string `json:"full_name,omitempty"`
|
FullName string `json:"full_name,omitempty"`
|
||||||
Clone string `json:"clone_url,omitempty"`
|
Clone string `json:"clone_url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Clone struct {
|
||||||
|
Origin string `json:"origin"`
|
||||||
|
Remote string `json:"remote"`
|
||||||
|
Branch string `json:"branch"`
|
||||||
|
Sha string `json:"sha"`
|
||||||
|
Ref string `json:"ref"`
|
||||||
|
Dir string `json:"dir"`
|
||||||
|
Netrc *Netrc `json:"netrc"`
|
||||||
|
Keypair *Keypair `json:"keypair"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Netrc struct {
|
||||||
|
Machine string `json:"machine"`
|
||||||
|
Login string `json:"login"`
|
||||||
|
Password string `json:"user"`
|
||||||
|
}
|
||||||
|
104
common/config.go
Normal file
104
common/config.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config represents a repository build configuration.
|
||||||
|
type Config struct {
|
||||||
|
Setup *Step
|
||||||
|
Clone *Step
|
||||||
|
Build *Step
|
||||||
|
|
||||||
|
Compose map[string]*Step
|
||||||
|
Publish map[string]*Step
|
||||||
|
Deploy map[string]*Step
|
||||||
|
Notify map[string]*Step
|
||||||
|
|
||||||
|
Matrix Matrix
|
||||||
|
Axis Axis
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matrix represents the build matrix.
|
||||||
|
type Matrix map[string][]string
|
||||||
|
|
||||||
|
// Axis represents a single permutation of entries
|
||||||
|
// from the build matrix.
|
||||||
|
type Axis map[string]string
|
||||||
|
|
||||||
|
// String returns a string representation of an Axis as
|
||||||
|
// a comma-separated list of environment variables.
|
||||||
|
func (a Axis) String() string {
|
||||||
|
var envs []string
|
||||||
|
for k, v := range a {
|
||||||
|
envs = append(envs, k+"="+v)
|
||||||
|
}
|
||||||
|
return strings.Join(envs, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step represents a step in the build process, including
|
||||||
|
// the execution environment and parameters.
|
||||||
|
type Step struct {
|
||||||
|
Image string
|
||||||
|
Pull bool
|
||||||
|
Privileged bool
|
||||||
|
Environment []string
|
||||||
|
Entrypoint []string
|
||||||
|
Command []string
|
||||||
|
Volumes []string
|
||||||
|
WorkingDir string `yaml:"working_dir"`
|
||||||
|
NetworkMode string `yaml:"net"`
|
||||||
|
|
||||||
|
// Condition represents a set of conditions that must
|
||||||
|
// be met in order to execute this step.
|
||||||
|
Condition *Condition `yaml:"when"`
|
||||||
|
|
||||||
|
// Config represents the unique configuration details
|
||||||
|
// for each plugin.
|
||||||
|
Config map[string]interface{} `yaml:"config,inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Condition represents a set of conditions that must
|
||||||
|
// be met in order to proceed with a build or build step.
|
||||||
|
type Condition struct {
|
||||||
|
Owner string // Indicates the step should run only for this repo (useful for forks)
|
||||||
|
Branch string // Indicates the step should run only for this branch
|
||||||
|
|
||||||
|
// Indicates the step should only run when the following
|
||||||
|
// matrix values are present for the sub-build.
|
||||||
|
Matrix map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchBranch is a helper function that returns true
|
||||||
|
// if all_branches is true. Else it returns false if a
|
||||||
|
// branch condition is specified, and the branch does
|
||||||
|
// not match.
|
||||||
|
func (c *Condition) MatchBranch(branch string) bool {
|
||||||
|
if len(c.Branch) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
match, _ := filepath.Match(c.Branch, branch)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// MatchOwner is a helper function that returns false
|
||||||
|
// if an owner condition is specified and the repository
|
||||||
|
// owner does not match.
|
||||||
|
//
|
||||||
|
// This is useful when you want to prevent forks from
|
||||||
|
// executing deployment, publish or notification steps.
|
||||||
|
func (c *Condition) MatchOwner(owner string) bool {
|
||||||
|
if len(c.Owner) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
parts := strings.Split(owner, "/")
|
||||||
|
switch len(parts) {
|
||||||
|
case 2:
|
||||||
|
return c.Owner == parts[0]
|
||||||
|
case 3:
|
||||||
|
return c.Owner == parts[1]
|
||||||
|
default:
|
||||||
|
return c.Owner == owner
|
||||||
|
}
|
||||||
|
}
|
54
parser/inject/inject.go
Normal file
54
parser/inject/inject.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package inject
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/drone/drone/common"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Inject injects a map of parameters into a raw string and returns
|
||||||
|
// the resulting string.
|
||||||
|
//
|
||||||
|
// Parameters are represented in the string using $$ notation, similar
|
||||||
|
// to how environment variables are defined in Makefiles.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// InjectSafe attempts to safely inject parameters without leaking
|
||||||
|
// parameters in the Build or Compose section of the yaml file.
|
||||||
|
//
|
||||||
|
// The intended use case for this function are public pull requests.
|
||||||
|
// We want to avoid a malicious pull request that allows someone
|
||||||
|
// to inject and print private variables.
|
||||||
|
func InjectSafe(raw string, params map[string]string) string {
|
||||||
|
before, _ := parse(raw)
|
||||||
|
after, _ := parse(Inject(raw, params))
|
||||||
|
before.Notify = after.Notify
|
||||||
|
before.Publish = after.Publish
|
||||||
|
before.Deploy = after.Deploy
|
||||||
|
result, _ := yaml.Marshal(before)
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper funtion to parse a yaml configuration file.
|
||||||
|
func parse(raw string) (*common.Config, error) {
|
||||||
|
cfg := common.Config{}
|
||||||
|
err := yaml.Unmarshal([]byte(raw), &cfg)
|
||||||
|
return &cfg, err
|
||||||
|
}
|
67
parser/inject/inject_test.go
Normal file
67
parser/inject/inject_test.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package inject
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
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["digital_ocean"].Config["token"]).Equal("FOO")
|
||||||
|
g.Assert(c.Deploy["digital_ocean"].Config["secret"]).Equal("BAR")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should not replace vars in script section", func() {
|
||||||
|
g.Assert(c.Build.Config["commands"].([]interface{})[0]).Equal("echo $$TOKEN")
|
||||||
|
g.Assert(c.Build.Config["commands"].([]interface{})[1]).Equal("echo $$SECRET")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var yml = `
|
||||||
|
build:
|
||||||
|
image: foo
|
||||||
|
commands:
|
||||||
|
- echo $$TOKEN
|
||||||
|
- echo $$SECRET
|
||||||
|
deploy:
|
||||||
|
digital_ocean:
|
||||||
|
token: $$TOKEN
|
||||||
|
secret: $$SECRET
|
||||||
|
`
|
105
parser/lint.go
Normal file
105
parser/lint.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/drone/drone/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// lintRule defines a function that runs lint
|
||||||
|
// checks against a Yaml Config file. If the rule
|
||||||
|
// fails it should return an error message.
|
||||||
|
type lintRule func(*common.Config) error
|
||||||
|
|
||||||
|
var lintRules = [...]lintRule{
|
||||||
|
expectBuild,
|
||||||
|
expectImage,
|
||||||
|
expectCommand,
|
||||||
|
expectTrustedSetup,
|
||||||
|
expectTrustedClone,
|
||||||
|
expectTrustedPublish,
|
||||||
|
expectTrustedDeploy,
|
||||||
|
expectTrustedNotify,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lint runs all lint rules against the Yaml Config.
|
||||||
|
func Lint(c *common.Config) error {
|
||||||
|
for _, rule := range lintRules {
|
||||||
|
err := rule(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lint rule that fails when no build is defined
|
||||||
|
func expectBuild(c *common.Config) error {
|
||||||
|
if c.Build == nil {
|
||||||
|
return fmt.Errorf("Yaml must define a build section")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lint rule that fails when no build image is defined
|
||||||
|
func expectImage(c *common.Config) error {
|
||||||
|
if len(c.Build.Image) == 0 {
|
||||||
|
return fmt.Errorf("Yaml must define a build image")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lint rule that fails when no build commands are defined
|
||||||
|
func expectCommand(c *common.Config) error {
|
||||||
|
if c.Build.Config == nil || c.Build.Config["commands"] == nil {
|
||||||
|
return fmt.Errorf("Yaml must define build commands")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lint rule that fails when a non-trusted clone plugin is used.
|
||||||
|
func expectTrustedClone(c *common.Config) error {
|
||||||
|
if c.Clone != nil && strings.Contains(c.Clone.Image, "/") {
|
||||||
|
return fmt.Errorf("Yaml must use trusted clone plugins")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lint rule that fails when a non-trusted setup plugin is used.
|
||||||
|
func expectTrustedSetup(c *common.Config) error {
|
||||||
|
if c.Setup != nil && strings.Contains(c.Setup.Image, "/") {
|
||||||
|
return fmt.Errorf("Yaml must use trusted setup plugins")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lint rule that fails when a non-trusted publish plugin is used.
|
||||||
|
func expectTrustedPublish(c *common.Config) error {
|
||||||
|
for _, step := range c.Publish {
|
||||||
|
if strings.Contains(step.Image, "/") {
|
||||||
|
return fmt.Errorf("Yaml must use trusted publish plugins")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lint rule that fails when a non-trusted deploy plugin is used.
|
||||||
|
func expectTrustedDeploy(c *common.Config) error {
|
||||||
|
for _, step := range c.Deploy {
|
||||||
|
if strings.Contains(step.Image, "/") {
|
||||||
|
return fmt.Errorf("Yaml must use trusted deploy plugins")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lint rule that fails when a non-trusted notify plugin is used.
|
||||||
|
func expectTrustedNotify(c *common.Config) error {
|
||||||
|
for _, step := range c.Notify {
|
||||||
|
if strings.Contains(step.Image, "/") {
|
||||||
|
return fmt.Errorf("Yaml must use trusted notify plugins")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
92
parser/lint_test.go
Normal file
92
parser/lint_test.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/common"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Linter(t *testing.T) {
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("Linter", func() {
|
||||||
|
|
||||||
|
g.It("Should fail when nil build", func() {
|
||||||
|
c := &common.Config{}
|
||||||
|
g.Assert(expectBuild(c) != nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should fail when no image", func() {
|
||||||
|
c := &common.Config{
|
||||||
|
Build: &common.Step{},
|
||||||
|
}
|
||||||
|
g.Assert(expectImage(c) != nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should fail when no commands", func() {
|
||||||
|
c := &common.Config{
|
||||||
|
Build: &common.Step{},
|
||||||
|
}
|
||||||
|
g.Assert(expectCommand(c) != nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should pass when proper Build provided", func() {
|
||||||
|
c := &common.Config{
|
||||||
|
Build: &common.Step{
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"commands": []string{"echo hi"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
g.Assert(expectImage(c) != nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should fail when untrusted setup image", func() {
|
||||||
|
c := &common.Config{Setup: &common.Step{Image: "foo/bar"}}
|
||||||
|
g.Assert(expectTrustedSetup(c) != nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should fail when untrusted clone image", func() {
|
||||||
|
c := &common.Config{Clone: &common.Step{Image: "foo/bar"}}
|
||||||
|
g.Assert(expectTrustedClone(c) != nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should fail when untrusted publish image", func() {
|
||||||
|
c := &common.Config{}
|
||||||
|
c.Publish = map[string]*common.Step{}
|
||||||
|
c.Publish["docker"] = &common.Step{Image: "foo/bar"}
|
||||||
|
g.Assert(expectTrustedPublish(c) != nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should fail when untrusted deploy image", func() {
|
||||||
|
c := &common.Config{}
|
||||||
|
c.Deploy = map[string]*common.Step{}
|
||||||
|
c.Deploy["amazon"] = &common.Step{Image: "foo/bar"}
|
||||||
|
g.Assert(expectTrustedDeploy(c) != nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should fail when untrusted notify image", func() {
|
||||||
|
c := &common.Config{}
|
||||||
|
c.Notify = map[string]*common.Step{}
|
||||||
|
c.Notify["hipchat"] = &common.Step{Image: "foo/bar"}
|
||||||
|
g.Assert(expectTrustedNotify(c) != nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should pass linter when build properly setup", func() {
|
||||||
|
c := &common.Config{}
|
||||||
|
c.Build = &common.Step{}
|
||||||
|
c.Build.Image = "golang"
|
||||||
|
c.Build.Config = map[string]interface{}{}
|
||||||
|
c.Build.Config["commands"] = []string{"go build", "go test"}
|
||||||
|
c.Publish = map[string]*common.Step{}
|
||||||
|
c.Publish["docker"] = &common.Step{Image: "docker"}
|
||||||
|
c.Deploy = map[string]*common.Step{}
|
||||||
|
c.Deploy["kubernetes"] = &common.Step{Image: "kubernetes"}
|
||||||
|
c.Notify = map[string]*common.Step{}
|
||||||
|
c.Notify["email"] = &common.Step{Image: "email"}
|
||||||
|
g.Assert(Lint(c) == nil).IsTrue()
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
109
parser/matrix/matrix.go
Normal file
109
parser/matrix/matrix.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package matrix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
limitTags = 10
|
||||||
|
limitAxis = 25
|
||||||
|
)
|
||||||
|
|
||||||
|
// Matrix represents the build matrix.
|
||||||
|
type Matrix map[string][]string
|
||||||
|
|
||||||
|
// Axis represents a single permutation of entries
|
||||||
|
// from the build matrix.
|
||||||
|
type Axis map[string]string
|
||||||
|
|
||||||
|
// String returns a string representation of an Axis as
|
||||||
|
// a comma-separated list of environment variables.
|
||||||
|
func (a Axis) String() string {
|
||||||
|
var envs []string
|
||||||
|
for k, v := range a {
|
||||||
|
envs = append(envs, k+"="+v)
|
||||||
|
}
|
||||||
|
return strings.Join(envs, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the Matrix section of the yaml file and
|
||||||
|
// returns a list of axis.
|
||||||
|
func Parse(raw string) ([]Axis, error) {
|
||||||
|
matrix, err := parseMatrix(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not a matrix build return an array
|
||||||
|
// with just the single axis.
|
||||||
|
if len(matrix) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return Calc(matrix), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calc calculates the permutations for th build matrix.
|
||||||
|
//
|
||||||
|
// Note that this method will cap the number of permutations
|
||||||
|
// to 25 to prevent an overly expensive calculation.
|
||||||
|
func Calc(matrix Matrix) []Axis {
|
||||||
|
// calculate number of permutations and
|
||||||
|
// extract the list of tags
|
||||||
|
// (ie go_version, redis_version, etc)
|
||||||
|
var perm int
|
||||||
|
var tags []string
|
||||||
|
for k, v := range matrix {
|
||||||
|
perm *= len(v)
|
||||||
|
if perm == 0 {
|
||||||
|
perm = len(v)
|
||||||
|
}
|
||||||
|
tags = append(tags, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// structure to hold the transformed
|
||||||
|
// result set
|
||||||
|
axisList := []Axis{}
|
||||||
|
|
||||||
|
// for each axis calculate the uniqe
|
||||||
|
// set of values that should be used.
|
||||||
|
for p := 0; p < perm; p++ {
|
||||||
|
axis := map[string]string{}
|
||||||
|
decr := perm
|
||||||
|
for i, tag := range tags {
|
||||||
|
elems := matrix[tag]
|
||||||
|
decr = decr / len(elems)
|
||||||
|
elem := p / decr % len(elems)
|
||||||
|
axis[tag] = elems[elem]
|
||||||
|
|
||||||
|
// enforce a maximum number of tags
|
||||||
|
// in the build matrix.
|
||||||
|
if i > limitTags {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append to the list of axis.
|
||||||
|
axisList = append(axisList, axis)
|
||||||
|
|
||||||
|
// enforce a maximum number of axis
|
||||||
|
// that should be calculated.
|
||||||
|
if p > limitAxis {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return axisList
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to parse the Matrix data from
|
||||||
|
// the raw yaml file.
|
||||||
|
func parseMatrix(raw string) (Matrix, error) {
|
||||||
|
data := struct {
|
||||||
|
Matrix map[string][]string
|
||||||
|
}{}
|
||||||
|
err := yaml.Unmarshal([]byte(raw), &data)
|
||||||
|
return data.Matrix, err
|
||||||
|
}
|
33
parser/matrix/matrix_test.go
Normal file
33
parser/matrix/matrix_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package matrix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Matrix(t *testing.T) {
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("Calculate matrix", func() {
|
||||||
|
|
||||||
|
m := map[string][]string{}
|
||||||
|
m["go_version"] = []string{"go1", "go1.2"}
|
||||||
|
m["python_version"] = []string{"3.2", "3.3"}
|
||||||
|
m["django_version"] = []string{"1.7", "1.7.1", "1.7.2"}
|
||||||
|
m["redis_version"] = []string{"2.6", "2.8"}
|
||||||
|
axis := Calc(m)
|
||||||
|
|
||||||
|
g.It("Should calculate permutations", func() {
|
||||||
|
g.Assert(len(axis)).Equal(24)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should not duplicate permutations", func() {
|
||||||
|
set := map[string]bool{}
|
||||||
|
for _, perm := range axis {
|
||||||
|
set[perm.String()] = true
|
||||||
|
}
|
||||||
|
g.Assert(len(set)).Equal(24)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
99
parser/parse.go
Normal file
99
parser/parse.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/drone/drone/common"
|
||||||
|
"github.com/drone/drone/parser/inject"
|
||||||
|
"github.com/drone/drone/parser/matrix"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Opts specifies parser options that will permit
|
||||||
|
// or deny certain Yaml settings.
|
||||||
|
type Opts struct {
|
||||||
|
Volumes bool
|
||||||
|
Network bool
|
||||||
|
Privileged bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultOpts = &Opts{
|
||||||
|
Volumes: false,
|
||||||
|
Network: false,
|
||||||
|
Privileged: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses a build matrix and returns
|
||||||
|
// a list of build configurations for each axis
|
||||||
|
// using the default parsing options.
|
||||||
|
func Parse(raw string) ([]*common.Config, error) {
|
||||||
|
return ParseOpts(raw, defaultOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseOpts parses a build matrix and returns
|
||||||
|
// a list of build configurations for each axis
|
||||||
|
// using the provided parsing options.
|
||||||
|
func ParseOpts(raw string, opts *Opts) ([]*common.Config, error) {
|
||||||
|
confs, err := parse(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, conf := range confs {
|
||||||
|
err := Lint(conf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
transformSetup(conf)
|
||||||
|
transformClone(conf)
|
||||||
|
transformBuild(conf)
|
||||||
|
transformImages(conf)
|
||||||
|
transformDockerPlugin(conf)
|
||||||
|
if !opts.Network {
|
||||||
|
rmNetwork(conf)
|
||||||
|
}
|
||||||
|
if !opts.Volumes {
|
||||||
|
rmVolumes(conf)
|
||||||
|
}
|
||||||
|
if !opts.Privileged {
|
||||||
|
rmPrivileged(conf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return confs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to parse a matrix configuraiton file.
|
||||||
|
func parse(raw string) ([]*common.Config, error) {
|
||||||
|
axis, err := matrix.Parse(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
confs := []*common.Config{}
|
||||||
|
|
||||||
|
// when no matrix values exist we should return
|
||||||
|
// a single config value with an empty label.
|
||||||
|
if len(axis) == 0 {
|
||||||
|
conf, err := parseYaml(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
confs = append(confs, conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ax := range axis {
|
||||||
|
// inject the matrix values into the raw script
|
||||||
|
injected := inject.Inject(raw, ax)
|
||||||
|
conf, err := parseYaml(injected)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conf.Axis = common.Axis(ax)
|
||||||
|
confs = append(confs, conf)
|
||||||
|
}
|
||||||
|
return confs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper funtion to parse a yaml configuration file.
|
||||||
|
func parseYaml(raw string) (*common.Config, error) {
|
||||||
|
conf := &common.Config{}
|
||||||
|
err := yaml.Unmarshal([]byte(raw), conf)
|
||||||
|
return conf, err
|
||||||
|
}
|
183
parser/trans.go
Normal file
183
parser/trans.go
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/drone/drone/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// transformRule applies a check or transformation rule
|
||||||
|
// to the build configuration.
|
||||||
|
type transformRule func(*common.Config)
|
||||||
|
|
||||||
|
// Transform executes the default transformers that
|
||||||
|
// ensure the minimal Yaml configuration is in place
|
||||||
|
// and correctly configured.
|
||||||
|
func Transform(c *common.Config) {
|
||||||
|
transformSetup(c)
|
||||||
|
transformClone(c)
|
||||||
|
transformBuild(c)
|
||||||
|
transformImages(c)
|
||||||
|
transformDockerPlugin(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransformSafe executes all transformers that remove
|
||||||
|
// privileged options from the Yaml.
|
||||||
|
func TransformSafe(c *common.Config) {
|
||||||
|
rmPrivileged(c)
|
||||||
|
rmVolumes(c)
|
||||||
|
rmNetwork(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// transformSetup is a transformer that adds a default
|
||||||
|
// setup step if none exists.
|
||||||
|
func transformSetup(c *common.Config) {
|
||||||
|
c.Setup = &common.Step{}
|
||||||
|
c.Setup.Image = "plugins/drone-build"
|
||||||
|
c.Setup.Config = c.Build.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// transformClone is a transformer that adds a default
|
||||||
|
// clone step if none exists.
|
||||||
|
func transformClone(c *common.Config) {
|
||||||
|
if c.Clone == nil {
|
||||||
|
c.Clone = &common.Step{}
|
||||||
|
}
|
||||||
|
if len(c.Clone.Image) == 0 {
|
||||||
|
c.Clone.Image = "plugins/drone-git"
|
||||||
|
c.Clone.Volumes = nil
|
||||||
|
c.Clone.NetworkMode = ""
|
||||||
|
}
|
||||||
|
if c.Clone.Config == nil {
|
||||||
|
c.Clone.Config = map[string]interface{}{}
|
||||||
|
c.Clone.Config["depth"] = 50
|
||||||
|
c.Clone.Config["recursive"] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// transformBuild is a transformer that removes the
|
||||||
|
// build configuration vargs. They should have
|
||||||
|
// already been transferred to the Setup step.
|
||||||
|
func transformBuild(c *common.Config) {
|
||||||
|
c.Build.Config = nil
|
||||||
|
c.Build.Entrypoint = []string{"/bin/bash", "-e"}
|
||||||
|
c.Build.Command = []string{"/drone/bin/build.sh"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// transformImages is a transformer that ensures every
|
||||||
|
// step has an image and uses a fully-qualified
|
||||||
|
// image name.
|
||||||
|
func transformImages(c *common.Config) {
|
||||||
|
c.Setup.Image = imageName(c.Setup.Image)
|
||||||
|
c.Clone.Image = imageName(c.Clone.Image)
|
||||||
|
for name, step := range c.Publish {
|
||||||
|
step.Image = imageNameDefault(step.Image, name)
|
||||||
|
}
|
||||||
|
for name, step := range c.Deploy {
|
||||||
|
step.Image = imageNameDefault(step.Image, name)
|
||||||
|
}
|
||||||
|
for name, step := range c.Notify {
|
||||||
|
step.Image = imageNameDefault(step.Image, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// transformDockerPlugin is a transformer that ensures the
|
||||||
|
// official Docker plugin can runs in privileged mode. It
|
||||||
|
// will disable volumes and network mode for added protection.
|
||||||
|
func transformDockerPlugin(c *common.Config) {
|
||||||
|
for _, step := range c.Publish {
|
||||||
|
if step.Image == "plugins/drone-docker" {
|
||||||
|
step.Privileged = true
|
||||||
|
step.Volumes = nil
|
||||||
|
step.NetworkMode = ""
|
||||||
|
step.Entrypoint = []string{}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rmPrivileged is a transformer that ensures every
|
||||||
|
// step is executed in non-privileged mode.
|
||||||
|
func rmPrivileged(c *common.Config) {
|
||||||
|
c.Setup.Privileged = false
|
||||||
|
c.Clone.Privileged = false
|
||||||
|
c.Build.Privileged = false
|
||||||
|
for _, step := range c.Publish {
|
||||||
|
if step.Image == "plugins/drone-docker" {
|
||||||
|
continue // the official docker plugin is the only exception here
|
||||||
|
}
|
||||||
|
step.Privileged = false
|
||||||
|
}
|
||||||
|
for _, step := range c.Deploy {
|
||||||
|
step.Privileged = false
|
||||||
|
}
|
||||||
|
for _, step := range c.Notify {
|
||||||
|
step.Privileged = false
|
||||||
|
}
|
||||||
|
for _, step := range c.Compose {
|
||||||
|
step.Privileged = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rmVolumes is a transformer that ensures every
|
||||||
|
// step is executed without volumes.
|
||||||
|
func rmVolumes(c *common.Config) {
|
||||||
|
c.Setup.Volumes = nil
|
||||||
|
c.Clone.Volumes = nil
|
||||||
|
c.Build.Volumes = nil
|
||||||
|
for _, step := range c.Publish {
|
||||||
|
step.Volumes = nil
|
||||||
|
}
|
||||||
|
for _, step := range c.Deploy {
|
||||||
|
step.Volumes = nil
|
||||||
|
}
|
||||||
|
for _, step := range c.Notify {
|
||||||
|
step.Volumes = nil
|
||||||
|
}
|
||||||
|
for _, step := range c.Compose {
|
||||||
|
step.Volumes = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rmNetwork is a transformer that ensures every
|
||||||
|
// step is executed with default bridge networking.
|
||||||
|
func rmNetwork(c *common.Config) {
|
||||||
|
c.Setup.NetworkMode = ""
|
||||||
|
c.Clone.NetworkMode = ""
|
||||||
|
c.Build.NetworkMode = ""
|
||||||
|
for _, step := range c.Publish {
|
||||||
|
step.NetworkMode = ""
|
||||||
|
}
|
||||||
|
for _, step := range c.Deploy {
|
||||||
|
step.NetworkMode = ""
|
||||||
|
}
|
||||||
|
for _, step := range c.Notify {
|
||||||
|
step.NetworkMode = ""
|
||||||
|
}
|
||||||
|
for _, step := range c.Compose {
|
||||||
|
step.NetworkMode = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageName is a helper function that resolves the
|
||||||
|
// image name. When using official drone plugins it
|
||||||
|
// is possible to use an alias name. This converts to
|
||||||
|
// the fully qualified name.
|
||||||
|
func imageName(name string) string {
|
||||||
|
if strings.Contains(name, "/") {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
name = strings.Replace(name, "_", "-", -1)
|
||||||
|
name = "plugins/drone-" + name
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageNameDefault is a helper function that resolves
|
||||||
|
// the image name. If the image name is blank the
|
||||||
|
// default name is used instead.
|
||||||
|
func imageNameDefault(name, defaultName string) string {
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = defaultName
|
||||||
|
}
|
||||||
|
return imageName(name)
|
||||||
|
}
|
146
parser/trans_test.go
Normal file
146
parser/trans_test.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/drone/drone/common"
|
||||||
|
"github.com/franela/goblin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Transform(t *testing.T) {
|
||||||
|
|
||||||
|
g := goblin.Goblin(t)
|
||||||
|
g.Describe("Transform", func() {
|
||||||
|
|
||||||
|
g.It("Should transform setup step", func() {
|
||||||
|
c := &common.Config{}
|
||||||
|
c.Build = &common.Step{}
|
||||||
|
c.Build.Config = map[string]interface{}{}
|
||||||
|
transformSetup(c)
|
||||||
|
g.Assert(c.Setup != nil).IsTrue()
|
||||||
|
g.Assert(c.Setup.Image).Equal("plugins/drone-build")
|
||||||
|
g.Assert(c.Setup.Config).Equal(c.Build.Config)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should transform clone step", func() {
|
||||||
|
c := &common.Config{}
|
||||||
|
transformClone(c)
|
||||||
|
g.Assert(c.Clone != nil).IsTrue()
|
||||||
|
g.Assert(c.Clone.Image).Equal("plugins/drone-git")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should transform build", func() {
|
||||||
|
c := &common.Config{}
|
||||||
|
c.Build = &common.Step{}
|
||||||
|
c.Build.Config = map[string]interface{}{}
|
||||||
|
c.Build.Config["commands"] = []string{"echo hello"}
|
||||||
|
transformBuild(c)
|
||||||
|
g.Assert(len(c.Build.Config)).Equal(0)
|
||||||
|
g.Assert(c.Build.Entrypoint[0]).Equal("/bin/bash")
|
||||||
|
g.Assert(c.Build.Command[0]).Equal("/drone/bin/build.sh")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should transform images", func() {
|
||||||
|
c := &common.Config{}
|
||||||
|
c.Setup = &common.Step{Image: "foo"}
|
||||||
|
c.Clone = &common.Step{Image: "foo/bar"}
|
||||||
|
c.Build = &common.Step{Image: "golang"}
|
||||||
|
c.Publish = map[string]*common.Step{"google_compute": &common.Step{}}
|
||||||
|
c.Deploy = map[string]*common.Step{"amazon": &common.Step{}}
|
||||||
|
c.Notify = map[string]*common.Step{"slack": &common.Step{}}
|
||||||
|
transformImages(c)
|
||||||
|
|
||||||
|
g.Assert(c.Setup.Image).Equal("plugins/drone-foo")
|
||||||
|
g.Assert(c.Clone.Image).Equal("foo/bar")
|
||||||
|
g.Assert(c.Build.Image).Equal("golang")
|
||||||
|
g.Assert(c.Publish["google_compute"].Image).Equal("plugins/drone-google-compute")
|
||||||
|
g.Assert(c.Deploy["amazon"].Image).Equal("plugins/drone-amazon")
|
||||||
|
g.Assert(c.Notify["slack"].Image).Equal("plugins/drone-slack")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should transform docker plugin", func() {
|
||||||
|
c := &common.Config{}
|
||||||
|
c.Publish = map[string]*common.Step{}
|
||||||
|
c.Publish["docker"] = &common.Step{Image: "plugins/drone-docker"}
|
||||||
|
transformDockerPlugin(c)
|
||||||
|
g.Assert(c.Publish["docker"].Privileged).Equal(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should remove privileged flag", func() {
|
||||||
|
c := &common.Config{}
|
||||||
|
c.Setup = &common.Step{Privileged: true}
|
||||||
|
c.Clone = &common.Step{Privileged: true}
|
||||||
|
c.Build = &common.Step{Privileged: true}
|
||||||
|
c.Compose = map[string]*common.Step{"postgres": &common.Step{Privileged: true}}
|
||||||
|
c.Publish = map[string]*common.Step{"google": &common.Step{Privileged: true}}
|
||||||
|
c.Deploy = map[string]*common.Step{"amazon": &common.Step{Privileged: true}}
|
||||||
|
c.Notify = map[string]*common.Step{"slack": &common.Step{Privileged: true}}
|
||||||
|
rmPrivileged(c)
|
||||||
|
|
||||||
|
g.Assert(c.Setup.Privileged).Equal(false)
|
||||||
|
g.Assert(c.Clone.Privileged).Equal(false)
|
||||||
|
g.Assert(c.Build.Privileged).Equal(false)
|
||||||
|
g.Assert(c.Compose["postgres"].Privileged).Equal(false)
|
||||||
|
g.Assert(c.Publish["google"].Privileged).Equal(false)
|
||||||
|
g.Assert(c.Deploy["amazon"].Privileged).Equal(false)
|
||||||
|
g.Assert(c.Notify["slack"].Privileged).Equal(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should not remove docker plugin privileged flag", func() {
|
||||||
|
c := &common.Config{}
|
||||||
|
c.Setup = &common.Step{}
|
||||||
|
c.Clone = &common.Step{}
|
||||||
|
c.Build = &common.Step{}
|
||||||
|
c.Publish = map[string]*common.Step{}
|
||||||
|
c.Publish["docker"] = &common.Step{Image: "plugins/drone-docker"}
|
||||||
|
transformDockerPlugin(c)
|
||||||
|
g.Assert(c.Publish["docker"].Privileged).Equal(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should remove volumes", func() {
|
||||||
|
c := &common.Config{}
|
||||||
|
c.Setup = &common.Step{Volumes: []string{"/:/tmp"}}
|
||||||
|
c.Clone = &common.Step{Volumes: []string{"/:/tmp"}}
|
||||||
|
c.Build = &common.Step{Volumes: []string{"/:/tmp"}}
|
||||||
|
c.Compose = map[string]*common.Step{"postgres": &common.Step{Volumes: []string{"/:/tmp"}}}
|
||||||
|
c.Publish = map[string]*common.Step{"google": &common.Step{Volumes: []string{"/:/tmp"}}}
|
||||||
|
c.Deploy = map[string]*common.Step{"amazon": &common.Step{Volumes: []string{"/:/tmp"}}}
|
||||||
|
c.Notify = map[string]*common.Step{"slack": &common.Step{Volumes: []string{"/:/tmp"}}}
|
||||||
|
rmVolumes(c)
|
||||||
|
|
||||||
|
g.Assert(len(c.Setup.Volumes)).Equal(0)
|
||||||
|
g.Assert(len(c.Clone.Volumes)).Equal(0)
|
||||||
|
g.Assert(len(c.Build.Volumes)).Equal(0)
|
||||||
|
g.Assert(len(c.Compose["postgres"].Volumes)).Equal(0)
|
||||||
|
g.Assert(len(c.Publish["google"].Volumes)).Equal(0)
|
||||||
|
g.Assert(len(c.Deploy["amazon"].Volumes)).Equal(0)
|
||||||
|
g.Assert(len(c.Notify["slack"].Volumes)).Equal(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should remove network", func() {
|
||||||
|
c := &common.Config{}
|
||||||
|
c.Setup = &common.Step{NetworkMode: "host"}
|
||||||
|
c.Clone = &common.Step{NetworkMode: "host"}
|
||||||
|
c.Build = &common.Step{NetworkMode: "host"}
|
||||||
|
c.Compose = map[string]*common.Step{"postgres": &common.Step{NetworkMode: "host"}}
|
||||||
|
c.Publish = map[string]*common.Step{"google": &common.Step{NetworkMode: "host"}}
|
||||||
|
c.Deploy = map[string]*common.Step{"amazon": &common.Step{NetworkMode: "host"}}
|
||||||
|
c.Notify = map[string]*common.Step{"slack": &common.Step{NetworkMode: "host"}}
|
||||||
|
rmNetwork(c)
|
||||||
|
|
||||||
|
g.Assert(c.Setup.NetworkMode).Equal("")
|
||||||
|
g.Assert(c.Clone.NetworkMode).Equal("")
|
||||||
|
g.Assert(c.Build.NetworkMode).Equal("")
|
||||||
|
g.Assert(c.Compose["postgres"].NetworkMode).Equal("")
|
||||||
|
g.Assert(c.Publish["google"].NetworkMode).Equal("")
|
||||||
|
g.Assert(c.Deploy["amazon"].NetworkMode).Equal("")
|
||||||
|
g.Assert(c.Notify["slack"].NetworkMode).Equal("")
|
||||||
|
})
|
||||||
|
|
||||||
|
g.It("Should return full qualified image name", func() {
|
||||||
|
g.Assert(imageName("microsoft/azure")).Equal("microsoft/azure")
|
||||||
|
g.Assert(imageName("azure")).Equal("plugins/drone-azure")
|
||||||
|
g.Assert(imageName("azure_storage")).Equal("plugins/drone-azure-storage")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user