mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-18 14:36:30 +00:00
removed legacy code, updated cli
This commit is contained in:
parent
73f8c62528
commit
a95b118cb3
129 changed files with 1041 additions and 7532 deletions
21
.drone.yml
21
.drone.yml
|
@ -22,17 +22,16 @@ pipeline:
|
|||
when:
|
||||
event: push
|
||||
|
||||
|
||||
# archive:
|
||||
# image: plugins/s3
|
||||
# acl: public-read
|
||||
# bucket: downloads.drone.io
|
||||
# source: release/**/*.*
|
||||
# access_key: ${AWS_ACCESS_KEY_ID}
|
||||
# secret_key: ${AWS_SECRET_ACCESS_KEY}
|
||||
# when:
|
||||
# event: push
|
||||
# branch: master
|
||||
archive:
|
||||
image: plugins/s3
|
||||
acl: public-read
|
||||
bucket: downloads.drone.io
|
||||
source: release/**/*.*
|
||||
access_key: ${AWS_ACCESS_KEY_ID}
|
||||
secret_key: ${AWS_SECRET_ACCESS_KEY}
|
||||
when:
|
||||
event: push
|
||||
branch: master
|
||||
|
||||
publish:
|
||||
image: plugins/docker
|
||||
|
|
329
agent/agent.go
329
agent/agent.go
|
@ -1,329 +0,0 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/build"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/version"
|
||||
"github.com/drone/drone/yaml"
|
||||
"github.com/drone/drone/yaml/transform"
|
||||
"github.com/drone/envsubst"
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
Write(*build.Line)
|
||||
}
|
||||
|
||||
type Agent struct {
|
||||
Update UpdateFunc
|
||||
Logger LoggerFunc
|
||||
Engine build.Engine
|
||||
Timeout time.Duration
|
||||
Platform string
|
||||
Namespace string
|
||||
Extension []string
|
||||
Escalate []string
|
||||
Netrc []string
|
||||
Local string
|
||||
Pull bool
|
||||
}
|
||||
|
||||
func (a *Agent) Poll() error {
|
||||
|
||||
// logrus.Infof("Starting build %s/%s#%d.%d",
|
||||
// payload.Repo.Owner, payload.Repo.Name, payload.Build.Number, payload.Job.Number)
|
||||
//
|
||||
//
|
||||
// logrus.Infof("Finished build %s/%s#%d.%d",
|
||||
// payload.Repo.Owner, payload.Repo.Name, payload.Build.Number, payload.Job.Number)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) Run(payload *model.Work, cancel <-chan bool) error {
|
||||
|
||||
payload.Job.Status = model.StatusRunning
|
||||
payload.Job.Started = time.Now().Unix()
|
||||
|
||||
spec, err := a.prep(payload)
|
||||
if err != nil {
|
||||
payload.Job.Error = err.Error()
|
||||
payload.Job.ExitCode = 255
|
||||
payload.Job.Finished = payload.Job.Started
|
||||
payload.Job.Status = model.StatusError
|
||||
a.Update(payload)
|
||||
return err
|
||||
}
|
||||
a.Update(payload)
|
||||
err = a.exec(spec, payload, cancel)
|
||||
|
||||
if err != nil {
|
||||
payload.Job.ExitCode = 255
|
||||
payload.Job.Error = err.Error()
|
||||
}
|
||||
if exitErr, ok := err.(*build.ExitError); ok {
|
||||
payload.Job.ExitCode = exitErr.Code
|
||||
payload.Job.Error = "" // exit errors are already written to the log
|
||||
}
|
||||
|
||||
payload.Job.Finished = time.Now().Unix()
|
||||
|
||||
switch payload.Job.ExitCode {
|
||||
case 128, 130, 137:
|
||||
payload.Job.Status = model.StatusKilled
|
||||
case 0:
|
||||
payload.Job.Status = model.StatusSuccess
|
||||
default:
|
||||
payload.Job.Status = model.StatusFailure
|
||||
}
|
||||
|
||||
a.Update(payload)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Agent) prep(w *model.Work) (*yaml.Config, error) {
|
||||
|
||||
envs := toEnv(w)
|
||||
envSecrets := map[string]string{}
|
||||
|
||||
// list of secrets to interpolate in the yaml
|
||||
for _, secret := range w.Secrets {
|
||||
if (w.Verified || secret.SkipVerify) && secret.MatchEvent(w.Build.Event) {
|
||||
envSecrets[secret.Name] = secret.Value
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
w.Yaml, err = envsubst.Eval(w.Yaml, func(s string) string {
|
||||
env, ok := envSecrets[s]
|
||||
if !ok {
|
||||
env, _ = envs[s]
|
||||
}
|
||||
if strings.Contains(env, "\n") {
|
||||
env = fmt.Sprintf("%q", env)
|
||||
}
|
||||
return env
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// append secrets when verified or when a secret does not require
|
||||
// verification
|
||||
var secrets []*model.Secret
|
||||
for _, secret := range w.Secrets {
|
||||
if w.Verified || secret.SkipVerify {
|
||||
secrets = append(secrets, secret)
|
||||
}
|
||||
}
|
||||
|
||||
// inject the netrc file into the clone plugin if the repository is
|
||||
// private and requires authentication.
|
||||
if w.Repo.IsPrivate {
|
||||
secrets = append(secrets, &model.Secret{
|
||||
Name: "DRONE_NETRC_USERNAME",
|
||||
Value: w.Netrc.Login,
|
||||
Images: []string{"*"},
|
||||
Events: []string{"*"},
|
||||
})
|
||||
secrets = append(secrets, &model.Secret{
|
||||
Name: "DRONE_NETRC_PASSWORD",
|
||||
Value: w.Netrc.Password,
|
||||
Images: []string{"*"},
|
||||
Events: []string{"*"},
|
||||
})
|
||||
secrets = append(secrets, &model.Secret{
|
||||
Name: "DRONE_NETRC_MACHINE",
|
||||
Value: w.Netrc.Machine,
|
||||
Images: []string{"*"},
|
||||
Events: []string{"*"},
|
||||
})
|
||||
}
|
||||
|
||||
conf, err := yaml.ParseString(w.Yaml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
src := "src"
|
||||
if url, _ := url.Parse(w.Repo.Link); url != nil {
|
||||
host, _, err := net.SplitHostPort(url.Host)
|
||||
if err == nil {
|
||||
url.Host = host
|
||||
}
|
||||
src = filepath.Join(src, url.Host, url.Path)
|
||||
}
|
||||
|
||||
transform.Clone(conf, w.Repo.Kind)
|
||||
transform.Environ(conf, envs)
|
||||
transform.DefaultFilter(conf)
|
||||
if w.BuildLast != nil {
|
||||
transform.ChangeFilter(conf, w.BuildLast.Status)
|
||||
}
|
||||
|
||||
transform.ImageSecrets(conf, secrets, w.Build.Event)
|
||||
transform.Identifier(conf)
|
||||
transform.WorkspaceTransform(conf, "/drone", src)
|
||||
|
||||
if err := transform.Check(conf, w.Repo.IsTrusted); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transform.CommandTransform(conf)
|
||||
transform.ImagePull(conf, a.Pull)
|
||||
transform.ImageTag(conf)
|
||||
if err := transform.ImageEscalate(conf, a.Escalate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transform.PluginParams(conf)
|
||||
|
||||
if a.Local != "" {
|
||||
transform.PluginDisable(conf, true)
|
||||
transform.ImageVolume(conf, []string{a.Local + ":" + conf.Workspace.Path})
|
||||
}
|
||||
|
||||
transform.Pod(conf, a.Platform)
|
||||
if err := transform.RemoteTransform(conf, a.Extension); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func (a *Agent) exec(spec *yaml.Config, payload *model.Work, cancel <-chan bool) error {
|
||||
|
||||
conf := build.Config{
|
||||
Engine: a.Engine,
|
||||
Buffer: 500,
|
||||
}
|
||||
|
||||
pipeline := conf.Pipeline(spec)
|
||||
defer pipeline.Teardown()
|
||||
|
||||
// setup the build environment
|
||||
if err := pipeline.Setup(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
replacer := NewSecretReplacer(payload.Secrets)
|
||||
timeout := time.After(time.Duration(payload.Repo.Timeout) * time.Minute)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-pipeline.Done():
|
||||
return pipeline.Err()
|
||||
case <-cancel:
|
||||
pipeline.Stop()
|
||||
return fmt.Errorf("termination request received, build cancelled")
|
||||
case <-timeout:
|
||||
pipeline.Stop()
|
||||
return fmt.Errorf("maximum time limit exceeded, build cancelled")
|
||||
case <-time.After(a.Timeout):
|
||||
pipeline.Stop()
|
||||
return fmt.Errorf("terminal inactive for %v, build cancelled", a.Timeout)
|
||||
case <-pipeline.Next():
|
||||
|
||||
// TODO(bradrydzewski) this entire block of code should probably get
|
||||
// encapsulated in the pipeline.
|
||||
status := model.StatusSuccess
|
||||
if pipeline.Err() != nil {
|
||||
status = model.StatusFailure
|
||||
}
|
||||
// updates the build status passed into each container. I realize this is
|
||||
// a bit out of place and will work to resolve.
|
||||
pipeline.Head().Environment["DRONE_BUILD_STATUS"] = status
|
||||
|
||||
if !pipeline.Head().Constraints.Match(
|
||||
a.Platform,
|
||||
payload.Build.Deploy,
|
||||
payload.Build.Event,
|
||||
payload.Build.Branch,
|
||||
status, payload.Job.Environment) { // TODO: fix this whole section
|
||||
|
||||
pipeline.Skip()
|
||||
} else {
|
||||
pipeline.Exec()
|
||||
}
|
||||
case line := <-pipeline.Pipe():
|
||||
line.Out = replacer.Replace(line.Out)
|
||||
a.Logger(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toEnv(w *model.Work) map[string]string {
|
||||
envs := map[string]string{
|
||||
"CI": "drone",
|
||||
"DRONE": "true",
|
||||
"DRONE_ARCH": "linux/amd64",
|
||||
"DRONE_REPO": w.Repo.FullName,
|
||||
"DRONE_REPO_SCM": w.Repo.Kind,
|
||||
"DRONE_REPO_OWNER": w.Repo.Owner,
|
||||
"DRONE_REPO_NAME": w.Repo.Name,
|
||||
"DRONE_REPO_LINK": w.Repo.Link,
|
||||
"DRONE_REPO_AVATAR": w.Repo.Avatar,
|
||||
"DRONE_REPO_BRANCH": w.Repo.Branch,
|
||||
"DRONE_REPO_PRIVATE": fmt.Sprintf("%v", w.Repo.IsPrivate),
|
||||
"DRONE_REPO_TRUSTED": fmt.Sprintf("%v", w.Repo.IsTrusted),
|
||||
"DRONE_REMOTE_URL": w.Repo.Clone,
|
||||
"DRONE_COMMIT_SHA": w.Build.Commit,
|
||||
"DRONE_COMMIT_REF": w.Build.Ref,
|
||||
"DRONE_COMMIT_REFSPEC": w.Build.Refspec,
|
||||
"DRONE_COMMIT_BRANCH": w.Build.Branch,
|
||||
"DRONE_COMMIT_LINK": w.Build.Link,
|
||||
"DRONE_COMMIT_MESSAGE": w.Build.Message,
|
||||
"DRONE_COMMIT_AUTHOR": w.Build.Author,
|
||||
"DRONE_COMMIT_AUTHOR_EMAIL": w.Build.Email,
|
||||
"DRONE_COMMIT_AUTHOR_AVATAR": w.Build.Avatar,
|
||||
"DRONE_BUILD_NUMBER": fmt.Sprintf("%d", w.Build.Number),
|
||||
"DRONE_BUILD_EVENT": w.Build.Event,
|
||||
"DRONE_BUILD_STATUS": w.Build.Status,
|
||||
"DRONE_BUILD_LINK": fmt.Sprintf("%s/%s/%d", w.System.Link, w.Repo.FullName, w.Build.Number),
|
||||
"DRONE_BUILD_CREATED": fmt.Sprintf("%d", w.Build.Created),
|
||||
"DRONE_BUILD_STARTED": fmt.Sprintf("%d", w.Build.Started),
|
||||
"DRONE_BUILD_FINISHED": fmt.Sprintf("%d", w.Build.Finished),
|
||||
"DRONE_JOB_NUMBER": fmt.Sprintf("%d", w.Job.Number),
|
||||
"DRONE_JOB_STATUS": w.Job.Status,
|
||||
"DRONE_JOB_ERROR": w.Job.Error,
|
||||
"DRONE_JOB_EXIT_CODE": fmt.Sprintf("%d", w.Job.ExitCode),
|
||||
"DRONE_JOB_STARTED": fmt.Sprintf("%d", w.Job.Started),
|
||||
"DRONE_JOB_FINISHED": fmt.Sprintf("%d", w.Job.Finished),
|
||||
"DRONE_YAML_VERIFIED": fmt.Sprintf("%v", w.Verified),
|
||||
"DRONE_YAML_SIGNED": fmt.Sprintf("%v", w.Signed),
|
||||
"DRONE_BRANCH": w.Build.Branch,
|
||||
"DRONE_COMMIT": w.Build.Commit,
|
||||
"DRONE_VERSION": version.Version,
|
||||
}
|
||||
|
||||
if w.Build.Event == model.EventTag {
|
||||
envs["DRONE_TAG"] = strings.TrimPrefix(w.Build.Ref, "refs/tags/")
|
||||
}
|
||||
if w.Build.Event == model.EventPull {
|
||||
envs["DRONE_PULL_REQUEST"] = pullRegexp.FindString(w.Build.Ref)
|
||||
}
|
||||
if w.Build.Event == model.EventDeploy {
|
||||
envs["DRONE_DEPLOY_TO"] = w.Build.Deploy
|
||||
}
|
||||
|
||||
if w.BuildLast != nil {
|
||||
envs["DRONE_PREV_BUILD_STATUS"] = w.BuildLast.Status
|
||||
envs["DRONE_PREV_BUILD_NUMBER"] = fmt.Sprintf("%v", w.BuildLast.Number)
|
||||
envs["DRONE_PREV_COMMIT_SHA"] = w.BuildLast.Commit
|
||||
}
|
||||
|
||||
// inject matrix values as environment variables
|
||||
for key, val := range w.Job.Environment {
|
||||
envs[key] = val
|
||||
}
|
||||
return envs
|
||||
}
|
||||
|
||||
var pullRegexp = regexp.MustCompile("\\d+")
|
|
@ -1,46 +0,0 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
)
|
||||
|
||||
// SecretReplacer hides secrets from being exposed by the build output.
|
||||
type SecretReplacer interface {
|
||||
// Replace conceals instances of secrets found in s.
|
||||
Replace(s string) string
|
||||
}
|
||||
|
||||
// NewSecretReplacer creates a SecretReplacer based on whether any value in
|
||||
// secrets requests it be hidden.
|
||||
func NewSecretReplacer(secrets []*model.Secret) SecretReplacer {
|
||||
var r []string
|
||||
for _, s := range secrets {
|
||||
if s.Conceal {
|
||||
r = append(r, s.Value, "*****")
|
||||
}
|
||||
}
|
||||
|
||||
if len(r) == 0 {
|
||||
return &noopReplacer{}
|
||||
}
|
||||
|
||||
return &secretReplacer{
|
||||
replacer: strings.NewReplacer(r...),
|
||||
}
|
||||
}
|
||||
|
||||
type noopReplacer struct{}
|
||||
|
||||
func (*noopReplacer) Replace(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
type secretReplacer struct {
|
||||
replacer *strings.Replacer
|
||||
}
|
||||
|
||||
func (r *secretReplacer) Replace(s string) string {
|
||||
return r.replacer.Replace(s)
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
const testString = "This is SECRET: secret_value"
|
||||
|
||||
func TestSecret(t *testing.T) {
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("SecretReplacer", func() {
|
||||
g.It("Should conceal secret", func() {
|
||||
secrets := []*model.Secret{
|
||||
{
|
||||
Name: "SECRET",
|
||||
Value: "secret_value",
|
||||
Conceal: true,
|
||||
},
|
||||
}
|
||||
r := NewSecretReplacer(secrets)
|
||||
g.Assert(r.Replace(testString)).Equal("This is SECRET: *****")
|
||||
})
|
||||
|
||||
g.It("Should not conceal secret", func() {
|
||||
secrets := []*model.Secret{
|
||||
{
|
||||
Name: "SECRET",
|
||||
Value: "secret_value",
|
||||
Conceal: false,
|
||||
},
|
||||
}
|
||||
r := NewSecretReplacer(secrets)
|
||||
g.Assert(r.Replace(testString)).Equal(testString)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/drone/drone/build"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/mq/logger"
|
||||
"github.com/drone/mq/stomp"
|
||||
)
|
||||
|
||||
// UpdateFunc handles buid pipeline status updates.
|
||||
type UpdateFunc func(*model.Work)
|
||||
|
||||
// LoggerFunc handles buid pipeline logging updates.
|
||||
type LoggerFunc func(*build.Line)
|
||||
|
||||
var NoopUpdateFunc = func(*model.Work) {}
|
||||
|
||||
var TermLoggerFunc = func(line *build.Line) {
|
||||
fmt.Println(line)
|
||||
}
|
||||
|
||||
// NewClientUpdater returns an updater that sends updated build details
|
||||
// to the drone server.
|
||||
func NewClientUpdater(client *stomp.Client) UpdateFunc {
|
||||
return func(w *model.Work) {
|
||||
err := client.SendJSON("/queue/updates", w)
|
||||
if err != nil {
|
||||
logger.Warningf("Error updating %s/%s#%d.%d. %s",
|
||||
w.Repo.Owner, w.Repo.Name, w.Build.Number, w.Job.Number, err)
|
||||
}
|
||||
if w.Job.Status != model.StatusRunning {
|
||||
var dest = fmt.Sprintf("/topic/logs.%d", w.Job.ID)
|
||||
var opts = []stomp.MessageOption{
|
||||
stomp.WithHeader("eof", "true"),
|
||||
stomp.WithRetain("all"),
|
||||
}
|
||||
|
||||
if err := client.Send(dest, []byte("eof"), opts...); err != nil {
|
||||
logger.Warningf("Error sending eof %s/%s#%d.%d. %s",
|
||||
w.Repo.Owner, w.Repo.Name, w.Build.Number, w.Job.Number, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewClientLogger(client *stomp.Client, id int64, limit int64) LoggerFunc {
|
||||
|
||||
var size int64
|
||||
var dest = fmt.Sprintf("/topic/logs.%d", id)
|
||||
var opts = []stomp.MessageOption{
|
||||
stomp.WithRetain("all"),
|
||||
}
|
||||
|
||||
return func(line *build.Line) {
|
||||
if size > limit {
|
||||
return
|
||||
}
|
||||
if err := client.SendJSON(dest, line, opts...); err != nil {
|
||||
logrus.Errorf("Error streaming build logs. %s", err)
|
||||
}
|
||||
|
||||
size += int64(len(line.Out))
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
package build
|
||||
|
||||
import "github.com/drone/drone/yaml"
|
||||
|
||||
// Config defines the configuration for creating the Pipeline.
|
||||
type Config struct {
|
||||
Engine Engine
|
||||
|
||||
// Buffer defines the size of the buffer for the channel to which the
|
||||
// console output is streamed.
|
||||
Buffer uint
|
||||
}
|
||||
|
||||
// Pipeline creates a build Pipeline using the specific configuration for
|
||||
// the given Yaml specification.
|
||||
func (c *Config) Pipeline(spec *yaml.Config) *Pipeline {
|
||||
|
||||
pipeline := Pipeline{
|
||||
engine: c.Engine,
|
||||
pipe: make(chan *Line, c.Buffer),
|
||||
next: make(chan error),
|
||||
done: make(chan error),
|
||||
}
|
||||
|
||||
var containers []*yaml.Container
|
||||
containers = append(containers, spec.Services...)
|
||||
containers = append(containers, spec.Pipeline...)
|
||||
|
||||
for _, c := range containers {
|
||||
if c.Disabled {
|
||||
continue
|
||||
}
|
||||
next := &element{Container: c}
|
||||
if pipeline.head == nil {
|
||||
pipeline.head = next
|
||||
pipeline.tail = next
|
||||
} else {
|
||||
pipeline.tail.next = next
|
||||
pipeline.tail = next
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
pipeline.next <- nil
|
||||
}()
|
||||
|
||||
return &pipeline
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/drone/drone/build"
|
||||
"github.com/drone/drone/build/docker/internal"
|
||||
"github.com/drone/drone/yaml"
|
||||
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
type dockerEngine struct {
|
||||
client dockerclient.Client
|
||||
}
|
||||
|
||||
func (e *dockerEngine) ContainerStart(container *yaml.Container) (string, error) {
|
||||
conf := toContainerConfig(container)
|
||||
auth := toAuthConfig(container)
|
||||
|
||||
// pull the image if it does not exists or if the Container
|
||||
// is configured to always pull a new image.
|
||||
_, err := e.client.InspectImage(container.Image)
|
||||
if err != nil || container.Pull {
|
||||
e.client.PullImage(container.Image, auth)
|
||||
}
|
||||
|
||||
// create and start the container and return the Container ID.
|
||||
id, err := e.client.CreateContainer(conf, container.ID, auth)
|
||||
if err != nil {
|
||||
return id, err
|
||||
}
|
||||
err = e.client.StartContainer(id, &conf.HostConfig)
|
||||
if err != nil {
|
||||
|
||||
// remove the container if it cannot be started
|
||||
e.client.RemoveContainer(id, true, true)
|
||||
return id, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (e *dockerEngine) ContainerStop(id string) error {
|
||||
e.client.StopContainer(id, 1)
|
||||
e.client.KillContainer(id, "9")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *dockerEngine) ContainerRemove(id string) error {
|
||||
e.client.StopContainer(id, 1)
|
||||
e.client.KillContainer(id, "9")
|
||||
e.client.RemoveContainer(id, true, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *dockerEngine) ContainerWait(id string) (*build.State, error) {
|
||||
// wait for the container to exit
|
||||
//
|
||||
// TODO(bradrydzewski) we should have a for loop here
|
||||
// to re-connect and wait if this channel returns a
|
||||
// result even though the container is still running.
|
||||
//
|
||||
<-e.client.Wait(id)
|
||||
v, err := e.client.InspectContainer(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &build.State{
|
||||
ExitCode: v.State.ExitCode,
|
||||
OOMKilled: v.State.OOMKilled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *dockerEngine) ContainerLogs(id string) (io.ReadCloser, error) {
|
||||
opts := &dockerclient.LogOptions{
|
||||
Follow: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
}
|
||||
|
||||
piper, pipew := io.Pipe()
|
||||
go func() {
|
||||
defer pipew.Close()
|
||||
|
||||
// sometimes the docker logs fails due to parsing errors. this
|
||||
// routine will check for such a failure and attempt to resume
|
||||
// if necessary.
|
||||
for i := 0; i < 5; i++ {
|
||||
if i > 0 {
|
||||
opts.Tail = 1
|
||||
}
|
||||
|
||||
rc, err := e.client.ContainerLogs(id, opts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
// use Docker StdCopy
|
||||
internal.StdCopy(pipew, pipew, rc)
|
||||
|
||||
// check to see if the container is still running. If not,
|
||||
// we can safely exit and assume there are no more logs left
|
||||
// to stream.
|
||||
v, err := e.client.InspectContainer(id)
|
||||
if err != nil || !v.State.Running {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return piper, nil
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package docker
|
|
@ -1,25 +0,0 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/build"
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
// NewClient returns a new Docker engine using the provided Docker client.
|
||||
func NewClient(client dockerclient.Client) build.Engine {
|
||||
return &dockerEngine{client}
|
||||
}
|
||||
|
||||
// New returns a new Docker engine from the provided DOCKER_HOST and
|
||||
// DOCKER_CERT_PATH environment variables.
|
||||
func New(host, cert string, tls bool) (build.Engine, error) {
|
||||
config, err := dockerclient.TLSConfigFromCertPath(cert)
|
||||
if err == nil && tls {
|
||||
config.InsecureSkipVerify = true
|
||||
}
|
||||
client, err := dockerclient.NewDockerClient(host, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewClient(client), nil
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package docker
|
|
@ -1 +0,0 @@
|
|||
This is an internal copy of the Docker stdcopy package that removes the logrus debug logging. The original package is found at https://github.com/docker/docker/tree/master/pkg/stdcopy
|
|
@ -1,167 +0,0 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// StdType is the type of standard stream
|
||||
// a writer can multiplex to.
|
||||
type StdType byte
|
||||
|
||||
const (
|
||||
// Stdin represents standard input stream type.
|
||||
Stdin StdType = iota
|
||||
// Stdout represents standard output stream type.
|
||||
Stdout
|
||||
// Stderr represents standard error steam type.
|
||||
Stderr
|
||||
|
||||
stdWriterPrefixLen = 8
|
||||
stdWriterFdIndex = 0
|
||||
stdWriterSizeIndex = 4
|
||||
|
||||
startingBufLen = 32*1024 + stdWriterPrefixLen + 1
|
||||
)
|
||||
|
||||
// stdWriter is wrapper of io.Writer with extra customized info.
|
||||
type stdWriter struct {
|
||||
io.Writer
|
||||
prefix byte
|
||||
}
|
||||
|
||||
// Write sends the buffer to the underneath writer.
|
||||
// It insert the prefix header before the buffer,
|
||||
// so stdcopy.StdCopy knows where to multiplex the output.
|
||||
// It makes stdWriter to implement io.Writer.
|
||||
func (w *stdWriter) Write(buf []byte) (n int, err error) {
|
||||
if w == nil || w.Writer == nil {
|
||||
return 0, errors.New("Writer not instantiated")
|
||||
}
|
||||
if buf == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
header := [stdWriterPrefixLen]byte{stdWriterFdIndex: w.prefix}
|
||||
binary.BigEndian.PutUint32(header[stdWriterSizeIndex:], uint32(len(buf)))
|
||||
|
||||
line := append(header[:], buf...)
|
||||
|
||||
n, err = w.Writer.Write(line)
|
||||
n -= stdWriterPrefixLen
|
||||
|
||||
if n < 0 {
|
||||
n = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NewStdWriter instantiates a new Writer.
|
||||
// Everything written to it will be encapsulated using a custom format,
|
||||
// and written to the underlying `w` stream.
|
||||
// This allows multiple write streams (e.g. stdout and stderr) to be muxed into a single connection.
|
||||
// `t` indicates the id of the stream to encapsulate.
|
||||
// It can be stdcopy.Stdin, stdcopy.Stdout, stdcopy.Stderr.
|
||||
func NewStdWriter(w io.Writer, t StdType) io.Writer {
|
||||
return &stdWriter{
|
||||
Writer: w,
|
||||
prefix: byte(t),
|
||||
}
|
||||
}
|
||||
|
||||
// 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, startingBufLen)
|
||||
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 StdType(buf[stdWriterFdIndex]) {
|
||||
case Stdin:
|
||||
fallthrough
|
||||
case Stdout:
|
||||
// Write on stdout
|
||||
out = dstout
|
||||
case Stderr:
|
||||
// Write on stderr
|
||||
out = dsterr
|
||||
default:
|
||||
return 0, fmt.Errorf("Unrecognized input header: %d", buf[stdWriterFdIndex])
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
|
@ -1,260 +0,0 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewStdWriter(t *testing.T) {
|
||||
writer := NewStdWriter(ioutil.Discard, Stdout)
|
||||
if writer == nil {
|
||||
t.Fatalf("NewStdWriter with an invalid StdType should not return nil.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteWithUnitializedStdWriter(t *testing.T) {
|
||||
writer := stdWriter{
|
||||
Writer: nil,
|
||||
prefix: byte(Stdout),
|
||||
}
|
||||
n, err := writer.Write([]byte("Something here"))
|
||||
if n != 0 || err == nil {
|
||||
t.Fatalf("Should fail when given an uncomplete or uninitialized StdWriter")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteWithNilBytes(t *testing.T) {
|
||||
writer := NewStdWriter(ioutil.Discard, Stdout)
|
||||
n, err := writer.Write(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Shouldn't have fail when given no data")
|
||||
}
|
||||
if n > 0 {
|
||||
t.Fatalf("Write should have written 0 byte, but has written %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite(t *testing.T) {
|
||||
writer := NewStdWriter(ioutil.Discard, Stdout)
|
||||
data := []byte("Test StdWrite.Write")
|
||||
n, err := writer.Write(data)
|
||||
if err != nil {
|
||||
t.Fatalf("Error while writing with StdWrite")
|
||||
}
|
||||
if n != len(data) {
|
||||
t.Fatalf("Write should have written %d byte but wrote %d.", len(data), n)
|
||||
}
|
||||
}
|
||||
|
||||
type errWriter struct {
|
||||
n int
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *errWriter) Write(buf []byte) (int, error) {
|
||||
return f.n, f.err
|
||||
}
|
||||
|
||||
func TestWriteWithWriterError(t *testing.T) {
|
||||
expectedError := errors.New("expected")
|
||||
expectedReturnedBytes := 10
|
||||
writer := NewStdWriter(&errWriter{
|
||||
n: stdWriterPrefixLen + expectedReturnedBytes,
|
||||
err: expectedError}, Stdout)
|
||||
data := []byte("This won't get written, sigh")
|
||||
n, err := writer.Write(data)
|
||||
if err != expectedError {
|
||||
t.Fatalf("Didn't get expected error.")
|
||||
}
|
||||
if n != expectedReturnedBytes {
|
||||
t.Fatalf("Didn't get expected written bytes %d, got %d.",
|
||||
expectedReturnedBytes, n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteDoesNotReturnNegativeWrittenBytes(t *testing.T) {
|
||||
writer := NewStdWriter(&errWriter{n: -1}, Stdout)
|
||||
data := []byte("This won't get written, sigh")
|
||||
actual, _ := writer.Write(data)
|
||||
if actual != 0 {
|
||||
t.Fatalf("Expected returned written bytes equal to 0, got %d", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func getSrcBuffer(stdOutBytes, stdErrBytes []byte) (buffer *bytes.Buffer, err error) {
|
||||
buffer = new(bytes.Buffer)
|
||||
dstOut := NewStdWriter(buffer, Stdout)
|
||||
_, err = dstOut.Write(stdOutBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dstErr := NewStdWriter(buffer, Stderr)
|
||||
_, err = dstErr.Write(stdErrBytes)
|
||||
return
|
||||
}
|
||||
|
||||
func TestStdCopyWriteAndRead(t *testing.T) {
|
||||
stdOutBytes := []byte(strings.Repeat("o", startingBufLen))
|
||||
stdErrBytes := []byte(strings.Repeat("e", startingBufLen))
|
||||
buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
written, err := StdCopy(ioutil.Discard, ioutil.Discard, buffer)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedTotalWritten := len(stdOutBytes) + len(stdErrBytes)
|
||||
if written != int64(expectedTotalWritten) {
|
||||
t.Fatalf("Expected to have total of %d bytes written, got %d", expectedTotalWritten, written)
|
||||
}
|
||||
}
|
||||
|
||||
type customReader struct {
|
||||
n int
|
||||
err error
|
||||
totalCalls int
|
||||
correctCalls int
|
||||
src *bytes.Buffer
|
||||
}
|
||||
|
||||
func (f *customReader) Read(buf []byte) (int, error) {
|
||||
f.totalCalls++
|
||||
if f.totalCalls <= f.correctCalls {
|
||||
return f.src.Read(buf)
|
||||
}
|
||||
return f.n, f.err
|
||||
}
|
||||
|
||||
func TestStdCopyReturnsErrorReadingHeader(t *testing.T) {
|
||||
expectedError := errors.New("error")
|
||||
reader := &customReader{
|
||||
err: expectedError}
|
||||
written, err := StdCopy(ioutil.Discard, ioutil.Discard, reader)
|
||||
if written != 0 {
|
||||
t.Fatalf("Expected 0 bytes read, got %d", written)
|
||||
}
|
||||
if err != expectedError {
|
||||
t.Fatalf("Didn't get expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStdCopyReturnsErrorReadingFrame(t *testing.T) {
|
||||
expectedError := errors.New("error")
|
||||
stdOutBytes := []byte(strings.Repeat("o", startingBufLen))
|
||||
stdErrBytes := []byte(strings.Repeat("e", startingBufLen))
|
||||
buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
reader := &customReader{
|
||||
correctCalls: 1,
|
||||
n: stdWriterPrefixLen + 1,
|
||||
err: expectedError,
|
||||
src: buffer}
|
||||
written, err := StdCopy(ioutil.Discard, ioutil.Discard, reader)
|
||||
if written != 0 {
|
||||
t.Fatalf("Expected 0 bytes read, got %d", written)
|
||||
}
|
||||
if err != expectedError {
|
||||
t.Fatalf("Didn't get expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStdCopyDetectsCorruptedFrame(t *testing.T) {
|
||||
stdOutBytes := []byte(strings.Repeat("o", startingBufLen))
|
||||
stdErrBytes := []byte(strings.Repeat("e", startingBufLen))
|
||||
buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
reader := &customReader{
|
||||
correctCalls: 1,
|
||||
n: stdWriterPrefixLen + 1,
|
||||
err: io.EOF,
|
||||
src: buffer}
|
||||
written, err := StdCopy(ioutil.Discard, ioutil.Discard, reader)
|
||||
if written != startingBufLen {
|
||||
t.Fatalf("Expected %d bytes read, got %d", startingBufLen, written)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal("Didn't get nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStdCopyWithInvalidInputHeader(t *testing.T) {
|
||||
dstOut := NewStdWriter(ioutil.Discard, Stdout)
|
||||
dstErr := NewStdWriter(ioutil.Discard, Stderr)
|
||||
src := strings.NewReader("Invalid input")
|
||||
_, err := StdCopy(dstOut, dstErr, src)
|
||||
if err == nil {
|
||||
t.Fatal("StdCopy with invalid input header should fail.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStdCopyWithCorruptedPrefix(t *testing.T) {
|
||||
data := []byte{0x01, 0x02, 0x03}
|
||||
src := bytes.NewReader(data)
|
||||
written, err := StdCopy(nil, nil, src)
|
||||
if err != nil {
|
||||
t.Fatalf("StdCopy should not return an error with corrupted prefix.")
|
||||
}
|
||||
if written != 0 {
|
||||
t.Fatalf("StdCopy should have written 0, but has written %d", written)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStdCopyReturnsWriteErrors(t *testing.T) {
|
||||
stdOutBytes := []byte(strings.Repeat("o", startingBufLen))
|
||||
stdErrBytes := []byte(strings.Repeat("e", startingBufLen))
|
||||
buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedError := errors.New("expected")
|
||||
|
||||
dstOut := &errWriter{err: expectedError}
|
||||
|
||||
written, err := StdCopy(dstOut, ioutil.Discard, buffer)
|
||||
if written != 0 {
|
||||
t.Fatalf("StdCopy should have written 0, but has written %d", written)
|
||||
}
|
||||
if err != expectedError {
|
||||
t.Fatalf("Didn't get expected error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStdCopyDetectsNotFullyWrittenFrames(t *testing.T) {
|
||||
stdOutBytes := []byte(strings.Repeat("o", startingBufLen))
|
||||
stdErrBytes := []byte(strings.Repeat("e", startingBufLen))
|
||||
buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dstOut := &errWriter{n: startingBufLen - 10}
|
||||
|
||||
written, err := StdCopy(dstOut, ioutil.Discard, buffer)
|
||||
if written != 0 {
|
||||
t.Fatalf("StdCopy should have return 0 written bytes, but returned %d", written)
|
||||
}
|
||||
if err != io.ErrShortWrite {
|
||||
t.Fatalf("Didn't get expected io.ErrShortWrite error")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWrite(b *testing.B) {
|
||||
w := NewStdWriter(ioutil.Discard, Stdout)
|
||||
data := []byte("Test line for testing stdwriter performance\n")
|
||||
data = bytes.Repeat(data, 100)
|
||||
b.SetBytes(int64(len(data)))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := w.Write(data); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/drone/drone/yaml"
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
// helper function that converts the Continer data structure to the exepcted
|
||||
// dockerclient.ContainerConfig.
|
||||
func toContainerConfig(c *yaml.Container) *dockerclient.ContainerConfig {
|
||||
config := &dockerclient.ContainerConfig{
|
||||
Image: c.Image,
|
||||
Env: toEnvironmentSlice(c.Environment),
|
||||
Labels: c.Labels,
|
||||
Cmd: c.Command,
|
||||
Entrypoint: c.Entrypoint,
|
||||
WorkingDir: c.WorkingDir,
|
||||
HostConfig: dockerclient.HostConfig{
|
||||
Privileged: c.Privileged,
|
||||
NetworkMode: c.Network,
|
||||
Memory: c.MemLimit,
|
||||
ShmSize: c.ShmSize,
|
||||
CpuShares: c.CPUShares,
|
||||
CpuQuota: c.CPUQuota,
|
||||
CpusetCpus: c.CPUSet,
|
||||
MemorySwappiness: -1,
|
||||
OomKillDisable: c.OomKillDisable,
|
||||
},
|
||||
}
|
||||
|
||||
if len(config.Entrypoint) == 0 {
|
||||
config.Entrypoint = nil
|
||||
}
|
||||
if len(config.Cmd) == 0 {
|
||||
config.Cmd = nil
|
||||
}
|
||||
if len(c.ExtraHosts) > 0 {
|
||||
config.HostConfig.ExtraHosts = c.ExtraHosts
|
||||
}
|
||||
if len(c.DNS) != 0 {
|
||||
config.HostConfig.Dns = c.DNS
|
||||
}
|
||||
if len(c.DNSSearch) != 0 {
|
||||
config.HostConfig.DnsSearch = c.DNSSearch
|
||||
}
|
||||
if len(c.VolumesFrom) != 0 {
|
||||
config.HostConfig.VolumesFrom = c.VolumesFrom
|
||||
}
|
||||
|
||||
config.Volumes = map[string]struct{}{}
|
||||
for _, path := range c.Volumes {
|
||||
if strings.Index(path, ":") == -1 {
|
||||
config.Volumes[path] = struct{}{}
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(path, ":")
|
||||
config.Volumes[parts[1]] = struct{}{}
|
||||
config.HostConfig.Binds = append(config.HostConfig.Binds, path)
|
||||
}
|
||||
|
||||
for _, path := range c.Devices {
|
||||
if strings.Index(path, ":") == -1 {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(path, ":")
|
||||
device := dockerclient.DeviceMapping{
|
||||
PathOnHost: parts[0],
|
||||
PathInContainer: parts[1],
|
||||
CgroupPermissions: "rwm",
|
||||
}
|
||||
config.HostConfig.Devices = append(config.HostConfig.Devices, device)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// helper function that converts the AuthConfig data structure to the exepcted
|
||||
// dockerclient.AuthConfig.
|
||||
func toAuthConfig(container *yaml.Container) *dockerclient.AuthConfig {
|
||||
if container.AuthConfig.Username == "" &&
|
||||
container.AuthConfig.Password == "" {
|
||||
return nil
|
||||
}
|
||||
return &dockerclient.AuthConfig{
|
||||
Email: container.AuthConfig.Email,
|
||||
Username: container.AuthConfig.Username,
|
||||
Password: container.AuthConfig.Password,
|
||||
}
|
||||
}
|
||||
|
||||
// helper function that converts a key value map of environment variables to a
|
||||
// string slice in key=value format.
|
||||
func toEnvironmentSlice(env map[string]string) []string {
|
||||
var envs []string
|
||||
for k, v := range env {
|
||||
envs = append(envs, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return envs
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_toContainerConfig(t *testing.T) {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
func Test_toAuthConfig(t *testing.T) {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
func Test_toEnvironmentSlice(t *testing.T) {
|
||||
env := map[string]string{
|
||||
"HOME": "/root",
|
||||
}
|
||||
envs := toEnvironmentSlice(env)
|
||||
want, got := "HOME=/root", envs[0]
|
||||
if want != got {
|
||||
t.Errorf("Wanted envar %s got %s", want, got)
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package build
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/drone/drone/yaml"
|
||||
)
|
||||
|
||||
// Engine defines the container runtime engine.
|
||||
type Engine interface {
|
||||
ContainerStart(*yaml.Container) (string, error)
|
||||
ContainerStop(string) error
|
||||
ContainerRemove(string) error
|
||||
ContainerWait(string) (*State, error)
|
||||
ContainerLogs(string) (io.ReadCloser, error)
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package build
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrSkip is used as a return value when container execution should be
|
||||
// skipped at runtime. It is not returned as an error by any function.
|
||||
ErrSkip = errors.New("Skip")
|
||||
|
||||
// ErrTerm is used as a return value when the runner should terminate
|
||||
// execution and exit. It is not returned as an error by any function.
|
||||
ErrTerm = errors.New("Terminate")
|
||||
)
|
||||
|
||||
// An ExitError reports an unsuccessful exit.
|
||||
type ExitError struct {
|
||||
Name string
|
||||
Code int
|
||||
}
|
||||
|
||||
// Error returns the error message in string format.
|
||||
func (e *ExitError) Error() string {
|
||||
return fmt.Sprintf("%s : exit code %d", e.Name, e.Code)
|
||||
}
|
||||
|
||||
// An OomError reports the process received an OOMKill from the kernel.
|
||||
type OomError struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// Error returns the error message in string format.
|
||||
func (e *OomError) Error() string {
|
||||
return fmt.Sprintf("%s : received oom kill", e.Name)
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package build
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
g := goblin.Goblin(t)
|
||||
|
||||
g.Describe("Error messages", func() {
|
||||
|
||||
g.It("should include OOM details", func() {
|
||||
err := OomError{Name: "golang"}
|
||||
got, want := err.Error(), "golang : received oom kill"
|
||||
g.Assert(got).Equal(want)
|
||||
})
|
||||
|
||||
g.It("should include Exit code", func() {
|
||||
err := ExitError{Name: "golang", Code: 255}
|
||||
got, want := err.Error(), "golang : exit code 255"
|
||||
g.Assert(got).Equal(want)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -1,241 +0,0 @@
|
|||
package build
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/drone/drone/yaml"
|
||||
)
|
||||
|
||||
// element represents a link in the linked list.
|
||||
type element struct {
|
||||
*yaml.Container
|
||||
next *element
|
||||
}
|
||||
|
||||
// Pipeline represents a build pipeline.
|
||||
type Pipeline struct {
|
||||
conf *yaml.Config
|
||||
head *element
|
||||
tail *element
|
||||
wait sync.WaitGroup
|
||||
pipe chan (*Line)
|
||||
next chan (error)
|
||||
done chan (error)
|
||||
err error
|
||||
|
||||
containers []string
|
||||
volumes []string
|
||||
networks []string
|
||||
|
||||
engine Engine
|
||||
}
|
||||
|
||||
// Done returns when the process is done executing.
|
||||
func (p *Pipeline) Done() <-chan error {
|
||||
return p.done
|
||||
}
|
||||
|
||||
// Err returns the error for the current process.
|
||||
func (p *Pipeline) Err() error {
|
||||
return p.err
|
||||
}
|
||||
|
||||
// Next returns the next step in the process.
|
||||
func (p *Pipeline) Next() <-chan error {
|
||||
return p.next
|
||||
}
|
||||
|
||||
// Exec executes the current step.
|
||||
func (p *Pipeline) Exec() {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logrus.Errorln("recover executing build step", r)
|
||||
}
|
||||
}()
|
||||
|
||||
err := p.exec(p.head.Container)
|
||||
if err != nil {
|
||||
p.err = err
|
||||
}
|
||||
p.step()
|
||||
}()
|
||||
}
|
||||
|
||||
// Skip skips the current step.
|
||||
func (p *Pipeline) Skip() {
|
||||
p.step()
|
||||
}
|
||||
|
||||
// Pipe returns the build output pipe.
|
||||
func (p *Pipeline) Pipe() <-chan *Line {
|
||||
return p.pipe
|
||||
}
|
||||
|
||||
// Head returns the head item in the list.
|
||||
func (p *Pipeline) Head() *yaml.Container {
|
||||
return p.head.Container
|
||||
}
|
||||
|
||||
// Tail returns the tail item in the list.
|
||||
func (p *Pipeline) Tail() *yaml.Container {
|
||||
return p.tail.Container
|
||||
}
|
||||
|
||||
// Stop stops the pipeline.
|
||||
func (p *Pipeline) Stop() {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logrus.Errorln("recover stopping the pipeline", r)
|
||||
}
|
||||
}()
|
||||
p.done <- ErrTerm
|
||||
}()
|
||||
}
|
||||
|
||||
// Setup prepares the build pipeline environment.
|
||||
func (p *Pipeline) Setup() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Teardown removes the pipeline environment.
|
||||
func (p *Pipeline) Teardown() {
|
||||
|
||||
for _, id := range p.containers {
|
||||
p.engine.ContainerRemove(id)
|
||||
}
|
||||
|
||||
close(p.next)
|
||||
close(p.done)
|
||||
|
||||
// TODO we have a race condition here where the program can try to async
|
||||
// write to a closed pipe channel. This package, in general, needs to be
|
||||
// tested for race conditions.
|
||||
// close(p.pipe)
|
||||
}
|
||||
|
||||
// step steps through the pipeline to head.next
|
||||
func (p *Pipeline) step() {
|
||||
if p.head == p.tail {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logrus.Errorln("recover executing step function", r)
|
||||
}
|
||||
}()
|
||||
|
||||
// stop all containers
|
||||
for _, id := range p.containers {
|
||||
p.engine.ContainerStop(id)
|
||||
}
|
||||
|
||||
// wait for all logs to terminate
|
||||
// p.wait.Done() // this is for the ambassador
|
||||
p.wait.Wait()
|
||||
|
||||
// signal completion
|
||||
p.done <- nil
|
||||
}()
|
||||
} else {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logrus.Errorln("recover executing step to head function", r)
|
||||
}
|
||||
}()
|
||||
|
||||
p.head = p.head.next
|
||||
p.next <- nil
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// close closes open channels and signals the pipeline is done.
|
||||
func (p *Pipeline) close(err error) {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logrus.Errorln("recover closing the pipeline", r)
|
||||
}
|
||||
}()
|
||||
p.done <- err
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *Pipeline) exec(c *yaml.Container) error {
|
||||
|
||||
name, err := p.engine.ContainerStart(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.containers = append(p.containers, name)
|
||||
|
||||
p.wait.Add(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logrus.Errorln("recover writing build output", r)
|
||||
}
|
||||
|
||||
p.wait.Done()
|
||||
}()
|
||||
|
||||
rc, rerr := p.engine.ContainerLogs(name)
|
||||
if rerr != nil {
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
num := 0
|
||||
now := time.Now().UTC()
|
||||
scanner := bufio.NewScanner(rc)
|
||||
for scanner.Scan() {
|
||||
p.pipe <- &Line{
|
||||
Proc: c.Name,
|
||||
Time: int64(time.Since(now).Seconds()),
|
||||
Pos: num,
|
||||
Out: scanner.Text(),
|
||||
}
|
||||
num++
|
||||
}
|
||||
}()
|
||||
|
||||
// exit when running container in detached mode in background
|
||||
if c.Detached {
|
||||
return nil
|
||||
}
|
||||
|
||||
state, err := p.engine.ContainerWait(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.wait.Add(1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logrus.Errorln("recover writing exit code to output", r)
|
||||
}
|
||||
p.wait.Done()
|
||||
}()
|
||||
|
||||
p.pipe <- &Line{
|
||||
Proc: c.Name,
|
||||
Type: ExitCodeLine,
|
||||
Out: strconv.Itoa(state.ExitCode),
|
||||
}
|
||||
}()
|
||||
|
||||
if state.OOMKilled {
|
||||
return &OomError{c.Name}
|
||||
} else if state.ExitCode != 0 {
|
||||
return &ExitError{c.Name, state.ExitCode}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package build
|
||||
|
||||
var sampleYaml = `
|
||||
image: hello-world
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
|
||||
workspace:
|
||||
path: src/github.com/octocat/hello-world
|
||||
base: /go
|
||||
|
||||
pipeline:
|
||||
test:
|
||||
image: golang
|
||||
commands:
|
||||
- go install
|
||||
- go test
|
||||
build:
|
||||
image: golang
|
||||
commands:
|
||||
- go build
|
||||
when:
|
||||
event: push
|
||||
notify:
|
||||
image: slack
|
||||
channel: dev
|
||||
when:
|
||||
event: failure
|
||||
|
||||
services:
|
||||
database:
|
||||
image: mysql
|
||||
|
||||
networks:
|
||||
custom:
|
||||
driver: overlay
|
||||
|
||||
volumes:
|
||||
custom:
|
||||
driver: blockbridge
|
||||
`
|
|
@ -1,35 +0,0 @@
|
|||
package build
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
StdoutLine int = iota
|
||||
StderrLine
|
||||
ExitCodeLine
|
||||
MetadataLine
|
||||
ProgressLine
|
||||
)
|
||||
|
||||
// Line is a line of console output.
|
||||
type Line struct {
|
||||
Proc string `json:"proc,omitempty"`
|
||||
Time int64 `json:"time,omitempty"`
|
||||
Type int `json:"type,omitempty"`
|
||||
Pos int `json:"pos,omityempty"`
|
||||
Out string `json:"out,omitempty"`
|
||||
}
|
||||
|
||||
func (l *Line) String() string {
|
||||
switch l.Type {
|
||||
case ExitCodeLine:
|
||||
return fmt.Sprintf("[%s] exit code %s", l.Proc, l.Out)
|
||||
default:
|
||||
return fmt.Sprintf("[%s:L%v:%vs] %s", l.Proc, l.Pos, l.Time, l.Out)
|
||||
}
|
||||
}
|
||||
|
||||
// State defines the state of the container.
|
||||
type State struct {
|
||||
ExitCode int // container exit code
|
||||
OOMKilled bool // container exited due to oom error
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package build
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestLine(t *testing.T) {
|
||||
g := goblin.Goblin(t)
|
||||
|
||||
g.Describe("Line output", func() {
|
||||
g.It("should prefix string() with metadata", func() {
|
||||
line := Line{
|
||||
Proc: "redis",
|
||||
Time: 60,
|
||||
Pos: 1,
|
||||
Out: "starting redis server",
|
||||
}
|
||||
g.Assert(line.String()).Equal("[redis:L1:60s] starting redis server")
|
||||
})
|
||||
})
|
||||
}
|
|
@ -1,77 +1,37 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"net/url"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/mq/logger"
|
||||
"github.com/drone/mq/stomp"
|
||||
"github.com/tidwall/redlog"
|
||||
"github.com/cncd/pipeline/pipeline"
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
"github.com/cncd/pipeline/pipeline/backend/docker"
|
||||
"github.com/cncd/pipeline/pipeline/interrupt"
|
||||
"github.com/cncd/pipeline/pipeline/multipart"
|
||||
"github.com/cncd/pipeline/pipeline/rpc"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/samalba/dockerclient"
|
||||
"github.com/tevino/abool"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// AgentCmd is the exported command for starting the drone agent.
|
||||
var AgentCmd = cli.Command{
|
||||
Name: "agent",
|
||||
Usage: "starts the drone agent",
|
||||
Action: start,
|
||||
Action: loop,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
EnvVar: "DOCKER_HOST",
|
||||
Name: "docker-host",
|
||||
Usage: "docker daemon address",
|
||||
Value: "unix:///var/run/docker.sock",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
EnvVar: "DOCKER_TLS_VERIFY",
|
||||
Name: "docker-tls-verify",
|
||||
Usage: "docker daemon supports tlsverify",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DOCKER_CERT_PATH",
|
||||
Name: "docker-cert-path",
|
||||
Usage: "docker certificate directory",
|
||||
Value: "",
|
||||
},
|
||||
cli.IntFlag{
|
||||
EnvVar: "DOCKER_MAX_PROCS",
|
||||
Name: "docker-max-procs",
|
||||
Usage: "limit number of running docker processes",
|
||||
Value: 2,
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DOCKER_OS",
|
||||
Name: "docker-os",
|
||||
Usage: "docker operating system",
|
||||
Value: "linux",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DOCKER_ARCH",
|
||||
Name: "docker-arch",
|
||||
Usage: "docker architecture system",
|
||||
Value: "amd64",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_SERVER,DRONE_ENDPOINT",
|
||||
Name: "drone-server",
|
||||
Usage: "drone server address",
|
||||
Value: "ws://localhost:8000/ws/broker",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_TOKEN",
|
||||
Name: "drone-token",
|
||||
Usage: "drone authorization token",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_SECRET,DRONE_AGENT_SECRET",
|
||||
Name: "drone-secret",
|
||||
|
@ -83,89 +43,21 @@ var AgentCmd = cli.Command{
|
|||
Usage: "drone server backoff interval",
|
||||
Value: time.Second * 15,
|
||||
},
|
||||
cli.DurationFlag{
|
||||
EnvVar: "DRONE_PING",
|
||||
Name: "ping",
|
||||
Usage: "drone server ping frequency",
|
||||
Value: time.Minute * 5,
|
||||
cli.IntFlag{
|
||||
Name: "retry-limit",
|
||||
EnvVar: "DRONE_RETRY_LIMIT",
|
||||
Value: math.MaxInt32,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
EnvVar: "DRONE_DEBUG",
|
||||
Name: "debug",
|
||||
Usage: "start the agent in debug mode",
|
||||
},
|
||||
cli.DurationFlag{
|
||||
EnvVar: "DRONE_TIMEOUT",
|
||||
Name: "timeout",
|
||||
Usage: "drone timeout due to log inactivity",
|
||||
Value: time.Minute * 15,
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_FILTER",
|
||||
Name: "filter",
|
||||
Usage: "filter jobs processed by this agent",
|
||||
},
|
||||
cli.IntFlag{
|
||||
EnvVar: "DRONE_MAX_LOGS",
|
||||
Name: "max-log-size",
|
||||
Usage: "drone maximum log size in megabytes",
|
||||
Value: 5,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
EnvVar: "DRONE_PLUGIN_PRIVILEGED",
|
||||
Name: "privileged",
|
||||
Usage: "plugins that require privileged mode",
|
||||
Value: &cli.StringSlice{
|
||||
"plugins/docker",
|
||||
"plugins/docker:*",
|
||||
"plugins/gcr",
|
||||
"plugins/gcr:*",
|
||||
"plugins/ecr",
|
||||
"plugins/ecr:*",
|
||||
},
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_PLUGIN_NAMESPACE",
|
||||
Name: "namespace",
|
||||
Value: "plugins",
|
||||
Usage: "default plugin image namespace",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
EnvVar: "DRONE_PLUGIN_PULL",
|
||||
Name: "pull",
|
||||
Usage: "always pull latest plugin images",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
EnvVar: "DRONE_YAML_EXTENSION",
|
||||
Name: "extension",
|
||||
Usage: "custom plugin extension endpoint",
|
||||
},
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
cli.BoolFlag{
|
||||
EnvVar: "DRONE_CANARY",
|
||||
Name: "canary",
|
||||
Usage: "enable experimental features at your own risk",
|
||||
},
|
||||
|
||||
// cli.StringFlag{
|
||||
// Name: "endpoint",
|
||||
// EnvVar: "DRONE_ENDPOINT,DRONE_SERVER",
|
||||
// Value: "ws://localhost:9999/ws/rpc",
|
||||
// },
|
||||
// cli.DurationFlag{
|
||||
// Name: "backoff",
|
||||
// EnvVar: "DRONE_BACKOFF",
|
||||
// Value: time.Second * 15,
|
||||
// },
|
||||
cli.IntFlag{
|
||||
Name: "retry-limit",
|
||||
EnvVar: "DRONE_RETRY_LIMIT",
|
||||
Value: math.MaxInt32,
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "max-procs",
|
||||
EnvVar: "DRONE_MAX_PROCS",
|
||||
|
@ -179,139 +71,188 @@ var AgentCmd = cli.Command{
|
|||
},
|
||||
}
|
||||
|
||||
func start(c *cli.Context) {
|
||||
|
||||
if c.Bool("canary") {
|
||||
if err := loop(c); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log := redlog.New(os.Stderr)
|
||||
log.SetLevel(0)
|
||||
logger.SetLogger(log)
|
||||
|
||||
// debug level if requested by user
|
||||
if c.Bool("debug") {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
|
||||
log.SetLevel(1)
|
||||
} else {
|
||||
logrus.SetLevel(logrus.WarnLevel)
|
||||
}
|
||||
|
||||
var accessToken string
|
||||
if c.String("drone-secret") != "" {
|
||||
// secretToken := c.String("drone-secret")
|
||||
accessToken = c.String("drone-secret")
|
||||
// accessToken, _ = token.New(token.AgentToken, "").Sign(secretToken)
|
||||
} else {
|
||||
accessToken = c.String("drone-token")
|
||||
}
|
||||
|
||||
logger.Noticef("connecting to server %s", c.String("drone-server"))
|
||||
|
||||
server := strings.TrimRight(c.String("drone-server"), "/")
|
||||
|
||||
tls, err := dockerclient.TLSConfigFromCertPath(c.String("docker-cert-path"))
|
||||
if err == nil {
|
||||
tls.InsecureSkipVerify = c.Bool("docker-tls-verify")
|
||||
}
|
||||
docker, err := dockerclient.NewDockerClient(c.String("docker-host"), tls)
|
||||
func loop(c *cli.Context) error {
|
||||
endpoint, err := url.Parse(
|
||||
c.String("drone-server"),
|
||||
)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var client *stomp.Client
|
||||
|
||||
handler := func(m *stomp.Message) {
|
||||
running.Add(1)
|
||||
defer func() {
|
||||
running.Done()
|
||||
client.Ack(m.Ack)
|
||||
}()
|
||||
|
||||
r := pipelinet{
|
||||
drone: client,
|
||||
docker: docker,
|
||||
config: config{
|
||||
platform: c.String("docker-os") + "/" + c.String("docker-arch"),
|
||||
timeout: c.Duration("timeout"),
|
||||
namespace: c.String("namespace"),
|
||||
privileged: c.StringSlice("privileged"),
|
||||
pull: c.BoolT("pull"),
|
||||
logs: int64(c.Int("max-log-size")) * 1000000,
|
||||
extension: c.StringSlice("extension"),
|
||||
filter := rpc.Filter{
|
||||
Labels: map[string]string{
|
||||
"platform": c.String("platform"),
|
||||
},
|
||||
}
|
||||
|
||||
work := new(model.Work)
|
||||
m.Unmarshal(work)
|
||||
r.run(work)
|
||||
}
|
||||
|
||||
handleSignals()
|
||||
|
||||
backoff := c.Duration("backoff")
|
||||
|
||||
for {
|
||||
// dial the drone server to establish a TCP connection.
|
||||
client, err = stomp.Dial(server)
|
||||
if err != nil {
|
||||
logger.Warningf("connection failed, retry in %v. %s", backoff, err)
|
||||
<-time.After(backoff)
|
||||
continue
|
||||
}
|
||||
opts := []stomp.MessageOption{
|
||||
stomp.WithCredentials("x-token", accessToken),
|
||||
}
|
||||
|
||||
// initialize the stomp session and authenticate.
|
||||
if err = client.Connect(opts...); err != nil {
|
||||
logger.Warningf("session failed, retry in %v. %s", backoff, err)
|
||||
<-time.After(backoff)
|
||||
continue
|
||||
}
|
||||
|
||||
opts = []stomp.MessageOption{
|
||||
stomp.WithAck("client"),
|
||||
stomp.WithPrefetch(
|
||||
c.Int("docker-max-procs"),
|
||||
client, err := rpc.NewClient(
|
||||
endpoint.String(),
|
||||
rpc.WithRetryLimit(
|
||||
c.Int("retry-limit"),
|
||||
),
|
||||
rpc.WithBackoff(
|
||||
c.Duration("backoff"),
|
||||
),
|
||||
rpc.WithToken(
|
||||
c.String("drone-secret"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if filter := c.String("filter"); filter != "" {
|
||||
opts = append(opts, stomp.WithSelector(filter))
|
||||
defer client.Close()
|
||||
|
||||
sigterm := abool.New()
|
||||
ctx := context.Background()
|
||||
ctx = interrupt.WithContextFunc(ctx, func() {
|
||||
println("ctrl+c received, terminating process")
|
||||
sigterm.Set()
|
||||
})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
parallel := c.Int("max-procs")
|
||||
wg.Add(parallel)
|
||||
|
||||
for i := 0; i < parallel; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
if sigterm.IsSet() {
|
||||
return
|
||||
}
|
||||
if err := run(ctx, client, filter); err != nil {
|
||||
log.Printf("build runner encountered error: exiting: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// subscribe to the pending build queue.
|
||||
client.Subscribe("/queue/pending", stomp.HandlerFunc(func(m *stomp.Message) {
|
||||
go handler(m) // HACK until we a channel based Subscribe implementation
|
||||
}), opts...)
|
||||
|
||||
logger.Noticef("connection established, ready to process builds.")
|
||||
<-client.Done()
|
||||
|
||||
logger.Warningf("connection interrupted, attempting to reconnect.")
|
||||
}
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// tracks running builds
|
||||
var running sync.WaitGroup
|
||||
const (
|
||||
maxFileUpload = 5000000
|
||||
maxLogsUpload = 5000000
|
||||
)
|
||||
|
||||
func handleSignals() {
|
||||
// Graceful shut-down on SIGINT/SIGTERM
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
signal.Notify(c, syscall.SIGTERM)
|
||||
func run(ctx context.Context, client rpc.Peer, filter rpc.Filter) error {
|
||||
log.Println("pipeline: request next execution")
|
||||
|
||||
// get the next job from the queue
|
||||
work, err := client.Next(ctx, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if work == nil {
|
||||
return nil
|
||||
}
|
||||
log.Printf("pipeline: received next execution: %s", work.ID)
|
||||
|
||||
// new docker engine
|
||||
engine, err := docker.NewEnv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeout := time.Hour
|
||||
if minutes := work.Timeout; minutes != 0 {
|
||||
timeout = time.Duration(minutes) * time.Minute
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
cancelled := abool.New()
|
||||
go func() {
|
||||
if werr := client.Wait(ctx, work.ID); werr != nil {
|
||||
cancelled.SetTo(true)
|
||||
log.Printf("pipeline: cancel signal received: %s: %s", work.ID, werr)
|
||||
cancel()
|
||||
} else {
|
||||
log.Printf("pipeline: cancel channel closed: %s", work.ID)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
<-c
|
||||
logger.Warningf("SIGTERM received.")
|
||||
logger.Warningf("wait for running builds to finish.")
|
||||
running.Wait()
|
||||
logger.Warningf("done.")
|
||||
os.Exit(0)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Printf("pipeline: cancel ping loop: %s", work.ID)
|
||||
return
|
||||
case <-time.After(time.Minute):
|
||||
log.Printf("pipeline: ping queue: %s", work.ID)
|
||||
client.Extend(ctx, work.ID)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
state := rpc.State{}
|
||||
state.Started = time.Now().Unix()
|
||||
err = client.Update(context.Background(), work.ID, state)
|
||||
if err != nil {
|
||||
log.Printf("pipeline: error updating pipeline status: %s: %s", work.ID, err)
|
||||
}
|
||||
|
||||
var uploads sync.WaitGroup
|
||||
defaultLogger := pipeline.LogFunc(func(proc *backend.Step, rc multipart.Reader) error {
|
||||
part, rerr := rc.NextPart()
|
||||
if rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
uploads.Add(1)
|
||||
writer := rpc.NewLineWriter(client, work.ID, proc.Alias)
|
||||
rlimit := io.LimitReader(part, maxLogsUpload)
|
||||
io.Copy(writer, rlimit)
|
||||
|
||||
defer func() {
|
||||
log.Printf("pipeline: finish uploading logs: %s: step %s", work.ID, proc.Alias)
|
||||
uploads.Done()
|
||||
}()
|
||||
|
||||
part, rerr = rc.NextPart()
|
||||
if rerr != nil {
|
||||
return nil
|
||||
}
|
||||
rlimit = io.LimitReader(part, maxFileUpload)
|
||||
mime := part.Header().Get("Content-Type")
|
||||
if serr := client.Upload(context.Background(), work.ID, mime, rlimit); serr != nil {
|
||||
log.Printf("pipeline: cannot upload artifact: %s: %s: %s", work.ID, mime, serr)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
err = pipeline.New(work.Config,
|
||||
pipeline.WithContext(ctx),
|
||||
pipeline.WithLogger(defaultLogger),
|
||||
pipeline.WithTracer(pipeline.DefaultTracer),
|
||||
pipeline.WithEngine(engine),
|
||||
).Run()
|
||||
|
||||
state.Finished = time.Now().Unix()
|
||||
state.Exited = true
|
||||
if err != nil {
|
||||
state.Error = err.Error()
|
||||
if xerr, ok := err.(*pipeline.ExitError); ok {
|
||||
state.ExitCode = xerr.Code
|
||||
}
|
||||
if xerr, ok := err.(*pipeline.OomError); ok {
|
||||
state.ExitCode = xerr.Code
|
||||
}
|
||||
if cancelled.IsSet() {
|
||||
state.ExitCode = 137
|
||||
} else if state.ExitCode == 0 {
|
||||
state.ExitCode = 1
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("pipeline: execution complete: %s", work.ID)
|
||||
|
||||
uploads.Wait()
|
||||
err = client.Update(context.Background(), work.ID, state)
|
||||
if err != nil {
|
||||
log.Printf("Pipeine: error updating pipeline status: %s: %s", work.ID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/drone/drone/agent"
|
||||
"github.com/drone/drone/build/docker"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/mq/stomp"
|
||||
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
platform string
|
||||
namespace string
|
||||
privileged []string
|
||||
pull bool
|
||||
logs int64
|
||||
timeout time.Duration
|
||||
extension []string
|
||||
}
|
||||
|
||||
type pipelinet struct {
|
||||
drone *stomp.Client
|
||||
docker dockerclient.Client
|
||||
config config
|
||||
}
|
||||
|
||||
func (r *pipelinet) run(w *model.Work) {
|
||||
|
||||
// defer func() {
|
||||
// // r.drone.Ack(id, opts)
|
||||
// }()
|
||||
|
||||
logrus.Infof("Starting build %s/%s#%d.%d",
|
||||
w.Repo.Owner, w.Repo.Name, w.Build.Number, w.Job.Number)
|
||||
|
||||
cancel := make(chan bool, 1)
|
||||
engine := docker.NewClient(r.docker)
|
||||
|
||||
a := agent.Agent{
|
||||
Update: agent.NewClientUpdater(r.drone),
|
||||
Logger: agent.NewClientLogger(r.drone, w.Job.ID, r.config.logs),
|
||||
Engine: engine,
|
||||
Timeout: r.config.timeout,
|
||||
Platform: r.config.platform,
|
||||
Namespace: r.config.namespace,
|
||||
Escalate: r.config.privileged,
|
||||
Extension: r.config.extension,
|
||||
Pull: r.config.pull,
|
||||
}
|
||||
|
||||
cancelFunc := func(m *stomp.Message) {
|
||||
defer m.Release()
|
||||
|
||||
id := m.Header.GetInt64("job-id")
|
||||
if id == w.Job.ID {
|
||||
cancel <- true
|
||||
logrus.Infof("Cancel build %s/%s#%d.%d",
|
||||
w.Repo.Owner, w.Repo.Name, w.Build.Number, w.Job.Number)
|
||||
}
|
||||
}
|
||||
|
||||
// signal for canceling the build.
|
||||
sub, err := r.drone.Subscribe("/topic/cancel", stomp.HandlerFunc(cancelFunc))
|
||||
if err != nil {
|
||||
logrus.Errorf("Error subscribing to /topic/cancel. %s", err)
|
||||
}
|
||||
defer func() {
|
||||
r.drone.Unsubscribe(sub)
|
||||
}()
|
||||
|
||||
a.Run(w, cancel)
|
||||
|
||||
// if err := r.drone.LogPost(w.Job.ID, ioutil.NopCloser(&buf)); err != nil {
|
||||
// logrus.Errorf("Error sending logs for %s/%s#%d.%d",
|
||||
// w.Repo.Owner, w.Repo.Name, w.Build.Number, w.Job.Number)
|
||||
// }
|
||||
// stream.Close()
|
||||
|
||||
logrus.Infof("Finished build %s/%s#%d.%d",
|
||||
w.Repo.Owner, w.Repo.Name, w.Build.Number, w.Job.Number)
|
||||
}
|
|
@ -1,206 +0,0 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline"
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
"github.com/cncd/pipeline/pipeline/backend/docker"
|
||||
"github.com/cncd/pipeline/pipeline/interrupt"
|
||||
"github.com/cncd/pipeline/pipeline/multipart"
|
||||
"github.com/cncd/pipeline/pipeline/rpc"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/tevino/abool"
|
||||
)
|
||||
|
||||
func loop(c *cli.Context) error {
|
||||
endpoint, err := url.Parse(
|
||||
c.String("drone-server"),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter := rpc.Filter{
|
||||
Labels: map[string]string{
|
||||
"platform": c.String("platform"),
|
||||
},
|
||||
}
|
||||
|
||||
client, err := rpc.NewClient(
|
||||
endpoint.String(),
|
||||
rpc.WithRetryLimit(
|
||||
c.Int("retry-limit"),
|
||||
),
|
||||
rpc.WithBackoff(
|
||||
c.Duration("backoff"),
|
||||
),
|
||||
rpc.WithToken(
|
||||
c.String("drone-secret"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
sigterm := abool.New()
|
||||
ctx := context.Background()
|
||||
ctx = interrupt.WithContextFunc(ctx, func() {
|
||||
println("ctrl+c received, terminating process")
|
||||
sigterm.Set()
|
||||
})
|
||||
|
||||
var wg sync.WaitGroup
|
||||
parallel := c.Int("max-procs")
|
||||
wg.Add(parallel)
|
||||
|
||||
for i := 0; i < parallel; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
if sigterm.IsSet() {
|
||||
return
|
||||
}
|
||||
if err := run(ctx, client, filter); err != nil {
|
||||
log.Printf("build runner encountered error: exiting: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
maxFileUpload = 5000000
|
||||
maxLogsUpload = 5000000
|
||||
)
|
||||
|
||||
func run(ctx context.Context, client rpc.Peer, filter rpc.Filter) error {
|
||||
log.Println("pipeline: request next execution")
|
||||
|
||||
// get the next job from the queue
|
||||
work, err := client.Next(ctx, filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if work == nil {
|
||||
return nil
|
||||
}
|
||||
log.Printf("pipeline: received next execution: %s", work.ID)
|
||||
|
||||
// new docker engine
|
||||
engine, err := docker.NewEnv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeout := time.Hour
|
||||
if minutes := work.Timeout; minutes != 0 {
|
||||
timeout = time.Duration(minutes) * time.Minute
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
cancelled := abool.New()
|
||||
go func() {
|
||||
if werr := client.Wait(ctx, work.ID); werr != nil {
|
||||
cancelled.SetTo(true)
|
||||
log.Printf("pipeline: cancel signal received: %s: %s", work.ID, werr)
|
||||
cancel()
|
||||
} else {
|
||||
log.Printf("pipeline: cancel channel closed: %s", work.ID)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Printf("pipeline: cancel ping loop: %s", work.ID)
|
||||
return
|
||||
case <-time.After(time.Minute):
|
||||
log.Printf("pipeline: ping queue: %s", work.ID)
|
||||
client.Extend(ctx, work.ID)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
state := rpc.State{}
|
||||
state.Started = time.Now().Unix()
|
||||
err = client.Update(context.Background(), work.ID, state)
|
||||
if err != nil {
|
||||
log.Printf("pipeline: error updating pipeline status: %s: %s", work.ID, err)
|
||||
}
|
||||
|
||||
var uploads sync.WaitGroup
|
||||
defaultLogger := pipeline.LogFunc(func(proc *backend.Step, rc multipart.Reader) error {
|
||||
part, rerr := rc.NextPart()
|
||||
if rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
uploads.Add(1)
|
||||
writer := rpc.NewLineWriter(client, work.ID, proc.Alias)
|
||||
rlimit := io.LimitReader(part, maxLogsUpload)
|
||||
io.Copy(writer, rlimit)
|
||||
|
||||
defer func() {
|
||||
log.Printf("pipeline: finish uploading logs: %s: step %s", work.ID, proc.Alias)
|
||||
uploads.Done()
|
||||
}()
|
||||
|
||||
part, rerr = rc.NextPart()
|
||||
if rerr != nil {
|
||||
return nil
|
||||
}
|
||||
rlimit = io.LimitReader(part, maxFileUpload)
|
||||
mime := part.Header().Get("Content-Type")
|
||||
if serr := client.Upload(context.Background(), work.ID, mime, rlimit); serr != nil {
|
||||
log.Printf("pipeline: cannot upload artifact: %s: %s: %s", work.ID, mime, serr)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
err = pipeline.New(work.Config,
|
||||
pipeline.WithContext(ctx),
|
||||
pipeline.WithLogger(defaultLogger),
|
||||
pipeline.WithTracer(pipeline.DefaultTracer),
|
||||
pipeline.WithEngine(engine),
|
||||
).Run()
|
||||
|
||||
state.Finished = time.Now().Unix()
|
||||
state.Exited = true
|
||||
if err != nil {
|
||||
state.Error = err.Error()
|
||||
if xerr, ok := err.(*pipeline.ExitError); ok {
|
||||
state.ExitCode = xerr.Code
|
||||
}
|
||||
if xerr, ok := err.(*pipeline.OomError); ok {
|
||||
state.ExitCode = xerr.Code
|
||||
}
|
||||
if cancelled.IsSet() {
|
||||
state.ExitCode = 137
|
||||
} else if state.ExitCode == 0 {
|
||||
state.ExitCode = 1
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("pipeline: execution complete: %s", work.ID)
|
||||
|
||||
uploads.Wait()
|
||||
err = client.Update(context.Background(), work.ID, state)
|
||||
if err != nil {
|
||||
log.Printf("Pipeine: error updating pipeline status: %s: %s", work.ID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
var agentsCmd = cli.Command{
|
||||
Name: "agents",
|
||||
Usage: "manage agents",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := agentList(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
Usage: "format output",
|
||||
Value: tmplAgentList,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func agentList(c *cli.Context) error {
|
||||
client, err := newClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
agents, err := client.AgentList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Funcs(funcMap).Parse(c.String("format") + "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, agent := range agents {
|
||||
tmpl.Execute(os.Stdout, agent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// template for build list information
|
||||
var tmplAgentList = "\x1b[33m{{ .Address }} \x1b[0m" + `
|
||||
Platform: {{ .Platform }}
|
||||
Capacity: {{ .Capacity }} concurrent build(s)
|
||||
Pinged: {{ since .Updated }} ago
|
||||
Uptime: {{ since .Created }}
|
||||
`
|
||||
|
||||
var funcMap = template.FuncMap{
|
||||
"since": func(t int64) string {
|
||||
d := time.Now().Sub(time.Unix(t, 0))
|
||||
return d.String()
|
||||
},
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package main
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var buildCmd = cli.Command{
|
||||
Name: "build",
|
||||
|
|
|
@ -1,22 +1,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var buildInfoCmd = cli.Command{
|
||||
Name: "info",
|
||||
Usage: "show build details",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := buildInfo(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: buildInfo,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var buildLastCmd = cli.Command{
|
||||
Name: "last",
|
||||
Usage: "show latest build details",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := buildLast(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: buildLast,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var buildListCmd = cli.Command{
|
||||
Name: "list",
|
||||
Usage: "show build history",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := buildList(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: buildList,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/build"
|
||||
"github.com/cncd/pipeline/pipeline/rpc"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var buildLogsCmd = cli.Command{
|
||||
|
@ -61,7 +61,7 @@ func buildLogs(c *cli.Context) error {
|
|||
|
||||
dec := json.NewDecoder(r)
|
||||
fmt.Printf("Logs for build %s/%s#%d.%d\n", owner, name, number, job)
|
||||
var line build.Line
|
||||
var line rpc.Line
|
||||
|
||||
_, err = dec.Token()
|
||||
if err != nil {
|
||||
|
|
|
@ -2,21 +2,16 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var buildQueueCmd = cli.Command{
|
||||
Name: "queue",
|
||||
Usage: "show build queue",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := buildQueue(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: buildQueue,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -2,21 +2,16 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var buildStartCmd = cli.Command{
|
||||
Name: "start",
|
||||
Usage: "start a build",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := buildStart(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: buildStart,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "fork",
|
||||
|
|
|
@ -2,20 +2,15 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var buildStopCmd = cli.Command{
|
||||
Name: "stop",
|
||||
Usage: "stop a build",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := buildStop(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: buildStop,
|
||||
}
|
||||
|
||||
func buildStop(c *cli.Context) (err error) {
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
package main
|
|
@ -3,22 +3,17 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var deployCmd = cli.Command{
|
||||
Name: "deploy",
|
||||
Usage: "deploy code",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := deploy(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: deploy,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
748
drone/exec.go
748
drone/exec.go
|
@ -1,22 +1,26 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/agent"
|
||||
"github.com/drone/drone/build/docker"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/yaml"
|
||||
"github.com/cncd/pipeline/pipeline"
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
"github.com/cncd/pipeline/pipeline/backend/docker"
|
||||
"github.com/cncd/pipeline/pipeline/frontend"
|
||||
"github.com/cncd/pipeline/pipeline/frontend/yaml"
|
||||
"github.com/cncd/pipeline/pipeline/frontend/yaml/compiler"
|
||||
"github.com/cncd/pipeline/pipeline/interrupt"
|
||||
"github.com/cncd/pipeline/pipeline/multipart"
|
||||
"github.com/drone/envsubst"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var execCmd = cli.Command{
|
||||
|
@ -33,439 +37,387 @@ var execCmd = cli.Command{
|
|||
Usage: "build from local directory",
|
||||
EnvVar: "DRONE_LOCAL",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "secret",
|
||||
Usage: "build secrets in KEY=VALUE format",
|
||||
EnvVar: "DRONE_SECRET",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "secrets-file",
|
||||
Usage: "build secrets file in KEY=VALUE format",
|
||||
EnvVar: "DRONE_SECRETS_FILE",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "matrix",
|
||||
Usage: "build matrix in KEY=VALUE format",
|
||||
EnvVar: "DRONE_MATRIX",
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Usage: "build timeout",
|
||||
Value: time.Hour,
|
||||
EnvVar: "DRONE_TIMEOUT",
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "timeout.inactivity",
|
||||
Usage: "build timeout for inactivity",
|
||||
Value: time.Minute * 15,
|
||||
EnvVar: "DRONE_TIMEOUT_INACTIVITY",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
EnvVar: "DRONE_PLUGIN_PULL",
|
||||
Name: "pull",
|
||||
Usage: "always pull latest plugin images",
|
||||
cli.StringSliceFlag{
|
||||
Name: "volumes",
|
||||
Usage: "build volumes",
|
||||
EnvVar: "DRONE_VOLUMES",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
EnvVar: "DRONE_PLUGIN_PRIVILEGED",
|
||||
Name: "privileged",
|
||||
Usage: "plugins that require privileged mode",
|
||||
Usage: "privileged plugins",
|
||||
Value: &cli.StringSlice{
|
||||
"plugins/docker",
|
||||
"plugins/docker:*",
|
||||
"plugins/gcr",
|
||||
"plugins/gcr:*",
|
||||
"plugins/ecr",
|
||||
"plugins/ecr:*",
|
||||
},
|
||||
},
|
||||
|
||||
// Docker daemon flags
|
||||
|
||||
cli.StringFlag{
|
||||
EnvVar: "DOCKER_HOST",
|
||||
Name: "docker-host",
|
||||
Usage: "docker daemon address",
|
||||
Value: "unix:///var/run/docker.sock",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
EnvVar: "DOCKER_TLS_VERIFY",
|
||||
Name: "docker-tls-verify",
|
||||
Usage: "docker daemon supports tlsverify",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DOCKER_CERT_PATH",
|
||||
Name: "docker-cert-path",
|
||||
Usage: "docker certificate directory",
|
||||
Value: "",
|
||||
},
|
||||
|
||||
//
|
||||
// Please note the below flags are mirrored in the plugin starter kit and
|
||||
// should be kept synchronized.
|
||||
// https://github.com/drone/drone-plugin-starter
|
||||
// Please note the below flags are mirrored in the pipec and
|
||||
// should be kept synchronized. Do not edit directly
|
||||
// https://github.com/cncd/pipeline/pipec
|
||||
//
|
||||
|
||||
//
|
||||
// workspace default
|
||||
//
|
||||
cli.StringFlag{
|
||||
Name: "repo.fullname",
|
||||
Usage: "repository full name",
|
||||
EnvVar: "DRONE_REPO",
|
||||
Name: "workspace-base",
|
||||
Value: "/pipeline",
|
||||
EnvVar: "DRONE_WORKSPACE_BASE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo.owner",
|
||||
Usage: "repository owner",
|
||||
EnvVar: "DRONE_REPO_OWNER",
|
||||
Name: "workspace-path",
|
||||
Value: "src",
|
||||
EnvVar: "DRONE_WORKSPACE_PATH",
|
||||
},
|
||||
//
|
||||
// netrc parameters
|
||||
//
|
||||
cli.StringFlag{
|
||||
Name: "repo.name",
|
||||
Usage: "repository name",
|
||||
EnvVar: "DRONE_REPO_NAME",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo.type",
|
||||
Value: "git",
|
||||
Usage: "repository type",
|
||||
EnvVar: "DRONE_REPO_SCM",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo.link",
|
||||
Usage: "repository link",
|
||||
EnvVar: "DRONE_REPO_LINK",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo.avatar",
|
||||
Usage: "repository avatar",
|
||||
EnvVar: "DRONE_REPO_AVATAR",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo.branch",
|
||||
Usage: "repository default branch",
|
||||
EnvVar: "DRONE_REPO_BRANCH",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "repo.private",
|
||||
Usage: "repository is private",
|
||||
EnvVar: "DRONE_REPO_PRIVATE",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "repo.trusted",
|
||||
Usage: "repository is trusted",
|
||||
EnvVar: "DRONE_REPO_TRUSTED",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "remote.url",
|
||||
Usage: "git remote url",
|
||||
EnvVar: "DRONE_REMOTE_URL",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit.sha",
|
||||
Usage: "git commit sha",
|
||||
EnvVar: "DRONE_COMMIT_SHA",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit.ref",
|
||||
Value: "refs/heads/master",
|
||||
Usage: "git commit ref",
|
||||
EnvVar: "DRONE_COMMIT_REF",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit.branch",
|
||||
Value: "master",
|
||||
Usage: "git commit branch",
|
||||
EnvVar: "DRONE_COMMIT_BRANCH",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit.message",
|
||||
Usage: "git commit message",
|
||||
EnvVar: "DRONE_COMMIT_MESSAGE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit.link",
|
||||
Usage: "git commit link",
|
||||
EnvVar: "DRONE_COMMIT_LINK",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit.author.name",
|
||||
Usage: "git author name",
|
||||
EnvVar: "DRONE_COMMIT_AUTHOR",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit.author.email",
|
||||
Usage: "git author email",
|
||||
EnvVar: "DRONE_COMMIT_AUTHOR_EMAIL",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit.author.avatar",
|
||||
Usage: "git author avatar",
|
||||
EnvVar: "DRONE_COMMIT_AUTHOR_AVATAR",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "build.event",
|
||||
Value: "push",
|
||||
Usage: "build event",
|
||||
EnvVar: "DRONE_BUILD_EVENT",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "build.number",
|
||||
Usage: "build number",
|
||||
EnvVar: "DRONE_BUILD_NUMBER",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "build.created",
|
||||
Usage: "build created",
|
||||
EnvVar: "DRONE_BUILD_CREATED",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "build.started",
|
||||
Usage: "build started",
|
||||
EnvVar: "DRONE_BUILD_STARTED",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "build.finished",
|
||||
Usage: "build finished",
|
||||
EnvVar: "DRONE_BUILD_FINISHED",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "build.status",
|
||||
Usage: "build status",
|
||||
Value: "success",
|
||||
EnvVar: "DRONE_BUILD_STATUS",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "build.link",
|
||||
Usage: "build link",
|
||||
EnvVar: "DRONE_BUILD_LINK",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "build.deploy",
|
||||
Usage: "build deployment target",
|
||||
EnvVar: "DRONE_DEPLOY_TO",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "yaml.verified",
|
||||
Usage: "build yaml is verified",
|
||||
EnvVar: "DRONE_YAML_VERIFIED",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "yaml.signed",
|
||||
Usage: "build yaml is signed",
|
||||
EnvVar: "DRONE_YAML_SIGNED",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "prev.build.number",
|
||||
Usage: "previous build number",
|
||||
EnvVar: "DRONE_PREV_BUILD_NUMBER",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "prev.build.status",
|
||||
Usage: "previous build status",
|
||||
EnvVar: "DRONE_PREV_BUILD_STATUS",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "prev.commit.sha",
|
||||
Usage: "previous build sha",
|
||||
EnvVar: "DRONE_PREV_COMMIT_SHA",
|
||||
},
|
||||
|
||||
cli.StringFlag{
|
||||
Name: "netrc.username",
|
||||
Usage: "previous build sha",
|
||||
Name: "netrc-username",
|
||||
EnvVar: "DRONE_NETRC_USERNAME",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "netrc.password",
|
||||
Usage: "previous build sha",
|
||||
Name: "netrc-password",
|
||||
EnvVar: "DRONE_NETRC_PASSWORD",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "netrc.machine",
|
||||
Usage: "previous build sha",
|
||||
Name: "netrc-machine",
|
||||
EnvVar: "DRONE_NETRC_MACHINE",
|
||||
},
|
||||
//
|
||||
// metadata parameters
|
||||
//
|
||||
cli.StringFlag{
|
||||
Name: "system-arch",
|
||||
Value: "linux/amd64",
|
||||
EnvVar: "DRONE_SYSTEM_ARCH",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "system-name",
|
||||
Value: "pipec",
|
||||
EnvVar: "DRONE_SYSTEM_NAME",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "system-link",
|
||||
Value: "https://github.com/cncd/pipec",
|
||||
EnvVar: "DRONE_SYSTEM_LINK",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo-name",
|
||||
EnvVar: "DRONE_REPO_NAME",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo-link",
|
||||
EnvVar: "DRONE_REPO_LINK",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo-remote-url",
|
||||
EnvVar: "DRONE_REPO_REMOTE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo-private",
|
||||
EnvVar: "DRONE_REPO_PRIVATE",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "build-number",
|
||||
EnvVar: "DRONE_BUILD_NUMBER",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "build-created",
|
||||
EnvVar: "DRONE_BUILD_CREATED",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "build-started",
|
||||
EnvVar: "DRONE_BUILD_STARTED",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "build-finished",
|
||||
EnvVar: "DRONE_BUILD_FINISHED",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "build-status",
|
||||
EnvVar: "DRONE_BUILD_STATUS",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "build-event",
|
||||
EnvVar: "DRONE_BUILD_EVENT",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "build-link",
|
||||
EnvVar: "DRONE_BUILD_LINK",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "build-target",
|
||||
EnvVar: "DRONE_BUILD_TARGET",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit-sha",
|
||||
EnvVar: "DRONE_COMMIT_SHA",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit-ref",
|
||||
EnvVar: "DRONE_COMMIT_REF",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit-refspec",
|
||||
EnvVar: "DRONE_COMMIT_REFSPEC",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit-branch",
|
||||
EnvVar: "DRONE_COMMIT_BRANCH",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit-message",
|
||||
EnvVar: "DRONE_COMMIT_MESSAGE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit-author-name",
|
||||
EnvVar: "DRONE_COMMIT_AUTHOR_NAME",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit-author-avatar",
|
||||
EnvVar: "DRONE_COMMIT_AUTHOR_AVATAR",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "commit-author-email",
|
||||
EnvVar: "DRONE_COMMIT_AUTHOR_EMAIL",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "prev-build-number",
|
||||
EnvVar: "DRONE_PREV_BUILD_NUMBER",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "prev-build-created",
|
||||
EnvVar: "DRONE_PREV_BUILD_CREATED",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "prev-build-started",
|
||||
EnvVar: "DRONE_PREV_BUILD_STARTED",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "prev-build-finished",
|
||||
EnvVar: "DRONE_PREV_BUILD_FINISHED",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "prev-build-status",
|
||||
EnvVar: "DRONE_PREV_BUILD_STATUS",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "prev-build-event",
|
||||
EnvVar: "DRONE_PREV_BUILD_EVENT",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "prev-build-link",
|
||||
EnvVar: "DRONE_PREV_BUILD_LINK",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "prev-commit-sha",
|
||||
EnvVar: "DRONE_PREV_COMMIT_SHA",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "prev-commit-ref",
|
||||
EnvVar: "DRONE_PREV_COMMIT_REF",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "prev-commit-refspec",
|
||||
EnvVar: "DRONE_PREV_COMMIT_REFSPEC",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "prev-commit-branch",
|
||||
EnvVar: "DRONE_PREV_COMMIT_BRANCH",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "prev-commit-message",
|
||||
EnvVar: "DRONE_PREV_COMMIT_MESSAGE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "prev-commit-author-name",
|
||||
EnvVar: "DRONE_PREV_COMMIT_AUTHOR_NAME",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "prev-commit-author-avatar",
|
||||
EnvVar: "DRONE_PREV_COMMIT_AUTHOR_AVATAR",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "prev-commit-author-email",
|
||||
EnvVar: "DRONE_PREV_COMMIT_AUTHOR_EMAIL",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "job-number",
|
||||
EnvVar: "DRONE_JOB_NUMBER",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func exec(c *cli.Context) error {
|
||||
sigterm := make(chan os.Signal, 1)
|
||||
cancelc := make(chan bool, 1)
|
||||
signal.Notify(sigterm, os.Interrupt)
|
||||
go func() {
|
||||
<-sigterm
|
||||
cancelc <- true
|
||||
}()
|
||||
|
||||
path := c.Args().First()
|
||||
if path == "" {
|
||||
path = ".drone.yml"
|
||||
file := c.Args().First()
|
||||
if file == "" {
|
||||
file = ".drone.yml"
|
||||
}
|
||||
path, _ = filepath.Abs(path)
|
||||
dir := filepath.Dir(path)
|
||||
|
||||
file, err := ioutil.ReadFile(path)
|
||||
metadata := metadataFromContext(c)
|
||||
environ := metadata.Environ()
|
||||
for k, v := range metadata.EnvironDrone() {
|
||||
environ[k] = v
|
||||
}
|
||||
for _, env := range os.Environ() {
|
||||
k := strings.Split(env, "=")[0]
|
||||
v := strings.Split(env, "=")[1]
|
||||
environ[k] = v
|
||||
}
|
||||
|
||||
tmpl, err := envsubst.ParseFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
engine, err := docker.New(
|
||||
c.String("docker-host"),
|
||||
c.String("docker-cert-path"),
|
||||
c.Bool("docker-tls-verify"),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := agent.Agent{
|
||||
Update: agent.NoopUpdateFunc,
|
||||
Logger: agent.TermLoggerFunc,
|
||||
Engine: engine,
|
||||
Timeout: c.Duration("timeout.inactivity"),
|
||||
Platform: "linux/amd64",
|
||||
Escalate: c.StringSlice("privileged"),
|
||||
Netrc: []string{},
|
||||
Local: dir,
|
||||
Pull: c.Bool("pull"),
|
||||
}
|
||||
|
||||
payload := &model.Work{
|
||||
Yaml: string(file),
|
||||
Verified: c.BoolT("yaml.verified"),
|
||||
Signed: c.BoolT("yaml.signed"),
|
||||
Repo: &model.Repo{
|
||||
FullName: c.String("repo.fullname"),
|
||||
Owner: c.String("repo.owner"),
|
||||
Name: c.String("repo.name"),
|
||||
Kind: c.String("repo.type"),
|
||||
Link: c.String("repo.link"),
|
||||
Branch: c.String("repo.branch"),
|
||||
Avatar: c.String("repo.avatar"),
|
||||
Timeout: int64(c.Duration("timeout").Minutes()),
|
||||
IsPrivate: c.Bool("repo.private"),
|
||||
IsTrusted: c.BoolT("repo.trusted"),
|
||||
Clone: c.String("remote.url"),
|
||||
},
|
||||
System: &model.System{
|
||||
Link: c.GlobalString("server"),
|
||||
},
|
||||
Secrets: getSecrets(c),
|
||||
Netrc: &model.Netrc{
|
||||
Login: c.String("netrc.username"),
|
||||
Password: c.String("netrc.password"),
|
||||
Machine: c.String("netrc.machine"),
|
||||
},
|
||||
Build: &model.Build{
|
||||
Commit: c.String("commit.sha"),
|
||||
Branch: c.String("commit.branch"),
|
||||
Ref: c.String("commit.ref"),
|
||||
Link: c.String("commit.link"),
|
||||
Message: c.String("commit.message"),
|
||||
Author: c.String("commit.author.name"),
|
||||
Email: c.String("commit.author.email"),
|
||||
Avatar: c.String("commit.author.avatar"),
|
||||
Number: c.Int("build.number"),
|
||||
Event: c.String("build.event"),
|
||||
Deploy: c.String("build.deploy"),
|
||||
},
|
||||
BuildLast: &model.Build{
|
||||
Number: c.Int("prev.build.number"),
|
||||
Status: c.String("prev.build.status"),
|
||||
Commit: c.String("prev.commit.sha"),
|
||||
},
|
||||
}
|
||||
|
||||
if len(c.StringSlice("matrix")) > 0 {
|
||||
p := *payload
|
||||
p.Job = &model.Job{
|
||||
Environment: getMatrix(c),
|
||||
}
|
||||
return a.Run(&p, cancelc)
|
||||
}
|
||||
|
||||
axes, err := yaml.ParseMatrix(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(axes) == 0 {
|
||||
axes = append(axes, yaml.Axis{})
|
||||
}
|
||||
|
||||
var jobs []*model.Job
|
||||
count := 0
|
||||
for _, axis := range axes {
|
||||
jobs = append(jobs, &model.Job{
|
||||
Number: count,
|
||||
Environment: axis,
|
||||
confstr, err := tmpl.Execute(func(name string) string {
|
||||
return environ[name]
|
||||
})
|
||||
count++
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
fmt.Printf("Running Matrix job #%d\n", job.Number)
|
||||
p := *payload
|
||||
p.Job = job
|
||||
if err := a.Run(&p, cancelc); err != nil {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conf, err := yaml.ParseString(confstr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// configure volumes for local execution
|
||||
volumes := c.StringSlice("volumes")
|
||||
if c.Bool("local") {
|
||||
var (
|
||||
workspaceBase = conf.Workspace.Base
|
||||
workspacePath = conf.Workspace.Path
|
||||
)
|
||||
if workspaceBase == "" {
|
||||
workspaceBase = c.String("workspace-base")
|
||||
}
|
||||
if workspacePath == "" {
|
||||
workspacePath = c.String("workspace-path")
|
||||
}
|
||||
dir, _ := filepath.Abs(filepath.Dir(file))
|
||||
volumes = append(volumes, dir+":"+path.Join(workspaceBase, workspacePath))
|
||||
}
|
||||
|
||||
// compiles the yaml file
|
||||
compiled := compiler.New(
|
||||
compiler.WithEscalated(
|
||||
c.StringSlice("privileged")...,
|
||||
),
|
||||
compiler.WithVolumes(volumes...),
|
||||
compiler.WithWorkspace(
|
||||
c.String("workspace-base"),
|
||||
c.String("workspace-path"),
|
||||
),
|
||||
compiler.WithPrefix(
|
||||
c.String("prefix"),
|
||||
),
|
||||
compiler.WithProxy(),
|
||||
compiler.WithLocal(
|
||||
c.Bool("local"),
|
||||
),
|
||||
compiler.WithNetrc(
|
||||
c.String("netrc-username"),
|
||||
c.String("netrc-password"),
|
||||
c.String("netrc-machine"),
|
||||
),
|
||||
compiler.WithMetadata(metadata),
|
||||
).Compile(conf)
|
||||
|
||||
engine, err := docker.NewEnv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), c.Duration("timeout"))
|
||||
defer cancel()
|
||||
ctx = interrupt.WithContext(ctx)
|
||||
|
||||
return pipeline.New(compiled,
|
||||
pipeline.WithContext(ctx),
|
||||
pipeline.WithLogger(defaultLogger),
|
||||
pipeline.WithTracer(pipeline.DefaultTracer),
|
||||
pipeline.WithLogger(defaultLogger),
|
||||
pipeline.WithEngine(engine),
|
||||
).Run()
|
||||
}
|
||||
|
||||
// return the metadata from the cli context.
|
||||
func metadataFromContext(c *cli.Context) frontend.Metadata {
|
||||
return frontend.Metadata{
|
||||
Repo: frontend.Repo{
|
||||
Name: c.String("repo-name"),
|
||||
Link: c.String("repo-link"),
|
||||
Remote: c.String("repo-remote-url"),
|
||||
Private: c.Bool("repo-private"),
|
||||
},
|
||||
Curr: frontend.Build{
|
||||
Number: c.Int("build-number"),
|
||||
Created: c.Int64("build-created"),
|
||||
Started: c.Int64("build-started"),
|
||||
Finished: c.Int64("build-finished"),
|
||||
Status: c.String("build-status"),
|
||||
Event: c.String("build-event"),
|
||||
Link: c.String("build-link"),
|
||||
Target: c.String("build-target"),
|
||||
Commit: frontend.Commit{
|
||||
Sha: c.String("commit-sha"),
|
||||
Ref: c.String("commit-ref"),
|
||||
Refspec: c.String("commit-refspec"),
|
||||
Branch: c.String("commit-branch"),
|
||||
Message: c.String("commit-message"),
|
||||
Author: frontend.Author{
|
||||
Name: c.String("commit-author-name"),
|
||||
Email: c.String("commit-author-email"),
|
||||
Avatar: c.String("commit-author-avatar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Prev: frontend.Build{
|
||||
Number: c.Int("prev-build-number"),
|
||||
Created: c.Int64("prev-build-created"),
|
||||
Started: c.Int64("prev-build-started"),
|
||||
Finished: c.Int64("prev-build-finished"),
|
||||
Status: c.String("prev-build-status"),
|
||||
Event: c.String("prev-build-event"),
|
||||
Link: c.String("prev-build-link"),
|
||||
Commit: frontend.Commit{
|
||||
Sha: c.String("prev-commit-sha"),
|
||||
Ref: c.String("prev-commit-ref"),
|
||||
Refspec: c.String("prev-commit-refspec"),
|
||||
Branch: c.String("prev-commit-branch"),
|
||||
Message: c.String("prev-commit-message"),
|
||||
Author: frontend.Author{
|
||||
Name: c.String("prev-commit-author-name"),
|
||||
Email: c.String("prev-commit-author-email"),
|
||||
Avatar: c.String("prev-commit-author-avatar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Job: frontend.Job{
|
||||
Number: c.Int("job-number"),
|
||||
},
|
||||
Sys: frontend.System{
|
||||
Name: c.String("system-name"),
|
||||
Link: c.String("system-link"),
|
||||
Arch: c.String("system-arch"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var defaultLogger = pipeline.LogFunc(func(proc *backend.Step, rc multipart.Reader) error {
|
||||
part, err := rc.NextPart()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
io.Copy(os.Stderr, part)
|
||||
return nil
|
||||
}
|
||||
|
||||
// helper function to retrieve matrix variables.
|
||||
func getMatrix(c *cli.Context) map[string]string {
|
||||
envs := map[string]string{}
|
||||
for _, s := range c.StringSlice("matrix") {
|
||||
parts := strings.SplitN(s, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
k := parts[0]
|
||||
v := parts[1]
|
||||
envs[k] = v
|
||||
}
|
||||
return envs
|
||||
}
|
||||
|
||||
// helper function to retrieve secret variables.
|
||||
func getSecrets(c *cli.Context) []*model.Secret {
|
||||
|
||||
var secrets []*model.Secret
|
||||
|
||||
if c.String("secrets-file") != "" {
|
||||
envs, _ := godotenv.Read(c.String("secrets-file"))
|
||||
for k, v := range envs {
|
||||
secret := &model.Secret{
|
||||
Name: k,
|
||||
Value: v,
|
||||
Events: []string{
|
||||
model.EventPull,
|
||||
model.EventPush,
|
||||
model.EventTag,
|
||||
model.EventDeploy,
|
||||
},
|
||||
Images: []string{"*"},
|
||||
}
|
||||
secrets = append(secrets, secret)
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range c.StringSlice("secret") {
|
||||
parts := strings.SplitN(s, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
secret := &model.Secret{
|
||||
Name: parts[0],
|
||||
Value: parts[1],
|
||||
Events: []string{
|
||||
model.EventPull,
|
||||
model.EventPush,
|
||||
model.EventTag,
|
||||
model.EventDeploy,
|
||||
},
|
||||
Images: []string{"*"},
|
||||
}
|
||||
secrets = append(secrets, secret)
|
||||
}
|
||||
return secrets
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package main
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var globalCmd = cli.Command{
|
||||
Name: "global",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package main
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var globalSecretCmd = cli.Command{
|
||||
Name: "secret",
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var globalSecretAddCmd = cli.Command{
|
||||
Name: "add",
|
||||
Usage: "adds a secret",
|
||||
ArgsUsage: "[key] [value]",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := globalSecretAdd(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: globalSecretAdd,
|
||||
Flags: secretAddFlags(),
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var globalSecretListCmd = cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "list all secrets",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := globalSecretList(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: globalSecretList,
|
||||
Flags: secretListFlags(),
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var globalSecretRemoveCmd = cli.Command{
|
||||
Name: "rm",
|
||||
Usage: "remove a secret",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := globalSecretRemove(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: globalSecretRemove,
|
||||
}
|
||||
|
||||
func globalSecretRemove(c *cli.Context) error {
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var infoCmd = cli.Command{
|
||||
Name: "info",
|
||||
Usage: "show information about the current user",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := info(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: info,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/drone/drone/drone/agent"
|
||||
"github.com/drone/drone/version"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/ianschenck/envflag"
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -32,7 +33,6 @@ func main() {
|
|||
}
|
||||
app.Commands = []cli.Command{
|
||||
agent.AgentCmd,
|
||||
agentsCmd,
|
||||
buildCmd,
|
||||
deployCmd,
|
||||
execCmd,
|
||||
|
@ -46,5 +46,8 @@ func main() {
|
|||
globalCmd,
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package main
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var orgCmd = cli.Command{
|
||||
Name: "org",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package main
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var orgSecretCmd = cli.Command{
|
||||
Name: "secret",
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var orgSecretAddCmd = cli.Command{
|
||||
Name: "add",
|
||||
Usage: "adds a secret",
|
||||
ArgsUsage: "[org] [key] [value]",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := orgSecretAdd(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: orgSecretAdd,
|
||||
Flags: secretAddFlags(),
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var orgSecretListCmd = cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "list all secrets",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := orgSecretList(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: orgSecretList,
|
||||
Flags: secretListFlags(),
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package main
|
|||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var orgSecretRemoveCmd = cli.Command{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package main
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var repoCmd = cli.Command{
|
||||
Name: "repo",
|
||||
|
|
|
@ -2,19 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var repoAddCmd = cli.Command{
|
||||
Name: "add",
|
||||
Usage: "add a repository",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := repoAdd(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: repoAdd,
|
||||
}
|
||||
|
||||
func repoAdd(c *cli.Context) error {
|
||||
|
|
|
@ -2,19 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var repoChownCmd = cli.Command{
|
||||
Name: "chown",
|
||||
Usage: "assume ownership of a repository",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := repoChown(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: repoChown,
|
||||
}
|
||||
|
||||
func repoChown(c *cli.Context) error {
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var repoInfoCmd = cli.Command{
|
||||
Name: "info",
|
||||
Usage: "show repository details",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := repoInfo(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: repoInfo,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var repoListCmd = cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "list all repos",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := repoList(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: repoList,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -2,19 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var repoRemoveCmd = cli.Command{
|
||||
Name: "rm",
|
||||
Usage: "remove a repository",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := repoRemove(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: repoRemove,
|
||||
}
|
||||
|
||||
func repoRemove(c *cli.Context) error {
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var secretCmd = cli.Command{
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var secretAddCmd = cli.Command{
|
||||
Name: "add",
|
||||
Usage: "adds a secret",
|
||||
ArgsUsage: "[repo] [key] [value]",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := secretAdd(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: secretAdd,
|
||||
Flags: secretAddFlags(),
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var secretListCmd = cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "list all secrets",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := secretList(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: secretList,
|
||||
Flags: secretListFlags(),
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var secretRemoveCmd = cli.Command{
|
||||
Name: "rm",
|
||||
Usage: "remove a secret",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := secretRemove(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: secretRemove,
|
||||
}
|
||||
|
||||
func secretRemove(c *cli.Context) error {
|
||||
|
|
|
@ -8,18 +8,14 @@ import (
|
|||
"github.com/drone/drone/router/middleware"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/gin-gonic/contrib/ginrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var serverCmd = cli.Command{
|
||||
Name: "server",
|
||||
Usage: "starts the drone server daemon",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := server(c); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
},
|
||||
Action: server,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
EnvVar: "DRONE_DEBUG",
|
||||
|
@ -301,8 +297,6 @@ func server(c *cli.Context) error {
|
|||
middleware.Cache(c),
|
||||
middleware.Store(c),
|
||||
middleware.Remote(c),
|
||||
middleware.Agents(c),
|
||||
middleware.Broker(c),
|
||||
)
|
||||
|
||||
// start the server with tls enabled
|
||||
|
|
|
@ -2,19 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var signCmd = cli.Command{
|
||||
Name: "sign",
|
||||
Usage: "creates a secure yaml file",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := sign(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: sign,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "in",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package main
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var userCmd = cli.Command{
|
||||
Name: "user",
|
||||
|
|
|
@ -2,20 +2,15 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var userAddCmd = cli.Command{
|
||||
Name: "add",
|
||||
Usage: "adds a user",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := userAdd(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: userAdd,
|
||||
}
|
||||
|
||||
func userAdd(c *cli.Context) error {
|
||||
|
|
|
@ -2,21 +2,16 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var userInfoCmd = cli.Command{
|
||||
Name: "info",
|
||||
Usage: "show user details",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := userInfo(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: userInfo,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var userListCmd = cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "list all users",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := userList(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: userList,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -2,19 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var userRemoveCmd = cli.Command{
|
||||
Name: "rm",
|
||||
Usage: "remove a user",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := userRemove(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Action: userRemove,
|
||||
}
|
||||
|
||||
func userRemove(c *cli.Context) error {
|
||||
|
|
|
@ -9,8 +9,8 @@ import (
|
|||
|
||||
"github.com/drone/drone/client"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/jackspirou/syscerts"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func newClient(c *cli.Context) (client.Client, error) {
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/shared/token"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const agentKey = "agent"
|
||||
|
||||
// Agents is a middleware function that initializes the authorization middleware
|
||||
// for agents to connect to the queue.
|
||||
func Agents(cli *cli.Context) gin.HandlerFunc {
|
||||
secret := cli.String("agent-secret")
|
||||
if secret == "" {
|
||||
logrus.Fatalf("failed to generate token from DRONE_AGENT_SECRET")
|
||||
}
|
||||
|
||||
t := token.New(token.AgentToken, secret)
|
||||
s, err := t.Sign(secret)
|
||||
if err != nil {
|
||||
logrus.Fatalf("failed to generate token from DRONE_AGENT_SECRET. %s", err)
|
||||
}
|
||||
|
||||
logrus.Infof("using agent secret %s", secret)
|
||||
logrus.Warnf("agents can connect with token %s", s)
|
||||
|
||||
return func(c *gin.Context) {
|
||||
c.Set(agentKey, secret)
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
handlers "github.com/drone/drone/server"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/mq/logger"
|
||||
"github.com/drone/mq/server"
|
||||
"github.com/drone/mq/stomp"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/tidwall/redlog"
|
||||
)
|
||||
|
||||
const (
|
||||
serverKey = "broker"
|
||||
clientKey = "stomp.client" // mirrored from stomp/context
|
||||
)
|
||||
|
||||
// Broker is a middleware function that initializes the broker
|
||||
// and adds the broker client to the request context.
|
||||
func Broker(cli *cli.Context) gin.HandlerFunc {
|
||||
secret := cli.String("agent-secret")
|
||||
if secret == "" {
|
||||
logrus.Fatalf("fatal error. please provide the DRONE_SECRET")
|
||||
}
|
||||
|
||||
// setup broker logging.
|
||||
log := redlog.New(os.Stderr)
|
||||
log.SetLevel(2)
|
||||
logger.SetLogger(log)
|
||||
if cli.Bool("broker-debug") {
|
||||
log.SetLevel(1)
|
||||
}
|
||||
|
||||
broker := server.NewServer(
|
||||
server.WithCredentials("x-token", secret),
|
||||
)
|
||||
client := broker.Client()
|
||||
|
||||
var once sync.Once
|
||||
return func(c *gin.Context) {
|
||||
c.Set(serverKey, broker)
|
||||
c.Set(clientKey, client)
|
||||
once.Do(func() {
|
||||
// this is some really hacky stuff
|
||||
// turns out I need to do some refactoring
|
||||
// don't judge!
|
||||
// will fix in 0.6 release
|
||||
ctx := c.Copy()
|
||||
client.Connect(
|
||||
stomp.WithCredentials("x-token", secret),
|
||||
)
|
||||
client.Subscribe("/queue/updates", stomp.HandlerFunc(func(m *stomp.Message) {
|
||||
go handlers.HandleUpdate(ctx, m.Copy())
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
|
@ -3,8 +3,8 @@ package middleware
|
|||
import (
|
||||
"github.com/drone/drone/cache"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Cache is a middleware function that initializes the Cache and attaches to
|
||||
|
|
|
@ -3,8 +3,8 @@ package middleware
|
|||
import (
|
||||
"github.com/drone/drone/model"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const configKey = "config"
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/remote"
|
||||
"github.com/drone/drone/remote/bitbucket"
|
||||
"github.com/drone/drone/remote/bitbucketserver"
|
||||
|
@ -12,6 +11,7 @@ import (
|
|||
"github.com/drone/drone/remote/gitlab"
|
||||
"github.com/drone/drone/remote/gogs"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Remote is a middleware function that initializes the Remote and attaches to
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/store"
|
||||
"github.com/drone/drone/store/datastore"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
|
|
@ -2,7 +2,6 @@ package router
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
|
@ -41,8 +40,6 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
|
|||
e.GET("/logout", server.GetLogout)
|
||||
e.NoRoute(server.ShowIndex)
|
||||
|
||||
// TODO above will Go away with React UI
|
||||
|
||||
user := e.Group("/api/user")
|
||||
{
|
||||
user.Use(session.MustUser())
|
||||
|
@ -121,18 +118,13 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
|
|||
badges.GET("/cc.xml", server.GetCC)
|
||||
}
|
||||
|
||||
if os.Getenv("DRONE_CANARY") == "" {
|
||||
e.POST("/hook", server.PostHook)
|
||||
e.POST("/api/hook", server.PostHook)
|
||||
} else {
|
||||
e.POST("/hook", server.PostHook2)
|
||||
e.POST("/api/hook", server.PostHook2)
|
||||
}
|
||||
|
||||
if os.Getenv("DRONE_CANARY") == "" {
|
||||
ws := e.Group("/ws")
|
||||
{
|
||||
ws.GET("/broker", server.Broker)
|
||||
ws.GET("/broker", server.RPCHandler)
|
||||
ws.GET("/rpc", server.RPCHandler)
|
||||
ws.GET("/feed", server.EventStream)
|
||||
ws.GET("/logs/:owner/:name/:build/:number",
|
||||
session.SetRepo(),
|
||||
|
@ -141,19 +133,7 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
|
|||
server.LogStream,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
ws := e.Group("/ws")
|
||||
{
|
||||
ws.GET("/broker", server.RPCHandler)
|
||||
ws.GET("/rpc", server.RPCHandler)
|
||||
ws.GET("/feed", server.EventStream2)
|
||||
ws.GET("/logs/:owner/:name/:build/:number",
|
||||
session.SetRepo(),
|
||||
session.SetPerm(),
|
||||
session.MustPull,
|
||||
server.LogStream2,
|
||||
)
|
||||
}
|
||||
|
||||
info := e.Group("/api/info")
|
||||
{
|
||||
info.GET("/queue",
|
||||
|
@ -161,7 +141,6 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
|
|||
server.GetQueueInfo,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
auth := e.Group("/authorize")
|
||||
{
|
||||
|
@ -191,12 +170,5 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
|
|||
debugger.GET("/pprof/trace", debug.TraceHandler())
|
||||
}
|
||||
|
||||
// bots := e.Group("/bots")
|
||||
// {
|
||||
// bots.Use(session.MustUser())
|
||||
// bots.POST("/slack", Slack)
|
||||
// bots.POST("/slack/:command", Slack)
|
||||
// }
|
||||
|
||||
return e
|
||||
}
|
||||
|
|
225
server/build.go
225
server/build.go
|
@ -7,7 +7,6 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -18,13 +17,10 @@ import (
|
|||
"github.com/drone/drone/remote"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/store"
|
||||
"github.com/drone/drone/yaml"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/square/go-jose"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/router/middleware/session"
|
||||
"github.com/drone/mq/stomp"
|
||||
)
|
||||
|
||||
func GetBuilds(c *gin.Context) {
|
||||
|
@ -156,229 +152,10 @@ func DeleteBuild(c *gin.Context) {
|
|||
job.ExitCode = 137
|
||||
store.UpdateBuildJob(c, build, job)
|
||||
|
||||
if os.Getenv("DRONE_CANARY") == "" {
|
||||
client := stomp.MustFromContext(c)
|
||||
client.SendJSON("/topic/cancel", model.Event{
|
||||
Type: model.Cancelled,
|
||||
Repo: *repo,
|
||||
Build: *build,
|
||||
Job: *job,
|
||||
}, stomp.WithHeader("job-id", strconv.FormatInt(job.ID, 10)))
|
||||
} else {
|
||||
config.queue.Error(context.Background(), fmt.Sprint(job.ID), queue.ErrCancel)
|
||||
}
|
||||
c.String(204, "")
|
||||
}
|
||||
|
||||
func PostBuild(c *gin.Context) {
|
||||
|
||||
if os.Getenv("DRONE_CANARY") == "true" {
|
||||
PostBuild2(c)
|
||||
return
|
||||
}
|
||||
|
||||
remote_ := remote.FromContext(c)
|
||||
repo := session.Repo(c)
|
||||
fork := c.DefaultQuery("fork", "false")
|
||||
|
||||
num, err := strconv.Atoi(c.Param("number"))
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := store.GetUser(c, repo.UserID)
|
||||
if err != nil {
|
||||
log.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
build, err := store.GetBuildNumber(c, repo, num)
|
||||
if err != nil {
|
||||
log.Errorf("failure to get build %d. %s", num, err)
|
||||
c.AbortWithError(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
// if the remote has a refresh token, the current access token
|
||||
// may be stale. Therefore, we should refresh prior to dispatching
|
||||
// the job.
|
||||
if refresher, ok := remote_.(remote.Refresher); ok {
|
||||
ok, _ := refresher.Refresh(user)
|
||||
if ok {
|
||||
store.UpdateUser(c, user)
|
||||
}
|
||||
}
|
||||
|
||||
// fetch the .drone.yml file from the database
|
||||
cfg := ToConfig(c)
|
||||
raw, err := remote_.File(user, repo, build, cfg.Yaml)
|
||||
if err != nil {
|
||||
log.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch secrets file but don't exit on error as it's optional
|
||||
sec, err := remote_.File(user, repo, build, cfg.Shasum)
|
||||
if err != nil {
|
||||
log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err)
|
||||
}
|
||||
|
||||
netrc, err := remote_.Netrc(user, repo)
|
||||
if err != nil {
|
||||
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
jobs, err := store.GetJobList(c, build)
|
||||
if err != nil {
|
||||
log.Errorf("failure to get build %d jobs. %s", build.Number, err)
|
||||
c.AbortWithError(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
// must not restart a running build
|
||||
if build.Status == model.StatusPending || build.Status == model.StatusRunning {
|
||||
c.String(409, "Cannot re-start a started build")
|
||||
return
|
||||
}
|
||||
|
||||
// forking the build creates a duplicate of the build
|
||||
// and then executes. This retains prior build history.
|
||||
if forkit, _ := strconv.ParseBool(fork); forkit {
|
||||
build.ID = 0
|
||||
build.Number = 0
|
||||
build.Parent = num
|
||||
for _, job := range jobs {
|
||||
job.ID = 0
|
||||
job.NodeID = 0
|
||||
}
|
||||
err := store.CreateBuild(c, build, jobs...)
|
||||
if err != nil {
|
||||
c.String(500, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
event := c.DefaultQuery("event", build.Event)
|
||||
if event == model.EventPush ||
|
||||
event == model.EventPull ||
|
||||
event == model.EventTag ||
|
||||
event == model.EventDeploy {
|
||||
build.Event = event
|
||||
}
|
||||
build.Deploy = c.DefaultQuery("deploy_to", build.Deploy)
|
||||
}
|
||||
|
||||
// Read query string parameters into buildParams, exclude reserved params
|
||||
var buildParams = map[string]string{}
|
||||
for key, val := range c.Request.URL.Query() {
|
||||
switch key {
|
||||
case "fork", "event", "deploy_to":
|
||||
default:
|
||||
// We only accept string literals, because build parameters will be
|
||||
// injected as environment variables
|
||||
buildParams[key] = val[0]
|
||||
}
|
||||
}
|
||||
|
||||
// todo move this to database tier
|
||||
// and wrap inside a transaction
|
||||
build.Status = model.StatusPending
|
||||
build.Started = 0
|
||||
build.Finished = 0
|
||||
build.Enqueued = time.Now().UTC().Unix()
|
||||
build.Error = ""
|
||||
for _, job := range jobs {
|
||||
for k, v := range buildParams {
|
||||
job.Environment[k] = v
|
||||
}
|
||||
job.Error = ""
|
||||
job.Status = model.StatusPending
|
||||
job.Started = 0
|
||||
job.Finished = 0
|
||||
job.ExitCode = 0
|
||||
job.NodeID = 0
|
||||
job.Enqueued = build.Enqueued
|
||||
store.UpdateJob(c, job)
|
||||
}
|
||||
|
||||
err = store.UpdateBuild(c, build)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(500)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(202, build)
|
||||
|
||||
// get the previous build so that we can send
|
||||
// on status change notifications
|
||||
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
|
||||
secs, err := store.GetMergedSecretList(c, repo)
|
||||
if err != nil {
|
||||
log.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
|
||||
}
|
||||
|
||||
var signed bool
|
||||
var verified bool
|
||||
|
||||
signature, err := jose.ParseSigned(string(sec))
|
||||
if err != nil {
|
||||
log.Debugf("cannot parse .drone.yml.sig file. %s", err)
|
||||
} else if len(sec) == 0 {
|
||||
log.Debugf("cannot parse .drone.yml.sig file. empty file")
|
||||
} else {
|
||||
signed = true
|
||||
output, err := signature.Verify([]byte(repo.Hash))
|
||||
if err != nil {
|
||||
log.Debugf("cannot verify .drone.yml.sig file. %s", err)
|
||||
} else if string(output) != string(raw) {
|
||||
log.Debugf("cannot verify .drone.yml.sig file. no match. %q <> %q", string(output), string(raw))
|
||||
} else {
|
||||
verified = true
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf(".drone.yml is signed=%v and verified=%v", signed, verified)
|
||||
|
||||
client := stomp.MustFromContext(c)
|
||||
client.SendJSON("/topic/events", model.Event{
|
||||
Type: model.Enqueued,
|
||||
Repo: *repo,
|
||||
Build: *build,
|
||||
},
|
||||
stomp.WithHeader("repo", repo.FullName),
|
||||
stomp.WithHeader("private", strconv.FormatBool(repo.IsPrivate)),
|
||||
)
|
||||
|
||||
for _, job := range jobs {
|
||||
broker, _ := stomp.FromContext(c)
|
||||
broker.SendJSON("/queue/pending", &model.Work{
|
||||
Signed: signed,
|
||||
Verified: verified,
|
||||
User: user,
|
||||
Repo: repo,
|
||||
Build: build,
|
||||
BuildLast: last,
|
||||
Job: job,
|
||||
Netrc: netrc,
|
||||
Yaml: string(raw),
|
||||
Secrets: secs,
|
||||
System: &model.System{Link: httputil.GetURL(c.Request)},
|
||||
},
|
||||
stomp.WithHeader(
|
||||
"platform",
|
||||
yaml.ParsePlatformDefault(raw, "linux/amd64"),
|
||||
),
|
||||
stomp.WithHeaders(
|
||||
yaml.ParseLabel(raw),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func GetBuildQueue(c *gin.Context) {
|
||||
out, err := store.GetBuildQueue(c)
|
||||
if err != nil {
|
||||
|
@ -412,7 +189,7 @@ func copyLogs(dest io.Writer, src io.Reader) error {
|
|||
//
|
||||
//
|
||||
|
||||
func PostBuild2(c *gin.Context) {
|
||||
func PostBuild(c *gin.Context) {
|
||||
|
||||
remote_ := remote.FromContext(c)
|
||||
repo := session.Repo(c)
|
||||
|
|
436
server/hook.go
436
server/hook.go
|
@ -1,28 +1,268 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/square/go-jose"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/remote"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/shared/token"
|
||||
"github.com/drone/drone/store"
|
||||
"github.com/drone/drone/yaml"
|
||||
"github.com/drone/mq/stomp"
|
||||
"github.com/drone/envsubst"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
"github.com/cncd/pipeline/pipeline/frontend"
|
||||
"github.com/cncd/pipeline/pipeline/frontend/yaml"
|
||||
"github.com/cncd/pipeline/pipeline/frontend/yaml/compiler"
|
||||
"github.com/cncd/pipeline/pipeline/frontend/yaml/linter"
|
||||
"github.com/cncd/pipeline/pipeline/frontend/yaml/matrix"
|
||||
"github.com/cncd/pipeline/pipeline/rpc"
|
||||
"github.com/cncd/pubsub"
|
||||
"github.com/cncd/queue"
|
||||
)
|
||||
|
||||
//
|
||||
// CANARY IMPLEMENTATION
|
||||
//
|
||||
// This file is a complete disaster because I'm trying to wedge in some
|
||||
// experimental code. Please pardon our appearance during renovations.
|
||||
//
|
||||
|
||||
var skipRe = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`)
|
||||
|
||||
func GetQueueInfo(c *gin.Context) {
|
||||
c.IndentedJSON(200,
|
||||
config.queue.Info(c),
|
||||
)
|
||||
}
|
||||
|
||||
// return the metadata from the cli context.
|
||||
func metadataFromStruct(repo *model.Repo, build, last *model.Build, job *model.Job, link string) frontend.Metadata {
|
||||
return frontend.Metadata{
|
||||
Repo: frontend.Repo{
|
||||
Name: repo.Name,
|
||||
Link: repo.Link,
|
||||
Remote: repo.Clone,
|
||||
Private: repo.IsPrivate,
|
||||
},
|
||||
Curr: frontend.Build{
|
||||
Number: build.Number,
|
||||
Created: build.Created,
|
||||
Started: build.Started,
|
||||
Finished: build.Finished,
|
||||
Status: build.Status,
|
||||
Event: build.Event,
|
||||
Link: build.Link,
|
||||
Target: build.Deploy,
|
||||
Commit: frontend.Commit{
|
||||
Sha: build.Commit,
|
||||
Ref: build.Ref,
|
||||
Refspec: build.Refspec,
|
||||
Branch: build.Branch,
|
||||
Message: build.Message,
|
||||
Author: frontend.Author{
|
||||
Name: build.Author,
|
||||
Email: build.Email,
|
||||
Avatar: build.Avatar,
|
||||
},
|
||||
},
|
||||
},
|
||||
Prev: frontend.Build{
|
||||
Number: last.Number,
|
||||
Created: last.Created,
|
||||
Started: last.Started,
|
||||
Finished: last.Finished,
|
||||
Status: last.Status,
|
||||
Event: last.Event,
|
||||
Link: last.Link,
|
||||
Target: last.Deploy,
|
||||
Commit: frontend.Commit{
|
||||
Sha: last.Commit,
|
||||
Ref: last.Ref,
|
||||
Refspec: last.Refspec,
|
||||
Branch: last.Branch,
|
||||
Message: last.Message,
|
||||
Author: frontend.Author{
|
||||
Name: last.Author,
|
||||
Email: last.Email,
|
||||
Avatar: last.Avatar,
|
||||
},
|
||||
},
|
||||
},
|
||||
Job: frontend.Job{
|
||||
Number: job.Number,
|
||||
Matrix: job.Environment,
|
||||
},
|
||||
Sys: frontend.System{
|
||||
Name: "drone",
|
||||
Link: link,
|
||||
Arch: "linux/amd64",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type builder struct {
|
||||
Repo *model.Repo
|
||||
Curr *model.Build
|
||||
Last *model.Build
|
||||
Netrc *model.Netrc
|
||||
Secs []*model.Secret
|
||||
Link string
|
||||
Yaml string
|
||||
}
|
||||
|
||||
type buildItem struct {
|
||||
Job *model.Job
|
||||
Platform string
|
||||
Labels map[string]string
|
||||
Config *backend.Config
|
||||
}
|
||||
|
||||
func (b *builder) Build() ([]*buildItem, error) {
|
||||
|
||||
axes, err := matrix.ParseString(b.Yaml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(axes) == 0 {
|
||||
axes = append(axes, matrix.Axis{})
|
||||
}
|
||||
|
||||
var items []*buildItem
|
||||
for i, axis := range axes {
|
||||
job := &model.Job{
|
||||
BuildID: b.Curr.ID,
|
||||
Number: i + 1,
|
||||
Status: model.StatusPending,
|
||||
Environment: axis,
|
||||
Enqueued: b.Curr.Created,
|
||||
}
|
||||
|
||||
metadata := metadataFromStruct(b.Repo, b.Curr, b.Last, job, b.Link)
|
||||
environ := metadata.Environ()
|
||||
for k, v := range metadata.EnvironDrone() {
|
||||
environ[k] = v
|
||||
}
|
||||
|
||||
secrets := map[string]string{}
|
||||
for _, sec := range b.Secs {
|
||||
if !sec.MatchEvent(b.Curr.Event) {
|
||||
continue
|
||||
}
|
||||
if b.Curr.Verified || sec.SkipVerify {
|
||||
secrets[sec.Name] = sec.Value
|
||||
}
|
||||
}
|
||||
sub := func(name string) string {
|
||||
if v, ok := environ[name]; ok {
|
||||
return v
|
||||
}
|
||||
return secrets[name]
|
||||
}
|
||||
|
||||
y := b.Yaml
|
||||
if s, err := envsubst.Eval(y, sub); err != nil {
|
||||
y = s
|
||||
}
|
||||
|
||||
parsed, err := yaml.ParseString(y)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata.Sys.Arch = parsed.Platform
|
||||
if metadata.Sys.Arch == "" {
|
||||
metadata.Sys.Arch = "linux/amd64"
|
||||
}
|
||||
|
||||
lerr := linter.New(
|
||||
linter.WithTrusted(b.Repo.IsTrusted),
|
||||
).Lint(parsed)
|
||||
if lerr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ir := compiler.New(
|
||||
compiler.WithEnviron(environ),
|
||||
// TODO ability to customize the escalated plugins
|
||||
compiler.WithEscalated("plugins/docker", "plugins/gcr", "plugins/ecr"),
|
||||
compiler.WithLocal(false),
|
||||
compiler.WithNetrc(b.Netrc.Login, b.Netrc.Password, b.Netrc.Machine),
|
||||
compiler.WithPrefix(
|
||||
fmt.Sprintf(
|
||||
"%d_%d",
|
||||
job.ID,
|
||||
time.Now().Unix(),
|
||||
),
|
||||
),
|
||||
compiler.WithEnviron(job.Environment),
|
||||
compiler.WithProxy(),
|
||||
// TODO ability to set global volumes for things like certs
|
||||
compiler.WithVolumes(),
|
||||
compiler.WithWorkspaceFromURL("/drone", b.Curr.Link),
|
||||
).Compile(parsed)
|
||||
|
||||
for _, sec := range b.Secs {
|
||||
if !sec.MatchEvent(b.Curr.Event) {
|
||||
continue
|
||||
}
|
||||
if b.Curr.Verified || sec.SkipVerify {
|
||||
ir.Secrets = append(ir.Secrets, &backend.Secret{
|
||||
Mask: sec.Conceal,
|
||||
Name: sec.Name,
|
||||
Value: sec.Value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
item := &buildItem{
|
||||
Job: job,
|
||||
Config: ir,
|
||||
Labels: parsed.Labels,
|
||||
Platform: metadata.Sys.Arch,
|
||||
}
|
||||
if item.Labels == nil {
|
||||
item.Labels = map[string]string{}
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
func PostHook(c *gin.Context) {
|
||||
remote_ := remote.FromContext(c)
|
||||
|
||||
tmprepo, build, err := remote_.Hook(c.Request)
|
||||
if err != nil {
|
||||
log.Errorf("failure to parse hook. %s", err)
|
||||
logrus.Errorf("failure to parse hook. %s", err)
|
||||
c.AbortWithError(400, err)
|
||||
return
|
||||
}
|
||||
|
@ -31,7 +271,7 @@ func PostHook(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
if tmprepo == nil {
|
||||
log.Errorf("failure to ascertain repo from hook.")
|
||||
logrus.Errorf("failure to ascertain repo from hook.")
|
||||
c.Writer.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
|
@ -40,14 +280,14 @@ func PostHook(c *gin.Context) {
|
|||
// wrapped in square brackets appear in the commit message
|
||||
skipMatch := skipRe.FindString(build.Message)
|
||||
if len(skipMatch) > 0 {
|
||||
log.Infof("ignoring hook. %s found in %s", skipMatch, build.Commit)
|
||||
logrus.Infof("ignoring hook. %s found in %s", skipMatch, build.Commit)
|
||||
c.Writer.WriteHeader(204)
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := store.GetRepoOwnerName(c, tmprepo.Owner, tmprepo.Name)
|
||||
if err != nil {
|
||||
log.Errorf("failure to find repo %s/%s from hook. %s", tmprepo.Owner, tmprepo.Name, err)
|
||||
logrus.Errorf("failure to find repo %s/%s from hook. %s", tmprepo.Owner, tmprepo.Name, err)
|
||||
c.AbortWithError(404, err)
|
||||
return
|
||||
}
|
||||
|
@ -57,18 +297,18 @@ func PostHook(c *gin.Context) {
|
|||
return repo.Hash, nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
|
||||
logrus.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(400, err)
|
||||
return
|
||||
}
|
||||
if parsed.Text != repo.FullName {
|
||||
log.Errorf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text)
|
||||
logrus.Errorf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text)
|
||||
c.AbortWithStatus(403)
|
||||
return
|
||||
}
|
||||
|
||||
if repo.UserID == 0 {
|
||||
log.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
|
||||
logrus.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
|
||||
c.Writer.WriteHeader(204)
|
||||
return
|
||||
}
|
||||
|
@ -81,33 +321,18 @@ func PostHook(c *gin.Context) {
|
|||
}
|
||||
|
||||
if skipped {
|
||||
log.Infof("ignoring hook. repo %s is disabled for %s events.", repo.FullName, build.Event)
|
||||
logrus.Infof("ignoring hook. repo %s is disabled for %s events.", repo.FullName, build.Event)
|
||||
c.Writer.WriteHeader(204)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := store.GetUser(c, repo.UserID)
|
||||
if err != nil {
|
||||
log.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
||||
logrus.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
// if there is no email address associated with the pull request,
|
||||
// we lookup the email address based on the authors github login.
|
||||
//
|
||||
// my initial hesitation with this code is that it has the ability
|
||||
// to expose your email address. At the same time, your email address
|
||||
// is already exposed in the public .git log. So while some people will
|
||||
// a small number of people will probably be upset by this, I'm not sure
|
||||
// it is actually that big of a deal.
|
||||
if len(build.Email) == 0 {
|
||||
author, uerr := store.GetUserLogin(c, build.Author)
|
||||
if uerr == nil {
|
||||
build.Email = author.Email
|
||||
}
|
||||
}
|
||||
|
||||
// if the remote has a refresh token, the current access token
|
||||
// may be stale. Therefore, we should refresh prior to dispatching
|
||||
// the job.
|
||||
|
@ -119,26 +344,16 @@ func PostHook(c *gin.Context) {
|
|||
}
|
||||
|
||||
// fetch the build file from the database
|
||||
config := ToConfig(c)
|
||||
raw, err := remote_.File(user, repo, build, config.Yaml)
|
||||
cfg := ToConfig(c)
|
||||
raw, err := remote_.File(user, repo, build, cfg.Yaml)
|
||||
if err != nil {
|
||||
log.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
||||
logrus.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(404, err)
|
||||
return
|
||||
}
|
||||
sec, err := remote_.File(user, repo, build, config.Shasum)
|
||||
sec, err := remote_.File(user, repo, build, cfg.Shasum)
|
||||
if err != nil {
|
||||
log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err)
|
||||
// NOTE we don't exit on failure. The sec file is optional
|
||||
}
|
||||
|
||||
axes, err := yaml.ParseMatrix(raw)
|
||||
if err != nil {
|
||||
c.String(500, "Failed to parse yaml file or calculate matrix. %s", err)
|
||||
return
|
||||
}
|
||||
if len(axes) == 0 {
|
||||
axes = append(axes, yaml.Axis{})
|
||||
logrus.Debugf("cannot find yaml signature for %s. %s", repo.FullName, err)
|
||||
}
|
||||
|
||||
netrc, err := remote_.Netrc(user, repo)
|
||||
|
@ -148,24 +363,28 @@ func PostHook(c *gin.Context) {
|
|||
}
|
||||
|
||||
// verify the branches can be built vs skipped
|
||||
branches := yaml.ParseBranch(raw)
|
||||
if !branches.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
|
||||
branches, err := yaml.ParseBytes(raw)
|
||||
if err != nil {
|
||||
c.String(500, "Failed to parse yaml file. %s", err)
|
||||
return
|
||||
}
|
||||
if !branches.Branches.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
|
||||
c.String(200, "Branch does not match restrictions defined in yaml")
|
||||
return
|
||||
}
|
||||
|
||||
signature, err := jose.ParseSigned(string(sec))
|
||||
if err != nil {
|
||||
log.Debugf("cannot parse .drone.yml.sig file. %s", err)
|
||||
logrus.Debugf("cannot parse .drone.yml.sig file. %s", err)
|
||||
} else if len(sec) == 0 {
|
||||
log.Debugf("cannot parse .drone.yml.sig file. empty file")
|
||||
logrus.Debugf("cannot parse .drone.yml.sig file. empty file")
|
||||
} else {
|
||||
build.Signed = true
|
||||
output, verr := signature.Verify([]byte(repo.Hash))
|
||||
if verr != nil {
|
||||
log.Debugf("cannot verify .drone.yml.sig file. %s", verr)
|
||||
logrus.Debugf("cannot verify .drone.yml.sig file. %s", verr)
|
||||
} else if string(output) != string(raw) {
|
||||
log.Debugf("cannot verify .drone.yml.sig file. no match")
|
||||
logrus.Debugf("cannot verify .drone.yml.sig file. no match")
|
||||
} else {
|
||||
build.Verified = true
|
||||
}
|
||||
|
@ -175,71 +394,94 @@ func PostHook(c *gin.Context) {
|
|||
build.Status = model.StatusPending
|
||||
build.RepoID = repo.ID
|
||||
|
||||
// and use a transaction
|
||||
var jobs []*model.Job
|
||||
for num, axis := range axes {
|
||||
jobs = append(jobs, &model.Job{
|
||||
BuildID: build.ID,
|
||||
Number: num + 1,
|
||||
Status: model.StatusPending,
|
||||
Environment: axis,
|
||||
})
|
||||
}
|
||||
err = store.CreateBuild(c, build, jobs...)
|
||||
if err != nil {
|
||||
log.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
||||
if err := store.CreateBuild(c, build, build.Jobs...); err != nil {
|
||||
logrus.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, build)
|
||||
|
||||
url := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
||||
err = remote_.Status(user, repo, build, url)
|
||||
if err != nil {
|
||||
log.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
|
||||
}
|
||||
|
||||
// get the previous build so that we can send
|
||||
// on status change notifications
|
||||
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
|
||||
secs, err := store.GetMergedSecretList(c, repo)
|
||||
if err != nil {
|
||||
log.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
|
||||
logrus.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
|
||||
}
|
||||
|
||||
client := stomp.MustFromContext(c)
|
||||
client.SendJSON("topic/events", model.Event{
|
||||
//
|
||||
// BELOW: NEW
|
||||
//
|
||||
|
||||
defer func() {
|
||||
uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
||||
err = remote_.Status(user, repo, build, uri)
|
||||
if err != nil {
|
||||
logrus.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
|
||||
}
|
||||
}()
|
||||
|
||||
b := builder{
|
||||
Repo: repo,
|
||||
Curr: build,
|
||||
Last: last,
|
||||
Netrc: netrc,
|
||||
Secs: secs,
|
||||
Link: httputil.GetURL(c.Request),
|
||||
Yaml: string(raw),
|
||||
}
|
||||
items, err := b.Build()
|
||||
if err != nil {
|
||||
build.Status = model.StatusError
|
||||
build.Started = time.Now().Unix()
|
||||
build.Finished = build.Started
|
||||
build.Error = err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
build.Jobs = append(build.Jobs, item.Job)
|
||||
store.CreateJob(c, item.Job)
|
||||
// TODO err
|
||||
}
|
||||
|
||||
//
|
||||
// publish topic
|
||||
//
|
||||
message := pubsub.Message{
|
||||
Labels: map[string]string{
|
||||
"repo": repo.FullName,
|
||||
"private": strconv.FormatBool(repo.IsPrivate),
|
||||
},
|
||||
}
|
||||
message.Data, _ = json.Marshal(model.Event{
|
||||
Type: model.Enqueued,
|
||||
Repo: *repo,
|
||||
Build: *build,
|
||||
},
|
||||
stomp.WithHeader("repo", repo.FullName),
|
||||
stomp.WithHeader("private", strconv.FormatBool(repo.IsPrivate)),
|
||||
)
|
||||
})
|
||||
// TODO remove global reference
|
||||
config.pubsub.Publish(c, "topic/events", message)
|
||||
//
|
||||
// end publish topic
|
||||
//
|
||||
|
||||
for _, job := range jobs {
|
||||
broker, _ := stomp.FromContext(c)
|
||||
broker.SendJSON("/queue/pending", &model.Work{
|
||||
Signed: build.Signed,
|
||||
Verified: build.Verified,
|
||||
User: user,
|
||||
Repo: repo,
|
||||
Build: build,
|
||||
BuildLast: last,
|
||||
Job: job,
|
||||
Netrc: netrc,
|
||||
Yaml: string(raw),
|
||||
Secrets: secs,
|
||||
System: &model.System{Link: httputil.GetURL(c.Request)},
|
||||
},
|
||||
stomp.WithHeader(
|
||||
"platform",
|
||||
yaml.ParsePlatformDefault(raw, "linux/amd64"),
|
||||
),
|
||||
stomp.WithHeaders(
|
||||
yaml.ParseLabel(raw),
|
||||
),
|
||||
)
|
||||
for _, item := range items {
|
||||
task := new(queue.Task)
|
||||
task.ID = fmt.Sprint(item.Job.ID)
|
||||
task.Labels = map[string]string{}
|
||||
task.Labels["platform"] = item.Platform
|
||||
for k, v := range item.Labels {
|
||||
task.Labels[k] = v
|
||||
}
|
||||
|
||||
task.Data, _ = json.Marshal(rpc.Pipeline{
|
||||
ID: fmt.Sprint(item.Job.ID),
|
||||
Config: item.Config,
|
||||
Timeout: b.Repo.Timeout,
|
||||
})
|
||||
|
||||
config.logger.Open(context.Background(), task.ID)
|
||||
config.queue.Push(context.Background(), task)
|
||||
}
|
||||
}
|
||||
|
|
869
server/hook2.go
869
server/hook2.go
|
@ -1,869 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/square/go-jose"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/remote"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/shared/token"
|
||||
"github.com/drone/drone/store"
|
||||
"github.com/drone/envsubst"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
"github.com/cncd/pipeline/pipeline/frontend"
|
||||
"github.com/cncd/pipeline/pipeline/frontend/yaml"
|
||||
"github.com/cncd/pipeline/pipeline/frontend/yaml/compiler"
|
||||
"github.com/cncd/pipeline/pipeline/frontend/yaml/linter"
|
||||
"github.com/cncd/pipeline/pipeline/frontend/yaml/matrix"
|
||||
"github.com/cncd/pipeline/pipeline/rpc"
|
||||
"github.com/cncd/pubsub"
|
||||
"github.com/cncd/queue"
|
||||
)
|
||||
|
||||
//
|
||||
// CANARY IMPLEMENTATION
|
||||
//
|
||||
// This file is a complete disaster because I'm trying to wedge in some
|
||||
// experimental code. Please pardon our appearance during renovations.
|
||||
//
|
||||
|
||||
var skipRe = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`)
|
||||
|
||||
func GetQueueInfo(c *gin.Context) {
|
||||
c.IndentedJSON(200,
|
||||
config.queue.Info(c),
|
||||
)
|
||||
}
|
||||
|
||||
// func PostHookOld(c *gin.Context) {
|
||||
// remote_ := remote.FromContext(c)
|
||||
//
|
||||
// tmprepo, build, err := remote_.Hook(c.Request)
|
||||
// if err != nil {
|
||||
// logrus.Errorf("failure to parse hook. %s", err)
|
||||
// c.AbortWithError(400, err)
|
||||
// return
|
||||
// }
|
||||
// if build == nil {
|
||||
// c.Writer.WriteHeader(200)
|
||||
// return
|
||||
// }
|
||||
// if tmprepo == nil {
|
||||
// logrus.Errorf("failure to ascertain repo from hook.")
|
||||
// c.Writer.WriteHeader(400)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // skip the build if any case-insensitive combination of the words "skip" and "ci"
|
||||
// // wrapped in square brackets appear in the commit message
|
||||
// skipMatch := skipRe.FindString(build.Message)
|
||||
// if len(skipMatch) > 0 {
|
||||
// logrus.Infof("ignoring hook. %s found in %s", skipMatch, build.Commit)
|
||||
// c.Writer.WriteHeader(204)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// repo, err := store.GetRepoOwnerName(c, tmprepo.Owner, tmprepo.Name)
|
||||
// if err != nil {
|
||||
// logrus.Errorf("failure to find repo %s/%s from hook. %s", tmprepo.Owner, tmprepo.Name, err)
|
||||
// c.AbortWithError(404, err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // get the token and verify the hook is authorized
|
||||
// parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
||||
// return repo.Hash, nil
|
||||
// })
|
||||
// if err != nil {
|
||||
// logrus.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
|
||||
// c.AbortWithError(400, err)
|
||||
// return
|
||||
// }
|
||||
// if parsed.Text != repo.FullName {
|
||||
// logrus.Errorf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text)
|
||||
// c.AbortWithStatus(403)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// if repo.UserID == 0 {
|
||||
// logrus.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
|
||||
// c.Writer.WriteHeader(204)
|
||||
// return
|
||||
// }
|
||||
// var skipped = true
|
||||
// if (build.Event == model.EventPush && repo.AllowPush) ||
|
||||
// (build.Event == model.EventPull && repo.AllowPull) ||
|
||||
// (build.Event == model.EventDeploy && repo.AllowDeploy) ||
|
||||
// (build.Event == model.EventTag && repo.AllowTag) {
|
||||
// skipped = false
|
||||
// }
|
||||
//
|
||||
// if skipped {
|
||||
// logrus.Infof("ignoring hook. repo %s is disabled for %s events.", repo.FullName, build.Event)
|
||||
// c.Writer.WriteHeader(204)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// user, err := store.GetUser(c, repo.UserID)
|
||||
// if err != nil {
|
||||
// logrus.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
||||
// c.AbortWithError(500, err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // if the remote has a refresh token, the current access token
|
||||
// // may be stale. Therefore, we should refresh prior to dispatching
|
||||
// // the job.
|
||||
// if refresher, ok := remote_.(remote.Refresher); ok {
|
||||
// ok, _ := refresher.Refresh(user)
|
||||
// if ok {
|
||||
// store.UpdateUser(c, user)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // fetch the build file from the database
|
||||
// cfg := ToConfig(c)
|
||||
// raw, err := remote_.File(user, repo, build, cfg.Yaml)
|
||||
// if err != nil {
|
||||
// logrus.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
||||
// c.AbortWithError(404, err)
|
||||
// return
|
||||
// }
|
||||
// sec, err := remote_.File(user, repo, build, cfg.Shasum)
|
||||
// if err != nil {
|
||||
// logrus.Debugf("cannot find yaml signature for %s. %s", repo.FullName, err)
|
||||
// // NOTE we don't exit on failure. The sec file is optional
|
||||
// }
|
||||
//
|
||||
// axes, err := matrix.Parse(raw)
|
||||
// if err != nil {
|
||||
// c.String(500, "Failed to parse yaml file or calculate matrix. %s", err)
|
||||
// return
|
||||
// }
|
||||
// if len(axes) == 0 {
|
||||
// axes = append(axes, matrix.Axis{})
|
||||
// }
|
||||
//
|
||||
// netrc, err := remote_.Netrc(user, repo)
|
||||
// if err != nil {
|
||||
// c.String(500, "Failed to generate netrc file. %s", err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // verify the branches can be built vs skipped
|
||||
// branches, err := yaml.ParseBytes(raw)
|
||||
// if err != nil {
|
||||
// c.String(500, "Failed to parse yaml file. %s", err)
|
||||
// return
|
||||
// }
|
||||
// if !branches.Branches.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
|
||||
// c.String(200, "Branch does not match restrictions defined in yaml")
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// signature, err := jose.ParseSigned(string(sec))
|
||||
// if err != nil {
|
||||
// logrus.Debugf("cannot parse .drone.yml.sig file. %s", err)
|
||||
// } else if len(sec) == 0 {
|
||||
// logrus.Debugf("cannot parse .drone.yml.sig file. empty file")
|
||||
// } else {
|
||||
// build.Signed = true
|
||||
// output, verr := signature.Verify([]byte(repo.Hash))
|
||||
// if verr != nil {
|
||||
// logrus.Debugf("cannot verify .drone.yml.sig file. %s", verr)
|
||||
// } else if string(output) != string(raw) {
|
||||
// logrus.Debugf("cannot verify .drone.yml.sig file. no match")
|
||||
// } else {
|
||||
// build.Verified = true
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // update some build fields
|
||||
// build.Status = model.StatusPending
|
||||
// build.RepoID = repo.ID
|
||||
//
|
||||
// // and use a transaction
|
||||
// var jobs []*model.Job
|
||||
// for num, axis := range axes {
|
||||
// jobs = append(jobs, &model.Job{
|
||||
// BuildID: build.ID,
|
||||
// Number: num + 1,
|
||||
// Status: model.StatusPending,
|
||||
// Environment: axis,
|
||||
// })
|
||||
// }
|
||||
// err = store.CreateBuild(c, build, jobs...)
|
||||
// if err != nil {
|
||||
// logrus.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
||||
// c.AbortWithError(500, err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// c.JSON(200, build)
|
||||
//
|
||||
// uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
||||
// err = remote_.Status(user, repo, build, uri)
|
||||
// if err != nil {
|
||||
// logrus.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
|
||||
// }
|
||||
//
|
||||
// // get the previous build so that we can send
|
||||
// // on status change notifications
|
||||
// last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
|
||||
// secs, err := store.GetMergedSecretList(c, repo)
|
||||
// if err != nil {
|
||||
// logrus.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
|
||||
// }
|
||||
//
|
||||
// //
|
||||
// // BELOW: NEW
|
||||
// //
|
||||
//
|
||||
// b := builder{
|
||||
// Repo: repo,
|
||||
// Curr: build,
|
||||
// Last: last,
|
||||
// Netrc: netrc,
|
||||
// Secs: secs,
|
||||
// Link: httputil.GetURL(c.Request),
|
||||
// Yaml: string(raw),
|
||||
// }
|
||||
// items, err := b.Build()
|
||||
// if err != nil {
|
||||
// build.Status = model.StatusError
|
||||
// build.Started = time.Now().Unix()
|
||||
// build.Finished = build.Started
|
||||
// build.Error = err.Error()
|
||||
// store.CreateBuild(c, build, build.Jobs...)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// for _, item := range items {
|
||||
// build.Jobs = append(build.Jobs, item.Job)
|
||||
// }
|
||||
//
|
||||
// if err := store.CreateBuild(c, build, build.Jobs...); err != nil {
|
||||
// logrus.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
||||
// c.AbortWithError(500, err)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// for _, item := range items {
|
||||
//
|
||||
// task := new(queue.Task)
|
||||
// task.ID = fmt.Sprint(item.Job.ID)
|
||||
// task.Labels = map[string]string{}
|
||||
// task.Labels["platform"] = item.Platform
|
||||
// for k, v := range item.Labels {
|
||||
// task.Labels[k] = v
|
||||
// }
|
||||
//
|
||||
// task.Data, _ = json.Marshal(rpc.Pipeline{
|
||||
// ID: fmt.Sprint(item.Job.ID),
|
||||
// Config: item.Config,
|
||||
// Timeout: b.Repo.Timeout,
|
||||
// })
|
||||
//
|
||||
// config.logger.Open(context.Background(), task.ID)
|
||||
// config.queue.Push(context.Background(), task)
|
||||
// }
|
||||
//
|
||||
// //
|
||||
// // new code here
|
||||
// //
|
||||
//
|
||||
// message := pubsub.Message{
|
||||
// Labels: map[string]string{
|
||||
// "repo": repo.FullName,
|
||||
// "private": strconv.FormatBool(repo.IsPrivate),
|
||||
// },
|
||||
// }
|
||||
// message.Data, _ = json.Marshal(model.Event{
|
||||
// Type: model.Enqueued,
|
||||
// Repo: *repo,
|
||||
// Build: *build,
|
||||
// })
|
||||
// // TODO remove global reference
|
||||
// config.pubsub.Publish(c, "topic/events", message)
|
||||
//
|
||||
// //
|
||||
// // workspace
|
||||
// //
|
||||
//
|
||||
// for _, job := range jobs {
|
||||
//
|
||||
// metadata := metadataFromStruct(repo, build, last, job, httputil.GetURL(c.Request))
|
||||
// environ := metadata.Environ()
|
||||
//
|
||||
// secrets := map[string]string{}
|
||||
// for _, sec := range secs {
|
||||
// if !sec.MatchEvent(build.Event) {
|
||||
// continue
|
||||
// }
|
||||
// if build.Verified || sec.SkipVerify {
|
||||
// secrets[sec.Name] = sec.Value
|
||||
// }
|
||||
// }
|
||||
// sub := func(name string) string {
|
||||
// if v, ok := environ[name]; ok {
|
||||
// return v
|
||||
// }
|
||||
// return secrets[name]
|
||||
// }
|
||||
// if s, err := envsubst.Eval(string(raw), sub); err != nil {
|
||||
// raw = []byte(s)
|
||||
// }
|
||||
// parsed, err := yaml.ParseBytes(raw)
|
||||
// if err != nil {
|
||||
// job.ExitCode = 255
|
||||
// job.Enqueued = time.Now().Unix()
|
||||
// job.Started = time.Now().Unix()
|
||||
// job.Finished = time.Now().Unix()
|
||||
// job.Error = err.Error()
|
||||
// store.UpdateBuildJob(c, build, job)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// lerr := linter.New(
|
||||
// linter.WithTrusted(repo.IsTrusted),
|
||||
// ).Lint(parsed)
|
||||
// if lerr != nil {
|
||||
// job.ExitCode = 255
|
||||
// job.Enqueued = time.Now().Unix()
|
||||
// job.Started = time.Now().Unix()
|
||||
// job.Finished = time.Now().Unix()
|
||||
// job.Error = lerr.Error()
|
||||
// store.UpdateBuildJob(c, build, job)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// ir := compiler.New(
|
||||
// compiler.WithEnviron(environ),
|
||||
// // TODO ability to customize the escalated plugins
|
||||
// compiler.WithEscalated("plugins/docker", "plugins/gcr", "plugins/ecr"),
|
||||
// compiler.WithLocal(false),
|
||||
// compiler.WithNetrc(netrc.Login, netrc.Password, netrc.Machine),
|
||||
// compiler.WithPrefix(
|
||||
// fmt.Sprintf(
|
||||
// "%d_%d",
|
||||
// job.ID,
|
||||
// time.Now().Unix(),
|
||||
// ),
|
||||
// ),
|
||||
// compiler.WithEnviron(job.Environment),
|
||||
// compiler.WithProxy(),
|
||||
// // TODO ability to set global volumes for things like certs
|
||||
// compiler.WithVolumes(),
|
||||
// compiler.WithWorkspaceFromURL("/drone", repo.Link),
|
||||
// ).Compile(parsed)
|
||||
//
|
||||
// // TODO there is a chicken and egg problem here because
|
||||
// // the compiled yaml has a platform environment variable
|
||||
// // that is not correctly set, because we are just about
|
||||
// // to set it ....
|
||||
// // TODO maybe we remove platform from metadata and let
|
||||
// // the compiler set the value from the yaml itself.
|
||||
// if parsed.Platform == "" {
|
||||
// parsed.Platform = "linux/amd64"
|
||||
// }
|
||||
//
|
||||
// for _, sec := range secs {
|
||||
// if !sec.MatchEvent(build.Event) {
|
||||
// continue
|
||||
// }
|
||||
// if build.Verified || sec.SkipVerify {
|
||||
// ir.Secrets = append(ir.Secrets, &backend.Secret{
|
||||
// Mask: sec.Conceal,
|
||||
// Name: sec.Name,
|
||||
// Value: sec.Value,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// task := new(queue.Task)
|
||||
// task.ID = fmt.Sprint(job.ID)
|
||||
// task.Labels = map[string]string{}
|
||||
// task.Labels["platform"] = parsed.Platform
|
||||
// if parsed.Labels != nil {
|
||||
// for k, v := range parsed.Labels {
|
||||
// task.Labels[k] = v
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// task.Data, _ = json.Marshal(rpc.Pipeline{
|
||||
// ID: fmt.Sprint(job.ID),
|
||||
// Config: ir,
|
||||
// Timeout: repo.Timeout,
|
||||
// })
|
||||
//
|
||||
// config.logger.Open(context.Background(), task.ID)
|
||||
// config.queue.Push(context.Background(), task)
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
// return the metadata from the cli context.
|
||||
func metadataFromStruct(repo *model.Repo, build, last *model.Build, job *model.Job, link string) frontend.Metadata {
|
||||
return frontend.Metadata{
|
||||
Repo: frontend.Repo{
|
||||
Name: repo.Name,
|
||||
Link: repo.Link,
|
||||
Remote: repo.Clone,
|
||||
Private: repo.IsPrivate,
|
||||
},
|
||||
Curr: frontend.Build{
|
||||
Number: build.Number,
|
||||
Created: build.Created,
|
||||
Started: build.Started,
|
||||
Finished: build.Finished,
|
||||
Status: build.Status,
|
||||
Event: build.Event,
|
||||
Link: build.Link,
|
||||
Target: build.Deploy,
|
||||
Commit: frontend.Commit{
|
||||
Sha: build.Commit,
|
||||
Ref: build.Ref,
|
||||
Refspec: build.Refspec,
|
||||
Branch: build.Branch,
|
||||
Message: build.Message,
|
||||
Author: frontend.Author{
|
||||
Name: build.Author,
|
||||
Email: build.Email,
|
||||
Avatar: build.Avatar,
|
||||
},
|
||||
},
|
||||
},
|
||||
Prev: frontend.Build{
|
||||
Number: last.Number,
|
||||
Created: last.Created,
|
||||
Started: last.Started,
|
||||
Finished: last.Finished,
|
||||
Status: last.Status,
|
||||
Event: last.Event,
|
||||
Link: last.Link,
|
||||
Target: last.Deploy,
|
||||
Commit: frontend.Commit{
|
||||
Sha: last.Commit,
|
||||
Ref: last.Ref,
|
||||
Refspec: last.Refspec,
|
||||
Branch: last.Branch,
|
||||
Message: last.Message,
|
||||
Author: frontend.Author{
|
||||
Name: last.Author,
|
||||
Email: last.Email,
|
||||
Avatar: last.Avatar,
|
||||
},
|
||||
},
|
||||
},
|
||||
Job: frontend.Job{
|
||||
Number: job.Number,
|
||||
Matrix: job.Environment,
|
||||
},
|
||||
Sys: frontend.System{
|
||||
Name: "drone",
|
||||
Link: link,
|
||||
Arch: "linux/amd64",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// use helper funciton to return ([]backend.Config, error)
|
||||
|
||||
// 1. fetch everything from github
|
||||
// 2. create and persist the build object
|
||||
//
|
||||
// 3. generate the build jobs [Launcher?]
|
||||
// a. parse yaml
|
||||
// b. lint yaml
|
||||
// c. compile yaml
|
||||
//
|
||||
// 4. persist the build jobs (... what if I already have jobs, via re-start)
|
||||
// 5. update github status
|
||||
// 6. send to queue
|
||||
// 7. trigger pubsub
|
||||
|
||||
type builder struct {
|
||||
Repo *model.Repo
|
||||
Curr *model.Build
|
||||
Last *model.Build
|
||||
Netrc *model.Netrc
|
||||
Secs []*model.Secret
|
||||
Link string
|
||||
Yaml string
|
||||
}
|
||||
|
||||
type buildItem struct {
|
||||
Job *model.Job
|
||||
Platform string
|
||||
Labels map[string]string
|
||||
Config *backend.Config
|
||||
}
|
||||
|
||||
func (b *builder) Build() ([]*buildItem, error) {
|
||||
|
||||
axes, err := matrix.ParseString(b.Yaml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(axes) == 0 {
|
||||
axes = append(axes, matrix.Axis{})
|
||||
}
|
||||
|
||||
var items []*buildItem
|
||||
for i, axis := range axes {
|
||||
job := &model.Job{
|
||||
BuildID: b.Curr.ID,
|
||||
Number: i + 1,
|
||||
Status: model.StatusPending,
|
||||
Environment: axis,
|
||||
Enqueued: b.Curr.Created,
|
||||
}
|
||||
|
||||
metadata := metadataFromStruct(b.Repo, b.Curr, b.Last, job, b.Link)
|
||||
environ := metadata.Environ()
|
||||
for k, v := range metadata.EnvironDrone() {
|
||||
environ[k] = v
|
||||
}
|
||||
|
||||
secrets := map[string]string{}
|
||||
for _, sec := range b.Secs {
|
||||
if !sec.MatchEvent(b.Curr.Event) {
|
||||
continue
|
||||
}
|
||||
if b.Curr.Verified || sec.SkipVerify {
|
||||
secrets[sec.Name] = sec.Value
|
||||
}
|
||||
}
|
||||
sub := func(name string) string {
|
||||
if v, ok := environ[name]; ok {
|
||||
return v
|
||||
}
|
||||
return secrets[name]
|
||||
}
|
||||
|
||||
y := b.Yaml
|
||||
if s, err := envsubst.Eval(y, sub); err != nil {
|
||||
y = s
|
||||
}
|
||||
|
||||
parsed, err := yaml.ParseString(y)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metadata.Sys.Arch = parsed.Platform
|
||||
if metadata.Sys.Arch == "" {
|
||||
metadata.Sys.Arch = "linux/amd64"
|
||||
}
|
||||
|
||||
lerr := linter.New(
|
||||
linter.WithTrusted(b.Repo.IsTrusted),
|
||||
).Lint(parsed)
|
||||
if lerr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ir := compiler.New(
|
||||
compiler.WithEnviron(environ),
|
||||
// TODO ability to customize the escalated plugins
|
||||
compiler.WithEscalated("plugins/docker", "plugins/gcr", "plugins/ecr"),
|
||||
compiler.WithLocal(false),
|
||||
compiler.WithNetrc(b.Netrc.Login, b.Netrc.Password, b.Netrc.Machine),
|
||||
compiler.WithPrefix(
|
||||
fmt.Sprintf(
|
||||
"%d_%d",
|
||||
job.ID,
|
||||
time.Now().Unix(),
|
||||
),
|
||||
),
|
||||
compiler.WithEnviron(job.Environment),
|
||||
compiler.WithProxy(),
|
||||
// TODO ability to set global volumes for things like certs
|
||||
compiler.WithVolumes(),
|
||||
compiler.WithWorkspaceFromURL("/drone", b.Curr.Link),
|
||||
).Compile(parsed)
|
||||
|
||||
for _, sec := range b.Secs {
|
||||
if !sec.MatchEvent(b.Curr.Event) {
|
||||
continue
|
||||
}
|
||||
if b.Curr.Verified || sec.SkipVerify {
|
||||
ir.Secrets = append(ir.Secrets, &backend.Secret{
|
||||
Mask: sec.Conceal,
|
||||
Name: sec.Name,
|
||||
Value: sec.Value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
item := &buildItem{
|
||||
Job: job,
|
||||
Config: ir,
|
||||
Labels: parsed.Labels,
|
||||
Platform: metadata.Sys.Arch,
|
||||
}
|
||||
if item.Labels == nil {
|
||||
item.Labels = map[string]string{}
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
func PostHook2(c *gin.Context) {
|
||||
remote_ := remote.FromContext(c)
|
||||
|
||||
tmprepo, build, err := remote_.Hook(c.Request)
|
||||
if err != nil {
|
||||
logrus.Errorf("failure to parse hook. %s", err)
|
||||
c.AbortWithError(400, err)
|
||||
return
|
||||
}
|
||||
if build == nil {
|
||||
c.Writer.WriteHeader(200)
|
||||
return
|
||||
}
|
||||
if tmprepo == nil {
|
||||
logrus.Errorf("failure to ascertain repo from hook.")
|
||||
c.Writer.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
|
||||
// skip the build if any case-insensitive combination of the words "skip" and "ci"
|
||||
// wrapped in square brackets appear in the commit message
|
||||
skipMatch := skipRe.FindString(build.Message)
|
||||
if len(skipMatch) > 0 {
|
||||
logrus.Infof("ignoring hook. %s found in %s", skipMatch, build.Commit)
|
||||
c.Writer.WriteHeader(204)
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := store.GetRepoOwnerName(c, tmprepo.Owner, tmprepo.Name)
|
||||
if err != nil {
|
||||
logrus.Errorf("failure to find repo %s/%s from hook. %s", tmprepo.Owner, tmprepo.Name, err)
|
||||
c.AbortWithError(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
// get the token and verify the hook is authorized
|
||||
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
||||
return repo.Hash, nil
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(400, err)
|
||||
return
|
||||
}
|
||||
if parsed.Text != repo.FullName {
|
||||
logrus.Errorf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text)
|
||||
c.AbortWithStatus(403)
|
||||
return
|
||||
}
|
||||
|
||||
if repo.UserID == 0 {
|
||||
logrus.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
|
||||
c.Writer.WriteHeader(204)
|
||||
return
|
||||
}
|
||||
var skipped = true
|
||||
if (build.Event == model.EventPush && repo.AllowPush) ||
|
||||
(build.Event == model.EventPull && repo.AllowPull) ||
|
||||
(build.Event == model.EventDeploy && repo.AllowDeploy) ||
|
||||
(build.Event == model.EventTag && repo.AllowTag) {
|
||||
skipped = false
|
||||
}
|
||||
|
||||
if skipped {
|
||||
logrus.Infof("ignoring hook. repo %s is disabled for %s events.", repo.FullName, build.Event)
|
||||
c.Writer.WriteHeader(204)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := store.GetUser(c, repo.UserID)
|
||||
if err != nil {
|
||||
logrus.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
// if the remote has a refresh token, the current access token
|
||||
// may be stale. Therefore, we should refresh prior to dispatching
|
||||
// the job.
|
||||
if refresher, ok := remote_.(remote.Refresher); ok {
|
||||
ok, _ := refresher.Refresh(user)
|
||||
if ok {
|
||||
store.UpdateUser(c, user)
|
||||
}
|
||||
}
|
||||
|
||||
// fetch the build file from the database
|
||||
cfg := ToConfig(c)
|
||||
raw, err := remote_.File(user, repo, build, cfg.Yaml)
|
||||
if err != nil {
|
||||
logrus.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(404, err)
|
||||
return
|
||||
}
|
||||
sec, err := remote_.File(user, repo, build, cfg.Shasum)
|
||||
if err != nil {
|
||||
logrus.Debugf("cannot find yaml signature for %s. %s", repo.FullName, err)
|
||||
}
|
||||
|
||||
netrc, err := remote_.Netrc(user, repo)
|
||||
if err != nil {
|
||||
c.String(500, "Failed to generate netrc file. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// verify the branches can be built vs skipped
|
||||
branches, err := yaml.ParseBytes(raw)
|
||||
if err != nil {
|
||||
c.String(500, "Failed to parse yaml file. %s", err)
|
||||
return
|
||||
}
|
||||
if !branches.Branches.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
|
||||
c.String(200, "Branch does not match restrictions defined in yaml")
|
||||
return
|
||||
}
|
||||
|
||||
signature, err := jose.ParseSigned(string(sec))
|
||||
if err != nil {
|
||||
logrus.Debugf("cannot parse .drone.yml.sig file. %s", err)
|
||||
} else if len(sec) == 0 {
|
||||
logrus.Debugf("cannot parse .drone.yml.sig file. empty file")
|
||||
} else {
|
||||
build.Signed = true
|
||||
output, verr := signature.Verify([]byte(repo.Hash))
|
||||
if verr != nil {
|
||||
logrus.Debugf("cannot verify .drone.yml.sig file. %s", verr)
|
||||
} else if string(output) != string(raw) {
|
||||
logrus.Debugf("cannot verify .drone.yml.sig file. no match")
|
||||
} else {
|
||||
build.Verified = true
|
||||
}
|
||||
}
|
||||
|
||||
// update some build fields
|
||||
build.Status = model.StatusPending
|
||||
build.RepoID = repo.ID
|
||||
|
||||
if err := store.CreateBuild(c, build, build.Jobs...); err != nil {
|
||||
logrus.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
||||
c.AbortWithError(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, build)
|
||||
|
||||
// get the previous build so that we can send
|
||||
// on status change notifications
|
||||
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
|
||||
secs, err := store.GetMergedSecretList(c, repo)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
|
||||
}
|
||||
|
||||
//
|
||||
// BELOW: NEW
|
||||
//
|
||||
|
||||
defer func() {
|
||||
uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
||||
err = remote_.Status(user, repo, build, uri)
|
||||
if err != nil {
|
||||
logrus.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
|
||||
}
|
||||
}()
|
||||
|
||||
b := builder{
|
||||
Repo: repo,
|
||||
Curr: build,
|
||||
Last: last,
|
||||
Netrc: netrc,
|
||||
Secs: secs,
|
||||
Link: httputil.GetURL(c.Request),
|
||||
Yaml: string(raw),
|
||||
}
|
||||
items, err := b.Build()
|
||||
if err != nil {
|
||||
build.Status = model.StatusError
|
||||
build.Started = time.Now().Unix()
|
||||
build.Finished = build.Started
|
||||
build.Error = err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
build.Jobs = append(build.Jobs, item.Job)
|
||||
store.CreateJob(c, item.Job)
|
||||
// TODO err
|
||||
}
|
||||
|
||||
//
|
||||
// publish topic
|
||||
//
|
||||
message := pubsub.Message{
|
||||
Labels: map[string]string{
|
||||
"repo": repo.FullName,
|
||||
"private": strconv.FormatBool(repo.IsPrivate),
|
||||
},
|
||||
}
|
||||
message.Data, _ = json.Marshal(model.Event{
|
||||
Type: model.Enqueued,
|
||||
Repo: *repo,
|
||||
Build: *build,
|
||||
})
|
||||
// TODO remove global reference
|
||||
config.pubsub.Publish(c, "topic/events", message)
|
||||
//
|
||||
// end publish topic
|
||||
//
|
||||
|
||||
for _, item := range items {
|
||||
task := new(queue.Task)
|
||||
task.ID = fmt.Sprint(item.Job.ID)
|
||||
task.Labels = map[string]string{}
|
||||
task.Labels["platform"] = item.Platform
|
||||
for k, v := range item.Labels {
|
||||
task.Labels[k] = v
|
||||
}
|
||||
|
||||
task.Data, _ = json.Marshal(rpc.Pipeline{
|
||||
ID: fmt.Sprint(item.Job.ID),
|
||||
Config: item.Config,
|
||||
Timeout: b.Repo.Timeout,
|
||||
})
|
||||
|
||||
config.logger.Open(context.Background(), task.ID)
|
||||
config.queue.Push(context.Background(), task)
|
||||
}
|
||||
}
|
163
server/queue.go
163
server/queue.go
|
@ -1,163 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/remote"
|
||||
"github.com/drone/drone/store"
|
||||
"github.com/drone/mq/stomp"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// newline defines a newline constant to separate lines in the build output
|
||||
var newline = []byte{'\n'}
|
||||
|
||||
// upgrader defines the default behavior for upgrading the websocket.
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// HandleUpdate handles build updates from the agent and persists to the database.
|
||||
func HandleUpdate(c context.Context, message *stomp.Message) {
|
||||
defer func() {
|
||||
message.Release()
|
||||
if r := recover(); r != nil {
|
||||
err := r.(error)
|
||||
logrus.Errorf("Panic recover: broker update handler: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
work := new(model.Work)
|
||||
if err := message.Unmarshal(work); err != nil {
|
||||
logrus.Errorf("Invalid input. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(bradrydzewski) it is really annoying that we have to do this lookup
|
||||
// and I'd prefer not to. The reason we do this is because the Build and Job
|
||||
// have fields that aren't serialized to json and would be reset to their
|
||||
// empty values if we just saved what was coming in the http.Request body.
|
||||
build, err := store.GetBuild(c, work.Build.ID)
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to find build. %s", err)
|
||||
return
|
||||
}
|
||||
job, err := store.GetJob(c, work.Job.ID)
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to find job. %s", err)
|
||||
return
|
||||
}
|
||||
build.Started = work.Build.Started
|
||||
build.Finished = work.Build.Finished
|
||||
build.Status = work.Build.Status
|
||||
job.Started = work.Job.Started
|
||||
job.Finished = work.Job.Finished
|
||||
job.Status = work.Job.Status
|
||||
job.ExitCode = work.Job.ExitCode
|
||||
job.Error = work.Job.Error
|
||||
|
||||
if build.Status == model.StatusPending {
|
||||
build.Started = work.Job.Started
|
||||
build.Status = model.StatusRunning
|
||||
store.UpdateBuild(c, build)
|
||||
}
|
||||
|
||||
// if job.Status == model.StatusRunning {
|
||||
// err := stream.Create(c, stream.ToKey(job.ID))
|
||||
// if err != nil {
|
||||
// logrus.Errorf("Unable to create stream. %s", err)
|
||||
// }
|
||||
// }
|
||||
|
||||
ok, err := store.UpdateBuildJob(c, build, job)
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to update job. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if ok {
|
||||
// get the user because we transfer the user form the server to agent
|
||||
// and back we lose the token which does not get serialized to json.
|
||||
user, uerr := store.GetUser(c, work.User.ID)
|
||||
if uerr != nil {
|
||||
logrus.Errorf("Unable to find user. %s", err)
|
||||
return
|
||||
}
|
||||
remote.Status(c, user, work.Repo, build,
|
||||
fmt.Sprintf("%s/%s/%d", work.System.Link, work.Repo.FullName, work.Build.Number))
|
||||
}
|
||||
|
||||
client := stomp.MustFromContext(c)
|
||||
err = client.SendJSON("/topic/events", model.Event{
|
||||
Type: func() model.EventType {
|
||||
// HACK we don't even really care about the event type.
|
||||
// so we should just simplify how events are triggered.
|
||||
if job.Status == model.StatusRunning {
|
||||
return model.Started
|
||||
}
|
||||
return model.Finished
|
||||
}(),
|
||||
Repo: *work.Repo,
|
||||
Build: *build,
|
||||
Job: *job,
|
||||
},
|
||||
stomp.WithHeader("repo", work.Repo.FullName),
|
||||
stomp.WithHeader("private", strconv.FormatBool(work.Repo.IsPrivate)),
|
||||
)
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to publish to /topic/events. %s", err)
|
||||
}
|
||||
|
||||
if job.Status == model.StatusRunning {
|
||||
return
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
var sub []byte
|
||||
|
||||
done := make(chan bool)
|
||||
dest := fmt.Sprintf("/topic/logs.%d", job.ID)
|
||||
sub, err = client.Subscribe(dest, stomp.HandlerFunc(func(m *stomp.Message) {
|
||||
defer m.Release()
|
||||
if m.Header.GetBool("eof") {
|
||||
done <- true
|
||||
return
|
||||
}
|
||||
buf.Write(m.Body)
|
||||
buf.WriteByte('\n')
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to read logs from broker. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
client.Send(dest, []byte{}, stomp.WithRetain("remove"))
|
||||
client.Unsubscribe(sub)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(30 * time.Second):
|
||||
logrus.Errorf("Unable to read logs from broker. Timeout. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := store.WriteLog(c, job, &buf); err != nil {
|
||||
logrus.Errorf("Unable to write logs to store. %s", err)
|
||||
return
|
||||
}
|
||||
}
|
174
server/stream.go
174
server/stream.go
|
@ -3,6 +3,7 @@ package server
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -12,7 +13,6 @@ import (
|
|||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/router/middleware/session"
|
||||
"github.com/drone/drone/store"
|
||||
"github.com/drone/mq/stomp"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
@ -28,160 +28,17 @@ var (
|
|||
|
||||
// Send pings to client with this period. Must be less than pongWait.
|
||||
pingPeriod = 30 * time.Second
|
||||
|
||||
// upgrader defines the default behavior for upgrading the websocket.
|
||||
upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// LogStream streams the build log output to the client.
|
||||
func LogStream(c *gin.Context) {
|
||||
repo := session.Repo(c)
|
||||
buildn, _ := strconv.Atoi(c.Param("build"))
|
||||
jobn, _ := strconv.Atoi(c.Param("number"))
|
||||
|
||||
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
||||
|
||||
build, err := store.GetBuildNumber(c, repo, buildn)
|
||||
if err != nil {
|
||||
logrus.Debugln("stream cannot get build number.", err)
|
||||
c.AbortWithError(404, err)
|
||||
return
|
||||
}
|
||||
job, err := store.GetJobNumber(c, build, jobn)
|
||||
if err != nil {
|
||||
logrus.Debugln("stream cannot get job number.", err)
|
||||
c.AbortWithError(404, err)
|
||||
return
|
||||
}
|
||||
if job.Status != model.StatusRunning {
|
||||
logrus.Debugln("stream not found.")
|
||||
c.AbortWithStatus(404)
|
||||
return
|
||||
}
|
||||
|
||||
ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
if _, ok := err.(websocket.HandshakeError); !ok {
|
||||
logrus.Errorf("Cannot upgrade websocket. %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
logrus.Debugf("Successfull upgraded websocket")
|
||||
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer ticker.Stop()
|
||||
|
||||
logs := make(chan []byte)
|
||||
done := make(chan bool)
|
||||
var eof bool
|
||||
dest := fmt.Sprintf("/topic/logs.%d", job.ID)
|
||||
client, _ := stomp.FromContext(c)
|
||||
sub, err := client.Subscribe(dest, stomp.HandlerFunc(func(m *stomp.Message) {
|
||||
if m.Header.GetBool("eof") {
|
||||
eof = true
|
||||
done <- true
|
||||
} else if eof {
|
||||
return
|
||||
} else {
|
||||
logs <- m.Body
|
||||
}
|
||||
m.Release()
|
||||
}))
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to read logs from broker. %s", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
client.Unsubscribe(sub)
|
||||
close(done)
|
||||
close(logs)
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case buf := <-logs:
|
||||
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
ws.WriteMessage(websocket.TextMessage, buf)
|
||||
case <-done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EventStream produces the User event stream, sending all repository, build
|
||||
// and agent events to the client.
|
||||
func EventStream(c *gin.Context) {
|
||||
ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
if _, ok := err.(websocket.HandshakeError); !ok {
|
||||
logrus.Errorf("Cannot upgrade websocket. %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
logrus.Debugf("Successfull upgraded websocket")
|
||||
|
||||
user := session.User(c)
|
||||
repo := map[string]bool{}
|
||||
if user != nil {
|
||||
repo, _ = cache.GetRepoMap(c, user)
|
||||
}
|
||||
|
||||
eventc := make(chan []byte, 10)
|
||||
quitc := make(chan bool)
|
||||
tick := time.NewTicker(pingPeriod)
|
||||
defer func() {
|
||||
tick.Stop()
|
||||
ws.Close()
|
||||
logrus.Debug("Successfully closed websocket")
|
||||
}()
|
||||
|
||||
client := stomp.MustFromContext(c)
|
||||
sub, err := client.Subscribe("/topic/events", stomp.HandlerFunc(func(m *stomp.Message) {
|
||||
name := m.Header.GetString("repo")
|
||||
priv := m.Header.GetBool("private")
|
||||
if repo[name] || !priv {
|
||||
eventc <- m.Body
|
||||
}
|
||||
m.Release()
|
||||
}))
|
||||
if err != nil {
|
||||
logrus.Errorf("Unable to read logs from broker. %s", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
client.Unsubscribe(sub)
|
||||
close(quitc)
|
||||
close(eventc)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-quitc:
|
||||
return
|
||||
case event, ok := <-eventc:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
ws.WriteMessage(websocket.TextMessage, event)
|
||||
case <-tick.C:
|
||||
err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
reader(ws)
|
||||
}
|
||||
|
||||
func reader(ws *websocket.Conn) {
|
||||
defer ws.Close()
|
||||
ws.SetReadLimit(512)
|
||||
|
@ -198,14 +55,7 @@ func reader(ws *websocket.Conn) {
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// CANARY IMPLEMENTATION
|
||||
//
|
||||
// This file is a complete disaster because I'm trying to wedge in some
|
||||
// experimental code. Please pardon our appearance during renovations.
|
||||
//
|
||||
|
||||
func LogStream2(c *gin.Context) {
|
||||
func LogStream(c *gin.Context) {
|
||||
repo := session.Repo(c)
|
||||
buildn, _ := strconv.Atoi(c.Param("build"))
|
||||
jobn, _ := strconv.Atoi(c.Param("number"))
|
||||
|
@ -286,7 +136,7 @@ func LogStream2(c *gin.Context) {
|
|||
reader(ws)
|
||||
}
|
||||
|
||||
func EventStream2(c *gin.Context) {
|
||||
func EventStream(c *gin.Context) {
|
||||
ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
if _, ok := err.(websocket.HandshakeError); !ok {
|
||||
|
|
|
@ -6,7 +6,7 @@ var (
|
|||
// VersionMajor is for an API incompatible changes
|
||||
VersionMajor = 0
|
||||
// VersionMinor is for functionality in a backwards-compatible manner
|
||||
VersionMinor = 5
|
||||
VersionMinor = 6
|
||||
// VersionPatch is for backwards-compatible bug fixes
|
||||
VersionPatch = 0
|
||||
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import "gopkg.in/yaml.v2"
|
||||
|
||||
// ParseBranch parses the branch section of the Yaml document.
|
||||
func ParseBranch(in []byte) Constraint {
|
||||
out := struct {
|
||||
Constraint Constraint `yaml:"branches"`
|
||||
}{}
|
||||
|
||||
yaml.Unmarshal(in, &out)
|
||||
return out.Constraint
|
||||
}
|
||||
|
||||
// ParseBranchString parses the branch section of the Yaml document.
|
||||
func ParseBranchString(in string) Constraint {
|
||||
return ParseBranch([]byte(in))
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestBranch(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Branch filter", func() {
|
||||
|
||||
g.It("Should parse and match emtpy", func() {
|
||||
branch := ParseBranchString("")
|
||||
g.Assert(branch.Match("master")).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should parse and match", func() {
|
||||
branch := ParseBranchString("branches: { include: [ master, develop ] }")
|
||||
g.Assert(branch.Match("master")).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should parse and match shortand", func() {
|
||||
branch := ParseBranchString("branches: [ master, develop ]")
|
||||
g.Assert(branch.Match("master")).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should parse and match shortand string", func() {
|
||||
branch := ParseBranchString("branches: master")
|
||||
g.Assert(branch.Match("master")).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should parse and match exclude", func() {
|
||||
branch := ParseBranchString("branches: { exclude: [ master, develop ] }")
|
||||
g.Assert(branch.Match("master")).IsFalse()
|
||||
})
|
||||
|
||||
g.It("Should parse and match exclude shorthand", func() {
|
||||
branch := ParseBranchString("branches: { exclude: master }")
|
||||
g.Assert(branch.Match("master")).IsFalse()
|
||||
})
|
||||
})
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package yaml
|
||||
|
||||
// Build represents Docker image build instructions.
|
||||
type Build struct {
|
||||
Context string
|
||||
Dockerfile string
|
||||
Args map[string]string
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements custom Yaml unmarshaling.
|
||||
func (b *Build) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
err := unmarshal(&b.Context)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
out := struct {
|
||||
Context string
|
||||
Dockerfile string
|
||||
Args map[string]string
|
||||
}{}
|
||||
err = unmarshal(&out)
|
||||
b.Context = out.Context
|
||||
b.Args = out.Args
|
||||
b.Dockerfile = out.Dockerfile
|
||||
return err
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
g := goblin.Goblin(t)
|
||||
|
||||
g.Describe("Build", func() {
|
||||
g.Describe("given a yaml file", func() {
|
||||
|
||||
g.It("should unmarshal", func() {
|
||||
in := []byte(".")
|
||||
out := Build{}
|
||||
err := yaml.Unmarshal(in, &out)
|
||||
if err != nil {
|
||||
g.Fail(err)
|
||||
}
|
||||
g.Assert(out.Context).Equal(".")
|
||||
})
|
||||
|
||||
g.It("should unmarshal shorthand", func() {
|
||||
in := []byte("{ context: ., dockerfile: Dockerfile }")
|
||||
out := Build{}
|
||||
err := yaml.Unmarshal(in, &out)
|
||||
if err != nil {
|
||||
g.Fail(err)
|
||||
}
|
||||
g.Assert(out.Context).Equal(".")
|
||||
g.Assert(out.Dockerfile).Equal("Dockerfile")
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import "gopkg.in/yaml.v2"
|
||||
|
||||
// Workspace represents the build workspace.
|
||||
type Workspace struct {
|
||||
Base string
|
||||
Path string
|
||||
}
|
||||
|
||||
// Config represents the build configuration Yaml document.
|
||||
type Config struct {
|
||||
Image string
|
||||
Build *Build
|
||||
Workspace *Workspace
|
||||
Pipeline []*Container
|
||||
Services []*Container
|
||||
Volumes []*Volume
|
||||
Networks []*Network
|
||||
}
|
||||
|
||||
// ParseString parses the Yaml configuration document.
|
||||
func ParseString(data string) (*Config, error) {
|
||||
return Parse([]byte(data))
|
||||
}
|
||||
|
||||
// Parse parses Yaml configuration document.
|
||||
func Parse(data []byte) (*Config, error) {
|
||||
v := struct {
|
||||
Image string
|
||||
Build *Build
|
||||
Workspace *Workspace
|
||||
Services containerList
|
||||
Pipeline containerList
|
||||
Networks networkList
|
||||
Volumes volumeList
|
||||
}{}
|
||||
|
||||
err := yaml.Unmarshal(data, &v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, c := range v.Services.containers {
|
||||
c.Detached = true
|
||||
}
|
||||
|
||||
return &Config{
|
||||
Image: v.Image,
|
||||
Build: v.Build,
|
||||
Workspace: v.Workspace,
|
||||
Services: v.Services.containers,
|
||||
Pipeline: v.Pipeline.containers,
|
||||
Networks: v.Networks.networks,
|
||||
Volumes: v.Volumes.volumes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Image string
|
||||
Build *Build
|
||||
Workspace *Workspace
|
||||
Services containerList
|
||||
Pipeline containerList
|
||||
Networks networkList
|
||||
Volumes volumeList
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
g := goblin.Goblin(t)
|
||||
|
||||
g.Describe("Parser", func() {
|
||||
g.Describe("Given a yaml file", func() {
|
||||
|
||||
g.It("Should unmarshal a string", func() {
|
||||
out, err := ParseString(sampleYaml)
|
||||
if err != nil {
|
||||
g.Fail(err)
|
||||
}
|
||||
g.Assert(out.Image).Equal("hello-world")
|
||||
g.Assert(out.Workspace.Base).Equal("/go")
|
||||
g.Assert(out.Workspace.Path).Equal("src/github.com/octocat/hello-world")
|
||||
g.Assert(out.Build.Context).Equal(".")
|
||||
g.Assert(out.Build.Dockerfile).Equal("Dockerfile")
|
||||
g.Assert(out.Volumes[0].Name).Equal("custom")
|
||||
g.Assert(out.Volumes[0].Driver).Equal("blockbridge")
|
||||
g.Assert(out.Networks[0].Name).Equal("custom")
|
||||
g.Assert(out.Networks[0].Driver).Equal("overlay")
|
||||
g.Assert(out.Services[0].Name).Equal("database")
|
||||
g.Assert(out.Services[0].Image).Equal("mysql")
|
||||
g.Assert(out.Pipeline[0].Name).Equal("test")
|
||||
g.Assert(out.Pipeline[0].Image).Equal("golang")
|
||||
g.Assert(out.Pipeline[0].Commands).Equal([]string{"go install", "go test"})
|
||||
g.Assert(out.Pipeline[1].Name).Equal("build")
|
||||
g.Assert(out.Pipeline[1].Image).Equal("golang")
|
||||
g.Assert(out.Pipeline[1].Commands).Equal([]string{"go build"})
|
||||
g.Assert(out.Pipeline[2].Name).Equal("notify")
|
||||
g.Assert(out.Pipeline[2].Image).Equal("slack")
|
||||
})
|
||||
// Check to make sure variable expansion works in yaml.MapSlice
|
||||
g.It("Should unmarshal variables", func() {
|
||||
out, err := ParseString(sampleVarYaml)
|
||||
if err != nil {
|
||||
g.Fail(err)
|
||||
}
|
||||
g.Assert(out.Pipeline[0].Name).Equal("notify_fail")
|
||||
g.Assert(out.Pipeline[0].Image).Equal("plugins/slack")
|
||||
g.Assert(len(out.Pipeline[0].Constraints.Event.Include)).Equal(0)
|
||||
g.Assert(out.Pipeline[1].Name).Equal("notify_success")
|
||||
g.Assert(out.Pipeline[1].Image).Equal("plugins/slack")
|
||||
g.Assert(out.Pipeline[1].Constraints.Event.Include).Equal([]string{"success"})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var sampleYaml = `
|
||||
image: hello-world
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
|
||||
workspace:
|
||||
path: src/github.com/octocat/hello-world
|
||||
base: /go
|
||||
|
||||
pipeline:
|
||||
test:
|
||||
image: golang
|
||||
commands:
|
||||
- go install
|
||||
- go test
|
||||
build:
|
||||
image: golang
|
||||
commands:
|
||||
- go build
|
||||
when:
|
||||
event: push
|
||||
notify:
|
||||
image: slack
|
||||
channel: dev
|
||||
when:
|
||||
event: failure
|
||||
|
||||
services:
|
||||
database:
|
||||
image: mysql
|
||||
|
||||
networks:
|
||||
custom:
|
||||
driver: overlay
|
||||
|
||||
volumes:
|
||||
custom:
|
||||
driver: blockbridge
|
||||
`
|
||||
|
||||
var sampleVarYaml = `
|
||||
_slack: &SLACK
|
||||
image: plugins/slack
|
||||
|
||||
pipeline:
|
||||
notify_fail: *SLACK
|
||||
notify_success:
|
||||
<< : *SLACK
|
||||
when:
|
||||
event: success
|
||||
`
|
|
@ -1,155 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/drone/drone/yaml/types"
|
||||
)
|
||||
|
||||
// Constraints define constraints for container execution.
|
||||
type Constraints struct {
|
||||
Repo Constraint
|
||||
Ref Constraint
|
||||
Platform Constraint
|
||||
Environment Constraint
|
||||
Event Constraint
|
||||
Branch Constraint
|
||||
Status Constraint
|
||||
Matrix ConstraintMap
|
||||
Local types.BoolTrue
|
||||
}
|
||||
|
||||
// Match returns true if all constraints match the given input. If a single constraint
|
||||
// fails a false value is returned.
|
||||
func (c *Constraints) Match(arch, target, event, branch, status string, matrix map[string]string) bool {
|
||||
return c.Platform.Match(arch) &&
|
||||
c.Environment.Match(target) &&
|
||||
c.Event.Match(event) &&
|
||||
c.Branch.Match(branch) &&
|
||||
c.Status.Match(status) &&
|
||||
c.Matrix.Match(matrix)
|
||||
}
|
||||
|
||||
// Constraint defines an individual constraint.
|
||||
type Constraint struct {
|
||||
Include []string
|
||||
Exclude []string
|
||||
}
|
||||
|
||||
// Match returns true if the string matches the include patterns and does not
|
||||
// match any of the exclude patterns.
|
||||
func (c *Constraint) Match(v string) bool {
|
||||
if c.Excludes(v) {
|
||||
return false
|
||||
}
|
||||
if c.Includes(v) {
|
||||
return true
|
||||
}
|
||||
if len(c.Include) == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Includes returns true if the string matches matches the include patterns.
|
||||
func (c *Constraint) Includes(v string) bool {
|
||||
for _, pattern := range c.Include {
|
||||
if ok, _ := filepath.Match(pattern, v); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Excludes returns true if the string matches matches the exclude patterns.
|
||||
func (c *Constraint) Excludes(v string) bool {
|
||||
for _, pattern := range c.Exclude {
|
||||
if ok, _ := filepath.Match(pattern, v); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements custom Yaml unmarshaling.
|
||||
func (c *Constraint) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
|
||||
var out1 = struct {
|
||||
Include types.StringOrSlice
|
||||
Exclude types.StringOrSlice
|
||||
}{}
|
||||
|
||||
var out2 types.StringOrSlice
|
||||
|
||||
unmarshal(&out1)
|
||||
unmarshal(&out2)
|
||||
|
||||
c.Exclude = out1.Exclude.Slice()
|
||||
c.Include = append(
|
||||
out1.Include.Slice(),
|
||||
out2.Slice()...,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConstraintMap defines an individual constraint for key value structures.
|
||||
type ConstraintMap struct {
|
||||
Include map[string]string
|
||||
Exclude map[string]string
|
||||
}
|
||||
|
||||
// Match returns true if the params matches the include key values and does not
|
||||
// match any of the exclude key values.
|
||||
func (c *ConstraintMap) Match(params map[string]string) bool {
|
||||
// when no includes or excludes automatically match
|
||||
if len(c.Include) == 0 && len(c.Exclude) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// exclusions are processed first. So we can include everything and then
|
||||
// selectively include others.
|
||||
if len(c.Exclude) != 0 {
|
||||
var matches int
|
||||
|
||||
for key, val := range c.Exclude {
|
||||
if params[key] == val {
|
||||
matches++
|
||||
}
|
||||
}
|
||||
if matches == len(c.Exclude) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for key, val := range c.Include {
|
||||
if params[key] != val {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements custom Yaml unmarshaling.
|
||||
func (c *ConstraintMap) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
|
||||
out1 := struct {
|
||||
Include map[string]string
|
||||
Exclude map[string]string
|
||||
}{
|
||||
Include: map[string]string{},
|
||||
Exclude: map[string]string{},
|
||||
}
|
||||
|
||||
out2 := map[string]string{}
|
||||
|
||||
unmarshal(&out1)
|
||||
unmarshal(&out2)
|
||||
|
||||
c.Include = out1.Include
|
||||
c.Exclude = out1.Exclude
|
||||
for k, v := range out2 {
|
||||
c.Include[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func TestConstraint(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Constraint", func() {
|
||||
|
||||
g.It("Should parse and match emtpy", func() {
|
||||
c := parseConstraint("")
|
||||
g.Assert(c.Match("master")).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should parse and match", func() {
|
||||
c := parseConstraint("{ include: [ master, develop ] }")
|
||||
g.Assert(c.Include[0]).Equal("master")
|
||||
g.Assert(c.Include[1]).Equal("develop")
|
||||
g.Assert(c.Match("master")).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should parse and match shortand", func() {
|
||||
c := parseConstraint("[ master, develop ]")
|
||||
g.Assert(c.Include[0]).Equal("master")
|
||||
g.Assert(c.Include[1]).Equal("develop")
|
||||
g.Assert(c.Match("master")).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should parse and match shortand string", func() {
|
||||
c := parseConstraint("master")
|
||||
g.Assert(c.Include[0]).Equal("master")
|
||||
g.Assert(c.Match("master")).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should parse and match exclude", func() {
|
||||
c := parseConstraint("{ exclude: [ master, develop ] }")
|
||||
g.Assert(c.Exclude[0]).Equal("master")
|
||||
g.Assert(c.Exclude[1]).Equal("develop")
|
||||
g.Assert(c.Match("master")).IsFalse()
|
||||
})
|
||||
|
||||
g.It("Should parse and match exclude shorthand", func() {
|
||||
c := parseConstraint("{ exclude: master }")
|
||||
g.Assert(c.Exclude[0]).Equal("master")
|
||||
g.Assert(c.Match("master")).IsFalse()
|
||||
})
|
||||
|
||||
g.It("Should match include", func() {
|
||||
b := Constraint{}
|
||||
b.Include = []string{"master"}
|
||||
g.Assert(b.Match("master")).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should match include pattern", func() {
|
||||
b := Constraint{}
|
||||
b.Include = []string{"feature/*"}
|
||||
g.Assert(b.Match("feature/foo")).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should fail to match include pattern", func() {
|
||||
b := Constraint{}
|
||||
b.Include = []string{"feature/*"}
|
||||
g.Assert(b.Match("master")).IsFalse()
|
||||
})
|
||||
|
||||
g.It("Should match exclude", func() {
|
||||
b := Constraint{}
|
||||
b.Exclude = []string{"master"}
|
||||
g.Assert(b.Match("master")).IsFalse()
|
||||
})
|
||||
|
||||
g.It("Should match exclude pattern", func() {
|
||||
b := Constraint{}
|
||||
b.Exclude = []string{"feature/*"}
|
||||
g.Assert(b.Match("feature/foo")).IsFalse()
|
||||
})
|
||||
|
||||
g.It("Should match when eclude patterns mismatch", func() {
|
||||
b := Constraint{}
|
||||
b.Exclude = []string{"foo"}
|
||||
g.Assert(b.Match("bar")).IsTrue()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestConstraintMap(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Constraint Map", func() {
|
||||
g.It("Should parse and match emtpy", func() {
|
||||
p := map[string]string{"golang": "1.5", "redis": "3.2"}
|
||||
c := parseConstraintMap("")
|
||||
g.Assert(c.Match(p)).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should parse and match", func() {
|
||||
p := map[string]string{"golang": "1.5", "redis": "3.2"}
|
||||
c := parseConstraintMap("{ include: { golang: 1.5 } }")
|
||||
g.Assert(c.Include["golang"]).Equal("1.5")
|
||||
g.Assert(c.Match(p)).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should parse and match shortand", func() {
|
||||
p := map[string]string{"golang": "1.5", "redis": "3.2"}
|
||||
c := parseConstraintMap("{ golang: 1.5 }")
|
||||
g.Assert(c.Include["golang"]).Equal("1.5")
|
||||
g.Assert(c.Match(p)).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should parse and match exclude", func() {
|
||||
p := map[string]string{"golang": "1.5", "redis": "3.2"}
|
||||
c := parseConstraintMap("{ exclude: { golang: 1.5 } }")
|
||||
g.Assert(c.Exclude["golang"]).Equal("1.5")
|
||||
g.Assert(c.Match(p)).IsFalse()
|
||||
})
|
||||
|
||||
g.It("Should parse and mismatch exclude", func() {
|
||||
p := map[string]string{"golang": "1.5", "redis": "3.2"}
|
||||
c := parseConstraintMap("{ exclude: { golang: 1.5, redis: 2.8 } }")
|
||||
g.Assert(c.Exclude["golang"]).Equal("1.5")
|
||||
g.Assert(c.Exclude["redis"]).Equal("2.8")
|
||||
g.Assert(c.Match(p)).IsTrue()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func parseConstraint(s string) *Constraint {
|
||||
c := &Constraint{}
|
||||
yaml.Unmarshal([]byte(s), c)
|
||||
return c
|
||||
}
|
||||
|
||||
func parseConstraintMap(s string) *ConstraintMap {
|
||||
c := &ConstraintMap{}
|
||||
yaml.Unmarshal([]byte(s), c)
|
||||
return c
|
||||
}
|
|
@ -1,166 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/drone/drone/yaml/types"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Auth defines Docker authentication credentials.
|
||||
type Auth struct {
|
||||
Username string
|
||||
Password string
|
||||
Email string
|
||||
}
|
||||
|
||||
// Container defines a Docker container.
|
||||
type Container struct {
|
||||
ID string
|
||||
Name string
|
||||
Image string
|
||||
Build string
|
||||
Pull bool
|
||||
AuthConfig Auth
|
||||
Detached bool
|
||||
Disabled bool
|
||||
Privileged bool
|
||||
WorkingDir string
|
||||
Environment map[string]string
|
||||
Labels map[string]string
|
||||
Entrypoint []string
|
||||
Command []string
|
||||
Commands []string
|
||||
ExtraHosts []string
|
||||
Volumes []string
|
||||
VolumesFrom []string
|
||||
Devices []string
|
||||
Network string
|
||||
DNS []string
|
||||
DNSSearch []string
|
||||
MemSwapLimit int64
|
||||
MemLimit int64
|
||||
ShmSize int64
|
||||
CPUQuota int64
|
||||
CPUShares int64
|
||||
CPUSet string
|
||||
OomKillDisable bool
|
||||
Constraints Constraints
|
||||
|
||||
Vargs map[string]interface{}
|
||||
}
|
||||
|
||||
// container is an intermediate type used for decoding a container in a format
|
||||
// compatible with docker-compose.yml.
|
||||
|
||||
// this file has a bunch of custom types that are annoying to work with, which
|
||||
// is why this is used for intermediate purposes and converted to something
|
||||
// easier to work with.
|
||||
type container struct {
|
||||
Name string `yaml:"name"`
|
||||
Image string `yaml:"image"`
|
||||
Build string `yaml:"build"`
|
||||
Pull bool `yaml:"pull"`
|
||||
Detached bool `yaml:"detach"`
|
||||
Privileged bool `yaml:"privileged"`
|
||||
Environment types.MapEqualSlice `yaml:"environment"`
|
||||
Labels types.MapEqualSlice `yaml:"labels"`
|
||||
Entrypoint types.StringOrSlice `yaml:"entrypoint"`
|
||||
Command types.StringOrSlice `yaml:"command"`
|
||||
Commands types.StringOrSlice `yaml:"commands"`
|
||||
ExtraHosts types.StringOrSlice `yaml:"extra_hosts"`
|
||||
Volumes types.StringOrSlice `yaml:"volumes"`
|
||||
VolumesFrom types.StringOrSlice `yaml:"volumes_from"`
|
||||
Devices types.StringOrSlice `yaml:"devices"`
|
||||
Network string `yaml:"network_mode"`
|
||||
DNS types.StringOrSlice `yaml:"dns"`
|
||||
DNSSearch types.StringOrSlice `yaml:"dns_search"`
|
||||
MemSwapLimit int64 `yaml:"memswap_limit"`
|
||||
MemLimit int64 `yaml:"mem_limit"`
|
||||
ShmSize int64 `yaml:"shm_size"`
|
||||
CPUQuota int64 `yaml:"cpu_quota"`
|
||||
CPUShares int64 `yaml:"cpu_shares"`
|
||||
CPUSet string `yaml:"cpuset"`
|
||||
OomKillDisable bool `yaml:"oom_kill_disable"`
|
||||
|
||||
AuthConfig struct {
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
Email string `yaml:"email"`
|
||||
Token string `yaml:"registry_token"`
|
||||
} `yaml:"auth_config"`
|
||||
|
||||
Constraints Constraints `yaml:"when"`
|
||||
|
||||
Vargs map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// containerList is an intermediate type used for decoding a slice of containers
|
||||
// in a format compatible with docker-compose.yml
|
||||
type containerList struct {
|
||||
containers []*Container
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements custom Yaml unmarshaling.
|
||||
func (c *containerList) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
slice := yaml.MapSlice{}
|
||||
err := unmarshal(&slice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range slice {
|
||||
cc := container{}
|
||||
|
||||
out, merr := yaml.Marshal(s.Value)
|
||||
if err != nil {
|
||||
return merr
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(out, &cc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cc.Name == "" {
|
||||
cc.Name = fmt.Sprintf("%v", s.Key)
|
||||
}
|
||||
if cc.Image == "" {
|
||||
cc.Image = fmt.Sprintf("%v", s.Key)
|
||||
}
|
||||
c.containers = append(c.containers, &Container{
|
||||
Name: cc.Name,
|
||||
Image: cc.Image,
|
||||
Build: cc.Build,
|
||||
Pull: cc.Pull,
|
||||
Detached: cc.Detached,
|
||||
Privileged: cc.Privileged,
|
||||
Environment: cc.Environment.Map(),
|
||||
Labels: cc.Labels.Map(),
|
||||
Entrypoint: cc.Entrypoint.Slice(),
|
||||
Command: cc.Command.Slice(),
|
||||
Commands: cc.Commands.Slice(),
|
||||
ExtraHosts: cc.ExtraHosts.Slice(),
|
||||
Volumes: cc.Volumes.Slice(),
|
||||
VolumesFrom: cc.VolumesFrom.Slice(),
|
||||
Devices: cc.Devices.Slice(),
|
||||
Network: cc.Network,
|
||||
DNS: cc.DNS.Slice(),
|
||||
DNSSearch: cc.DNSSearch.Slice(),
|
||||
MemSwapLimit: cc.MemSwapLimit,
|
||||
MemLimit: cc.MemLimit,
|
||||
ShmSize: cc.ShmSize,
|
||||
CPUQuota: cc.CPUQuota,
|
||||
CPUShares: cc.CPUShares,
|
||||
CPUSet: cc.CPUSet,
|
||||
OomKillDisable: cc.OomKillDisable,
|
||||
Vargs: cc.Vargs,
|
||||
AuthConfig: Auth{
|
||||
Username: cc.AuthConfig.Username,
|
||||
Password: cc.AuthConfig.Password,
|
||||
Email: cc.AuthConfig.Email,
|
||||
},
|
||||
Constraints: cc.Constraints,
|
||||
})
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func TestContainerNode(t *testing.T) {
|
||||
g := goblin.Goblin(t)
|
||||
|
||||
g.Describe("Containers", func() {
|
||||
g.Describe("given a yaml file", func() {
|
||||
|
||||
g.It("should unmarshal", func() {
|
||||
in := []byte(sampleContainer)
|
||||
out := containerList{}
|
||||
err := yaml.Unmarshal(in, &out)
|
||||
if err != nil {
|
||||
g.Fail(err)
|
||||
}
|
||||
g.Assert(len(out.containers)).Equal(1)
|
||||
|
||||
c := out.containers[0]
|
||||
g.Assert(c.Name).Equal("foo")
|
||||
g.Assert(c.Image).Equal("golang")
|
||||
g.Assert(c.Build).Equal(".")
|
||||
g.Assert(c.Pull).Equal(true)
|
||||
g.Assert(c.Detached).Equal(true)
|
||||
g.Assert(c.Privileged).Equal(true)
|
||||
g.Assert(c.Entrypoint).Equal([]string{"/bin/sh"})
|
||||
g.Assert(c.Command).Equal([]string{"yes"})
|
||||
g.Assert(c.Commands).Equal([]string{"whoami"})
|
||||
g.Assert(c.ExtraHosts).Equal([]string{"foo.com"})
|
||||
g.Assert(c.Volumes).Equal([]string{"/foo:/bar"})
|
||||
g.Assert(c.VolumesFrom).Equal([]string{"foo"})
|
||||
g.Assert(c.Devices).Equal([]string{"/dev/tty0"})
|
||||
g.Assert(c.Network).Equal("bridge")
|
||||
g.Assert(c.DNS).Equal([]string{"8.8.8.8"})
|
||||
g.Assert(c.MemSwapLimit).Equal(int64(1))
|
||||
g.Assert(c.MemLimit).Equal(int64(2))
|
||||
g.Assert(c.CPUQuota).Equal(int64(3))
|
||||
g.Assert(c.CPUSet).Equal("1,2")
|
||||
g.Assert(c.OomKillDisable).Equal(true)
|
||||
g.Assert(c.AuthConfig.Username).Equal("octocat")
|
||||
g.Assert(c.AuthConfig.Password).Equal("password")
|
||||
g.Assert(c.AuthConfig.Email).Equal("octocat@github.com")
|
||||
g.Assert(c.Vargs["access_key"]).Equal("970d28f4dd477bc184fbd10b376de753")
|
||||
g.Assert(c.Vargs["secret_key"]).Equal("9c5785d3ece6a9cdefa42eb99b58986f9095ff1c")
|
||||
})
|
||||
|
||||
g.It("should unmarshal named", func() {
|
||||
in := []byte("foo: { name: bar }")
|
||||
out := containerList{}
|
||||
err := yaml.Unmarshal(in, &out)
|
||||
if err != nil {
|
||||
g.Fail(err)
|
||||
}
|
||||
g.Assert(len(out.containers)).Equal(1)
|
||||
g.Assert(out.containers[0].Name).Equal("bar")
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var sampleContainer = `
|
||||
foo:
|
||||
image: golang
|
||||
build: .
|
||||
pull: true
|
||||
detach: true
|
||||
privileged: true
|
||||
environment:
|
||||
FOO: BAR
|
||||
entrypoint: /bin/sh
|
||||
command: "yes"
|
||||
commands: whoami
|
||||
extra_hosts: foo.com
|
||||
volumes: /foo:/bar
|
||||
volumes_from: foo
|
||||
devices: /dev/tty0
|
||||
network_mode: bridge
|
||||
dns: 8.8.8.8
|
||||
memswap_limit: 1
|
||||
mem_limit: 2
|
||||
cpu_quota: 3
|
||||
cpuset: 1,2
|
||||
oom_kill_disable: true
|
||||
|
||||
auth_config:
|
||||
username: octocat
|
||||
password: password
|
||||
email: octocat@github.com
|
||||
|
||||
access_key: 970d28f4dd477bc184fbd10b376de753
|
||||
secret_key: 9c5785d3ece6a9cdefa42eb99b58986f9095ff1c
|
||||
`
|
|
@ -1,26 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/drone/drone/yaml/types"
|
||||
)
|
||||
|
||||
// ParseLabel parses the labels section of the Yaml document.
|
||||
func ParseLabel(in []byte) map[string]string {
|
||||
out := struct {
|
||||
Labels types.MapEqualSlice `yaml:"labels"`
|
||||
}{}
|
||||
|
||||
yaml.Unmarshal(in, &out)
|
||||
labels := out.Labels.Map()
|
||||
if labels == nil {
|
||||
labels = make(map[string]string)
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
// ParseLabelString parses the labels section of the Yaml document.
|
||||
func ParseLabelString(in string) map[string]string {
|
||||
return ParseLabel([]byte(in))
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestLabel(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Label parser", func() {
|
||||
|
||||
g.It("Should parse empty yaml", func() {
|
||||
labels := ParseLabelString("")
|
||||
g.Assert(len(labels)).Equal(0)
|
||||
})
|
||||
|
||||
g.It("Should parse slice", func() {
|
||||
labels := ParseLabelString("labels: [foo=bar, baz=boo]")
|
||||
g.Assert(len(labels)).Equal(2)
|
||||
g.Assert(labels["foo"]).Equal("bar")
|
||||
g.Assert(labels["baz"]).Equal("boo")
|
||||
})
|
||||
|
||||
g.It("Should parse map", func() {
|
||||
labels := ParseLabelString("labels: {foo: bar, baz: boo}")
|
||||
g.Assert(labels["foo"]).Equal("bar")
|
||||
g.Assert(labels["baz"]).Equal("boo")
|
||||
})
|
||||
})
|
||||
}
|
117
yaml/matrix.go
117
yaml/matrix.go
|
@ -1,117 +0,0 @@
|
|||
package yaml
|
||||
|
||||
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, " ")
|
||||
}
|
||||
|
||||
// ParseMatrix parses the Yaml matrix definition.
|
||||
func ParseMatrix(data []byte) ([]Axis, error) {
|
||||
|
||||
axis, err := parseMatrixList(data)
|
||||
if err == nil && len(axis) != 0 {
|
||||
return axis, nil
|
||||
}
|
||||
|
||||
matrix, err := parseMatrix(data)
|
||||
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 calcMatrix(matrix), nil
|
||||
}
|
||||
|
||||
// ParseMatrixString parses the Yaml string matrix definition.
|
||||
func ParseMatrixString(data string) ([]Axis, error) {
|
||||
return ParseMatrix([]byte(data))
|
||||
}
|
||||
|
||||
func calcMatrix(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
|
||||
}
|
||||
|
||||
func parseMatrix(raw []byte) (Matrix, error) {
|
||||
data := struct {
|
||||
Matrix map[string][]string
|
||||
}{}
|
||||
err := yaml.Unmarshal(raw, &data)
|
||||
return data.Matrix, err
|
||||
}
|
||||
|
||||
func parseMatrixList(raw []byte) ([]Axis, error) {
|
||||
data := struct {
|
||||
Matrix struct {
|
||||
Include []Axis
|
||||
}
|
||||
}{}
|
||||
|
||||
err := yaml.Unmarshal(raw, &data)
|
||||
return data.Matrix.Include, err
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestMatrix(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Calculate matrix", func() {
|
||||
|
||||
axis, _ := ParseMatrixString(fakeMatrix)
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
g.It("Should return nil if no matrix", func() {
|
||||
axis, err := ParseMatrixString("")
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(axis == nil).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should return included axis", func() {
|
||||
axis, err := ParseMatrixString(fakeMatrixInclude)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(len(axis)).Equal(2)
|
||||
g.Assert(axis[0]["go_version"]).Equal("1.5")
|
||||
g.Assert(axis[1]["go_version"]).Equal("1.6")
|
||||
g.Assert(axis[0]["python_version"]).Equal("3.4")
|
||||
g.Assert(axis[1]["python_version"]).Equal("3.4")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var fakeMatrix = `
|
||||
matrix:
|
||||
go_version:
|
||||
- go1
|
||||
- go1.2
|
||||
python_version:
|
||||
- 3.2
|
||||
- 3.3
|
||||
django_version:
|
||||
- 1.7
|
||||
- 1.7.1
|
||||
- 1.7.2
|
||||
redis_version:
|
||||
- 2.6
|
||||
- 2.8
|
||||
`
|
||||
|
||||
var fakeMatrixInclude = `
|
||||
matrix:
|
||||
include:
|
||||
- go_version: 1.5
|
||||
python_version: 3.4
|
||||
- go_version: 1.6
|
||||
python_version: 3.4
|
||||
`
|
|
@ -1,51 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Network defines a Docker network.
|
||||
type Network struct {
|
||||
Name string
|
||||
Driver string
|
||||
DriverOpts map[string]string `yaml:"driver_opts"`
|
||||
}
|
||||
|
||||
// networkList is an intermediate type used for decoding a slice of networks
|
||||
// in a format compatible with docker-compose.yml
|
||||
type networkList struct {
|
||||
networks []*Network
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements custom Yaml unmarshaling.
|
||||
func (n *networkList) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
slice := yaml.MapSlice{}
|
||||
err := unmarshal(&slice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range slice {
|
||||
nn := Network{}
|
||||
|
||||
out, merr := yaml.Marshal(s.Value)
|
||||
if merr != nil {
|
||||
return merr
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(out, &nn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if nn.Name == "" {
|
||||
nn.Name = fmt.Sprintf("%v", s.Key)
|
||||
}
|
||||
if nn.Driver == "" {
|
||||
nn.Driver = "bridge"
|
||||
}
|
||||
n.networks = append(n.networks, &nn)
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func TestNetworks(t *testing.T) {
|
||||
g := goblin.Goblin(t)
|
||||
|
||||
g.Describe("Networks", func() {
|
||||
g.Describe("given a yaml file", func() {
|
||||
|
||||
g.It("should unmarshal", func() {
|
||||
in := []byte("foo: { driver: overlay }")
|
||||
out := networkList{}
|
||||
err := yaml.Unmarshal(in, &out)
|
||||
if err != nil {
|
||||
g.Fail(err)
|
||||
}
|
||||
g.Assert(len(out.networks)).Equal(1)
|
||||
g.Assert(out.networks[0].Name).Equal("foo")
|
||||
g.Assert(out.networks[0].Driver).Equal("overlay")
|
||||
})
|
||||
|
||||
g.It("should unmarshal named", func() {
|
||||
in := []byte("foo: { name: bar }")
|
||||
out := networkList{}
|
||||
err := yaml.Unmarshal(in, &out)
|
||||
if err != nil {
|
||||
g.Fail(err)
|
||||
}
|
||||
g.Assert(len(out.networks)).Equal(1)
|
||||
g.Assert(out.networks[0].Name).Equal("bar")
|
||||
})
|
||||
|
||||
g.It("should unmarshal and use default driver", func() {
|
||||
in := []byte("foo: { name: bar }")
|
||||
out := networkList{}
|
||||
err := yaml.Unmarshal(in, &out)
|
||||
if err != nil {
|
||||
g.Fail(err)
|
||||
}
|
||||
g.Assert(len(out.networks)).Equal(1)
|
||||
g.Assert(out.networks[0].Driver).Equal("bridge")
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import "gopkg.in/yaml.v2"
|
||||
|
||||
// ParsePlatform parses the platform section of the Yaml document.
|
||||
func ParsePlatform(in []byte) string {
|
||||
out := struct {
|
||||
Platform string `yaml:"platform"`
|
||||
}{}
|
||||
|
||||
yaml.Unmarshal(in, &out)
|
||||
return out.Platform
|
||||
}
|
||||
|
||||
// ParsePlatformString parses the platform section of the Yaml document.
|
||||
func ParsePlatformString(in string) string {
|
||||
return ParsePlatform([]byte(in))
|
||||
}
|
||||
|
||||
// ParsePlatformDefault parses the platform section of the Yaml document.
|
||||
func ParsePlatformDefault(in []byte, platform string) string {
|
||||
if p := ParsePlatform([]byte(in)); p != "" {
|
||||
return p
|
||||
}
|
||||
return platform
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package transform
|
||||
|
||||
import "github.com/drone/drone/yaml"
|
||||
|
||||
const clone = "clone"
|
||||
|
||||
// Clone transforms the Yaml to include a clone step.
|
||||
func Clone(c *yaml.Config, plugin string) error {
|
||||
switch plugin {
|
||||
case "", "git":
|
||||
plugin = "plugins/git:latest"
|
||||
case "hg":
|
||||
plugin = "plugins/hg:latest"
|
||||
}
|
||||
|
||||
for _, p := range c.Pipeline {
|
||||
if p.Name == clone {
|
||||
if p.Image == "" {
|
||||
p.Image = plugin
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
s := &yaml.Container{
|
||||
Image: plugin,
|
||||
Name: clone,
|
||||
}
|
||||
|
||||
c.Pipeline = append([]*yaml.Container{s}, c.Pipeline...)
|
||||
return nil
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package transform
|
|
@ -1,83 +0,0 @@
|
|||
package transform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/drone/drone/yaml"
|
||||
)
|
||||
|
||||
// CommandTransform transforms the custom shell commands in the Yaml pipeline
|
||||
// into a container ENTRYPOINT and and CMD for execution.
|
||||
func CommandTransform(c *yaml.Config) error {
|
||||
for _, p := range c.Pipeline {
|
||||
|
||||
if isPlugin(p) {
|
||||
continue
|
||||
}
|
||||
|
||||
p.Entrypoint = []string{
|
||||
"/bin/sh", "-c",
|
||||
}
|
||||
p.Command = []string{
|
||||
"echo $DRONE_SCRIPT | base64 -d | /bin/sh -e",
|
||||
}
|
||||
if p.Environment == nil {
|
||||
p.Environment = map[string]string{}
|
||||
}
|
||||
p.Environment["HOME"] = "/root"
|
||||
p.Environment["SHELL"] = "/bin/sh"
|
||||
p.Environment["DRONE_SCRIPT"] = toScript(
|
||||
p.Commands,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toScript(commands []string) string {
|
||||
var buf bytes.Buffer
|
||||
for _, command := range commands {
|
||||
escaped := fmt.Sprintf("%q", command)
|
||||
escaped = strings.Replace(escaped, "$", `\$`, -1)
|
||||
buf.WriteString(fmt.Sprintf(
|
||||
traceScript,
|
||||
escaped,
|
||||
command,
|
||||
))
|
||||
}
|
||||
|
||||
script := fmt.Sprintf(
|
||||
setupScript,
|
||||
buf.String(),
|
||||
)
|
||||
|
||||
return base64.StdEncoding.EncodeToString([]byte(script))
|
||||
}
|
||||
|
||||
// setupScript is a helper script this is added to the build to ensure
|
||||
// a minimum set of environment variables are set correctly.
|
||||
const setupScript = `
|
||||
if [ -n "$DRONE_NETRC_MACHINE" ]; then
|
||||
cat <<EOF > $HOME/.netrc
|
||||
machine $DRONE_NETRC_MACHINE
|
||||
login $DRONE_NETRC_USERNAME
|
||||
password $DRONE_NETRC_PASSWORD
|
||||
EOF
|
||||
chmod 0600 $HOME/.netrc
|
||||
fi
|
||||
|
||||
unset DRONE_NETRC_USERNAME
|
||||
unset DRONE_NETRC_PASSWORD
|
||||
unset DRONE_SCRIPT
|
||||
|
||||
%s
|
||||
`
|
||||
|
||||
// traceScript is a helper script that is added to the build script
|
||||
// to trace a command.
|
||||
const traceScript = `
|
||||
echo + %s
|
||||
%s
|
||||
`
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue