mirror of
https://github.com/drone/drone-cli.git
synced 2024-11-23 09:21:56 +01:00
refactoring
This commit is contained in:
parent
1cc8bc3b55
commit
c29ddfbec5
@ -1 +1,77 @@
|
||||
package compiler
|
||||
|
||||
import "github.com/drone/drone-cli/common"
|
||||
|
||||
// Flags holds flags for loading a builder.
|
||||
type Flags struct {
|
||||
Lint bool
|
||||
Safe bool
|
||||
}
|
||||
|
||||
// Builder represents a build execution tree.
|
||||
type Builder struct {
|
||||
Build Node
|
||||
Deploy Node
|
||||
Notify Node
|
||||
}
|
||||
|
||||
// Run runs the build, deploy and notify nodes
|
||||
// in the build tree.
|
||||
func (b *Builder) Run() error {
|
||||
var err error
|
||||
err = b.RunBuild()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = b.RunDeploy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.RunNotify()
|
||||
}
|
||||
|
||||
// RunBuild runs only the build node.
|
||||
func (b *Builder) RunBuild() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunDeploy runs only the deploy node.
|
||||
func (b *Builder) RunDeploy() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunNotify runs on the notify node.
|
||||
func (b *Builder) RunNotify() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load loads a build configuration file.
|
||||
func Load(conf *common.Config, flags *Flags) *Builder {
|
||||
var (
|
||||
builds []Node
|
||||
deploys []Node
|
||||
notifys []Node
|
||||
)
|
||||
|
||||
for _, step := range conf.Compose {
|
||||
builds = append(builds, &batchNode{step}) // compose
|
||||
}
|
||||
builds = append(builds, &batchNode{}) // setup
|
||||
builds = append(builds, &batchNode{}) // clone
|
||||
builds = append(builds, &batchNode{}) // 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),
|
||||
}
|
||||
}
|
||||
|
@ -1,54 +0,0 @@
|
||||
package inject
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/drone/drone-cli/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
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
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
|
||||
`
|
@ -1,105 +0,0 @@
|
||||
package linter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/drone/drone-cli/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 clone 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
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
package linter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone-cli/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()
|
||||
})
|
||||
|
||||
})
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
package parser
|
||||
|
||||
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
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package parser
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
}
|
110
compiler/node.go
Normal file
110
compiler/node.go
Normal file
@ -0,0 +1,110 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/drone/drone-cli/common"
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
// Node is an element in the build execution tree.
|
||||
type Node interface {
|
||||
Run(*State, *ResultWriter) error
|
||||
}
|
||||
|
||||
// parallelNode runs a set of build nodes in parallel.
|
||||
type parallelNode []Node
|
||||
|
||||
func (n parallelNode) Run(s *State, rw *ResultWriter) error {
|
||||
var wg sync.WaitGroup
|
||||
for _, node := range n {
|
||||
wg.Add(1)
|
||||
|
||||
go func(node Node) {
|
||||
defer wg.Done()
|
||||
node.Run(s, rw)
|
||||
}(node)
|
||||
}
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// serialNode runs a set of build nodes in sequential order.
|
||||
type serialNode []Node
|
||||
|
||||
func (n serialNode) Run(s *State, rw *ResultWriter) error {
|
||||
for _, node := range n {
|
||||
err := node.Run(s, rw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rw.ExitCode() != 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// batchNode runs a container and blocks until complete.
|
||||
type batchNode struct {
|
||||
step *common.Step
|
||||
}
|
||||
|
||||
func (n *batchNode) Run(s *State, rw *ResultWriter) error {
|
||||
host := toHostConfig(n.step)
|
||||
conf := toContainerConfig(n.step)
|
||||
if n.step.Config != nil {
|
||||
conf.Cmd = toCommand(s, n.step)
|
||||
conf.Entrypoint = []string{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// serviceNode runs a container, blocking, writes output, uses config section
|
||||
type serviceNode struct {
|
||||
step *common.Step
|
||||
}
|
||||
|
||||
func (n *serviceNode) Run(s *State, rw *ResultWriter) error {
|
||||
host := toHostConfig(n.step)
|
||||
conf := toContainerConfig(n.step)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cloneNode runs a clone container, blocking, uses build section
|
||||
type cloneNode struct {
|
||||
step *common.Step
|
||||
}
|
||||
|
||||
func (n *cloneNode) Run(s *State, rw *ResultWriter) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildNode runs a build container, discards the step.config section
|
||||
type buildNode struct {
|
||||
host *dockerclient.HostConfig
|
||||
conf *dockerclient.ContainerConfig
|
||||
}
|
||||
|
||||
func newBuildNode(step *common.Step) *buildNode {
|
||||
host := toHostConfig(step)
|
||||
conf := toContainerConfig(step)
|
||||
conf.Entrypoint = []string{"/bin/bash"}
|
||||
conf.Cmd = []string{"/drone/bin/build.sh"}
|
||||
return &buildNode{host, conf}
|
||||
}
|
||||
|
||||
func (n *buildNode) Run(s *State, rw *ResultWriter) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupNode container, discards the step.config section
|
||||
type setupNode struct {
|
||||
step *common.Step
|
||||
}
|
||||
|
||||
func (n *setupNode) Run(s *State, rw *ResultWriter) error {
|
||||
return nil
|
||||
}
|
99
compiler/parse/node.go
Normal file
99
compiler/parse/node.go
Normal file
@ -0,0 +1,99 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/drone/drone-cli/common"
|
||||
)
|
||||
|
||||
// parallelNode runs a set of build nodes in parallel.
|
||||
type parallelNode struct {
|
||||
nodes []Node
|
||||
}
|
||||
|
||||
func (n *parallelNode) Run(s *State, rw *ResultWriter) error {
|
||||
for _, node := range n.nodes {
|
||||
err := node.Run(s, rw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rw.ExitCode() != 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// serialNode runs a set of build nodes in sequential order.
|
||||
type serialNode struct {
|
||||
nodes []Node
|
||||
}
|
||||
|
||||
func (n *serialNode) Run(s *State, rw *ResultWriter) error {
|
||||
var wg sync.WaitGroup
|
||||
for _, task := range n.nodes {
|
||||
wg.Add(1)
|
||||
|
||||
go func(task Node) {
|
||||
defer wg.Done()
|
||||
task.Run(s, rw)
|
||||
}(task)
|
||||
}
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// batchNode runs a container and blocks until complete.
|
||||
type batchNode struct {
|
||||
step *common.Step
|
||||
}
|
||||
|
||||
func (n *batchNode) Run(s *State, rw *ResultWriter) error {
|
||||
host := toHostConfig(n.step)
|
||||
conf := toContainerConfig(n.step)
|
||||
if n.step.Config != nil {
|
||||
conf.Cmd = toCommand(s, n.step)
|
||||
conf.Entrypoint = []string{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// serviceNode runs a container, blocking, writes output, uses config section
|
||||
type serviceNode struct {
|
||||
step *common.Step
|
||||
}
|
||||
|
||||
func (n *serviceNode) Run(s *State, rw *ResultWriter) error {
|
||||
host := toHostConfig(n.step)
|
||||
conf := toContainerConfig(n.step)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cloneNode runs a clone container, blocking, uses build section
|
||||
type cloneNode struct {
|
||||
step *common.Step
|
||||
}
|
||||
|
||||
func (n *cloneNode) Run(s *State, rw *ResultWriter) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildNode runs a build container, discards the step.config section
|
||||
type buildNode struct {
|
||||
step *common.Step
|
||||
}
|
||||
|
||||
func (n *buildNode) Run(s *State, rw *ResultWriter) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupNode container, discards the step.config section
|
||||
type setupNode struct {
|
||||
step *common.Step
|
||||
}
|
||||
|
||||
func (n *setupNode) Run(s *State, rw *ResultWriter) error {
|
||||
return nil
|
||||
}
|
@ -3,8 +3,6 @@ package parser
|
||||
import (
|
||||
"github.com/drone/drone-cli/common"
|
||||
"github.com/drone/drone-cli/common/matrix"
|
||||
"github.com/drone/drone-cli/parser/inject"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
@ -29,7 +27,7 @@ func Parse(raw string) ([]*common.Config, error) {
|
||||
|
||||
for _, ax := range axis {
|
||||
// inject the matrix values into the raw script
|
||||
injected := inject.Inject(raw, ax)
|
||||
injected := Inject(raw, ax)
|
||||
conf, err := parse(injected)
|
||||
if err != nil {
|
||||
return nil, err
|
14
compiler/parse/result.go
Normal file
14
compiler/parse/result.go
Normal file
@ -0,0 +1,14 @@
|
||||
package parser
|
||||
|
||||
// ResultWriter represents the result from a build request.
|
||||
type ResultWriter interface {
|
||||
|
||||
// Write writes the build stdout and stderr to the result.
|
||||
Write([]byte) (int, error)
|
||||
|
||||
// WriteExitCode writes the build exit status to the result.
|
||||
WriteExitCode(int)
|
||||
|
||||
// ExitCode returns the build exit status.
|
||||
ExitCode() int
|
||||
}
|
8
compiler/parse/tree.go
Normal file
8
compiler/parse/tree.go
Normal file
@ -0,0 +1,8 @@
|
||||
package parser
|
||||
|
||||
type Tree struct {
|
||||
Build Node
|
||||
Publish Node
|
||||
Deploy Node
|
||||
Notify Node
|
||||
}
|
31
compiler/result.go
Normal file
31
compiler/result.go
Normal file
@ -0,0 +1,31 @@
|
||||
package compiler
|
||||
|
||||
import "io"
|
||||
|
||||
// ResultWriter represents the result from a build request.
|
||||
type ResultWriter struct {
|
||||
writer io.Writer
|
||||
exitCode int
|
||||
}
|
||||
|
||||
// NewResultWriter returns a new ResultWriter.
|
||||
func NewResultWriter(w io.Writer) *ResultWriter {
|
||||
return &ResultWriter{writer: w}
|
||||
}
|
||||
|
||||
// Write writes the build stdout and stderr to the result.
|
||||
func (r *ResultWriter) Write(p []byte) (n int, err error) {
|
||||
return r.writer.Write(p)
|
||||
}
|
||||
|
||||
// WriteExitCode writes the build exit status to the result.
|
||||
func (r *ResultWriter) WriteExitCode(code int) {
|
||||
if code != 0 {
|
||||
r.exitCode = code
|
||||
}
|
||||
}
|
||||
|
||||
// ExitCode returns the build exit status.
|
||||
func (r *ResultWriter) ExitCode() int {
|
||||
return r.exitCode
|
||||
}
|
35
compiler/state.go
Normal file
35
compiler/state.go
Normal file
@ -0,0 +1,35 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/drone/drone-cli/common"
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
// State represents the execution state of the
|
||||
// running build.
|
||||
type State struct {
|
||||
Repo *common.Repo
|
||||
Commit *common.Repo
|
||||
Config *common.Config
|
||||
Clone *common.Clone
|
||||
|
||||
Client dockerclient.Client
|
||||
}
|
||||
|
||||
func (*State) Run() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (*State) Remove(string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*State) RemoveAll() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*State) Logs(string) (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
}
|
68
compiler/util.go
Normal file
68
compiler/util.go
Normal file
@ -0,0 +1,68 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/drone/drone-cli/common"
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
// 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(state *State, step *common.Step) []string {
|
||||
p := payload{
|
||||
state.Repo,
|
||||
state.Commit,
|
||||
state.Clone,
|
||||
step.Config,
|
||||
}
|
||||
return []string{p.Encode()}
|
||||
}
|
||||
|
||||
// helper function that converts a build step to
|
||||
// a hostConfig for use with the dockerclient
|
||||
func toHostConfig(step *common.Step) *dockerclient.HostConfig {
|
||||
return &dockerclient.HostConfig{
|
||||
Privileged: step.Privileged,
|
||||
NetworkMode: step.NetworkMode,
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
if len(step.Volumes) != 0 {
|
||||
config.Volumes = map[string]struct{}{}
|
||||
for _, path := range step.Volumes {
|
||||
config.Volumes[path] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// 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"`
|
||||
Commit *common.Repo `json:"commit"`
|
||||
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)
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
package linter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/drone/drone-cli/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 clone 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
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
package linter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone-cli/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()
|
||||
})
|
||||
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user