mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-22 09:51:01 +00:00
put updated agent behind flag
This commit is contained in:
parent
d7758a23e0
commit
623be6d830
58 changed files with 4824 additions and 23 deletions
|
@ -1,6 +1,8 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
@ -60,7 +62,7 @@ var AgentCmd = cli.Command{
|
|||
Value: "amd64",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_SERVER",
|
||||
EnvVar: "DRONE_SERVER,DRONE_ENDPOINT",
|
||||
Name: "drone-server",
|
||||
Usage: "drone server address",
|
||||
Value: "ws://localhost:8000/ws/broker",
|
||||
|
@ -138,11 +140,55 @@ var AgentCmd = cli.Command{
|
|||
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",
|
||||
Value: 1,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "platform",
|
||||
EnvVar: "DRONE_PLATFORM",
|
||||
Value: "linux/amd64",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -187,7 +233,7 @@ func start(c *cli.Context) {
|
|||
client.Ack(m.Ack)
|
||||
}()
|
||||
|
||||
r := pipeline{
|
||||
r := pipelinet{
|
||||
drone: client,
|
||||
docker: docker,
|
||||
config: config{
|
||||
|
|
|
@ -22,13 +22,13 @@ type config struct {
|
|||
extension []string
|
||||
}
|
||||
|
||||
type pipeline struct {
|
||||
type pipelinet struct {
|
||||
drone *stomp.Client
|
||||
docker dockerclient.Client
|
||||
config config
|
||||
}
|
||||
|
||||
func (r *pipeline) run(w *model.Work) {
|
||||
func (r *pipelinet) run(w *model.Work) {
|
||||
|
||||
// defer func() {
|
||||
// // r.drone.Ack(id, opts)
|
||||
|
|
191
drone/agent/exp.go
Normal file
191
drone/agent/exp.go
Normal file
|
@ -0,0 +1,191 @@
|
|||
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
|
||||
}
|
||||
|
||||
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); err != nil {
|
||||
log.Printf("build runner encountered error: exiting: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func run(ctx context.Context, client rpc.Peer) error {
|
||||
log.Println("pipeline: request next execution")
|
||||
|
||||
// get the next job from the queue
|
||||
work, err := client.Next(ctx)
|
||||
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() {
|
||||
ok, _ := client.Notify(ctx, work.ID)
|
||||
if ok {
|
||||
cancelled.SetTo(true)
|
||||
log.Printf("pipeline: cancel signal received: %s", work.ID)
|
||||
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)
|
||||
}
|
||||
|
||||
defaultLogger := pipeline.LogFunc(func(proc *backend.Step, rc multipart.Reader) error {
|
||||
part, rerr := rc.NextPart()
|
||||
if rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
writer := rpc.NewLineWriter(client, work.ID, proc.Alias)
|
||||
io.Copy(writer, part)
|
||||
|
||||
defer func() {
|
||||
log.Printf("pipeline: finish uploading logs: %s: step %s", work.ID, proc.Alias)
|
||||
}()
|
||||
|
||||
part, rerr = rc.NextPart()
|
||||
if rerr != nil {
|
||||
return nil
|
||||
}
|
||||
mime := part.Header().Get("Content-Type")
|
||||
if serr := client.Save(context.Background(), work.ID, mime, part); 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 = 130
|
||||
} else if state.ExitCode == 0 {
|
||||
state.ExitCode = 1
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("pipeline: execution complete: %s", work.ID)
|
||||
|
||||
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
|
||||
}
|
|
@ -2,6 +2,7 @@ package router
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
|
@ -119,19 +120,46 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
|
|||
badges.GET("/cc.xml", server.GetCC)
|
||||
}
|
||||
|
||||
e.POST("/hook", server.PostHook)
|
||||
e.POST("/api/hook", server.PostHook)
|
||||
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)
|
||||
}
|
||||
|
||||
ws := e.Group("/ws")
|
||||
{
|
||||
ws.GET("/broker", server.Broker)
|
||||
ws.GET("/feed", server.EventStream)
|
||||
ws.GET("/logs/:owner/:name/:build/:number",
|
||||
session.SetRepo(),
|
||||
session.SetPerm(),
|
||||
session.MustPull,
|
||||
server.LogStream,
|
||||
)
|
||||
if os.Getenv("DRONE_CANARY") == "" {
|
||||
ws := e.Group("/ws")
|
||||
{
|
||||
ws.GET("/broker", server.Broker)
|
||||
ws.GET("/feed", server.EventStream)
|
||||
ws.GET("/logs/:owner/:name/:build/:number",
|
||||
session.SetRepo(),
|
||||
session.SetPerm(),
|
||||
session.MustPull,
|
||||
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",
|
||||
session.MustAdmin(),
|
||||
server.GetQueueInfo,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
auth := e.Group("/authorize")
|
||||
|
|
412
server/hook.go
412
server/hook.go
|
@ -1,9 +1,13 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/square/go-jose"
|
||||
|
@ -15,7 +19,18 @@ import (
|
|||
"github.com/drone/drone/shared/token"
|
||||
"github.com/drone/drone/store"
|
||||
"github.com/drone/drone/yaml"
|
||||
"github.com/drone/envsubst"
|
||||
"github.com/drone/mq/stomp"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
"github.com/cncd/pipeline/pipeline/frontend"
|
||||
yaml2 "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"
|
||||
)
|
||||
|
||||
var skipRe = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`)
|
||||
|
@ -105,8 +120,8 @@ func PostHook(c *gin.Context) {
|
|||
// 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, err := store.GetUserLogin(c, build.Author)
|
||||
if err == nil {
|
||||
author, uerr := store.GetUserLogin(c, build.Author)
|
||||
if uerr == nil {
|
||||
build.Email = author.Email
|
||||
}
|
||||
}
|
||||
|
@ -164,9 +179,9 @@ func PostHook(c *gin.Context) {
|
|||
log.Debugf("cannot parse .drone.yml.sig file. empty file")
|
||||
} else {
|
||||
build.Signed = true
|
||||
output, err := signature.Verify([]byte(repo.Hash))
|
||||
if err != nil {
|
||||
log.Debugf("cannot verify .drone.yml.sig file. %s", err)
|
||||
output, verr := signature.Verify([]byte(repo.Hash))
|
||||
if verr != nil {
|
||||
log.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")
|
||||
} else {
|
||||
|
@ -212,7 +227,7 @@ func PostHook(c *gin.Context) {
|
|||
}
|
||||
|
||||
client := stomp.MustFromContext(c)
|
||||
client.SendJSON("/topic/events", model.Event{
|
||||
client.SendJSON("topic/events", model.Event{
|
||||
Type: model.Enqueued,
|
||||
Repo: *repo,
|
||||
Build: *build,
|
||||
|
@ -247,3 +262,388 @@ func PostHook(c *gin.Context) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// 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 GetQueueInfo(c *gin.Context) {
|
||||
c.IndentedJSON(200,
|
||||
config.queue.Info(c),
|
||||
)
|
||||
}
|
||||
|
||||
func PostHook2(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)
|
||||
c.AbortWithError(400, err)
|
||||
return
|
||||
}
|
||||
if build == nil {
|
||||
c.Writer.WriteHeader(200)
|
||||
return
|
||||
}
|
||||
if tmprepo == nil {
|
||||
log.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 {
|
||||
log.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)
|
||||
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 {
|
||||
log.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)
|
||||
c.AbortWithStatus(403)
|
||||
return
|
||||
}
|
||||
|
||||
if repo.UserID == 0 {
|
||||
log.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 {
|
||||
log.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)
|
||||
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.
|
||||
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 {
|
||||
log.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 {
|
||||
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 := 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 := yaml2.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)
|
||||
} else if len(sec) == 0 {
|
||||
log.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)
|
||||
} else if string(output) != string(raw) {
|
||||
log.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 {
|
||||
log.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 {
|
||||
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)
|
||||
}
|
||||
|
||||
//
|
||||
// new code here
|
||||
//
|
||||
|
||||
message := pubsub.Message{}
|
||||
message.Data, _ = json.Marshal(model.Event{
|
||||
Type: model.Enqueued,
|
||||
Repo: *repo,
|
||||
Build: *build,
|
||||
})
|
||||
message.Labels = map[string]string{
|
||||
"repo": repo.FullName,
|
||||
"private": strconv.FormatBool(repo.IsPrivate),
|
||||
}
|
||||
// TODO remove global reference
|
||||
config.pubsub.Publish(c, "topic/events", message)
|
||||
|
||||
//
|
||||
// workspace
|
||||
//
|
||||
|
||||
var (
|
||||
link, _ = url.Parse(repo.Link)
|
||||
base = "/drone"
|
||||
path = "src/" + link.Host + "/" + repo.FullName
|
||||
)
|
||||
|
||||
for _, job := range jobs {
|
||||
|
||||
metadata := metadataFromStruct(repo, build, last, job, "linux/amd64")
|
||||
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 := yaml2.ParseBytes(raw)
|
||||
if err != nil {
|
||||
// TODO
|
||||
}
|
||||
|
||||
lerr := linter.New(
|
||||
linter.WithTrusted(repo.IsTrusted),
|
||||
).Lint(parsed)
|
||||
if lerr != nil {
|
||||
// TODO
|
||||
}
|
||||
|
||||
ir := compiler.New(
|
||||
compiler.WithEnviron(environ),
|
||||
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.WithProxy(),
|
||||
compiler.WithVolumes(), // todo set global volumes
|
||||
compiler.WithWorkspace(base, path),
|
||||
).Compile(parsed)
|
||||
|
||||
task := new(queue.Task)
|
||||
task.ID = fmt.Sprint(job.ID)
|
||||
task.Labels = map[string]string{}
|
||||
task.Labels["platform"] = "linux/amd64"
|
||||
// TODO set proper platform
|
||||
// TODO set proper labels
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// use helper funciton to return ([]backend.Config, error)
|
||||
|
||||
type builder struct {
|
||||
secs []*model.Secret
|
||||
repo *model.Repo
|
||||
build *model.Build
|
||||
last *model.Build
|
||||
jobs []*model.Job
|
||||
link string
|
||||
}
|
||||
|
||||
func (b *builder) Build() ([]*backend.Config, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 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",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
221
server/rpc.go
Normal file
221
server/rpc.go
Normal file
|
@ -0,0 +1,221 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/cncd/logging"
|
||||
"github.com/cncd/pipeline/pipeline/rpc"
|
||||
"github.com/cncd/pubsub"
|
||||
"github.com/cncd/queue"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/remote"
|
||||
"github.com/drone/drone/store"
|
||||
)
|
||||
|
||||
// This file is a complete disaster because I'm trying to wedge in some
|
||||
// experimental code. Please pardon our appearance during renovations.
|
||||
|
||||
var config = struct {
|
||||
pubsub pubsub.Publisher
|
||||
queue queue.Queue
|
||||
logger logging.Log
|
||||
secret string
|
||||
host string
|
||||
}{
|
||||
pubsub.New(),
|
||||
queue.New(),
|
||||
logging.New(),
|
||||
os.Getenv("DRONE_SECRET"),
|
||||
os.Getenv("DRONE_HOST"),
|
||||
}
|
||||
|
||||
func init() {
|
||||
config.pubsub.Create(context.Background(), "topic/events")
|
||||
}
|
||||
|
||||
// func SetupRPC() gin.HandlerFunc {
|
||||
// return func(c *gin.Context) {
|
||||
// c.Next()
|
||||
// }
|
||||
// }
|
||||
|
||||
func RPCHandler(c *gin.Context) {
|
||||
|
||||
fmt.Println(c.Request.Header.Write(os.Stdout))
|
||||
if secret := c.Request.Header.Get("Authorization"); secret != "Bearer "+config.secret {
|
||||
log.Printf("Unable to connect agent. Invalid authorization token %q does not match %q", secret, config.secret)
|
||||
c.String(401, "Unable to connect agent. Invalid authorization token")
|
||||
return
|
||||
}
|
||||
peer := RPC{
|
||||
remote: remote.FromContext(c),
|
||||
store: store.FromContext(c),
|
||||
queue: config.queue,
|
||||
pubsub: config.pubsub,
|
||||
logger: config.logger,
|
||||
host: config.host,
|
||||
}
|
||||
rpc.NewServer(&peer).ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
|
||||
type RPC struct {
|
||||
remote remote.Remote
|
||||
queue queue.Queue
|
||||
pubsub pubsub.Publisher
|
||||
logger logging.Log
|
||||
store store.Store
|
||||
host string
|
||||
}
|
||||
|
||||
// Next implements the rpc.Next function
|
||||
func (s *RPC) Next(c context.Context) (*rpc.Pipeline, error) {
|
||||
filter := func(*queue.Task) bool { return true }
|
||||
task, err := s.queue.Poll(c, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if task == nil {
|
||||
return nil, nil
|
||||
}
|
||||
pipeline := new(rpc.Pipeline)
|
||||
err = json.Unmarshal(task.Data, pipeline)
|
||||
return pipeline, err
|
||||
}
|
||||
|
||||
// Notify implements the rpc.Notify function
|
||||
func (s *RPC) Notify(c context.Context, id string) (bool, error) {
|
||||
err := s.queue.Wait(c, id)
|
||||
return (err == queue.ErrCancel), nil
|
||||
}
|
||||
|
||||
// Extend implements the rpc.Extend function
|
||||
func (s *RPC) Extend(c context.Context, id string) error {
|
||||
return s.queue.Extend(c, id)
|
||||
}
|
||||
|
||||
// Update implements the rpc.Update function
|
||||
func (s *RPC) Update(c context.Context, id string, state rpc.State) error {
|
||||
jobID, err := strconv.ParseInt(id, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
job, err := s.store.GetJob(jobID)
|
||||
if err != nil {
|
||||
log.Printf("error: cannot find job with id %d: %s", jobID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
build, err := s.store.GetBuild(job.BuildID)
|
||||
if err != nil {
|
||||
log.Printf("error: cannot find build with id %d: %s", job.BuildID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
repo, err := s.store.GetRepo(build.RepoID)
|
||||
if err != nil {
|
||||
log.Printf("error: cannot find repo with id %d: %s", build.RepoID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if build.Status != model.StatusRunning {
|
||||
|
||||
}
|
||||
|
||||
job.Started = state.Started
|
||||
job.Finished = state.Finished
|
||||
job.ExitCode = state.ExitCode
|
||||
job.Status = model.StatusRunning
|
||||
job.Error = state.Error
|
||||
|
||||
if build.Status == model.StatusPending {
|
||||
build.Started = job.Started
|
||||
build.Status = model.StatusRunning
|
||||
s.store.UpdateBuild(build)
|
||||
}
|
||||
|
||||
log.Printf("pipeline: update %s: exited=%v, exit_code=%d", id, state.Exited, state.ExitCode)
|
||||
|
||||
if state.Exited {
|
||||
|
||||
job.Status = model.StatusSuccess
|
||||
if job.ExitCode != 0 || job.Error != "" {
|
||||
job.Status = model.StatusFailure
|
||||
}
|
||||
|
||||
// save the logs
|
||||
var buf bytes.Buffer
|
||||
if serr := s.logger.Snapshot(context.Background(), id, &buf); serr != nil {
|
||||
log.Printf("error: snapshotting logs: %s", serr)
|
||||
}
|
||||
if werr := s.store.WriteLog(job, &buf); werr != nil {
|
||||
log.Printf("error: persisting logs: %s", werr)
|
||||
}
|
||||
|
||||
// close the logger
|
||||
s.logger.Close(c, id)
|
||||
s.queue.Done(c, id)
|
||||
}
|
||||
|
||||
// hackity hack
|
||||
cc := context.WithValue(c, "store", s.store)
|
||||
ok, uerr := store.UpdateBuildJob(cc, build, job)
|
||||
if uerr != nil {
|
||||
log.Printf("error: updating job: %s", uerr)
|
||||
}
|
||||
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 := s.store.GetUser(repo.UserID)
|
||||
if uerr != nil {
|
||||
logrus.Errorf("Unable to find user. %s", err)
|
||||
} else {
|
||||
s.remote.Status(user, repo, build,
|
||||
fmt.Sprintf("%s/%s/%d", s.host, repo.FullName, build.Number))
|
||||
}
|
||||
}
|
||||
|
||||
message := pubsub.Message{}
|
||||
message.Data, _ = json.Marshal(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.
|
||||
// WTF was this being used for?????????????????????????
|
||||
if job.Status == model.StatusRunning {
|
||||
return model.Started
|
||||
}
|
||||
return model.Finished
|
||||
}(),
|
||||
Repo: *repo,
|
||||
Build: *build,
|
||||
Job: *job,
|
||||
})
|
||||
message.Labels = map[string]string{
|
||||
"repo": repo.FullName,
|
||||
"private": strconv.FormatBool(repo.IsPrivate),
|
||||
}
|
||||
s.pubsub.Publish(c, "topic/events", message)
|
||||
log.Println("finish rpc.update")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save implements the rpc.Save function
|
||||
func (s *RPC) Save(c context.Context, id, mime string, file io.Reader) error { return nil }
|
||||
|
||||
// Log implements the rpc.Log function
|
||||
func (s *RPC) Log(c context.Context, id string, line *rpc.Line) error {
|
||||
entry := new(logging.Entry)
|
||||
entry.Data, _ = json.Marshal(line)
|
||||
fmt.Println(string(entry.Data))
|
||||
s.logger.Write(c, id, entry)
|
||||
return nil
|
||||
}
|
158
server/stream.go
158
server/stream.go
|
@ -1,10 +1,13 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/cncd/logging"
|
||||
"github.com/cncd/pubsub"
|
||||
"github.com/drone/drone/cache"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/router/middleware/session"
|
||||
|
@ -194,3 +197,158 @@ 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) {
|
||||
repo := session.Repo(c)
|
||||
buildn, _ := strconv.Atoi(c.Param("build"))
|
||||
jobn, _ := strconv.Atoi(c.Param("number"))
|
||||
|
||||
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)
|
||||
logc := make(chan []byte, 10)
|
||||
|
||||
ctx, cancel := context.WithCancel(
|
||||
context.Background(),
|
||||
)
|
||||
defer func() {
|
||||
cancel()
|
||||
ticker.Stop()
|
||||
close(logc)
|
||||
logrus.Debugf("Successfully closing websocket")
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// TODO remove global variable
|
||||
config.logger.Tail(ctx, fmt.Sprint(job.ID), func(entries ...*logging.Entry) {
|
||||
for _, entry := range entries {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
logc <- entry.Data
|
||||
}
|
||||
}
|
||||
})
|
||||
cancel()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case buf, ok := <-logc:
|
||||
if ok {
|
||||
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
ws.WriteMessage(websocket.TextMessage, buf)
|
||||
}
|
||||
case <-ticker.C:
|
||||
err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
reader(ws)
|
||||
}
|
||||
|
||||
func EventStream2(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)
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
eventc := make(chan []byte, 10)
|
||||
|
||||
ctx, cancel := context.WithCancel(
|
||||
context.Background(),
|
||||
)
|
||||
defer func() {
|
||||
cancel()
|
||||
ticker.Stop()
|
||||
close(eventc)
|
||||
logrus.Debugf("Successfully closing websocket")
|
||||
}()
|
||||
|
||||
go func() {
|
||||
// TODO remove this from global config
|
||||
config.pubsub.Subscribe(c, "topic/events", func(m pubsub.Message) {
|
||||
name := m.Labels["repo"]
|
||||
priv := m.Labels["private"]
|
||||
if repo[name] || priv == "false" {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
eventc <- m.Data
|
||||
}
|
||||
}
|
||||
})
|
||||
cancel()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case buf, ok := <-eventc:
|
||||
if ok {
|
||||
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
ws.WriteMessage(websocket.TextMessage, buf)
|
||||
}
|
||||
case <-ticker.C:
|
||||
err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
reader(ws)
|
||||
}
|
||||
|
|
29
vendor/github.com/cncd/logging/LICENSE
generated
vendored
Normal file
29
vendor/github.com/cncd/logging/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2017, Brad Rydzewski
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
6
vendor/github.com/cncd/logging/README
generated
vendored
Normal file
6
vendor/github.com/cncd/logging/README
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
Go package provides a common interface for storing and streaming logs.
|
||||
|
||||
Documentation:
|
||||
|
||||
http://godoc.org/github.com/cncd/logging
|
||||
http://godoc.org/github.com/cncd/logging/gcp
|
143
vendor/github.com/cncd/logging/log.go
generated
vendored
Normal file
143
vendor/github.com/cncd/logging/log.go
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// TODO (bradrydzewski) writing to subscribers is currently a blocking
|
||||
// operation and does not protect against slow clients from locking
|
||||
// the stream. This should be resolved.
|
||||
|
||||
// TODO (bradrydzewski) implement a mux.Info to fetch information and
|
||||
// statistics for the multiplexier. Streams, subscribers, etc
|
||||
// mux.Info()
|
||||
|
||||
// TODO (bradrydzewski) refactor code to place publisher and subscriber
|
||||
// operations in separate files with more encapsulated logic.
|
||||
// sub.push()
|
||||
// sub.join()
|
||||
// sub.start()... event loop
|
||||
|
||||
type subscriber struct {
|
||||
handler Handler
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
sync.Mutex
|
||||
|
||||
path string
|
||||
hist []*Entry
|
||||
subs map[*subscriber]struct{}
|
||||
done chan struct{}
|
||||
wait sync.WaitGroup
|
||||
}
|
||||
|
||||
type log struct {
|
||||
sync.Mutex
|
||||
|
||||
streams map[string]*stream
|
||||
}
|
||||
|
||||
// New returns a new logger.
|
||||
func New() Log {
|
||||
return &log{
|
||||
streams: map[string]*stream{},
|
||||
}
|
||||
}
|
||||
|
||||
func (l *log) Open(c context.Context, path string) error {
|
||||
l.Lock()
|
||||
_, ok := l.streams[path]
|
||||
if !ok {
|
||||
l.streams[path] = &stream{
|
||||
path: path,
|
||||
subs: make(map[*subscriber]struct{}),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
l.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *log) Write(c context.Context, path string, entry *Entry) error {
|
||||
l.Lock()
|
||||
s, ok := l.streams[path]
|
||||
l.Unlock()
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
s.Lock()
|
||||
s.hist = append(s.hist, entry)
|
||||
for sub := range s.subs {
|
||||
go sub.handler(entry)
|
||||
}
|
||||
s.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *log) Tail(c context.Context, path string, handler Handler) error {
|
||||
l.Lock()
|
||||
s, ok := l.streams[path]
|
||||
l.Unlock()
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
sub := &subscriber{
|
||||
handler: handler,
|
||||
}
|
||||
s.Lock()
|
||||
if len(s.hist) != 0 {
|
||||
sub.handler(s.hist...)
|
||||
}
|
||||
s.subs[sub] = struct{}{}
|
||||
s.Unlock()
|
||||
|
||||
select {
|
||||
case <-c.Done():
|
||||
case <-s.done:
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
delete(s.subs, sub)
|
||||
s.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *log) Close(c context.Context, path string) error {
|
||||
l.Lock()
|
||||
s, ok := l.streams[path]
|
||||
l.Unlock()
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
close(s.done)
|
||||
s.Unlock()
|
||||
|
||||
l.Lock()
|
||||
delete(l.streams, path)
|
||||
l.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *log) Snapshot(c context.Context, path string, w io.Writer) error {
|
||||
l.Lock()
|
||||
s, ok := l.streams[path]
|
||||
l.Unlock()
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
s.Lock()
|
||||
for _, entry := range s.hist {
|
||||
w.Write(entry.Data)
|
||||
w.Write(cr)
|
||||
}
|
||||
s.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
var cr = []byte{'\n'}
|
80
vendor/github.com/cncd/logging/logging.go
generated
vendored
Normal file
80
vendor/github.com/cncd/logging/logging.go
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
package logging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ErrNotFound is returned when the log does not exist.
|
||||
var ErrNotFound = errors.New("stream: not found")
|
||||
|
||||
// Entry defines a log entry.
|
||||
type Entry struct {
|
||||
// ID identifies this message.
|
||||
ID string `json:"id,omitempty"`
|
||||
|
||||
// Data is the actual data in the entry.
|
||||
Data []byte `json:"data"`
|
||||
|
||||
// Tags represents the key-value pairs the
|
||||
// entry is tagged with.
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// Handler defines a callback function for handling log entries.
|
||||
type Handler func(...*Entry)
|
||||
|
||||
// Log defines a log multiplexer.
|
||||
type Log interface {
|
||||
// Open opens the log.
|
||||
Open(c context.Context, path string) error
|
||||
|
||||
// Write writes the entry to the log.
|
||||
Write(c context.Context, path string, entry *Entry) error
|
||||
|
||||
// Tail tails the log.
|
||||
Tail(c context.Context, path string, handler Handler) error
|
||||
|
||||
// Close closes the log.
|
||||
Close(c context.Context, path string) error
|
||||
|
||||
// Snapshot snapshots the stream to Writer w.
|
||||
Snapshot(c context.Context, path string, w io.Writer) error
|
||||
|
||||
// Info returns runtime information about the multiplexer.
|
||||
// Info(c context.Context) (interface{}, error)
|
||||
}
|
||||
|
||||
// // global streamer
|
||||
// var global = New()
|
||||
//
|
||||
// // Set sets a default global logger.
|
||||
// func Set(log Log) {
|
||||
// global = log
|
||||
// }
|
||||
//
|
||||
// // Open opens the log stream.
|
||||
// func Open(c context.Context, path string) error {
|
||||
// return global.Open(c, path)
|
||||
// }
|
||||
//
|
||||
// // Write writes the log entry to the stream.
|
||||
// func Write(c context.Context, path string, entry *Entry) error {
|
||||
// return global.Write(c, path, entry)
|
||||
// }
|
||||
//
|
||||
// // Tail tails the log stream.
|
||||
// func Tail(c context.Context, path string, handler Handler) error {
|
||||
// return global.Tail(c, path, handler)
|
||||
// }
|
||||
//
|
||||
// // Close closes the log stream.
|
||||
// func Close(c context.Context, path string) error {
|
||||
// return global.Close(c, path)
|
||||
// }
|
||||
//
|
||||
// // Snapshot snapshots the stream to Writer w.
|
||||
// func Snapshot(c context.Context, path string, w io.Writer) error {
|
||||
// return global.Snapshot(c, path, w)
|
||||
// }
|
29
vendor/github.com/cncd/pipeline/LICENSE
generated
vendored
Normal file
29
vendor/github.com/cncd/pipeline/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2017, Brad Rydzewski
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
21
vendor/github.com/cncd/pipeline/pipeline/backend/backend.go
generated
vendored
Normal file
21
vendor/github.com/cncd/pipeline/pipeline/backend/backend.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
package backend
|
||||
|
||||
import "io"
|
||||
|
||||
// Engine defines a container orchestration backend and is used
|
||||
// to create and manage container resources.
|
||||
type Engine interface {
|
||||
// Setup the pipeline environment.
|
||||
Setup(*Config) error
|
||||
// Start the pipeline step.
|
||||
Exec(*Step) error
|
||||
// Kill the pipeline step.
|
||||
Kill(*Step) error
|
||||
// Wait for the pipeline step to complete and returns
|
||||
// the completion results.
|
||||
Wait(*Step) (*State, error)
|
||||
// Tail the pipeline step logs.
|
||||
Tail(*Step) (io.ReadCloser, error)
|
||||
// Destroy the pipeline environment.
|
||||
Destroy(*Config) error
|
||||
}
|
130
vendor/github.com/cncd/pipeline/pipeline/backend/docker/convert.go
generated
vendored
Normal file
130
vendor/github.com/cncd/pipeline/pipeline/backend/docker/convert.go
generated
vendored
Normal file
|
@ -0,0 +1,130 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
)
|
||||
|
||||
// returns a container configuration.
|
||||
func toConfig(proc *backend.Step) *container.Config {
|
||||
config := &container.Config{
|
||||
Image: proc.Image,
|
||||
Labels: proc.Labels,
|
||||
WorkingDir: proc.WorkingDir,
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
}
|
||||
if len(proc.Environment) != 0 {
|
||||
config.Env = toEnv(proc.Environment)
|
||||
}
|
||||
if len(proc.Command) != 0 {
|
||||
config.Cmd = proc.Command
|
||||
}
|
||||
if len(proc.Entrypoint) != 0 {
|
||||
config.Entrypoint = proc.Entrypoint
|
||||
}
|
||||
if len(proc.Volumes) != 0 {
|
||||
config.Volumes = toVol(proc.Volumes)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// returns a container host configuration.
|
||||
func toHostConfig(proc *backend.Step) *container.HostConfig {
|
||||
config := &container.HostConfig{
|
||||
Resources: container.Resources{
|
||||
CPUQuota: proc.CPUQuota,
|
||||
CPUShares: proc.CPUShares,
|
||||
CpusetCpus: proc.CPUSet,
|
||||
Memory: proc.MemLimit,
|
||||
MemorySwap: proc.MemSwapLimit,
|
||||
},
|
||||
Privileged: proc.Privileged,
|
||||
ShmSize: proc.ShmSize,
|
||||
}
|
||||
// if len(proc.VolumesFrom) != 0 {
|
||||
// config.VolumesFrom = proc.VolumesFrom
|
||||
// }
|
||||
// if len(proc.Network) != 0 {
|
||||
// config.NetworkMode = container.NetworkMode(
|
||||
// proc.Network,
|
||||
// )
|
||||
// }
|
||||
if len(proc.DNS) != 0 {
|
||||
config.DNS = proc.DNS
|
||||
}
|
||||
if len(proc.DNSSearch) != 0 {
|
||||
config.DNSSearch = proc.DNSSearch
|
||||
}
|
||||
if len(proc.ExtraHosts) != 0 {
|
||||
config.ExtraHosts = proc.ExtraHosts
|
||||
}
|
||||
if len(proc.Devices) != 0 {
|
||||
config.Devices = toDev(proc.Devices)
|
||||
}
|
||||
if len(proc.Volumes) != 0 {
|
||||
config.Binds = proc.Volumes
|
||||
}
|
||||
// if proc.OomKillDisable {
|
||||
// config.OomKillDisable = &proc.OomKillDisable
|
||||
// }
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// helper function that converts a slice of volume paths to a set of
|
||||
// unique volume names.
|
||||
func toVol(paths []string) map[string]struct{} {
|
||||
set := map[string]struct{}{}
|
||||
for _, path := range paths {
|
||||
parts := strings.Split(path, ":")
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
set[parts[1]] = struct{}{}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// helper function that converts a key value map of environment variables to a
|
||||
// string slice in key=value format.
|
||||
func toEnv(env map[string]string) []string {
|
||||
var envs []string
|
||||
for k, v := range env {
|
||||
envs = append(envs, k+"="+v)
|
||||
}
|
||||
return envs
|
||||
}
|
||||
|
||||
// helper function that converts a slice of device paths to a slice of
|
||||
// container.DeviceMapping.
|
||||
func toDev(paths []string) []container.DeviceMapping {
|
||||
var devices []container.DeviceMapping
|
||||
for _, path := range paths {
|
||||
parts := strings.Split(path, ":")
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
devices = append(devices, container.DeviceMapping{
|
||||
PathOnHost: parts[0],
|
||||
PathInContainer: parts[1],
|
||||
CgroupPermissions: "rwm",
|
||||
})
|
||||
}
|
||||
return devices
|
||||
}
|
||||
|
||||
// helper function that serializes the auth configuration as JSON
|
||||
// base64 payload.
|
||||
func encodeAuthToBase64(authConfig backend.Auth) (string, error) {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(buf), nil
|
||||
}
|
202
vendor/github.com/cncd/pipeline/pipeline/backend/docker/docker.go
generated
vendored
Normal file
202
vendor/github.com/cncd/pipeline/pipeline/backend/docker/docker.go
generated
vendored
Normal file
|
@ -0,0 +1,202 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
)
|
||||
|
||||
type engine struct {
|
||||
client client.APIClient
|
||||
}
|
||||
|
||||
// New returns a new Docker Engine using the given client.
|
||||
func New(cli client.APIClient) backend.Engine {
|
||||
return &engine{
|
||||
client: cli,
|
||||
}
|
||||
}
|
||||
|
||||
// NewEnv returns a new Docker Engine using the client connection
|
||||
// environment variables.
|
||||
func NewEnv() (backend.Engine, error) {
|
||||
cli, err := client.NewEnvClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return New(cli), nil
|
||||
}
|
||||
|
||||
func (e *engine) Setup(conf *backend.Config) error {
|
||||
for _, vol := range conf.Volumes {
|
||||
_, err := e.client.VolumeCreate(noContext, volume.VolumesCreateBody{
|
||||
Name: vol.Name,
|
||||
Driver: vol.Driver,
|
||||
DriverOpts: vol.DriverOpts,
|
||||
// Labels: defaultLabels,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, network := range conf.Networks {
|
||||
_, err := e.client.NetworkCreate(noContext, network.Name, types.NetworkCreate{
|
||||
Driver: network.Driver,
|
||||
Options: network.DriverOpts,
|
||||
// Labels: defaultLabels,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *engine) Exec(proc *backend.Step) error {
|
||||
ctx := context.Background()
|
||||
|
||||
config := toConfig(proc)
|
||||
hostConfig := toHostConfig(proc)
|
||||
|
||||
// create pull options with encoded authorization credentials.
|
||||
pullopts := types.ImagePullOptions{}
|
||||
if proc.AuthConfig.Username != "" && proc.AuthConfig.Password != "" {
|
||||
pullopts.RegistryAuth, _ = encodeAuthToBase64(proc.AuthConfig)
|
||||
}
|
||||
|
||||
// automatically pull the latest version of the image if requested
|
||||
// by the process configuration.
|
||||
if proc.Pull {
|
||||
rc, perr := e.client.ImagePull(ctx, config.Image, pullopts)
|
||||
if perr == nil {
|
||||
io.Copy(ioutil.Discard, rc)
|
||||
rc.Close()
|
||||
}
|
||||
// fix for drone/drone#1917
|
||||
if perr != nil && proc.AuthConfig.Password != "" {
|
||||
return perr
|
||||
}
|
||||
}
|
||||
|
||||
_, err := e.client.ContainerCreate(ctx, config, hostConfig, nil, proc.Name)
|
||||
if client.IsErrImageNotFound(err) {
|
||||
// automatically pull and try to re-create the image if the
|
||||
// failure is caused because the image does not exist.
|
||||
rc, perr := e.client.ImagePull(ctx, config.Image, pullopts)
|
||||
if perr != nil {
|
||||
return perr
|
||||
}
|
||||
io.Copy(ioutil.Discard, rc)
|
||||
rc.Close()
|
||||
|
||||
_, err = e.client.ContainerCreate(ctx, config, hostConfig, nil, proc.Name)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, net := range proc.Networks {
|
||||
err = e.client.NetworkConnect(ctx, net.Name, proc.Name, &network.EndpointSettings{
|
||||
Aliases: net.Aliases,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// if proc.Network != "host" { // or bridge, overlay, none, internal, container:<name> ....
|
||||
// err = e.client.NetworkConnect(ctx, proc.Network, proc.Name, &network.EndpointSettings{
|
||||
// Aliases: proc.NetworkAliases,
|
||||
// })
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
return e.client.ContainerStart(ctx, proc.Name, startOpts)
|
||||
}
|
||||
|
||||
func (e *engine) Kill(proc *backend.Step) error {
|
||||
return e.client.ContainerKill(noContext, proc.Name, "9")
|
||||
}
|
||||
|
||||
func (e *engine) Wait(proc *backend.Step) (*backend.State, error) {
|
||||
_, err := e.client.ContainerWait(noContext, proc.Name)
|
||||
if err != nil {
|
||||
// todo
|
||||
}
|
||||
|
||||
info, err := e.client.ContainerInspect(noContext, proc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.State.Running {
|
||||
// todo
|
||||
}
|
||||
|
||||
return &backend.State{
|
||||
Exited: true,
|
||||
ExitCode: info.State.ExitCode,
|
||||
OOMKilled: info.State.OOMKilled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *engine) Tail(proc *backend.Step) (io.ReadCloser, error) {
|
||||
logs, err := e.client.ContainerLogs(noContext, proc.Name, logsOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rc, wc := io.Pipe()
|
||||
|
||||
go func() {
|
||||
stdcopy.StdCopy(wc, wc, logs)
|
||||
logs.Close()
|
||||
wc.Close()
|
||||
rc.Close()
|
||||
}()
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (e *engine) Destroy(conf *backend.Config) error {
|
||||
for _, stage := range conf.Stages {
|
||||
for _, step := range stage.Steps {
|
||||
e.client.ContainerKill(noContext, step.Name, "9")
|
||||
e.client.ContainerRemove(noContext, step.Name, removeOpts)
|
||||
}
|
||||
}
|
||||
for _, volume := range conf.Volumes {
|
||||
e.client.VolumeRemove(noContext, volume.Name, true)
|
||||
}
|
||||
for _, network := range conf.Networks {
|
||||
e.client.NetworkRemove(noContext, network.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
noContext = context.Background()
|
||||
|
||||
startOpts = types.ContainerStartOptions{}
|
||||
|
||||
removeOpts = types.ContainerRemoveOptions{
|
||||
RemoveVolumes: true,
|
||||
RemoveLinks: false,
|
||||
Force: false,
|
||||
}
|
||||
|
||||
logsOpts = types.ContainerLogsOptions{
|
||||
Follow: true,
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Details: false,
|
||||
Timestamps: false,
|
||||
}
|
||||
)
|
44
vendor/github.com/cncd/pipeline/pipeline/backend/docker/pool.go
generated
vendored
Normal file
44
vendor/github.com/cncd/pipeline/pipeline/backend/docker/pool.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
|||
package docker
|
||||
|
||||
// import (
|
||||
// "context"
|
||||
//
|
||||
// "github.com/cncd/pipeline/pipeline/backend"
|
||||
// )
|
||||
//
|
||||
// // Pool manages a pool of Docker clients.
|
||||
// type Pool struct {
|
||||
// queue chan (backend.Engine)
|
||||
// }
|
||||
//
|
||||
// // NewPool returns a Pool.
|
||||
// func NewPool(engines ...backend.Engine) *Pool {
|
||||
// return &Pool{
|
||||
// queue: make(chan backend.Engine, len(engines)),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Reserve requests the next available Docker client in the pool.
|
||||
// func (p *Pool) Reserve(c context.Context) backend.Engine {
|
||||
// select {
|
||||
// case <-c.Done():
|
||||
// case engine := <-p.queue:
|
||||
// return engine
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// // Release releases the Docker client back to the pool.
|
||||
// func (p *Pool) Release(engine backend.Engine) {
|
||||
// p.queue <- engine
|
||||
// }
|
||||
|
||||
// pool := docker.Pool(
|
||||
// docker.FromEnvironmentMust(),
|
||||
// docker.FromEnvironmentMust(),
|
||||
// docker.FromEnvironmentMust(),
|
||||
// docker.FromEnvironmentMust(),
|
||||
// )
|
||||
//
|
||||
// client := pool.Reserve()
|
||||
// defer pool.Release(client)
|
103
vendor/github.com/cncd/pipeline/pipeline/backend/types.go
generated
vendored
Normal file
103
vendor/github.com/cncd/pipeline/pipeline/backend/types.go
generated
vendored
Normal file
|
@ -0,0 +1,103 @@
|
|||
package backend
|
||||
|
||||
type (
|
||||
// Config defines the runtime configuration of a pipeline.
|
||||
Config struct {
|
||||
Stages []*Stage `json:"pipeline"` // pipeline stages
|
||||
Networks []*Network `json:"networks"` // network definitions
|
||||
Volumes []*Volume `json:"volumes"` // volume definitions
|
||||
}
|
||||
|
||||
// Stage denotes a collection of one or more steps.
|
||||
Stage struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Steps []*Step `json:"steps,omitempty"`
|
||||
}
|
||||
|
||||
// Step defines a container process.
|
||||
Step struct {
|
||||
Name string `json:"name"`
|
||||
Alias string `json:"alias,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
Pull bool `json:"pull,omitempty"`
|
||||
Detached bool `json:"detach,omitempty"`
|
||||
Privileged bool `json:"privileged,omitempty"`
|
||||
WorkingDir string `json:"working_dir,omitempty"`
|
||||
Environment map[string]string `json:"environment,omitempty"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Entrypoint []string `json:"entrypoint,omitempty"`
|
||||
Command []string `json:"command,omitempty"`
|
||||
ExtraHosts []string `json:"extra_hosts,omitempty"`
|
||||
Volumes []string `json:"volumes,omitempty"`
|
||||
Devices []string `json:"devices,omitempty"`
|
||||
Networks []Conn `json:"networks,omitempty"`
|
||||
DNS []string `json:"dns,omitempty"`
|
||||
DNSSearch []string `json:"dns_search,omitempty"`
|
||||
MemSwapLimit int64 `json:"memswap_limit,omitempty"`
|
||||
MemLimit int64 `json:"mem_limit,omitempty"`
|
||||
ShmSize int64 `json:"shm_size,omitempty"`
|
||||
CPUQuota int64 `json:"cpu_quota,omitempty"`
|
||||
CPUShares int64 `json:"cpu_shares,omitempty"`
|
||||
CPUSet string `json:"cpu_set,omitempty"`
|
||||
OnFailure bool `json:"on_failure,omitempty"`
|
||||
OnSuccess bool `json:"on_success,omitempty"`
|
||||
AuthConfig Auth `json:"auth_config,omitempty"`
|
||||
}
|
||||
|
||||
// Auth defines registry authentication credentials.
|
||||
Auth struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
// Conn defines a container network connection.
|
||||
Conn struct {
|
||||
Name string `json:"name"`
|
||||
Aliases []string `json:"aliases"`
|
||||
}
|
||||
|
||||
// Network defines a container network.
|
||||
Network struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Driver string `json:"driver,omitempty"`
|
||||
DriverOpts map[string]string `json:"driver_opts,omitempty"`
|
||||
}
|
||||
|
||||
// Volume defines a container volume.
|
||||
Volume struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Driver string `json:"driver,omitempty"`
|
||||
DriverOpts map[string]string `json:"driver_opts,omitempty"`
|
||||
}
|
||||
|
||||
// State defines a container state.
|
||||
State struct {
|
||||
// Container exit code
|
||||
ExitCode int `json:"exit_code"`
|
||||
// Container exited, true or false
|
||||
Exited bool `json:"exited"`
|
||||
// Container is oom killed, true or false
|
||||
OOMKilled bool `json:"oom_killed"`
|
||||
}
|
||||
|
||||
// // State defines the pipeline and process state.
|
||||
// State struct {
|
||||
// Pipeline struct {
|
||||
// // Current pipeline step
|
||||
// Step *Step `json:"step"`
|
||||
// // Current pipeline error state
|
||||
// Error error `json:"error"`
|
||||
// }
|
||||
//
|
||||
// Process struct {
|
||||
// // Container exit code
|
||||
// ExitCode int `json:"exit_code"`
|
||||
// // Container exited, true or false
|
||||
// Exited bool `json:"exited"`
|
||||
// // Container is oom killed, true or false
|
||||
// OOMKilled bool `json:"oom_killed"`
|
||||
// }
|
||||
// }
|
||||
)
|
38
vendor/github.com/cncd/pipeline/pipeline/error.go
generated
vendored
Normal file
38
vendor/github.com/cncd/pipeline/pipeline/error.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
package pipeline
|
||||
|
||||
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("Skipped")
|
||||
|
||||
// ErrCancel is used as a return value when the container execution receives
|
||||
// a cancellation signal from the context.
|
||||
ErrCancel = errors.New("Cancelled")
|
||||
)
|
||||
|
||||
// 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
|
||||
Code int
|
||||
}
|
||||
|
||||
// Error reteurns the error message in string format.
|
||||
func (e *OomError) Error() string {
|
||||
return fmt.Sprintf("%s : received oom kill", e.Name)
|
||||
}
|
121
vendor/github.com/cncd/pipeline/pipeline/frontend/metadata.go
generated
vendored
Normal file
121
vendor/github.com/cncd/pipeline/pipeline/frontend/metadata.go
generated
vendored
Normal file
|
@ -0,0 +1,121 @@
|
|||
package frontend
|
||||
|
||||
import "strconv"
|
||||
|
||||
type (
|
||||
// Metadata defines runtime m.
|
||||
Metadata struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Repo Repo `json:"repo,omitempty"`
|
||||
Curr Build `json:"curr,omitempty"`
|
||||
Prev Build `json:"prev,omitempty"`
|
||||
Job Job `json:"job,omitempty"`
|
||||
Sys System `json:"sys,omitempty"`
|
||||
}
|
||||
|
||||
// Repo defines runtime metadata for a repository.
|
||||
Repo struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Link string `json:"link,omitempty"`
|
||||
Remote string `json:"remote,omitempty"`
|
||||
Private bool `json:"private,omitempty"`
|
||||
}
|
||||
|
||||
// Build defines runtime metadata for a build.
|
||||
Build struct {
|
||||
Number int `json:"number,omitempty"`
|
||||
Created int64 `json:"created,omitempty"`
|
||||
Started int64 `json:"started,omitempty"`
|
||||
Finished int64 `json:"finished,omitempty"`
|
||||
Timeout int64 `json:"timeout,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Event string `json:"event,omitempty"`
|
||||
Link string `json:"link,omitempty"`
|
||||
Target string `json:"target,omitempty"`
|
||||
Trusted bool `json:"trusted,omitempty"`
|
||||
Commit Commit `json:"commit,omitempty"`
|
||||
}
|
||||
|
||||
// Commit defines runtime metadata for a commit.
|
||||
Commit struct {
|
||||
Sha string `json:"sha,omitempty"`
|
||||
Ref string `json:"ref,omitempty"`
|
||||
Refspec string `json:"refspec,omitempty"`
|
||||
Branch string `json:"branch,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Author Author `json:"author,omitempty"`
|
||||
}
|
||||
|
||||
// Author defines runtime metadata for a commit author.
|
||||
Author struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Avatar string `json:"avatar,omitempty"`
|
||||
}
|
||||
|
||||
// Job defines runtime metadata for a job.
|
||||
Job struct {
|
||||
Number int `json:"number,omitempty"`
|
||||
Matrix map[string]string `json:"matrix,omitempty"`
|
||||
}
|
||||
|
||||
// System defines runtime metadata for a ci/cd system.
|
||||
System struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
Link string `json:"link,omitempty"`
|
||||
Arch string `json:"arch,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
// Environ returns the metadata as a map of environment variables.
|
||||
func (m *Metadata) Environ() map[string]string {
|
||||
return map[string]string{
|
||||
"CI_REPO": m.Repo.Name,
|
||||
"CI_REPO_NAME": m.Repo.Name,
|
||||
"CI_REPO_LINK": m.Repo.Link,
|
||||
"CI_REPO_REMOTE": m.Repo.Remote,
|
||||
"CI_REMOTE_URL": m.Repo.Remote,
|
||||
"CI_REPO_PRIVATE": strconv.FormatBool(m.Repo.Private),
|
||||
"CI_BUILD_NUMBER": strconv.Itoa(m.Curr.Number),
|
||||
"CI_BUILD_CREATED": strconv.FormatInt(m.Curr.Created, 10),
|
||||
"CI_BUILD_STARTED": strconv.FormatInt(m.Curr.Started, 10),
|
||||
"CI_BUILD_FINISHED": strconv.FormatInt(m.Curr.Finished, 10),
|
||||
"CI_BUILD_STATUS": m.Curr.Status,
|
||||
"CI_BUILD_EVENT": m.Curr.Event,
|
||||
"CI_BUILD_LINK": m.Curr.Link,
|
||||
"CI_BUILD_TARGET": m.Curr.Target,
|
||||
"CI_COMMIT_SHA": m.Curr.Commit.Sha,
|
||||
"CI_COMMIT_REF": m.Curr.Commit.Ref,
|
||||
"CI_COMMIT_REFSPEC": m.Curr.Commit.Refspec,
|
||||
"CI_COMMIT_BRANCH": m.Curr.Commit.Branch,
|
||||
"CI_COMMIT_MESSAGE": m.Curr.Commit.Message,
|
||||
"CI_COMMIT_AUTHOR": m.Curr.Commit.Author.Name,
|
||||
"CI_COMMIT_AUTHOR_NAME": m.Curr.Commit.Author.Name,
|
||||
"CI_COMMIT_AUTHOR_EMAIL": m.Curr.Commit.Author.Email,
|
||||
"CI_COMMIT_AUTHOR_AVATAR": m.Curr.Commit.Author.Avatar,
|
||||
"CI_PREV_BUILD_NUMBER": strconv.Itoa(m.Prev.Number),
|
||||
"CI_PREV_BUILD_CREATED": strconv.FormatInt(m.Prev.Created, 10),
|
||||
"CI_PREV_BUILD_STARTED": strconv.FormatInt(m.Prev.Started, 10),
|
||||
"CI_PREV_BUILD_FINISHED": strconv.FormatInt(m.Prev.Finished, 10),
|
||||
"CI_PREV_BUILD_STATUS": m.Prev.Status,
|
||||
"CI_PREV_BUILD_EVENT": m.Prev.Event,
|
||||
"CI_PREV_BUILD_LINK": m.Prev.Link,
|
||||
"CI_PREV_COMMIT_SHA": m.Prev.Commit.Sha,
|
||||
"CI_PREV_COMMIT_REF": m.Prev.Commit.Ref,
|
||||
"CI_PREV_COMMIT_REFSPEC": m.Prev.Commit.Refspec,
|
||||
"CI_PREV_COMMIT_BRANCH": m.Prev.Commit.Branch,
|
||||
"CI_PREV_COMMIT_MESSAGE": m.Prev.Commit.Message,
|
||||
"CI_PREV_COMMIT_AUTHOR": m.Prev.Commit.Author.Name,
|
||||
"CI_PREV_COMMIT_AUTHOR_NAME": m.Prev.Commit.Author.Name,
|
||||
"CI_PREV_COMMIT_AUTHOR_EMAIL": m.Prev.Commit.Author.Email,
|
||||
"CI_PREV_COMMIT_AUTHOR_AVATAR": m.Prev.Commit.Author.Avatar,
|
||||
"CI_JOB_NUMBER": strconv.Itoa(m.Job.Number),
|
||||
"CI_SYSTEM": m.Sys.Name,
|
||||
"CI_SYSTEM_NAME": m.Sys.Name,
|
||||
"CI_SYSTEM_LINK": m.Sys.Link,
|
||||
"CI_SYSTEM_HOST": m.Sys.Host,
|
||||
"CI_SYSTEM_ARCH": m.Sys.Arch,
|
||||
"CI": m.Sys.Name,
|
||||
}
|
||||
}
|
167
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/compiler/compiler.go
generated
vendored
Normal file
167
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/compiler/compiler.go
generated
vendored
Normal file
|
@ -0,0 +1,167 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
"github.com/cncd/pipeline/pipeline/frontend"
|
||||
"github.com/cncd/pipeline/pipeline/frontend/yaml"
|
||||
// libcompose "github.com/docker/libcompose/yaml"
|
||||
)
|
||||
|
||||
// TODO(bradrydzewski) compiler should handle user-defined volumes from YAML
|
||||
// TODO(bradrydzewski) compiler should handle user-defined networks from YAML
|
||||
|
||||
// Compiler compiles the yaml
|
||||
type Compiler struct {
|
||||
local bool
|
||||
escalated []string
|
||||
prefix string
|
||||
volumes []string
|
||||
env map[string]string
|
||||
base string
|
||||
path string
|
||||
metadata frontend.Metadata
|
||||
aliases []string
|
||||
}
|
||||
|
||||
// New creates a new Compiler with options.
|
||||
func New(opts ...Option) *Compiler {
|
||||
compiler := new(Compiler)
|
||||
compiler.env = map[string]string{}
|
||||
for _, opt := range opts {
|
||||
opt(compiler)
|
||||
}
|
||||
return compiler
|
||||
}
|
||||
|
||||
// Compile compiles the YAML configuration to the pipeline intermediate
|
||||
// representation configuration format.
|
||||
func (c *Compiler) Compile(conf *yaml.Config) *backend.Config {
|
||||
config := new(backend.Config)
|
||||
|
||||
// create a default volume
|
||||
config.Volumes = append(config.Volumes, &backend.Volume{
|
||||
Name: fmt.Sprintf("%s_default", c.prefix),
|
||||
Driver: "local",
|
||||
})
|
||||
|
||||
// create a default network
|
||||
config.Networks = append(config.Networks, &backend.Network{
|
||||
Name: fmt.Sprintf("%s_default", c.prefix),
|
||||
Driver: "bridge",
|
||||
})
|
||||
|
||||
// overrides the default workspace paths when specified
|
||||
// in the YAML file.
|
||||
if len(conf.Workspace.Base) != 0 {
|
||||
c.base = conf.Workspace.Base
|
||||
}
|
||||
if len(conf.Workspace.Path) != 0 {
|
||||
c.path = conf.Workspace.Path
|
||||
}
|
||||
|
||||
// add default clone step
|
||||
if c.local == false && len(conf.Clone.Containers) == 0 {
|
||||
container := &yaml.Container{
|
||||
Image: "plugins/git:latest",
|
||||
Vargs: map[string]interface{}{"depth": "0"},
|
||||
}
|
||||
name := fmt.Sprintf("%s_clone", c.prefix)
|
||||
step := c.createProcess(name, container)
|
||||
|
||||
stage := new(backend.Stage)
|
||||
stage.Name = name
|
||||
stage.Alias = "clone"
|
||||
stage.Steps = append(stage.Steps, step)
|
||||
|
||||
config.Stages = append(config.Stages, stage)
|
||||
} else if c.local == false {
|
||||
for i, container := range conf.Clone.Containers {
|
||||
if !container.Constraints.Match(c.metadata) {
|
||||
continue
|
||||
}
|
||||
stage := new(backend.Stage)
|
||||
stage.Name = fmt.Sprintf("%s_clone_%v", c.prefix, i)
|
||||
stage.Alias = container.Name
|
||||
|
||||
name := fmt.Sprintf("%s_clone_%d", c.prefix, i)
|
||||
step := c.createProcess(name, container)
|
||||
stage.Steps = append(stage.Steps, step)
|
||||
|
||||
config.Stages = append(config.Stages, stage)
|
||||
}
|
||||
}
|
||||
|
||||
// add services steps
|
||||
if len(conf.Services.Containers) != 0 {
|
||||
stage := new(backend.Stage)
|
||||
stage.Name = fmt.Sprintf("%s_services", c.prefix)
|
||||
stage.Alias = "services"
|
||||
|
||||
for _, container := range conf.Services.Containers {
|
||||
c.aliases = append(c.aliases, container.Name)
|
||||
}
|
||||
|
||||
for i, container := range conf.Services.Containers {
|
||||
name := fmt.Sprintf("%s_services_%d", c.prefix, i)
|
||||
step := c.createProcess(name, container)
|
||||
stage.Steps = append(stage.Steps, step)
|
||||
|
||||
}
|
||||
config.Stages = append(config.Stages, stage)
|
||||
}
|
||||
|
||||
// add pipeline steps. 1 pipeline step per stage, at the moment
|
||||
var stage *backend.Stage
|
||||
var group string
|
||||
for i, container := range conf.Pipeline.Containers {
|
||||
if !container.Constraints.Match(c.metadata) {
|
||||
continue
|
||||
}
|
||||
|
||||
if stage == nil || group != container.Group || container.Group == "" {
|
||||
group = container.Group
|
||||
|
||||
stage = new(backend.Stage)
|
||||
stage.Name = fmt.Sprintf("%s_stage_%v", c.prefix, i)
|
||||
stage.Alias = container.Name
|
||||
config.Stages = append(config.Stages, stage)
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("%s_step_%d", c.prefix, i)
|
||||
step := c.createProcess(name, container)
|
||||
stage.Steps = append(stage.Steps, step)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// func setupNetwork(step *backend.Step, network *libcompose.Network) {
|
||||
// step.Networks = append(step.Networks, backend.Conn{
|
||||
// Name: network.Name,
|
||||
// // Aliases:
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// func setupVolume(step *backend.Step, volume *libcompose.Volume) {
|
||||
// step.Volumes = append(step.Volumes, volume.String())
|
||||
// }
|
||||
//
|
||||
// var (
|
||||
// // Default plugin used to clone the repository.
|
||||
// defaultCloneImage = "plugins/git:latest"
|
||||
//
|
||||
// // Default plugin settings used to clone the repository.
|
||||
// defaultCloneVargs = map[string]interface{}{
|
||||
// "depth": 0,
|
||||
// }
|
||||
// )
|
||||
//
|
||||
// // defaultClone returns the default step for cloning an image.
|
||||
// func defaultClone() *yaml.Container {
|
||||
// return &yaml.Container{
|
||||
// Image: defaultCloneImage,
|
||||
// Vargs: defaultCloneVargs,
|
||||
// }
|
||||
// }
|
152
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/compiler/convert.go
generated
vendored
Normal file
152
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/compiler/convert.go
generated
vendored
Normal file
|
@ -0,0 +1,152 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
"github.com/cncd/pipeline/pipeline/frontend/yaml"
|
||||
)
|
||||
|
||||
func (c *Compiler) createProcess(name string, container *yaml.Container) *backend.Step {
|
||||
var (
|
||||
detached bool
|
||||
workingdir string
|
||||
|
||||
workspace = fmt.Sprintf("%s_default:%s", c.prefix, c.base)
|
||||
privileged = container.Privileged
|
||||
entrypoint = container.Entrypoint
|
||||
command = container.Command
|
||||
image = expandImage(container.Image)
|
||||
// network = container.Network
|
||||
)
|
||||
|
||||
networks := []backend.Conn{
|
||||
backend.Conn{
|
||||
Name: fmt.Sprintf("%s_default", c.prefix),
|
||||
Aliases: c.aliases,
|
||||
},
|
||||
}
|
||||
|
||||
volumes := []string{
|
||||
workspace,
|
||||
}
|
||||
for _, volume := range container.Volumes.Volumes {
|
||||
volumes = append(volumes, volume.String())
|
||||
}
|
||||
// if network == "" {
|
||||
// network = fmt.Sprintf("%s_default", c.prefix)
|
||||
// for _, alias := range c.aliases {
|
||||
// // if alias != container.Name {
|
||||
// aliases = append(aliases, alias)
|
||||
// // }
|
||||
// }
|
||||
// } // host, bridge, none, container:<name>, overlay
|
||||
|
||||
// append default environment variables
|
||||
environment := map[string]string{}
|
||||
for k, v := range container.Environment {
|
||||
environment[k] = v
|
||||
}
|
||||
for k, v := range c.env {
|
||||
switch v {
|
||||
case "", "0", "false":
|
||||
continue
|
||||
default:
|
||||
environment[k] = v
|
||||
|
||||
// legacy code for drone plugins
|
||||
if strings.HasPrefix(k, "CI_") {
|
||||
p := strings.Replace(k, "CI_", "DRONE_", 1)
|
||||
environment[p] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
environment["CI_WORKSPACE"] = path.Join(c.base, c.path)
|
||||
environment["DRONE_WORKSPACE"] = path.Join(c.base, c.path)
|
||||
|
||||
if !isService(container) {
|
||||
workingdir = path.Join(c.base, c.path)
|
||||
}
|
||||
|
||||
if isService(container) {
|
||||
detached = true
|
||||
}
|
||||
|
||||
if isPlugin(container) {
|
||||
paramsToEnv(container.Vargs, environment)
|
||||
|
||||
if imageMatches(container.Image, c.escalated) {
|
||||
privileged = true
|
||||
entrypoint = []string{}
|
||||
command = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
if isShell(container) {
|
||||
entrypoint = []string{"/bin/sh", "-c"}
|
||||
command = []string{"echo $CI_SCRIPT | base64 -d | /bin/sh -e"}
|
||||
environment["CI_SCRIPT"] = generateScriptPosix(container.Commands)
|
||||
environment["HOME"] = "/root"
|
||||
environment["SHELL"] = "/bin/sh"
|
||||
}
|
||||
|
||||
return &backend.Step{
|
||||
Name: name,
|
||||
Alias: container.Name,
|
||||
Image: image,
|
||||
Pull: container.Pull,
|
||||
Detached: detached,
|
||||
Privileged: privileged,
|
||||
WorkingDir: workingdir,
|
||||
Environment: environment,
|
||||
Labels: container.Labels,
|
||||
Entrypoint: entrypoint,
|
||||
Command: command,
|
||||
ExtraHosts: container.ExtraHosts,
|
||||
Volumes: volumes,
|
||||
Devices: container.Devices,
|
||||
Networks: networks,
|
||||
DNS: container.DNS,
|
||||
DNSSearch: container.DNSSearch,
|
||||
MemSwapLimit: int64(container.MemSwapLimit),
|
||||
MemLimit: int64(container.MemLimit),
|
||||
ShmSize: int64(container.ShmSize),
|
||||
CPUQuota: int64(container.CPUQuota),
|
||||
CPUShares: int64(container.CPUShares),
|
||||
CPUSet: container.CPUSet,
|
||||
AuthConfig: backend.Auth{
|
||||
Username: container.AuthConfig.Username,
|
||||
Password: container.AuthConfig.Password,
|
||||
Email: container.AuthConfig.Email,
|
||||
},
|
||||
OnSuccess: container.Constraints.Status.Match("success"),
|
||||
OnFailure: (len(container.Constraints.Status.Include)+
|
||||
len(container.Constraints.Status.Exclude) != 0) &&
|
||||
container.Constraints.Status.Match("failure"),
|
||||
}
|
||||
}
|
||||
|
||||
func imageMatches(image string, to []string) bool {
|
||||
image = trimImage(image)
|
||||
for _, i := range to {
|
||||
if image == i {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isPlugin(c *yaml.Container) bool {
|
||||
return len(c.Vargs) != 0
|
||||
}
|
||||
|
||||
func isShell(c *yaml.Container) bool {
|
||||
return len(c.Commands) != 0
|
||||
}
|
||||
|
||||
func isService(c *yaml.Container) bool {
|
||||
return c.Detached || (isPlugin(c) == false && isShell(c) == false)
|
||||
}
|
36
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/compiler/image.go
generated
vendored
Normal file
36
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/compiler/image.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/reference"
|
||||
)
|
||||
|
||||
// trimImage returns the short image name without tag.
|
||||
func trimImage(name string) string {
|
||||
ref, err := reference.ParseNamed(name)
|
||||
if err != nil {
|
||||
return name
|
||||
}
|
||||
return reference.TrimNamed(ref).String()
|
||||
}
|
||||
|
||||
// expandImage returns the fully qualified image name.
|
||||
func expandImage(name string) string {
|
||||
ref, err := reference.ParseNamed(name)
|
||||
if err != nil {
|
||||
return name
|
||||
}
|
||||
return reference.WithDefaultTag(ref).String()
|
||||
}
|
||||
|
||||
// matchImage returns true if the image name matches
|
||||
// an image in the list. Note the image tag is not used
|
||||
// in the matching logic.
|
||||
func matchImage(from string, to ...string) bool {
|
||||
from = trimImage(from)
|
||||
for _, match := range to {
|
||||
if from == match {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
145
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/compiler/option.go
generated
vendored
Normal file
145
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/compiler/option.go
generated
vendored
Normal file
|
@ -0,0 +1,145 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline/frontend"
|
||||
)
|
||||
|
||||
// Option configures a compiler option.
|
||||
type Option func(*Compiler)
|
||||
|
||||
// WithVolumes configutes the compiler with default volumes that
|
||||
// are mounted to each container in the pipeline.
|
||||
func WithVolumes(volumes ...string) Option {
|
||||
return func(compiler *Compiler) {
|
||||
compiler.volumes = volumes
|
||||
}
|
||||
}
|
||||
|
||||
// WithMetadata configutes the compiler with the repostiory, build
|
||||
// and system metadata. The metadata is used to remove steps from
|
||||
// the compiled pipeline configuration that should be skipped. The
|
||||
// metadata is also added to each container as environment variables.
|
||||
func WithMetadata(metadata frontend.Metadata) Option {
|
||||
return func(compiler *Compiler) {
|
||||
compiler.metadata = metadata
|
||||
|
||||
for k, v := range metadata.Environ() {
|
||||
compiler.env[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithNetrc configures the compiler with netrc authentication
|
||||
// credentials added by default to every container in the pipeline.
|
||||
func WithNetrc(username, password, machine string) Option {
|
||||
return WithEnviron(
|
||||
map[string]string{
|
||||
"CI_NETRC_USERNAME": username,
|
||||
"CI_NETRC_PASSWORD": password,
|
||||
"CI_NETRC_MACHINE": machine,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// WithWorkspace configures the compiler with the workspace base
|
||||
// and path. The workspace base is a volume created at runtime and
|
||||
// mounted into all containers in the pipeline. The base and path
|
||||
// are joined to provide the working directory for all build and
|
||||
// plugin steps in the pipeline.
|
||||
func WithWorkspace(base, path string) Option {
|
||||
return func(compiler *Compiler) {
|
||||
compiler.base = base
|
||||
compiler.path = path
|
||||
}
|
||||
}
|
||||
|
||||
// WithEscalated configures the compiler to automatically execute
|
||||
// images as privileged containers if the match the given list.
|
||||
func WithEscalated(images ...string) Option {
|
||||
return func(compiler *Compiler) {
|
||||
compiler.escalated = images
|
||||
}
|
||||
}
|
||||
|
||||
// WithPrefix configures the compiler with the prefix. The prefix is
|
||||
// used to prefix container, volume and network names to avoid
|
||||
// collision at runtime.
|
||||
func WithPrefix(prefix string) Option {
|
||||
return func(compiler *Compiler) {
|
||||
compiler.prefix = prefix
|
||||
}
|
||||
}
|
||||
|
||||
// WithLocal configures the compiler with the local flag. The local
|
||||
// flag indicates the pipeline execution is running in a local development
|
||||
// environment with a mounted local working directory.
|
||||
func WithLocal(local bool) Option {
|
||||
return func(compiler *Compiler) {
|
||||
compiler.local = local
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnviron configures the compiler with environment variables
|
||||
// added by default to every container in the pipeline.
|
||||
func WithEnviron(env map[string]string) Option {
|
||||
return func(compiler *Compiler) {
|
||||
for k, v := range env {
|
||||
compiler.env[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithProxy configures the compiler with HTTP_PROXY, HTTPS_PROXY,
|
||||
// and NO_PROXY environment variables added by default to every
|
||||
// container in the pipeline.
|
||||
func WithProxy() Option {
|
||||
return WithEnviron(
|
||||
map[string]string{
|
||||
"no_proxy": noProxy,
|
||||
"NO_PROXY": noProxy,
|
||||
"http_proxy": httpProxy,
|
||||
"HTTP_PROXY": httpProxy,
|
||||
"HTTPS_PROXY": httpsProxy,
|
||||
"https_proxy": httpsProxy,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// TODO(bradrydzewski) consider an alternate approach to
|
||||
// WithProxy where the proxy strings are passed directly
|
||||
// to the function as named parameters.
|
||||
|
||||
// func WithProxy2(http, https, none string) Option {
|
||||
// return WithEnviron(
|
||||
// map[string]string{
|
||||
// "no_proxy": none,
|
||||
// "NO_PROXY": none,
|
||||
// "http_proxy": http,
|
||||
// "HTTP_PROXY": http,
|
||||
// "HTTPS_PROXY": https,
|
||||
// "https_proxy": https,
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
|
||||
var (
|
||||
noProxy = getenv("no_proxy")
|
||||
httpProxy = getenv("https_proxy")
|
||||
httpsProxy = getenv("https_proxy")
|
||||
)
|
||||
|
||||
// getenv returns the named environment variable.
|
||||
func getenv(name string) (value string) {
|
||||
name = strings.ToUpper(name)
|
||||
if value := os.Getenv(name); value != "" {
|
||||
return value
|
||||
}
|
||||
name = strings.ToLower(name)
|
||||
if value := os.Getenv(name); value != "" {
|
||||
return value
|
||||
}
|
||||
return
|
||||
}
|
65
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/compiler/params.go
generated
vendored
Normal file
65
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/compiler/params.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
json "github.com/ghodss/yaml"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// paramsToEnv uses reflection to convert a map[string]interface to a list
|
||||
// of environment variables.
|
||||
func paramsToEnv(from map[string]interface{}, to map[string]string) error {
|
||||
for k, v := range from {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
t := reflect.TypeOf(v)
|
||||
vv := reflect.ValueOf(v)
|
||||
|
||||
k = "PLUGIN_" + strings.ToUpper(k)
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
to[k] = strconv.FormatBool(vv.Bool())
|
||||
|
||||
case reflect.String:
|
||||
to[k] = vv.String()
|
||||
|
||||
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int8:
|
||||
to[k] = fmt.Sprintf("%v", vv.Int())
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
to[k] = fmt.Sprintf("%v", vv.Float())
|
||||
|
||||
case reflect.Map:
|
||||
yml, _ := yaml.Marshal(vv.Interface())
|
||||
out, _ := json.YAMLToJSON(yml)
|
||||
to[k] = string(out)
|
||||
|
||||
case reflect.Slice:
|
||||
out, err := yaml.Marshal(vv.Interface())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
in := []string{}
|
||||
err = yaml.Unmarshal(out, &in)
|
||||
if err == nil {
|
||||
to[k] = strings.Join(in, ",")
|
||||
} else {
|
||||
out, err = json.YAMLToJSON(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
to[k] = string(out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
52
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/compiler/script_posix.go
generated
vendored
Normal file
52
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/compiler/script_posix.go
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// generateScriptPosix is a helper function that generates a build script
|
||||
// for a linux container using the given
|
||||
func generateScriptPosix(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 "$CI_NETRC_MACHINE" ]; then
|
||||
cat <<EOF > $HOME/.netrc
|
||||
machine $CI_NETRC_MACHINE
|
||||
login $CI_NETRC_USERNAME
|
||||
password $CI_NETRC_PASSWORD
|
||||
EOF
|
||||
chmod 0600 $HOME/.netrc
|
||||
fi
|
||||
unset CI_NETRC_USERNAME
|
||||
unset CI_NETRC_PASSWORD
|
||||
unset CI_SCRIPT
|
||||
%s
|
||||
`
|
||||
|
||||
// traceScript is a helper script that is added to the build script
|
||||
// to trace a command.
|
||||
const traceScript = `
|
||||
echo + %s
|
||||
%s
|
||||
`
|
1
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/compiler/script_win.go
generated
vendored
Normal file
1
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/compiler/script_win.go
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
package compiler
|
68
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/config.go
generated
vendored
Normal file
68
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/config.go
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
libcompose "github.com/docker/libcompose/yaml"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type (
|
||||
// Config defines a pipeline configuration.
|
||||
Config struct {
|
||||
Platform string
|
||||
Branches Constraint
|
||||
Workspace Workspace
|
||||
Clone Containers
|
||||
Pipeline Containers
|
||||
Services Containers
|
||||
Networks Networks
|
||||
Volumes Volumes
|
||||
Labels libcompose.SliceorMap
|
||||
}
|
||||
|
||||
// Workspace defines a pipeline workspace.
|
||||
Workspace struct {
|
||||
Base string
|
||||
Path string
|
||||
}
|
||||
)
|
||||
|
||||
// Parse parses the configuration from bytes b.
|
||||
func Parse(r io.Reader) (*Config, error) {
|
||||
out, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseBytes(out)
|
||||
}
|
||||
|
||||
// ParseBytes parses the configuration from bytes b.
|
||||
func ParseBytes(b []byte) (*Config, error) {
|
||||
out := new(Config)
|
||||
err := yaml.Unmarshal(b, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ParseString parses the configuration from string s.
|
||||
func ParseString(s string) (*Config, error) {
|
||||
return ParseBytes(
|
||||
[]byte(s),
|
||||
)
|
||||
}
|
||||
|
||||
// ParseFile parses the configuration from path p.
|
||||
func ParseFile(p string) (*Config, error) {
|
||||
f, err := os.Open(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return Parse(f)
|
||||
}
|
152
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/constraint.go
generated
vendored
Normal file
152
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/constraint.go
generated
vendored
Normal file
|
@ -0,0 +1,152 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline/frontend"
|
||||
libcompose "github.com/docker/libcompose/yaml"
|
||||
)
|
||||
|
||||
type (
|
||||
// Constraints defines a set of runtime constraints.
|
||||
Constraints struct {
|
||||
Repo Constraint
|
||||
Instance Constraint
|
||||
Platform Constraint
|
||||
Environment Constraint
|
||||
Event Constraint
|
||||
Branch Constraint
|
||||
Status Constraint
|
||||
Matrix ConstraintMap
|
||||
}
|
||||
|
||||
// Constraint defines a runtime constraint.
|
||||
Constraint struct {
|
||||
Include []string
|
||||
Exclude []string
|
||||
}
|
||||
|
||||
// ConstraintMap defines a runtime constraint map.
|
||||
ConstraintMap struct {
|
||||
Include map[string]string
|
||||
Exclude map[string]string
|
||||
}
|
||||
)
|
||||
|
||||
// Match returns true if all constraints match the given input. If a single
|
||||
// constraint fails a false value is returned.
|
||||
func (c *Constraints) Match(metadata frontend.Metadata) bool {
|
||||
return c.Platform.Match(metadata.Sys.Arch) &&
|
||||
c.Environment.Match(metadata.Curr.Target) &&
|
||||
c.Event.Match(metadata.Curr.Event) &&
|
||||
c.Branch.Match(metadata.Curr.Commit.Branch) &&
|
||||
c.Repo.Match(metadata.Repo.Name) &&
|
||||
c.Matrix.Match(metadata.Job.Matrix)
|
||||
}
|
||||
|
||||
// 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 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 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 unmarshals the constraint.
|
||||
func (c *Constraint) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var out1 = struct {
|
||||
Include libcompose.Stringorslice
|
||||
Exclude libcompose.Stringorslice
|
||||
}{}
|
||||
|
||||
var out2 libcompose.Stringorslice
|
||||
|
||||
unmarshal(&out1)
|
||||
unmarshal(&out2)
|
||||
|
||||
c.Exclude = out1.Exclude
|
||||
c.Include = append(
|
||||
out1.Include,
|
||||
out2...,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 unmarshals the constraint map.
|
||||
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
|
||||
}
|
80
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/container.go
generated
vendored
Normal file
80
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/container.go
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
libcompose "github.com/docker/libcompose/yaml"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type (
|
||||
// AuthConfig defines registry authentication credentials.
|
||||
AuthConfig struct {
|
||||
Username string
|
||||
Password string
|
||||
Email string
|
||||
}
|
||||
|
||||
// Containers denotes an ordered collection of containers.
|
||||
Containers struct {
|
||||
Containers []*Container
|
||||
}
|
||||
|
||||
// Container defines a container.
|
||||
Container struct {
|
||||
AuthConfig AuthConfig `yaml:"auth_config,omitempty"`
|
||||
CapAdd []string `yaml:"cap_add,omitempty"`
|
||||
CapDrop []string `yaml:"cap_drop,omitempty"`
|
||||
Command libcompose.Command `yaml:"command,omitempty"`
|
||||
Commands libcompose.Stringorslice `yaml:"commands,omitempty"`
|
||||
CPUQuota libcompose.StringorInt `yaml:"cpu_quota,omitempty"`
|
||||
CPUSet string `yaml:"cpuset,omitempty"`
|
||||
CPUShares libcompose.StringorInt `yaml:"cpu_shares,omitempty"`
|
||||
Detached bool `yaml:"detach,omitempty"`
|
||||
Devices []string `yaml:"devices,omitempty"`
|
||||
DNS libcompose.Stringorslice `yaml:"dns,omitempty"`
|
||||
DNSSearch libcompose.Stringorslice `yaml:"dns_search,omitempty"`
|
||||
Entrypoint libcompose.Command `yaml:"entrypoint,omitempty"`
|
||||
Environment libcompose.SliceorMap `yaml:"environment,omitempty"`
|
||||
ExtraHosts []string `yaml:"extra_hosts,omitempty"`
|
||||
Group string `yaml:"group,omitempty"`
|
||||
Image string `yaml:"image,omitempty"`
|
||||
Isolation string `yaml:"isolation,omitempty"`
|
||||
Labels libcompose.SliceorMap `yaml:"labels,omitempty"`
|
||||
MemLimit libcompose.MemStringorInt `yaml:"mem_limit,omitempty"`
|
||||
MemSwapLimit libcompose.MemStringorInt `yaml:"memswap_limit,omitempty"`
|
||||
MemSwappiness libcompose.MemStringorInt `yaml:"mem_swappiness,omitempty"`
|
||||
Name string `yaml:"name,omitempty"`
|
||||
NetworkMode string `yaml:"network_mode,omitempty"`
|
||||
Networks libcompose.Networks `yaml:"networks,omitempty"`
|
||||
Privileged bool `yaml:"privileged,omitempty"`
|
||||
Pull bool `yaml:"pull,omitempty"`
|
||||
ShmSize libcompose.MemStringorInt `yaml:"shm_size,omitempty"`
|
||||
Ulimits libcompose.Ulimits `yaml:"ulimits,omitempty"`
|
||||
Volumes libcompose.Volumes `yaml:"volumes,omitempty"`
|
||||
Constraints Constraints `yaml:"when,omitempty"`
|
||||
Vargs map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
)
|
||||
|
||||
// UnmarshalYAML implements the Unmarshaller interface.
|
||||
func (c *Containers) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
slice := yaml.MapSlice{}
|
||||
if err := unmarshal(&slice); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range slice {
|
||||
container := Container{}
|
||||
out, _ := yaml.Marshal(s.Value)
|
||||
|
||||
if err := yaml.Unmarshal(out, &container); err != nil {
|
||||
return err
|
||||
}
|
||||
if container.Name == "" {
|
||||
container.Name = fmt.Sprintf("%v", s.Key)
|
||||
}
|
||||
c.Containers = append(c.Containers, &container)
|
||||
}
|
||||
return nil
|
||||
}
|
109
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/linter/linter.go
generated
vendored
Normal file
109
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/linter/linter.go
generated
vendored
Normal file
|
@ -0,0 +1,109 @@
|
|||
package linter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline/frontend/yaml"
|
||||
)
|
||||
|
||||
// A Linter lints a pipeline configuration.
|
||||
type Linter struct {
|
||||
trusted bool
|
||||
}
|
||||
|
||||
// New creates a new Linter with options.
|
||||
func New(opts ...Option) *Linter {
|
||||
linter := new(Linter)
|
||||
for _, opt := range opts {
|
||||
opt(linter)
|
||||
}
|
||||
return linter
|
||||
}
|
||||
|
||||
// Lint lints the configuration.
|
||||
func (l *Linter) Lint(c *yaml.Config) error {
|
||||
var containers []*yaml.Container
|
||||
containers = append(containers, c.Pipeline.Containers...)
|
||||
containers = append(containers, c.Services.Containers...)
|
||||
|
||||
for _, container := range containers {
|
||||
if err := l.lintImage(container); err != nil {
|
||||
return err
|
||||
}
|
||||
if l.trusted == false {
|
||||
if err := l.lintTrusted(container); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if isService(container) == false {
|
||||
if err := l.lintEntrypoint(container); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.Pipeline.Containers) == 0 {
|
||||
return fmt.Errorf("Invalid or missing pipeline section")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Linter) lintImage(c *yaml.Container) error {
|
||||
if len(c.Image) == 0 {
|
||||
return fmt.Errorf("Invalid or missing image")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Linter) lintEntrypoint(c *yaml.Container) error {
|
||||
if len(c.Entrypoint) != 0 {
|
||||
return fmt.Errorf("Cannot override container entrypoint")
|
||||
}
|
||||
if len(c.Command) != 0 {
|
||||
return fmt.Errorf("Cannot override container command")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Linter) lintTrusted(c *yaml.Container) error {
|
||||
if c.Privileged {
|
||||
return fmt.Errorf("Insufficient privileges to use privileged mode")
|
||||
}
|
||||
if c.ShmSize != 0 {
|
||||
return fmt.Errorf("Insufficient privileges to override shm_size")
|
||||
}
|
||||
if len(c.DNS) != 0 {
|
||||
return fmt.Errorf("Insufficient privileges to use custom dns")
|
||||
}
|
||||
if len(c.DNSSearch) != 0 {
|
||||
return fmt.Errorf("Insufficient privileges to use dns_search")
|
||||
}
|
||||
if len(c.Devices) != 0 {
|
||||
return fmt.Errorf("Insufficient privileges to use devices")
|
||||
}
|
||||
if len(c.ExtraHosts) != 0 {
|
||||
return fmt.Errorf("Insufficient privileges to use extra_hosts")
|
||||
}
|
||||
if len(c.NetworkMode) != 0 {
|
||||
return fmt.Errorf("Insufficient privileges to use network_mode")
|
||||
}
|
||||
if c.Networks.Networks != nil && len(c.Networks.Networks) != 0 {
|
||||
return fmt.Errorf("Insufficient privileges to use networks")
|
||||
}
|
||||
if c.Volumes.Volumes != nil && len(c.Volumes.Volumes) != 0 {
|
||||
return fmt.Errorf("Insufficient privileges to use volumes")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isService(c *yaml.Container) bool {
|
||||
return !isScript(c) && !isPlugin(c)
|
||||
}
|
||||
|
||||
func isScript(c *yaml.Container) bool {
|
||||
return len(c.Commands) != 0
|
||||
}
|
||||
|
||||
func isPlugin(c *yaml.Container) bool {
|
||||
return len(c.Vargs) != 0
|
||||
}
|
11
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/linter/option.go
generated
vendored
Normal file
11
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/linter/option.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
package linter
|
||||
|
||||
// Option configures a linting option.
|
||||
type Option func(*Linter)
|
||||
|
||||
// WithTrusted adds the trusted option to the linter.
|
||||
func WithTrusted(trusted bool) Option {
|
||||
return func(linter *Linter) {
|
||||
linter.trusted = trusted
|
||||
}
|
||||
}
|
117
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/matrix/matrix.go
generated
vendored
Normal file
117
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/matrix/matrix.go
generated
vendored
Normal file
|
@ -0,0 +1,117 @@
|
|||
package matrix
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
limitTags = 10
|
||||
limitAxis = 25
|
||||
)
|
||||
|
||||
// Matrix represents the build matrix.
|
||||
type Matrix map[string][]string
|
||||
|
||||
// Axis represents a single permutation of entries from the build matrix.
|
||||
type Axis map[string]string
|
||||
|
||||
// String returns a string representation of an Axis as a comma-separated list
|
||||
// of environment variables.
|
||||
func (a Axis) String() string {
|
||||
var envs []string
|
||||
for k, v := range a {
|
||||
envs = append(envs, k+"="+v)
|
||||
}
|
||||
return strings.Join(envs, " ")
|
||||
}
|
||||
|
||||
// Parse parses the Yaml matrix definition.
|
||||
func Parse(data []byte) ([]Axis, error) {
|
||||
|
||||
axis, err := parseList(data)
|
||||
if err == nil && len(axis) != 0 {
|
||||
return axis, nil
|
||||
}
|
||||
|
||||
matrix, err := parse(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 calc(matrix), nil
|
||||
}
|
||||
|
||||
// ParseString parses the Yaml string matrix definition.
|
||||
func ParseString(data string) ([]Axis, error) {
|
||||
return Parse([]byte(data))
|
||||
}
|
||||
|
||||
func calc(matrix Matrix) []Axis {
|
||||
// calculate number of permutations and extract the list of tags
|
||||
// (ie go_version, redis_version, etc)
|
||||
var perm int
|
||||
var tags []string
|
||||
for k, v := range matrix {
|
||||
perm *= len(v)
|
||||
if perm == 0 {
|
||||
perm = len(v)
|
||||
}
|
||||
tags = append(tags, k)
|
||||
}
|
||||
|
||||
// structure to hold the transformed result set
|
||||
axisList := []Axis{}
|
||||
|
||||
// for each axis calculate the uniqe set of values that should be used.
|
||||
for p := 0; p < perm; p++ {
|
||||
axis := map[string]string{}
|
||||
decr := perm
|
||||
for i, tag := range tags {
|
||||
elems := matrix[tag]
|
||||
decr = decr / len(elems)
|
||||
elem := p / decr % len(elems)
|
||||
axis[tag] = elems[elem]
|
||||
|
||||
// enforce a maximum number of tags in the build matrix.
|
||||
if i > limitTags {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// append to the list of axis.
|
||||
axisList = append(axisList, axis)
|
||||
|
||||
// enforce a maximum number of axis that should be calculated.
|
||||
if p > limitAxis {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return axisList
|
||||
}
|
||||
|
||||
func parse(raw []byte) (Matrix, error) {
|
||||
data := struct {
|
||||
Matrix map[string][]string
|
||||
}{}
|
||||
err := yaml.Unmarshal(raw, &data)
|
||||
return data.Matrix, err
|
||||
}
|
||||
|
||||
func parseList(raw []byte) ([]Axis, error) {
|
||||
data := struct {
|
||||
Matrix struct {
|
||||
Include []Axis
|
||||
}
|
||||
}{}
|
||||
|
||||
err := yaml.Unmarshal(raw, &data)
|
||||
return data.Matrix.Include, err
|
||||
}
|
48
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/network.go
generated
vendored
Normal file
48
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/network.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type (
|
||||
// Networks defines a collection of networks.
|
||||
Networks struct {
|
||||
Networks []*Network
|
||||
}
|
||||
|
||||
// Network defines a container network.
|
||||
Network struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Driver string `yaml:"driver,omitempty"`
|
||||
DriverOpts map[string]string `yaml:"driver_opts,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
// UnmarshalYAML implements the Unmarshaller interface.
|
||||
func (n *Networks) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
slice := yaml.MapSlice{}
|
||||
err := unmarshal(&slice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range slice {
|
||||
nn := Network{}
|
||||
out, _ := yaml.Marshal(s.Value)
|
||||
|
||||
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
|
||||
}
|
48
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/volume.go
generated
vendored
Normal file
48
vendor/github.com/cncd/pipeline/pipeline/frontend/yaml/volume.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type (
|
||||
// Volumes defines a collection of volumes.
|
||||
Volumes struct {
|
||||
Volumes []*Volume
|
||||
}
|
||||
|
||||
// Volume defines a container volume.
|
||||
Volume struct {
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Driver string `yaml:"driver,omitempty"`
|
||||
DriverOpts map[string]string `yaml:"driver_opts,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
// UnmarshalYAML implements the Unmarshaller interface.
|
||||
func (v *Volumes) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
slice := yaml.MapSlice{}
|
||||
err := unmarshal(&slice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range slice {
|
||||
vv := Volume{}
|
||||
out, _ := yaml.Marshal(s.Value)
|
||||
|
||||
err = yaml.Unmarshal(out, &vv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if vv.Name == "" {
|
||||
vv.Name = fmt.Sprintf("%v", s.Key)
|
||||
}
|
||||
if vv.Driver == "" {
|
||||
vv.Driver = "local"
|
||||
}
|
||||
v.Volumes = append(v.Volumes, &vv)
|
||||
}
|
||||
return err
|
||||
}
|
36
vendor/github.com/cncd/pipeline/pipeline/interrupt/interrupt.go
generated
vendored
Normal file
36
vendor/github.com/cncd/pipeline/pipeline/interrupt/interrupt.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
package interrupt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
// WithContext returns a copy of parent context whose Done channel is closed
|
||||
// when an os interrupt signal is received.
|
||||
func WithContext(ctx context.Context) context.Context {
|
||||
return WithContextFunc(ctx, func() {
|
||||
println("ctrl+c received, terminating process")
|
||||
})
|
||||
}
|
||||
|
||||
// WithContextFunc returns a copy of parent context that is cancelled when
|
||||
// an os interrupt signal is received. The callback function f is invoked
|
||||
// before cancellation.
|
||||
func WithContextFunc(ctx context.Context, f func()) context.Context {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
go func() {
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
defer signal.Stop(c)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-c:
|
||||
f()
|
||||
cancel()
|
||||
}
|
||||
}()
|
||||
|
||||
return ctx
|
||||
}
|
20
vendor/github.com/cncd/pipeline/pipeline/logger.go
generated
vendored
Normal file
20
vendor/github.com/cncd/pipeline/pipeline/logger.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
"github.com/cncd/pipeline/pipeline/multipart"
|
||||
)
|
||||
|
||||
// Logger handles the process logging.
|
||||
type Logger interface {
|
||||
Log(*backend.Step, multipart.Reader) error
|
||||
}
|
||||
|
||||
// LogFunc type is an adapter to allow the use of an ordinary
|
||||
// function for process logging.
|
||||
type LogFunc func(*backend.Step, multipart.Reader) error
|
||||
|
||||
// Log calls f(proc, r).
|
||||
func (f LogFunc) Log(step *backend.Step, r multipart.Reader) error {
|
||||
return f(step, r)
|
||||
}
|
1
vendor/github.com/cncd/pipeline/pipeline/multipart/doc.go
generated
vendored
Normal file
1
vendor/github.com/cncd/pipeline/pipeline/multipart/doc.go
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
package multipart
|
102
vendor/github.com/cncd/pipeline/pipeline/multipart/reader.go
generated
vendored
Normal file
102
vendor/github.com/cncd/pipeline/pipeline/multipart/reader.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
package multipart
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
)
|
||||
|
||||
// Reader is an iterator over parts in a multipart log stream.
|
||||
type Reader interface {
|
||||
// NextPart returns the next part in the multipart or
|
||||
// an error. When there are no more parts, the error
|
||||
// io.EOF is returned.
|
||||
NextPart() (Part, error)
|
||||
}
|
||||
|
||||
// A Part represents a single part in a multipart body.
|
||||
type Part interface {
|
||||
io.Reader
|
||||
|
||||
// Header returns the headers of the body with the
|
||||
// keys canonicalized.
|
||||
Header() textproto.MIMEHeader
|
||||
|
||||
// FileName returns the filename parameter of the
|
||||
// Content-Disposition header.
|
||||
FileName() string
|
||||
|
||||
// FormName returns the name parameter if p has a
|
||||
// Content-Disposition of type form-data.
|
||||
FormName() string
|
||||
}
|
||||
|
||||
// New returns a new multipart Reader.
|
||||
func New(r io.Reader) Reader {
|
||||
buf := bufio.NewReader(r)
|
||||
out, _ := buf.Peek(4)
|
||||
|
||||
if bytes.Equal(out, []byte("MIME")) {
|
||||
return &multipartReader{
|
||||
reader: multipart.NewReader(buf, "boundary"),
|
||||
}
|
||||
}
|
||||
return &textReader{
|
||||
reader: r,
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
type multipartReader struct {
|
||||
reader *multipart.Reader
|
||||
}
|
||||
|
||||
func (r *multipartReader) NextPart() (Part, error) {
|
||||
next, err := r.reader.NextPart()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
part := new(part)
|
||||
part.Reader = next
|
||||
part.filename = next.FileName()
|
||||
part.formname = next.FormName()
|
||||
part.header = next.Header
|
||||
return part, nil
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
type textReader struct {
|
||||
reader io.Reader
|
||||
done bool
|
||||
}
|
||||
|
||||
func (r *textReader) NextPart() (Part, error) {
|
||||
if r.done {
|
||||
return nil, io.EOF
|
||||
}
|
||||
r.done = true
|
||||
p := new(part)
|
||||
p.Reader = r.reader
|
||||
p.filename = "terminal.log"
|
||||
return p, nil
|
||||
}
|
||||
|
||||
type part struct {
|
||||
io.Reader
|
||||
|
||||
filename string
|
||||
formname string
|
||||
header textproto.MIMEHeader
|
||||
}
|
||||
|
||||
func (p *part) Header() textproto.MIMEHeader { return p.header }
|
||||
func (p *part) FileName() string { return p.filename }
|
||||
func (p *part) FormName() string { return p.filename }
|
38
vendor/github.com/cncd/pipeline/pipeline/option.go
generated
vendored
Normal file
38
vendor/github.com/cncd/pipeline/pipeline/option.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
)
|
||||
|
||||
// Option configures a runtime option.
|
||||
type Option func(*Runtime)
|
||||
|
||||
// WithEngine returns an option configured with a runtime engine.
|
||||
func WithEngine(engine backend.Engine) Option {
|
||||
return func(r *Runtime) {
|
||||
r.engine = engine
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger returns an option configured with a runtime logger.
|
||||
func WithLogger(logger Logger) Option {
|
||||
return func(r *Runtime) {
|
||||
r.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
// WithTracer returns an option configured with a runtime tracer.
|
||||
func WithTracer(tracer Tracer) Option {
|
||||
return func(r *Runtime) {
|
||||
r.tracer = tracer
|
||||
}
|
||||
}
|
||||
|
||||
// WithContext returns an option configured with a context.
|
||||
func WithContext(ctx context.Context) Option {
|
||||
return func(r *Runtime) {
|
||||
r.ctx = ctx
|
||||
}
|
||||
}
|
37
vendor/github.com/cncd/pipeline/pipeline/parse.go
generated
vendored
Normal file
37
vendor/github.com/cncd/pipeline/pipeline/parse.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
)
|
||||
|
||||
// Parse parses the pipeline config from an io.Reader.
|
||||
func Parse(r io.Reader) (*backend.Config, error) {
|
||||
cfg := new(backend.Config)
|
||||
err := json.NewDecoder(r).Decode(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// ParseFile parses the pipeline config from a file.
|
||||
func ParseFile(path string) (*backend.Config, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return Parse(f)
|
||||
}
|
||||
|
||||
// ParseString parses the pipeline config from a string.
|
||||
func ParseString(s string) (*backend.Config, error) {
|
||||
return Parse(
|
||||
strings.NewReader(s),
|
||||
)
|
||||
}
|
175
vendor/github.com/cncd/pipeline/pipeline/pipeline.go
generated
vendored
Normal file
175
vendor/github.com/cncd/pipeline/pipeline/pipeline.go
generated
vendored
Normal file
|
@ -0,0 +1,175 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
"github.com/cncd/pipeline/pipeline/multipart"
|
||||
)
|
||||
|
||||
type (
|
||||
// State defines the pipeline and process state.
|
||||
State struct {
|
||||
// Global state of the pipeline.
|
||||
Pipeline struct {
|
||||
// Pipeline time started
|
||||
Time int64 `json:"time"`
|
||||
// Current pipeline step
|
||||
Step *backend.Step `json:"step"`
|
||||
// Current pipeline error state
|
||||
Error error `json:"error"`
|
||||
}
|
||||
|
||||
// Current process state.
|
||||
Process *backend.State
|
||||
}
|
||||
)
|
||||
|
||||
// Runtime is a configuration runtime.
|
||||
type Runtime struct {
|
||||
err error
|
||||
spec *backend.Config
|
||||
engine backend.Engine
|
||||
started int64
|
||||
|
||||
ctx context.Context
|
||||
tracer Tracer
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// New returns a new runtime using the specified runtime
|
||||
// configuration and runtime engine.
|
||||
func New(spec *backend.Config, opts ...Option) *Runtime {
|
||||
r := new(Runtime)
|
||||
r.spec = spec
|
||||
r.ctx = context.Background()
|
||||
for _, opts := range opts {
|
||||
opts(r)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Run starts the runtime and waits for it to complete.
|
||||
func (r *Runtime) Run() error {
|
||||
defer func() {
|
||||
r.engine.Destroy(r.spec)
|
||||
}()
|
||||
|
||||
r.started = time.Now().Unix()
|
||||
if err := r.engine.Setup(r.spec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, stage := range r.spec.Stages {
|
||||
select {
|
||||
case <-r.ctx.Done():
|
||||
return ErrCancel
|
||||
case err := <-r.execAll(stage.Steps):
|
||||
if err != nil {
|
||||
r.err = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return r.err
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
func (r *Runtime) execAll(procs []*backend.Step) <-chan error {
|
||||
var g errgroup.Group
|
||||
done := make(chan error)
|
||||
|
||||
for _, proc := range procs {
|
||||
proc := proc
|
||||
g.Go(func() error {
|
||||
return r.exec(proc)
|
||||
})
|
||||
}
|
||||
|
||||
go func() {
|
||||
done <- g.Wait()
|
||||
close(done)
|
||||
}()
|
||||
return done
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
func (r *Runtime) exec(proc *backend.Step) error {
|
||||
switch {
|
||||
case r.err != nil && proc.OnFailure == false:
|
||||
return nil
|
||||
case r.err == nil && proc.OnSuccess == false:
|
||||
return nil
|
||||
}
|
||||
|
||||
if r.tracer != nil {
|
||||
state := new(State)
|
||||
state.Pipeline.Time = r.started
|
||||
state.Pipeline.Error = r.err
|
||||
state.Pipeline.Step = proc
|
||||
state.Process = new(backend.State) // empty
|
||||
if err := r.tracer.Trace(state); err == ErrSkip {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.engine.Exec(proc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.logger != nil {
|
||||
rc, err := r.engine.Tail(proc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
r.logger.Log(proc, multipart.New(rc))
|
||||
rc.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
if proc.Detached {
|
||||
return nil
|
||||
}
|
||||
|
||||
wait, err := r.engine.Wait(proc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.tracer != nil {
|
||||
state := new(State)
|
||||
state.Pipeline.Time = r.started
|
||||
state.Pipeline.Error = r.err
|
||||
state.Pipeline.Step = proc
|
||||
state.Process = wait
|
||||
if err := r.tracer.Trace(state); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if wait.OOMKilled {
|
||||
return &OomError{
|
||||
Name: proc.Name,
|
||||
Code: wait.ExitCode,
|
||||
}
|
||||
} else if wait.ExitCode != 0 {
|
||||
return &ExitError{
|
||||
Name: proc.Name,
|
||||
Code: wait.ExitCode,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
180
vendor/github.com/cncd/pipeline/pipeline/rpc/client.go
generated
vendored
Normal file
180
vendor/github.com/cncd/pipeline/pipeline/rpc/client.go
generated
vendored
Normal file
|
@ -0,0 +1,180 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sourcegraph/jsonrpc2"
|
||||
websocketrpc "github.com/sourcegraph/jsonrpc2/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
methodNext = "next"
|
||||
methodNotify = "notify"
|
||||
methodExtend = "extend"
|
||||
methodUpdate = "update"
|
||||
methodLog = "log"
|
||||
methodSave = "save"
|
||||
)
|
||||
|
||||
type (
|
||||
saveReq struct {
|
||||
ID string `json:"id"`
|
||||
Mime string `json:"mime"`
|
||||
Data []byte `json:"data"`
|
||||
}
|
||||
|
||||
updateReq struct {
|
||||
ID string `json:"id"`
|
||||
State State `json:"state"`
|
||||
}
|
||||
|
||||
logReq struct {
|
||||
ID string `json:"id"`
|
||||
Line *Line `json:"line"`
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRetryClount = math.MaxInt32
|
||||
defaultBackoff = 10 * time.Second
|
||||
)
|
||||
|
||||
// Client represents an rpc client.
|
||||
type Client struct {
|
||||
sync.Mutex
|
||||
|
||||
conn *jsonrpc2.Conn
|
||||
done bool
|
||||
retry int
|
||||
backoff time.Duration
|
||||
endpoint string
|
||||
token string
|
||||
}
|
||||
|
||||
// NewClient returns a new Client.
|
||||
func NewClient(endpoint string, opts ...Option) (*Client, error) {
|
||||
cli := &Client{
|
||||
endpoint: endpoint,
|
||||
retry: defaultRetryClount,
|
||||
backoff: defaultBackoff,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(cli)
|
||||
}
|
||||
err := cli.openRetry()
|
||||
return cli, err
|
||||
}
|
||||
|
||||
// Next returns the next pipeline in the queue.
|
||||
func (t *Client) Next(c context.Context) (*Pipeline, error) {
|
||||
res := new(Pipeline)
|
||||
err := t.call(c, methodNext, nil, res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Notify returns true if the pipeline should be cancelled.
|
||||
func (t *Client) Notify(c context.Context, id string) (bool, error) {
|
||||
out := false
|
||||
err := t.call(c, methodNotify, id, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Extend extends the pipeline deadline.
|
||||
func (t *Client) Extend(c context.Context, id string) error {
|
||||
return t.call(c, methodExtend, id, nil)
|
||||
}
|
||||
|
||||
// Update updates the pipeline state.
|
||||
func (t *Client) Update(c context.Context, id string, state State) error {
|
||||
params := updateReq{id, state}
|
||||
return t.call(c, methodUpdate, ¶ms, nil)
|
||||
}
|
||||
|
||||
// Log writes the pipeline log entry.
|
||||
func (t *Client) Log(c context.Context, id string, line *Line) error {
|
||||
params := logReq{id, line}
|
||||
return t.call(c, methodLog, ¶ms, nil)
|
||||
}
|
||||
|
||||
// Save saves the pipeline artifact.
|
||||
func (t *Client) Save(c context.Context, id, mime string, file io.Reader) error {
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params := saveReq{id, mime, data}
|
||||
return t.call(c, methodSave, params, nil)
|
||||
}
|
||||
|
||||
// Close closes the client connection.
|
||||
func (t *Client) Close() error {
|
||||
t.Lock()
|
||||
t.done = true
|
||||
t.Unlock()
|
||||
return t.conn.Close()
|
||||
}
|
||||
|
||||
// call makes the remote prodedure call. If the call fails due to connectivity
|
||||
// issues the connection is re-establish and call re-attempted.
|
||||
func (t *Client) call(ctx context.Context, name string, req, res interface{}) error {
|
||||
if err := t.conn.Call(ctx, name, req, res); err == nil {
|
||||
return nil
|
||||
} else if err != jsonrpc2.ErrClosed && err != io.ErrUnexpectedEOF {
|
||||
log.Printf("rpc: error making call: %s", err)
|
||||
return err
|
||||
} else {
|
||||
log.Printf("rpc: error making call: connection closed: %s", err)
|
||||
}
|
||||
if err := t.openRetry(); err != nil {
|
||||
return err
|
||||
}
|
||||
return t.conn.Call(ctx, name, req, res)
|
||||
}
|
||||
|
||||
// openRetry opens the connection and will retry on failure until
|
||||
// the connection is successfully open, or the maximum retry count
|
||||
// is exceeded.
|
||||
func (t *Client) openRetry() error {
|
||||
for i := 0; i < t.retry; i++ {
|
||||
err := t.open()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if err == io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("rpc: error re-connecting: %s", err)
|
||||
<-time.After(t.backoff)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// open creates a websocket connection to a peer and establishes a json
|
||||
// rpc communication stream.
|
||||
func (t *Client) open() error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
if t.done {
|
||||
return io.EOF
|
||||
}
|
||||
header := map[string][]string{
|
||||
"Content-Type": {"application/json-rpc"},
|
||||
"Authorization": {"Bearer " + t.token},
|
||||
}
|
||||
conn, _, err := websocket.DefaultDialer.Dial(t.endpoint, http.Header(header))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stream := websocketrpc.NewObjectStream(conn)
|
||||
t.conn = jsonrpc2.NewConn(context.Background(), stream, nil)
|
||||
return nil
|
||||
}
|
70
vendor/github.com/cncd/pipeline/pipeline/rpc/line.go
generated
vendored
Normal file
70
vendor/github.com/cncd/pipeline/pipeline/rpc/line.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Identifies the type of line in the logs.
|
||||
const (
|
||||
LineStdout int = iota
|
||||
LineStderr
|
||||
LineExitCode
|
||||
LineMetadata
|
||||
LineProgress
|
||||
)
|
||||
|
||||
// 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 LineExitCode:
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// LineWriter sends logs to the client.
|
||||
type LineWriter struct {
|
||||
peer Peer
|
||||
id string
|
||||
name string
|
||||
num int
|
||||
now time.Time
|
||||
}
|
||||
|
||||
// NewLineWriter returns a new line reader.
|
||||
func NewLineWriter(peer Peer, id, name string) *LineWriter {
|
||||
w := new(LineWriter)
|
||||
w.peer = peer
|
||||
w.id = id
|
||||
w.name = name
|
||||
w.num = 0
|
||||
w.now = time.Now().UTC()
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *LineWriter) Write(p []byte) (n int, err error) {
|
||||
for _, part := range bytes.Split(p, []byte{'\n'}) {
|
||||
line := &Line{
|
||||
Out: string(part),
|
||||
Proc: w.name,
|
||||
Pos: w.num,
|
||||
Time: int64(time.Since(w.now).Seconds()),
|
||||
Type: LineStdout,
|
||||
}
|
||||
w.peer.Log(context.Background(), w.id, line)
|
||||
w.num++
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
29
vendor/github.com/cncd/pipeline/pipeline/rpc/option.go
generated
vendored
Normal file
29
vendor/github.com/cncd/pipeline/pipeline/rpc/option.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
package rpc
|
||||
|
||||
import "time"
|
||||
|
||||
// Option configures a client option.
|
||||
type Option func(*Client)
|
||||
|
||||
// WithBackoff configures the backoff duration when attempting
|
||||
// to re-connect to a server.
|
||||
func WithBackoff(d time.Duration) Option {
|
||||
return func(c *Client) {
|
||||
c.backoff = d
|
||||
}
|
||||
}
|
||||
|
||||
// WithRetryLimit configures the maximum number of retries when
|
||||
// connecting to the server.
|
||||
func WithRetryLimit(i int) Option {
|
||||
return func(c *Client) {
|
||||
c.retry = i
|
||||
}
|
||||
}
|
||||
|
||||
// WithToken configures the client authorization token.
|
||||
func WithToken(t string) Option {
|
||||
return func(c *Client) {
|
||||
c.token = t
|
||||
}
|
||||
}
|
55
vendor/github.com/cncd/pipeline/pipeline/rpc/peer.go
generated
vendored
Normal file
55
vendor/github.com/cncd/pipeline/pipeline/rpc/peer.go
generated
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
)
|
||||
|
||||
type (
|
||||
// Filter defines filters for fetching items from the queue.
|
||||
Filter struct {
|
||||
Platform string `json:"platform"`
|
||||
}
|
||||
|
||||
// State defines the pipeline state.
|
||||
State struct {
|
||||
Proc string `json:"proc"`
|
||||
Exited bool `json:"exited"`
|
||||
ExitCode int `json:"exit_code"`
|
||||
Started int64 `json:"started"`
|
||||
Finished int64 `json:"finished"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// Pipeline defines the pipeline execution details.
|
||||
Pipeline struct {
|
||||
ID string `json:"id"`
|
||||
Config *backend.Config `json:"config"`
|
||||
Timeout int64 `json:"timeout"`
|
||||
}
|
||||
)
|
||||
|
||||
// Peer defines a peer-to-peer connection.
|
||||
type Peer interface {
|
||||
// Next returns the next pipeline in the queue.
|
||||
Next(c context.Context) (*Pipeline, error)
|
||||
|
||||
// Notify returns true if the pipeline should be cancelled.
|
||||
// TODO: rename to Done, Wait?
|
||||
Notify(c context.Context, id string) (bool, error)
|
||||
|
||||
// Extend extends the pipeline deadline
|
||||
Extend(c context.Context, id string) error
|
||||
|
||||
// Update updates the pipeline state.
|
||||
Update(c context.Context, id string, state State) error
|
||||
|
||||
// Save saves the pipeline artifact.
|
||||
// TODO rename to Upload
|
||||
Save(c context.Context, id, mime string, file io.Reader) error
|
||||
|
||||
// Log writes the pipeline log entry.
|
||||
Log(c context.Context, id string, line *Line) error
|
||||
}
|
126
vendor/github.com/cncd/pipeline/pipeline/rpc/server.go
generated
vendored
Normal file
126
vendor/github.com/cncd/pipeline/pipeline/rpc/server.go
generated
vendored
Normal file
|
@ -0,0 +1,126 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sourcegraph/jsonrpc2"
|
||||
websocketrpc "github.com/sourcegraph/jsonrpc2/websocket"
|
||||
)
|
||||
|
||||
// errNoSuchMethod is returned when the name rpc method does not exist.
|
||||
var errNoSuchMethod = errors.New("No such rpc method")
|
||||
|
||||
// noContext is an empty context used when no context is required.
|
||||
var noContext = context.Background()
|
||||
|
||||
// Server represents an rpc server.
|
||||
type Server struct {
|
||||
peer Peer
|
||||
}
|
||||
|
||||
// NewServer returns an rpc Server.
|
||||
func NewServer(peer Peer) *Server {
|
||||
return &Server{peer}
|
||||
}
|
||||
|
||||
// ServeHTTP implements an http.Handler that answers rpc requests.
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
upgrader := websocket.Upgrader{}
|
||||
c, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
conn := jsonrpc2.NewConn(ctx,
|
||||
websocketrpc.NewObjectStream(c),
|
||||
jsonrpc2.HandlerWithError(s.router),
|
||||
)
|
||||
defer func() {
|
||||
cancel()
|
||||
conn.Close()
|
||||
}()
|
||||
<-conn.DisconnectNotify()
|
||||
}
|
||||
|
||||
// router implements an jsonrpc2.Handler that answers RPC requests.
|
||||
func (s *Server) router(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) (interface{}, error) {
|
||||
switch req.Method {
|
||||
case methodNext:
|
||||
return s.next(ctx, req)
|
||||
case methodNotify:
|
||||
return s.notify(ctx, req)
|
||||
case methodExtend:
|
||||
return s.extend(ctx, req)
|
||||
case methodUpdate:
|
||||
return s.update(req)
|
||||
case methodLog:
|
||||
return s.log(req)
|
||||
case methodSave:
|
||||
return s.save(req)
|
||||
default:
|
||||
return nil, errNoSuchMethod
|
||||
}
|
||||
}
|
||||
|
||||
// next unmarshals the rpc request parameters and invokes the peer.Next
|
||||
// procedure. The results are retuned and written to the rpc response.
|
||||
func (s *Server) next(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) {
|
||||
return s.peer.Next(ctx)
|
||||
}
|
||||
|
||||
// notify unmarshals the rpc request parameters and invokes the peer.Notify
|
||||
// procedure. The results are retuned and written to the rpc response.
|
||||
func (s *Server) notify(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) {
|
||||
var id string
|
||||
err := json.Unmarshal([]byte(*req.Params), &id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.peer.Notify(ctx, id)
|
||||
}
|
||||
|
||||
// extend unmarshals the rpc request parameters and invokes the peer.Extend
|
||||
// procedure. The results are retuned and written to the rpc response.
|
||||
func (s *Server) extend(ctx context.Context, req *jsonrpc2.Request) (interface{}, error) {
|
||||
var id string
|
||||
err := json.Unmarshal([]byte(*req.Params), &id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, s.peer.Extend(ctx, id)
|
||||
}
|
||||
|
||||
// update unmarshals the rpc request parameters and invokes the peer.Update
|
||||
// procedure. The results are retuned and written to the rpc response.
|
||||
func (s *Server) update(req *jsonrpc2.Request) (interface{}, error) {
|
||||
in := new(updateReq)
|
||||
if err := json.Unmarshal([]byte(*req.Params), in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, s.peer.Update(noContext, in.ID, in.State)
|
||||
}
|
||||
|
||||
// log unmarshals the rpc request parameters and invokes the peer.Log
|
||||
// procedure. The results are retuned and written to the rpc response.
|
||||
func (s *Server) log(req *jsonrpc2.Request) (interface{}, error) {
|
||||
in := new(logReq)
|
||||
if err := json.Unmarshal([]byte(*req.Params), in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, s.peer.Log(noContext, in.ID, in.Line)
|
||||
}
|
||||
|
||||
// save unmarshals the rpc request parameters and invokes the peer.Save
|
||||
// procedure. The results are retuned and written to the rpc response.
|
||||
func (s *Server) save(req *jsonrpc2.Request) (interface{}, error) {
|
||||
in := new(saveReq)
|
||||
if err := json.Unmarshal([]byte(*req.Params), in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, s.peer.Save(noContext, in.ID, in.Mime, bytes.NewBuffer(in.Data))
|
||||
}
|
45
vendor/github.com/cncd/pipeline/pipeline/tracer.go
generated
vendored
Normal file
45
vendor/github.com/cncd/pipeline/pipeline/tracer.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Tracer handles process tracing.
|
||||
type Tracer interface {
|
||||
Trace(*State) error
|
||||
}
|
||||
|
||||
// TraceFunc type is an adapter to allow the use of ordinary
|
||||
// functions as a Tracer.
|
||||
type TraceFunc func(*State) error
|
||||
|
||||
// Trace calls f(proc, state).
|
||||
func (f TraceFunc) Trace(state *State) error {
|
||||
return f(state)
|
||||
}
|
||||
|
||||
// DefaultTracer provides a tracer that updates the CI_ enviornment
|
||||
// variables to include the correct timestamp and status.
|
||||
// TODO(bradrydzewski) find either a new home or better name for this.
|
||||
var DefaultTracer = TraceFunc(func(state *State) error {
|
||||
if state.Process.Exited {
|
||||
return nil
|
||||
}
|
||||
if state.Pipeline.Step.Environment == nil {
|
||||
return nil
|
||||
}
|
||||
state.Pipeline.Step.Environment["CI_BUILD_STATUS"] = "success"
|
||||
state.Pipeline.Step.Environment["CI_BUILD_STARTED"] = strconv.FormatInt(state.Pipeline.Time, 10)
|
||||
state.Pipeline.Step.Environment["CI_BUILD_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10)
|
||||
|
||||
state.Pipeline.Step.Environment["CI_JOB_STATUS"] = "success"
|
||||
state.Pipeline.Step.Environment["CI_JOB_STARTED"] = strconv.FormatInt(state.Pipeline.Time, 10)
|
||||
state.Pipeline.Step.Environment["CI_JOB_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10)
|
||||
|
||||
if state.Pipeline.Error != nil {
|
||||
state.Pipeline.Step.Environment["CI_BUILD_STATUS"] = "failure"
|
||||
state.Pipeline.Step.Environment["CI_JOB_STATUS"] = "failure"
|
||||
}
|
||||
return nil
|
||||
})
|
29
vendor/github.com/cncd/pubsub/LICENSE
generated
vendored
Normal file
29
vendor/github.com/cncd/pubsub/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2017, Brad Rydzewski
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
6
vendor/github.com/cncd/pubsub/README
generated
vendored
Normal file
6
vendor/github.com/cncd/pubsub/README
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
Go package provides a common interface for publish-subscriber messaging.
|
||||
|
||||
Documentation:
|
||||
|
||||
http://godoc.org/github.com/cncd/pubsub
|
||||
http://godoc.org/github.com/cncd/pubsub/gcp
|
75
vendor/github.com/cncd/pubsub/pub.go
generated
vendored
Normal file
75
vendor/github.com/cncd/pubsub/pub.go
generated
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
package pubsub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type subscriber struct {
|
||||
receiver Receiver
|
||||
}
|
||||
|
||||
type publisher struct {
|
||||
sync.Mutex
|
||||
|
||||
topics map[string]*topic
|
||||
}
|
||||
|
||||
// New creates an in-memory publisher.
|
||||
func New() Publisher {
|
||||
return &publisher{
|
||||
topics: make(map[string]*topic),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *publisher) Create(c context.Context, dest string) error {
|
||||
p.Lock()
|
||||
t, ok := p.topics[dest]
|
||||
if !ok {
|
||||
t = newTopic(dest)
|
||||
p.topics[dest] = t
|
||||
}
|
||||
p.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *publisher) Publish(c context.Context, dest string, message Message) error {
|
||||
p.Lock()
|
||||
t, ok := p.topics[dest]
|
||||
p.Unlock()
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
t.publish(message)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *publisher) Subscribe(c context.Context, dest string, receiver Receiver) error {
|
||||
p.Lock()
|
||||
t, ok := p.topics[dest]
|
||||
p.Unlock()
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
s := &subscriber{
|
||||
receiver: receiver,
|
||||
}
|
||||
t.subscribe(s)
|
||||
select {
|
||||
case <-c.Done():
|
||||
case <-t.done:
|
||||
}
|
||||
t.unsubscribe(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *publisher) Remove(c context.Context, dest string) error {
|
||||
p.Lock()
|
||||
t, ok := p.topics[dest]
|
||||
if ok {
|
||||
delete(p.topics, dest)
|
||||
t.close()
|
||||
}
|
||||
p.Unlock()
|
||||
return nil
|
||||
}
|
71
vendor/github.com/cncd/pubsub/pubsub.go
generated
vendored
Normal file
71
vendor/github.com/cncd/pubsub/pubsub.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
// Package pubsub implements a publish-subscriber messaging system.
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// ErrNotFound is returned when the named topic does not exist.
|
||||
var ErrNotFound = errors.New("topic not found")
|
||||
|
||||
// Message defines a published message.
|
||||
type Message struct {
|
||||
// ID identifies this message.
|
||||
ID string `json:"id,omitempty"`
|
||||
|
||||
// Data is the actual data in the entry.
|
||||
Data []byte `json:"data"`
|
||||
|
||||
// Labels represents the key-value pairs the entry is lebeled with.
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
// Receiver receives published messages.
|
||||
type Receiver func(Message)
|
||||
|
||||
// Publisher defines a mechanism for communicating messages from a group
|
||||
// of senders, called producers, to a group of consumers.
|
||||
type Publisher interface {
|
||||
// Create creates the named topic.
|
||||
Create(c context.Context, topic string) error
|
||||
|
||||
// Publish publishes the message.
|
||||
Publish(c context.Context, topic string, message Message) error
|
||||
|
||||
// Subscribe subscribes to the topic. The Receiver function is a callback
|
||||
// function that receives published messages.
|
||||
Subscribe(c context.Context, topic string, receiver Receiver) error
|
||||
|
||||
// Remove removes the named topic.
|
||||
Remove(c context.Context, topic string) error
|
||||
}
|
||||
|
||||
// // global instance of the queue.
|
||||
// var global = New()
|
||||
//
|
||||
// // Set sets the global queue.
|
||||
// func Set(p Publisher) {
|
||||
// global = p
|
||||
// }
|
||||
//
|
||||
// // Create creates the named topic.
|
||||
// func Create(c context.Context, topic string) error {
|
||||
// return global.Create(c, topic)
|
||||
// }
|
||||
//
|
||||
// // Publish publishes the message.
|
||||
// func Publish(c context.Context, topic string, message Message) error {
|
||||
// return global.Publish(c, topic, message)
|
||||
// }
|
||||
//
|
||||
// // Subscribe subscribes to the topic. The Receiver function is a callback
|
||||
// // function that receives published messages.
|
||||
// func Subscribe(c context.Context, topic string, receiver Receiver) error {
|
||||
// return global.Subscribe(c, topic, receiver)
|
||||
// }
|
||||
//
|
||||
// // Remove removes the topic.
|
||||
// func Remove(c context.Context, topic string) error {
|
||||
// return global.Remove(c, topic)
|
||||
// }
|
45
vendor/github.com/cncd/pubsub/topic.go
generated
vendored
Normal file
45
vendor/github.com/cncd/pubsub/topic.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
package pubsub
|
||||
|
||||
import "sync"
|
||||
|
||||
type topic struct {
|
||||
sync.Mutex
|
||||
|
||||
name string
|
||||
done chan bool
|
||||
subs map[*subscriber]struct{}
|
||||
}
|
||||
|
||||
func newTopic(dest string) *topic {
|
||||
return &topic{
|
||||
name: dest,
|
||||
done: make(chan bool),
|
||||
subs: make(map[*subscriber]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *topic) subscribe(s *subscriber) {
|
||||
t.Lock()
|
||||
t.subs[s] = struct{}{}
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *topic) unsubscribe(s *subscriber) {
|
||||
t.Lock()
|
||||
delete(t.subs, s)
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *topic) publish(m Message) {
|
||||
t.Lock()
|
||||
for s := range t.subs {
|
||||
go s.receiver(m)
|
||||
}
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *topic) close() {
|
||||
t.Lock()
|
||||
close(t.done)
|
||||
t.Unlock()
|
||||
}
|
29
vendor/github.com/cncd/queue/LICENSE
generated
vendored
Normal file
29
vendor/github.com/cncd/queue/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2017, Brad Rydzewski
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
6
vendor/github.com/cncd/queue/README
generated
vendored
Normal file
6
vendor/github.com/cncd/queue/README
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
Go package provides a common interface for working with task queues.
|
||||
|
||||
Documentation:
|
||||
|
||||
http://godoc.org/github.com/cncd/queue
|
||||
http://godoc.org/github.com/cncd/queue/gcp
|
190
vendor/github.com/cncd/queue/fifo.go
generated
vendored
Normal file
190
vendor/github.com/cncd/queue/fifo.go
generated
vendored
Normal file
|
@ -0,0 +1,190 @@
|
|||
package queue
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"context"
|
||||
"log"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type entry struct {
|
||||
item *Task
|
||||
done chan bool
|
||||
retry int
|
||||
error error
|
||||
deadline time.Time
|
||||
}
|
||||
|
||||
type worker struct {
|
||||
filter Filter
|
||||
channel chan *Task
|
||||
}
|
||||
|
||||
type fifo struct {
|
||||
sync.Mutex
|
||||
|
||||
workers map[*worker]struct{}
|
||||
running map[string]*entry
|
||||
pending *list.List
|
||||
extension time.Duration
|
||||
}
|
||||
|
||||
// New returns a new fifo queue.
|
||||
func New() Queue {
|
||||
return &fifo{
|
||||
workers: map[*worker]struct{}{},
|
||||
running: map[string]*entry{},
|
||||
pending: list.New(),
|
||||
extension: time.Minute * 10,
|
||||
}
|
||||
}
|
||||
|
||||
// Push pushes an item to the tail of this queue.
|
||||
func (q *fifo) Push(c context.Context, task *Task) error {
|
||||
q.Lock()
|
||||
q.pending.PushBack(task)
|
||||
q.Unlock()
|
||||
go q.process()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Poll retrieves and removes the head of this queue.
|
||||
func (q *fifo) Poll(c context.Context, f Filter) (*Task, error) {
|
||||
q.Lock()
|
||||
w := &worker{
|
||||
channel: make(chan *Task, 1),
|
||||
filter: f,
|
||||
}
|
||||
q.workers[w] = struct{}{}
|
||||
q.Unlock()
|
||||
go q.process()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.Done():
|
||||
q.Lock()
|
||||
delete(q.workers, w)
|
||||
q.Unlock()
|
||||
return nil, nil
|
||||
case t := <-w.channel:
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Done signals that the item is done executing.
|
||||
func (q *fifo) Done(c context.Context, id string) error {
|
||||
return q.Error(c, id, nil)
|
||||
}
|
||||
|
||||
// Error signals that the item is done executing with error.
|
||||
func (q *fifo) Error(c context.Context, id string, err error) error {
|
||||
q.Lock()
|
||||
state, ok := q.running[id]
|
||||
if ok {
|
||||
state.error = err
|
||||
close(state.done)
|
||||
delete(q.running, id)
|
||||
}
|
||||
q.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wait waits until the item is done executing.
|
||||
func (q *fifo) Wait(c context.Context, id string) error {
|
||||
q.Lock()
|
||||
state := q.running[id]
|
||||
q.Unlock()
|
||||
if state != nil {
|
||||
select {
|
||||
case <-c.Done():
|
||||
case <-state.done:
|
||||
return state.error
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Extend extends the task execution deadline.
|
||||
func (q *fifo) Extend(c context.Context, id string) error {
|
||||
q.Lock()
|
||||
defer q.Unlock()
|
||||
|
||||
state, ok := q.running[id]
|
||||
if ok {
|
||||
state.deadline = time.Now().Add(q.extension)
|
||||
return nil
|
||||
}
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
// Info returns internal queue information.
|
||||
func (q *fifo) Info(c context.Context) InfoT {
|
||||
q.Lock()
|
||||
stats := InfoT{}
|
||||
stats.Stats.Workers = len(q.workers)
|
||||
stats.Stats.Pending = q.pending.Len()
|
||||
stats.Stats.Running = len(q.running)
|
||||
|
||||
for e := q.pending.Front(); e != nil; e = e.Next() {
|
||||
stats.Pending = append(stats.Pending, e.Value.(*Task))
|
||||
}
|
||||
for _, entry := range q.running {
|
||||
stats.Running = append(stats.Running, entry.item)
|
||||
}
|
||||
|
||||
q.Unlock()
|
||||
return stats
|
||||
}
|
||||
|
||||
// helper function that loops through the queue and attempts to
|
||||
// match the item to a single subscriber.
|
||||
func (q *fifo) process() {
|
||||
defer func() {
|
||||
// the risk of panic is low. This code can probably be removed
|
||||
// once the code has been used in real world installs without issue.
|
||||
if err := recover(); err != nil {
|
||||
const size = 64 << 10
|
||||
buf := make([]byte, size)
|
||||
buf = buf[:runtime.Stack(buf, false)]
|
||||
log.Printf("queue: unexpected panic: %v\n%s", err, buf)
|
||||
}
|
||||
}()
|
||||
|
||||
q.Lock()
|
||||
defer q.Unlock()
|
||||
|
||||
// TODO(bradrydzewski) move this to a helper function
|
||||
// push items to the front of the queue if the item expires.
|
||||
for id, state := range q.running {
|
||||
if time.Now().After(state.deadline) {
|
||||
q.pending.PushFront(state.item)
|
||||
delete(q.running, id)
|
||||
close(state.done)
|
||||
}
|
||||
}
|
||||
|
||||
var next *list.Element
|
||||
loop:
|
||||
for e := q.pending.Front(); e != nil; e = next {
|
||||
next = e.Next()
|
||||
item := e.Value.(*Task)
|
||||
for w := range q.workers {
|
||||
if w.filter(item) {
|
||||
delete(q.workers, w)
|
||||
q.pending.Remove(e)
|
||||
|
||||
q.running[item.ID] = &entry{
|
||||
item: item,
|
||||
done: make(chan bool),
|
||||
deadline: time.Now().Add(q.extension),
|
||||
}
|
||||
|
||||
w.channel <- item
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
110
vendor/github.com/cncd/queue/queue.go
generated
vendored
Normal file
110
vendor/github.com/cncd/queue/queue.go
generated
vendored
Normal file
|
@ -0,0 +1,110 @@
|
|||
package queue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrCancel indicates the task was cancelled.
|
||||
ErrCancel = errors.New("queue: task cancelled")
|
||||
|
||||
// ErrNotFound indicates the task was not found in the queue.
|
||||
ErrNotFound = errors.New("queue: task not found")
|
||||
)
|
||||
|
||||
// Task defines a unit of work in the queue.
|
||||
type Task struct {
|
||||
// ID identifies this task.
|
||||
ID string `json:"id,omitempty"`
|
||||
|
||||
// Data is the actual data in the entry.
|
||||
Data []byte `json:"data"`
|
||||
|
||||
// Labels represents the key-value pairs the entry is lebeled with.
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
// InfoT provides runtime information.
|
||||
type InfoT struct {
|
||||
Pending []*Task `json:"pending"`
|
||||
Running []*Task `json:"running"`
|
||||
Stats struct {
|
||||
Workers int `json:"worker_count"`
|
||||
Pending int `json:"pending_count"`
|
||||
Running int `json:"running_count"`
|
||||
Complete int `json:"completed_count"`
|
||||
} `json:"stats"`
|
||||
}
|
||||
|
||||
// Filter filters tasks in the queue. If the Filter returns false,
|
||||
// the Task is skipped and not returned to the subscriber.
|
||||
type Filter func(*Task) bool
|
||||
|
||||
// Queue defines a task queue for scheduling tasks among
|
||||
// a pool of workers.
|
||||
type Queue interface {
|
||||
// Push pushes an task to the tail of this queue.
|
||||
Push(c context.Context, task *Task) error
|
||||
|
||||
// Poll retrieves and removes a task head of this queue.
|
||||
Poll(c context.Context, f Filter) (*Task, error)
|
||||
|
||||
// Extend extends the deadline for a task.
|
||||
Extend(c context.Context, id string) error
|
||||
|
||||
// Done signals the task is complete.
|
||||
Done(c context.Context, id string) error
|
||||
|
||||
// Error signals the task is complete with errors.
|
||||
Error(c context.Context, id string, err error) error
|
||||
|
||||
// Wait waits until the task is complete.
|
||||
Wait(c context.Context, id string) error
|
||||
|
||||
// Info returns internal queue information.
|
||||
Info(c context.Context) InfoT
|
||||
}
|
||||
|
||||
// // global instance of the queue.
|
||||
// var global = New()
|
||||
//
|
||||
// // Set sets the global queue.
|
||||
// func Set(queue Queue) {
|
||||
// global = queue
|
||||
// }
|
||||
//
|
||||
// // Push pushes an task to the tail of the global queue.
|
||||
// func Push(c context.Context, task *Task) error {
|
||||
// return global.Push(c, task)
|
||||
// }
|
||||
//
|
||||
// // Poll retrieves and removes a task head of the global queue.
|
||||
// func Poll(c context.Context, f Filter) (*Task, error) {
|
||||
// return global.Poll(c, f)
|
||||
// }
|
||||
//
|
||||
// // Extend extends the deadline for a task.
|
||||
// func Extend(c context.Context, id string) error {
|
||||
// return global.Extend(c, id)
|
||||
// }
|
||||
//
|
||||
// // Done signals the task is complete.
|
||||
// func Done(c context.Context, id string) error {
|
||||
// return global.Done(c, id)
|
||||
// }
|
||||
//
|
||||
// // Error signals the task is complete with errors.
|
||||
// func Error(c context.Context, id string, err error) {
|
||||
// global.Error(c, id, err)
|
||||
// }
|
||||
//
|
||||
// // Wait waits until the task is complete.
|
||||
// func Wait(c context.Context, id string) error {
|
||||
// return global.Wait(c, id)
|
||||
// }
|
||||
//
|
||||
// // Info returns internal queue information.
|
||||
// func Info(c context.Context) InfoT {
|
||||
// return global.Info(c)
|
||||
// }
|
1
vendor/github.com/cncd/queue/worker.go
generated
vendored
Normal file
1
vendor/github.com/cncd/queue/worker.go
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
package queue
|
86
vendor/vendor.json
vendored
86
vendor/vendor.json
vendored
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"comment": "",
|
||||
"ignore": "test",
|
||||
"ignore": "test github.com/drone/mq/ github.com/tidwall/redlog/ google.golang.org/appengine/ github.com/syndtr/goleveldb/ github.com/drone/drone-ui/",
|
||||
"package": [
|
||||
{
|
||||
"path": "code.google.com/p/go.crypto/ssh",
|
||||
|
@ -17,6 +17,90 @@
|
|||
"revision": "5cef21e2e4f0fd147973b558d4db7395176bcd95",
|
||||
"revisionTime": "2016-03-22T13:50:45-07:00"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "HhHyclewPAZ7sZQCsl2nMSe1T5s=",
|
||||
"path": "github.com/cncd/logging",
|
||||
"revision": "03b6463409fecbd23f04587adf9bc71da13796a2",
|
||||
"revisionTime": "2017-03-05T07:05:34Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "W3AuK8ocqHwlUajGmQLFvnRhTZE=",
|
||||
"path": "github.com/cncd/pipeline/pipeline",
|
||||
"revision": "b5457789534af0da2a05bd9fd0c0afe6fea391c6",
|
||||
"revisionTime": "2017-03-04T04:47:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "PSzh0ix/rlMrS/Cl3aH6GHGrJuo=",
|
||||
"path": "github.com/cncd/pipeline/pipeline/backend",
|
||||
"revision": "b5457789534af0da2a05bd9fd0c0afe6fea391c6",
|
||||
"revisionTime": "2017-03-04T04:47:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "0CGXRaYwZhJxGIrGhn8WGpkFqPo=",
|
||||
"path": "github.com/cncd/pipeline/pipeline/backend/docker",
|
||||
"revision": "b5457789534af0da2a05bd9fd0c0afe6fea391c6",
|
||||
"revisionTime": "2017-03-04T04:47:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "uUagpzha5ah/a3RO6IImvzHYFlY=",
|
||||
"path": "github.com/cncd/pipeline/pipeline/frontend",
|
||||
"revision": "b5457789534af0da2a05bd9fd0c0afe6fea391c6",
|
||||
"revisionTime": "2017-03-04T04:47:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "O0sulBQAHJeNLg3lO38Cq5uf/eg=",
|
||||
"path": "github.com/cncd/pipeline/pipeline/frontend/yaml",
|
||||
"revision": "b5457789534af0da2a05bd9fd0c0afe6fea391c6",
|
||||
"revisionTime": "2017-03-04T04:47:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "e1lZWQdObXCKWqZOGlOeaeERQMc=",
|
||||
"path": "github.com/cncd/pipeline/pipeline/frontend/yaml/compiler",
|
||||
"revision": "b5457789534af0da2a05bd9fd0c0afe6fea391c6",
|
||||
"revisionTime": "2017-03-04T04:47:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Q0GkNUFamVYIA1Fd8r0A5M6Gx54=",
|
||||
"path": "github.com/cncd/pipeline/pipeline/frontend/yaml/linter",
|
||||
"revision": "b5457789534af0da2a05bd9fd0c0afe6fea391c6",
|
||||
"revisionTime": "2017-03-04T04:47:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "kx2sPUIMozPC/g6E4w48h3FfH3k=",
|
||||
"path": "github.com/cncd/pipeline/pipeline/frontend/yaml/matrix",
|
||||
"revision": "b5457789534af0da2a05bd9fd0c0afe6fea391c6",
|
||||
"revisionTime": "2017-03-04T04:47:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "2/3f3oNmxXy5kcrRLCFa24Oc9O4=",
|
||||
"path": "github.com/cncd/pipeline/pipeline/interrupt",
|
||||
"revision": "b5457789534af0da2a05bd9fd0c0afe6fea391c6",
|
||||
"revisionTime": "2017-03-04T04:47:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "8eTwXZPM/Kp9uE/mnhpWDTiX7nY=",
|
||||
"path": "github.com/cncd/pipeline/pipeline/multipart",
|
||||
"revision": "b5457789534af0da2a05bd9fd0c0afe6fea391c6",
|
||||
"revisionTime": "2017-03-04T04:47:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "5axmtZsHaQ5uE/tuNQZygquNx8U=",
|
||||
"path": "github.com/cncd/pipeline/pipeline/rpc",
|
||||
"revision": "b5457789534af0da2a05bd9fd0c0afe6fea391c6",
|
||||
"revisionTime": "2017-03-04T04:47:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "7Qj1DK0ceAXkYztW0l3+L6sn+V8=",
|
||||
"path": "github.com/cncd/pubsub",
|
||||
"revision": "0691529ab100a0f3c4e2087407d2788a0787ad9c",
|
||||
"revisionTime": "2017-03-03T07:06:35Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "AG4M07wOZNTnSFHJIfdXT2ymnts=",
|
||||
"path": "github.com/cncd/queue",
|
||||
"revision": "1ce1ada7160f1eda015a16c1b7f9ea497fa36873",
|
||||
"revisionTime": "2017-03-03T07:04:55Z"
|
||||
},
|
||||
{
|
||||
"path": "github.com/codegangsta/cli",
|
||||
"revision": "70e3fa51ebed95df8c0fbe1519c1c1f9bc16bb13",
|
||||
|
|
Loading…
Reference in a new issue