Merge pull request #1954 from bradrydzewski/master

implement jsonrpc2 agent behind feature flag
This commit is contained in:
Brad Rydzewski 2017-03-15 10:10:09 +08:00 committed by GitHub
commit fe669b744c
329 changed files with 30281 additions and 190 deletions

View file

@ -4,7 +4,7 @@ workspace:
pipeline:
test:
image: golang:1.6
image: golang:1.8
environment:
- GO15VENDOREXPERIMENT=1
commands:
@ -12,7 +12,7 @@ pipeline:
- make test test_postgres test_mysql
compile:
image: golang:1.6
image: golang:1.8
environment:
- GO15VENDOREXPERIMENT=1
- GOPATH=/go

View file

@ -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{

View file

@ -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)

206
drone/agent/exp.go Normal file
View file

@ -0,0 +1,206 @@
package agent
import (
"context"
"io"
"log"
"net/url"
"sync"
"time"
"github.com/cncd/pipeline/pipeline"
"github.com/cncd/pipeline/pipeline/backend"
"github.com/cncd/pipeline/pipeline/backend/docker"
"github.com/cncd/pipeline/pipeline/interrupt"
"github.com/cncd/pipeline/pipeline/multipart"
"github.com/cncd/pipeline/pipeline/rpc"
"github.com/codegangsta/cli"
"github.com/tevino/abool"
)
func loop(c *cli.Context) error {
endpoint, err := url.Parse(
c.String("drone-server"),
)
if err != nil {
return err
}
filter := rpc.Filter{
Labels: map[string]string{
"platform": c.String("platform"),
},
}
client, err := rpc.NewClient(
endpoint.String(),
rpc.WithRetryLimit(
c.Int("retry-limit"),
),
rpc.WithBackoff(
c.Duration("backoff"),
),
rpc.WithToken(
c.String("drone-secret"),
),
)
if err != nil {
return err
}
defer client.Close()
sigterm := abool.New()
ctx := context.Background()
ctx = interrupt.WithContextFunc(ctx, func() {
println("ctrl+c received, terminating process")
sigterm.Set()
})
var wg sync.WaitGroup
parallel := c.Int("max-procs")
wg.Add(parallel)
for i := 0; i < parallel; i++ {
go func() {
defer wg.Done()
for {
if sigterm.IsSet() {
return
}
if err := run(ctx, client, filter); err != nil {
log.Printf("build runner encountered error: exiting: %s", err)
return
}
}
}()
}
wg.Wait()
return nil
}
const (
maxFileUpload = 5000000
maxLogsUpload = 5000000
)
func run(ctx context.Context, client rpc.Peer, filter rpc.Filter) error {
log.Println("pipeline: request next execution")
// get the next job from the queue
work, err := client.Next(ctx, filter)
if err != nil {
return err
}
if work == nil {
return nil
}
log.Printf("pipeline: received next execution: %s", work.ID)
// new docker engine
engine, err := docker.NewEnv()
if err != nil {
return err
}
timeout := time.Hour
if minutes := work.Timeout; minutes != 0 {
timeout = time.Duration(minutes) * time.Minute
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cancelled := abool.New()
go func() {
if werr := client.Wait(ctx, work.ID); werr != nil {
cancelled.SetTo(true)
log.Printf("pipeline: cancel signal received: %s: %s", work.ID, werr)
cancel()
} else {
log.Printf("pipeline: cancel channel closed: %s", work.ID)
}
}()
go func() {
for {
select {
case <-ctx.Done():
log.Printf("pipeline: cancel ping loop: %s", work.ID)
return
case <-time.After(time.Minute):
log.Printf("pipeline: ping queue: %s", work.ID)
client.Extend(ctx, work.ID)
}
}
}()
state := rpc.State{}
state.Started = time.Now().Unix()
err = client.Update(context.Background(), work.ID, state)
if err != nil {
log.Printf("pipeline: error updating pipeline status: %s: %s", work.ID, err)
}
var uploads sync.WaitGroup
defaultLogger := pipeline.LogFunc(func(proc *backend.Step, rc multipart.Reader) error {
part, rerr := rc.NextPart()
if rerr != nil {
return rerr
}
uploads.Add(1)
writer := rpc.NewLineWriter(client, work.ID, proc.Alias)
rlimit := io.LimitReader(part, maxLogsUpload)
io.Copy(writer, rlimit)
defer func() {
log.Printf("pipeline: finish uploading logs: %s: step %s", work.ID, proc.Alias)
uploads.Done()
}()
part, rerr = rc.NextPart()
if rerr != nil {
return nil
}
rlimit = io.LimitReader(part, maxFileUpload)
mime := part.Header().Get("Content-Type")
if serr := client.Upload(context.Background(), work.ID, mime, rlimit); serr != nil {
log.Printf("pipeline: cannot upload artifact: %s: %s: %s", work.ID, mime, serr)
}
return nil
})
err = pipeline.New(work.Config,
pipeline.WithContext(ctx),
pipeline.WithLogger(defaultLogger),
pipeline.WithTracer(pipeline.DefaultTracer),
pipeline.WithEngine(engine),
).Run()
state.Finished = time.Now().Unix()
state.Exited = true
if err != nil {
state.Error = err.Error()
if xerr, ok := err.(*pipeline.ExitError); ok {
state.ExitCode = xerr.Code
}
if xerr, ok := err.(*pipeline.OomError); ok {
state.ExitCode = xerr.Code
}
if cancelled.IsSet() {
state.ExitCode = 137
} else if state.ExitCode == 0 {
state.ExitCode = 1
}
}
log.Printf("pipeline: execution complete: %s", work.ID)
uploads.Wait()
err = client.Update(context.Background(), work.ID, state)
if err != nil {
log.Printf("Pipeine: error updating pipeline status: %s: %s", work.ID, err)
}
return nil
}

View file

@ -8,6 +8,7 @@ type Build struct {
Parent int `json:"parent" meddler:"build_parent"`
Event string `json:"event" meddler:"build_event"`
Status string `json:"status" meddler:"build_status"`
Error string `json:"error" meddler:"build_error"`
Enqueued int64 `json:"enqueued_at" meddler:"build_enqueued"`
Created int64 `json:"created_at" meddler:"build_created"`
Started int64 `json:"started_at" meddler:"build_started"`

View file

@ -2,6 +2,7 @@ package router
import (
"net/http"
"os"
"github.com/gin-gonic/gin"
@ -9,6 +10,7 @@ import (
"github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/router/middleware/token"
"github.com/drone/drone/server"
"github.com/drone/drone/server/debug"
"github.com/drone/drone/server/template"
"github.com/drone/drone-ui/dist"
@ -119,19 +121,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")
@ -147,41 +176,21 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
builds.GET("", server.GetBuildQueue)
}
agents := e.Group("/api/agents")
debugger := e.Group("/api/debug")
{
agents.Use(session.MustAdmin())
agents.GET("", server.GetAgents)
debugger.Use(session.MustAdmin())
debugger.GET("/pprof/", debug.IndexHandler())
debugger.GET("/pprof/heap", debug.HeapHandler())
debugger.GET("/pprof/goroutine", debug.GoroutineHandler())
debugger.GET("/pprof/block", debug.BlockHandler())
debugger.GET("/pprof/threadcreate", debug.ThreadCreateHandler())
debugger.GET("/pprof/cmdline", debug.CmdlineHandler())
debugger.GET("/pprof/profile", debug.ProfileHandler())
debugger.GET("/pprof/symbol", debug.SymbolHandler())
debugger.POST("/pprof/symbol", debug.SymbolHandler())
debugger.GET("/pprof/trace", debug.TraceHandler())
}
debug := e.Group("/api/debug")
{
debug.Use(session.MustAdmin())
debug.GET("/pprof/", server.IndexHandler())
debug.GET("/pprof/heap", server.HeapHandler())
debug.GET("/pprof/goroutine", server.GoroutineHandler())
debug.GET("/pprof/block", server.BlockHandler())
debug.GET("/pprof/threadcreate", server.ThreadCreateHandler())
debug.GET("/pprof/cmdline", server.CmdlineHandler())
debug.GET("/pprof/profile", server.ProfileHandler())
debug.GET("/pprof/symbol", server.SymbolHandler())
debug.POST("/pprof/symbol", server.SymbolHandler())
debug.GET("/pprof/trace", server.TraceHandler())
}
// DELETE THESE
// gitlab := e.Group("/gitlab/:owner/:name")
// {
// gitlab.Use(session.SetRepo())
// gitlab.GET("/commits/:sha", GetCommit)
// gitlab.GET("/pulls/:number", GetPullRequest)
//
// redirects := gitlab.Group("/redirect")
// {
// redirects.GET("/commits/:sha", RedirectSha)
// redirects.GET("/pulls/:number", RedirectPullRequest)
// }
// }
// bots := e.Group("/bots")
// {
// bots.Use(session.MustUser())

View file

@ -1,15 +0,0 @@
package server
import (
"github.com/drone/drone/store"
"github.com/gin-gonic/gin"
)
func GetAgents(c *gin.Context) {
agents, err := store.GetAgentList(c)
if err != nil {
c.String(500, "Error getting agent list. %s", err)
return
}
c.JSON(200, agents)
}

View file

@ -2,12 +2,19 @@ package server
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
log "github.com/Sirupsen/logrus"
"github.com/cncd/pipeline/pipeline/rpc"
"github.com/cncd/pubsub"
"github.com/cncd/queue"
"github.com/drone/drone/remote"
"github.com/drone/drone/shared/httputil"
"github.com/drone/drone/store"
@ -149,19 +156,27 @@ func DeleteBuild(c *gin.Context) {
job.ExitCode = 137
store.UpdateBuildJob(c, build, job)
client := stomp.MustFromContext(c)
client.SendJSON("/topic/cancel", model.Event{
Type: model.Cancelled,
Repo: *repo,
Build: *build,
Job: *job,
}, stomp.WithHeader("job-id", strconv.FormatInt(job.ID, 10)))
if os.Getenv("DRONE_CANARY") == "" {
client := stomp.MustFromContext(c)
client.SendJSON("/topic/cancel", model.Event{
Type: model.Cancelled,
Repo: *repo,
Build: *build,
Job: *job,
}, stomp.WithHeader("job-id", strconv.FormatInt(job.ID, 10)))
} else {
config.queue.Error(context.Background(), fmt.Sprint(job.ID), queue.ErrCancel)
}
c.String(204, "")
}
func PostBuild(c *gin.Context) {
if os.Getenv("DRONE_CANARY") == "true" {
PostBuild2(c)
return
}
remote_ := remote.FromContext(c)
repo := session.Repo(c)
fork := c.DefaultQuery("fork", "false")
@ -197,8 +212,8 @@ func PostBuild(c *gin.Context) {
}
// fetch the .drone.yml file from the database
config := ToConfig(c)
raw, err := remote_.File(user, repo, build, config.Yaml)
cfg := ToConfig(c)
raw, err := remote_.File(user, repo, build, cfg.Yaml)
if err != nil {
log.Errorf("failure to get build config for %s. %s", repo.FullName, err)
c.AbortWithError(404, err)
@ -206,7 +221,7 @@ func PostBuild(c *gin.Context) {
}
// Fetch secrets file but don't exit on error as it's optional
sec, err := remote_.File(user, repo, build, config.Shasum)
sec, err := remote_.File(user, repo, build, cfg.Shasum)
if err != nil {
log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err)
}
@ -275,6 +290,7 @@ func PostBuild(c *gin.Context) {
build.Started = 0
build.Finished = 0
build.Enqueued = time.Now().UTC().Unix()
build.Error = ""
for _, job := range jobs {
for k, v := range buildParams {
job.Environment[k] = v
@ -388,3 +404,215 @@ func copyLogs(dest io.Writer, src io.Reader) error {
return nil
}
//
//
//
//
//
//
func PostBuild2(c *gin.Context) {
remote_ := remote.FromContext(c)
repo := session.Repo(c)
fork := c.DefaultQuery("fork", "false")
num, err := strconv.Atoi(c.Param("number"))
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
user, err := store.GetUser(c, repo.UserID)
if err != nil {
log.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
c.AbortWithError(500, err)
return
}
build, err := store.GetBuildNumber(c, repo, num)
if err != nil {
log.Errorf("failure to get build %d. %s", num, err)
c.AbortWithError(404, err)
return
}
// if the remote has a refresh token, the current access token
// may be stale. Therefore, we should refresh prior to dispatching
// the job.
if refresher, ok := remote_.(remote.Refresher); ok {
ok, _ := refresher.Refresh(user)
if ok {
store.UpdateUser(c, user)
}
}
// fetch the .drone.yml file from the database
cfg := ToConfig(c)
raw, err := remote_.File(user, repo, build, cfg.Yaml)
if err != nil {
log.Errorf("failure to get build config for %s. %s", repo.FullName, err)
c.AbortWithError(404, err)
return
}
netrc, err := remote_.Netrc(user, repo)
if err != nil {
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
c.AbortWithError(500, err)
return
}
jobs, err := store.GetJobList(c, build)
if err != nil {
log.Errorf("failure to get build %d jobs. %s", build.Number, err)
c.AbortWithError(404, err)
return
}
// must not restart a running build
if build.Status == model.StatusPending || build.Status == model.StatusRunning {
c.String(409, "Cannot re-start a started build")
return
}
// forking the build creates a duplicate of the build
// and then executes. This retains prior build history.
if forkit, _ := strconv.ParseBool(fork); forkit {
build.ID = 0
build.Number = 0
build.Parent = num
for _, job := range jobs {
job.ID = 0
job.NodeID = 0
}
err := store.CreateBuild(c, build, jobs...)
if err != nil {
c.String(500, err.Error())
return
}
event := c.DefaultQuery("event", build.Event)
if event == model.EventPush ||
event == model.EventPull ||
event == model.EventTag ||
event == model.EventDeploy {
build.Event = event
}
build.Deploy = c.DefaultQuery("deploy_to", build.Deploy)
}
// Read query string parameters into buildParams, exclude reserved params
var buildParams = map[string]string{}
for key, val := range c.Request.URL.Query() {
switch key {
case "fork", "event", "deploy_to":
default:
// We only accept string literals, because build parameters will be
// injected as environment variables
buildParams[key] = val[0]
}
}
// todo move this to database tier
// and wrap inside a transaction
build.Status = model.StatusPending
build.Started = 0
build.Finished = 0
build.Enqueued = time.Now().UTC().Unix()
build.Error = ""
for _, job := range jobs {
for k, v := range buildParams {
job.Environment[k] = v
}
job.Error = ""
job.Status = model.StatusPending
job.Started = 0
job.Finished = 0
job.ExitCode = 0
job.NodeID = 0
job.Enqueued = build.Enqueued
store.UpdateJob(c, job)
}
err = store.UpdateBuild(c, build)
if err != nil {
c.AbortWithStatus(500)
return
}
c.JSON(202, build)
// get the previous build so that we can send
// on status change notifications
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
secs, err := store.GetMergedSecretList(c, repo)
if err != nil {
log.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
}
b := builder{
Repo: repo,
Curr: build,
Last: last,
Netrc: netrc,
Secs: secs,
Link: httputil.GetURL(c.Request),
Yaml: string(raw),
}
items, err := b.Build()
if err != nil {
build.Status = model.StatusError
build.Started = time.Now().Unix()
build.Finished = build.Started
build.Error = err.Error()
return
}
for i, item := range items {
// TODO prevent possible index out of bounds
item.Job.ID = jobs[i].ID
build.Jobs = append(build.Jobs, item.Job)
store.UpdateJob(c, item.Job)
}
//
// publish topic
//
message := pubsub.Message{
Labels: map[string]string{
"repo": repo.FullName,
"private": strconv.FormatBool(repo.IsPrivate),
},
}
message.Data, _ = json.Marshal(model.Event{
Type: model.Enqueued,
Repo: *repo,
Build: *build,
})
// TODO remove global reference
config.pubsub.Publish(c, "topic/events", message)
//
// end publish topic
//
for _, item := range items {
task := new(queue.Task)
task.ID = fmt.Sprint(item.Job.ID)
task.Labels = map[string]string{}
task.Labels["platform"] = item.Platform
for k, v := range item.Labels {
task.Labels[k] = v
}
task.Data, _ = json.Marshal(rpc.Pipeline{
ID: fmt.Sprint(item.Job.ID),
Config: item.Config,
Timeout: b.Repo.Timeout,
})
config.logger.Open(context.Background(), task.ID)
config.queue.Push(context.Background(), task)
}
}

View file

@ -1,4 +1,4 @@
package server
package debug
import (
"net/http/pprof"

View file

@ -1,100 +0,0 @@
package server
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/shared/token"
"github.com/drone/drone/store"
)
func GetCommit(c *gin.Context) {
repo := session.Repo(c)
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
return repo.Hash, nil
})
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
if parsed.Text != repo.FullName {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
commit := c.Param("sha")
branch := c.Query("branch")
if len(branch) == 0 {
branch = repo.Branch
}
build, err := store.GetBuildCommit(c, repo, commit, branch)
if err != nil {
c.AbortWithError(http.StatusNotFound, err)
return
}
c.JSON(http.StatusOK, build)
}
func GetPullRequest(c *gin.Context) {
repo := session.Repo(c)
refs := fmt.Sprintf("refs/pull/%s/head", c.Param("number"))
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
return repo.Hash, nil
})
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
if parsed.Text != repo.FullName {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
build, err := store.GetBuildRef(c, repo, refs)
if err != nil {
c.AbortWithError(http.StatusNotFound, err)
return
}
c.JSON(http.StatusOK, build)
}
func RedirectSha(c *gin.Context) {
repo := session.Repo(c)
commit := c.Param("sha")
branch := c.Query("branch")
if len(branch) == 0 {
branch = repo.Branch
}
build, err := store.GetBuildCommit(c, repo, commit, branch)
if err != nil {
c.AbortWithError(http.StatusNotFound, err)
return
}
path := fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.Number)
c.Redirect(http.StatusSeeOther, path)
}
func RedirectPullRequest(c *gin.Context) {
repo := session.Repo(c)
refs := fmt.Sprintf("refs/pull/%s/head", c.Param("number"))
build, err := store.GetBuildRef(c, repo, refs)
if err != nil {
c.AbortWithError(http.StatusNotFound, err)
return
}
path := fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.Number)
c.Redirect(http.StatusSeeOther, path)
}

View file

@ -2,7 +2,6 @@ package server
import (
"fmt"
"regexp"
"strconv"
"github.com/gin-gonic/gin"
@ -18,8 +17,6 @@ import (
"github.com/drone/mq/stomp"
)
var skipRe = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`)
func PostHook(c *gin.Context) {
remote_ := remote.FromContext(c)
@ -105,8 +102,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 +161,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 +209,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,
@ -245,5 +242,4 @@ func PostHook(c *gin.Context) {
),
)
}
}

869
server/hook2.go Normal file
View file

@ -0,0 +1,869 @@
package server
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/square/go-jose"
"github.com/Sirupsen/logrus"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/shared/httputil"
"github.com/drone/drone/shared/token"
"github.com/drone/drone/store"
"github.com/drone/envsubst"
"github.com/cncd/pipeline/pipeline/backend"
"github.com/cncd/pipeline/pipeline/frontend"
"github.com/cncd/pipeline/pipeline/frontend/yaml"
"github.com/cncd/pipeline/pipeline/frontend/yaml/compiler"
"github.com/cncd/pipeline/pipeline/frontend/yaml/linter"
"github.com/cncd/pipeline/pipeline/frontend/yaml/matrix"
"github.com/cncd/pipeline/pipeline/rpc"
"github.com/cncd/pubsub"
"github.com/cncd/queue"
)
//
// CANARY IMPLEMENTATION
//
// This file is a complete disaster because I'm trying to wedge in some
// experimental code. Please pardon our appearance during renovations.
//
var skipRe = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`)
func GetQueueInfo(c *gin.Context) {
c.IndentedJSON(200,
config.queue.Info(c),
)
}
// func PostHookOld(c *gin.Context) {
// remote_ := remote.FromContext(c)
//
// tmprepo, build, err := remote_.Hook(c.Request)
// if err != nil {
// logrus.Errorf("failure to parse hook. %s", err)
// c.AbortWithError(400, err)
// return
// }
// if build == nil {
// c.Writer.WriteHeader(200)
// return
// }
// if tmprepo == nil {
// logrus.Errorf("failure to ascertain repo from hook.")
// c.Writer.WriteHeader(400)
// return
// }
//
// // skip the build if any case-insensitive combination of the words "skip" and "ci"
// // wrapped in square brackets appear in the commit message
// skipMatch := skipRe.FindString(build.Message)
// if len(skipMatch) > 0 {
// logrus.Infof("ignoring hook. %s found in %s", skipMatch, build.Commit)
// c.Writer.WriteHeader(204)
// return
// }
//
// repo, err := store.GetRepoOwnerName(c, tmprepo.Owner, tmprepo.Name)
// if err != nil {
// logrus.Errorf("failure to find repo %s/%s from hook. %s", tmprepo.Owner, tmprepo.Name, err)
// c.AbortWithError(404, err)
// return
// }
//
// // get the token and verify the hook is authorized
// parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
// return repo.Hash, nil
// })
// if err != nil {
// logrus.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
// c.AbortWithError(400, err)
// return
// }
// if parsed.Text != repo.FullName {
// logrus.Errorf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text)
// c.AbortWithStatus(403)
// return
// }
//
// if repo.UserID == 0 {
// logrus.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
// c.Writer.WriteHeader(204)
// return
// }
// var skipped = true
// if (build.Event == model.EventPush && repo.AllowPush) ||
// (build.Event == model.EventPull && repo.AllowPull) ||
// (build.Event == model.EventDeploy && repo.AllowDeploy) ||
// (build.Event == model.EventTag && repo.AllowTag) {
// skipped = false
// }
//
// if skipped {
// logrus.Infof("ignoring hook. repo %s is disabled for %s events.", repo.FullName, build.Event)
// c.Writer.WriteHeader(204)
// return
// }
//
// user, err := store.GetUser(c, repo.UserID)
// if err != nil {
// logrus.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
// c.AbortWithError(500, err)
// return
// }
//
// // if the remote has a refresh token, the current access token
// // may be stale. Therefore, we should refresh prior to dispatching
// // the job.
// if refresher, ok := remote_.(remote.Refresher); ok {
// ok, _ := refresher.Refresh(user)
// if ok {
// store.UpdateUser(c, user)
// }
// }
//
// // fetch the build file from the database
// cfg := ToConfig(c)
// raw, err := remote_.File(user, repo, build, cfg.Yaml)
// if err != nil {
// logrus.Errorf("failure to get build config for %s. %s", repo.FullName, err)
// c.AbortWithError(404, err)
// return
// }
// sec, err := remote_.File(user, repo, build, cfg.Shasum)
// if err != nil {
// logrus.Debugf("cannot find yaml signature for %s. %s", repo.FullName, err)
// // NOTE we don't exit on failure. The sec file is optional
// }
//
// axes, err := matrix.Parse(raw)
// if err != nil {
// c.String(500, "Failed to parse yaml file or calculate matrix. %s", err)
// return
// }
// if len(axes) == 0 {
// axes = append(axes, matrix.Axis{})
// }
//
// netrc, err := remote_.Netrc(user, repo)
// if err != nil {
// c.String(500, "Failed to generate netrc file. %s", err)
// return
// }
//
// // verify the branches can be built vs skipped
// branches, err := yaml.ParseBytes(raw)
// if err != nil {
// c.String(500, "Failed to parse yaml file. %s", err)
// return
// }
// if !branches.Branches.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
// c.String(200, "Branch does not match restrictions defined in yaml")
// return
// }
//
// signature, err := jose.ParseSigned(string(sec))
// if err != nil {
// logrus.Debugf("cannot parse .drone.yml.sig file. %s", err)
// } else if len(sec) == 0 {
// logrus.Debugf("cannot parse .drone.yml.sig file. empty file")
// } else {
// build.Signed = true
// output, verr := signature.Verify([]byte(repo.Hash))
// if verr != nil {
// logrus.Debugf("cannot verify .drone.yml.sig file. %s", verr)
// } else if string(output) != string(raw) {
// logrus.Debugf("cannot verify .drone.yml.sig file. no match")
// } else {
// build.Verified = true
// }
// }
//
// // update some build fields
// build.Status = model.StatusPending
// build.RepoID = repo.ID
//
// // and use a transaction
// var jobs []*model.Job
// for num, axis := range axes {
// jobs = append(jobs, &model.Job{
// BuildID: build.ID,
// Number: num + 1,
// Status: model.StatusPending,
// Environment: axis,
// })
// }
// err = store.CreateBuild(c, build, jobs...)
// if err != nil {
// logrus.Errorf("failure to save commit for %s. %s", repo.FullName, err)
// c.AbortWithError(500, err)
// return
// }
//
// c.JSON(200, build)
//
// uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
// err = remote_.Status(user, repo, build, uri)
// if err != nil {
// logrus.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
// }
//
// // get the previous build so that we can send
// // on status change notifications
// last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
// secs, err := store.GetMergedSecretList(c, repo)
// if err != nil {
// logrus.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
// }
//
// //
// // BELOW: NEW
// //
//
// b := builder{
// Repo: repo,
// Curr: build,
// Last: last,
// Netrc: netrc,
// Secs: secs,
// Link: httputil.GetURL(c.Request),
// Yaml: string(raw),
// }
// items, err := b.Build()
// if err != nil {
// build.Status = model.StatusError
// build.Started = time.Now().Unix()
// build.Finished = build.Started
// build.Error = err.Error()
// store.CreateBuild(c, build, build.Jobs...)
// return
// }
//
// for _, item := range items {
// build.Jobs = append(build.Jobs, item.Job)
// }
//
// if err := store.CreateBuild(c, build, build.Jobs...); err != nil {
// logrus.Errorf("failure to save commit for %s. %s", repo.FullName, err)
// c.AbortWithError(500, err)
// return
// }
//
// for _, item := range items {
//
// task := new(queue.Task)
// task.ID = fmt.Sprint(item.Job.ID)
// task.Labels = map[string]string{}
// task.Labels["platform"] = item.Platform
// for k, v := range item.Labels {
// task.Labels[k] = v
// }
//
// task.Data, _ = json.Marshal(rpc.Pipeline{
// ID: fmt.Sprint(item.Job.ID),
// Config: item.Config,
// Timeout: b.Repo.Timeout,
// })
//
// config.logger.Open(context.Background(), task.ID)
// config.queue.Push(context.Background(), task)
// }
//
// //
// // new code here
// //
//
// message := pubsub.Message{
// Labels: map[string]string{
// "repo": repo.FullName,
// "private": strconv.FormatBool(repo.IsPrivate),
// },
// }
// message.Data, _ = json.Marshal(model.Event{
// Type: model.Enqueued,
// Repo: *repo,
// Build: *build,
// })
// // TODO remove global reference
// config.pubsub.Publish(c, "topic/events", message)
//
// //
// // workspace
// //
//
// for _, job := range jobs {
//
// metadata := metadataFromStruct(repo, build, last, job, httputil.GetURL(c.Request))
// environ := metadata.Environ()
//
// secrets := map[string]string{}
// for _, sec := range secs {
// if !sec.MatchEvent(build.Event) {
// continue
// }
// if build.Verified || sec.SkipVerify {
// secrets[sec.Name] = sec.Value
// }
// }
// sub := func(name string) string {
// if v, ok := environ[name]; ok {
// return v
// }
// return secrets[name]
// }
// if s, err := envsubst.Eval(string(raw), sub); err != nil {
// raw = []byte(s)
// }
// parsed, err := yaml.ParseBytes(raw)
// if err != nil {
// job.ExitCode = 255
// job.Enqueued = time.Now().Unix()
// job.Started = time.Now().Unix()
// job.Finished = time.Now().Unix()
// job.Error = err.Error()
// store.UpdateBuildJob(c, build, job)
// continue
// }
//
// lerr := linter.New(
// linter.WithTrusted(repo.IsTrusted),
// ).Lint(parsed)
// if lerr != nil {
// job.ExitCode = 255
// job.Enqueued = time.Now().Unix()
// job.Started = time.Now().Unix()
// job.Finished = time.Now().Unix()
// job.Error = lerr.Error()
// store.UpdateBuildJob(c, build, job)
// continue
// }
//
// ir := compiler.New(
// compiler.WithEnviron(environ),
// // TODO ability to customize the escalated plugins
// compiler.WithEscalated("plugins/docker", "plugins/gcr", "plugins/ecr"),
// compiler.WithLocal(false),
// compiler.WithNetrc(netrc.Login, netrc.Password, netrc.Machine),
// compiler.WithPrefix(
// fmt.Sprintf(
// "%d_%d",
// job.ID,
// time.Now().Unix(),
// ),
// ),
// compiler.WithEnviron(job.Environment),
// compiler.WithProxy(),
// // TODO ability to set global volumes for things like certs
// compiler.WithVolumes(),
// compiler.WithWorkspaceFromURL("/drone", repo.Link),
// ).Compile(parsed)
//
// // TODO there is a chicken and egg problem here because
// // the compiled yaml has a platform environment variable
// // that is not correctly set, because we are just about
// // to set it ....
// // TODO maybe we remove platform from metadata and let
// // the compiler set the value from the yaml itself.
// if parsed.Platform == "" {
// parsed.Platform = "linux/amd64"
// }
//
// for _, sec := range secs {
// if !sec.MatchEvent(build.Event) {
// continue
// }
// if build.Verified || sec.SkipVerify {
// ir.Secrets = append(ir.Secrets, &backend.Secret{
// Mask: sec.Conceal,
// Name: sec.Name,
// Value: sec.Value,
// })
// }
// }
//
// task := new(queue.Task)
// task.ID = fmt.Sprint(job.ID)
// task.Labels = map[string]string{}
// task.Labels["platform"] = parsed.Platform
// if parsed.Labels != nil {
// for k, v := range parsed.Labels {
// task.Labels[k] = v
// }
// }
//
// task.Data, _ = json.Marshal(rpc.Pipeline{
// ID: fmt.Sprint(job.ID),
// Config: ir,
// Timeout: repo.Timeout,
// })
//
// config.logger.Open(context.Background(), task.ID)
// config.queue.Push(context.Background(), task)
// }
//
// }
// return the metadata from the cli context.
func metadataFromStruct(repo *model.Repo, build, last *model.Build, job *model.Job, link string) frontend.Metadata {
return frontend.Metadata{
Repo: frontend.Repo{
Name: repo.Name,
Link: repo.Link,
Remote: repo.Clone,
Private: repo.IsPrivate,
},
Curr: frontend.Build{
Number: build.Number,
Created: build.Created,
Started: build.Started,
Finished: build.Finished,
Status: build.Status,
Event: build.Event,
Link: build.Link,
Target: build.Deploy,
Commit: frontend.Commit{
Sha: build.Commit,
Ref: build.Ref,
Refspec: build.Refspec,
Branch: build.Branch,
Message: build.Message,
Author: frontend.Author{
Name: build.Author,
Email: build.Email,
Avatar: build.Avatar,
},
},
},
Prev: frontend.Build{
Number: last.Number,
Created: last.Created,
Started: last.Started,
Finished: last.Finished,
Status: last.Status,
Event: last.Event,
Link: last.Link,
Target: last.Deploy,
Commit: frontend.Commit{
Sha: last.Commit,
Ref: last.Ref,
Refspec: last.Refspec,
Branch: last.Branch,
Message: last.Message,
Author: frontend.Author{
Name: last.Author,
Email: last.Email,
Avatar: last.Avatar,
},
},
},
Job: frontend.Job{
Number: job.Number,
Matrix: job.Environment,
},
Sys: frontend.System{
Name: "drone",
Link: link,
Arch: "linux/amd64",
},
}
}
// use helper funciton to return ([]backend.Config, error)
// 1. fetch everything from github
// 2. create and persist the build object
//
// 3. generate the build jobs [Launcher?]
// a. parse yaml
// b. lint yaml
// c. compile yaml
//
// 4. persist the build jobs (... what if I already have jobs, via re-start)
// 5. update github status
// 6. send to queue
// 7. trigger pubsub
type builder struct {
Repo *model.Repo
Curr *model.Build
Last *model.Build
Netrc *model.Netrc
Secs []*model.Secret
Link string
Yaml string
}
type buildItem struct {
Job *model.Job
Platform string
Labels map[string]string
Config *backend.Config
}
func (b *builder) Build() ([]*buildItem, error) {
axes, err := matrix.ParseString(b.Yaml)
if err != nil {
return nil, err
}
if len(axes) == 0 {
axes = append(axes, matrix.Axis{})
}
var items []*buildItem
for i, axis := range axes {
job := &model.Job{
BuildID: b.Curr.ID,
Number: i + 1,
Status: model.StatusPending,
Environment: axis,
Enqueued: b.Curr.Created,
}
metadata := metadataFromStruct(b.Repo, b.Curr, b.Last, job, b.Link)
environ := metadata.Environ()
for k, v := range metadata.EnvironDrone() {
environ[k] = v
}
secrets := map[string]string{}
for _, sec := range b.Secs {
if !sec.MatchEvent(b.Curr.Event) {
continue
}
if b.Curr.Verified || sec.SkipVerify {
secrets[sec.Name] = sec.Value
}
}
sub := func(name string) string {
if v, ok := environ[name]; ok {
return v
}
return secrets[name]
}
y := b.Yaml
if s, err := envsubst.Eval(y, sub); err != nil {
y = s
}
parsed, err := yaml.ParseString(y)
if err != nil {
return nil, err
}
metadata.Sys.Arch = parsed.Platform
if metadata.Sys.Arch == "" {
metadata.Sys.Arch = "linux/amd64"
}
lerr := linter.New(
linter.WithTrusted(b.Repo.IsTrusted),
).Lint(parsed)
if lerr != nil {
return nil, err
}
ir := compiler.New(
compiler.WithEnviron(environ),
// TODO ability to customize the escalated plugins
compiler.WithEscalated("plugins/docker", "plugins/gcr", "plugins/ecr"),
compiler.WithLocal(false),
compiler.WithNetrc(b.Netrc.Login, b.Netrc.Password, b.Netrc.Machine),
compiler.WithPrefix(
fmt.Sprintf(
"%d_%d",
job.ID,
time.Now().Unix(),
),
),
compiler.WithEnviron(job.Environment),
compiler.WithProxy(),
// TODO ability to set global volumes for things like certs
compiler.WithVolumes(),
compiler.WithWorkspaceFromURL("/drone", b.Curr.Link),
).Compile(parsed)
for _, sec := range b.Secs {
if !sec.MatchEvent(b.Curr.Event) {
continue
}
if b.Curr.Verified || sec.SkipVerify {
ir.Secrets = append(ir.Secrets, &backend.Secret{
Mask: sec.Conceal,
Name: sec.Name,
Value: sec.Value,
})
}
}
item := &buildItem{
Job: job,
Config: ir,
Labels: parsed.Labels,
Platform: metadata.Sys.Arch,
}
if item.Labels == nil {
item.Labels = map[string]string{}
}
items = append(items, item)
}
return items, nil
}
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
func PostHook2(c *gin.Context) {
remote_ := remote.FromContext(c)
tmprepo, build, err := remote_.Hook(c.Request)
if err != nil {
logrus.Errorf("failure to parse hook. %s", err)
c.AbortWithError(400, err)
return
}
if build == nil {
c.Writer.WriteHeader(200)
return
}
if tmprepo == nil {
logrus.Errorf("failure to ascertain repo from hook.")
c.Writer.WriteHeader(400)
return
}
// skip the build if any case-insensitive combination of the words "skip" and "ci"
// wrapped in square brackets appear in the commit message
skipMatch := skipRe.FindString(build.Message)
if len(skipMatch) > 0 {
logrus.Infof("ignoring hook. %s found in %s", skipMatch, build.Commit)
c.Writer.WriteHeader(204)
return
}
repo, err := store.GetRepoOwnerName(c, tmprepo.Owner, tmprepo.Name)
if err != nil {
logrus.Errorf("failure to find repo %s/%s from hook. %s", tmprepo.Owner, tmprepo.Name, err)
c.AbortWithError(404, err)
return
}
// get the token and verify the hook is authorized
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
return repo.Hash, nil
})
if err != nil {
logrus.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
c.AbortWithError(400, err)
return
}
if parsed.Text != repo.FullName {
logrus.Errorf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text)
c.AbortWithStatus(403)
return
}
if repo.UserID == 0 {
logrus.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
c.Writer.WriteHeader(204)
return
}
var skipped = true
if (build.Event == model.EventPush && repo.AllowPush) ||
(build.Event == model.EventPull && repo.AllowPull) ||
(build.Event == model.EventDeploy && repo.AllowDeploy) ||
(build.Event == model.EventTag && repo.AllowTag) {
skipped = false
}
if skipped {
logrus.Infof("ignoring hook. repo %s is disabled for %s events.", repo.FullName, build.Event)
c.Writer.WriteHeader(204)
return
}
user, err := store.GetUser(c, repo.UserID)
if err != nil {
logrus.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
c.AbortWithError(500, err)
return
}
// if the remote has a refresh token, the current access token
// may be stale. Therefore, we should refresh prior to dispatching
// the job.
if refresher, ok := remote_.(remote.Refresher); ok {
ok, _ := refresher.Refresh(user)
if ok {
store.UpdateUser(c, user)
}
}
// fetch the build file from the database
cfg := ToConfig(c)
raw, err := remote_.File(user, repo, build, cfg.Yaml)
if err != nil {
logrus.Errorf("failure to get build config for %s. %s", repo.FullName, err)
c.AbortWithError(404, err)
return
}
sec, err := remote_.File(user, repo, build, cfg.Shasum)
if err != nil {
logrus.Debugf("cannot find yaml signature for %s. %s", repo.FullName, err)
}
netrc, err := remote_.Netrc(user, repo)
if err != nil {
c.String(500, "Failed to generate netrc file. %s", err)
return
}
// verify the branches can be built vs skipped
branches, err := yaml.ParseBytes(raw)
if err != nil {
c.String(500, "Failed to parse yaml file. %s", err)
return
}
if !branches.Branches.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
c.String(200, "Branch does not match restrictions defined in yaml")
return
}
signature, err := jose.ParseSigned(string(sec))
if err != nil {
logrus.Debugf("cannot parse .drone.yml.sig file. %s", err)
} else if len(sec) == 0 {
logrus.Debugf("cannot parse .drone.yml.sig file. empty file")
} else {
build.Signed = true
output, verr := signature.Verify([]byte(repo.Hash))
if verr != nil {
logrus.Debugf("cannot verify .drone.yml.sig file. %s", verr)
} else if string(output) != string(raw) {
logrus.Debugf("cannot verify .drone.yml.sig file. no match")
} else {
build.Verified = true
}
}
// update some build fields
build.Status = model.StatusPending
build.RepoID = repo.ID
if err := store.CreateBuild(c, build, build.Jobs...); err != nil {
logrus.Errorf("failure to save commit for %s. %s", repo.FullName, err)
c.AbortWithError(500, err)
return
}
c.JSON(200, build)
// get the previous build so that we can send
// on status change notifications
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
secs, err := store.GetMergedSecretList(c, repo)
if err != nil {
logrus.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
}
//
// BELOW: NEW
//
defer func() {
uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
err = remote_.Status(user, repo, build, uri)
if err != nil {
logrus.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
}
}()
b := builder{
Repo: repo,
Curr: build,
Last: last,
Netrc: netrc,
Secs: secs,
Link: httputil.GetURL(c.Request),
Yaml: string(raw),
}
items, err := b.Build()
if err != nil {
build.Status = model.StatusError
build.Started = time.Now().Unix()
build.Finished = build.Started
build.Error = err.Error()
return
}
for _, item := range items {
build.Jobs = append(build.Jobs, item.Job)
store.CreateJob(c, item.Job)
// TODO err
}
//
// publish topic
//
message := pubsub.Message{
Labels: map[string]string{
"repo": repo.FullName,
"private": strconv.FormatBool(repo.IsPrivate),
},
}
message.Data, _ = json.Marshal(model.Event{
Type: model.Enqueued,
Repo: *repo,
Build: *build,
})
// TODO remove global reference
config.pubsub.Publish(c, "topic/events", message)
//
// end publish topic
//
for _, item := range items {
task := new(queue.Task)
task.ID = fmt.Sprint(item.Job.ID)
task.Labels = map[string]string{}
task.Labels["platform"] = item.Platform
for k, v := range item.Labels {
task.Labels[k] = v
}
task.Data, _ = json.Marshal(rpc.Pipeline{
ID: fmt.Sprint(item.Job.ID),
Config: item.Config,
Timeout: b.Repo.Timeout,
})
config.logger.Open(context.Background(), task.ID)
config.queue.Push(context.Background(), task)
}
}

228
server/rpc.go Normal file
View file

@ -0,0 +1,228 @@
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) {
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, filter rpc.Filter) (*rpc.Pipeline, error) {
fn := func(task *queue.Task) bool {
for k, v := range filter.Labels {
if task.Labels[k] != v {
return false
}
}
return true
}
task, err := s.queue.Poll(c, fn)
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
}
// Wait implements the rpc.Wait function
func (s *RPC) Wait(c context.Context, id string) error {
return s.queue.Wait(c, id)
}
// 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
}
// Upload implements the rpc.Upload function
func (s *RPC) Upload(c context.Context, id, mime string, file io.Reader) error { return nil }
// Done implements the rpc.Done function
func (s *RPC) Done(c context.Context, id string) 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)
s.logger.Write(c, id, entry)
return nil
}

View file

@ -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)
}

View file

@ -0,0 +1,8 @@
-- +migrate Up
ALTER TABLE builds ADD COLUMN build_error VARCHAR(500);
UPDATE builds SET build_error = '';
-- +migrate Down
ALTER TABLE builds DROP COLUMN build_error;

View file

@ -0,0 +1,8 @@
-- +migrate Up
ALTER TABLE builds ADD COLUMN build_error VARCHAR(500);
UPDATE builds SET build_error = '';
-- +migrate Down
ALTER TABLE builds DROP COLUMN build_error;

View file

@ -0,0 +1,8 @@
-- +migrate Up
ALTER TABLE builds ADD COLUMN build_error TEXT;
UPDATE builds SET build_error = '';
-- +migrate Down
ALTER TABLE builds DROP COLUMN build_error;

22
vendor/github.com/Microsoft/go-winio/LICENSE generated vendored Normal file
View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Microsoft
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

268
vendor/github.com/Microsoft/go-winio/backup.go generated vendored Normal file
View file

@ -0,0 +1,268 @@
// +build windows
package winio
import (
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"runtime"
"syscall"
"unicode/utf16"
)
//sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
//sys backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
const (
BackupData = uint32(iota + 1)
BackupEaData
BackupSecurity
BackupAlternateData
BackupLink
BackupPropertyData
BackupObjectId
BackupReparseData
BackupSparseBlock
BackupTxfsData
)
const (
StreamSparseAttributes = uint32(8)
)
const (
WRITE_DAC = 0x40000
WRITE_OWNER = 0x80000
ACCESS_SYSTEM_SECURITY = 0x1000000
)
// BackupHeader represents a backup stream of a file.
type BackupHeader struct {
Id uint32 // The backup stream ID
Attributes uint32 // Stream attributes
Size int64 // The size of the stream in bytes
Name string // The name of the stream (for BackupAlternateData only).
Offset int64 // The offset of the stream in the file (for BackupSparseBlock only).
}
type win32StreamId struct {
StreamId uint32
Attributes uint32
Size uint64
NameSize uint32
}
// BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series
// of BackupHeader values.
type BackupStreamReader struct {
r io.Reader
bytesLeft int64
}
// NewBackupStreamReader produces a BackupStreamReader from any io.Reader.
func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
return &BackupStreamReader{r, 0}
}
// Next returns the next backup stream and prepares for calls to Write(). It skips the remainder of the current stream if
// it was not completely read.
func (r *BackupStreamReader) Next() (*BackupHeader, error) {
if r.bytesLeft > 0 {
if _, err := io.Copy(ioutil.Discard, r); err != nil {
return nil, err
}
}
var wsi win32StreamId
if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil {
return nil, err
}
hdr := &BackupHeader{
Id: wsi.StreamId,
Attributes: wsi.Attributes,
Size: int64(wsi.Size),
}
if wsi.NameSize != 0 {
name := make([]uint16, int(wsi.NameSize/2))
if err := binary.Read(r.r, binary.LittleEndian, name); err != nil {
return nil, err
}
hdr.Name = syscall.UTF16ToString(name)
}
if wsi.StreamId == BackupSparseBlock {
if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil {
return nil, err
}
hdr.Size -= 8
}
r.bytesLeft = hdr.Size
return hdr, nil
}
// Read reads from the current backup stream.
func (r *BackupStreamReader) Read(b []byte) (int, error) {
if r.bytesLeft == 0 {
return 0, io.EOF
}
if int64(len(b)) > r.bytesLeft {
b = b[:r.bytesLeft]
}
n, err := r.r.Read(b)
r.bytesLeft -= int64(n)
if err == io.EOF {
err = io.ErrUnexpectedEOF
} else if r.bytesLeft == 0 && err == nil {
err = io.EOF
}
return n, err
}
// BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API.
type BackupStreamWriter struct {
w io.Writer
bytesLeft int64
}
// NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer.
func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter {
return &BackupStreamWriter{w, 0}
}
// WriteHeader writes the next backup stream header and prepares for calls to Write().
func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error {
if w.bytesLeft != 0 {
return fmt.Errorf("missing %d bytes", w.bytesLeft)
}
name := utf16.Encode([]rune(hdr.Name))
wsi := win32StreamId{
StreamId: hdr.Id,
Attributes: hdr.Attributes,
Size: uint64(hdr.Size),
NameSize: uint32(len(name) * 2),
}
if hdr.Id == BackupSparseBlock {
// Include space for the int64 block offset
wsi.Size += 8
}
if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil {
return err
}
if len(name) != 0 {
if err := binary.Write(w.w, binary.LittleEndian, name); err != nil {
return err
}
}
if hdr.Id == BackupSparseBlock {
if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil {
return err
}
}
w.bytesLeft = hdr.Size
return nil
}
// Write writes to the current backup stream.
func (w *BackupStreamWriter) Write(b []byte) (int, error) {
if w.bytesLeft < int64(len(b)) {
return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft)
}
n, err := w.w.Write(b)
w.bytesLeft -= int64(n)
return n, err
}
// BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API.
type BackupFileReader struct {
f *os.File
includeSecurity bool
ctx uintptr
}
// NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true,
// Read will attempt to read the security descriptor of the file.
func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader {
r := &BackupFileReader{f, includeSecurity, 0}
runtime.SetFinalizer(r, func(r *BackupFileReader) { r.Close() })
return r
}
// Read reads a backup stream from the file by calling the Win32 API BackupRead().
func (r *BackupFileReader) Read(b []byte) (int, error) {
var bytesRead uint32
err := backupRead(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
if err != nil {
return 0, &os.PathError{"BackupRead", r.f.Name(), err}
}
if bytesRead == 0 {
return 0, io.EOF
}
return int(bytesRead), nil
}
// Close frees Win32 resources associated with the BackupFileReader. It does not close
// the underlying file.
func (r *BackupFileReader) Close() error {
if r.ctx != 0 {
backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
r.ctx = 0
}
return nil
}
// BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API.
type BackupFileWriter struct {
f *os.File
includeSecurity bool
ctx uintptr
}
// NewBackupFileWrtier returns a new BackupFileWriter from a file handle. If includeSecurity is true,
// Write() will attempt to restore the security descriptor from the stream.
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
w := &BackupFileWriter{f, includeSecurity, 0}
runtime.SetFinalizer(w, func(w *BackupFileWriter) { w.Close() })
return w
}
// Write restores a portion of the file using the provided backup stream.
func (w *BackupFileWriter) Write(b []byte) (int, error) {
var bytesWritten uint32
err := backupWrite(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
if err != nil {
return 0, &os.PathError{"BackupWrite", w.f.Name(), err}
}
if int(bytesWritten) != len(b) {
return int(bytesWritten), errors.New("not all bytes could be written")
}
return len(b), nil
}
// Close frees Win32 resources associated with the BackupFileWriter. It does not
// close the underlying file.
func (w *BackupFileWriter) Close() error {
if w.ctx != 0 {
backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
w.ctx = 0
}
return nil
}
// OpenForBackup opens a file or directory, potentially skipping access checks if the backup
// or restore privileges have been acquired.
//
// If the file opened was a directory, it cannot be used with Readdir().
func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) {
winPath, err := syscall.UTF16FromString(path)
if err != nil {
return nil, err
}
h, err := syscall.CreateFile(&winPath[0], access, share, nil, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0)
if err != nil {
err = &os.PathError{Op: "open", Path: path, Err: err}
return nil, err
}
return os.NewFile(uintptr(h), path), nil
}

221
vendor/github.com/Microsoft/go-winio/file.go generated vendored Normal file
View file

@ -0,0 +1,221 @@
// +build windows
package winio
import (
"errors"
"io"
"runtime"
"sync"
"syscall"
"time"
)
//sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
//sys timeBeginPeriod(period uint32) (n int32) = winmm.timeBeginPeriod
const (
cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
cFILE_SKIP_SET_EVENT_ON_HANDLE = 2
)
var (
ErrFileClosed = errors.New("file has already been closed")
ErrTimeout = &timeoutError{}
)
type timeoutError struct{}
func (e *timeoutError) Error() string { return "i/o timeout" }
func (e *timeoutError) Timeout() bool { return true }
func (e *timeoutError) Temporary() bool { return true }
var ioInitOnce sync.Once
var ioCompletionPort syscall.Handle
// ioResult contains the result of an asynchronous IO operation
type ioResult struct {
bytes uint32
err error
}
// ioOperation represents an outstanding asynchronous Win32 IO
type ioOperation struct {
o syscall.Overlapped
ch chan ioResult
}
func initIo() {
h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff)
if err != nil {
panic(err)
}
ioCompletionPort = h
go ioCompletionProcessor(h)
}
// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
// It takes ownership of this handle and will close it if it is garbage collected.
type win32File struct {
handle syscall.Handle
wg sync.WaitGroup
closing bool
readDeadline time.Time
writeDeadline time.Time
}
// makeWin32File makes a new win32File from an existing file handle
func makeWin32File(h syscall.Handle) (*win32File, error) {
f := &win32File{handle: h}
ioInitOnce.Do(initIo)
_, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
if err != nil {
return nil, err
}
err = setFileCompletionNotificationModes(h, cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE)
if err != nil {
return nil, err
}
runtime.SetFinalizer(f, (*win32File).closeHandle)
return f, nil
}
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
return makeWin32File(h)
}
// closeHandle closes the resources associated with a Win32 handle
func (f *win32File) closeHandle() {
if !f.closing {
// cancel all IO and wait for it to complete
f.closing = true
cancelIoEx(f.handle, nil)
f.wg.Wait()
// at this point, no new IO can start
syscall.Close(f.handle)
f.handle = 0
}
}
// Close closes a win32File.
func (f *win32File) Close() error {
f.closeHandle()
runtime.SetFinalizer(f, nil)
return nil
}
// prepareIo prepares for a new IO operation
func (f *win32File) prepareIo() (*ioOperation, error) {
f.wg.Add(1)
if f.closing {
return nil, ErrFileClosed
}
c := &ioOperation{}
c.ch = make(chan ioResult)
return c, nil
}
// ioCompletionProcessor processes completed async IOs forever
func ioCompletionProcessor(h syscall.Handle) {
// Set the timer resolution to 1. This fixes a performance regression in golang 1.6.
timeBeginPeriod(1)
for {
var bytes uint32
var key uintptr
var op *ioOperation
err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE)
if op == nil {
panic(err)
}
op.ch <- ioResult{bytes, err}
}
}
// asyncIo processes the return value from ReadFile or WriteFile, blocking until
// the operation has actually completed.
func (f *win32File) asyncIo(c *ioOperation, deadline time.Time, bytes uint32, err error) (int, error) {
if err != syscall.ERROR_IO_PENDING {
f.wg.Done()
return int(bytes), err
} else {
var r ioResult
wait := true
timedout := false
if f.closing {
cancelIoEx(f.handle, &c.o)
} else if !deadline.IsZero() {
now := time.Now()
if !deadline.After(now) {
timedout = true
} else {
timeout := time.After(deadline.Sub(now))
select {
case r = <-c.ch:
wait = false
case <-timeout:
timedout = true
}
}
}
if timedout {
cancelIoEx(f.handle, &c.o)
}
if wait {
r = <-c.ch
}
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED {
if f.closing {
err = ErrFileClosed
} else if timedout {
err = ErrTimeout
}
}
f.wg.Done()
return int(r.bytes), err
}
}
// Read reads from a file handle.
func (f *win32File) Read(b []byte) (int, error) {
c, err := f.prepareIo()
if err != nil {
return 0, err
}
var bytes uint32
err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIo(c, f.readDeadline, bytes, err)
// Handle EOF conditions.
if err == nil && n == 0 && len(b) != 0 {
return 0, io.EOF
} else if err == syscall.ERROR_BROKEN_PIPE {
return 0, io.EOF
} else {
return n, err
}
}
// Write writes to a file handle.
func (f *win32File) Write(b []byte) (int, error) {
c, err := f.prepareIo()
if err != nil {
return 0, err
}
var bytes uint32
err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
return f.asyncIo(c, f.writeDeadline, bytes, err)
}
func (f *win32File) SetReadDeadline(t time.Time) error {
f.readDeadline = t
return nil
}
func (f *win32File) SetWriteDeadline(t time.Time) error {
f.writeDeadline = t
return nil
}

56
vendor/github.com/Microsoft/go-winio/fileinfo.go generated vendored Normal file
View file

@ -0,0 +1,56 @@
// +build windows
package winio
import (
"os"
"syscall"
"unsafe"
)
//sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx
//sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle
const (
fileBasicInfo = 0
fileIDInfo = 0x12
)
// FileBasicInfo contains file access time and file attributes information.
type FileBasicInfo struct {
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime
FileAttributes uintptr // includes padding
}
// GetFileBasicInfo retrieves times and attributes for a file.
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
bi := &FileBasicInfo{}
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
}
return bi, nil
}
// SetFileBasicInfo sets times and attributes for a file.
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
if err := setFileInformationByHandle(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
}
return nil
}
// FileIDInfo contains the volume serial number and file ID for a file. This pair should be
// unique on a system.
type FileIDInfo struct {
VolumeSerialNumber uint64
FileID [16]byte
}
// GetFileID retrieves the unique (volume, file ID) pair for a file.
func GetFileID(f *os.File) (*FileIDInfo, error) {
fileID := &FileIDInfo{}
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileIDInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
}
return fileID, nil
}

400
vendor/github.com/Microsoft/go-winio/pipe.go generated vendored Normal file
View file

@ -0,0 +1,400 @@
// +build windows
package winio
import (
"errors"
"io"
"net"
"os"
"syscall"
"time"
"unsafe"
)
//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *securityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
//sys createFile(name string, access uint32, mode uint32, sa *securityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW
//sys waitNamedPipe(name string, timeout uint32) (err error) = WaitNamedPipeW
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
type securityAttributes struct {
Length uint32
SecurityDescriptor *byte
InheritHandle uint32
}
const (
cERROR_PIPE_BUSY = syscall.Errno(231)
cERROR_PIPE_CONNECTED = syscall.Errno(535)
cERROR_SEM_TIMEOUT = syscall.Errno(121)
cPIPE_ACCESS_DUPLEX = 0x3
cFILE_FLAG_FIRST_PIPE_INSTANCE = 0x80000
cSECURITY_SQOS_PRESENT = 0x100000
cSECURITY_ANONYMOUS = 0
cPIPE_REJECT_REMOTE_CLIENTS = 0x8
cPIPE_UNLIMITED_INSTANCES = 255
cNMPWAIT_USE_DEFAULT_WAIT = 0
cNMPWAIT_NOWAIT = 1
cPIPE_TYPE_MESSAGE = 4
cPIPE_READMODE_MESSAGE = 2
)
var (
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
// This error should match net.errClosing since docker takes a dependency on its text.
ErrPipeListenerClosed = errors.New("use of closed network connection")
errPipeWriteClosed = errors.New("pipe has been closed for write")
)
type win32Pipe struct {
*win32File
path string
}
type win32MessageBytePipe struct {
win32Pipe
writeClosed bool
readEOF bool
}
type pipeAddress string
func (f *win32Pipe) LocalAddr() net.Addr {
return pipeAddress(f.path)
}
func (f *win32Pipe) RemoteAddr() net.Addr {
return pipeAddress(f.path)
}
func (f *win32Pipe) SetDeadline(t time.Time) error {
f.SetReadDeadline(t)
f.SetWriteDeadline(t)
return nil
}
// CloseWrite closes the write side of a message pipe in byte mode.
func (f *win32MessageBytePipe) CloseWrite() error {
if f.writeClosed {
return errPipeWriteClosed
}
_, err := f.win32File.Write(nil)
if err != nil {
return err
}
f.writeClosed = true
return nil
}
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
// they are used to implement CloseWrite().
func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
if f.writeClosed {
return 0, errPipeWriteClosed
}
if len(b) == 0 {
return 0, nil
}
return f.win32File.Write(b)
}
// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message
// mode pipe will return io.EOF, as will all subsequent reads.
func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
if f.readEOF {
return 0, io.EOF
}
n, err := f.win32File.Read(b)
if err == io.EOF {
// If this was the result of a zero-byte read, then
// it is possible that the read was due to a zero-size
// message. Since we are simulating CloseWrite with a
// zero-byte message, ensure that all future Read() calls
// also return EOF.
f.readEOF = true
}
return n, err
}
func (s pipeAddress) Network() string {
return "pipe"
}
func (s pipeAddress) String() string {
return string(s)
}
// DialPipe connects to a named pipe by path, timing out if the connection
// takes longer than the specified duration. If timeout is nil, then the timeout
// is the default timeout established by the pipe server.
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
var absTimeout time.Time
if timeout != nil {
absTimeout = time.Now().Add(*timeout)
}
var err error
var h syscall.Handle
for {
h, err = createFile(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
if err != cERROR_PIPE_BUSY {
break
}
now := time.Now()
var ms uint32
if absTimeout.IsZero() {
ms = cNMPWAIT_USE_DEFAULT_WAIT
} else if now.After(absTimeout) {
ms = cNMPWAIT_NOWAIT
} else {
ms = uint32(absTimeout.Sub(now).Nanoseconds() / 1000 / 1000)
}
err = waitNamedPipe(path, ms)
if err != nil {
if err == cERROR_SEM_TIMEOUT {
return nil, ErrTimeout
}
break
}
}
if err != nil {
return nil, &os.PathError{Op: "open", Path: path, Err: err}
}
var flags uint32
err = getNamedPipeInfo(h, &flags, nil, nil, nil)
if err != nil {
return nil, err
}
var state uint32
err = getNamedPipeHandleState(h, &state, nil, nil, nil, nil, 0)
if err != nil {
return nil, err
}
if state&cPIPE_READMODE_MESSAGE != 0 {
return nil, &os.PathError{Op: "open", Path: path, Err: errors.New("message readmode pipes not supported")}
}
f, err := makeWin32File(h)
if err != nil {
syscall.Close(h)
return nil, err
}
// If the pipe is in message mode, return a message byte pipe, which
// supports CloseWrite().
if flags&cPIPE_TYPE_MESSAGE != 0 {
return &win32MessageBytePipe{
win32Pipe: win32Pipe{win32File: f, path: path},
}, nil
}
return &win32Pipe{win32File: f, path: path}, nil
}
type acceptResponse struct {
f *win32File
err error
}
type win32PipeListener struct {
firstHandle syscall.Handle
path string
securityDescriptor []byte
config PipeConfig
acceptCh chan (chan acceptResponse)
closeCh chan int
doneCh chan int
}
func makeServerPipeHandle(path string, securityDescriptor []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
var flags uint32 = cPIPE_ACCESS_DUPLEX | syscall.FILE_FLAG_OVERLAPPED
if first {
flags |= cFILE_FLAG_FIRST_PIPE_INSTANCE
}
var mode uint32 = cPIPE_REJECT_REMOTE_CLIENTS
if c.MessageMode {
mode |= cPIPE_TYPE_MESSAGE
}
var sa securityAttributes
sa.Length = uint32(unsafe.Sizeof(sa))
if securityDescriptor != nil {
sa.SecurityDescriptor = &securityDescriptor[0]
}
h, err := createNamedPipe(path, flags, mode, cPIPE_UNLIMITED_INSTANCES, uint32(c.OutputBufferSize), uint32(c.InputBufferSize), 0, &sa)
if err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err}
}
return h, nil
}
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
h, err := makeServerPipeHandle(l.path, l.securityDescriptor, &l.config, false)
if err != nil {
return nil, err
}
f, err := makeWin32File(h)
if err != nil {
syscall.Close(h)
return nil, err
}
return f, nil
}
func (l *win32PipeListener) listenerRoutine() {
closed := false
for !closed {
select {
case <-l.closeCh:
closed = true
case responseCh := <-l.acceptCh:
p, err := l.makeServerPipe()
if err == nil {
// Wait for the client to connect.
ch := make(chan error)
go func() {
ch <- connectPipe(p)
}()
select {
case err = <-ch:
if err != nil {
p.Close()
p = nil
}
case <-l.closeCh:
// Abort the connect request by closing the handle.
p.Close()
p = nil
err = <-ch
if err == nil || err == ErrFileClosed {
err = ErrPipeListenerClosed
}
closed = true
}
}
responseCh <- acceptResponse{p, err}
}
}
syscall.Close(l.firstHandle)
l.firstHandle = 0
// Notify Close() and Accept() callers that the handle has been closed.
close(l.doneCh)
}
// PipeConfig contain configuration for the pipe listener.
type PipeConfig struct {
// SecurityDescriptor contains a Windows security descriptor in SDDL format.
SecurityDescriptor string
// MessageMode determines whether the pipe is in byte or message mode. In either
// case the pipe is read in byte mode by default. The only practical difference in
// this implementation is that CloseWrite() is only supported for message mode pipes;
// CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only
// transferred to the reader (and returned as io.EOF in this implementation)
// when the pipe is in message mode.
MessageMode bool
// InputBufferSize specifies the size the input buffer, in bytes.
InputBufferSize int32
// OutputBufferSize specifies the size the input buffer, in bytes.
OutputBufferSize int32
}
// ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe.
// The pipe must not already exist.
func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
var (
sd []byte
err error
)
if c == nil {
c = &PipeConfig{}
}
if c.SecurityDescriptor != "" {
sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor)
if err != nil {
return nil, err
}
}
h, err := makeServerPipeHandle(path, sd, c, true)
if err != nil {
return nil, err
}
// Immediately open and then close a client handle so that the named pipe is
// created but not currently accepting connections.
h2, err := createFile(path, 0, 0, nil, syscall.OPEN_EXISTING, cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
if err != nil {
syscall.Close(h)
return nil, err
}
syscall.Close(h2)
l := &win32PipeListener{
firstHandle: h,
path: path,
securityDescriptor: sd,
config: *c,
acceptCh: make(chan (chan acceptResponse)),
closeCh: make(chan int),
doneCh: make(chan int),
}
go l.listenerRoutine()
return l, nil
}
func connectPipe(p *win32File) error {
c, err := p.prepareIo()
if err != nil {
return err
}
err = connectNamedPipe(p.handle, &c.o)
_, err = p.asyncIo(c, time.Time{}, 0, err)
if err != nil && err != cERROR_PIPE_CONNECTED {
return err
}
return nil
}
func (l *win32PipeListener) Accept() (net.Conn, error) {
ch := make(chan acceptResponse)
select {
case l.acceptCh <- ch:
response := <-ch
err := response.err
if err != nil {
return nil, err
}
if l.config.MessageMode {
return &win32MessageBytePipe{
win32Pipe: win32Pipe{win32File: response.f, path: l.path},
}, nil
}
return &win32Pipe{win32File: response.f, path: l.path}, nil
case <-l.doneCh:
return nil, ErrPipeListenerClosed
}
}
func (l *win32PipeListener) Close() error {
select {
case l.closeCh <- 1:
<-l.doneCh
case <-l.doneCh:
}
return nil
}
func (l *win32PipeListener) Addr() net.Addr {
return pipeAddress(l.path)
}

202
vendor/github.com/Microsoft/go-winio/privilege.go generated vendored Normal file
View file

@ -0,0 +1,202 @@
// +build windows
package winio
import (
"bytes"
"encoding/binary"
"fmt"
"runtime"
"sync"
"syscall"
"unicode/utf16"
"golang.org/x/sys/windows"
)
//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges
//sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf
//sys revertToSelf() (err error) = advapi32.RevertToSelf
//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken
//sys getCurrentThread() (h syscall.Handle) = GetCurrentThread
//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW
//sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
//sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW
const (
SE_PRIVILEGE_ENABLED = 2
ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300
SeBackupPrivilege = "SeBackupPrivilege"
SeRestorePrivilege = "SeRestorePrivilege"
)
const (
securityAnonymous = iota
securityIdentification
securityImpersonation
securityDelegation
)
var (
privNames = make(map[string]uint64)
privNameMutex sync.Mutex
)
// PrivilegeError represents an error enabling privileges.
type PrivilegeError struct {
privileges []uint64
}
func (e *PrivilegeError) Error() string {
s := ""
if len(e.privileges) > 1 {
s = "Could not enable privileges "
} else {
s = "Could not enable privilege "
}
for i, p := range e.privileges {
if i != 0 {
s += ", "
}
s += `"`
s += getPrivilegeName(p)
s += `"`
}
return s
}
// RunWithPrivilege enables a single privilege for a function call.
func RunWithPrivilege(name string, fn func() error) error {
return RunWithPrivileges([]string{name}, fn)
}
// RunWithPrivileges enables privileges for a function call.
func RunWithPrivileges(names []string, fn func() error) error {
privileges, err := mapPrivileges(names)
if err != nil {
return err
}
runtime.LockOSThread()
defer runtime.UnlockOSThread()
token, err := newThreadToken()
if err != nil {
return err
}
defer releaseThreadToken(token)
err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED)
if err != nil {
return err
}
return fn()
}
func mapPrivileges(names []string) ([]uint64, error) {
var privileges []uint64
privNameMutex.Lock()
defer privNameMutex.Unlock()
for _, name := range names {
p, ok := privNames[name]
if !ok {
err := lookupPrivilegeValue("", name, &p)
if err != nil {
return nil, err
}
privNames[name] = p
}
privileges = append(privileges, p)
}
return privileges, nil
}
// EnableProcessPrivileges enables privileges globally for the process.
func EnableProcessPrivileges(names []string) error {
return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED)
}
// DisableProcessPrivileges disables privileges globally for the process.
func DisableProcessPrivileges(names []string) error {
return enableDisableProcessPrivilege(names, 0)
}
func enableDisableProcessPrivilege(names []string, action uint32) error {
privileges, err := mapPrivileges(names)
if err != nil {
return err
}
p, _ := windows.GetCurrentProcess()
var token windows.Token
err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
if err != nil {
return err
}
defer token.Close()
return adjustPrivileges(token, privileges, action)
}
func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error {
var b bytes.Buffer
binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
for _, p := range privileges {
binary.Write(&b, binary.LittleEndian, p)
binary.Write(&b, binary.LittleEndian, action)
}
prevState := make([]byte, b.Len())
reqSize := uint32(0)
success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize)
if !success {
return err
}
if err == ERROR_NOT_ALL_ASSIGNED {
return &PrivilegeError{privileges}
}
return nil
}
func getPrivilegeName(luid uint64) string {
var nameBuffer [256]uint16
bufSize := uint32(len(nameBuffer))
err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize)
if err != nil {
return fmt.Sprintf("<unknown privilege %d>", luid)
}
var displayNameBuffer [256]uint16
displayBufSize := uint32(len(displayNameBuffer))
var langID uint32
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID)
if err != nil {
return fmt.Sprintf("<unknown privilege %s>", string(utf16.Decode(nameBuffer[:bufSize])))
}
return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
}
func newThreadToken() (windows.Token, error) {
err := impersonateSelf(securityImpersonation)
if err != nil {
return 0, err
}
var token windows.Token
err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token)
if err != nil {
rerr := revertToSelf()
if rerr != nil {
panic(rerr)
}
return 0, err
}
return token, nil
}
func releaseThreadToken(h windows.Token) {
err := revertToSelf()
if err != nil {
panic(err)
}
h.Close()
}

128
vendor/github.com/Microsoft/go-winio/reparse.go generated vendored Normal file
View file

@ -0,0 +1,128 @@
package winio
import (
"bytes"
"encoding/binary"
"fmt"
"strings"
"unicode/utf16"
"unsafe"
)
const (
reparseTagMountPoint = 0xA0000003
reparseTagSymlink = 0xA000000C
)
type reparseDataBuffer struct {
ReparseTag uint32
ReparseDataLength uint16
Reserved uint16
SubstituteNameOffset uint16
SubstituteNameLength uint16
PrintNameOffset uint16
PrintNameLength uint16
}
// ReparsePoint describes a Win32 symlink or mount point.
type ReparsePoint struct {
Target string
IsMountPoint bool
}
// UnsupportedReparsePointError is returned when trying to decode a non-symlink or
// mount point reparse point.
type UnsupportedReparsePointError struct {
Tag uint32
}
func (e *UnsupportedReparsePointError) Error() string {
return fmt.Sprintf("unsupported reparse point %x", e.Tag)
}
// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink
// or a mount point.
func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
tag := binary.LittleEndian.Uint32(b[0:4])
return DecodeReparsePointData(tag, b[8:])
}
func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
isMountPoint := false
switch tag {
case reparseTagMountPoint:
isMountPoint = true
case reparseTagSymlink:
default:
return nil, &UnsupportedReparsePointError{tag}
}
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
if !isMountPoint {
nameOffset += 4
}
nameLength := binary.LittleEndian.Uint16(b[6:8])
name := make([]uint16, nameLength/2)
err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
if err != nil {
return nil, err
}
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
}
func isDriveLetter(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
// mount point.
func EncodeReparsePoint(rp *ReparsePoint) []byte {
// Generate an NT path and determine if this is a relative path.
var ntTarget string
relative := false
if strings.HasPrefix(rp.Target, `\\?\`) {
ntTarget = `\??\` + rp.Target[4:]
} else if strings.HasPrefix(rp.Target, `\\`) {
ntTarget = `\??\UNC\` + rp.Target[2:]
} else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
ntTarget = `\??\` + rp.Target
} else {
ntTarget = rp.Target
relative = true
}
// The paths must be NUL-terminated even though they are counted strings.
target16 := utf16.Encode([]rune(rp.Target + "\x00"))
ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00"))
size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8
size += len(ntTarget16)*2 + len(target16)*2
tag := uint32(reparseTagMountPoint)
if !rp.IsMountPoint {
tag = reparseTagSymlink
size += 4 // Add room for symlink flags
}
data := reparseDataBuffer{
ReparseTag: tag,
ReparseDataLength: uint16(size),
SubstituteNameOffset: 0,
SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2),
PrintNameOffset: uint16(len(ntTarget16) * 2),
PrintNameLength: uint16((len(target16) - 1) * 2),
}
var b bytes.Buffer
binary.Write(&b, binary.LittleEndian, &data)
if !rp.IsMountPoint {
flags := uint32(0)
if relative {
flags |= 1
}
binary.Write(&b, binary.LittleEndian, flags)
}
binary.Write(&b, binary.LittleEndian, ntTarget16)
binary.Write(&b, binary.LittleEndian, target16)
return b.Bytes()
}

98
vendor/github.com/Microsoft/go-winio/sd.go generated vendored Normal file
View file

@ -0,0 +1,98 @@
// +build windows
package winio
import (
"syscall"
"unsafe"
)
//sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW
//sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW
//sys convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) = advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW
//sys convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) = advapi32.ConvertSecurityDescriptorToStringSecurityDescriptorW
//sys localFree(mem uintptr) = LocalFree
//sys getSecurityDescriptorLength(sd uintptr) (len uint32) = advapi32.GetSecurityDescriptorLength
const (
cERROR_NONE_MAPPED = syscall.Errno(1332)
)
type AccountLookupError struct {
Name string
Err error
}
func (e *AccountLookupError) Error() string {
if e.Name == "" {
return "lookup account: empty account name specified"
}
var s string
switch e.Err {
case cERROR_NONE_MAPPED:
s = "not found"
default:
s = e.Err.Error()
}
return "lookup account " + e.Name + ": " + s
}
type SddlConversionError struct {
Sddl string
Err error
}
func (e *SddlConversionError) Error() string {
return "convert " + e.Sddl + ": " + e.Err.Error()
}
// LookupSidByName looks up the SID of an account by name
func LookupSidByName(name string) (sid string, err error) {
if name == "" {
return "", &AccountLookupError{name, cERROR_NONE_MAPPED}
}
var sidSize, sidNameUse, refDomainSize uint32
err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse)
if err != nil && err != syscall.ERROR_INSUFFICIENT_BUFFER {
return "", &AccountLookupError{name, err}
}
sidBuffer := make([]byte, sidSize)
refDomainBuffer := make([]uint16, refDomainSize)
err = lookupAccountName(nil, name, &sidBuffer[0], &sidSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse)
if err != nil {
return "", &AccountLookupError{name, err}
}
var strBuffer *uint16
err = convertSidToStringSid(&sidBuffer[0], &strBuffer)
if err != nil {
return "", &AccountLookupError{name, err}
}
sid = syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:])
localFree(uintptr(unsafe.Pointer(strBuffer)))
return sid, nil
}
func SddlToSecurityDescriptor(sddl string) ([]byte, error) {
var sdBuffer uintptr
err := convertStringSecurityDescriptorToSecurityDescriptor(sddl, 1, &sdBuffer, nil)
if err != nil {
return nil, &SddlConversionError{sddl, err}
}
defer localFree(sdBuffer)
sd := make([]byte, getSecurityDescriptorLength(sdBuffer))
copy(sd, (*[0xffff]byte)(unsafe.Pointer(sdBuffer))[:len(sd)])
return sd, nil
}
func SecurityDescriptorToSddl(sd []byte) (string, error) {
var sddl *uint16
// The returned string length seems to including an aribtrary number of terminating NULs.
// Don't use it.
err := convertSecurityDescriptorToStringSecurityDescriptor(&sd[0], 1, 0xff, &sddl, nil)
if err != nil {
return "", err
}
defer localFree(uintptr(unsafe.Pointer(sddl)))
return syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(sddl))[:]), nil
}

3
vendor/github.com/Microsoft/go-winio/syscall.go generated vendored Normal file
View file

@ -0,0 +1,3 @@
package winio
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go

View file

@ -0,0 +1,496 @@
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
package winio
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
var (
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
modwinmm = windows.NewLazySystemDLL("winmm.dll")
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
proctimeBeginPeriod = modwinmm.NewProc("timeBeginPeriod")
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
procCreateFileW = modkernel32.NewProc("CreateFileW")
procWaitNamedPipeW = modkernel32.NewProc("WaitNamedPipeW")
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
procConvertSecurityDescriptorToStringSecurityDescriptorW = modadvapi32.NewProc("ConvertSecurityDescriptorToStringSecurityDescriptorW")
procLocalFree = modkernel32.NewProc("LocalFree")
procGetSecurityDescriptorLength = modadvapi32.NewProc("GetSecurityDescriptorLength")
procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
procBackupRead = modkernel32.NewProc("BackupRead")
procBackupWrite = modkernel32.NewProc("BackupWrite")
)
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
newport = syscall.Handle(r0)
if newport == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) {
r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func timeBeginPeriod(period uint32) (n int32) {
r0, _, _ := syscall.Syscall(proctimeBeginPeriod.Addr(), 1, uintptr(period), 0, 0)
n = int32(r0)
return
}
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *securityAttributes) (handle syscall.Handle, err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa)
}
func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *securityAttributes) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func createFile(name string, access uint32, mode uint32, sa *securityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _createFile(_p0, access, mode, sa, createmode, attrs, templatefile)
}
func _createFile(name *uint16, access uint32, mode uint32, sa *securityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func waitNamedPipe(name string, timeout uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _waitNamedPipe(_p0, timeout)
}
func _waitNamedPipe(name *uint16, timeout uint32) (err error) {
r1, _, e1 := syscall.Syscall(procWaitNamedPipeW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(timeout), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(accountName)
if err != nil {
return
}
return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse)
}
func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procLookupAccountNameW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func convertSidToStringSid(sid *byte, str **uint16) (err error) {
r1, _, e1 := syscall.Syscall(procConvertSidToStringSidW.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(str)
if err != nil {
return
}
return _convertStringSecurityDescriptorToSecurityDescriptor(_p0, revision, sd, size)
}
func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision uint32, sd *uintptr, size *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procConvertStringSecurityDescriptorToSecurityDescriptorW.Addr(), 4, uintptr(unsafe.Pointer(str)), uintptr(revision), uintptr(unsafe.Pointer(sd)), uintptr(unsafe.Pointer(size)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), 5, uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func localFree(mem uintptr) {
syscall.Syscall(procLocalFree.Addr(), 1, uintptr(mem), 0, 0)
return
}
func getSecurityDescriptorLength(sd uintptr) (len uint32) {
r0, _, _ := syscall.Syscall(procGetSecurityDescriptorLength.Addr(), 1, uintptr(sd), 0, 0)
len = uint32(r0)
return
}
func getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procSetFileInformationByHandle.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
var _p0 uint32
if releaseAll {
_p0 = 1
} else {
_p0 = 0
}
r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
success = r0 != 0
if true {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func impersonateSelf(level uint32) (err error) {
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(level), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func revertToSelf() (err error) {
r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) {
var _p0 uint32
if openAsSelf {
_p0 = 1
} else {
_p0 = 0
}
r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getCurrentThread() (h syscall.Handle) {
r0, _, _ := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
h = syscall.Handle(r0)
return
}
func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
var _p1 *uint16
_p1, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _lookupPrivilegeValue(_p0, _p1, luid)
}
func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) {
r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
return _lookupPrivilegeName(_p0, luid, buffer, size)
}
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName)
if err != nil {
return
}
return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId)
}
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeDisplayNameW.Addr(), 5, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)), 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
var _p0 *byte
if len(b) > 0 {
_p0 = &b[0]
}
var _p1 uint32
if abort {
_p1 = 1
} else {
_p1 = 0
}
var _p2 uint32
if processSecurity {
_p2 = 1
} else {
_p2 = 0
}
r1, _, e1 := syscall.Syscall9(procBackupRead.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
var _p0 *byte
if len(b) > 0 {
_p0 = &b[0]
}
var _p1 uint32
if abort {
_p1 = 1
} else {
_p1 = 0
}
var _p2 uint32
if processSecurity {
_p2 = 1
} else {
_p2 = 0
}
r1, _, e1 := syscall.Syscall9(procBackupWrite.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}

29
vendor/github.com/cncd/logging/LICENSE generated vendored Normal file
View 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
View 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
View 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
View 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
View 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.

View 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
}

View 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
}

View 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,
}
)

View 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)

View file

@ -0,0 +1,112 @@
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
Secrets []*Secret `json:"secrets"` // secret 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"`
}
// Secret defines a runtime secret
Secret struct {
Name string `json:"name,omitempty"`
Value string `json:"value,omitempty"`
Mount string `json:"mount,omitempty"`
Mask bool `json:"mask,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
View 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)
}

View file

@ -0,0 +1,208 @@
package frontend
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// Event types corresponding to scm hooks.
const (
EventPush = "push"
EventPull = "pull_request"
EventTag = "tag"
EventDeploy = "deployment"
)
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"`
Secrets []Secret `json:"secrets,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"`
}
// Secret defines a runtime secret
Secret struct {
Name string `json:"name,omitempty"`
Value string `json:"value,omitempty"`
Mount string `json:"mount,omitempty"`
Mask bool `json:"mask,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"`
Version string `json:"version,omitempty"`
}
)
// Environ returns the metadata as a map of environment variables.
func (m *Metadata) Environ() map[string]string {
params := 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_SYSTEM_VERSION": m.Sys.Version,
"CI": m.Sys.Name,
}
if m.Curr.Event == EventTag {
params["CI_TAG"] = strings.TrimPrefix(m.Curr.Commit.Ref, "refs/tags/")
}
if m.Curr.Event == EventPull {
params["CI_PULL_REQUEST"] = pullRegexp.FindString(m.Curr.Commit.Ref)
}
return params
}
// EnvironDrone returns metadata as a map of DRONE_ environment variables.
// This is here for backward compatibility and will eventually be removed.
func (m *Metadata) EnvironDrone() map[string]string {
// MISSING PARAMETERS
// * DRONE_REPO_TRUSTED
// * DRONE_YAML_VERIFIED
// * DRONE_YAML_VERIFIED
params := map[string]string{
"CI": "drone",
"DRONE": "true",
"DRONE_ARCH": "linux/amd64",
"DRONE_REPO": m.Repo.Name,
"DRONE_REPO_SCM": "git",
"DRONE_REPO_OWNER": strings.Split(m.Repo.Name, "/")[0],
"DRONE_REPO_NAME": strings.Split(m.Repo.Name, "/")[0],
"DRONE_REPO_LINK": m.Repo.Link,
"DRONE_REPO_BRANCH": m.Curr.Commit.Branch,
"DRONE_REPO_PRIVATE": fmt.Sprintf("%v", m.Repo.Private),
"DRONE_REPO_TRUSTED": "false", // TODO should this be added?
"DRONE_REMOTE_URL": m.Repo.Remote,
"DRONE_COMMIT_SHA": m.Curr.Commit.Sha,
"DRONE_COMMIT_REF": m.Curr.Commit.Ref,
"DRONE_COMMIT_REFSPEC": m.Curr.Commit.Refspec,
"DRONE_COMMIT_BRANCH": m.Curr.Commit.Branch,
"DRONE_COMMIT_LINK": m.Curr.Link,
"DRONE_COMMIT_MESSAGE": m.Curr.Commit.Message,
"DRONE_COMMIT_AUTHOR": m.Curr.Commit.Author.Name,
"DRONE_COMMIT_AUTHOR_EMAIL": m.Curr.Commit.Author.Email,
"DRONE_COMMIT_AUTHOR_AVATAR": m.Curr.Commit.Author.Avatar,
"DRONE_BUILD_NUMBER": fmt.Sprintf("%d", m.Curr.Number),
"DRONE_BUILD_EVENT": m.Curr.Event,
"DRONE_BUILD_LINK": fmt.Sprintf("%s/%s/%d", m.Sys.Link, m.Repo.Name, m.Curr.Number),
"DRONE_BUILD_CREATED": fmt.Sprintf("%d", m.Curr.Created),
"DRONE_BUILD_STARTED": fmt.Sprintf("%d", m.Curr.Started),
"DRONE_BUILD_FINISHED": fmt.Sprintf("%d", m.Curr.Finished),
"DRONE_JOB_NUMBER": fmt.Sprintf("%d", m.Job.Number),
"DRONE_JOB_STARTED": fmt.Sprintf("%d", m.Curr.Started), // ISSUE: no job started
"DRONE_BRANCH": m.Curr.Commit.Branch,
"DRONE_COMMIT": m.Curr.Commit.Sha,
"DRONE_VERSION": m.Sys.Version,
"DRONE_DEPLOY_TO": m.Curr.Target,
"DRONE_PREV_BUILD_STATUS": m.Prev.Status,
"DRONE_PREV_BUILD_NUMBER": fmt.Sprintf("%v", m.Prev.Number),
"DRONE_PREV_COMMIT_SHA": m.Prev.Commit.Sha,
}
if m.Curr.Event == EventTag {
params["DRONE_TAG"] = strings.TrimPrefix(m.Curr.Commit.Ref, "refs/tags/")
}
if m.Curr.Event == EventPull {
params["DRONE_PULL_REQUEST"] = pullRegexp.FindString(m.Curr.Commit.Ref)
}
return params
}
var pullRegexp = regexp.MustCompile("\\d+")

View file

@ -0,0 +1,168 @@
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{
Name: "clone",
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,
// }
// }

View file

@ -0,0 +1,145 @@
package compiler
import (
"fmt"
"path"
"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
}
}
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)
}

View 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
}

View file

@ -0,0 +1,169 @@
package compiler
import (
"net/url"
"os"
"path/filepath"
"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
}
// TODO this is present for backward compatibility and should
// be removed in a future version.
for k, v := range metadata.EnvironDrone() {
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,
// TODO this is present for backward compatibility and should
// be removed in a future version.
"DRONE_NETRC_USERNAME": username,
"DRONE_NETRC_PASSWORD": password,
"DRONE_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
}
}
// WithWorkspaceFromURL configures the compiler with the workspace
// base and path based on the repository url.
func WithWorkspaceFromURL(base, link string) Option {
path := "src"
parsed, err := url.Parse(link)
if err == nil {
path = filepath.Join(path, parsed.Host, parsed.Path)
}
return WithWorkspace(base, 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
}

View 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
}

View 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
`

View file

@ -0,0 +1 @@
package compiler

View 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)
}

View 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
}

View 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
}

View 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
}

View 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
}
}

View 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
}

View 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
}

View 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
}

View 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
View 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)
}

View file

@ -0,0 +1 @@
package multipart

View file

@ -0,0 +1,103 @@
package multipart
import (
"bufio"
"bytes"
"io"
"mime/multipart"
"net/textproto"
)
type (
// Reader is an iterator over parts in a multipart log stream.
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.
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(8)
if bytes.Equal(out, []byte("PIPELINE")) {
return &multipartReader{
reader: multipart.NewReader(buf, "boundary"),
}
}
return &textReader{
reader: buf,
}
}
//
// wraps the stdlib multi-part reader
//
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
}
//
// wraps a simple io.Reader to satisfy the multi-part interface
//
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
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
View 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
View 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
View 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
}

188
vendor/github.com/cncd/pipeline/pipeline/rpc/client.go generated vendored Normal file
View file

@ -0,0 +1,188 @@
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"
methodWait = "wait"
methodDone = "done"
methodExtend = "extend"
methodUpdate = "update"
methodUpload = "upload"
methodLog = "log"
)
type (
uploadReq 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, f Filter) (*Pipeline, error) {
res := new(Pipeline)
err := t.call(c, methodNext, f, res)
return res, err
}
// Wait blocks until the pipeline is complete.
func (t *Client) Wait(c context.Context, id string) error {
// err := t.call(c, methodWait, id, nil)
// if err != nil && err.Error() == ErrCancelled.Error() {
// return ErrCancelled
// }
return t.call(c, methodWait, id, nil)
}
// Done signals the pipeline is complete.
func (t *Client) Done(c context.Context, id string) error {
return t.call(c, methodDone, id, nil)
}
// 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, &params, 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, &params, nil)
}
// Upload uploads the pipeline artifact.
func (t *Client) Upload(c context.Context, id, mime string, file io.Reader) error {
data, err := ioutil.ReadAll(file)
if err != nil {
return err
}
params := uploadReq{id, mime, data}
return t.call(c, methodUpload, 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
}

95
vendor/github.com/cncd/pipeline/pipeline/rpc/line.go generated vendored Normal file
View file

@ -0,0 +1,95 @@
package rpc
import (
"context"
"fmt"
"strings"
"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
rep *strings.Replacer
}
// NewLineWriter returns a new line reader.
func NewLineWriter(peer Peer, id, name string, secret ...string) *LineWriter {
w := new(LineWriter)
w.peer = peer
w.id = id
w.name = name
w.num = 0
w.now = time.Now().UTC()
var oldnew []string
for _, old := range secret {
oldnew = append(oldnew, old)
oldnew = append(oldnew, "********")
}
if len(oldnew) != 0 {
w.rep = strings.NewReplacer(oldnew...)
}
return w
}
func (w *LineWriter) Write(p []byte) (n int, err error) {
out := string(p)
if w.rep != nil {
out = w.rep.Replace(out)
}
line := &Line{
Out: out,
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++
// 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
View 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
}
}

63
vendor/github.com/cncd/pipeline/pipeline/rpc/peer.go generated vendored Normal file
View file

@ -0,0 +1,63 @@
package rpc
import (
"context"
"io"
"github.com/cncd/pipeline/pipeline/backend"
)
// ErrCancelled signals the pipeine is cancelled.
// var ErrCancelled = errors.New("cancelled")
type (
// Filter defines filters for fetching items from the queue.
Filter struct {
Labels map[string]string `json:"labels"`
Expr string `json:"expr"`
}
// 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"`
}
)
// NoFilter is an empty filter.
var NoFilter = Filter{}
// Peer defines a peer-to-peer connection.
type Peer interface {
// Next returns the next pipeline in the queue.
Next(c context.Context, f Filter) (*Pipeline, error)
// Wait blocks untilthe pipeline is complete.
Wait(c context.Context, id string) error
// Done signals the pipeline is complete.
Done(c context.Context, id string) 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
// Upload uploads the pipeline artifact.
Upload(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
}

141
vendor/github.com/cncd/pipeline/pipeline/rpc/server.go generated vendored Normal file
View file

@ -0,0 +1,141 @@
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 methodWait:
return s.wait(ctx, req)
case methodDone:
return s.done(ctx, req)
case methodExtend:
return s.extend(ctx, req)
case methodUpdate:
return s.update(req)
case methodLog:
return s.log(req)
case methodUpload:
return s.upload(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) {
in := Filter{}
if err := json.Unmarshal([]byte(*req.Params), &in); err != nil {
return nil, err
}
return s.peer.Next(ctx, in)
}
// wait unmarshals the rpc request parameters and invokes the peer.Wait
// procedure. The results are retuned and written to the rpc response.
func (s *Server) wait(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.Wait(ctx, id)
}
// done unmarshals the rpc request parameters and invokes the peer.Done
// procedure. The results are retuned and written to the rpc response.
func (s *Server) done(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.Done(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)
}
func (s *Server) upload(req *jsonrpc2.Request) (interface{}, error) {
in := new(uploadReq)
if err := json.Unmarshal([]byte(*req.Params), in); err != nil {
return nil, err
}
return nil, s.peer.Upload(noContext, in.ID, in.Mime, bytes.NewBuffer(in.Data))
}

45
vendor/github.com/cncd/pipeline/pipeline/tracer.go generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1 @@
package queue

202
vendor/github.com/docker/distribution/LICENSE generated vendored Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,12 @@
package reference
// IsNameOnly returns true if reference only contains a repo name.
func IsNameOnly(ref Named) bool {
if _, ok := ref.(NamedTagged); ok {
return false
}
if _, ok := ref.(Canonical); ok {
return false
}
return true
}

View file

@ -0,0 +1,22 @@
package reference
var (
defaultTag = "latest"
)
// EnsureTagged adds the default tag "latest" to a reference if it only has
// a repo name.
func EnsureTagged(ref Named) NamedTagged {
namedTagged, ok := ref.(NamedTagged)
if !ok {
namedTagged, err := WithTag(ref, defaultTag)
if err != nil {
// Default tag must be valid, to create a NamedTagged
// type with non-validated input the WithTag function
// should be used instead
panic(err)
}
return namedTagged
}
return namedTagged
}

View file

@ -0,0 +1,370 @@
// Package reference provides a general type to represent any way of referencing images within the registry.
// Its main purpose is to abstract tags and digests (content-addressable hash).
//
// Grammar
//
// reference := name [ ":" tag ] [ "@" digest ]
// name := [hostname '/'] component ['/' component]*
// hostname := hostcomponent ['.' hostcomponent]* [':' port-number]
// hostcomponent := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
// port-number := /[0-9]+/
// component := alpha-numeric [separator alpha-numeric]*
// alpha-numeric := /[a-z0-9]+/
// separator := /[_.]|__|[-]*/
//
// tag := /[\w][\w.-]{0,127}/
//
// digest := digest-algorithm ":" digest-hex
// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]
// digest-algorithm-separator := /[+.-_]/
// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/
// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
package reference
import (
"errors"
"fmt"
"path"
"strings"
"github.com/opencontainers/go-digest"
)
const (
// NameTotalLengthMax is the maximum total number of characters in a repository name.
NameTotalLengthMax = 255
)
var (
// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
ErrReferenceInvalidFormat = errors.New("invalid reference format")
// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
ErrTagInvalidFormat = errors.New("invalid tag format")
// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
ErrDigestInvalidFormat = errors.New("invalid digest format")
// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
ErrNameContainsUppercase = errors.New("repository name must be lowercase")
// ErrNameEmpty is returned for empty, invalid repository names.
ErrNameEmpty = errors.New("repository name must have at least one component")
// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
)
// Reference is an opaque object reference identifier that may include
// modifiers such as a hostname, name, tag, and digest.
type Reference interface {
// String returns the full reference
String() string
}
// Field provides a wrapper type for resolving correct reference types when
// working with encoding.
type Field struct {
reference Reference
}
// AsField wraps a reference in a Field for encoding.
func AsField(reference Reference) Field {
return Field{reference}
}
// Reference unwraps the reference type from the field to
// return the Reference object. This object should be
// of the appropriate type to further check for different
// reference types.
func (f Field) Reference() Reference {
return f.reference
}
// MarshalText serializes the field to byte text which
// is the string of the reference.
func (f Field) MarshalText() (p []byte, err error) {
return []byte(f.reference.String()), nil
}
// UnmarshalText parses text bytes by invoking the
// reference parser to ensure the appropriately
// typed reference object is wrapped by field.
func (f *Field) UnmarshalText(p []byte) error {
r, err := Parse(string(p))
if err != nil {
return err
}
f.reference = r
return nil
}
// Named is an object with a full name
type Named interface {
Reference
Name() string
}
// Tagged is an object which has a tag
type Tagged interface {
Reference
Tag() string
}
// NamedTagged is an object including a name and tag.
type NamedTagged interface {
Named
Tag() string
}
// Digested is an object which has a digest
// in which it can be referenced by
type Digested interface {
Reference
Digest() digest.Digest
}
// Canonical reference is an object with a fully unique
// name including a name with hostname and digest
type Canonical interface {
Named
Digest() digest.Digest
}
// SplitHostname splits a named reference into a
// hostname and name string. If no valid hostname is
// found, the hostname is empty and the full value
// is returned as name
func SplitHostname(named Named) (string, string) {
name := named.Name()
match := anchoredNameRegexp.FindStringSubmatch(name)
if len(match) != 3 {
return "", name
}
return match[1], match[2]
}
// Parse parses s and returns a syntactically valid Reference.
// If an error was encountered it is returned, along with a nil Reference.
// NOTE: Parse will not handle short digests.
func Parse(s string) (Reference, error) {
matches := ReferenceRegexp.FindStringSubmatch(s)
if matches == nil {
if s == "" {
return nil, ErrNameEmpty
}
if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil {
return nil, ErrNameContainsUppercase
}
return nil, ErrReferenceInvalidFormat
}
if len(matches[1]) > NameTotalLengthMax {
return nil, ErrNameTooLong
}
ref := reference{
name: matches[1],
tag: matches[2],
}
if matches[3] != "" {
var err error
ref.digest, err = digest.Parse(matches[3])
if err != nil {
return nil, err
}
}
r := getBestReferenceType(ref)
if r == nil {
return nil, ErrNameEmpty
}
return r, nil
}
// ParseNamed parses s and returns a syntactically valid reference implementing
// the Named interface. The reference must have a name, otherwise an error is
// returned.
// If an error was encountered it is returned, along with a nil Reference.
// NOTE: ParseNamed will not handle short digests.
func ParseNamed(s string) (Named, error) {
ref, err := Parse(s)
if err != nil {
return nil, err
}
named, isNamed := ref.(Named)
if !isNamed {
return nil, fmt.Errorf("reference %s has no name", ref.String())
}
return named, nil
}
// WithName returns a named object representing the given string. If the input
// is invalid ErrReferenceInvalidFormat will be returned.
func WithName(name string) (Named, error) {
if len(name) > NameTotalLengthMax {
return nil, ErrNameTooLong
}
if !anchoredNameRegexp.MatchString(name) {
return nil, ErrReferenceInvalidFormat
}
return repository(name), nil
}
// WithTag combines the name from "name" and the tag from "tag" to form a
// reference incorporating both the name and the tag.
func WithTag(name Named, tag string) (NamedTagged, error) {
if !anchoredTagRegexp.MatchString(tag) {
return nil, ErrTagInvalidFormat
}
if canonical, ok := name.(Canonical); ok {
return reference{
name: name.Name(),
tag: tag,
digest: canonical.Digest(),
}, nil
}
return taggedReference{
name: name.Name(),
tag: tag,
}, nil
}
// WithDigest combines the name from "name" and the digest from "digest" to form
// a reference incorporating both the name and the digest.
func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
if !anchoredDigestRegexp.MatchString(digest.String()) {
return nil, ErrDigestInvalidFormat
}
if tagged, ok := name.(Tagged); ok {
return reference{
name: name.Name(),
tag: tagged.Tag(),
digest: digest,
}, nil
}
return canonicalReference{
name: name.Name(),
digest: digest,
}, nil
}
// Match reports whether ref matches the specified pattern.
// See https://godoc.org/path#Match for supported patterns.
func Match(pattern string, ref Reference) (bool, error) {
matched, err := path.Match(pattern, ref.String())
if namedRef, isNamed := ref.(Named); isNamed && !matched {
matched, _ = path.Match(pattern, namedRef.Name())
}
return matched, err
}
// TrimNamed removes any tag or digest from the named reference.
func TrimNamed(ref Named) Named {
return repository(ref.Name())
}
func getBestReferenceType(ref reference) Reference {
if ref.name == "" {
// Allow digest only references
if ref.digest != "" {
return digestReference(ref.digest)
}
return nil
}
if ref.tag == "" {
if ref.digest != "" {
return canonicalReference{
name: ref.name,
digest: ref.digest,
}
}
return repository(ref.name)
}
if ref.digest == "" {
return taggedReference{
name: ref.name,
tag: ref.tag,
}
}
return ref
}
type reference struct {
name string
tag string
digest digest.Digest
}
func (r reference) String() string {
return r.name + ":" + r.tag + "@" + r.digest.String()
}
func (r reference) Name() string {
return r.name
}
func (r reference) Tag() string {
return r.tag
}
func (r reference) Digest() digest.Digest {
return r.digest
}
type repository string
func (r repository) String() string {
return string(r)
}
func (r repository) Name() string {
return string(r)
}
type digestReference digest.Digest
func (d digestReference) String() string {
return d.String()
}
func (d digestReference) Digest() digest.Digest {
return digest.Digest(d)
}
type taggedReference struct {
name string
tag string
}
func (t taggedReference) String() string {
return t.name + ":" + t.tag
}
func (t taggedReference) Name() string {
return t.name
}
func (t taggedReference) Tag() string {
return t.tag
}
type canonicalReference struct {
name string
digest digest.Digest
}
func (c canonicalReference) String() string {
return c.name + "@" + c.digest.String()
}
func (c canonicalReference) Name() string {
return c.name
}
func (c canonicalReference) Digest() digest.Digest {
return c.digest
}

View file

@ -0,0 +1,124 @@
package reference
import "regexp"
var (
// alphaNumericRegexp defines the alpha numeric atom, typically a
// component of names. This only allows lower case characters and digits.
alphaNumericRegexp = match(`[a-z0-9]+`)
// separatorRegexp defines the separators allowed to be embedded in name
// components. This allow one period, one or two underscore and multiple
// dashes.
separatorRegexp = match(`(?:[._]|__|[-]*)`)
// nameComponentRegexp restricts registry path component names to start
// with at least one letter or number, with following parts able to be
// separated by one period, one or two underscore and multiple dashes.
nameComponentRegexp = expression(
alphaNumericRegexp,
optional(repeated(separatorRegexp, alphaNumericRegexp)))
// hostnameComponentRegexp restricts the registry hostname component of a
// repository name to start with a component as defined by hostnameRegexp
// and followed by an optional port.
hostnameComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
// hostnameRegexp defines the structure of potential hostname components
// that may be part of image names. This is purposely a subset of what is
// allowed by DNS to ensure backwards compatibility with Docker image
// names.
hostnameRegexp = expression(
hostnameComponentRegexp,
optional(repeated(literal(`.`), hostnameComponentRegexp)),
optional(literal(`:`), match(`[0-9]+`)))
// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
TagRegexp = match(`[\w][\w.-]{0,127}`)
// anchoredTagRegexp matches valid tag names, anchored at the start and
// end of the matched string.
anchoredTagRegexp = anchored(TagRegexp)
// DigestRegexp matches valid digests.
DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
// anchoredDigestRegexp matches valid digests, anchored at the start and
// end of the matched string.
anchoredDigestRegexp = anchored(DigestRegexp)
// NameRegexp is the format for the name component of references. The
// regexp has capturing groups for the hostname and name part omitting
// the separating forward slash from either.
NameRegexp = expression(
optional(hostnameRegexp, literal(`/`)),
nameComponentRegexp,
optional(repeated(literal(`/`), nameComponentRegexp)))
// anchoredNameRegexp is used to parse a name value, capturing the
// hostname and trailing components.
anchoredNameRegexp = anchored(
optional(capture(hostnameRegexp), literal(`/`)),
capture(nameComponentRegexp,
optional(repeated(literal(`/`), nameComponentRegexp))))
// ReferenceRegexp is the full supported format of a reference. The regexp
// is anchored and has capturing groups for name, tag, and digest
// components.
ReferenceRegexp = anchored(capture(NameRegexp),
optional(literal(":"), capture(TagRegexp)),
optional(literal("@"), capture(DigestRegexp)))
)
// match compiles the string to a regular expression.
var match = regexp.MustCompile
// literal compiles s into a literal regular expression, escaping any regexp
// reserved characters.
func literal(s string) *regexp.Regexp {
re := match(regexp.QuoteMeta(s))
if _, complete := re.LiteralPrefix(); !complete {
panic("must be a literal")
}
return re
}
// expression defines a full expression, where each regular expression must
// follow the previous.
func expression(res ...*regexp.Regexp) *regexp.Regexp {
var s string
for _, re := range res {
s += re.String()
}
return match(s)
}
// optional wraps the expression in a non-capturing group and makes the
// production optional.
func optional(res ...*regexp.Regexp) *regexp.Regexp {
return match(group(expression(res...)).String() + `?`)
}
// repeated wraps the regexp in a non-capturing group to get one or more
// matches.
func repeated(res ...*regexp.Regexp) *regexp.Regexp {
return match(group(expression(res...)).String() + `+`)
}
// group wraps the regexp in a non-capturing group.
func group(res ...*regexp.Regexp) *regexp.Regexp {
return match(`(?:` + expression(res...).String() + `)`)
}
// capture wraps the expression in a capturing group.
func capture(res ...*regexp.Regexp) *regexp.Regexp {
return match(`(` + expression(res...).String() + `)`)
}
// anchored anchors the regular expression by adding start and end delimiters.
func anchored(res ...*regexp.Regexp) *regexp.Regexp {
return match(`^` + expression(res...).String() + `$`)
}

View file

@ -176,7 +176,7 @@
END OF TERMS AND CONDITIONS
Copyright 2013-2015 Docker, Inc.
Copyright 2013-2016 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View file

@ -1,5 +1,5 @@
Docker
Copyright 2012-2015 Docker, Inc.
Copyright 2012-2016 Docker, Inc.
This product includes software developed at Docker, Inc. (https://www.docker.com).

22
vendor/github.com/docker/docker/api/types/auth.go generated vendored Normal file
View file

@ -0,0 +1,22 @@
package types
// AuthConfig contains authorization information for connecting to a Registry
type AuthConfig struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Auth string `json:"auth,omitempty"`
// Email is an optional value associated with the username.
// This field is deprecated and will be removed in a later
// version of docker.
Email string `json:"email,omitempty"`
ServerAddress string `json:"serveraddress,omitempty"`
// IdentityToken is used to authenticate the user and get
// an access token for the registry.
IdentityToken string `json:"identitytoken,omitempty"`
// RegistryToken is a bearer token to be sent to a registry
RegistryToken string `json:"registrytoken,omitempty"`
}

View file

@ -0,0 +1,23 @@
package blkiodev
import "fmt"
// WeightDevice is a structure that holds device:weight pair
type WeightDevice struct {
Path string
Weight uint16
}
func (w *WeightDevice) String() string {
return fmt.Sprintf("%s:%d", w.Path, w.Weight)
}
// ThrottleDevice is a structure that holds device:rate_per_second pair
type ThrottleDevice struct {
Path string
Rate uint64
}
func (t *ThrottleDevice) String() string {
return fmt.Sprintf("%s:%d", t.Path, t.Rate)
}

378
vendor/github.com/docker/docker/api/types/client.go generated vendored Normal file
View file

@ -0,0 +1,378 @@
package types
import (
"bufio"
"io"
"net"
"os"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/go-units"
)
// CheckpointCreateOptions holds parameters to create a checkpoint from a container
type CheckpointCreateOptions struct {
CheckpointID string
CheckpointDir string
Exit bool
}
// CheckpointListOptions holds parameters to list checkpoints for a container
type CheckpointListOptions struct {
CheckpointDir string
}
// CheckpointDeleteOptions holds parameters to delete a checkpoint from a container
type CheckpointDeleteOptions struct {
CheckpointID string
CheckpointDir string
}
// ContainerAttachOptions holds parameters to attach to a container.
type ContainerAttachOptions struct {
Stream bool
Stdin bool
Stdout bool
Stderr bool
DetachKeys string
Logs bool
}
// ContainerCommitOptions holds parameters to commit changes into a container.
type ContainerCommitOptions struct {
Reference string
Comment string
Author string
Changes []string
Pause bool
Config *container.Config
}
// ContainerExecInspect holds information returned by exec inspect.
type ContainerExecInspect struct {
ExecID string
ContainerID string
Running bool
ExitCode int
Pid int
}
// ContainerListOptions holds parameters to list containers with.
type ContainerListOptions struct {
Quiet bool
Size bool
All bool
Latest bool
Since string
Before string
Limit int
Filters filters.Args
}
// ContainerLogsOptions holds parameters to filter logs with.
type ContainerLogsOptions struct {
ShowStdout bool
ShowStderr bool
Since string
Timestamps bool
Follow bool
Tail string
Details bool
}
// ContainerRemoveOptions holds parameters to remove containers.
type ContainerRemoveOptions struct {
RemoveVolumes bool
RemoveLinks bool
Force bool
}
// ContainerStartOptions holds parameters to start containers.
type ContainerStartOptions struct {
CheckpointID string
CheckpointDir string
}
// CopyToContainerOptions holds information
// about files to copy into a container
type CopyToContainerOptions struct {
AllowOverwriteDirWithFile bool
}
// EventsOptions holds parameters to filter events with.
type EventsOptions struct {
Since string
Until string
Filters filters.Args
}
// NetworkListOptions holds parameters to filter the list of networks with.
type NetworkListOptions struct {
Filters filters.Args
}
// HijackedResponse holds connection information for a hijacked request.
type HijackedResponse struct {
Conn net.Conn
Reader *bufio.Reader
}
// Close closes the hijacked connection and reader.
func (h *HijackedResponse) Close() {
h.Conn.Close()
}
// CloseWriter is an interface that implements structs
// that close input streams to prevent from writing.
type CloseWriter interface {
CloseWrite() error
}
// CloseWrite closes a readWriter for writing.
func (h *HijackedResponse) CloseWrite() error {
if conn, ok := h.Conn.(CloseWriter); ok {
return conn.CloseWrite()
}
return nil
}
// ImageBuildOptions holds the information
// necessary to build images.
type ImageBuildOptions struct {
Tags []string
SuppressOutput bool
RemoteContext string
NoCache bool
Remove bool
ForceRemove bool
PullParent bool
Isolation container.Isolation
CPUSetCPUs string
CPUSetMems string
CPUShares int64
CPUQuota int64
CPUPeriod int64
Memory int64
MemorySwap int64
CgroupParent string
NetworkMode string
ShmSize int64
Dockerfile string
Ulimits []*units.Ulimit
// See the parsing of buildArgs in api/server/router/build/build_routes.go
// for an explanation of why BuildArgs needs to use *string instead of
// just a string
BuildArgs map[string]*string
AuthConfigs map[string]AuthConfig
Context io.Reader
Labels map[string]string
// squash the resulting image's layers to the parent
// preserves the original image and creates a new one from the parent with all
// the changes applied to a single layer
Squash bool
// CacheFrom specifies images that are used for matching cache. Images
// specified here do not need to have a valid parent chain to match cache.
CacheFrom []string
SecurityOpt []string
}
// ImageBuildResponse holds information
// returned by a server after building
// an image.
type ImageBuildResponse struct {
Body io.ReadCloser
OSType string
}
// ImageCreateOptions holds information to create images.
type ImageCreateOptions struct {
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
}
// ImageImportSource holds source information for ImageImport
type ImageImportSource struct {
Source io.Reader // Source is the data to send to the server to create this image from. You must set SourceName to "-" to leverage this.
SourceName string // SourceName is the name of the image to pull. Set to "-" to leverage the Source attribute.
}
// ImageImportOptions holds information to import images from the client host.
type ImageImportOptions struct {
Tag string // Tag is the name to tag this image with. This attribute is deprecated.
Message string // Message is the message to tag the image with
Changes []string // Changes are the raw changes to apply to this image
}
// ImageListOptions holds parameters to filter the list of images with.
type ImageListOptions struct {
All bool
Filters filters.Args
}
// ImageLoadResponse returns information to the client about a load process.
type ImageLoadResponse struct {
// Body must be closed to avoid a resource leak
Body io.ReadCloser
JSON bool
}
// ImagePullOptions holds information to pull images.
type ImagePullOptions struct {
All bool
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
PrivilegeFunc RequestPrivilegeFunc
}
// RequestPrivilegeFunc is a function interface that
// clients can supply to retry operations after
// getting an authorization error.
// This function returns the registry authentication
// header value in base 64 format, or an error
// if the privilege request fails.
type RequestPrivilegeFunc func() (string, error)
//ImagePushOptions holds information to push images.
type ImagePushOptions ImagePullOptions
// ImageRemoveOptions holds parameters to remove images.
type ImageRemoveOptions struct {
Force bool
PruneChildren bool
}
// ImageSearchOptions holds parameters to search images with.
type ImageSearchOptions struct {
RegistryAuth string
PrivilegeFunc RequestPrivilegeFunc
Filters filters.Args
Limit int
}
// ResizeOptions holds parameters to resize a tty.
// It can be used to resize container ttys and
// exec process ttys too.
type ResizeOptions struct {
Height uint
Width uint
}
// VersionResponse holds version information for the client and the server
type VersionResponse struct {
Client *Version
Server *Version
}
// ServerOK returns true when the client could connect to the docker server
// and parse the information received. It returns false otherwise.
func (v VersionResponse) ServerOK() bool {
return v.Server != nil
}
// NodeListOptions holds parameters to list nodes with.
type NodeListOptions struct {
Filters filters.Args
}
// NodeRemoveOptions holds parameters to remove nodes with.
type NodeRemoveOptions struct {
Force bool
}
// ServiceCreateOptions contains the options to use when creating a service.
type ServiceCreateOptions struct {
// EncodedRegistryAuth is the encoded registry authorization credentials to
// use when updating the service.
//
// This field follows the format of the X-Registry-Auth header.
EncodedRegistryAuth string
}
// ServiceCreateResponse contains the information returned to a client
// on the creation of a new service.
type ServiceCreateResponse struct {
// ID is the ID of the created service.
ID string
// Warnings is a set of non-fatal warning messages to pass on to the user.
Warnings []string `json:",omitempty"`
}
// Values for RegistryAuthFrom in ServiceUpdateOptions
const (
RegistryAuthFromSpec = "spec"
RegistryAuthFromPreviousSpec = "previous-spec"
)
// ServiceUpdateOptions contains the options to be used for updating services.
type ServiceUpdateOptions struct {
// EncodedRegistryAuth is the encoded registry authorization credentials to
// use when updating the service.
//
// This field follows the format of the X-Registry-Auth header.
EncodedRegistryAuth string
// TODO(stevvooe): Consider moving the version parameter of ServiceUpdate
// into this field. While it does open API users up to racy writes, most
// users may not need that level of consistency in practice.
// RegistryAuthFrom specifies where to find the registry authorization
// credentials if they are not given in EncodedRegistryAuth. Valid
// values are "spec" and "previous-spec".
RegistryAuthFrom string
}
// ServiceListOptions holds parameters to list services with.
type ServiceListOptions struct {
Filters filters.Args
}
// TaskListOptions holds parameters to list tasks with.
type TaskListOptions struct {
Filters filters.Args
}
// PluginRemoveOptions holds parameters to remove plugins.
type PluginRemoveOptions struct {
Force bool
}
// PluginEnableOptions holds parameters to enable plugins.
type PluginEnableOptions struct {
Timeout int
}
// PluginDisableOptions holds parameters to disable plugins.
type PluginDisableOptions struct {
Force bool
}
// PluginInstallOptions holds parameters to install a plugin.
type PluginInstallOptions struct {
Disabled bool
AcceptAllPermissions bool
RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry
RemoteRef string // RemoteRef is the plugin name on the registry
PrivilegeFunc RequestPrivilegeFunc
AcceptPermissionsFunc func(PluginPrivileges) (bool, error)
Args []string
}
// SecretRequestOption is a type for requesting secrets
type SecretRequestOption struct {
Source string
Target string
UID string
GID string
Mode os.FileMode
}
// SwarmUnlockKeyResponse contains the response for Engine API:
// GET /swarm/unlockkey
type SwarmUnlockKeyResponse struct {
// UnlockKey is the unlock key in ASCII-armored format.
UnlockKey string
}
// PluginCreateOptions hold all options to plugin create.
type PluginCreateOptions struct {
RepoName string
}

69
vendor/github.com/docker/docker/api/types/configs.go generated vendored Normal file
View file

@ -0,0 +1,69 @@
package types
import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
)
// configs holds structs used for internal communication between the
// frontend (such as an http server) and the backend (such as the
// docker daemon).
// ContainerCreateConfig is the parameter set to ContainerCreate()
type ContainerCreateConfig struct {
Name string
Config *container.Config
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
AdjustCPUShares bool
}
// ContainerRmConfig holds arguments for the container remove
// operation. This struct is used to tell the backend what operations
// to perform.
type ContainerRmConfig struct {
ForceRemove, RemoveVolume, RemoveLink bool
}
// ContainerCommitConfig contains build configs for commit operation,
// and is used when making a commit with the current state of the container.
type ContainerCommitConfig struct {
Pause bool
Repo string
Tag string
Author string
Comment string
// merge container config into commit config before commit
MergeConfigs bool
Config *container.Config
}
// ExecConfig is a small subset of the Config struct that holds the configuration
// for the exec feature of docker.
type ExecConfig struct {
User string // User that will run the command
Privileged bool // Is the container in privileged mode
Tty bool // Attach standard streams to a tty.
AttachStdin bool // Attach the standard input, makes possible user interaction
AttachStderr bool // Attach the standard error
AttachStdout bool // Attach the standard output
Detach bool // Execute in detach mode
DetachKeys string // Escape keys for detach
Env []string // Environment variables
Cmd []string // Execution commands and args
}
// PluginRmConfig holds arguments for plugin remove.
type PluginRmConfig struct {
ForceRemove bool
}
// PluginEnableConfig holds arguments for plugin enable
type PluginEnableConfig struct {
Timeout int
}
// PluginDisableConfig holds arguments for plugin disable.
type PluginDisableConfig struct {
ForceDisable bool
}

View file

@ -0,0 +1,62 @@
package container
import (
"time"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/go-connections/nat"
)
// HealthConfig holds configuration settings for the HEALTHCHECK feature.
type HealthConfig struct {
// Test is the test to perform to check that the container is healthy.
// An empty slice means to inherit the default.
// The options are:
// {} : inherit healthcheck
// {"NONE"} : disable healthcheck
// {"CMD", args...} : exec arguments directly
// {"CMD-SHELL", command} : run command with system's default shell
Test []string `json:",omitempty"`
// Zero means to inherit. Durations are expressed as integer nanoseconds.
Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks.
Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung.
// Retries is the number of consecutive failures needed to consider a container as unhealthy.
// Zero means inherit.
Retries int `json:",omitempty"`
}
// Config contains the configuration data about a container.
// It should hold only portable information about the container.
// Here, "portable" means "independent from the host we are running on".
// Non-portable information *should* appear in HostConfig.
// All fields added to this struct must be marked `omitempty` to keep getting
// predictable hashes from the old `v1Compatibility` configuration.
type Config struct {
Hostname string // Hostname
Domainname string // Domainname
User string // User that will run the command(s) inside the container, also support user:group
AttachStdin bool // Attach the standard input, makes possible user interaction
AttachStdout bool // Attach the standard output
AttachStderr bool // Attach the standard error
ExposedPorts nat.PortSet `json:",omitempty"` // List of exposed ports
Tty bool // Attach standard streams to a tty, including stdin if it is not closed.
OpenStdin bool // Open stdin
StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
Env []string // List of environment variable to set in the container
Cmd strslice.StrSlice // Command to run when starting the container
Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy
ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (Windows specific)
Image string // Name of the image as it was passed by the operator (e.g. could be symbolic)
Volumes map[string]struct{} // List of volumes (mounts) used for the container
WorkingDir string // Current directory (PWD) in the command will be launched
Entrypoint strslice.StrSlice // Entrypoint to run when starting the container
NetworkDisabled bool `json:",omitempty"` // Is network disabled
MacAddress string `json:",omitempty"` // Mac Address of the container
OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile
Labels map[string]string // List of labels set to this container
StopSignal string `json:",omitempty"` // Signal to stop a container
StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container
Shell strslice.StrSlice `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT
}

View file

@ -0,0 +1,21 @@
package container
// ----------------------------------------------------------------------------
// DO NOT EDIT THIS FILE
// This file was generated by `swagger generate operation`
//
// See hack/generate-swagger-api.sh
// ----------------------------------------------------------------------------
// ContainerCreateCreatedBody container create created body
// swagger:model ContainerCreateCreatedBody
type ContainerCreateCreatedBody struct {
// The ID of the created container
// Required: true
ID string `json:"Id"`
// Warnings encountered when creating the container
// Required: true
Warnings []string `json:"Warnings"`
}

View file

@ -0,0 +1,17 @@
package container
// ----------------------------------------------------------------------------
// DO NOT EDIT THIS FILE
// This file was generated by `swagger generate operation`
//
// See hack/generate-swagger-api.sh
// ----------------------------------------------------------------------------
// ContainerUpdateOKBody container update o k body
// swagger:model ContainerUpdateOKBody
type ContainerUpdateOKBody struct {
// warnings
// Required: true
Warnings []string `json:"Warnings"`
}

View file

@ -0,0 +1,17 @@
package container
// ----------------------------------------------------------------------------
// DO NOT EDIT THIS FILE
// This file was generated by `swagger generate operation`
//
// See hack/generate-swagger-api.sh
// ----------------------------------------------------------------------------
// ContainerWaitOKBody container wait o k body
// swagger:model ContainerWaitOKBody
type ContainerWaitOKBody struct {
// Exit code of the container
// Required: true
StatusCode int64 `json:"StatusCode"`
}

View file

@ -0,0 +1,333 @@
package container
import (
"strings"
"github.com/docker/docker/api/types/blkiodev"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
)
// NetworkMode represents the container network stack.
type NetworkMode string
// Isolation represents the isolation technology of a container. The supported
// values are platform specific
type Isolation string
// IsDefault indicates the default isolation technology of a container. On Linux this
// is the native driver. On Windows, this is a Windows Server Container.
func (i Isolation) IsDefault() bool {
return strings.ToLower(string(i)) == "default" || string(i) == ""
}
// IpcMode represents the container ipc stack.
type IpcMode string
// IsPrivate indicates whether the container uses its private ipc stack.
func (n IpcMode) IsPrivate() bool {
return !(n.IsHost() || n.IsContainer())
}
// IsHost indicates whether the container uses the host's ipc stack.
func (n IpcMode) IsHost() bool {
return n == "host"
}
// IsContainer indicates whether the container uses a container's ipc stack.
func (n IpcMode) IsContainer() bool {
parts := strings.SplitN(string(n), ":", 2)
return len(parts) > 1 && parts[0] == "container"
}
// Valid indicates whether the ipc stack is valid.
func (n IpcMode) Valid() bool {
parts := strings.Split(string(n), ":")
switch mode := parts[0]; mode {
case "", "host":
case "container":
if len(parts) != 2 || parts[1] == "" {
return false
}
default:
return false
}
return true
}
// Container returns the name of the container ipc stack is going to be used.
func (n IpcMode) Container() string {
parts := strings.SplitN(string(n), ":", 2)
if len(parts) > 1 {
return parts[1]
}
return ""
}
// UsernsMode represents userns mode in the container.
type UsernsMode string
// IsHost indicates whether the container uses the host's userns.
func (n UsernsMode) IsHost() bool {
return n == "host"
}
// IsPrivate indicates whether the container uses the a private userns.
func (n UsernsMode) IsPrivate() bool {
return !(n.IsHost())
}
// Valid indicates whether the userns is valid.
func (n UsernsMode) Valid() bool {
parts := strings.Split(string(n), ":")
switch mode := parts[0]; mode {
case "", "host":
default:
return false
}
return true
}
// CgroupSpec represents the cgroup to use for the container.
type CgroupSpec string
// IsContainer indicates whether the container is using another container cgroup
func (c CgroupSpec) IsContainer() bool {
parts := strings.SplitN(string(c), ":", 2)
return len(parts) > 1 && parts[0] == "container"
}
// Valid indicates whether the cgroup spec is valid.
func (c CgroupSpec) Valid() bool {
return c.IsContainer() || c == ""
}
// Container returns the name of the container whose cgroup will be used.
func (c CgroupSpec) Container() string {
parts := strings.SplitN(string(c), ":", 2)
if len(parts) > 1 {
return parts[1]
}
return ""
}
// UTSMode represents the UTS namespace of the container.
type UTSMode string
// IsPrivate indicates whether the container uses its private UTS namespace.
func (n UTSMode) IsPrivate() bool {
return !(n.IsHost())
}
// IsHost indicates whether the container uses the host's UTS namespace.
func (n UTSMode) IsHost() bool {
return n == "host"
}
// Valid indicates whether the UTS namespace is valid.
func (n UTSMode) Valid() bool {
parts := strings.Split(string(n), ":")
switch mode := parts[0]; mode {
case "", "host":
default:
return false
}
return true
}
// PidMode represents the pid namespace of the container.
type PidMode string
// IsPrivate indicates whether the container uses its own new pid namespace.
func (n PidMode) IsPrivate() bool {
return !(n.IsHost() || n.IsContainer())
}
// IsHost indicates whether the container uses the host's pid namespace.
func (n PidMode) IsHost() bool {
return n == "host"
}
// IsContainer indicates whether the container uses a container's pid namespace.
func (n PidMode) IsContainer() bool {
parts := strings.SplitN(string(n), ":", 2)
return len(parts) > 1 && parts[0] == "container"
}
// Valid indicates whether the pid namespace is valid.
func (n PidMode) Valid() bool {
parts := strings.Split(string(n), ":")
switch mode := parts[0]; mode {
case "", "host":
case "container":
if len(parts) != 2 || parts[1] == "" {
return false
}
default:
return false
}
return true
}
// Container returns the name of the container whose pid namespace is going to be used.
func (n PidMode) Container() string {
parts := strings.SplitN(string(n), ":", 2)
if len(parts) > 1 {
return parts[1]
}
return ""
}
// DeviceMapping represents the device mapping between the host and the container.
type DeviceMapping struct {
PathOnHost string
PathInContainer string
CgroupPermissions string
}
// RestartPolicy represents the restart policies of the container.
type RestartPolicy struct {
Name string
MaximumRetryCount int
}
// IsNone indicates whether the container has the "no" restart policy.
// This means the container will not automatically restart when exiting.
func (rp *RestartPolicy) IsNone() bool {
return rp.Name == "no" || rp.Name == ""
}
// IsAlways indicates whether the container has the "always" restart policy.
// This means the container will automatically restart regardless of the exit status.
func (rp *RestartPolicy) IsAlways() bool {
return rp.Name == "always"
}
// IsOnFailure indicates whether the container has the "on-failure" restart policy.
// This means the container will automatically restart of exiting with a non-zero exit status.
func (rp *RestartPolicy) IsOnFailure() bool {
return rp.Name == "on-failure"
}
// IsUnlessStopped indicates whether the container has the
// "unless-stopped" restart policy. This means the container will
// automatically restart unless user has put it to stopped state.
func (rp *RestartPolicy) IsUnlessStopped() bool {
return rp.Name == "unless-stopped"
}
// IsSame compares two RestartPolicy to see if they are the same
func (rp *RestartPolicy) IsSame(tp *RestartPolicy) bool {
return rp.Name == tp.Name && rp.MaximumRetryCount == tp.MaximumRetryCount
}
// LogConfig represents the logging configuration of the container.
type LogConfig struct {
Type string
Config map[string]string
}
// Resources contains container's resources (cgroups config, ulimits...)
type Resources struct {
// Applicable to all platforms
CPUShares int64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers)
Memory int64 // Memory limit (in bytes)
NanoCPUs int64 `json:"NanoCpus"` // CPU quota in units of 10<sup>-9</sup> CPUs.
// Applicable to UNIX platforms
CgroupParent string // Parent cgroup.
BlkioWeight uint16 // Block IO weight (relative weight vs. other containers)
BlkioWeightDevice []*blkiodev.WeightDevice
BlkioDeviceReadBps []*blkiodev.ThrottleDevice
BlkioDeviceWriteBps []*blkiodev.ThrottleDevice
BlkioDeviceReadIOps []*blkiodev.ThrottleDevice
BlkioDeviceWriteIOps []*blkiodev.ThrottleDevice
CPUPeriod int64 `json:"CpuPeriod"` // CPU CFS (Completely Fair Scheduler) period
CPUQuota int64 `json:"CpuQuota"` // CPU CFS (Completely Fair Scheduler) quota
CPURealtimePeriod int64 `json:"CpuRealtimePeriod"` // CPU real-time period
CPURealtimeRuntime int64 `json:"CpuRealtimeRuntime"` // CPU real-time runtime
CpusetCpus string // CpusetCpus 0-2, 0,1
CpusetMems string // CpusetMems 0-2, 0,1
Devices []DeviceMapping // List of devices to map inside the container
DiskQuota int64 // Disk limit (in bytes)
KernelMemory int64 // Kernel memory limit (in bytes)
MemoryReservation int64 // Memory soft limit (in bytes)
MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap
MemorySwappiness *int64 // Tuning container memory swappiness behaviour
OomKillDisable *bool // Whether to disable OOM Killer or not
PidsLimit int64 // Setting pids limit for a container
Ulimits []*units.Ulimit // List of ulimits to be set in the container
// Applicable to Windows
CPUCount int64 `json:"CpuCount"` // CPU count
CPUPercent int64 `json:"CpuPercent"` // CPU percent
IOMaximumIOps uint64 // Maximum IOps for the container system drive
IOMaximumBandwidth uint64 // Maximum IO in bytes per second for the container system drive
}
// UpdateConfig holds the mutable attributes of a Container.
// Those attributes can be updated at runtime.
type UpdateConfig struct {
// Contains container's resources (cgroups, ulimits)
Resources
RestartPolicy RestartPolicy
}
// HostConfig the non-portable Config structure of a container.
// Here, "non-portable" means "dependent of the host we are running on".
// Portable information *should* appear in Config.
type HostConfig struct {
// Applicable to all platforms
Binds []string // List of volume bindings for this container
ContainerIDFile string // File (path) where the containerId is written
LogConfig LogConfig // Configuration of the logs for this container
NetworkMode NetworkMode // Network mode to use for the container
PortBindings nat.PortMap // Port mapping between the exposed port (container) and the host
RestartPolicy RestartPolicy // Restart policy to be used for the container
AutoRemove bool // Automatically remove container when it exits
VolumeDriver string // Name of the volume driver used to mount volumes
VolumesFrom []string // List of volumes to take from other container
// Applicable to UNIX platforms
CapAdd strslice.StrSlice // List of kernel capabilities to add to the container
CapDrop strslice.StrSlice // List of kernel capabilities to remove from the container
DNS []string `json:"Dns"` // List of DNS server to lookup
DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for
DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for
ExtraHosts []string // List of extra hosts
GroupAdd []string // List of additional groups that the container process will run as
IpcMode IpcMode // IPC namespace to use for the container
Cgroup CgroupSpec // Cgroup to use for the container
Links []string // List of links (in the name:alias form)
OomScoreAdj int // Container preference for OOM-killing
PidMode PidMode // PID namespace to use for the container
Privileged bool // Is the container in privileged mode
PublishAllPorts bool // Should docker publish all exposed port for the container
ReadonlyRootfs bool // Is the container root filesystem in read-only
SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux.
StorageOpt map[string]string `json:",omitempty"` // Storage driver options per container.
Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container
UTSMode UTSMode // UTS namespace to use for the container
UsernsMode UsernsMode // The user namespace to use for the container
ShmSize int64 // Total shm memory usage
Sysctls map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container
Runtime string `json:",omitempty"` // Runtime to use with this container
// Applicable to Windows
ConsoleSize [2]uint // Initial console size (height,width)
Isolation Isolation // Isolation technology of the container (e.g. default, hyperv)
// Contains container's resources (cgroups, ulimits)
Resources
// Mounts specs used by the container
Mounts []mount.Mount `json:",omitempty"`
// Run a custom init inside the container, if null, use the daemon's configured settings
Init *bool `json:",omitempty"`
// Custom init path
InitPath string `json:",omitempty"`
}

View file

@ -0,0 +1,81 @@
// +build !windows
package container
import "strings"
// IsValid indicates if an isolation technology is valid
func (i Isolation) IsValid() bool {
return i.IsDefault()
}
// IsPrivate indicates whether container uses its private network stack.
func (n NetworkMode) IsPrivate() bool {
return !(n.IsHost() || n.IsContainer())
}
// IsDefault indicates whether container uses the default network stack.
func (n NetworkMode) IsDefault() bool {
return n == "default"
}
// NetworkName returns the name of the network stack.
func (n NetworkMode) NetworkName() string {
if n.IsBridge() {
return "bridge"
} else if n.IsHost() {
return "host"
} else if n.IsContainer() {
return "container"
} else if n.IsNone() {
return "none"
} else if n.IsDefault() {
return "default"
} else if n.IsUserDefined() {
return n.UserDefined()
}
return ""
}
// IsBridge indicates whether container uses the bridge network stack
func (n NetworkMode) IsBridge() bool {
return n == "bridge"
}
// IsHost indicates whether container uses the host network stack.
func (n NetworkMode) IsHost() bool {
return n == "host"
}
// IsContainer indicates whether container uses a container network stack.
func (n NetworkMode) IsContainer() bool {
parts := strings.SplitN(string(n), ":", 2)
return len(parts) > 1 && parts[0] == "container"
}
// IsNone indicates whether container isn't using a network stack.
func (n NetworkMode) IsNone() bool {
return n == "none"
}
// ConnectedContainer is the id of the container which network this container is connected to.
func (n NetworkMode) ConnectedContainer() string {
parts := strings.SplitN(string(n), ":", 2)
if len(parts) > 1 {
return parts[1]
}
return ""
}
// IsUserDefined indicates user-created network
func (n NetworkMode) IsUserDefined() bool {
return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer()
}
//UserDefined indicates user-created network
func (n NetworkMode) UserDefined() string {
if n.IsUserDefined() {
return string(n)
}
return ""
}

View file

@ -0,0 +1,87 @@
package container
import (
"strings"
)
// IsDefault indicates whether container uses the default network stack.
func (n NetworkMode) IsDefault() bool {
return n == "default"
}
// IsNone indicates whether container isn't using a network stack.
func (n NetworkMode) IsNone() bool {
return n == "none"
}
// IsContainer indicates whether container uses a container network stack.
// Returns false as windows doesn't support this mode
func (n NetworkMode) IsContainer() bool {
return false
}
// IsBridge indicates whether container uses the bridge network stack
// in windows it is given the name NAT
func (n NetworkMode) IsBridge() bool {
return n == "nat"
}
// IsHost indicates whether container uses the host network stack.
// returns false as this is not supported by windows
func (n NetworkMode) IsHost() bool {
return false
}
// IsPrivate indicates whether container uses its private network stack.
func (n NetworkMode) IsPrivate() bool {
return !(n.IsHost() || n.IsContainer())
}
// ConnectedContainer is the id of the container which network this container is connected to.
// Returns blank string on windows
func (n NetworkMode) ConnectedContainer() string {
return ""
}
// IsUserDefined indicates user-created network
func (n NetworkMode) IsUserDefined() bool {
return !n.IsDefault() && !n.IsNone() && !n.IsBridge()
}
// IsHyperV indicates the use of a Hyper-V partition for isolation
func (i Isolation) IsHyperV() bool {
return strings.ToLower(string(i)) == "hyperv"
}
// IsProcess indicates the use of process isolation
func (i Isolation) IsProcess() bool {
return strings.ToLower(string(i)) == "process"
}
// IsValid indicates if an isolation technology is valid
func (i Isolation) IsValid() bool {
return i.IsDefault() || i.IsHyperV() || i.IsProcess()
}
// NetworkName returns the name of the network stack.
func (n NetworkMode) NetworkName() string {
if n.IsDefault() {
return "default"
} else if n.IsBridge() {
return "nat"
} else if n.IsNone() {
return "none"
} else if n.IsUserDefined() {
return n.UserDefined()
}
return ""
}
//UserDefined indicates user-created network
func (n NetworkMode) UserDefined() string {
if n.IsUserDefined() {
return string(n)
}
return ""
}

View file

@ -0,0 +1,13 @@
package types
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// ErrorResponse Represents an error.
// swagger:model ErrorResponse
type ErrorResponse struct {
// The error message.
// Required: true
Message string `json:"message"`
}

View file

@ -0,0 +1,42 @@
package events
const (
// ContainerEventType is the event type that containers generate
ContainerEventType = "container"
// DaemonEventType is the event type that daemon generate
DaemonEventType = "daemon"
// ImageEventType is the event type that images generate
ImageEventType = "image"
// NetworkEventType is the event type that networks generate
NetworkEventType = "network"
// PluginEventType is the event type that plugins generate
PluginEventType = "plugin"
// VolumeEventType is the event type that volumes generate
VolumeEventType = "volume"
)
// Actor describes something that generates events,
// like a container, or a network, or a volume.
// It has a defined name and a set or attributes.
// The container attributes are its labels, other actors
// can generate these attributes from other properties.
type Actor struct {
ID string
Attributes map[string]string
}
// Message represents the information an event contains
type Message struct {
// Deprecated information from JSONMessage.
// With data only in container events.
Status string `json:"status,omitempty"`
ID string `json:"id,omitempty"`
From string `json:"from,omitempty"`
Type string
Action string
Actor Actor
Time int64 `json:"time,omitempty"`
TimeNano int64 `json:"timeNano,omitempty"`
}

View file

@ -0,0 +1,310 @@
// Package filters provides helper function to parse and handle command line
// filter, used for example in docker ps or docker images commands.
package filters
import (
"encoding/json"
"errors"
"fmt"
"regexp"
"strings"
"github.com/docker/docker/api/types/versions"
)
// Args stores filter arguments as map key:{map key: bool}.
// It contains an aggregation of the map of arguments (which are in the form
// of -f 'key=value') based on the key, and stores values for the same key
// in a map with string keys and boolean values.
// e.g given -f 'label=label1=1' -f 'label=label2=2' -f 'image.name=ubuntu'
// the args will be {"image.name":{"ubuntu":true},"label":{"label1=1":true,"label2=2":true}}
type Args struct {
fields map[string]map[string]bool
}
// NewArgs initializes a new Args struct.
func NewArgs() Args {
return Args{fields: map[string]map[string]bool{}}
}
// ParseFlag parses the argument to the filter flag. Like
//
// `docker ps -f 'created=today' -f 'image.name=ubuntu*'`
//
// If prev map is provided, then it is appended to, and returned. By default a new
// map is created.
func ParseFlag(arg string, prev Args) (Args, error) {
filters := prev
if len(arg) == 0 {
return filters, nil
}
if !strings.Contains(arg, "=") {
return filters, ErrBadFormat
}
f := strings.SplitN(arg, "=", 2)
name := strings.ToLower(strings.TrimSpace(f[0]))
value := strings.TrimSpace(f[1])
filters.Add(name, value)
return filters, nil
}
// ErrBadFormat is an error returned in case of bad format for a filter.
var ErrBadFormat = errors.New("bad format of filter (expected name=value)")
// ToParam packs the Args into a string for easy transport from client to server.
func ToParam(a Args) (string, error) {
// this way we don't URL encode {}, just empty space
if a.Len() == 0 {
return "", nil
}
buf, err := json.Marshal(a.fields)
if err != nil {
return "", err
}
return string(buf), nil
}
// ToParamWithVersion packs the Args into a string for easy transport from client to server.
// The generated string will depend on the specified version (corresponding to the API version).
func ToParamWithVersion(version string, a Args) (string, error) {
// this way we don't URL encode {}, just empty space
if a.Len() == 0 {
return "", nil
}
// for daemons older than v1.10, filter must be of the form map[string][]string
var buf []byte
var err error
if version != "" && versions.LessThan(version, "1.22") {
buf, err = json.Marshal(convertArgsToSlice(a.fields))
} else {
buf, err = json.Marshal(a.fields)
}
if err != nil {
return "", err
}
return string(buf), nil
}
// FromParam unpacks the filter Args.
func FromParam(p string) (Args, error) {
if len(p) == 0 {
return NewArgs(), nil
}
r := strings.NewReader(p)
d := json.NewDecoder(r)
m := map[string]map[string]bool{}
if err := d.Decode(&m); err != nil {
r.Seek(0, 0)
// Allow parsing old arguments in slice format.
// Because other libraries might be sending them in this format.
deprecated := map[string][]string{}
if deprecatedErr := d.Decode(&deprecated); deprecatedErr == nil {
m = deprecatedArgs(deprecated)
} else {
return NewArgs(), err
}
}
return Args{m}, nil
}
// Get returns the list of values associates with a field.
// It returns a slice of strings to keep backwards compatibility with old code.
func (filters Args) Get(field string) []string {
values := filters.fields[field]
if values == nil {
return make([]string, 0)
}
slice := make([]string, 0, len(values))
for key := range values {
slice = append(slice, key)
}
return slice
}
// Add adds a new value to a filter field.
func (filters Args) Add(name, value string) {
if _, ok := filters.fields[name]; ok {
filters.fields[name][value] = true
} else {
filters.fields[name] = map[string]bool{value: true}
}
}
// Del removes a value from a filter field.
func (filters Args) Del(name, value string) {
if _, ok := filters.fields[name]; ok {
delete(filters.fields[name], value)
if len(filters.fields[name]) == 0 {
delete(filters.fields, name)
}
}
}
// Len returns the number of fields in the arguments.
func (filters Args) Len() int {
return len(filters.fields)
}
// MatchKVList returns true if the values for the specified field matches the ones
// from the sources.
// e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
// field is 'label' and sources are {'label1': '1', 'label2': '2'}
// it returns true.
func (filters Args) MatchKVList(field string, sources map[string]string) bool {
fieldValues := filters.fields[field]
//do not filter if there is no filter set or cannot determine filter
if len(fieldValues) == 0 {
return true
}
if len(sources) == 0 {
return false
}
for name2match := range fieldValues {
testKV := strings.SplitN(name2match, "=", 2)
v, ok := sources[testKV[0]]
if !ok {
return false
}
if len(testKV) == 2 && testKV[1] != v {
return false
}
}
return true
}
// Match returns true if the values for the specified field matches the source string
// e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
// field is 'image.name' and source is 'ubuntu'
// it returns true.
func (filters Args) Match(field, source string) bool {
if filters.ExactMatch(field, source) {
return true
}
fieldValues := filters.fields[field]
for name2match := range fieldValues {
match, err := regexp.MatchString(name2match, source)
if err != nil {
continue
}
if match {
return true
}
}
return false
}
// ExactMatch returns true if the source matches exactly one of the filters.
func (filters Args) ExactMatch(field, source string) bool {
fieldValues, ok := filters.fields[field]
//do not filter if there is no filter set or cannot determine filter
if !ok || len(fieldValues) == 0 {
return true
}
// try to match full name value to avoid O(N) regular expression matching
return fieldValues[source]
}
// UniqueExactMatch returns true if there is only one filter and the source matches exactly this one.
func (filters Args) UniqueExactMatch(field, source string) bool {
fieldValues := filters.fields[field]
//do not filter if there is no filter set or cannot determine filter
if len(fieldValues) == 0 {
return true
}
if len(filters.fields[field]) != 1 {
return false
}
// try to match full name value to avoid O(N) regular expression matching
return fieldValues[source]
}
// FuzzyMatch returns true if the source matches exactly one of the filters,
// or the source has one of the filters as a prefix.
func (filters Args) FuzzyMatch(field, source string) bool {
if filters.ExactMatch(field, source) {
return true
}
fieldValues := filters.fields[field]
for prefix := range fieldValues {
if strings.HasPrefix(source, prefix) {
return true
}
}
return false
}
// Include returns true if the name of the field to filter is in the filters.
func (filters Args) Include(field string) bool {
_, ok := filters.fields[field]
return ok
}
// Validate ensures that all the fields in the filter are valid.
// It returns an error as soon as it finds an invalid field.
func (filters Args) Validate(accepted map[string]bool) error {
for name := range filters.fields {
if !accepted[name] {
return fmt.Errorf("Invalid filter '%s'", name)
}
}
return nil
}
// WalkValues iterates over the list of filtered values for a field.
// It stops the iteration if it finds an error and it returns that error.
func (filters Args) WalkValues(field string, op func(value string) error) error {
if _, ok := filters.fields[field]; !ok {
return nil
}
for v := range filters.fields[field] {
if err := op(v); err != nil {
return err
}
}
return nil
}
func deprecatedArgs(d map[string][]string) map[string]map[string]bool {
m := map[string]map[string]bool{}
for k, v := range d {
values := map[string]bool{}
for _, vv := range v {
values[vv] = true
}
m[k] = values
}
return m
}
func convertArgsToSlice(f map[string]map[string]bool) map[string][]string {
m := map[string][]string{}
for k, v := range f {
values := []string{}
for kk := range v {
if v[kk] {
values = append(values, kk)
}
}
m[k] = values
}
return m
}

View file

@ -0,0 +1,13 @@
package types
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// IDResponse Response to an API call that returns just an Id
// swagger:model IdResponse
type IDResponse struct {
// The id of the newly created object.
// Required: true
ID string `json:"Id"`
}

View file

@ -0,0 +1,49 @@
package types
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
// ImageSummary image summary
// swagger:model ImageSummary
type ImageSummary struct {
// containers
// Required: true
Containers int64 `json:"Containers"`
// created
// Required: true
Created int64 `json:"Created"`
// Id
// Required: true
ID string `json:"Id"`
// labels
// Required: true
Labels map[string]string `json:"Labels"`
// parent Id
// Required: true
ParentID string `json:"ParentId"`
// repo digests
// Required: true
RepoDigests []string `json:"RepoDigests"`
// repo tags
// Required: true
RepoTags []string `json:"RepoTags"`
// shared size
// Required: true
SharedSize int64 `json:"SharedSize"`
// size
// Required: true
Size int64 `json:"Size"`
// virtual size
// Required: true
VirtualSize int64 `json:"VirtualSize"`
}

Some files were not shown because too many files have changed in this diff Show more