mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-02-02 04:32:23 +00:00
backported 0.4 changes to existing database
This commit is contained in:
parent
d07c0cb80d
commit
85256d3a22
69 changed files with 3731 additions and 2884 deletions
153
builder/build.go
153
builder/build.go
|
@ -1,153 +0,0 @@
|
|||
package builder
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
// B is a type passed to build nodes. B implements an io.Writer
|
||||
// and will accumulate build output during execution.
|
||||
type B struct {
|
||||
sync.Mutex
|
||||
|
||||
Repo *common.Repo
|
||||
Build *common.Build
|
||||
Task *common.Task
|
||||
Clone *common.Clone
|
||||
|
||||
client dockerclient.Client
|
||||
|
||||
writer io.Writer
|
||||
|
||||
exitCode int
|
||||
|
||||
start time.Time // Time build started
|
||||
duration time.Duration
|
||||
timerOn bool
|
||||
|
||||
containers []string
|
||||
}
|
||||
|
||||
// NewB returns a new Build context.
|
||||
func NewB(client dockerclient.Client, w io.Writer) *B {
|
||||
return &B{
|
||||
client: client,
|
||||
writer: w,
|
||||
}
|
||||
}
|
||||
|
||||
// Run creates and runs a Docker container.
|
||||
func (b *B) Run(conf *dockerclient.ContainerConfig) (string, error) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
name, err := b.client.CreateContainer(conf, "")
|
||||
if err != nil {
|
||||
// on error try to pull the Docker image.
|
||||
// note that this may not be the cause of
|
||||
// the error, but we'll try just in case.
|
||||
b.client.PullImage(conf.Image, nil)
|
||||
|
||||
// then try to re-create
|
||||
name, err = b.client.CreateContainer(conf, "")
|
||||
if err != nil {
|
||||
return name, err
|
||||
}
|
||||
}
|
||||
b.containers = append(b.containers, name)
|
||||
err = b.client.StartContainer(name, &conf.HostConfig)
|
||||
if err != nil {
|
||||
return name, err
|
||||
}
|
||||
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// Inspect inspects the running Docker container and returns
|
||||
// the contianer runtime information and state.
|
||||
func (b *B) Inspect(name string) (*dockerclient.ContainerInfo, error) {
|
||||
return b.client.InspectContainer(name)
|
||||
}
|
||||
|
||||
// Remove stops and removes the named Docker container.
|
||||
func (b *B) Remove(name string) {
|
||||
b.client.StopContainer(name, 5)
|
||||
b.client.KillContainer(name, "9")
|
||||
b.client.RemoveContainer(name, true, true)
|
||||
}
|
||||
|
||||
// RemoveAll stops and removes all Docker containers that were
|
||||
// created and started during the build process.
|
||||
func (b *B) RemoveAll() {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
for i := len(b.containers) - 1; i >= 0; i-- {
|
||||
b.Remove(b.containers[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Logs returns an io.ReadCloser for reading the build stream of
|
||||
// the named Docker container.
|
||||
func (b *B) Logs(name string) (io.ReadCloser, error) {
|
||||
opts := dockerclient.LogOptions{
|
||||
Follow: true,
|
||||
Stderr: true,
|
||||
Stdout: true,
|
||||
Timestamps: false,
|
||||
}
|
||||
return b.client.ContainerLogs(name, &opts)
|
||||
}
|
||||
|
||||
// StartTimer starts timing a build. This function is called automatically
|
||||
// before a build starts, but it can also used to resume timing after
|
||||
// a call to StopTimer.
|
||||
func (b *B) StartTimer() {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
if !b.timerOn {
|
||||
b.start = time.Now()
|
||||
b.timerOn = true
|
||||
}
|
||||
}
|
||||
|
||||
// StopTimer stops timing a build. This can be used to pause the timer
|
||||
// while performing complex initialization that you don't want to measure.
|
||||
func (b *B) StopTimer() {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
if b.timerOn {
|
||||
b.duration += time.Now().Sub(b.start)
|
||||
b.timerOn = false
|
||||
}
|
||||
}
|
||||
|
||||
// Write writes the build stdout and stderr to the result.
|
||||
func (b *B) Write(p []byte) (n int, err error) {
|
||||
return b.writer.Write(p)
|
||||
}
|
||||
|
||||
// Exit writes the function as having failed but continues execution.
|
||||
func (b *B) Exit(code int) {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
if code != 0 { // never override non-zero exit
|
||||
b.exitCode = code
|
||||
}
|
||||
}
|
||||
|
||||
// ExitCode reports the build exit code. A non-zero value indicates
|
||||
// the build exited with errors.
|
||||
func (b *B) ExitCode() int {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
return b.exitCode
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
package builder
|
||||
|
||||
import "github.com/drone/drone/common"
|
||||
|
||||
// Builder represents a build execution tree.
|
||||
type Builder struct {
|
||||
builds Node
|
||||
deploy Node
|
||||
notify Node
|
||||
}
|
||||
|
||||
// Run runs the build, deploy and notify nodes
|
||||
// in the build tree.
|
||||
func (b *Builder) Run(build *B) error {
|
||||
var err error
|
||||
err = b.RunBuild(build)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = b.RunDeploy(build)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.RunNotify(build)
|
||||
}
|
||||
|
||||
// RunBuild runs only the build node.
|
||||
func (b *Builder) RunBuild(build *B) error {
|
||||
return b.builds.Run(build)
|
||||
}
|
||||
|
||||
// RunDeploy runs only the deploy node.
|
||||
func (b *Builder) RunDeploy(build *B) error {
|
||||
return b.notify.Run(build)
|
||||
}
|
||||
|
||||
// RunNotify runs on the notify node.
|
||||
func (b *Builder) RunNotify(build *B) error {
|
||||
return b.notify.Run(build)
|
||||
}
|
||||
|
||||
func (b *Builder) HasDeploy() bool {
|
||||
return len(b.deploy.(serialNode)) != 0
|
||||
}
|
||||
|
||||
func (b *Builder) HasNotify() bool {
|
||||
return len(b.notify.(serialNode)) != 0
|
||||
}
|
||||
|
||||
// Load loads a build configuration file.
|
||||
func Load(conf *common.Config) *Builder {
|
||||
var (
|
||||
builds []Node
|
||||
deploys []Node
|
||||
notifys []Node
|
||||
)
|
||||
|
||||
for _, step := range conf.Compose {
|
||||
builds = append(builds, &serviceNode{step}) // compose
|
||||
}
|
||||
builds = append(builds, &batchNode{conf.Setup}) // setup
|
||||
if conf.Clone != nil {
|
||||
builds = append(builds, &batchNode{conf.Clone}) // clone
|
||||
}
|
||||
builds = append(builds, &batchNode{conf.Build}) // build
|
||||
|
||||
for _, step := range conf.Publish {
|
||||
deploys = append(deploys, &batchNode{step}) // publish
|
||||
}
|
||||
for _, step := range conf.Deploy {
|
||||
deploys = append(deploys, &batchNode{step}) // deploy
|
||||
}
|
||||
for _, step := range conf.Notify {
|
||||
notifys = append(notifys, &batchNode{step}) // notify
|
||||
}
|
||||
return &Builder{
|
||||
serialNode(builds),
|
||||
serialNode(deploys),
|
||||
serialNode(notifys),
|
||||
}
|
||||
}
|
124
builder/copy.go
124
builder/copy.go
|
@ -1,124 +0,0 @@
|
|||
package builder
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
StdWriterPrefixLen = 8
|
||||
StdWriterFdIndex = 0
|
||||
StdWriterSizeIndex = 4
|
||||
)
|
||||
|
||||
type StdType [StdWriterPrefixLen]byte
|
||||
|
||||
var (
|
||||
Stdin StdType = StdType{0: 0}
|
||||
Stdout StdType = StdType{0: 1}
|
||||
Stderr StdType = StdType{0: 2}
|
||||
)
|
||||
|
||||
type StdWriter struct {
|
||||
io.Writer
|
||||
prefix StdType
|
||||
sizeBuf []byte
|
||||
}
|
||||
|
||||
var ErrInvalidStdHeader = errors.New("Unrecognized input header")
|
||||
|
||||
// StdCopy is a modified version of io.Copy.
|
||||
//
|
||||
// StdCopy will demultiplex `src`, assuming that it contains two streams,
|
||||
// previously multiplexed together using a StdWriter instance.
|
||||
// As it reads from `src`, StdCopy will write to `dstout` and `dsterr`.
|
||||
//
|
||||
// StdCopy will read until it hits EOF on `src`. It will then return a nil error.
|
||||
// In other words: if `err` is non nil, it indicates a real underlying error.
|
||||
//
|
||||
// `written` will hold the total number of bytes written to `dstout` and `dsterr`.
|
||||
func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) {
|
||||
var (
|
||||
buf = make([]byte, 32*1024+StdWriterPrefixLen+1)
|
||||
bufLen = len(buf)
|
||||
nr, nw int
|
||||
er, ew error
|
||||
out io.Writer
|
||||
frameSize int
|
||||
)
|
||||
|
||||
for {
|
||||
// Make sure we have at least a full header
|
||||
for nr < StdWriterPrefixLen {
|
||||
var nr2 int
|
||||
nr2, er = src.Read(buf[nr:])
|
||||
nr += nr2
|
||||
if er == io.EOF {
|
||||
if nr < StdWriterPrefixLen {
|
||||
return written, nil
|
||||
}
|
||||
break
|
||||
}
|
||||
if er != nil {
|
||||
return 0, er
|
||||
}
|
||||
}
|
||||
|
||||
// Check the first byte to know where to write
|
||||
switch buf[StdWriterFdIndex] {
|
||||
case 0:
|
||||
fallthrough
|
||||
case 1:
|
||||
// Write on stdout
|
||||
out = dstout
|
||||
case 2:
|
||||
// Write on stderr
|
||||
out = dsterr
|
||||
default:
|
||||
return 0, ErrInvalidStdHeader
|
||||
}
|
||||
|
||||
// Retrieve the size of the frame
|
||||
frameSize = int(binary.BigEndian.Uint32(buf[StdWriterSizeIndex : StdWriterSizeIndex+4]))
|
||||
|
||||
// Check if the buffer is big enough to read the frame.
|
||||
// Extend it if necessary.
|
||||
if frameSize+StdWriterPrefixLen > bufLen {
|
||||
buf = append(buf, make([]byte, frameSize+StdWriterPrefixLen-bufLen+1)...)
|
||||
bufLen = len(buf)
|
||||
}
|
||||
|
||||
// While the amount of bytes read is less than the size of the frame + header, we keep reading
|
||||
for nr < frameSize+StdWriterPrefixLen {
|
||||
var nr2 int
|
||||
nr2, er = src.Read(buf[nr:])
|
||||
nr += nr2
|
||||
if er == io.EOF {
|
||||
if nr < frameSize+StdWriterPrefixLen {
|
||||
return written, nil
|
||||
}
|
||||
break
|
||||
}
|
||||
if er != nil {
|
||||
return 0, er
|
||||
}
|
||||
}
|
||||
|
||||
// Write the retrieved frame (without header)
|
||||
nw, ew = out.Write(buf[StdWriterPrefixLen : frameSize+StdWriterPrefixLen])
|
||||
if ew != nil {
|
||||
return 0, ew
|
||||
}
|
||||
// If the frame has not been fully written: error
|
||||
if nw != frameSize {
|
||||
return 0, io.ErrShortWrite
|
||||
}
|
||||
written += int64(nw)
|
||||
|
||||
// Move the rest of the buffer to the beginning
|
||||
copy(buf, buf[frameSize+StdWriterPrefixLen:])
|
||||
// Move the index
|
||||
nr -= frameSize + StdWriterPrefixLen
|
||||
}
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
var errNop = errors.New("Operation not supported")
|
||||
|
||||
// Ambassador is a wrapper around the Docker client that
|
||||
// provides a shared volume and network for all containers.
|
||||
type Ambassador struct {
|
||||
dockerclient.Client
|
||||
name string
|
||||
}
|
||||
|
||||
// NewAmbassador creates an ambassador container and wraps the Docker
|
||||
// client to inject the ambassador volume and network into containers.
|
||||
func NewAmbassador(client dockerclient.Client) (_ *Ambassador, err error) {
|
||||
amb := &Ambassador{client, ""}
|
||||
|
||||
conf := &dockerclient.ContainerConfig{}
|
||||
host := &dockerclient.HostConfig{}
|
||||
conf.Entrypoint = []string{"/bin/sleep"}
|
||||
conf.Cmd = []string{"86400"}
|
||||
conf.Image = "busybox"
|
||||
conf.Volumes = map[string]struct{}{}
|
||||
conf.Volumes["/drone"] = struct{}{}
|
||||
|
||||
// creates the ambassador container
|
||||
amb.name, err = client.CreateContainer(conf, "")
|
||||
if err != nil {
|
||||
log.WithField("ambassador", conf.Image).Errorln(err)
|
||||
|
||||
// on failure attempts to pull the image
|
||||
client.PullImage(conf.Image, nil)
|
||||
|
||||
// then attempts to re-create the container
|
||||
amb.name, err = client.CreateContainer(conf, "")
|
||||
if err != nil {
|
||||
log.WithField("ambassador", conf.Image).Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err = client.StartContainer(amb.name, host)
|
||||
if err != nil {
|
||||
log.WithField("ambassador", conf.Image).Errorln(err)
|
||||
}
|
||||
return amb, err
|
||||
}
|
||||
|
||||
// Destroy stops and deletes the ambassador container.
|
||||
func (c *Ambassador) Destroy() error {
|
||||
c.Client.StopContainer(c.name, 5)
|
||||
c.Client.KillContainer(c.name, "9")
|
||||
return c.Client.RemoveContainer(c.name, true, true)
|
||||
}
|
||||
|
||||
// CreateContainer creates a container.
|
||||
func (c *Ambassador) CreateContainer(conf *dockerclient.ContainerConfig, name string) (string, error) {
|
||||
log.WithField("image", conf.Image).Infoln("create container")
|
||||
|
||||
// add the affinity flag for swarm
|
||||
conf.Env = append(conf.Env, "affinity:container=="+c.name)
|
||||
|
||||
id, err := c.Client.CreateContainer(conf, name)
|
||||
if err != nil {
|
||||
log.WithField("image", conf.Image).Errorln(err)
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
// StartContainer starts a container. The ambassador volume
|
||||
// is automatically linked. The ambassador network is linked
|
||||
// iff a network mode is not already specified.
|
||||
func (c *Ambassador) StartContainer(id string, conf *dockerclient.HostConfig) error {
|
||||
log.WithField("container", id).Debugln("start container")
|
||||
|
||||
conf.VolumesFrom = append(conf.VolumesFrom, c.name)
|
||||
if len(conf.NetworkMode) == 0 {
|
||||
conf.NetworkMode = "container:" + c.name
|
||||
}
|
||||
err := c.Client.StartContainer(id, conf)
|
||||
if err != nil {
|
||||
log.WithField("container", id).Errorln(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// StopContainer stops a container.
|
||||
func (c *Ambassador) StopContainer(id string, timeout int) error {
|
||||
log.WithField("container", id).Debugln("stop container")
|
||||
err := c.Client.StopContainer(id, timeout)
|
||||
if err != nil {
|
||||
log.WithField("container", id).Errorln(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// PullImage pulls an image.
|
||||
func (c *Ambassador) PullImage(name string, auth *dockerclient.AuthConfig) error {
|
||||
log.WithField("image", name).Debugln("pull image")
|
||||
err := c.Client.PullImage(name, auth)
|
||||
if err != nil {
|
||||
log.WithField("image", name).Errorln(err)
|
||||
}
|
||||
return err
|
||||
}
|
103
builder/node.go
103
builder/node.go
|
@ -1,103 +0,0 @@
|
|||
package builder
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
)
|
||||
|
||||
// Node is an element in the build execution tree.
|
||||
type Node interface {
|
||||
Run(*B) error
|
||||
}
|
||||
|
||||
// parallelNode runs a set of build nodes in parallel.
|
||||
type parallelNode []Node
|
||||
|
||||
func (n parallelNode) Run(b *B) error {
|
||||
var wg sync.WaitGroup
|
||||
for _, node := range n {
|
||||
wg.Add(1)
|
||||
|
||||
go func(node Node) {
|
||||
defer wg.Done()
|
||||
node.Run(b)
|
||||
}(node)
|
||||
}
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// serialNode runs a set of build nodes in sequential order.
|
||||
type serialNode []Node
|
||||
|
||||
func (n serialNode) Run(b *B) error {
|
||||
for _, node := range n {
|
||||
err := node.Run(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if b.ExitCode() != 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// batchNode runs a container and blocks until complete.
|
||||
type batchNode struct {
|
||||
step *common.Step
|
||||
}
|
||||
|
||||
func (n *batchNode) Run(b *B) error {
|
||||
|
||||
// switch {
|
||||
// case n.step.Condition == nil:
|
||||
// case n.step.Condition.MatchBranch(b.Commit.Branch) == false:
|
||||
// return nil
|
||||
// case n.step.Condition.MatchOwner(b.Repo.Owner) == false:
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// creates the container conf
|
||||
conf := toContainerConfig(n.step)
|
||||
if n.step.Config != nil {
|
||||
conf.Cmd = toCommand(b, n.step)
|
||||
}
|
||||
|
||||
// inject environment vars
|
||||
injectEnv(b, conf)
|
||||
|
||||
name, err := b.Run(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// streams the logs to the build results
|
||||
rc, err := b.Logs(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
StdCopy(b, b, rc)
|
||||
//io.Copy(b, rc)
|
||||
|
||||
// inspects the results and writes the
|
||||
// build result exit code
|
||||
info, err := b.Inspect(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Exit(info.State.ExitCode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// serviceNode runs a container, blocking, writes output, uses config section
|
||||
type serviceNode struct {
|
||||
step *common.Step
|
||||
}
|
||||
|
||||
func (n *serviceNode) Run(b *B) error {
|
||||
conf := toContainerConfig(n.step)
|
||||
_, err := b.Run(conf)
|
||||
return err
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
package builder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
// helper function that converts the build step to
|
||||
// a containerConfig for use with the dockerclient
|
||||
func toContainerConfig(step *common.Step) *dockerclient.ContainerConfig {
|
||||
config := &dockerclient.ContainerConfig{
|
||||
Image: step.Image,
|
||||
Env: step.Environment,
|
||||
Cmd: step.Command,
|
||||
Entrypoint: step.Entrypoint,
|
||||
WorkingDir: step.WorkingDir,
|
||||
HostConfig: dockerclient.HostConfig{
|
||||
Privileged: step.Privileged,
|
||||
NetworkMode: step.NetworkMode,
|
||||
},
|
||||
}
|
||||
|
||||
config.Volumes = map[string]struct{}{}
|
||||
for _, path := range step.Volumes {
|
||||
if strings.Index(path, ":") == -1 {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(path, ":")
|
||||
config.Volumes[parts[1]] = struct{}{}
|
||||
config.HostConfig.Binds = append(config.HostConfig.Binds, path)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// helper function to inject drone-specific environment
|
||||
// variables into the container.
|
||||
func injectEnv(b *B, conf *dockerclient.ContainerConfig) {
|
||||
var branch string
|
||||
var commit string
|
||||
if b.Build.Commit != nil {
|
||||
branch = b.Build.Commit.Ref
|
||||
commit = b.Build.Commit.Sha
|
||||
} else {
|
||||
branch = b.Build.PullRequest.Target.Ref
|
||||
commit = b.Build.PullRequest.Target.Sha
|
||||
}
|
||||
|
||||
conf.Env = append(conf.Env, "DRONE=true")
|
||||
conf.Env = append(conf.Env, fmt.Sprintf("DRONE_BRANCH=%s", branch))
|
||||
conf.Env = append(conf.Env, fmt.Sprintf("DRONE_COMMIT=%s", commit))
|
||||
|
||||
// for jenkins campatibility
|
||||
conf.Env = append(conf.Env, "CI=true")
|
||||
conf.Env = append(conf.Env, fmt.Sprintf("WORKSPACE=%s", b.Clone.Dir))
|
||||
conf.Env = append(conf.Env, fmt.Sprintf("JOB_NAME=%s/%s", b.Repo.Owner, b.Repo.Name))
|
||||
conf.Env = append(conf.Env, fmt.Sprintf("BUILD_ID=%d", b.Build.Number))
|
||||
conf.Env = append(conf.Env, fmt.Sprintf("BUILD_DIR=%s", b.Clone.Dir))
|
||||
conf.Env = append(conf.Env, fmt.Sprintf("GIT_BRANCH=%s", branch))
|
||||
conf.Env = append(conf.Env, fmt.Sprintf("GIT_COMMIT=%s", commit))
|
||||
|
||||
}
|
||||
|
||||
// helper function to encode the build step to
|
||||
// a json string. Primarily used for plugins, which
|
||||
// expect a json encoded string in stdin or arg[1].
|
||||
func toCommand(b *B, step *common.Step) []string {
|
||||
p := payload{
|
||||
b.Repo,
|
||||
b.Build,
|
||||
b.Task,
|
||||
b.Clone,
|
||||
step.Config,
|
||||
}
|
||||
return []string{p.Encode()}
|
||||
}
|
||||
|
||||
// payload represents the payload of a plugin
|
||||
// that is serialized and sent to the plugin in JSON
|
||||
// format via stdin or arg[1].
|
||||
type payload struct {
|
||||
Repo *common.Repo `json:"repo"`
|
||||
Build *common.Build `json:"build"`
|
||||
Task *common.Task `json:"task"`
|
||||
Clone *common.Clone `json:"clone"`
|
||||
|
||||
Config map[string]interface{} `json:"vargs"`
|
||||
}
|
||||
|
||||
// Encode encodes the payload in JSON format.
|
||||
func (p *payload) Encode() string {
|
||||
out, _ := json.Marshal(p)
|
||||
return string(out)
|
||||
}
|
100
common/build.go
100
common/build.go
|
@ -1,93 +1,19 @@
|
|||
package common
|
||||
|
||||
const (
|
||||
StatePending = "pending"
|
||||
StateRunning = "running"
|
||||
StateSuccess = "success"
|
||||
StateFailure = "failure"
|
||||
StateKilled = "killed"
|
||||
StateError = "error"
|
||||
)
|
||||
|
||||
type Build struct {
|
||||
Number int `json:"number"`
|
||||
State string `json:"state"`
|
||||
Duration int64 `json:"duration"`
|
||||
Started int64 `json:"started_at"`
|
||||
Finished int64 `json:"finished_at"`
|
||||
Created int64 `json:"created_at"`
|
||||
Updated int64 `json:"updated_at"`
|
||||
ID int64 `meddler:"build_id,pk" json:"-"`
|
||||
CommitID int64 `meddler:"commit_id" json:"-"`
|
||||
State string `meddler:"build_state" json:"state"`
|
||||
ExitCode int `meddler:"build_exit" json:"exit_code"`
|
||||
Sequence int `meddler:"build_seq" json:"sequence"`
|
||||
Duration int64 `meddler:"build_duration" json:"duration"`
|
||||
Started int64 `meddler:"build_started" json:"started_at"`
|
||||
Finished int64 `meddler:"build_finished" json:"finished_at"`
|
||||
Created int64 `meddler:"build_created" json:"created_at"`
|
||||
Updated int64 `meddler:"build_updated" json:"updated_at"`
|
||||
|
||||
// Tasks int `json:"task_count"`
|
||||
|
||||
// Commit represents the commit data send in the
|
||||
// post-commit hook. This will not be populated when
|
||||
// a pull requests.
|
||||
Commit *Commit `json:"head_commit,omitempty"`
|
||||
|
||||
// PullRequest represents the pull request data sent
|
||||
// in the post-commit hook. This will only be populated
|
||||
// when a pull request.
|
||||
PullRequest *PullRequest `json:"pull_request,omitempty"`
|
||||
|
||||
// Statuses represents a list of build statuses used
|
||||
// to annotate the build.
|
||||
Statuses []*Status `json:"statuses,omitempty"`
|
||||
|
||||
// Tasks represents a list of build tasks. A build is
|
||||
// comprised of one or many tasks.
|
||||
Tasks []*Task `json:"tasks,omitempty"`
|
||||
Environment map[string]string `meddler:"build_env,json" json:"environment"`
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
State string `json:"state"`
|
||||
Link string `json:"target_url"`
|
||||
Desc string `json:"description"`
|
||||
Context string `json:"context"`
|
||||
}
|
||||
|
||||
type Commit struct {
|
||||
Sha string `json:"sha,omitempty"`
|
||||
Ref string `json:"ref,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Timestamp string `json:"timestamp,omitempty"`
|
||||
Author *Author `json:"author,omitempty"`
|
||||
Remote *Remote `json:"repo,omitempty"`
|
||||
}
|
||||
|
||||
type PullRequest struct {
|
||||
Number int `json:"number,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Source *Commit `json:"source,omitempty"`
|
||||
Target *Commit `json:"target,omitempty"`
|
||||
}
|
||||
|
||||
type Author struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Login string `json:"login,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Gravatar string `json:"gravatar_id,omitempty"`
|
||||
}
|
||||
|
||||
type Remote struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
FullName string `json:"full_name,omitempty"`
|
||||
Clone string `json:"clone_url,omitempty"`
|
||||
}
|
||||
|
||||
type Clone struct {
|
||||
Origin string `json:"origin"`
|
||||
Remote string `json:"remote"`
|
||||
Branch string `json:"branch"`
|
||||
Sha string `json:"sha"`
|
||||
Ref string `json:"ref"`
|
||||
Dir string `json:"dir"`
|
||||
Netrc *Netrc `json:"netrc"`
|
||||
Keypair *Keypair `json:"keypair"`
|
||||
}
|
||||
|
||||
type Netrc struct {
|
||||
Machine string `json:"machine"`
|
||||
Login string `json:"login"`
|
||||
Password string `json:"user"`
|
||||
}
|
||||
// QUESTION: should we track if it was oom killed?
|
||||
// OOMKill bool `meddler:"build_oom" json:"oom_kill"`
|
||||
|
|
|
@ -23,7 +23,7 @@ type CCProject struct {
|
|||
WebURL string `xml:"webUrl,attr"`
|
||||
}
|
||||
|
||||
func NewCC(r *common.Repo, b *common.Build, url string) *CCProjects {
|
||||
func NewCC(r *common.Repo, c *common.Commit, url string) *CCProjects {
|
||||
proj := &CCProject{
|
||||
Name: r.Owner + "/" + r.Name,
|
||||
WebURL: url,
|
||||
|
@ -34,16 +34,16 @@ func NewCC(r *common.Repo, b *common.Build, url string) *CCProjects {
|
|||
|
||||
// if the build is not currently running then
|
||||
// we can return the latest build status.
|
||||
if b.State != common.StatePending &&
|
||||
b.State != common.StateRunning {
|
||||
if c.State != common.StatePending &&
|
||||
c.State != common.StateRunning {
|
||||
proj.Activity = "Sleeping"
|
||||
proj.LastBuildTime = time.Unix(b.Started, 0).Format(time.RFC3339)
|
||||
proj.LastBuildLabel = strconv.Itoa(b.Number)
|
||||
proj.LastBuildTime = time.Unix(c.Started, 0).Format(time.RFC3339)
|
||||
proj.LastBuildLabel = strconv.Itoa(c.Sequence)
|
||||
}
|
||||
|
||||
// ensure the last build state accepts a valid
|
||||
// ccmenu enumeration
|
||||
switch b.State {
|
||||
switch c.State {
|
||||
case common.StateError, common.StateKilled:
|
||||
proj.LastBuildStatus = "Exception"
|
||||
case common.StateSuccess:
|
||||
|
|
26
common/clone.go
Normal file
26
common/clone.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package common
|
||||
|
||||
type Clone struct {
|
||||
Origin string `json:"origin"`
|
||||
Remote string `json:"remote"`
|
||||
Branch string `json:"branch"`
|
||||
Sha string `json:"sha"`
|
||||
Ref string `json:"ref"`
|
||||
Dir string `json:"dir"`
|
||||
Netrc *Netrc `json:"netrc"`
|
||||
Keypair *Keypair `json:"keypair"`
|
||||
}
|
||||
|
||||
type Netrc struct {
|
||||
Machine string `json:"machine"`
|
||||
Login string `json:"login"`
|
||||
Password string `json:"user"`
|
||||
}
|
||||
|
||||
// Keypair represents an RSA public and private key
|
||||
// assigned to a repository. It may be used to clone
|
||||
// private repositories, or as a deployment key.
|
||||
type Keypair struct {
|
||||
Public string `json:"public,omitempty"`
|
||||
Private string `json:"private,omitempty"`
|
||||
}
|
34
common/commit.go
Normal file
34
common/commit.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package common
|
||||
|
||||
const (
|
||||
StatePending = "pending"
|
||||
StateRunning = "running"
|
||||
StateSuccess = "success"
|
||||
StateFailure = "failure"
|
||||
StateKilled = "killed"
|
||||
StateError = "error"
|
||||
)
|
||||
|
||||
type Commit struct {
|
||||
ID int64 `meddler:"commit_id,pk" json:"-"`
|
||||
RepoID int64 `meddler:"repo_id" json:"-"`
|
||||
Sequence int `meddler:"commit_seq" json:"sequence"`
|
||||
State string `meddler:"commit_state" json:"state"`
|
||||
Started int64 `meddler:"commit_started" json:"started_at"`
|
||||
Finished int64 `meddler:"commit_finished" json:"finished_at"`
|
||||
Sha string `meddler:"commit_sha" json:"sha"`
|
||||
Ref string `meddler:"commit_ref" json:"ref"`
|
||||
PullRequest string `meddler:"commit_pr" json:"pull_request,omitempty"`
|
||||
Branch string `meddler:"commit_branch" json:"branch"`
|
||||
Author string `meddler:"commit_author" json:"author"`
|
||||
Gravatar string `meddler:"commit_gravatar" json:"gravatar"`
|
||||
Timestamp string `meddler:"commit_timestamp" json:"timestamp"`
|
||||
Message string `meddler:"commit_message" json:"message"`
|
||||
SourceRemote string `meddler:"commit_source_remote" json:"source_remote,omitempty"`
|
||||
SourceBranch string `meddler:"commit_source_branch" json:"source_branch,omitempty"`
|
||||
SourceSha string `meddler:"commit_source_sha" json:"source_sha,omitempty"`
|
||||
Created int64 `meddler:"commit_created" json:"created_at"`
|
||||
Updated int64 `meddler:"commit_updated" json:"updated_at"`
|
||||
|
||||
Builds []*Build `meddler:"-" json:"builds,omitempty"`
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package common
|
||||
|
||||
type Hook struct {
|
||||
Repo *Repo
|
||||
Commit *Commit
|
||||
PullRequest *PullRequest
|
||||
Repo *Repo
|
||||
Commit *Commit
|
||||
}
|
||||
|
|
|
@ -1,62 +1,54 @@
|
|||
package common
|
||||
|
||||
type Repo struct {
|
||||
ID int64 `json:"id"`
|
||||
Owner string `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"full_name"`
|
||||
Language string `json:"language"`
|
||||
Private bool `json:"private"`
|
||||
Link string `json:"link_url"`
|
||||
Clone string `json:"clone_url"`
|
||||
Branch string `json:"default_branch"`
|
||||
ID int64 `meddler:"repo_id,pk" json:"id"`
|
||||
UserID int64 `meddler:"user_id" json:"-"`
|
||||
Owner string `meddler:"repo_owner" json:"owner"`
|
||||
Name string `meddler:"repo_name" json:"name"`
|
||||
FullName string `meddler:"repo_slug" json:"full_name"`
|
||||
Token string `meddler:"repo_token" json:"-"`
|
||||
Language string `meddler:"repo_lang" json:"language"`
|
||||
Private bool `meddler:"repo_private" json:"private"`
|
||||
Link string `meddler:"repo_link" json:"link_url"`
|
||||
Clone string `meddler:"repo_clone" json:"clone_url"`
|
||||
Branch string `meddler:"repo_branch" json:"default_branch"`
|
||||
Timeout int64 `meddler:"repo_timeout" json:"timeout"`
|
||||
Trusted bool `meddler:"repo_trusted" json:"trusted"`
|
||||
PostCommit bool `meddler:"repo_push" json:"post_commits"`
|
||||
PullRequest bool `meddler:"repo_pull" json:"pull_requests"`
|
||||
PublicKey string `meddler:"repo_public_key" json:"-"`
|
||||
PrivateKey string `meddler:"repo_private_key" json:"-"`
|
||||
Created int64 `meddler:"repo_created" json:"created_at"`
|
||||
Updated int64 `meddler:"repo_updated" json:"updated_at"`
|
||||
|
||||
Timeout int64 `json:"timeout"`
|
||||
Trusted bool `json:"trusted"`
|
||||
Disabled bool `json:"disabled"`
|
||||
DisablePR bool `json:"disable_prs"`
|
||||
DisableTag bool `json:"disable_tags"`
|
||||
|
||||
Created int64 `json:"created_at"`
|
||||
Updated int64 `json:"updated_at"`
|
||||
|
||||
User *Owner `json:"user,omitempty"`
|
||||
Last *Build `json:"last_build,omitempty"`
|
||||
Params map[string]string `meddler:"repo_params,json" json:"-"`
|
||||
}
|
||||
|
||||
// Keypair represents an RSA public and private key
|
||||
// assigned to a repository. It may be used to clone
|
||||
// private repositories, or as a deployment key.
|
||||
type Keypair struct {
|
||||
Public string `json:"public"`
|
||||
Private string `json:"private"`
|
||||
type RepoLite struct {
|
||||
ID int64 `meddler:"repo_id,pk" json:"id"`
|
||||
UserID int64 `meddler:"user_id" json:"-"`
|
||||
Owner string `meddler:"repo_owner" json:"owner"`
|
||||
Name string `meddler:"repo_name" json:"name"`
|
||||
FullName string `meddler:"repo_slug" json:"full_name"`
|
||||
Language string `meddler:"repo_lang" json:"language"`
|
||||
Private bool `meddler:"repo_private" json:"private"`
|
||||
Created int64 `meddler:"repo_created" json:"created_at"`
|
||||
Updated int64 `meddler:"repo_updated" json:"updated_at"`
|
||||
}
|
||||
|
||||
// Owner represents the owner of a repository.
|
||||
type Owner struct {
|
||||
Login string `json:"login"`
|
||||
type RepoCommit struct {
|
||||
ID int64 `meddler:"repo_id,pk" json:"id"`
|
||||
Owner string `meddler:"repo_owner" json:"owner"`
|
||||
Name string `meddler:"repo_name" json:"name"`
|
||||
FullName string `meddler:"repo_slug" json:"full_name"`
|
||||
Number int `meddler:"commit_seq" json:"number"`
|
||||
State string `meddler:"commit_state" json:"state"`
|
||||
Started int64 `meddler:"commit_started" json:"started_at"`
|
||||
Finished int64 `meddler:"commit_finished" json:"finished_at"`
|
||||
}
|
||||
|
||||
// Subscriber represents a user's subscription
|
||||
// to a repository. This determines if the repository
|
||||
// is displayed on the user dashboard and in the user
|
||||
// event feed.
|
||||
type Subscriber struct {
|
||||
// Determines if notifications should be
|
||||
// received from this repository.
|
||||
Subscribed bool `json:"subscribed"`
|
||||
|
||||
// Determines if all notifications should be
|
||||
// blocked from this repository.
|
||||
Ignored bool `json:"ignored"`
|
||||
}
|
||||
|
||||
// Perm represents a user's permissiont to access
|
||||
// a repository. Pull indicates read-only access. Push
|
||||
// indiates write access. Admin indicates god access.
|
||||
type Perm struct {
|
||||
Login string `json:"login,omitempty"`
|
||||
Pull bool `json:"pull"`
|
||||
Push bool `json:"push"`
|
||||
Admin bool `json:"admin"`
|
||||
Pull bool
|
||||
Push bool
|
||||
Admin bool
|
||||
}
|
||||
|
|
10
common/status.go
Normal file
10
common/status.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package common
|
||||
|
||||
type Status struct {
|
||||
ID int64 `meddler:"status_id,pk" json:"-"`
|
||||
CommitID int64 `meddler:"commit_id" json:"-"`
|
||||
State string `meddler:"status_state" json:"state"`
|
||||
Link string `meddler:"status_link" json:"target_url"`
|
||||
Desc string `meddler:"status_desc" json:"description"`
|
||||
Context string `meddler:"status_context" json:"context"`
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
package common
|
||||
|
||||
type Token struct {
|
||||
ID int64 `meddler:"token_id,pk" json:"-"`
|
||||
UserID int64 `meddler:"user_id" json:"-"`
|
||||
Login string `meddler:"-" json:"-"`
|
||||
Kind string `meddler:"token_kind" json:"kind,omitempty"`
|
||||
Label string `meddler:"token_label" json:"label,omitempty"`
|
||||
Expiry int64 `meddler:"token_expiry" json:"expiry,omitempty"`
|
||||
Issued int64 `meddler:"token_issued" json:"issued_at,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
TokenUser = "u"
|
||||
TokenSess = "s"
|
||||
TokenHook = "h"
|
||||
TokenAgent = "a"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Kind string `json:"kind"`
|
||||
Login string `json:"-"`
|
||||
Label string `json:"label"`
|
||||
Repos []string `json:"repos,omitempty"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
Expiry int64 `json:"expiry,omitempty"`
|
||||
Issued int64 `json:"issued_at,omitempty"`
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
package common
|
||||
|
||||
type User struct {
|
||||
Login string `json:"login,omitempty"`
|
||||
Token string `json:"-"`
|
||||
Secret string `json:"-"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Gravatar string `json:"gravatar_id,omitempty"`
|
||||
Admin bool `json:"admin,omitempty"`
|
||||
Created int64 `json:"created_at,omitempty"`
|
||||
Updated int64 `json:"updated_at,omitempty"`
|
||||
ID int64 `meddler:"user_id,pk" json:"-"`
|
||||
Login string `meddler:"user_login" json:"login,omitempty"`
|
||||
Token string `meddler:"user_token" json:"-"`
|
||||
Secret string `meddler:"user_secret" json:"-"`
|
||||
Name string `meddler:"user_name" json:"name,omitempty"`
|
||||
Email string `meddler:"user_email" json:"email,omitempty"`
|
||||
Gravatar string `meddler:"user_gravatar" json:"gravatar_id,omitempty"`
|
||||
Admin bool `meddler:"user_admin" json:"admin,omitempty"`
|
||||
Active bool `meddler:"user_active" json:"active,omitempty"`
|
||||
Created int64 `meddler:"user_created" json:"created_at,omitempty"`
|
||||
Updated int64 `meddler:"user_updated" json:"updated_at,omitempty"`
|
||||
}
|
||||
|
|
75
datastore/builtin/blob.go
Normal file
75
datastore/builtin/blob.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
type blob struct {
|
||||
ID int64 `meddler:"blob_id,pk"`
|
||||
Path string `meddler:"blob_path"`
|
||||
Data string `meddler:"blob_data,gobgzip"`
|
||||
}
|
||||
|
||||
type Blobstore struct {
|
||||
meddler.DB
|
||||
}
|
||||
|
||||
// Del removes an object from the blobstore.
|
||||
func (db *Blobstore) DelBlob(path string) error {
|
||||
var _, err = db.Exec(rebind(blobDeleteStmt), path)
|
||||
return err
|
||||
}
|
||||
|
||||
// Get retrieves an object from the blobstore.
|
||||
func (db *Blobstore) GetBlob(path string) ([]byte, error) {
|
||||
var blob = blob{}
|
||||
var err = meddler.QueryRow(db, &blob, rebind(blobQuery), path)
|
||||
return []byte(blob.Data), err
|
||||
}
|
||||
|
||||
// GetBlobReader retrieves an object from the blobstore.
|
||||
// It is the caller's responsibility to call Close on
|
||||
// the ReadCloser when finished reading.
|
||||
func (db *Blobstore) GetBlobReader(path string) (io.ReadCloser, error) {
|
||||
var blob, err = db.GetBlob(path)
|
||||
var buf = bytes.NewBuffer(blob)
|
||||
return ioutil.NopCloser(buf), err
|
||||
}
|
||||
|
||||
// SetBlob inserts an object into the blobstore.
|
||||
func (db *Blobstore) SetBlob(path string, data []byte) error {
|
||||
var blob = blob{}
|
||||
meddler.QueryRow(db, &blob, rebind(blobQuery), path)
|
||||
blob.Path = path
|
||||
blob.Data = string(data)
|
||||
return meddler.Save(db, blobTable, &blob)
|
||||
}
|
||||
|
||||
// SetBlobReader inserts an object into the blobstore by
|
||||
// consuming data from r until EOF.
|
||||
func (db *Blobstore) SetBlobReader(path string, r io.Reader) error {
|
||||
var data, _ = ioutil.ReadAll(r)
|
||||
return db.SetBlob(path, data)
|
||||
}
|
||||
|
||||
func NewBlobstore(db meddler.DB) *Blobstore {
|
||||
return &Blobstore{db}
|
||||
}
|
||||
|
||||
// Blob table name in database.
|
||||
const blobTable = "blobs"
|
||||
|
||||
const blobQuery = `
|
||||
SELECT *
|
||||
FROM blobs
|
||||
WHERE blob_path = ?;
|
||||
`
|
||||
|
||||
const blobDeleteStmt = `
|
||||
DELETE FROM blobs
|
||||
WHERE blob_path = ?;
|
||||
`
|
66
datastore/builtin/blob_test.go
Normal file
66
datastore/builtin/blob_test.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestBlobstore(t *testing.T) {
|
||||
db := mustConnectTest()
|
||||
bs := NewBlobstore(db)
|
||||
defer db.Close()
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Blobstore", func() {
|
||||
|
||||
// before each test be sure to purge the package
|
||||
// table data from the database.
|
||||
g.BeforeEach(func() {
|
||||
db.Exec("DELETE FROM blobs")
|
||||
})
|
||||
|
||||
g.It("Should Set a Blob", func() {
|
||||
err := bs.SetBlob("foo", []byte("bar"))
|
||||
g.Assert(err == nil).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should Set a Blob reader", func() {
|
||||
var buf bytes.Buffer
|
||||
buf.Write([]byte("bar"))
|
||||
err := bs.SetBlobReader("foo", &buf)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should Overwrite a Blob", func() {
|
||||
bs.SetBlob("foo", []byte("bar"))
|
||||
bs.SetBlob("foo", []byte("baz"))
|
||||
blob, err := bs.GetBlob("foo")
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(string(blob)).Equal("baz")
|
||||
})
|
||||
|
||||
g.It("Should Get a Blob", func() {
|
||||
bs.SetBlob("foo", []byte("bar"))
|
||||
blob, err := bs.GetBlob("foo")
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(string(blob)).Equal("bar")
|
||||
})
|
||||
|
||||
g.It("Should Get a Blob reader", func() {
|
||||
bs.SetBlob("foo", []byte("bar"))
|
||||
r, _ := bs.GetBlobReader("foo")
|
||||
blob, _ := ioutil.ReadAll(r)
|
||||
g.Assert(string(blob)).Equal("bar")
|
||||
})
|
||||
|
||||
g.It("Should Del a Blob", func() {
|
||||
bs.SetBlob("foo", []byte("bar"))
|
||||
err := bs.DelBlob("foo")
|
||||
g.Assert(err == nil).IsTrue()
|
||||
})
|
||||
|
||||
})
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrKeyNotFound = errors.New("Key not found")
|
||||
ErrKeyExists = errors.New("Key exists")
|
||||
)
|
||||
|
||||
var (
|
||||
bucketUser = []byte("user")
|
||||
bucketUserRepos = []byte("user_repos")
|
||||
bucketUserTokens = []byte("user_tokens")
|
||||
bucketTokens = []byte("token")
|
||||
bucketRepo = []byte("repo")
|
||||
bucketRepoKeys = []byte("repo_keys")
|
||||
bucketRepoParams = []byte("repo_params")
|
||||
bucketRepoUsers = []byte("repo_users")
|
||||
bucketBuild = []byte("build")
|
||||
bucketBuildAgent = []byte("build_agents")
|
||||
bucketBuildStatus = []byte("build_status")
|
||||
bucketBuildLogs = []byte("build_logs")
|
||||
bucketBuildSeq = []byte("build_seq")
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
*bolt.DB
|
||||
}
|
||||
|
||||
func New(path string) (*DB, error) {
|
||||
db, err := bolt.Open(path, 0600, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize all the required buckets.
|
||||
db.Update(func(tx *bolt.Tx) error {
|
||||
tx.CreateBucketIfNotExists(bucketUser)
|
||||
tx.CreateBucketIfNotExists(bucketUserRepos)
|
||||
tx.CreateBucketIfNotExists(bucketUserTokens)
|
||||
tx.CreateBucketIfNotExists(bucketTokens)
|
||||
tx.CreateBucketIfNotExists(bucketRepo)
|
||||
tx.CreateBucketIfNotExists(bucketRepoKeys)
|
||||
tx.CreateBucketIfNotExists(bucketRepoParams)
|
||||
tx.CreateBucketIfNotExists(bucketRepoUsers)
|
||||
tx.CreateBucketIfNotExists(bucketBuild)
|
||||
tx.CreateBucketIfNotExists(bucketBuildAgent)
|
||||
tx.CreateBucketIfNotExists(bucketBuildStatus)
|
||||
tx.CreateBucketIfNotExists(bucketBuildLogs)
|
||||
tx.CreateBucketIfNotExists(bucketBuildSeq)
|
||||
return nil
|
||||
})
|
||||
|
||||
return &DB{db}, nil
|
||||
}
|
||||
|
||||
func Must(path string) *DB {
|
||||
db, err := New(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return db
|
||||
}
|
|
@ -1,195 +1,62 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
// Build gets the specified build number for the
|
||||
// named repository and build number.
|
||||
func (db *DB) Build(repo string, build int) (*common.Build, error) {
|
||||
build_ := &common.Build{}
|
||||
key := []byte(repo + "/" + strconv.Itoa(build))
|
||||
|
||||
err := db.View(func(t *bolt.Tx) error {
|
||||
return get(t, bucketBuild, key, build_)
|
||||
})
|
||||
|
||||
return build_, err
|
||||
type Buildstore struct {
|
||||
meddler.DB
|
||||
}
|
||||
|
||||
// BuildList gets a list of recent builds for the
|
||||
// named repository.
|
||||
func (db *DB) BuildList(repo string) ([]*common.Build, error) {
|
||||
// TODO (bradrydzewski) we can do this more efficiently
|
||||
var builds []*common.Build
|
||||
build, err := db.BuildLast(repo)
|
||||
if err == ErrKeyNotFound {
|
||||
return builds, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.View(func(t *bolt.Tx) error {
|
||||
pos := build.Number - 25
|
||||
if pos < 1 {
|
||||
pos = 1
|
||||
}
|
||||
for i := pos; i <= build.Number; i++ {
|
||||
key := []byte(repo + "/" + strconv.Itoa(i))
|
||||
build := &common.Build{}
|
||||
err = get(t, bucketBuild, key, build)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
builds = append(builds, build)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return builds, err
|
||||
func NewBuildstore(db meddler.DB) *Buildstore {
|
||||
return &Buildstore{db}
|
||||
}
|
||||
|
||||
// BuildLast gets the last executed build for the
|
||||
// named repository.
|
||||
func (db *DB) BuildLast(repo string) (*common.Build, error) {
|
||||
key := []byte(repo)
|
||||
build := &common.Build{}
|
||||
err := db.View(func(t *bolt.Tx) error {
|
||||
raw := t.Bucket(bucketBuildSeq).Get(key)
|
||||
if raw == nil {
|
||||
return ErrKeyNotFound
|
||||
}
|
||||
num := binary.LittleEndian.Uint32(raw)
|
||||
key = []byte(repo + "/" + strconv.FormatUint(uint64(num), 10))
|
||||
return get(t, bucketBuild, key, build)
|
||||
})
|
||||
// Build returns a build by ID.
|
||||
func (db *Buildstore) Build(id int64) (*common.Build, error) {
|
||||
var build = new(common.Build)
|
||||
var err = meddler.Load(db, buildTable, build, id)
|
||||
return build, err
|
||||
}
|
||||
|
||||
// BuildAgent gets the agent that is currently executing
|
||||
// a build. If no agent exists ErrKeyNotFound is returned.
|
||||
func (db *DB) BuildAgent(repo string, build int) (*common.Agent, error) {
|
||||
key := []byte(repo + "/" + strconv.Itoa(build))
|
||||
agent := &common.Agent{}
|
||||
err := db.View(func(t *bolt.Tx) error {
|
||||
return get(t, bucketBuildAgent, key, agent)
|
||||
})
|
||||
return agent, err
|
||||
// BuildSeq returns a build by sequence number.
|
||||
func (db *Buildstore) BuildSeq(commit *common.Commit, seq int) (*common.Build, error) {
|
||||
var build = new(common.Build)
|
||||
var err = meddler.QueryRow(db, build, rebind(buildNumberQuery), commit.ID, seq)
|
||||
return build, err
|
||||
}
|
||||
|
||||
// SetBuild inserts or updates a build for the named
|
||||
// repository. The build number is incremented and
|
||||
// assigned to the provided build.
|
||||
func (db *DB) SetBuild(repo string, build *common.Build) error {
|
||||
repokey := []byte(repo)
|
||||
// BuildList returns a list of all commit builds
|
||||
func (db *Buildstore) BuildList(commit *common.Commit) ([]*common.Build, error) {
|
||||
var builds []*common.Build
|
||||
var err = meddler.QueryAll(db, &builds, rebind(buildListQuery), commit.ID)
|
||||
return builds, err
|
||||
}
|
||||
|
||||
// SetBuild updates an existing build.
|
||||
func (db *Buildstore) SetBuild(build *common.Build) error {
|
||||
build.Updated = time.Now().UTC().Unix()
|
||||
if build.Created == 0 {
|
||||
build.Created = build.Updated
|
||||
}
|
||||
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
|
||||
if build.Number == 0 {
|
||||
raw, err := raw(t, bucketBuildSeq, repokey)
|
||||
|
||||
var next_seq uint32
|
||||
switch err {
|
||||
case ErrKeyNotFound:
|
||||
next_seq = 1
|
||||
case nil:
|
||||
next_seq = 1 + binary.LittleEndian.Uint32(raw)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
|
||||
// covert our seqno to raw value
|
||||
raw = make([]byte, 4) // TODO(benschumacher) replace magic number 4 (uint32)
|
||||
binary.LittleEndian.PutUint32(raw, next_seq)
|
||||
err = t.Bucket(bucketBuildSeq).Put(repokey, raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// fill out the build structure
|
||||
build.Number = int(next_seq)
|
||||
build.Created = time.Now().UTC().Unix()
|
||||
}
|
||||
|
||||
key := []byte(repo + "/" + strconv.Itoa(build.Number))
|
||||
return update(t, bucketBuild, key, build)
|
||||
})
|
||||
return meddler.Update(db, buildTable, build)
|
||||
}
|
||||
|
||||
// Experimental
|
||||
func (db *DB) SetBuildState(repo string, build *common.Build) error {
|
||||
key := []byte(repo + "/" + strconv.Itoa(build.Number))
|
||||
// Build table name in database.
|
||||
const buildTable = "builds"
|
||||
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
build_ := &common.Build{}
|
||||
err := get(t, bucketBuild, key, build_)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
build_.Updated = time.Now().UTC().Unix()
|
||||
build_.Duration = build.Duration
|
||||
build_.Started = build.Started
|
||||
build_.Finished = build.Finished
|
||||
build_.State = build.State
|
||||
return update(t, bucketBuild, key, build_)
|
||||
})
|
||||
}
|
||||
// SQL query to retrieve a token by label.
|
||||
const buildListQuery = `
|
||||
SELECT *
|
||||
FROM builds
|
||||
WHERE commit_id = ?
|
||||
ORDER BY build_seq ASC
|
||||
`
|
||||
|
||||
func (db *DB) SetBuildStatus(repo string, build int, status *common.Status) error {
|
||||
key := []byte(repo + "/" + strconv.Itoa(build))
|
||||
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
build_ := &common.Build{}
|
||||
err := get(t, bucketBuild, key, build_)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
build_.Updated = time.Now().UTC().Unix()
|
||||
build_.Statuses = append(build_.Statuses, status)
|
||||
return update(t, bucketBuild, key, build_)
|
||||
})
|
||||
}
|
||||
|
||||
func (db *DB) SetBuildTask(repo string, build int, task *common.Task) error {
|
||||
key := []byte(repo + "/" + strconv.Itoa(build))
|
||||
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
build_ := &common.Build{}
|
||||
err := get(t, bucketBuild, key, build_)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// check index to prevent nil pointer / panic
|
||||
if task.Number > len(build_.Tasks) {
|
||||
return ErrKeyNotFound
|
||||
}
|
||||
build_.Updated = time.Now().UTC().Unix()
|
||||
//assuming task number is 1-based.
|
||||
build_.Tasks[task.Number-1] = task
|
||||
return update(t, bucketBuild, key, build_)
|
||||
})
|
||||
}
|
||||
|
||||
// SetBuildAgent insert or updates the agent that is
|
||||
// running a build.
|
||||
func (db *DB) SetBuildAgent(repo string, build int, agent *common.Agent) error {
|
||||
key := []byte(repo + "/" + strconv.Itoa(build))
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
return update(t, bucketBuildAgent, key, agent)
|
||||
})
|
||||
}
|
||||
|
||||
func (db *DB) DelBuildAgent(repo string, build int) error {
|
||||
key := []byte(repo + "/" + strconv.Itoa(build))
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
return delete(t, bucketBuildAgent, key)
|
||||
})
|
||||
}
|
||||
const buildNumberQuery = `
|
||||
SELECT *
|
||||
FROM builds
|
||||
WHERE commit_id = ?
|
||||
AND build_seq = ?
|
||||
LIMIT 1;
|
||||
`
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/common"
|
||||
. "github.com/franela/goblin"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
g := Goblin(t)
|
||||
g.Describe("Build", func() {
|
||||
var db *DB // temporary database
|
||||
repo := string("github.com/octopod/hq")
|
||||
//testUser := &common.User{Login: "octocat"}
|
||||
//testRepo := &common.Repo{FullName: "github.com/octopod/hq"}
|
||||
testUser := "octocat"
|
||||
testRepo := "github.com/octopod/hq"
|
||||
//testBuild := 1
|
||||
|
||||
// create a new database before each unit
|
||||
// test and destroy afterwards.
|
||||
g.BeforeEach(func() {
|
||||
db = Must("/tmp/drone.test.db")
|
||||
})
|
||||
g.AfterEach(func() {
|
||||
os.Remove(db.Path())
|
||||
})
|
||||
|
||||
g.It("Should sequence builds", func() {
|
||||
err := db.SetBuild(repo, &common.Build{State: "pending"})
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
// the first build should always be numero 1
|
||||
build, err := db.Build(repo, 1)
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(build.State).Equal("pending")
|
||||
|
||||
// add another build, just for fun
|
||||
err = db.SetBuild(repo, &common.Build{State: "success"})
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
// get the next build
|
||||
build, err = db.Build(repo, 2)
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(build.State).Equal("success")
|
||||
})
|
||||
|
||||
g.It("Should get the latest builds", func() {
|
||||
db.SetBuild(repo, &common.Build{State: "success"})
|
||||
db.SetBuild(repo, &common.Build{State: "success"})
|
||||
db.SetBuild(repo, &common.Build{State: "pending"})
|
||||
|
||||
build, err := db.BuildLast(repo)
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(build.State).Equal("pending")
|
||||
g.Assert(build.Number).Equal(3)
|
||||
})
|
||||
|
||||
g.It("Should get the recent list of builds", func() {
|
||||
db.SetBuild(repo, &common.Build{State: "success"})
|
||||
db.SetBuild(repo, &common.Build{State: "success"})
|
||||
db.SetBuild(repo, &common.Build{State: "pending"})
|
||||
|
||||
builds, err := db.BuildList(repo)
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(len(builds)).Equal(3)
|
||||
})
|
||||
|
||||
g.It("Should set build status: SetBuildStatus()", func() {
|
||||
//err := db.SetRepoNotExists(testUser, testRepo)
|
||||
err := db.SetRepoNotExists(&common.User{Login: testUser}, &common.Repo{FullName: testRepo})
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
db.SetBuild(repo, &common.Build{State: "error"})
|
||||
db.SetBuild(repo, &common.Build{State: "pending"})
|
||||
db.SetBuild(repo, &common.Build{State: "success"})
|
||||
err_ := db.SetBuildStatus(repo, 1, &common.Status{Context: "pending"})
|
||||
g.Assert(err_).Equal(nil)
|
||||
err_ = db.SetBuildStatus(repo, 2, &common.Status{Context: "running"})
|
||||
g.Assert(err_).Equal(nil)
|
||||
err_ = db.SetBuildStatus(repo, 3, &common.Status{Context: "success"})
|
||||
g.Assert(err_).Equal(nil)
|
||||
})
|
||||
|
||||
g.It("Should set build state: SetBuildState()", func() {
|
||||
err := db.SetRepoNotExists(&common.User{Login: testUser}, &common.Repo{FullName: testRepo})
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
db.SetBuild(repo, &common.Build{State: "error"})
|
||||
db.SetBuild(repo, &common.Build{State: "pending"})
|
||||
db.SetBuild(repo, &common.Build{State: "success"})
|
||||
err_ := db.SetBuildState(repo, &common.Build{Number: 1})
|
||||
g.Assert(err_).Equal(nil)
|
||||
err_ = db.SetBuildState(repo, &common.Build{Number: 2})
|
||||
g.Assert(err_).Equal(nil)
|
||||
err_ = db.SetBuildState(repo, &common.Build{Number: 3})
|
||||
g.Assert(err_).Equal(nil)
|
||||
})
|
||||
|
||||
g.It("Should set build task: SetBuildTask()", func() {
|
||||
err_ := db.SetRepoNotExists(&common.User{Login: testUser}, &common.Repo{FullName: testRepo})
|
||||
g.Assert(err_).Equal(nil)
|
||||
// setting up tasks.
|
||||
tasks := []*common.Task{
|
||||
&common.Task{
|
||||
Number: 1,
|
||||
State: "pending",
|
||||
ExitCode: 0,
|
||||
},
|
||||
&common.Task{
|
||||
Number: 2,
|
||||
State: "running",
|
||||
ExitCode: 0,
|
||||
},
|
||||
&common.Task{
|
||||
Number: 3,
|
||||
State: "success",
|
||||
ExitCode: 0,
|
||||
},
|
||||
}
|
||||
// setting up builds.
|
||||
err_ = db.SetBuild(repo, &common.Build{Number: 1, State: "failed", Tasks: tasks})
|
||||
g.Assert(err_).Equal(nil)
|
||||
err_ = db.SetBuildTask(repo, 1, &common.Task{Number: 1, State: "error", ExitCode: -1})
|
||||
g.Assert(err_).Equal(nil)
|
||||
db.SetBuild(repo, &common.Build{Number: 2, State: "success", Tasks: tasks})
|
||||
err_ = db.SetBuildTask(repo, 2, &common.Task{Number: 1, State: "success", ExitCode: 0})
|
||||
g.Assert(err_).Equal(nil)
|
||||
})
|
||||
})
|
||||
}
|
169
datastore/builtin/commit.go
Normal file
169
datastore/builtin/commit.go
Normal file
|
@ -0,0 +1,169 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
type Commitstore struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
func NewCommitstore(db *sql.DB) *Commitstore {
|
||||
return &Commitstore{db}
|
||||
}
|
||||
|
||||
// Commit gets a commit by ID
|
||||
func (db *Commitstore) Commit(id int64) (*common.Commit, error) {
|
||||
var commit = new(common.Commit)
|
||||
var err = meddler.Load(db, commitTable, commit, id)
|
||||
return commit, err
|
||||
}
|
||||
|
||||
// CommitSeq gets the specified commit sequence for the
|
||||
// named repository and commit number
|
||||
func (db *Commitstore) CommitSeq(repo *common.Repo, seq int) (*common.Commit, error) {
|
||||
var commit = new(common.Commit)
|
||||
var err = meddler.QueryRow(db, commit, rebind(commitNumberQuery), repo.ID, seq)
|
||||
return commit, err
|
||||
}
|
||||
|
||||
// CommitLast gets the last executed commit for the
|
||||
// named repository.
|
||||
func (db *Commitstore) CommitLast(repo *common.Repo, branch string) (*common.Commit, error) {
|
||||
var commit = new(common.Commit)
|
||||
var err = meddler.QueryRow(db, commit, rebind(commitLastQuery), repo.ID, branch)
|
||||
return commit, err
|
||||
}
|
||||
|
||||
// CommitList gets a list of recent commits for the
|
||||
// named repository.
|
||||
func (db *Commitstore) CommitList(repo *common.Repo, limit, offset int) ([]*common.Commit, error) {
|
||||
var commits []*common.Commit
|
||||
var err = meddler.QueryAll(db, &commits, rebind(commitListQuery), repo.ID, limit, offset)
|
||||
return commits, err
|
||||
}
|
||||
|
||||
// AddCommit inserts a new commit in the datastore.
|
||||
func (db *Commitstore) AddCommit(commit *common.Commit) error {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// extract the next commit number from the database
|
||||
row := tx.QueryRow(rebind(commitNumberLast), commit.RepoID)
|
||||
if row != nil {
|
||||
row.Scan(&commit.Sequence)
|
||||
}
|
||||
|
||||
commit.Sequence = commit.Sequence + 1 // increment
|
||||
commit.Created = time.Now().UTC().Unix()
|
||||
commit.Updated = time.Now().UTC().Unix()
|
||||
err = meddler.Insert(tx, commitTable, commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, build := range commit.Builds {
|
||||
build.CommitID = commit.ID
|
||||
build.Created = commit.Created
|
||||
build.Updated = commit.Updated
|
||||
err := meddler.Insert(tx, buildTable, build)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// SetCommit updates an existing commit and commit tasks.
|
||||
func (db *Commitstore) SetCommit(commit *common.Commit) error {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
commit.Updated = time.Now().UTC().Unix()
|
||||
err = meddler.Update(tx, commitTable, commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, build := range commit.Builds {
|
||||
build.Updated = commit.Updated
|
||||
err := meddler.Update(tx, buildTable, build)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// KillCommits updates all pending or started commits
|
||||
// in the datastore settings the status to killed.
|
||||
func (db *Commitstore) KillCommits() error {
|
||||
var _, err1 = db.Exec(rebind(buildKillStmt))
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
var _, err2 = db.Exec(rebind(commitKillStmt))
|
||||
return err2
|
||||
}
|
||||
|
||||
// Commit table name in database.
|
||||
const commitTable = "commits"
|
||||
|
||||
// SQL query to retrieve the latest commits across all branches.
|
||||
const commitListQuery = `
|
||||
SELECT *
|
||||
FROM commits
|
||||
WHERE repo_id = ?
|
||||
ORDER BY commit_seq DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`
|
||||
|
||||
// SQL query to retrieve a commit by number.
|
||||
const commitNumberQuery = `
|
||||
SELECT *
|
||||
FROM commits
|
||||
WHERE repo_id = ?
|
||||
AND commit_seq = ?
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
// SQL query to retrieve the most recent commit.
|
||||
// TODO exclude pull requests
|
||||
const commitLastQuery = `
|
||||
SELECT *
|
||||
FROM commits
|
||||
WHERE repo_id = ?
|
||||
AND commit_branch = ?
|
||||
ORDER BY commit_seq DESC
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
// SQL statement to cancel all running commits.
|
||||
const commitKillStmt = `
|
||||
UPDATE commits SET commit_state = 'killed'
|
||||
WHERE commit_state IN ('pending', 'running');
|
||||
`
|
||||
|
||||
// SQL statement to cancel all running commits.
|
||||
const buildKillStmt = `
|
||||
UPDATE builds SET build_state = 'killed'
|
||||
WHERE build_state IN ('pending', 'running');
|
||||
`
|
||||
|
||||
// SQL statement to retrieve the commit number for
|
||||
// a commit
|
||||
const commitNumberLast = `
|
||||
SELECT MAX(commit_seq)
|
||||
FROM commits
|
||||
WHERE repo_id = ?
|
||||
`
|
178
datastore/builtin/commit_test.go
Normal file
178
datastore/builtin/commit_test.go
Normal file
|
@ -0,0 +1,178 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestCommitstore(t *testing.T) {
|
||||
db := mustConnectTest()
|
||||
bs := NewCommitstore(db)
|
||||
defer db.Close()
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Commitstore", func() {
|
||||
|
||||
// before each test be sure to purge the package
|
||||
// table data from the database.
|
||||
g.BeforeEach(func() {
|
||||
db.Exec("DELETE FROM commits")
|
||||
db.Exec("DELETE FROM tasks")
|
||||
})
|
||||
|
||||
g.It("Should Post a Commit", func() {
|
||||
commit := common.Commit{
|
||||
RepoID: 1,
|
||||
State: common.StateSuccess,
|
||||
Ref: "refs/heads/master",
|
||||
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
err := bs.AddCommit(&commit)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(commit.ID != 0).IsTrue()
|
||||
g.Assert(commit.Sequence).Equal(1)
|
||||
})
|
||||
|
||||
g.It("Should Put a Commit", func() {
|
||||
commit := common.Commit{
|
||||
RepoID: 1,
|
||||
Sequence: 5,
|
||||
State: common.StatePending,
|
||||
Ref: "refs/heads/master",
|
||||
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
bs.AddCommit(&commit)
|
||||
commit.State = common.StateRunning
|
||||
err1 := bs.SetCommit(&commit)
|
||||
getcommit, err2 := bs.Commit(commit.ID)
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsTrue()
|
||||
g.Assert(commit.ID).Equal(getcommit.ID)
|
||||
g.Assert(commit.RepoID).Equal(getcommit.RepoID)
|
||||
g.Assert(commit.State).Equal(getcommit.State)
|
||||
g.Assert(commit.Sequence).Equal(getcommit.Sequence)
|
||||
})
|
||||
|
||||
g.It("Should Get a Commit", func() {
|
||||
commit := common.Commit{
|
||||
RepoID: 1,
|
||||
State: common.StateSuccess,
|
||||
}
|
||||
bs.AddCommit(&commit)
|
||||
getcommit, err := bs.Commit(commit.ID)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(commit.ID).Equal(getcommit.ID)
|
||||
g.Assert(commit.RepoID).Equal(getcommit.RepoID)
|
||||
g.Assert(commit.State).Equal(getcommit.State)
|
||||
})
|
||||
|
||||
g.It("Should Get a Commit by Sequence", func() {
|
||||
commit1 := &common.Commit{
|
||||
RepoID: 1,
|
||||
State: common.StatePending,
|
||||
Ref: "refs/heads/master",
|
||||
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
commit2 := &common.Commit{
|
||||
RepoID: 1,
|
||||
State: common.StatePending,
|
||||
Ref: "refs/heads/dev",
|
||||
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
err1 := bs.AddCommit(commit1)
|
||||
err2 := bs.AddCommit(commit2)
|
||||
getcommit, err3 := bs.CommitSeq(&common.Repo{ID: 1}, commit2.Sequence)
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsTrue()
|
||||
g.Assert(err3 == nil).IsTrue()
|
||||
g.Assert(commit2.ID).Equal(getcommit.ID)
|
||||
g.Assert(commit2.RepoID).Equal(getcommit.RepoID)
|
||||
g.Assert(commit2.Sequence).Equal(getcommit.Sequence)
|
||||
})
|
||||
|
||||
g.It("Should Kill Pending or Started Commits", func() {
|
||||
commit1 := &common.Commit{
|
||||
RepoID: 1,
|
||||
State: common.StateRunning,
|
||||
Ref: "refs/heads/master",
|
||||
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
commit2 := &common.Commit{
|
||||
RepoID: 1,
|
||||
State: common.StatePending,
|
||||
Ref: "refs/heads/dev",
|
||||
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
bs.AddCommit(commit1)
|
||||
bs.AddCommit(commit2)
|
||||
err1 := bs.KillCommits()
|
||||
getcommit1, err2 := bs.Commit(commit1.ID)
|
||||
getcommit2, err3 := bs.Commit(commit2.ID)
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsTrue()
|
||||
g.Assert(err3 == nil).IsTrue()
|
||||
g.Assert(getcommit1.State).Equal(common.StateKilled)
|
||||
g.Assert(getcommit2.State).Equal(common.StateKilled)
|
||||
})
|
||||
|
||||
g.It("Should get recent Commits", func() {
|
||||
commit1 := &common.Commit{
|
||||
RepoID: 1,
|
||||
State: common.StateFailure,
|
||||
Ref: "refs/heads/master",
|
||||
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
commit2 := &common.Commit{
|
||||
RepoID: 1,
|
||||
State: common.StateSuccess,
|
||||
Ref: "refs/heads/dev",
|
||||
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
bs.AddCommit(commit1)
|
||||
bs.AddCommit(commit2)
|
||||
commits, err := bs.CommitList(&common.Repo{ID: 1}, 20, 0)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(len(commits)).Equal(2)
|
||||
g.Assert(commits[0].ID).Equal(commit2.ID)
|
||||
g.Assert(commits[0].RepoID).Equal(commit2.RepoID)
|
||||
g.Assert(commits[0].State).Equal(commit2.State)
|
||||
})
|
||||
|
||||
g.It("Should get the last Commit", func() {
|
||||
commit1 := &common.Commit{
|
||||
RepoID: 1,
|
||||
State: common.StateFailure,
|
||||
Branch: "master",
|
||||
Ref: "refs/heads/master",
|
||||
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
commit2 := &common.Commit{
|
||||
RepoID: 1,
|
||||
State: common.StateFailure,
|
||||
Branch: "master",
|
||||
Ref: "refs/heads/master",
|
||||
Sha: "8d6a233744a5dcacbf2605d4592a4bfe8b37320d",
|
||||
}
|
||||
commit3 := &common.Commit{
|
||||
RepoID: 1,
|
||||
State: common.StateSuccess,
|
||||
Branch: "dev",
|
||||
Ref: "refs/heads/dev",
|
||||
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
err1 := bs.AddCommit(commit1)
|
||||
err2 := bs.AddCommit(commit2)
|
||||
err3 := bs.AddCommit(commit3)
|
||||
last, err4 := bs.CommitLast(&common.Repo{ID: 1}, "master")
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsTrue()
|
||||
g.Assert(err3 == nil).IsTrue()
|
||||
g.Assert(err4 == nil).IsTrue()
|
||||
g.Assert(last.ID).Equal(commit2.ID)
|
||||
g.Assert(last.RepoID).Equal(commit2.RepoID)
|
||||
g.Assert(last.Sequence).Equal(commit2.Sequence)
|
||||
})
|
||||
})
|
||||
}
|
92
datastore/builtin/datastore.go
Normal file
92
datastore/builtin/datastore.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
|
||||
"github.com/drone/drone/datastore"
|
||||
"github.com/drone/drone/datastore/builtin/migrate"
|
||||
|
||||
"github.com/BurntSushi/migration"
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
const (
|
||||
driverPostgres = "postgres"
|
||||
driverSqlite = "sqlite3"
|
||||
driverMysql = "mysql"
|
||||
)
|
||||
|
||||
// Connect is a helper function that establishes a new
|
||||
// database connection and auto-generates the database
|
||||
// schema. If the database already exists, it will perform
|
||||
// and update as needed.
|
||||
func Connect(driver, datasource string) (*sql.DB, error) {
|
||||
switch driver {
|
||||
case driverPostgres:
|
||||
meddler.Default = meddler.PostgreSQL
|
||||
case driverSqlite:
|
||||
meddler.Default = meddler.SQLite
|
||||
}
|
||||
|
||||
migration.DefaultGetVersion = migrate.GetVersion
|
||||
migration.DefaultSetVersion = migrate.SetVersion
|
||||
var migrations = []migration.Migrator{
|
||||
migrate.Setup,
|
||||
}
|
||||
return migration.Open(driver, datasource, migrations)
|
||||
}
|
||||
|
||||
// MustConnect is a helper function that creates a
|
||||
// new database connection and auto-generates the
|
||||
// database schema. An error causes a panic.
|
||||
func MustConnect(driver, datasource string) *sql.DB {
|
||||
db, err := Connect(driver, datasource)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// mustConnectTest is a helper function that creates a
|
||||
// new database connection using environment variables.
|
||||
// If not environment varaibles are found, the default
|
||||
// in-memory SQLite database is used.
|
||||
func mustConnectTest() *sql.DB {
|
||||
var (
|
||||
driver = os.Getenv("TEST_DRIVER")
|
||||
datasource = os.Getenv("TEST_DATASOURCE")
|
||||
)
|
||||
if len(driver) == 0 {
|
||||
driver = driverSqlite
|
||||
datasource = ":memory:"
|
||||
}
|
||||
db, err := Connect(driver, datasource)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
// New returns a new Datastore
|
||||
func New(db *sql.DB) datastore.Datastore {
|
||||
return struct {
|
||||
*Userstore
|
||||
*Repostore
|
||||
*Commitstore
|
||||
*Buildstore
|
||||
*Blobstore
|
||||
*Starstore
|
||||
*Tokenstore
|
||||
}{
|
||||
NewUserstore(db),
|
||||
NewRepostore(db),
|
||||
NewCommitstore(db),
|
||||
NewBuildstore(db),
|
||||
NewBlobstore(db),
|
||||
NewStarstore(db),
|
||||
NewTokenstore(db),
|
||||
}
|
||||
}
|
47
datastore/builtin/migrate/helper.go
Normal file
47
datastore/builtin/migrate/helper.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package migrate
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
// transform is a helper function that transforms sql
|
||||
// statements to work with multiple database types.
|
||||
func transform(stmt string) string {
|
||||
switch meddler.Default {
|
||||
case meddler.MySQL:
|
||||
stmt = strings.Replace(stmt, "AUTOINCREMENT", "AUTO_INCREMENT", -1)
|
||||
stmt = strings.Replace(stmt, "BLOB", "MEDIUMBLOB", -1)
|
||||
case meddler.PostgreSQL:
|
||||
stmt = strings.Replace(stmt, "INTEGER PRIMARY KEY AUTOINCREMENT", "SERIAL PRIMARY KEY", -1)
|
||||
stmt = strings.Replace(stmt, "BLOB", "BYTEA", -1)
|
||||
}
|
||||
return stmt
|
||||
}
|
||||
|
||||
// rebind is a helper function that changes the sql
|
||||
// bind type from ? to $ for postgres queries.
|
||||
func rebind(query string) string {
|
||||
if meddler.Default != meddler.PostgreSQL {
|
||||
return query
|
||||
}
|
||||
|
||||
qb := []byte(query)
|
||||
// Add space enough for 10 params before we have to allocate
|
||||
rqb := make([]byte, 0, len(qb)+10)
|
||||
j := 1
|
||||
for _, b := range qb {
|
||||
if b == '?' {
|
||||
rqb = append(rqb, '$')
|
||||
for _, b := range strconv.Itoa(j) {
|
||||
rqb = append(rqb, byte(b))
|
||||
}
|
||||
j++
|
||||
} else {
|
||||
rqb = append(rqb, b)
|
||||
}
|
||||
}
|
||||
return string(rqb)
|
||||
}
|
213
datastore/builtin/migrate/migrate.go
Normal file
213
datastore/builtin/migrate/migrate.go
Normal file
|
@ -0,0 +1,213 @@
|
|||
package migrate
|
||||
|
||||
import (
|
||||
"github.com/BurntSushi/migration"
|
||||
)
|
||||
|
||||
// Setup is the database migration function that
|
||||
// will setup the initial SQL database structure.
|
||||
func Setup(tx migration.LimitedTx) error {
|
||||
var stmts = []string{
|
||||
userTable,
|
||||
starTable,
|
||||
repoTable,
|
||||
repoKeyTable,
|
||||
repoKeyIndex,
|
||||
repoParamTable,
|
||||
repoParamsIndex,
|
||||
repoUserIndex,
|
||||
commitTable,
|
||||
commitRepoIndex,
|
||||
tokenTable,
|
||||
buildTable,
|
||||
buildCommitIndex,
|
||||
statusTable,
|
||||
statusCommitIndex,
|
||||
blobTable,
|
||||
}
|
||||
for _, stmt := range stmts {
|
||||
_, err := tx.Exec(transform(stmt))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var userTable = `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
user_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,user_login VARCHAR(255)
|
||||
,user_token VARCHAR(255)
|
||||
,user_secret VARCHAR(255)
|
||||
,user_name VARCHAR(255)
|
||||
,user_email VARCHAR(255)
|
||||
,user_gravatar VARCHAR(255)
|
||||
,user_admin BOOLEAN
|
||||
,user_active BOOLEAN
|
||||
,user_created INTEGER
|
||||
,user_updated INTEGER
|
||||
,UNIQUE(user_token)
|
||||
,UNIQUE(user_login)
|
||||
);
|
||||
`
|
||||
|
||||
var repoTable = `
|
||||
CREATE TABLE IF NOT EXISTS repos (
|
||||
repo_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,user_id INTEGER
|
||||
,repo_owner VARCHAR(255)
|
||||
,repo_name VARCHAR(255)
|
||||
,repo_slug VARCHAR(1024)
|
||||
,repo_token VARCHAR(255)
|
||||
,repo_lang VARCHAR(255)
|
||||
,repo_branch VARCHAR(255)
|
||||
,repo_private BOOLEAN
|
||||
,repo_trusted BOOLEAN
|
||||
,repo_link VARCHAR(1024)
|
||||
,repo_clone VARCHAR(1024)
|
||||
,repo_push BOOLEAN
|
||||
,repo_pull BOOLEAN
|
||||
,repo_public_key BLOB
|
||||
,repo_private_key BLOB
|
||||
,repo_params BLOB
|
||||
,repo_timeout INTEGER
|
||||
,repo_created INTEGER
|
||||
,repo_updated INTEGER
|
||||
,UNIQUE(repo_owner, repo_name)
|
||||
,UNIQUE(repo_slug)
|
||||
);
|
||||
`
|
||||
|
||||
var repoUserIndex = `
|
||||
CREATE INDEX repos_user_idx ON repos (user_id);
|
||||
`
|
||||
|
||||
var repoKeyTable = `
|
||||
CREATE TABLE IF NOT EXISTS repo_keys (
|
||||
keys_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,repo_id INTEGER
|
||||
,keys_public BLOB
|
||||
,keys_private BLOB
|
||||
,UNIQUE(repo_id)
|
||||
);
|
||||
`
|
||||
|
||||
var repoKeyIndex = `
|
||||
CREATE INDEX keys_repo_idx ON repo_keys (repo_id);
|
||||
`
|
||||
|
||||
var repoParamTable = `
|
||||
CREATE TABLE IF NOT EXISTS repo_params (
|
||||
param_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,repo_id INTEGER
|
||||
,param_map BLOB
|
||||
,UNIQUE(repo_id)
|
||||
);
|
||||
`
|
||||
|
||||
var repoParamsIndex = `
|
||||
CREATE INDEX params_repo_idx ON repo_params (repo_id);
|
||||
`
|
||||
|
||||
var starTable = `
|
||||
CREATE TABLE IF NOT EXISTS stars (
|
||||
star_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,user_id INTEGER
|
||||
,repo_id INTEGER
|
||||
,UNIQUE (repo_id, user_id)
|
||||
);
|
||||
`
|
||||
|
||||
var commitTable = `
|
||||
CREATE TABLE IF NOT EXISTS commits (
|
||||
commit_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,repo_id INTEGER
|
||||
,commit_seq INTEGER
|
||||
,commit_state VARCHAR(255)
|
||||
,commit_started INTEGER
|
||||
,commit_finished INTEGER
|
||||
,commit_sha VARCHAR(255)
|
||||
,commit_ref VARCHAR(255)
|
||||
,commit_branch VARCHAR(255)
|
||||
,commit_pr VARCHAR(255)
|
||||
,commit_author VARCHAR(255)
|
||||
,commit_gravatar VARCHAR(255)
|
||||
,commit_timestamp VARCHAR(255)
|
||||
,commit_message VARCHAR(1000)
|
||||
,commit_source_remote VARCHAR(255)
|
||||
,commit_source_branch VARCHAR(255)
|
||||
,commit_source_sha VARCHAR(255)
|
||||
,commit_created INTEGER
|
||||
,commit_updated INTEGER
|
||||
,UNIQUE(repo_id, commit_seq)
|
||||
,UNIQUE(repo_id, commit_sha, commit_ref)
|
||||
);
|
||||
`
|
||||
|
||||
var commitRepoIndex = `
|
||||
CREATE INDEX commits_repo_idx ON commits (repo_id);
|
||||
`
|
||||
|
||||
var tokenTable = `
|
||||
CREATE TABLE IF NOT EXISTS tokens (
|
||||
token_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,user_id INTEGER
|
||||
,token_kind VARCHAR(255)
|
||||
,token_label VARCHAR(255)
|
||||
,token_expiry INTEGER
|
||||
,token_issued INTEGER
|
||||
,UNIQUE(user_id, token_label)
|
||||
);
|
||||
`
|
||||
|
||||
var tokenUserIndex = `
|
||||
CREATE INDEX tokens_user_idx ON tokens (user_id);
|
||||
`
|
||||
|
||||
var buildTable = `
|
||||
CREATE TABLE IF NOT EXISTS builds (
|
||||
build_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,commit_id INTEGER
|
||||
,build_seq INTEGER
|
||||
,build_state VARCHAR(255)
|
||||
,build_exit INTEGER
|
||||
,build_duration INTEGER
|
||||
,build_started INTEGER
|
||||
,build_finished INTEGER
|
||||
,build_created INTEGER
|
||||
,build_updated INTEGER
|
||||
,build_env BLOB
|
||||
,UNIQUE(commit_id, build_seq)
|
||||
);
|
||||
`
|
||||
|
||||
var buildCommitIndex = `
|
||||
CREATE INDEX builds_commit_idx ON builds (commit_id);
|
||||
`
|
||||
|
||||
var statusTable = `
|
||||
CREATE TABLE IF NOT EXISTS status (
|
||||
status_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,commit_id INTEGER
|
||||
,status_state VARCHAR(255)
|
||||
,status_desc VARCHAR(2000)
|
||||
,status_link VARCHAR(2000)
|
||||
,status_context INTEGER
|
||||
,status_attachment BOOL
|
||||
,UNIQUE(commit_id, status_context)
|
||||
);
|
||||
`
|
||||
|
||||
var statusCommitIndex = `
|
||||
CREATE INDEX status_commit_idx ON status (commit_id);
|
||||
`
|
||||
|
||||
var blobTable = `
|
||||
CREATE TABLE IF NOT EXISTS blobs (
|
||||
blob_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,blob_path VARCHAR(255)
|
||||
,blob_data BLOB
|
||||
,UNIQUE(blob_path)
|
||||
);
|
||||
`
|
57
datastore/builtin/migrate/version.go
Normal file
57
datastore/builtin/migrate/version.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package migrate
|
||||
|
||||
import (
|
||||
"github.com/BurntSushi/migration"
|
||||
)
|
||||
|
||||
// GetVersion gets the migration version from the database,
|
||||
// creating the migration table if it does not already exist.
|
||||
func GetVersion(tx migration.LimitedTx) (int, error) {
|
||||
v, err := getVersion(tx)
|
||||
if err != nil {
|
||||
if err := createVersionTable(tx); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return getVersion(tx)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// SetVersion sets the migration version in the database,
|
||||
// creating the migration table if it does not already exist.
|
||||
func SetVersion(tx migration.LimitedTx, version int) error {
|
||||
if err := setVersion(tx, version); err != nil {
|
||||
if err := createVersionTable(tx); err != nil {
|
||||
return err
|
||||
}
|
||||
return setVersion(tx, version)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setVersion updates the migration version in the database.
|
||||
func setVersion(tx migration.LimitedTx, version int) error {
|
||||
_, err := tx.Exec(rebind("UPDATE migration_version SET version = ?"), version)
|
||||
return err
|
||||
}
|
||||
|
||||
// getVersion gets the migration version in the database.
|
||||
func getVersion(tx migration.LimitedTx) (int, error) {
|
||||
var version int
|
||||
row := tx.QueryRow("SELECT version FROM migration_version")
|
||||
if err := row.Scan(&version); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// createVersionTable creates the version table and inserts the
|
||||
// initial value (0) into the database.
|
||||
func createVersionTable(tx migration.LimitedTx) error {
|
||||
_, err := tx.Exec("CREATE TABLE migration_version ( version INTEGER )")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.Exec("INSERT INTO migration_version (version) VALUES (0)")
|
||||
return err
|
||||
}
|
|
@ -1,197 +1,138 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
// Repo returns the repository with the given name.
|
||||
func (db *DB) Repo(repo string) (*common.Repo, error) {
|
||||
repo_ := &common.Repo{}
|
||||
key := []byte(repo)
|
||||
|
||||
err := db.View(func(t *bolt.Tx) error {
|
||||
return get(t, bucketRepo, key, repo_)
|
||||
})
|
||||
|
||||
return repo_, err
|
||||
type Repostore struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
// RepoList returns a list of repositories for the
|
||||
// given user account.
|
||||
func (db *DB) RepoList(login string) ([]*common.Repo, error) {
|
||||
repos := []*common.Repo{}
|
||||
err := db.View(func(t *bolt.Tx) error {
|
||||
// get the index of user tokens and unmarshal
|
||||
// to a string array.
|
||||
var keys [][]byte
|
||||
err := get(t, bucketUserRepos, []byte(login), &keys)
|
||||
if err != nil && err != ErrKeyNotFound {
|
||||
return err
|
||||
}
|
||||
func NewRepostore(db *sql.DB) *Repostore {
|
||||
return &Repostore{db}
|
||||
}
|
||||
|
||||
// for each item in the index, get the repository
|
||||
// and append to the array
|
||||
for _, key := range keys {
|
||||
repo := &common.Repo{}
|
||||
err := get(t, bucketRepo, key, repo)
|
||||
if err == ErrKeyNotFound {
|
||||
// TODO if we come across ErrKeyNotFound it means
|
||||
// we need to re-build the index
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
repos = append(repos, repo)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
// Repo retrieves a specific repo from the
|
||||
// datastore for the given ID.
|
||||
func (db *Repostore) Repo(id int64) (*common.Repo, error) {
|
||||
var repo = new(common.Repo)
|
||||
var err = meddler.Load(db, repoTable, repo, id)
|
||||
return repo, err
|
||||
}
|
||||
|
||||
// RepoName retrieves a repo from the datastore
|
||||
// for the specified name.
|
||||
func (db *Repostore) RepoName(owner, name string) (*common.Repo, error) {
|
||||
var repo = new(common.Repo)
|
||||
var err = meddler.QueryRow(db, repo, rebind(repoNameQuery), owner, name)
|
||||
return repo, err
|
||||
}
|
||||
|
||||
// RepoList retrieves a list of all repos from
|
||||
// the datastore accessible by the given user ID.
|
||||
func (db *Repostore) RepoList(user *common.User) ([]*common.Repo, error) {
|
||||
var repos []*common.Repo
|
||||
var err = meddler.QueryAll(db, &repos, rebind(repoListQuery), user.ID)
|
||||
return repos, err
|
||||
}
|
||||
|
||||
// RepoParams returns the private environment parameters
|
||||
// for the given repository.
|
||||
func (db *DB) RepoParams(repo string) (map[string]string, error) {
|
||||
params := map[string]string{}
|
||||
key := []byte(repo)
|
||||
// // RepoKeys retrieves a set of repository keys from
|
||||
// // the datastore for the specified name.
|
||||
// func (db *Repostore) RepoKeypair(repo *common.Repo) (*common.Keypair, error) {
|
||||
// var keypair = new(common.Keypair)
|
||||
// var err = meddler.QueryRow(db, keypair, rebind(repoKeysQuery), repo.ID)
|
||||
// return keypair, err
|
||||
// }
|
||||
|
||||
err := db.View(func(t *bolt.Tx) error {
|
||||
return get(t, bucketRepoParams, key, ¶ms)
|
||||
})
|
||||
// // RepoParams retrieves a set of repository params from
|
||||
// // the datastore for the specified name.
|
||||
// func (db *Repostore) RepoParams(repo *common.Repo) (*common.Params, error) {
|
||||
// var params = new(common.Params)
|
||||
// var err = meddler.QueryRow(db, params, rebind(repoParamsQuery), repo.ID)
|
||||
// return params, err
|
||||
// }
|
||||
|
||||
return params, err
|
||||
}
|
||||
|
||||
// RepoKeypair returns the private and public rsa keys
|
||||
// for the given repository.
|
||||
func (db *DB) RepoKeypair(repo string) (*common.Keypair, error) {
|
||||
keypair := &common.Keypair{}
|
||||
key := []byte(repo)
|
||||
|
||||
err := db.View(func(t *bolt.Tx) error {
|
||||
return get(t, bucketRepoKeys, key, keypair)
|
||||
})
|
||||
|
||||
return keypair, err
|
||||
}
|
||||
|
||||
// SetRepo inserts or updates a repository.
|
||||
func (db *DB) SetRepo(repo *common.Repo) error {
|
||||
key := []byte(repo.FullName)
|
||||
repo.Updated = time.Now().UTC().Unix()
|
||||
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
return update(t, bucketRepo, key, repo)
|
||||
})
|
||||
}
|
||||
|
||||
// SetRepoNotExists updates a repository. If the repository
|
||||
// already exists ErrConflict is returned.
|
||||
func (db *DB) SetRepoNotExists(user *common.User, repo *common.Repo) error {
|
||||
repokey := []byte(repo.FullName)
|
||||
// AddRepo inserts a repo in the datastore.
|
||||
func (db *Repostore) AddRepo(repo *common.Repo) error {
|
||||
repo.Created = time.Now().UTC().Unix()
|
||||
repo.Updated = time.Now().UTC().Unix()
|
||||
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
userkey := []byte(user.Login)
|
||||
err := push(t, bucketUserRepos, userkey, repokey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return insert(t, bucketRepo, repokey, repo)
|
||||
})
|
||||
return meddler.Insert(db, repoTable, repo)
|
||||
}
|
||||
|
||||
// SetRepoParams inserts or updates the private
|
||||
// environment parameters for the named repository.
|
||||
func (db *DB) SetRepoParams(repo string, params map[string]string) error {
|
||||
key := []byte(repo)
|
||||
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
return update(t, bucketRepoParams, key, params)
|
||||
})
|
||||
// SetRepo updates a repo in the datastore.
|
||||
func (db *Repostore) SetRepo(repo *common.Repo) error {
|
||||
repo.Updated = time.Now().UTC().Unix()
|
||||
return meddler.Update(db, repoTable, repo)
|
||||
}
|
||||
|
||||
// SetRepoKeypair inserts or updates the private and
|
||||
// public keypair for the named repository.
|
||||
func (db *DB) SetRepoKeypair(repo string, keypair *common.Keypair) error {
|
||||
key := []byte(repo)
|
||||
// // SetRepoKeypair upserts a keypair in the datastore.
|
||||
// func (db *Repostore) SetRepoKeypair(keys *common.Keypair) error {
|
||||
// return meddler.Save(db, repoKeyTable, keys)
|
||||
// }
|
||||
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
return update(t, bucketRepoKeys, key, keypair)
|
||||
})
|
||||
// // SetRepoKeypair upserts a param set in the datastore.
|
||||
// func (db *Repostore) SetRepoParams(params *common.Params) error {
|
||||
// return meddler.Save(db, repoParamTable, params)
|
||||
// }
|
||||
|
||||
// DelRepo removes the repo from the datastore.
|
||||
func (db *Repostore) DelRepo(repo *common.Repo) error {
|
||||
var _, err = db.Exec(rebind(repoDeleteStmt), repo.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
// DelRepo deletes the repository.
|
||||
func (db *DB) DelRepo(repo *common.Repo) error {
|
||||
key := []byte(repo.FullName)
|
||||
// Repo table names in database.
|
||||
const (
|
||||
repoTable = "repos"
|
||||
repoKeyTable = "repo_keys"
|
||||
repoParamTable = "repo_params"
|
||||
)
|
||||
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
err := t.Bucket(bucketRepo).Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Bucket(bucketRepoKeys).Delete(key)
|
||||
t.Bucket(bucketRepoParams).Delete(key)
|
||||
t.Bucket(bucketBuildSeq).Delete(key)
|
||||
deleteWithPrefix(t, bucketBuild, append(key, '/'))
|
||||
deleteWithPrefix(t, bucketBuildLogs, append(key, '/'))
|
||||
deleteWithPrefix(t, bucketBuildStatus, append(key, '/'))
|
||||
// SQL statement to retrieve a Repo by name.
|
||||
const repoNameQuery = `
|
||||
SELECT *
|
||||
FROM repos
|
||||
WHERE repo_owner = ?
|
||||
AND repo_name = ?
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
// SQL statement to retrieve a list of Repos
|
||||
// with permissions for the given User ID.
|
||||
const repoListQuery = `
|
||||
SELECT r.*
|
||||
FROM
|
||||
repos r
|
||||
,stars s
|
||||
WHERE r.repo_id = s.repo_id
|
||||
AND s.user_id = ?
|
||||
`
|
||||
|
||||
// Subscribed returns true if the user is subscribed
|
||||
// to the named repository.
|
||||
//
|
||||
// TODO (bradrydzewski) we are currently storing the subscription
|
||||
// data in a wrapper element called common.Subscriber. This is
|
||||
// no longer necessary.
|
||||
func (db *DB) Subscribed(login, repo string) (bool, error) {
|
||||
sub := &common.Subscriber{}
|
||||
err := db.View(func(t *bolt.Tx) error {
|
||||
repokey := []byte(repo)
|
||||
// SQL statement to retrieve a keypair for
|
||||
// a Repository.
|
||||
const repoKeysQuery = `
|
||||
SELECT *
|
||||
FROM repo_keys
|
||||
WHERE repo_id = ?
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
// get the index of user tokens and unmarshal
|
||||
// to a string array.
|
||||
var keys [][]byte
|
||||
err := get(t, bucketUserRepos, []byte(login), &keys)
|
||||
if err != nil && err != ErrKeyNotFound {
|
||||
return err
|
||||
}
|
||||
// SQL statement to retrieve a keypair for
|
||||
// a Repository.
|
||||
const repoParamsQuery = `
|
||||
SELECT *
|
||||
FROM repo_params
|
||||
WHERE repo_id = ?
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
for _, key := range keys {
|
||||
if bytes.Equal(repokey, key) {
|
||||
sub.Subscribed = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return sub.Subscribed, err
|
||||
}
|
||||
|
||||
// SetSubscriber inserts a subscriber for the named
|
||||
// repository.
|
||||
func (db *DB) SetSubscriber(login, repo string) error {
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
userkey := []byte(login)
|
||||
repokey := []byte(repo)
|
||||
return push(t, bucketUserRepos, userkey, repokey)
|
||||
})
|
||||
}
|
||||
|
||||
// DelSubscriber removes the subscriber by login for the
|
||||
// named repository.
|
||||
func (db *DB) DelSubscriber(login, repo string) error {
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
userkey := []byte(login)
|
||||
repokey := []byte(repo)
|
||||
return splice(t, bucketUserRepos, userkey, repokey)
|
||||
})
|
||||
}
|
||||
// SQL statement to delete a User by ID.
|
||||
const (
|
||||
repoDeleteStmt = `DELETE FROM repos WHERE repo_id = ?`
|
||||
repoKeypairDeleteStmt = `DELETE FROM repo_params WHERE repo_id = ?`
|
||||
repoParamsDeleteStmt = `DELETE FROM repo_keys WHERE repo_id = ?`
|
||||
)
|
||||
|
|
|
@ -1,161 +1,169 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/drone/drone/common"
|
||||
. "github.com/franela/goblin"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestRepo(t *testing.T) {
|
||||
g := Goblin(t)
|
||||
g.Describe("Repo", func() {
|
||||
testUser := "octocat"
|
||||
testRepo := "github.com/octopod/hq"
|
||||
testRepo2 := "github.com/octopod/avengers"
|
||||
commUser := &common.User{Login: "freya"}
|
||||
var db *DB // Temp database
|
||||
func TestRepostore(t *testing.T) {
|
||||
db := mustConnectTest()
|
||||
rs := NewRepostore(db)
|
||||
ss := NewStarstore(db)
|
||||
defer db.Close()
|
||||
|
||||
// create a new database before each unit test and destroy afterwards.
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Repostore", func() {
|
||||
|
||||
// before each test be sure to purge the package
|
||||
// table data from the database.
|
||||
g.BeforeEach(func() {
|
||||
file, err := ioutil.TempFile(os.TempDir(), "drone-bolt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
db.Exec("DELETE FROM stars")
|
||||
db.Exec("DELETE FROM repos")
|
||||
db.Exec("DELETE FROM users")
|
||||
})
|
||||
|
||||
g.It("Should Set a Repo", func() {
|
||||
repo := common.Repo{
|
||||
UserID: 1,
|
||||
Owner: "bradrydzewski",
|
||||
Name: "drone",
|
||||
}
|
||||
|
||||
db = Must(file.Name())
|
||||
})
|
||||
g.AfterEach(func() {
|
||||
os.Remove(db.Path())
|
||||
err1 := rs.AddRepo(&repo)
|
||||
err2 := rs.SetRepo(&repo)
|
||||
getrepo, err3 := rs.Repo(repo.ID)
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsTrue()
|
||||
g.Assert(err3 == nil).IsTrue()
|
||||
g.Assert(repo.ID).Equal(getrepo.ID)
|
||||
})
|
||||
|
||||
g.It("Should set Repo", func() {
|
||||
err := db.SetRepo(&common.Repo{FullName: testRepo})
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
repo, err := db.Repo(testRepo)
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(repo.FullName).Equal(testRepo)
|
||||
g.It("Should Add a Repo", func() {
|
||||
repo := common.Repo{
|
||||
UserID: 1,
|
||||
Owner: "bradrydzewski",
|
||||
Name: "drone",
|
||||
}
|
||||
err := rs.AddRepo(&repo)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(repo.ID != 0).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should get Repo", func() {
|
||||
db.SetRepo(&common.Repo{FullName: testRepo})
|
||||
// g.It("Should Add a Repos Keypair", func() {
|
||||
// keypair := common.Keypair{
|
||||
// RepoID: 1,
|
||||
// Public: []byte("-----BEGIN RSA PRIVATE KEY----- ..."),
|
||||
// Private: []byte("ssh-rsa AAAAE1BzbF1xc2EABAvVA6Z ..."),
|
||||
// }
|
||||
// err := rs.SetRepoKeypair(&keypair)
|
||||
// g.Assert(err == nil).IsTrue()
|
||||
// g.Assert(keypair.ID != 0).IsTrue()
|
||||
// getkeypair, err := rs.RepoKeypair(&common.Repo{ID: 1})
|
||||
// g.Assert(err == nil).IsTrue()
|
||||
// g.Assert(keypair.ID).Equal(getkeypair.ID)
|
||||
// g.Assert(keypair.RepoID).Equal(getkeypair.RepoID)
|
||||
// g.Assert(keypair.Public).Equal(getkeypair.Public)
|
||||
// g.Assert(keypair.Private).Equal(getkeypair.Private)
|
||||
// })
|
||||
|
||||
repo, err := db.Repo(testRepo)
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(repo.FullName).Equal(testRepo)
|
||||
// g.It("Should Add a Repos Private Params", func() {
|
||||
// params := common.Params{
|
||||
// RepoID: 1,
|
||||
// Map: map[string]string{"foo": "bar"},
|
||||
// }
|
||||
// err := rs.SetRepoParams(¶ms)
|
||||
// g.Assert(err == nil).IsTrue()
|
||||
// g.Assert(params.ID != 0).IsTrue()
|
||||
// getparams, err := rs.RepoParams(&common.Repo{ID: 1})
|
||||
// g.Assert(err == nil).IsTrue()
|
||||
// g.Assert(params.ID).Equal(getparams.ID)
|
||||
// g.Assert(params.RepoID).Equal(getparams.RepoID)
|
||||
// g.Assert(params.Map).Equal(getparams.Map)
|
||||
// })
|
||||
|
||||
g.It("Should Get a Repo by ID", func() {
|
||||
repo := common.Repo{
|
||||
UserID: 1,
|
||||
Owner: "bradrydzewski",
|
||||
Name: "drone",
|
||||
}
|
||||
rs.AddRepo(&repo)
|
||||
getrepo, err := rs.Repo(repo.ID)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(repo.ID).Equal(getrepo.ID)
|
||||
g.Assert(repo.UserID).Equal(getrepo.UserID)
|
||||
g.Assert(repo.Owner).Equal(getrepo.Owner)
|
||||
g.Assert(repo.Name).Equal(getrepo.Name)
|
||||
})
|
||||
|
||||
g.It("Should be deletable", func() {
|
||||
db.SetRepo(&common.Repo{FullName: testRepo})
|
||||
|
||||
db.Repo(testRepo)
|
||||
err_ := db.DelRepo((&common.Repo{FullName: testRepo}))
|
||||
g.Assert(err_).Equal(nil)
|
||||
g.It("Should Get a Repo by Name", func() {
|
||||
repo := common.Repo{
|
||||
UserID: 1,
|
||||
Owner: "bradrydzewski",
|
||||
Name: "drone",
|
||||
}
|
||||
rs.AddRepo(&repo)
|
||||
getrepo, err := rs.RepoName(repo.Owner, repo.Name)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(repo.ID).Equal(getrepo.ID)
|
||||
g.Assert(repo.UserID).Equal(getrepo.UserID)
|
||||
g.Assert(repo.Owner).Equal(getrepo.Owner)
|
||||
g.Assert(repo.Name).Equal(getrepo.Name)
|
||||
})
|
||||
|
||||
g.It("Should cleanup builds when deleted", func() {
|
||||
repo := &common.Repo{FullName: testRepo}
|
||||
err := db.SetRepoNotExists(commUser, repo)
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
db.SetBuild(testRepo, &common.Build{State: "success"})
|
||||
db.SetBuild(testRepo, &common.Build{State: "success"})
|
||||
db.SetBuild(testRepo, &common.Build{State: "pending"})
|
||||
db.SetLogs(testRepo, 1, 1, (bytes.NewBuffer([]byte("foo"))))
|
||||
|
||||
// first a little sanity to validate our test conditions
|
||||
_, err = db.BuildLast(testRepo)
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
// now run our specific test suite
|
||||
// 1. ensure that we can delete the repo
|
||||
err = db.DelRepo(repo)
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
// 2. ensure that deleting the repo cleans up other references
|
||||
_, err = db.Build(testRepo, 1)
|
||||
g.Assert(err).Equal(ErrKeyNotFound)
|
||||
g.It("Should Get a Repo List by User", func() {
|
||||
repo1 := common.Repo{
|
||||
UserID: 1,
|
||||
Owner: "bradrydzewski",
|
||||
Name: "drone",
|
||||
}
|
||||
repo2 := common.Repo{
|
||||
UserID: 1,
|
||||
Owner: "bradrydzewski",
|
||||
Name: "drone-dart",
|
||||
}
|
||||
rs.AddRepo(&repo1)
|
||||
rs.AddRepo(&repo2)
|
||||
ss.AddStar(&common.User{ID: 1}, &repo1)
|
||||
repos, err := rs.RepoList(&common.User{ID: 1})
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(len(repos)).Equal(1)
|
||||
g.Assert(repos[0].UserID).Equal(repo1.UserID)
|
||||
g.Assert(repos[0].Owner).Equal(repo1.Owner)
|
||||
g.Assert(repos[0].Name).Equal(repo1.Name)
|
||||
})
|
||||
|
||||
g.It("Should get Repo list", func() {
|
||||
db.SetRepoNotExists(&common.User{Login: testUser}, &common.Repo{FullName: testRepo})
|
||||
db.SetRepoNotExists(&common.User{Login: testUser}, &common.Repo{FullName: testRepo2})
|
||||
|
||||
repos, err := db.RepoList(testUser)
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(len(repos)).Equal(2)
|
||||
g.It("Should Delete a Repo", func() {
|
||||
repo := common.Repo{
|
||||
UserID: 1,
|
||||
Owner: "bradrydzewski",
|
||||
Name: "drone",
|
||||
}
|
||||
rs.AddRepo(&repo)
|
||||
_, err1 := rs.Repo(repo.ID)
|
||||
err2 := rs.DelRepo(&repo)
|
||||
_, err3 := rs.Repo(repo.ID)
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsTrue()
|
||||
g.Assert(err3 == nil).IsFalse()
|
||||
})
|
||||
|
||||
g.It("Should set Repo parameters", func() {
|
||||
db.SetRepo(&common.Repo{FullName: testRepo})
|
||||
err := db.SetRepoParams(testRepo, map[string]string{"A": "Alpha"})
|
||||
g.Assert(err).Equal(nil)
|
||||
g.It("Should Enforce Unique Repo Name", func() {
|
||||
repo1 := common.Repo{
|
||||
UserID: 1,
|
||||
Owner: "bradrydzewski",
|
||||
Name: "drone",
|
||||
}
|
||||
repo2 := common.Repo{
|
||||
UserID: 2,
|
||||
Owner: "bradrydzewski",
|
||||
Name: "drone",
|
||||
}
|
||||
err1 := rs.AddRepo(&repo1)
|
||||
err2 := rs.AddRepo(&repo2)
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsFalse()
|
||||
})
|
||||
|
||||
g.It("Should get Repo parameters", func() {
|
||||
db.SetRepo(&common.Repo{FullName: testRepo})
|
||||
err := db.SetRepoParams(testRepo, map[string]string{"A": "Alpha", "B": "Beta"})
|
||||
params, err := db.RepoParams(testRepo)
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(len(params)).Equal(2)
|
||||
g.Assert(params["A"]).Equal("Alpha")
|
||||
g.Assert(params["B"]).Equal("Beta")
|
||||
})
|
||||
|
||||
// we test again with same repo/user already existing
|
||||
// to see if it will return "ErrConflict"
|
||||
g.It("Should set SetRepoNotExists", func() {
|
||||
err := db.SetRepoNotExists(&common.User{Login: testUser}, &common.Repo{FullName: testRepo})
|
||||
g.Assert(err).Equal(nil)
|
||||
// We should get ErrConflict now, trying to add the same repo again.
|
||||
err_ := db.SetRepoNotExists(&common.User{Login: testUser}, &common.Repo{FullName: testRepo})
|
||||
g.Assert(err_).Equal(ErrKeyExists)
|
||||
})
|
||||
|
||||
g.It("Should set Repo keypair", func() {
|
||||
db.SetRepo(&common.Repo{FullName: testRepo})
|
||||
|
||||
err := db.SetRepoKeypair(testRepo, &common.Keypair{Private: "A", Public: "Alpha"})
|
||||
g.Assert(err).Equal(nil)
|
||||
})
|
||||
|
||||
g.It("Should get Repo keypair", func() {
|
||||
db.SetRepo(&common.Repo{FullName: testRepo})
|
||||
err := db.SetRepoKeypair(testRepo, &common.Keypair{Private: "A", Public: "Alpha"})
|
||||
|
||||
keypair, err := db.RepoKeypair(testRepo)
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(keypair.Public).Equal("Alpha")
|
||||
g.Assert(keypair.Private).Equal("A")
|
||||
})
|
||||
|
||||
g.It("Should set subscriber", func() {
|
||||
db.SetRepo(&common.Repo{FullName: testRepo})
|
||||
err := db.SetSubscriber(testUser, testRepo)
|
||||
g.Assert(err).Equal(nil)
|
||||
})
|
||||
|
||||
g.It("Should get subscribed", func() {
|
||||
db.SetRepo(&common.Repo{FullName: testRepo})
|
||||
err := db.SetSubscriber(testUser, testRepo)
|
||||
subscribed, err := db.Subscribed(testUser, testRepo)
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(subscribed).Equal(true)
|
||||
})
|
||||
|
||||
g.It("Should del subscriber", func() {
|
||||
db.SetRepo(&common.Repo{FullName: testRepo})
|
||||
db.SetSubscriber(testUser, testRepo)
|
||||
err := db.DelSubscriber(testUser, testRepo)
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
subscribed, err := db.Subscribed(testUser, testRepo)
|
||||
g.Assert(subscribed).Equal(false)
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
|
60
datastore/builtin/star.go
Normal file
60
datastore/builtin/star.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
type Starstore struct {
|
||||
meddler.DB
|
||||
}
|
||||
|
||||
func NewStarstore(db meddler.DB) *Starstore {
|
||||
return &Starstore{db}
|
||||
}
|
||||
|
||||
// Starred returns true if the user starred
|
||||
// the given repository.
|
||||
func (db *Starstore) Starred(user *common.User, repo *common.Repo) (bool, error) {
|
||||
var star = new(star)
|
||||
err := meddler.QueryRow(db, star, rebind(starQuery), user.ID, repo.ID)
|
||||
return (err == nil), err
|
||||
}
|
||||
|
||||
// AddStar inserts a starred repo / user in the datastore.
|
||||
func (db *Starstore) AddStar(user *common.User, repo *common.Repo) error {
|
||||
var star = &star{UserID: user.ID, RepoID: repo.ID}
|
||||
return meddler.Insert(db, starTable, star)
|
||||
}
|
||||
|
||||
// DelStar removes starred repo / user from the datastore.
|
||||
func (db *Starstore) DelStar(user *common.User, repo *common.Repo) error {
|
||||
var _, err = db.Exec(rebind(starDeleteStmt), user.ID, repo.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
type star struct {
|
||||
ID int64 `meddler:"star_id,pk"`
|
||||
UserID int64 `meddler:"user_id"`
|
||||
RepoID int64 `meddler:"repo_id"`
|
||||
}
|
||||
|
||||
// Stars table name in database.
|
||||
const starTable = "stars"
|
||||
|
||||
// SQL query to retrieve a user's stars to
|
||||
// access a repository.
|
||||
const starQuery = `
|
||||
SELECT *
|
||||
FROM stars
|
||||
WHERE user_id=?
|
||||
AND repo_id=?
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
// SQL statement to delete a star by ID.
|
||||
const starDeleteStmt = `
|
||||
DELETE FROM stars
|
||||
WHERE user_id=?
|
||||
AND repo_id=?
|
||||
`
|
60
datastore/builtin/star_test.go
Normal file
60
datastore/builtin/star_test.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestStarstore(t *testing.T) {
|
||||
db := mustConnectTest()
|
||||
ss := NewStarstore(db)
|
||||
defer db.Close()
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Starstore", func() {
|
||||
|
||||
// before each test be sure to purge the package
|
||||
// table data from the database.
|
||||
g.BeforeEach(func() {
|
||||
db.Exec("DELETE FROM stars")
|
||||
})
|
||||
|
||||
g.It("Should Add a Star", func() {
|
||||
user := common.User{ID: 1}
|
||||
repo := common.Repo{ID: 2}
|
||||
err := ss.AddStar(&user, &repo)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should Get Starred", func() {
|
||||
user := common.User{ID: 1}
|
||||
repo := common.Repo{ID: 2}
|
||||
ss.AddStar(&user, &repo)
|
||||
ok, err := ss.Starred(&user, &repo)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(ok).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should Not Get Starred", func() {
|
||||
user := common.User{ID: 1}
|
||||
repo := common.Repo{ID: 2}
|
||||
ok, err := ss.Starred(&user, &repo)
|
||||
g.Assert(err != nil).IsTrue()
|
||||
g.Assert(ok).IsFalse()
|
||||
})
|
||||
|
||||
g.It("Should Del a Star", func() {
|
||||
user := common.User{ID: 1}
|
||||
repo := common.Repo{ID: 2}
|
||||
ss.AddStar(&user, &repo)
|
||||
_, err1 := ss.Starred(&user, &repo)
|
||||
err2 := ss.DelStar(&user, &repo)
|
||||
_, err3 := ss.Starred(&user, &repo)
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsTrue()
|
||||
g.Assert(err3 == nil).IsFalse()
|
||||
})
|
||||
})
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
// SetLogs inserts or updates a task logs for the
|
||||
// named repository and build number.
|
||||
func (db *DB) SetLogs(repo string, build int, task int, rd io.Reader) error {
|
||||
key := []byte(repo + "/" + strconv.Itoa(build) + "/" + strconv.Itoa(task))
|
||||
t, err := db.Begin(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log, err := ioutil.ReadAll(rd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = t.Bucket(bucketBuildLogs).Put(key, log)
|
||||
if err != nil {
|
||||
t.Rollback()
|
||||
return err
|
||||
}
|
||||
return t.Commit()
|
||||
}
|
||||
|
||||
// LogReader gets the task logs at index N for
|
||||
// the named repository and build number.
|
||||
func (db *DB) LogReader(repo string, build int, task int) (io.Reader, error) {
|
||||
key := []byte(repo + "/" + strconv.Itoa(build) + "/" + strconv.Itoa(task))
|
||||
|
||||
var log []byte
|
||||
err := db.View(func(t *bolt.Tx) error {
|
||||
var err error
|
||||
log, err = raw(t, bucketBuildLogs, key)
|
||||
return err
|
||||
})
|
||||
buf := bytes.NewBuffer(log)
|
||||
return buf, err
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/drone/drone/common"
|
||||
. "github.com/franela/goblin"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTask(t *testing.T) {
|
||||
g := Goblin(t)
|
||||
g.Describe("Tasks", func() {
|
||||
|
||||
testRepo := "octopod/hq"
|
||||
testBuild := 1
|
||||
testTask := 0
|
||||
|
||||
testLogInfo := []byte("Log Info for SetLogs()")
|
||||
var db *DB // Temp database
|
||||
|
||||
// create a new database before each unit
|
||||
// test and destroy afterwards.
|
||||
g.BeforeEach(func() {
|
||||
db = Must("/tmp/drone.test.db")
|
||||
})
|
||||
g.AfterEach(func() {
|
||||
os.Remove(db.Path())
|
||||
})
|
||||
|
||||
g.It("Should set Logs", func() {
|
||||
db.SetRepo(&common.Repo{FullName: testRepo})
|
||||
err := db.SetLogs(testRepo, testBuild, testTask, (bytes.NewBuffer(testLogInfo)))
|
||||
g.Assert(err).Equal(nil)
|
||||
})
|
||||
|
||||
g.It("Should get logs", func() {
|
||||
db.SetRepo(&common.Repo{FullName: testRepo})
|
||||
db.SetLogs(testRepo, testBuild, testTask, (bytes.NewBuffer(testLogInfo)))
|
||||
buf, err := db.LogReader(testRepo, testBuild, testTask)
|
||||
g.Assert(err).Equal(nil)
|
||||
logInfo, err := ioutil.ReadAll(buf)
|
||||
g.Assert(logInfo).Equal(testLogInfo)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -1,70 +1,74 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
// Token returns the token for the given user and label.
|
||||
func (db *DB) Token(user, label string) (*common.Token, error) {
|
||||
token := &common.Token{}
|
||||
key := []byte(user + "/" + label)
|
||||
type Tokenstore struct {
|
||||
meddler.DB
|
||||
}
|
||||
|
||||
err := db.View(func(t *bolt.Tx) error {
|
||||
return get(t, bucketTokens, key, token)
|
||||
})
|
||||
func NewTokenstore(db meddler.DB) *Tokenstore {
|
||||
return &Tokenstore{db}
|
||||
}
|
||||
|
||||
// Token returns a token by ID.
|
||||
func (db *Tokenstore) Token(id int64) (*common.Token, error) {
|
||||
var token = new(common.Token)
|
||||
var err = meddler.Load(db, tokenTable, token, id)
|
||||
return token, err
|
||||
}
|
||||
|
||||
// TokenList returns a list of all tokens for the given
|
||||
// user login.
|
||||
func (db *DB) TokenList(login string) ([]*common.Token, error) {
|
||||
tokens := []*common.Token{}
|
||||
userkey := []byte(login)
|
||||
err := db.Update(func(t *bolt.Tx) error {
|
||||
// get the index of user tokens and unmarshal
|
||||
// to a string array.
|
||||
var keys [][]byte
|
||||
err := get(t, bucketUserTokens, userkey, &keys)
|
||||
if err != nil && err != ErrKeyNotFound {
|
||||
return err
|
||||
}
|
||||
// for each item in the index, get the repository
|
||||
// and append to the array
|
||||
for _, key := range keys {
|
||||
token := &common.Token{}
|
||||
raw := t.Bucket(bucketTokens).Get(key)
|
||||
err = decode(raw, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tokens = append(tokens, token)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
// TokenLabel returns a token by label
|
||||
func (db *Tokenstore) TokenLabel(user *common.User, label string) (*common.Token, error) {
|
||||
var token = new(common.Token)
|
||||
var err = meddler.QueryRow(db, token, rebind(tokenLabelQuery), user.ID, label)
|
||||
return token, err
|
||||
}
|
||||
|
||||
// TokenList returns a list of all user tokens.
|
||||
func (db *Tokenstore) TokenList(user *common.User) ([]*common.Token, error) {
|
||||
var tokens []*common.Token
|
||||
var err = meddler.QueryAll(db, &tokens, rebind(tokenListQuery), user.ID)
|
||||
return tokens, err
|
||||
}
|
||||
|
||||
// SetToken inserts a new user token in the datastore.
|
||||
func (db *DB) SetToken(token *common.Token) error {
|
||||
key := []byte(token.Login + "/" + token.Label)
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
err := push(t, bucketUserTokens, []byte(token.Login), key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return insert(t, bucketTokens, key, token)
|
||||
})
|
||||
// AddToken inserts a new token into the datastore.
|
||||
// If the token label already exists for the user
|
||||
// an error is returned.
|
||||
func (db *Tokenstore) AddToken(token *common.Token) error {
|
||||
return meddler.Insert(db, tokenTable, token)
|
||||
}
|
||||
|
||||
// DelToken deletes the token.
|
||||
func (db *DB) DelToken(token *common.Token) error {
|
||||
key := []byte(token.Login + "/" + token.Label)
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
err := splice(t, bucketUserTokens, []byte(token.Login), key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return delete(t, bucketTokens, key)
|
||||
})
|
||||
// DelToken removes the DelToken from the datastore.
|
||||
func (db *Tokenstore) DelToken(token *common.Token) error {
|
||||
var _, err = db.Exec(rebind(tokenDeleteStmt), token.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
// Token table name in database.
|
||||
const tokenTable = "tokens"
|
||||
|
||||
// SQL query to retrieve a token by label.
|
||||
const tokenLabelQuery = `
|
||||
SELECT *
|
||||
FROM tokens
|
||||
WHERE user_id = ?
|
||||
AND token_label = ?
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
// SQL query to retrieve a list of user tokens.
|
||||
const tokenListQuery = `
|
||||
SELECT *
|
||||
FROM tokens
|
||||
WHERE user_id = ?
|
||||
ORDER BY token_label ASC
|
||||
`
|
||||
|
||||
// SQL statement to delete a Token by ID.
|
||||
const tokenDeleteStmt = `
|
||||
DELETE FROM tokens
|
||||
WHERE token_id=?
|
||||
`
|
||||
|
|
|
@ -1,65 +1,149 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
. "github.com/franela/goblin"
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestToken(t *testing.T) {
|
||||
g := Goblin(t)
|
||||
g.Describe("Tokens", func() {
|
||||
var db *DB // temporary database
|
||||
func TestTokenstore(t *testing.T) {
|
||||
db := mustConnectTest()
|
||||
ts := NewTokenstore(db)
|
||||
defer db.Close()
|
||||
|
||||
// create a new database before each unit
|
||||
// test and destroy afterwards.
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Tokenstore", func() {
|
||||
|
||||
// before each test be sure to purge the package
|
||||
// table data from the database.
|
||||
g.BeforeEach(func() {
|
||||
db = Must("/tmp/drone.test.db")
|
||||
})
|
||||
g.AfterEach(func() {
|
||||
os.Remove(db.Path())
|
||||
db.Exec("DELETE FROM tokens")
|
||||
})
|
||||
|
||||
g.It("Should list for user", func() {
|
||||
db.SetUserNotExists(&common.User{Login: "octocat"})
|
||||
err1 := db.SetToken(&common.Token{Login: "octocat", Label: "gist"})
|
||||
err2 := db.SetToken(&common.Token{Login: "octocat", Label: "github"})
|
||||
g.Assert(err1).Equal(nil)
|
||||
g.Assert(err2).Equal(nil)
|
||||
|
||||
list, err := db.TokenList("octocat")
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(len(list)).Equal(2)
|
||||
g.It("Should Add a new Token", func() {
|
||||
token := common.Token{
|
||||
UserID: 1,
|
||||
Label: "foo",
|
||||
Kind: common.TokenUser,
|
||||
Issued: time.Now().Unix(),
|
||||
Expiry: time.Now().Unix() + 1000,
|
||||
}
|
||||
err := ts.AddToken(&token)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(token.ID != 0).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should insert", func() {
|
||||
db.SetUserNotExists(&common.User{Login: "octocat"})
|
||||
err := db.SetToken(&common.Token{Login: "octocat", Label: "gist"})
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
token, err := db.Token("octocat", "gist")
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(token.Label).Equal("gist")
|
||||
g.Assert(token.Login).Equal("octocat")
|
||||
g.It("Should get a Token", func() {
|
||||
token := common.Token{
|
||||
UserID: 1,
|
||||
Label: "foo",
|
||||
Kind: common.TokenUser,
|
||||
Issued: time.Now().Unix(),
|
||||
Expiry: time.Now().Unix() + 1000,
|
||||
}
|
||||
err1 := ts.AddToken(&token)
|
||||
gettoken, err2 := ts.Token(token.ID)
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsTrue()
|
||||
g.Assert(token.ID).Equal(gettoken.ID)
|
||||
g.Assert(token.Label).Equal(gettoken.Label)
|
||||
g.Assert(token.Kind).Equal(gettoken.Kind)
|
||||
g.Assert(token.Issued).Equal(gettoken.Issued)
|
||||
g.Assert(token.Expiry).Equal(gettoken.Expiry)
|
||||
})
|
||||
|
||||
g.It("Should delete", func() {
|
||||
db.SetUserNotExists(&common.User{Login: "octocat"})
|
||||
err := db.SetToken(&common.Token{Login: "octocat", Label: "gist"})
|
||||
g.Assert(err).Equal(nil)
|
||||
g.It("Should Get a Token By Label", func() {
|
||||
token := common.Token{
|
||||
UserID: 1,
|
||||
Label: "foo",
|
||||
Kind: common.TokenUser,
|
||||
Issued: time.Now().Unix(),
|
||||
Expiry: time.Now().Unix() + 1000,
|
||||
}
|
||||
err1 := ts.AddToken(&token)
|
||||
gettoken, err2 := ts.TokenLabel(&common.User{ID: 1}, "foo")
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsTrue()
|
||||
g.Assert(token.ID).Equal(gettoken.ID)
|
||||
g.Assert(token.Label).Equal(gettoken.Label)
|
||||
g.Assert(token.Kind).Equal(gettoken.Kind)
|
||||
g.Assert(token.Issued).Equal(gettoken.Issued)
|
||||
g.Assert(token.Expiry).Equal(gettoken.Expiry)
|
||||
})
|
||||
|
||||
token, err := db.Token("octocat", "gist")
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(token.Label).Equal("gist")
|
||||
g.Assert(token.Login).Equal("octocat")
|
||||
g.It("Should Enforce Unique Token Label", func() {
|
||||
token1 := common.Token{
|
||||
UserID: 1,
|
||||
Label: "foo",
|
||||
Kind: common.TokenUser,
|
||||
Issued: time.Now().Unix(),
|
||||
Expiry: time.Now().Unix() + 1000,
|
||||
}
|
||||
token2 := common.Token{
|
||||
UserID: 1,
|
||||
Label: "foo",
|
||||
Kind: common.TokenUser,
|
||||
Issued: time.Now().Unix(),
|
||||
Expiry: time.Now().Unix() + 1000,
|
||||
}
|
||||
err1 := ts.AddToken(&token1)
|
||||
err2 := ts.AddToken(&token2)
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsFalse()
|
||||
})
|
||||
|
||||
err = db.DelToken(token)
|
||||
g.Assert(err).Equal(nil)
|
||||
g.It("Should Get a User Token List", func() {
|
||||
token1 := common.Token{
|
||||
UserID: 1,
|
||||
Label: "bar",
|
||||
Kind: common.TokenUser,
|
||||
Issued: time.Now().Unix(),
|
||||
Expiry: time.Now().Unix() + 1000,
|
||||
}
|
||||
token2 := common.Token{
|
||||
UserID: 1,
|
||||
Label: "foo",
|
||||
Kind: common.TokenUser,
|
||||
Issued: time.Now().Unix(),
|
||||
Expiry: time.Now().Unix() + 1000,
|
||||
}
|
||||
token3 := common.Token{
|
||||
UserID: 2,
|
||||
Label: "foo",
|
||||
Kind: common.TokenUser,
|
||||
Issued: time.Now().Unix(),
|
||||
Expiry: time.Now().Unix() + 1000,
|
||||
}
|
||||
ts.AddToken(&token1)
|
||||
ts.AddToken(&token2)
|
||||
ts.AddToken(&token3)
|
||||
tokens, err := ts.TokenList(&common.User{ID: 1})
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(len(tokens)).Equal(2)
|
||||
g.Assert(tokens[0].ID).Equal(token1.ID)
|
||||
g.Assert(tokens[0].Label).Equal(token1.Label)
|
||||
g.Assert(tokens[0].Kind).Equal(token1.Kind)
|
||||
g.Assert(tokens[0].Issued).Equal(token1.Issued)
|
||||
g.Assert(tokens[0].Expiry).Equal(token1.Expiry)
|
||||
})
|
||||
|
||||
token, err = db.Token("octocat", "gist")
|
||||
g.Assert(err != nil).IsTrue()
|
||||
g.It("Should Del a Token", func() {
|
||||
token := common.Token{
|
||||
UserID: 1,
|
||||
Label: "foo",
|
||||
Kind: common.TokenUser,
|
||||
Issued: time.Now().Unix(),
|
||||
Expiry: time.Now().Unix() + 1000,
|
||||
}
|
||||
ts.AddToken(&token)
|
||||
_, err1 := ts.Token(token.ID)
|
||||
err2 := ts.DelToken(&token)
|
||||
_, err3 := ts.Token(token.ID)
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsTrue()
|
||||
g.Assert(err3 == nil).IsFalse()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,85 +3,123 @@ package builtin
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
// User returns a user by user login.
|
||||
func (db *DB) User(login string) (*common.User, error) {
|
||||
user := &common.User{}
|
||||
key := []byte(login)
|
||||
|
||||
err := db.View(func(t *bolt.Tx) error {
|
||||
return get(t, bucketUser, key, user)
|
||||
})
|
||||
|
||||
return user, err
|
||||
type Userstore struct {
|
||||
meddler.DB
|
||||
}
|
||||
|
||||
// UserCount returns a count of all registered users.
|
||||
func (db *DB) UserCount() (int, error) {
|
||||
var out int
|
||||
var err = db.View(func(t *bolt.Tx) error {
|
||||
out = t.Bucket(bucketUser).Stats().KeyN
|
||||
return nil
|
||||
})
|
||||
return out, err
|
||||
func NewUserstore(db meddler.DB) *Userstore {
|
||||
return &Userstore{db}
|
||||
}
|
||||
|
||||
// User returns a user by user ID.
|
||||
func (db *Userstore) User(id int64) (*common.User, error) {
|
||||
var usr = new(common.User)
|
||||
var err = meddler.Load(db, userTable, usr, id)
|
||||
return usr, err
|
||||
}
|
||||
|
||||
// UserLogin returns a user by user login.
|
||||
func (db *Userstore) UserLogin(login string) (*common.User, error) {
|
||||
var usr = new(common.User)
|
||||
var err = meddler.QueryRow(db, usr, rebind(userLoginQuery), login)
|
||||
return usr, err
|
||||
}
|
||||
|
||||
// UserList returns a list of all registered users.
|
||||
func (db *DB) UserList() ([]*common.User, error) {
|
||||
users := []*common.User{}
|
||||
err := db.View(func(t *bolt.Tx) error {
|
||||
return t.Bucket(bucketUser).ForEach(func(key, raw []byte) error {
|
||||
user := &common.User{}
|
||||
err := decode(raw, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
users = append(users, user)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
func (db *Userstore) UserList() ([]*common.User, error) {
|
||||
var users []*common.User
|
||||
var err = meddler.QueryAll(db, &users, rebind(userListQuery))
|
||||
return users, err
|
||||
}
|
||||
|
||||
// SetUser inserts or updates a user.
|
||||
func (db *DB) SetUser(user *common.User) error {
|
||||
key := []byte(user.Login)
|
||||
user.Updated = time.Now().UTC().Unix()
|
||||
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
return update(t, bucketUser, key, user)
|
||||
})
|
||||
// UserFeed retrieves a digest of recent builds
|
||||
// from the datastore accessible to the specified user.
|
||||
func (db *Userstore) UserFeed(user *common.User, limit, offset int) ([]*common.RepoCommit, error) {
|
||||
var builds []*common.RepoCommit
|
||||
var err = meddler.QueryAll(db, &builds, rebind(userFeedQuery), user.ID, limit, offset)
|
||||
return builds, err
|
||||
}
|
||||
|
||||
// SetUserNotExists inserts a new user into the datastore.
|
||||
// If the user login already exists ErrConflict is returned.
|
||||
func (db *DB) SetUserNotExists(user *common.User) error {
|
||||
key := []byte(user.Login)
|
||||
// UserCount returns a count of all registered users.
|
||||
func (db *Userstore) UserCount() (int, error) {
|
||||
var count = struct{ Count int }{}
|
||||
var err = meddler.QueryRow(db, &count, rebind(userCountQuery))
|
||||
return count.Count, err
|
||||
}
|
||||
|
||||
// AddUser inserts a new user into the datastore.
|
||||
// If the user login already exists an error is returned.
|
||||
func (db *Userstore) AddUser(user *common.User) error {
|
||||
user.Created = time.Now().UTC().Unix()
|
||||
user.Updated = time.Now().UTC().Unix()
|
||||
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
return insert(t, bucketUser, key, user)
|
||||
})
|
||||
return meddler.Insert(db, userTable, user)
|
||||
}
|
||||
|
||||
// DelUser deletes the user.
|
||||
func (db *DB) DelUser(user *common.User) error {
|
||||
key := []byte(user.Login)
|
||||
return db.Update(func(t *bolt.Tx) error {
|
||||
err := delete(t, bucketUserTokens, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = delete(t, bucketUserRepos, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// IDEA: deleteKeys(t, bucketTokens, keys)
|
||||
deleteWithPrefix(t, bucketTokens, append(key, '/'))
|
||||
return delete(t, bucketUser, key)
|
||||
})
|
||||
// SetUser updates an existing user.
|
||||
func (db *Userstore) SetUser(user *common.User) error {
|
||||
user.Updated = time.Now().UTC().Unix()
|
||||
return meddler.Update(db, userTable, user)
|
||||
}
|
||||
|
||||
// DelUser removes the user from the datastore.
|
||||
func (db *Userstore) DelUser(user *common.User) error {
|
||||
var _, err = db.Exec(rebind(userDeleteStmt), user.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
// User table name in database.
|
||||
const userTable = "users"
|
||||
|
||||
// SQL query to retrieve a User by remote login.
|
||||
const userLoginQuery = `
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE user_login=?
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
// SQL query to retrieve a list of all users.
|
||||
const userListQuery = `
|
||||
SELECT *
|
||||
FROM users
|
||||
ORDER BY user_name ASC
|
||||
`
|
||||
|
||||
// SQL query to retrieve a list of all users.
|
||||
const userCountQuery = `
|
||||
SELECT count(1) as "Count"
|
||||
FROM users
|
||||
`
|
||||
|
||||
// SQL statement to delete a User by ID.
|
||||
const userDeleteStmt = `
|
||||
DELETE FROM users
|
||||
WHERE user_id=?
|
||||
`
|
||||
|
||||
// SQL query to retrieve a build feed for the given
|
||||
// user account.
|
||||
const userFeedQuery = `
|
||||
SELECT
|
||||
r.repo_id
|
||||
,r.repo_owner
|
||||
,r.repo_name
|
||||
,r.repo_slug
|
||||
,c.commit_seq
|
||||
,c.commit_state
|
||||
,c.commit_started
|
||||
,c.commit_finished
|
||||
FROM
|
||||
commits c
|
||||
,repos r
|
||||
,stars s
|
||||
WHERE c.repo_id = r.repo_id
|
||||
AND r.repo_id = s.repo_id
|
||||
AND s.user_id = ?
|
||||
ORDER BY c.commit_seq DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
. "github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestUser(t *testing.T) {
|
||||
g := Goblin(t)
|
||||
g.Describe("Users", func() {
|
||||
var db *DB // temporary database
|
||||
|
||||
// create a new database before each unit
|
||||
// test and destroy afterwards.
|
||||
g.BeforeEach(func() {
|
||||
db = Must("/tmp/drone.test.db")
|
||||
})
|
||||
g.AfterEach(func() {
|
||||
os.Remove(db.Path())
|
||||
})
|
||||
|
||||
g.It("Should find", func() {
|
||||
db.SetUserNotExists(&common.User{Login: "octocat"})
|
||||
user, err := db.User("octocat")
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(user.Login).Equal("octocat")
|
||||
})
|
||||
|
||||
g.It("Should insert", func() {
|
||||
err := db.SetUserNotExists(&common.User{Login: "octocat"})
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
user, err := db.User("octocat")
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(user.Login).Equal("octocat")
|
||||
g.Assert(user.Created != 0).IsTrue()
|
||||
g.Assert(user.Updated != 0).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should not insert if exists", func() {
|
||||
db.SetUser(&common.User{Login: "octocat"})
|
||||
err := db.SetUserNotExists(&common.User{Login: "octocat"})
|
||||
g.Assert(err).Equal(ErrKeyExists)
|
||||
})
|
||||
|
||||
g.It("Should update", func() {
|
||||
db.SetUserNotExists(&common.User{Login: "octocat"})
|
||||
user, err := db.User("octocat")
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
user.Email = "octocat@github.com"
|
||||
err = db.SetUser(user)
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
user_, err := db.User("octocat")
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(user_.Login).Equal(user.Login)
|
||||
g.Assert(user_.Email).Equal(user.Email)
|
||||
})
|
||||
|
||||
g.It("Should delete", func() {
|
||||
db.SetUserNotExists(&common.User{Login: "octocat"})
|
||||
user, err := db.User("octocat")
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
err = db.DelUser(user)
|
||||
g.Assert(err).Equal(nil)
|
||||
|
||||
_, err = db.User("octocat")
|
||||
g.Assert(err).Equal(ErrKeyNotFound)
|
||||
})
|
||||
|
||||
g.It("Should list", func() {
|
||||
db.SetUserNotExists(&common.User{Login: "bert"})
|
||||
db.SetUserNotExists(&common.User{Login: "ernie"})
|
||||
users, err := db.UserList()
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(len(users)).Equal(2)
|
||||
})
|
||||
|
||||
g.It("Should count", func() {
|
||||
db.SetUserNotExists(&common.User{Login: "bert"})
|
||||
db.SetUserNotExists(&common.User{Login: "ernie"})
|
||||
count, err := db.UserCount()
|
||||
g.Assert(err).Equal(nil)
|
||||
g.Assert(count).Equal(2)
|
||||
})
|
||||
})
|
||||
}
|
224
datastore/builtin/users_test.go
Normal file
224
datastore/builtin/users_test.go
Normal file
|
@ -0,0 +1,224 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestUserstore(t *testing.T) {
|
||||
db := mustConnectTest()
|
||||
us := NewUserstore(db)
|
||||
cs := NewCommitstore(db)
|
||||
rs := NewRepostore(db)
|
||||
ss := NewStarstore(db)
|
||||
defer db.Close()
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Userstore", func() {
|
||||
|
||||
// before each test be sure to purge the package
|
||||
// table data from the database.
|
||||
g.BeforeEach(func() {
|
||||
db.Exec("DELETE FROM users")
|
||||
db.Exec("DELETE FROM stars")
|
||||
db.Exec("DELETE FROM repos")
|
||||
db.Exec("DELETE FROM builds")
|
||||
db.Exec("DELETE FROM tasks")
|
||||
})
|
||||
|
||||
g.It("Should Update a User", func() {
|
||||
user := common.User{
|
||||
Login: "joe",
|
||||
Name: "Joe Sixpack",
|
||||
Email: "foo@bar.com",
|
||||
Token: "e42080dddf012c718e476da161d21ad5",
|
||||
}
|
||||
err1 := us.AddUser(&user)
|
||||
err2 := us.SetUser(&user)
|
||||
getuser, err3 := us.User(user.ID)
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsTrue()
|
||||
g.Assert(err3 == nil).IsTrue()
|
||||
g.Assert(user.ID).Equal(getuser.ID)
|
||||
})
|
||||
|
||||
g.It("Should Add a new User", func() {
|
||||
user := common.User{
|
||||
Login: "joe",
|
||||
Name: "Joe Sixpack",
|
||||
Email: "foo@bar.com",
|
||||
Token: "e42080dddf012c718e476da161d21ad5",
|
||||
}
|
||||
err := us.AddUser(&user)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(user.ID != 0).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should Get a User", func() {
|
||||
user := common.User{
|
||||
Login: "joe",
|
||||
Token: "f0b461ca586c27872b43a0685cbc2847",
|
||||
Secret: "976f22a5eef7caacb7e678d6c52f49b1",
|
||||
Name: "Joe Sixpack",
|
||||
Email: "foo@bar.com",
|
||||
Gravatar: "b9015b0857e16ac4d94a0ffd9a0b79c8",
|
||||
Active: true,
|
||||
Admin: true,
|
||||
Created: 1398065343,
|
||||
Updated: 1398065344,
|
||||
}
|
||||
us.AddUser(&user)
|
||||
getuser, err := us.User(user.ID)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(user.ID).Equal(getuser.ID)
|
||||
g.Assert(user.Login).Equal(getuser.Login)
|
||||
g.Assert(user.Token).Equal(getuser.Token)
|
||||
g.Assert(user.Secret).Equal(getuser.Secret)
|
||||
g.Assert(user.Name).Equal(getuser.Name)
|
||||
g.Assert(user.Email).Equal(getuser.Email)
|
||||
g.Assert(user.Gravatar).Equal(getuser.Gravatar)
|
||||
g.Assert(user.Active).Equal(getuser.Active)
|
||||
g.Assert(user.Admin).Equal(getuser.Admin)
|
||||
g.Assert(user.Created).Equal(getuser.Created)
|
||||
g.Assert(user.Updated).Equal(getuser.Updated)
|
||||
})
|
||||
|
||||
g.It("Should Get a User By Login", func() {
|
||||
user := common.User{
|
||||
Login: "joe",
|
||||
Name: "Joe Sixpack",
|
||||
Email: "foo@bar.com",
|
||||
Token: "e42080dddf012c718e476da161d21ad5",
|
||||
}
|
||||
us.AddUser(&user)
|
||||
getuser, err := us.UserLogin(user.Login)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(user.ID).Equal(getuser.ID)
|
||||
g.Assert(user.Login).Equal(getuser.Login)
|
||||
})
|
||||
|
||||
g.It("Should Enforce Unique User Login", func() {
|
||||
user1 := common.User{
|
||||
Login: "joe",
|
||||
Name: "Joe Sixpack",
|
||||
Email: "foo@bar.com",
|
||||
Token: "e42080dddf012c718e476da161d21ad5",
|
||||
}
|
||||
user2 := common.User{
|
||||
Login: "joe",
|
||||
Name: "Joe Sixpack",
|
||||
Email: "foo@bar.com",
|
||||
Token: "ab20g0ddaf012c744e136da16aa21ad9",
|
||||
}
|
||||
err1 := us.AddUser(&user1)
|
||||
err2 := us.AddUser(&user2)
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsFalse()
|
||||
})
|
||||
|
||||
g.It("Should Get a User List", func() {
|
||||
user1 := common.User{
|
||||
Login: "jane",
|
||||
Name: "Jane Doe",
|
||||
Email: "foo@bar.com",
|
||||
Token: "ab20g0ddaf012c744e136da16aa21ad9",
|
||||
}
|
||||
user2 := common.User{
|
||||
Login: "joe",
|
||||
Name: "Joe Sixpack",
|
||||
Email: "foo@bar.com",
|
||||
Token: "e42080dddf012c718e476da161d21ad5",
|
||||
}
|
||||
us.AddUser(&user1)
|
||||
us.AddUser(&user2)
|
||||
users, err := us.UserList()
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(len(users)).Equal(2)
|
||||
g.Assert(users[0].Login).Equal(user1.Login)
|
||||
g.Assert(users[0].Name).Equal(user1.Name)
|
||||
g.Assert(users[0].Email).Equal(user1.Email)
|
||||
g.Assert(users[0].Token).Equal(user1.Token)
|
||||
})
|
||||
|
||||
g.It("Should Get a User Count", func() {
|
||||
user1 := common.User{
|
||||
Login: "jane",
|
||||
Name: "Jane Doe",
|
||||
Email: "foo@bar.com",
|
||||
Token: "ab20g0ddaf012c744e136da16aa21ad9",
|
||||
}
|
||||
user2 := common.User{
|
||||
Login: "joe",
|
||||
Name: "Joe Sixpack",
|
||||
Email: "foo@bar.com",
|
||||
Token: "e42080dddf012c718e476da161d21ad5",
|
||||
}
|
||||
us.AddUser(&user1)
|
||||
us.AddUser(&user2)
|
||||
count, err := us.UserCount()
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(count).Equal(2)
|
||||
})
|
||||
|
||||
g.It("Should Del a User", func() {
|
||||
user := common.User{
|
||||
Login: "joe",
|
||||
Name: "Joe Sixpack",
|
||||
Email: "foo@bar.com",
|
||||
Token: "e42080dddf012c718e476da161d21ad5",
|
||||
}
|
||||
us.AddUser(&user)
|
||||
_, err1 := us.User(user.ID)
|
||||
err2 := us.DelUser(&user)
|
||||
_, err3 := us.User(user.ID)
|
||||
g.Assert(err1 == nil).IsTrue()
|
||||
g.Assert(err2 == nil).IsTrue()
|
||||
g.Assert(err3 == nil).IsFalse()
|
||||
})
|
||||
|
||||
g.It("Should get the Build feed for a User", func() {
|
||||
repo1 := &common.Repo{
|
||||
UserID: 1,
|
||||
Owner: "bradrydzewski",
|
||||
Name: "drone",
|
||||
}
|
||||
repo2 := &common.Repo{
|
||||
UserID: 2,
|
||||
Owner: "drone",
|
||||
Name: "drone",
|
||||
}
|
||||
rs.AddRepo(repo1)
|
||||
rs.AddRepo(repo2)
|
||||
ss.AddStar(&common.User{ID: 1}, repo1)
|
||||
commit1 := &common.Commit{
|
||||
RepoID: 1,
|
||||
State: common.StateFailure,
|
||||
Ref: "refs/heads/master",
|
||||
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
commit2 := &common.Commit{
|
||||
RepoID: 1,
|
||||
State: common.StateSuccess,
|
||||
Ref: "refs/heads/dev",
|
||||
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
commit3 := &common.Commit{
|
||||
RepoID: 2,
|
||||
State: common.StateSuccess,
|
||||
Ref: "refs/heads/dev",
|
||||
Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac",
|
||||
}
|
||||
cs.AddCommit(commit1)
|
||||
cs.AddCommit(commit2)
|
||||
cs.AddCommit(commit3)
|
||||
commits, err := us.UserFeed(&common.User{ID: 1}, 20, 0)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(len(commits)).Equal(2)
|
||||
g.Assert(commits[0].State).Equal(commit2.State)
|
||||
g.Assert(commits[0].Owner).Equal("bradrydzewski")
|
||||
g.Assert(commits[0].Name).Equal("drone")
|
||||
})
|
||||
})
|
||||
}
|
|
@ -1,115 +1,32 @@
|
|||
package builtin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
//"github.com/youtube/vitess/go/bson"
|
||||
"encoding/gob"
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
func encode(v interface{}) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
var err = gob.NewEncoder(&buf).Encode(v)
|
||||
return buf.Bytes(), err
|
||||
//return bson.Marshal(v)
|
||||
}
|
||||
|
||||
func decode(raw []byte, v interface{}) error {
|
||||
var buf bytes.Buffer
|
||||
buf.Write(raw)
|
||||
var err = gob.NewDecoder(&buf).Decode(v)
|
||||
return err
|
||||
//return bson.Unmarshal(raw, v)
|
||||
}
|
||||
|
||||
func get(t *bolt.Tx, bucket, key []byte, v interface{}) error {
|
||||
raw := t.Bucket(bucket).Get(key)
|
||||
if raw == nil {
|
||||
return ErrKeyNotFound
|
||||
// rebind is a helper function that changes the sql
|
||||
// bind type from ? to $ for postgres queries.
|
||||
func rebind(query string) string {
|
||||
if meddler.Default != meddler.PostgreSQL {
|
||||
return query
|
||||
}
|
||||
return decode(raw, v)
|
||||
}
|
||||
|
||||
func raw(t *bolt.Tx, bucket, key []byte) ([]byte, error) {
|
||||
raw := t.Bucket(bucket).Get(key)
|
||||
if raw == nil {
|
||||
return nil, ErrKeyNotFound
|
||||
}
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func update(t *bolt.Tx, bucket, key []byte, v interface{}) error {
|
||||
raw, err := encode(v)
|
||||
if err != nil {
|
||||
t.Rollback()
|
||||
return err
|
||||
}
|
||||
return t.Bucket(bucket).Put(key, raw)
|
||||
}
|
||||
|
||||
func insert(t *bolt.Tx, bucket, key []byte, v interface{}) error {
|
||||
raw, err := encode(v)
|
||||
if err != nil {
|
||||
t.Rollback()
|
||||
return err
|
||||
}
|
||||
// verify the key does not already exists
|
||||
// in the bucket. If exists, fail
|
||||
if t.Bucket(bucket).Get(key) != nil {
|
||||
return ErrKeyExists
|
||||
}
|
||||
return t.Bucket(bucket).Put(key, raw)
|
||||
}
|
||||
|
||||
func delete(t *bolt.Tx, bucket, key []byte) error {
|
||||
return t.Bucket(bucket).Delete(key)
|
||||
}
|
||||
|
||||
func push(t *bolt.Tx, bucket, index, value []byte) error {
|
||||
var keys [][]byte
|
||||
err := get(t, bucket, index, &keys)
|
||||
if err != nil && err != ErrKeyNotFound {
|
||||
return err
|
||||
}
|
||||
// we shouldn't add a key that already exists
|
||||
for _, key := range keys {
|
||||
if bytes.Equal(key, value) {
|
||||
return nil
|
||||
qb := []byte(query)
|
||||
// Add space enough for 5 params before we have to allocate
|
||||
rqb := make([]byte, 0, len(qb)+5)
|
||||
j := 1
|
||||
for _, b := range qb {
|
||||
if b == '?' {
|
||||
rqb = append(rqb, '$')
|
||||
for _, b := range strconv.Itoa(j) {
|
||||
rqb = append(rqb, byte(b))
|
||||
}
|
||||
j++
|
||||
} else {
|
||||
rqb = append(rqb, b)
|
||||
}
|
||||
}
|
||||
keys = append(keys, value)
|
||||
return update(t, bucket, index, &keys)
|
||||
}
|
||||
|
||||
func splice(t *bolt.Tx, bucket, index, value []byte) error {
|
||||
var keys [][]byte
|
||||
err := get(t, bucket, index, &keys)
|
||||
if err != nil && err != ErrKeyNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, key := range keys {
|
||||
if bytes.Equal(key, value) {
|
||||
keys = keys[:i+copy(keys[i:], keys[i+1:])]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return update(t, bucket, index, &keys)
|
||||
}
|
||||
|
||||
func deleteWithPrefix(t *bolt.Tx, bucket, prefix []byte) error {
|
||||
var err error
|
||||
|
||||
c := t.Bucket(bucket).Cursor()
|
||||
for k, _ := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, _ = c.Next() {
|
||||
err = c.Delete()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// only error here is if our Tx is read-only
|
||||
return err
|
||||
return string(rqb)
|
||||
}
|
||||
|
|
|
@ -1,144 +1,149 @@
|
|||
package datastore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrConflict = errors.New("Key not unique")
|
||||
ErrKeyNotFound = errors.New("Key not found")
|
||||
)
|
||||
|
||||
type Datastore interface {
|
||||
// User returns a user by user login.
|
||||
User(string) (*common.User, error)
|
||||
|
||||
// UserCount returns a count of all registered users.
|
||||
UserCount() (int, error)
|
||||
// User returns a user by user ID.
|
||||
User(id int64) (*common.User, error)
|
||||
|
||||
// UserLogin returns a user by user login.
|
||||
UserLogin(string) (*common.User, error)
|
||||
|
||||
// UserList returns a list of all registered users.
|
||||
UserList() ([]*common.User, error)
|
||||
|
||||
// SetUser inserts or updates a user.
|
||||
// UserFeed retrieves a digest of recent builds
|
||||
// from the datastore accessible to the specified user.
|
||||
UserFeed(*common.User, int, int) ([]*common.RepoCommit, error)
|
||||
|
||||
// UserCount returns a count of all registered users.
|
||||
UserCount() (int, error)
|
||||
|
||||
// AddUser inserts a new user into the datastore.
|
||||
// If the user login already exists an error is returned.
|
||||
AddUser(*common.User) error
|
||||
|
||||
// SetUser updates an existing user.
|
||||
SetUser(*common.User) error
|
||||
|
||||
// SetUserNotExists inserts a new user into the datastore.
|
||||
// If the user login already exists ErrConflict is returned.
|
||||
SetUserNotExists(*common.User) error
|
||||
|
||||
// Del deletes the user.
|
||||
// DelUser removes the user from the datastore.
|
||||
DelUser(*common.User) error
|
||||
|
||||
// Token returns the token for the given user and label.
|
||||
Token(string, string) (*common.Token, error)
|
||||
// Token returns a token by ID.
|
||||
Token(int64) (*common.Token, error)
|
||||
|
||||
// TokenList returns a list of all tokens for the given
|
||||
// user login.
|
||||
TokenList(string) ([]*common.Token, error)
|
||||
// TokenLabel returns a token by label
|
||||
TokenLabel(*common.User, string) (*common.Token, error)
|
||||
|
||||
// SetToken inserts a new user token in the datastore.
|
||||
SetToken(*common.Token) error
|
||||
// TokenList returns a list of all user tokens.
|
||||
TokenList(*common.User) ([]*common.Token, error)
|
||||
|
||||
// DelToken deletes the token.
|
||||
// AddToken inserts a new token into the datastore.
|
||||
// If the token label already exists for the user
|
||||
// an error is returned.
|
||||
AddToken(*common.Token) error
|
||||
|
||||
// DelToken removes the DelToken from the datastore.
|
||||
DelToken(*common.Token) error
|
||||
|
||||
// Subscribed returns true if the user is subscribed
|
||||
// to the named repository.
|
||||
Subscribed(string, string) (bool, error)
|
||||
//
|
||||
|
||||
// SetSubscriber inserts a subscriber for the named
|
||||
// repository.
|
||||
SetSubscriber(string, string) error
|
||||
// Starred returns true if the user starred
|
||||
// the given repository.
|
||||
Starred(*common.User, *common.Repo) (bool, error)
|
||||
|
||||
// DelSubscriber removes the subscriber by login for the
|
||||
// named repository.
|
||||
DelSubscriber(string, string) error
|
||||
// AddStar stars a repository.
|
||||
AddStar(*common.User, *common.Repo) error
|
||||
|
||||
// Repo returns the repository with the given name.
|
||||
Repo(string) (*common.Repo, error)
|
||||
// DelStar unstars a repository.
|
||||
DelStar(*common.User, *common.Repo) error
|
||||
|
||||
// RepoList returns a list of repositories for the
|
||||
// given user account.
|
||||
RepoList(string) ([]*common.Repo, error)
|
||||
//
|
||||
|
||||
// RepoParams returns the private environment parameters
|
||||
// for the given repository.
|
||||
RepoParams(string) (map[string]string, error)
|
||||
// Repo retrieves a specific repo from the
|
||||
// datastore for the given ID.
|
||||
Repo(id int64) (*common.Repo, error)
|
||||
|
||||
// RepoKeypair returns the private and public rsa keys
|
||||
// for the given repository.
|
||||
RepoKeypair(string) (*common.Keypair, error)
|
||||
// RepoName retrieves a repo from the datastore
|
||||
// for the specified name.
|
||||
RepoName(owner, name string) (*common.Repo, error)
|
||||
|
||||
// SetRepo inserts or updates a repository.
|
||||
// RepoList retrieves a list of all repos from
|
||||
// the datastore accessible by the given user ID.
|
||||
RepoList(*common.User) ([]*common.Repo, error)
|
||||
|
||||
// AddRepo inserts a repo in the datastore.
|
||||
AddRepo(*common.Repo) error
|
||||
|
||||
// SetRepo updates a repo in the datastore.
|
||||
SetRepo(*common.Repo) error
|
||||
|
||||
// SetRepo updates a repository. If the repository
|
||||
// already exists ErrConflict is returned.
|
||||
SetRepoNotExists(*common.User, *common.Repo) error
|
||||
|
||||
// SetRepoParams inserts or updates the private
|
||||
// environment parameters for the named repository.
|
||||
SetRepoParams(string, map[string]string) error
|
||||
|
||||
// SetRepoKeypair inserts or updates the private and
|
||||
// public keypair for the named repository.
|
||||
SetRepoKeypair(string, *common.Keypair) error
|
||||
|
||||
// DelRepo deletes the repository.
|
||||
// DelRepo removes the repo from the datastore.
|
||||
DelRepo(*common.Repo) error
|
||||
|
||||
// Build gets the specified build number for the
|
||||
// named repository and build number
|
||||
Build(string, int) (*common.Build, error)
|
||||
//
|
||||
|
||||
// BuildList gets a list of recent builds for the
|
||||
// Commit gets a commit by ID
|
||||
Commit(int64) (*common.Commit, error)
|
||||
|
||||
// CommitSeq gets the specified commit sequence for the
|
||||
// named repository and commit number
|
||||
CommitSeq(*common.Repo, int) (*common.Commit, error)
|
||||
|
||||
// CommitLast gets the last executed commit for the
|
||||
// named repository and branch
|
||||
CommitLast(*common.Repo, string) (*common.Commit, error)
|
||||
|
||||
// CommitList gets a list of recent commits for the
|
||||
// named repository.
|
||||
BuildList(string) ([]*common.Build, error)
|
||||
CommitList(*common.Repo, int, int) ([]*common.Commit, error)
|
||||
|
||||
// BuildLast gets the last executed build for the
|
||||
// named repository.
|
||||
BuildLast(string) (*common.Build, error)
|
||||
// AddCommit inserts a new commit in the datastore.
|
||||
AddCommit(*common.Commit) error
|
||||
|
||||
// BuildAgent returns the agent that is being
|
||||
// used to execute the build.
|
||||
BuildAgent(string, int) (*common.Agent, error)
|
||||
// SetCommit updates an existing commit and commit tasks.
|
||||
SetCommit(*common.Commit) error
|
||||
|
||||
// SetBuild inserts or updates a build for the named
|
||||
// repository. The build number is incremented and
|
||||
// assigned to the provided build.
|
||||
SetBuild(string, *common.Build) error
|
||||
// KillCommits updates all pending or started commits
|
||||
// in the datastore settings the status to killed.
|
||||
KillCommits() error
|
||||
|
||||
// SetBuildState updates an existing build's start time,
|
||||
// finish time, duration and state. No other fields are
|
||||
// updated.
|
||||
SetBuildState(string, *common.Build) error
|
||||
//
|
||||
|
||||
// SetBuildStatus appends a new build status to an
|
||||
// existing build record.
|
||||
SetBuildStatus(string, int, *common.Status) error
|
||||
// Build returns a build by ID.
|
||||
Build(int64) (*common.Build, error)
|
||||
|
||||
// SetBuildTask updates an existing build task. The build
|
||||
// and task must already exist. If the task does not exist
|
||||
// an error is returned.
|
||||
SetBuildTask(string, int, *common.Task) error
|
||||
// BuildSeq returns a build by sequence number.
|
||||
BuildSeq(*common.Commit, int) (*common.Build, error)
|
||||
|
||||
// SetBuildAgent insert or updates the agent that is
|
||||
// running a build.
|
||||
SetBuildAgent(string, int, *common.Agent) error
|
||||
// BuildList returns a list of all commit builds
|
||||
BuildList(*common.Commit) ([]*common.Build, error)
|
||||
|
||||
// DelBuildAgent purges the referce to the agent
|
||||
// that ran a build.
|
||||
DelBuildAgent(string, int) error
|
||||
// SetBuild updates an existing build.
|
||||
SetBuild(*common.Build) error
|
||||
|
||||
// LogReader gets the task logs at index N for
|
||||
// the named repository and build number.
|
||||
LogReader(string, int, int) (io.Reader, error)
|
||||
//
|
||||
|
||||
// SetLogs inserts or updates a task logs for the
|
||||
// named repository and build number.
|
||||
SetLogs(string, int, int, io.Reader) error
|
||||
// Get retrieves an object from the blobstore.
|
||||
GetBlob(path string) ([]byte, error)
|
||||
|
||||
// GetBlobReader retrieves an object from the blobstore.
|
||||
// It is the caller's responsibility to call Close on
|
||||
// the ReadCloser when finished reading.
|
||||
GetBlobReader(path string) (io.ReadCloser, error)
|
||||
|
||||
// Set inserts an object into the blobstore.
|
||||
SetBlob(path string, data []byte) error
|
||||
|
||||
// SetBlobReader inserts an object into the blobstore by
|
||||
// consuming data from r until EOF.
|
||||
SetBlobReader(path string, r io.Reader) error
|
||||
|
||||
// Del removes an object from the blobstore.
|
||||
DelBlob(path string) error
|
||||
}
|
||||
|
||||
|
|
39
drone.go
39
drone.go
|
@ -31,8 +31,9 @@ func main() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
store := store.Must(settings.Database.Path)
|
||||
defer store.Close()
|
||||
db := store.MustConnect(settings.Database.Driver, settings.Database.Datasource)
|
||||
store := store.New(db)
|
||||
defer db.Close()
|
||||
|
||||
remote := github.New(settings.Service)
|
||||
session := session.New(settings.Session)
|
||||
|
@ -101,12 +102,12 @@ func main() {
|
|||
repo.POST("/watch", server.Subscribe)
|
||||
repo.DELETE("/unwatch", server.Unsubscribe)
|
||||
|
||||
repo.GET("/builds", server.GetBuilds)
|
||||
repo.GET("/builds/:number", server.GetBuild)
|
||||
repo.GET("/builds", server.GetCommits)
|
||||
repo.GET("/builds/:number", server.GetCommit)
|
||||
repo.POST("/builds/:number", server.RunBuild)
|
||||
repo.DELETE("/builds/:number", server.KillBuild)
|
||||
repo.GET("/logs/:number/:task", server.GetBuildLogs)
|
||||
repo.POST("/status/:number", server.PostBuildStatus)
|
||||
repo.GET("/logs/:number/:task", server.GetLogs)
|
||||
// repo.POST("/status/:number", server.PostBuildStatus)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,20 +124,20 @@ func main() {
|
|||
hooks.POST("", server.PostHook)
|
||||
}
|
||||
|
||||
queue := api.Group("/queue")
|
||||
{
|
||||
queue.Use(server.MustAgent())
|
||||
queue.GET("", server.GetQueue)
|
||||
queue.POST("/pull", server.PollBuild)
|
||||
// queue := api.Group("/queue")
|
||||
// {
|
||||
// queue.Use(server.MustAgent())
|
||||
// queue.GET("", server.GetQueue)
|
||||
// queue.POST("/pull", server.PollBuild)
|
||||
|
||||
push := queue.Group("/push/:owner/:name")
|
||||
{
|
||||
push.Use(server.SetRepo())
|
||||
push.POST("", server.PushBuild)
|
||||
push.POST("/:build", server.PushTask)
|
||||
push.POST("/:build/:task/logs", server.PushLogs)
|
||||
}
|
||||
}
|
||||
// push := queue.Group("/push/:owner/:name")
|
||||
// {
|
||||
// push.Use(server.SetRepo())
|
||||
// push.POST("", server.PushBuild)
|
||||
// push.POST("/:build", server.PushTask)
|
||||
// push.POST("/:build/:task/logs", server.PushLogs)
|
||||
// }
|
||||
// }
|
||||
|
||||
stream := api.Group("/stream")
|
||||
{
|
||||
|
|
|
@ -9,12 +9,12 @@ import (
|
|||
// Work represents an item for work to be
|
||||
// processed by a worker.
|
||||
type Work struct {
|
||||
User *common.User `json:"user"`
|
||||
Repo *common.Repo `json:"repo"`
|
||||
Build *common.Build `json:"build"`
|
||||
Keys *common.Keypair `json:"keypair"`
|
||||
Netrc *common.Netrc `json:"netrc"`
|
||||
Yaml []byte `json:"yaml"`
|
||||
User *common.User `json:"user"`
|
||||
Repo *common.Repo `json:"repo"`
|
||||
Commit *common.Commit `json:"commit"`
|
||||
Keys *common.Keypair `json:"keypair"`
|
||||
Netrc *common.Netrc `json:"netrc"`
|
||||
Yaml []byte `json:"yaml"`
|
||||
}
|
||||
|
||||
// represents a worker that has connected
|
||||
|
|
|
@ -5,7 +5,9 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/drone/drone/settings"
|
||||
|
@ -103,15 +105,18 @@ func (g *GitHub) Repo(u *common.User, owner, name string) (*common.Repo, error)
|
|||
repo := &common.Repo{}
|
||||
repo.Owner = owner
|
||||
repo.Name = name
|
||||
|
||||
if repo_.Language != nil {
|
||||
repo.Language = *repo_.Language
|
||||
}
|
||||
repo.FullName = *repo_.FullName
|
||||
repo.Link = *repo_.HTMLURL
|
||||
repo.Private = *repo_.Private
|
||||
repo.Clone = *repo_.CloneURL
|
||||
|
||||
if repo_.Language != nil {
|
||||
repo.Language = *repo_.Language
|
||||
}
|
||||
if repo_.DefaultBranch != nil {
|
||||
repo.Branch = *repo_.DefaultBranch
|
||||
}
|
||||
|
||||
if g.PrivateMode {
|
||||
repo.Private = true
|
||||
}
|
||||
|
@ -132,7 +137,6 @@ func (g *GitHub) Perm(u *common.User, owner, name string) (*common.Perm, error)
|
|||
return nil, err
|
||||
}
|
||||
m := &common.Perm{}
|
||||
m.Login = u.Login
|
||||
m.Admin = (*repo.Permissions)["admin"]
|
||||
m.Push = (*repo.Permissions)["push"]
|
||||
m.Pull = (*repo.Permissions)["pull"]
|
||||
|
@ -142,13 +146,13 @@ func (g *GitHub) Perm(u *common.User, owner, name string) (*common.Perm, error)
|
|||
|
||||
// Script fetches the build script (.drone.yml) from the remote
|
||||
// repository and returns in string format.
|
||||
func (g *GitHub) Script(u *common.User, r *common.Repo, b *common.Build) ([]byte, error) {
|
||||
func (g *GitHub) Script(u *common.User, r *common.Repo, c *common.Commit) ([]byte, error) {
|
||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||
var sha string
|
||||
if b.Commit != nil {
|
||||
sha = b.Commit.Sha
|
||||
if len(c.SourceSha) == 0 {
|
||||
sha = c.Sha
|
||||
} else {
|
||||
sha = b.PullRequest.Source.Sha
|
||||
sha = c.SourceSha
|
||||
}
|
||||
return GetFile(client, r.Owner, r.Name, ".drone.yml", sha)
|
||||
}
|
||||
|
@ -208,23 +212,21 @@ func (g *GitHub) Deactivate(u *common.User, r *common.Repo, link string) error {
|
|||
return DeleteHook(client, r.Owner, r.Name, link)
|
||||
}
|
||||
|
||||
func (g *GitHub) Status(u *common.User, r *common.Repo, b *common.Build, link string) error {
|
||||
func (g *GitHub) Status(u *common.User, r *common.Repo, c *common.Commit, link string) error {
|
||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||
var ref string
|
||||
if b.Commit != nil {
|
||||
ref = b.Commit.Ref
|
||||
} else {
|
||||
ref = b.PullRequest.Source.Ref
|
||||
if len(c.PullRequest) == 0 {
|
||||
return nil
|
||||
}
|
||||
status := getStatus(b.State)
|
||||
desc := getDesc(b.State)
|
||||
|
||||
status := getStatus(c.State)
|
||||
desc := getDesc(c.State)
|
||||
data := github.RepoStatus{
|
||||
Context: github.String("Drone"),
|
||||
State: github.String(status),
|
||||
Description: github.String(desc),
|
||||
TargetURL: github.String(link),
|
||||
}
|
||||
_, _, err := client.Repositories.CreateStatus(r.Owner, r.Name, ref, &data)
|
||||
_, _, err := client.Repositories.CreateStatus(r.Owner, r.Name, c.SourceSha, &data)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -253,23 +255,27 @@ func (g *GitHub) push(r *http.Request) (*common.Hook, error) {
|
|||
|
||||
repo := &common.Repo{}
|
||||
repo.Owner = hook.Repo.Owner.Login
|
||||
if len(repo.Owner) == 0 {
|
||||
repo.Owner = hook.Repo.Owner.Name
|
||||
}
|
||||
repo.Name = hook.Repo.Name
|
||||
repo.Language = hook.Repo.Language
|
||||
repo.FullName = hook.Repo.FullName
|
||||
repo.Link = hook.Repo.HTMLURL
|
||||
repo.Private = hook.Repo.Private
|
||||
repo.Clone = hook.Repo.CloneURL
|
||||
repo.Language = hook.Repo.Language
|
||||
repo.Branch = hook.Repo.DefaultBranch
|
||||
|
||||
commit := &common.Commit{}
|
||||
commit.Sha = hook.Head.ID
|
||||
commit.Ref = hook.Ref
|
||||
commit.Branch = strings.Replace(commit.Ref, "refs/heads/", "", -1)
|
||||
commit.Message = hook.Head.Message
|
||||
commit.Timestamp = hook.Head.Timestamp
|
||||
|
||||
commit.Author = &common.Author{}
|
||||
commit.Author.Name = hook.Head.Author.Name
|
||||
commit.Author.Email = hook.Head.Author.Email
|
||||
commit.Author.Login = hook.Head.Author.Username
|
||||
commit.Author = hook.Head.Author.Username
|
||||
// commit.Author.Name = hook.Head.Author.Name
|
||||
// commit.Author.Email = hook.Head.Author.Email
|
||||
// commit.Author.Login = hook.Head.Author.Username
|
||||
|
||||
// we should ignore github pages
|
||||
if commit.Ref == "refs/heads/gh-pages" {
|
||||
|
@ -301,32 +307,31 @@ func (g *GitHub) pullRequest(r *http.Request) (*common.Hook, error) {
|
|||
repo := &common.Repo{}
|
||||
repo.Owner = *hook.Repo.Owner.Login
|
||||
repo.Name = *hook.Repo.Name
|
||||
repo.Language = *hook.Repo.Language
|
||||
repo.FullName = *hook.Repo.FullName
|
||||
repo.Link = *hook.Repo.HTMLURL
|
||||
repo.Private = *hook.Repo.Private
|
||||
repo.Clone = *hook.Repo.CloneURL
|
||||
if hook.Repo.Language != nil {
|
||||
repo.Language = *hook.Repo.Language
|
||||
}
|
||||
if hook.Repo.DefaultBranch != nil {
|
||||
repo.Branch = *hook.Repo.DefaultBranch
|
||||
}
|
||||
|
||||
pr := &common.PullRequest{}
|
||||
pr.Number = *hook.PullRequest.Number
|
||||
pr.Title = *hook.PullRequest.Title
|
||||
c := &common.Commit{}
|
||||
c.PullRequest = strconv.Itoa(*hook.PullRequest.Number)
|
||||
c.Message = *hook.PullRequest.Title
|
||||
c.Sha = *hook.PullRequest.Base.SHA
|
||||
c.Ref = *hook.PullRequest.Base.Ref
|
||||
c.Ref = fmt.Sprintf("refs/pull/%s/merge", c.PullRequest)
|
||||
c.Branch = *hook.PullRequest.Base.Ref
|
||||
c.Timestamp = time.Now().UTC().Format("2006-01-02 15:04:05.000000000 +0000 MST")
|
||||
c.Author = *hook.PullRequest.Base.User.Login
|
||||
c.SourceRemote = *hook.PullRequest.Head.Repo.CloneURL
|
||||
c.SourceBranch = *hook.PullRequest.Head.Ref
|
||||
c.SourceSha = *hook.PullRequest.Head.SHA
|
||||
|
||||
pr.Source = &common.Commit{}
|
||||
pr.Source.Sha = *hook.PullRequest.Head.SHA
|
||||
pr.Source.Ref = *hook.PullRequest.Head.Ref
|
||||
pr.Source.Author = &common.Author{}
|
||||
pr.Source.Author.Login = *hook.PullRequest.User.Login
|
||||
|
||||
pr.Source.Remote = &common.Remote{}
|
||||
pr.Source.Remote.Clone = *hook.PullRequest.Head.Repo.CloneURL
|
||||
pr.Source.Remote.Name = *hook.PullRequest.Head.Repo.Name
|
||||
pr.Source.Remote.FullName = *hook.PullRequest.Head.Repo.FullName
|
||||
|
||||
pr.Target = &common.Commit{}
|
||||
pr.Target.Sha = *hook.PullRequest.Base.SHA
|
||||
pr.Target.Ref = *hook.PullRequest.Base.Ref
|
||||
|
||||
return &common.Hook{Repo: repo, PullRequest: pr}, nil
|
||||
return &common.Hook{Repo: repo, Commit: c}, nil
|
||||
}
|
||||
|
||||
type pushHook struct {
|
||||
|
@ -353,13 +358,15 @@ type pushHook struct {
|
|||
Repo struct {
|
||||
Owner struct {
|
||||
Login string `json:"login"`
|
||||
Name string `json:"name"`
|
||||
} `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"full_name"`
|
||||
Language string `json:"language"`
|
||||
Private bool `json:"private"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
CloneURL string `json:"clone_url"`
|
||||
Name string `json:"name"`
|
||||
FullName string `json:"full_name"`
|
||||
Language string `json:"language"`
|
||||
Private bool `json:"private"`
|
||||
HTMLURL string `json:"html_url"`
|
||||
CloneURL string `json:"clone_url"`
|
||||
DefaultBranch string `json:"default_branch"`
|
||||
} `json:"repository"`
|
||||
}
|
||||
|
||||
|
|
|
@ -23,11 +23,11 @@ type Remote interface {
|
|||
|
||||
// Script fetches the build script (.drone.yml) from the remote
|
||||
// repository and returns in string format.
|
||||
Script(u *common.User, r *common.Repo, b *common.Build) ([]byte, error)
|
||||
Script(u *common.User, r *common.Repo, c *common.Commit) ([]byte, error)
|
||||
|
||||
// Status sends the commit status to the remote system.
|
||||
// An example would be the GitHub pull request status.
|
||||
Status(u *common.User, r *common.Repo, b *common.Build, link string) error
|
||||
Status(u *common.User, r *common.Repo, c *common.Commit, link string) error
|
||||
|
||||
// Netrc returns a .netrc file that can be used to clone
|
||||
// private repositories from a remote system.
|
||||
|
|
|
@ -2,8 +2,6 @@ package builtin
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -51,42 +49,41 @@ func (r *Runner) Run(w *queue.Work) error {
|
|||
worker.Remove()
|
||||
}
|
||||
|
||||
// if any part of the build fails and leaves
|
||||
// if any part of the commit fails and leaves
|
||||
// behind orphan sub-builds we need to cleanup
|
||||
// after ourselves.
|
||||
if w.Build.State == common.StateRunning {
|
||||
if w.Commit.State == common.StateRunning {
|
||||
// if any tasks are running or pending
|
||||
// we should mark them as complete.
|
||||
for _, t := range w.Build.Tasks {
|
||||
if t.State == common.StateRunning {
|
||||
t.State = common.StateError
|
||||
t.Finished = time.Now().UTC().Unix()
|
||||
t.Duration = t.Finished - t.Started
|
||||
for _, b := range w.Commit.Builds {
|
||||
if b.State == common.StateRunning {
|
||||
b.State = common.StateError
|
||||
b.Finished = time.Now().UTC().Unix()
|
||||
b.Duration = b.Finished - b.Started
|
||||
}
|
||||
if t.State == common.StatePending {
|
||||
t.State = common.StateError
|
||||
t.Started = time.Now().UTC().Unix()
|
||||
t.Finished = time.Now().UTC().Unix()
|
||||
t.Duration = 0
|
||||
if b.State == common.StatePending {
|
||||
b.State = common.StateError
|
||||
b.Started = time.Now().UTC().Unix()
|
||||
b.Finished = time.Now().UTC().Unix()
|
||||
b.Duration = 0
|
||||
}
|
||||
r.SetTask(w.Repo, w.Build, t)
|
||||
r.SetBuild(w.Repo, w.Commit, b)
|
||||
}
|
||||
// must populate build start
|
||||
if w.Build.Started == 0 {
|
||||
w.Build.Started = time.Now().UTC().Unix()
|
||||
if w.Commit.Started == 0 {
|
||||
w.Commit.Started = time.Now().UTC().Unix()
|
||||
}
|
||||
// mark the build as complete (with error)
|
||||
w.Build.State = common.StateError
|
||||
w.Build.Finished = time.Now().UTC().Unix()
|
||||
w.Build.Duration = w.Build.Finished - w.Build.Started
|
||||
r.SetBuild(w.User, w.Repo, w.Build)
|
||||
w.Commit.State = common.StateError
|
||||
w.Commit.Finished = time.Now().UTC().Unix()
|
||||
r.SetCommit(w.User, w.Repo, w.Commit)
|
||||
}
|
||||
}()
|
||||
|
||||
// marks the build as running
|
||||
w.Build.Started = time.Now().UTC().Unix()
|
||||
w.Build.State = common.StateRunning
|
||||
err := r.SetBuild(w.User, w.Repo, w.Build)
|
||||
w.Commit.Started = time.Now().UTC().Unix()
|
||||
w.Commit.State = common.StateRunning
|
||||
err := r.SetCommit(w.User, w.Repo, w.Commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -101,23 +98,23 @@ func (r *Runner) Run(w *queue.Work) error {
|
|||
|
||||
// loop through and execute the build and
|
||||
// clone steps for each build task.
|
||||
for _, task := range w.Build.Tasks {
|
||||
for _, task := range w.Commit.Builds {
|
||||
|
||||
// marks the task as running
|
||||
task.State = common.StateRunning
|
||||
task.Started = time.Now().UTC().Unix()
|
||||
err = r.SetTask(w.Repo, w.Build, task)
|
||||
err = r.SetBuild(w.Repo, w.Commit, task)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
work := &work{
|
||||
Repo: w.Repo,
|
||||
Build: w.Build,
|
||||
Keys: w.Keys,
|
||||
Netrc: w.Netrc,
|
||||
Yaml: w.Yaml,
|
||||
Task: task,
|
||||
Repo: w.Repo,
|
||||
Commit: w.Commit,
|
||||
Keys: w.Keys,
|
||||
Netrc: w.Netrc,
|
||||
Yaml: w.Yaml,
|
||||
Build: task,
|
||||
}
|
||||
in, err := json.Marshal(work)
|
||||
if err != nil {
|
||||
|
@ -125,7 +122,7 @@ func (r *Runner) Run(w *queue.Work) error {
|
|||
}
|
||||
worker := newWorkerTimeout(client, w.Repo.Timeout+10) // 10 minute buffer
|
||||
workers = append(workers, worker)
|
||||
cname := cname(w.Repo.FullName, w.Build.Number, task.Number)
|
||||
cname := cname(task)
|
||||
state, builderr := worker.Build(cname, in)
|
||||
|
||||
switch {
|
||||
|
@ -154,7 +151,7 @@ func (r *Runner) Run(w *queue.Work) error {
|
|||
defer rc.Close()
|
||||
StdCopy(&buf, &buf, rc)
|
||||
}
|
||||
err = r.SetLogs(w.Repo, w.Build, task, ioutil.NopCloser(&buf))
|
||||
err = r.SetLogs(w.Repo, w.Commit, task, ioutil.NopCloser(&buf))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -162,7 +159,7 @@ func (r *Runner) Run(w *queue.Work) error {
|
|||
// update the task in the datastore
|
||||
task.Finished = time.Now().UTC().Unix()
|
||||
task.Duration = task.Finished - task.Started
|
||||
err = r.SetTask(w.Repo, w.Build, task)
|
||||
err = r.SetBuild(w.Repo, w.Commit, task)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -170,28 +167,28 @@ func (r *Runner) Run(w *queue.Work) error {
|
|||
|
||||
// update the build state if any of the sub-tasks
|
||||
// had a non-success status
|
||||
w.Build.State = common.StateSuccess
|
||||
for _, task := range w.Build.Tasks {
|
||||
if task.State != common.StateSuccess {
|
||||
w.Build.State = task.State
|
||||
w.Commit.State = common.StateSuccess
|
||||
for _, build := range w.Commit.Builds {
|
||||
if build.State != common.StateSuccess {
|
||||
w.Commit.State = build.State
|
||||
break
|
||||
}
|
||||
}
|
||||
err = r.SetBuild(w.User, w.Repo, w.Build)
|
||||
err = r.SetCommit(w.User, w.Repo, w.Commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// loop through and execute the notifications and
|
||||
// the destroy all containers afterward.
|
||||
for i, task := range w.Build.Tasks {
|
||||
for i, build := range w.Commit.Builds {
|
||||
work := &work{
|
||||
Repo: w.Repo,
|
||||
Build: w.Build,
|
||||
Keys: w.Keys,
|
||||
Netrc: w.Netrc,
|
||||
Yaml: w.Yaml,
|
||||
Task: task,
|
||||
Repo: w.Repo,
|
||||
Commit: w.Commit,
|
||||
Keys: w.Keys,
|
||||
Netrc: w.Netrc,
|
||||
Yaml: w.Yaml,
|
||||
Build: build,
|
||||
}
|
||||
in, err := json.Marshal(work)
|
||||
if err != nil {
|
||||
|
@ -204,21 +201,21 @@ func (r *Runner) Run(w *queue.Work) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) Cancel(repo string, build, task int) error {
|
||||
func (r *Runner) Cancel(build *common.Build) error {
|
||||
client, err := dockerclient.NewDockerClient(DockerHost, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.StopContainer(cname(repo, build, task), 30)
|
||||
return client.StopContainer(cname(build), 30)
|
||||
}
|
||||
|
||||
func (r *Runner) Logs(repo string, build, task int) (io.ReadCloser, error) {
|
||||
func (r *Runner) Logs(build *common.Build) (io.ReadCloser, error) {
|
||||
client, err := dockerclient.NewDockerClient(DockerHost, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// make sure this container actually exists
|
||||
info, err := client.InspectContainer(cname(repo, build, task))
|
||||
info, err := client.InspectContainer(cname(build))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -252,12 +249,8 @@ func (r *Runner) Logs(repo string, build, task int) (io.ReadCloser, error) {
|
|||
return pr, nil
|
||||
}
|
||||
|
||||
func cname(repo string, number, task int) string {
|
||||
s := fmt.Sprintf("%s/%d/%d", repo, number, task)
|
||||
h := sha1.New()
|
||||
h.Write([]byte(s))
|
||||
hash := hex.EncodeToString(h.Sum(nil))[:10]
|
||||
return fmt.Sprintf("drone-%s", hash)
|
||||
func cname(build *common.Build) string {
|
||||
return fmt.Sprintf("drone-%d", build.ID)
|
||||
}
|
||||
|
||||
func (r *Runner) Poll(q queue.Queue) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package builtin
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
|
@ -11,9 +12,9 @@ import (
|
|||
)
|
||||
|
||||
type Updater interface {
|
||||
SetBuild(*common.User, *common.Repo, *common.Build) error
|
||||
SetTask(*common.Repo, *common.Build, *common.Task) error
|
||||
SetLogs(*common.Repo, *common.Build, *common.Task, io.ReadCloser) error
|
||||
SetCommit(*common.User, *common.Repo, *common.Commit) error
|
||||
SetBuild(*common.Repo, *common.Commit, *common.Build) error
|
||||
SetLogs(*common.Repo, *common.Commit, *common.Build, io.ReadCloser) error
|
||||
}
|
||||
|
||||
// NewUpdater returns an implementation of the Updater interface
|
||||
|
@ -28,29 +29,15 @@ type updater struct {
|
|||
remote remote.Remote
|
||||
}
|
||||
|
||||
func (u *updater) SetBuild(user *common.User, r *common.Repo, b *common.Build) error {
|
||||
err := u.store.SetBuildState(r.FullName, b)
|
||||
func (u *updater) SetCommit(user *common.User, r *common.Repo, c *common.Commit) error {
|
||||
err := u.store.SetCommit(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if the build is complete we may need to update
|
||||
if b.State != common.StatePending && b.State != common.StateRunning {
|
||||
repo, err := u.store.Repo(r.FullName)
|
||||
if err == nil {
|
||||
if repo.Last == nil || b.Number >= repo.Last.Number {
|
||||
repo.Last = b
|
||||
u.store.SetRepo(repo)
|
||||
}
|
||||
}
|
||||
// TODO invoke remote.Status to set the build status
|
||||
|
||||
// err = u.remote.Status(user, r, b, "")
|
||||
// if err != nil {
|
||||
//
|
||||
// }
|
||||
}
|
||||
|
||||
msg, err := json.Marshal(b)
|
||||
msg, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -63,13 +50,13 @@ func (u *updater) SetBuild(user *common.User, r *common.Repo, b *common.Build) e
|
|||
return nil
|
||||
}
|
||||
|
||||
func (u *updater) SetTask(r *common.Repo, b *common.Build, t *common.Task) error {
|
||||
err := u.store.SetBuildTask(r.FullName, b.Number, t)
|
||||
func (u *updater) SetBuild(r *common.Repo, c *common.Commit, b *common.Build) error {
|
||||
err := u.store.SetBuild(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg, err := json.Marshal(b)
|
||||
msg, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -82,12 +69,7 @@ func (u *updater) SetTask(r *common.Repo, b *common.Build, t *common.Task) error
|
|||
return nil
|
||||
}
|
||||
|
||||
func (u *updater) SetLogs(r *common.Repo, b *common.Build, t *common.Task, rc io.ReadCloser) error {
|
||||
//defer rc.Close()
|
||||
//out, err := ioutil.ReadAll(rc)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//return u.store.SetLogs(r.FullName, b.Number, t.Number, out)
|
||||
return u.store.SetLogs(r.FullName, b.Number, t.Number, rc)
|
||||
func (u *updater) SetLogs(r *common.Repo, c *common.Commit, b *common.Build, rc io.ReadCloser) error {
|
||||
path := fmt.Sprintf("/logs/%s/%v/%v", r.FullName, c.Sequence, b.Sequence)
|
||||
return u.store.SetBlobReader(path, rc)
|
||||
}
|
||||
|
|
|
@ -49,12 +49,12 @@ var (
|
|||
)
|
||||
|
||||
type work struct {
|
||||
Repo *common.Repo `json:"repo"`
|
||||
Build *common.Build `json:"build"`
|
||||
Task *common.Task `json:"task"`
|
||||
Keys *common.Keypair `json:"keys"`
|
||||
Netrc *common.Netrc `json:"netrc"`
|
||||
Yaml []byte `json:"yaml"`
|
||||
Repo *common.Repo `json:"repo"`
|
||||
Commit *common.Commit `json:"commit"`
|
||||
Build *common.Build `json:"build"`
|
||||
Keys *common.Keypair `json:"keys"`
|
||||
Netrc *common.Netrc `json:"netrc"`
|
||||
Yaml []byte `json:"yaml"`
|
||||
}
|
||||
|
||||
type worker struct {
|
||||
|
|
|
@ -9,14 +9,14 @@ import (
|
|||
|
||||
type Runner interface {
|
||||
Run(work *queue.Work) error
|
||||
Cancel(repo string, build, task int) error
|
||||
Logs(repo string, build, task int) (io.ReadCloser, error)
|
||||
Cancel(*common.Build) error
|
||||
Logs(*common.Build) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// Updater defines a set of functions that are required for
|
||||
// the runner to sent Drone updates during a build.
|
||||
type Updater interface {
|
||||
SetBuild(*common.Repo, *common.Build) error
|
||||
SetTask(*common.Repo, *common.Build, *common.Task) error
|
||||
SetLogs(*common.Repo, *common.Build, *common.Task, io.ReadCloser) error
|
||||
SetCommit(*common.User, *common.Repo, *common.Commit) error
|
||||
SetBuild(*common.Repo, *common.Commit, *common.Build) error
|
||||
SetLogs(*common.Repo, *common.Commit, *common.Build, io.ReadCloser) error
|
||||
}
|
||||
|
|
|
@ -24,6 +24,11 @@ var (
|
|||
//
|
||||
func GetBadge(c *gin.Context) {
|
||||
var repo = ToRepo(c)
|
||||
var store = ToDatastore(c)
|
||||
var branch = c.Request.FormValue("branch")
|
||||
if len(branch) == 0 {
|
||||
branch = repo.Branch
|
||||
}
|
||||
|
||||
// an SVG response is always served, even when error, so
|
||||
// we can go ahead and set the content type appropriately.
|
||||
|
@ -32,12 +37,13 @@ func GetBadge(c *gin.Context) {
|
|||
// if no commit was found then display
|
||||
// the 'none' badge, instead of throwing
|
||||
// an error response
|
||||
if repo.Last == nil {
|
||||
commit, err := store.CommitLast(repo, branch)
|
||||
if err != nil {
|
||||
c.Writer.Write(badgeNone)
|
||||
return
|
||||
}
|
||||
|
||||
switch repo.Last.State {
|
||||
switch commit.State {
|
||||
case common.StateSuccess:
|
||||
c.Writer.Write(badgeSuccess)
|
||||
case common.StateFailure:
|
||||
|
@ -61,14 +67,14 @@ func GetBadge(c *gin.Context) {
|
|||
func GetCC(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
last, err := store.BuildLast(repo.FullName)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
list, err := store.CommitList(repo, 1, 0)
|
||||
if err != nil || len(list) == 0 {
|
||||
c.AbortWithStatus(404)
|
||||
return
|
||||
}
|
||||
|
||||
link := httputil.GetURL(c.Request) + "/" + repo.FullName
|
||||
cc := ccmenu.NewCC(repo, last, link)
|
||||
cc := ccmenu.NewCC(repo, list[0], link)
|
||||
|
||||
c.Writer.Header().Set("Content-Type", "application/xml")
|
||||
c.XML(200, cc)
|
||||
|
|
263
server/builds.go
263
server/builds.go
|
@ -1,263 +0,0 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/drone/drone/parser/inject"
|
||||
"github.com/drone/drone/queue"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
// GetBuild accepts a request to retrieve a build
|
||||
// from the datastore for the given repository and
|
||||
// build number.
|
||||
//
|
||||
// GET /api/builds/:owner/:name/:number
|
||||
//
|
||||
func GetBuild(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
num, err := strconv.Atoi(c.Params.ByName("number"))
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
return
|
||||
}
|
||||
build, err := store.Build(repo.FullName, num)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
} else {
|
||||
c.JSON(200, build)
|
||||
}
|
||||
}
|
||||
|
||||
// GetBuilds accepts a request to retrieve a list
|
||||
// of builds from the datastore for the given repository.
|
||||
//
|
||||
// GET /api/builds/:owner/:name
|
||||
//
|
||||
func GetBuilds(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
builds, err := store.BuildList(repo.FullName)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
} else {
|
||||
c.JSON(200, builds)
|
||||
}
|
||||
}
|
||||
|
||||
// GetBuildLogs accepts a request to retrieve logs from the
|
||||
// datastore for the given repository, build and task
|
||||
// number.
|
||||
//
|
||||
// GET /api/repos/:owner/:name/logs/:number/:task
|
||||
//
|
||||
func GetBuildLogs(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
full, _ := strconv.ParseBool(c.Params.ByName("full"))
|
||||
build, _ := strconv.Atoi(c.Params.ByName("number"))
|
||||
task, _ := strconv.Atoi(c.Params.ByName("task"))
|
||||
|
||||
r, err := store.LogReader(repo.FullName, build, task)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
} else if full {
|
||||
io.Copy(c.Writer, r)
|
||||
} else {
|
||||
io.Copy(c.Writer, io.LimitReader(r, 2000000))
|
||||
}
|
||||
}
|
||||
|
||||
// PostBuildStatus accepts a request to create a new build
|
||||
// status. The created user status is returned in JSON
|
||||
// format if successful.
|
||||
//
|
||||
// POST /api/repos/:owner/:name/status/:number
|
||||
//
|
||||
func PostBuildStatus(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
num, err := strconv.Atoi(c.Params.ByName("number"))
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
return
|
||||
}
|
||||
in := &common.Status{}
|
||||
if !c.BindWith(in, binding.JSON) {
|
||||
c.AbortWithStatus(400)
|
||||
return
|
||||
}
|
||||
if err := store.SetBuildStatus(repo.Name, num, in); err != nil {
|
||||
c.Fail(400, err)
|
||||
} else {
|
||||
c.JSON(201, in)
|
||||
}
|
||||
}
|
||||
|
||||
// RunBuild accepts a request to restart an existing build.
|
||||
//
|
||||
// POST /api/builds/:owner/:name/builds/:number
|
||||
//
|
||||
func RunBuild(c *gin.Context) {
|
||||
remote := ToRemote(c)
|
||||
store := ToDatastore(c)
|
||||
queue_ := ToQueue(c)
|
||||
repo := ToRepo(c)
|
||||
num, err := strconv.Atoi(c.Params.ByName("number"))
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
return
|
||||
}
|
||||
build, err := store.Build(repo.FullName, num)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
keys, err := store.RepoKeypair(repo.FullName)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := store.User(repo.User.Login)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
// must not restart a running build
|
||||
if build.State == common.StatePending || build.State == common.StateRunning {
|
||||
c.AbortWithStatus(409)
|
||||
return
|
||||
}
|
||||
|
||||
build.State = common.StatePending
|
||||
build.Started = 0
|
||||
build.Finished = 0
|
||||
build.Duration = 0
|
||||
build.Statuses = []*common.Status{}
|
||||
for _, task := range build.Tasks {
|
||||
task.State = common.StatePending
|
||||
task.Started = 0
|
||||
task.Finished = 0
|
||||
task.ExitCode = 0
|
||||
}
|
||||
|
||||
err = store.SetBuild(repo.FullName, build)
|
||||
if err != nil {
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
netrc, err := remote.Netrc(user)
|
||||
if err != nil {
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
// featch the .drone.yml file from the database
|
||||
raw, err := remote.Script(user, repo, build)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
// inject any private parameters into the .drone.yml
|
||||
params, _ := store.RepoParams(repo.FullName)
|
||||
if params != nil && len(params) != 0 {
|
||||
raw = []byte(inject.InjectSafe(string(raw), params))
|
||||
}
|
||||
|
||||
c.JSON(202, build)
|
||||
|
||||
queue_.Publish(&queue.Work{
|
||||
User: user,
|
||||
Repo: repo,
|
||||
Build: build,
|
||||
Keys: keys,
|
||||
Netrc: netrc,
|
||||
Yaml: raw,
|
||||
})
|
||||
}
|
||||
|
||||
// KillBuild accepts a request to kill a running build.
|
||||
//
|
||||
// DELETE /api/builds/:owner/:name/builds/:number
|
||||
//
|
||||
func KillBuild(c *gin.Context) {
|
||||
runner := ToRunner(c)
|
||||
queue := ToQueue(c)
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
num, err := strconv.Atoi(c.Params.ByName("number"))
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
return
|
||||
}
|
||||
build, err := store.Build(repo.FullName, num)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
// must not restart a running build
|
||||
if build.State != common.StatePending && build.State != common.StateRunning {
|
||||
c.Fail(409, err)
|
||||
return
|
||||
}
|
||||
|
||||
// remove from the queue if exists
|
||||
for _, item := range queue.Items() {
|
||||
if item.Repo.FullName == repo.FullName && item.Build.Number == build.Number {
|
||||
queue.Remove(item)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
build.State = common.StateKilled
|
||||
build.Finished = time.Now().Unix()
|
||||
if build.Started == 0 {
|
||||
build.Started = build.Finished
|
||||
}
|
||||
build.Duration = build.Finished - build.Started
|
||||
for _, task := range build.Tasks {
|
||||
if task.State != common.StatePending && task.State != common.StateRunning {
|
||||
continue
|
||||
}
|
||||
task.State = common.StateKilled
|
||||
task.Started = build.Started
|
||||
task.Finished = build.Finished
|
||||
}
|
||||
err = store.SetBuild(repo.FullName, build)
|
||||
if err != nil {
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, task := range build.Tasks {
|
||||
runner.Cancel(repo.FullName, build.Number, task.Number)
|
||||
}
|
||||
// // get the agent from the repository so we can
|
||||
// // notify the agent to kill the build.
|
||||
// agent, err := store.BuildAgent(repo.FullName, build.Number)
|
||||
// if err != nil {
|
||||
// c.JSON(200, build)
|
||||
// return
|
||||
// }
|
||||
// url_, _ := url.Parse("http://" + agent.Addr)
|
||||
// url_.Path = fmt.Sprintf("/cancel/%s/%v", repo.FullName, build.Number)
|
||||
// resp, err := http.Post(url_.String(), "application/json", nil)
|
||||
// if err != nil {
|
||||
// c.Fail(500, err)
|
||||
// return
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
|
||||
c.JSON(200, build)
|
||||
}
|
279
server/commits.go
Normal file
279
server/commits.go
Normal file
|
@ -0,0 +1,279 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/drone/drone/parser/inject"
|
||||
"github.com/drone/drone/queue"
|
||||
"github.com/gin-gonic/gin"
|
||||
// "github.com/gin-gonic/gin/binding"
|
||||
)
|
||||
|
||||
// GetCommit accepts a request to retrieve a commit
|
||||
// from the datastore for the given repository and
|
||||
// commit sequence.
|
||||
//
|
||||
// GET /api/repos/:owner/:name/:number
|
||||
//
|
||||
func GetCommit(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
num, err := strconv.Atoi(c.Params.ByName("number"))
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
return
|
||||
}
|
||||
commit, err := store.CommitSeq(repo, num)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
}
|
||||
commit.Builds, err = store.BuildList(commit)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
} else {
|
||||
c.JSON(200, commit)
|
||||
}
|
||||
}
|
||||
|
||||
// GetCommits accepts a request to retrieve a list
|
||||
// of commits from the datastore for the given repository.
|
||||
//
|
||||
// GET /api/repos/:owner/:name/builds
|
||||
//
|
||||
func GetCommits(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
commits, err := store.CommitList(repo, 20, 0)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
} else {
|
||||
c.JSON(200, commits)
|
||||
}
|
||||
}
|
||||
|
||||
// GetLogs accepts a request to retrieve logs from the
|
||||
// datastore for the given repository, build and task
|
||||
// number.
|
||||
//
|
||||
// GET /api/repos/:owner/:name/logs/:number/:task
|
||||
//
|
||||
func GetLogs(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
full, _ := strconv.ParseBool(c.Params.ByName("full"))
|
||||
commit, _ := strconv.Atoi(c.Params.ByName("number"))
|
||||
build, _ := strconv.Atoi(c.Params.ByName("task"))
|
||||
|
||||
path := fmt.Sprintf("/logs/%s/%v/%v", repo.FullName, commit, build)
|
||||
r, err := store.GetBlobReader(path)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
} else if full {
|
||||
io.Copy(c.Writer, r)
|
||||
} else {
|
||||
io.Copy(c.Writer, io.LimitReader(r, 2000000))
|
||||
}
|
||||
}
|
||||
|
||||
// // PostBuildStatus accepts a request to create a new build
|
||||
// // status. The created user status is returned in JSON
|
||||
// // format if successful.
|
||||
// //
|
||||
// // POST /api/repos/:owner/:name/status/:number
|
||||
// //
|
||||
// func PostBuildStatus(c *gin.Context) {
|
||||
// store := ToDatastore(c)
|
||||
// repo := ToRepo(c)
|
||||
// num, err := strconv.Atoi(c.Params.ByName("number"))
|
||||
// if err != nil {
|
||||
// c.Fail(400, err)
|
||||
// return
|
||||
// }
|
||||
// in := &common.Status{}
|
||||
// if !c.BindWith(in, binding.JSON) {
|
||||
// c.AbortWithStatus(400)
|
||||
// return
|
||||
// }
|
||||
// if err := store.SetBuildStatus(repo.Name, num, in); err != nil {
|
||||
// c.Fail(400, err)
|
||||
// } else {
|
||||
// c.JSON(201, in)
|
||||
// }
|
||||
// }
|
||||
|
||||
// RunBuild accepts a request to restart an existing build.
|
||||
//
|
||||
// POST /api/builds/:owner/:name/builds/:number
|
||||
//
|
||||
func RunBuild(c *gin.Context) {
|
||||
remote := ToRemote(c)
|
||||
store := ToDatastore(c)
|
||||
queue_ := ToQueue(c)
|
||||
repo := ToRepo(c)
|
||||
num, err := strconv.Atoi(c.Params.ByName("number"))
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
return
|
||||
}
|
||||
commit, err := store.CommitSeq(repo, num)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
commit.Builds, err = store.BuildList(commit)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
keys := &common.Keypair{
|
||||
Public: repo.PublicKey,
|
||||
Private: repo.PrivateKey,
|
||||
}
|
||||
|
||||
user, err := store.User(repo.UserID)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
// must not restart a running build
|
||||
if commit.State == common.StatePending || commit.State == common.StateRunning {
|
||||
c.AbortWithStatus(409)
|
||||
return
|
||||
}
|
||||
|
||||
commit.State = common.StatePending
|
||||
commit.Started = 0
|
||||
commit.Finished = 0
|
||||
for _, build := range commit.Builds {
|
||||
build.State = common.StatePending
|
||||
build.Started = 0
|
||||
build.Finished = 0
|
||||
build.Duration = 0
|
||||
build.ExitCode = 0
|
||||
}
|
||||
|
||||
err = store.SetCommit(commit)
|
||||
if err != nil {
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
netrc, err := remote.Netrc(user)
|
||||
if err != nil {
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
// featch the .drone.yml file from the database
|
||||
raw, err := remote.Script(user, repo, commit)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
// inject any private parameters into the .drone.yml
|
||||
if repo.Params != nil && len(repo.Params) != 0 {
|
||||
raw = []byte(inject.InjectSafe(string(raw), repo.Params))
|
||||
}
|
||||
|
||||
c.JSON(202, commit)
|
||||
|
||||
queue_.Publish(&queue.Work{
|
||||
User: user,
|
||||
Repo: repo,
|
||||
Commit: commit,
|
||||
Keys: keys,
|
||||
Netrc: netrc,
|
||||
Yaml: raw,
|
||||
})
|
||||
}
|
||||
|
||||
// KillBuild accepts a request to kill a running build.
|
||||
//
|
||||
// DELETE /api/builds/:owner/:name/builds/:number
|
||||
//
|
||||
func KillBuild(c *gin.Context) {
|
||||
runner := ToRunner(c)
|
||||
queue := ToQueue(c)
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
num, err := strconv.Atoi(c.Params.ByName("number"))
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
return
|
||||
}
|
||||
commit, err := store.CommitSeq(repo, num)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
commit.Builds, err = store.BuildList(commit)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
// must not restart a running build
|
||||
if commit.State != common.StatePending && commit.State != common.StateRunning {
|
||||
c.Fail(409, err)
|
||||
return
|
||||
}
|
||||
|
||||
// remove from the queue if exists
|
||||
//
|
||||
// TODO(bradrydzewski) this could yield a race condition
|
||||
// because other threads may also be accessing these items.
|
||||
for _, item := range queue.Items() {
|
||||
if item.Repo.FullName == repo.FullName && item.Commit.Sequence == commit.Sequence {
|
||||
queue.Remove(item)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
commit.State = common.StateKilled
|
||||
commit.Finished = time.Now().Unix()
|
||||
if commit.Started == 0 {
|
||||
commit.Started = commit.Finished
|
||||
}
|
||||
for _, build := range commit.Builds {
|
||||
if build.State != common.StatePending && build.State != common.StateRunning {
|
||||
continue
|
||||
}
|
||||
build.State = common.StateKilled
|
||||
build.Started = commit.Started
|
||||
build.Finished = commit.Finished
|
||||
build.Duration = commit.Finished - commit.Started
|
||||
}
|
||||
err = store.SetCommit(commit)
|
||||
if err != nil {
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, build := range commit.Builds {
|
||||
runner.Cancel(build)
|
||||
}
|
||||
// // get the agent from the repository so we can
|
||||
// // notify the agent to kill the build.
|
||||
// agent, err := store.BuildAgent(repo.FullName, build.Number)
|
||||
// if err != nil {
|
||||
// c.JSON(200, build)
|
||||
// return
|
||||
// }
|
||||
// url_, _ := url.Parse("http://" + agent.Addr)
|
||||
// url_.Path = fmt.Sprintf("/cancel/%s/%v", repo.FullName, build.Number)
|
||||
// resp, err := http.Post(url_.String(), "application/json", nil)
|
||||
// if err != nil {
|
||||
// c.Fail(500, err)
|
||||
// return
|
||||
// }
|
||||
// defer resp.Body.Close()
|
||||
|
||||
c.JSON(200, commit)
|
||||
}
|
|
@ -57,52 +57,49 @@ func PostHook(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
repo, err := store.Repo(hook.Repo.FullName)
|
||||
repo, err := store.RepoName(hook.Repo.Owner, hook.Repo.Name)
|
||||
if err != nil {
|
||||
log.Errorf("failure to find repo %s from hook. %s", hook.Repo.FullName, err)
|
||||
log.Errorf("failure to find repo %s/%s from hook. %s", hook.Repo.Owner, hook.Repo.Name, err)
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case repo.Disabled:
|
||||
log.Infof("ignoring hook. repo %s is disabled.", repo.FullName)
|
||||
c.Writer.WriteHeader(204)
|
||||
return
|
||||
case repo.User == nil:
|
||||
case repo.UserID == 0:
|
||||
log.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
|
||||
c.Writer.WriteHeader(204)
|
||||
return
|
||||
case repo.DisablePR && hook.PullRequest != nil:
|
||||
case !repo.PostCommit && hook.Commit.PullRequest != "":
|
||||
log.Infof("ignoring hook. repo %s is disabled.", repo.FullName)
|
||||
c.Writer.WriteHeader(204)
|
||||
return
|
||||
case !repo.PullRequest && hook.Commit.PullRequest == "":
|
||||
log.Warnf("ignoring hook. repo %s is disabled for pull requests.", repo.FullName)
|
||||
c.Writer.WriteHeader(204)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := store.User(repo.User.Login)
|
||||
user, err := store.User(repo.UserID)
|
||||
if err != nil {
|
||||
log.Errorf("failure to find repo owner %s. %s", repo.User.Login, err)
|
||||
log.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
params, _ := store.RepoParams(repo.FullName)
|
||||
|
||||
build := &common.Build{}
|
||||
build.State = common.StatePending
|
||||
build.Commit = hook.Commit
|
||||
build.PullRequest = hook.PullRequest
|
||||
commit := hook.Commit
|
||||
commit.State = common.StatePending
|
||||
commit.RepoID = repo.ID
|
||||
|
||||
// featch the .drone.yml file from the database
|
||||
raw, err := remote.Script(user, repo, build)
|
||||
raw, err := remote.Script(user, repo, commit)
|
||||
if err != nil {
|
||||
log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err)
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
// inject any private parameters into the .drone.yml
|
||||
if params != nil && len(params) != 0 {
|
||||
raw = []byte(inject.InjectSafe(string(raw), params))
|
||||
if repo.Params != nil && len(repo.Params) != 0 {
|
||||
raw = []byte(inject.InjectSafe(string(raw), repo.Params))
|
||||
}
|
||||
axes, err := matrix.Parse(string(raw))
|
||||
if err != nil {
|
||||
|
@ -114,58 +111,59 @@ func PostHook(c *gin.Context) {
|
|||
axes = append(axes, matrix.Axis{})
|
||||
}
|
||||
for num, axis := range axes {
|
||||
build.Tasks = append(build.Tasks, &common.Task{
|
||||
Number: num + 1,
|
||||
commit.Builds = append(commit.Builds, &common.Build{
|
||||
CommitID: commit.ID,
|
||||
Sequence: num + 1,
|
||||
State: common.StatePending,
|
||||
Environment: axis,
|
||||
})
|
||||
}
|
||||
keys, err := store.RepoKeypair(repo.FullName)
|
||||
if err != nil {
|
||||
log.Errorf("failure to fetch keypair for %s. %s", repo.FullName, err)
|
||||
c.Fail(404, err)
|
||||
return
|
||||
keys := &common.Keypair{
|
||||
Public: repo.PublicKey,
|
||||
Private: repo.PrivateKey,
|
||||
}
|
||||
|
||||
netrc, err := remote.Netrc(user)
|
||||
if err != nil {
|
||||
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
// verify the branches can be built vs skipped
|
||||
when, _ := parser.ParseCondition(string(raw))
|
||||
if build.Commit != nil && when != nil && !when.MatchBranch(build.Commit.Ref) {
|
||||
log.Infof("ignoring hook. yaml file excludes repo and branch %s %s", repo.FullName, build.Commit.Ref)
|
||||
if commit.PullRequest != "" && when != nil && !when.MatchBranch(commit.Branch) {
|
||||
log.Infof("ignoring hook. yaml file excludes repo and branch %s %s", repo.FullName, commit.Branch)
|
||||
c.AbortWithStatus(200)
|
||||
return
|
||||
}
|
||||
|
||||
err = store.SetBuild(repo.FullName, build)
|
||||
err = store.AddCommit(commit)
|
||||
if err != nil {
|
||||
log.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, build)
|
||||
c.JSON(200, commit)
|
||||
|
||||
link := fmt.Sprintf(
|
||||
"%s/%s/%d",
|
||||
httputil.GetURL(c.Request),
|
||||
repo.FullName,
|
||||
build.Number,
|
||||
commit.Sequence,
|
||||
)
|
||||
err = remote.Status(user, repo, build, link)
|
||||
err = remote.Status(user, repo, commit, link)
|
||||
if err != nil {
|
||||
log.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
|
||||
log.Errorf("error setting commit status for %s/%d", repo.FullName, commit.Sequence)
|
||||
}
|
||||
|
||||
queue_.Publish(&queue.Work{
|
||||
User: user,
|
||||
Repo: repo,
|
||||
Build: build,
|
||||
Keys: keys,
|
||||
Netrc: netrc,
|
||||
Yaml: raw,
|
||||
User: user,
|
||||
Repo: repo,
|
||||
Commit: commit,
|
||||
Keys: keys,
|
||||
Netrc: netrc,
|
||||
Yaml: raw,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ func GetLogin(c *gin.Context) {
|
|||
}
|
||||
|
||||
// get the user from the database
|
||||
u, err := store.User(login.Login)
|
||||
u, err := store.UserLogin(login.Login)
|
||||
if err != nil {
|
||||
count, err := store.UserCount()
|
||||
if err != nil {
|
||||
|
@ -89,7 +89,7 @@ func GetLogin(c *gin.Context) {
|
|||
u.Gravatar = gravatar.Hash(u.Email)
|
||||
|
||||
// insert the user into the database
|
||||
if err := store.SetUserNotExists(u); err != nil {
|
||||
if err := store.AddUser(u); err != nil {
|
||||
log.Errorf("cannot insert %s. %s", login.Login, err)
|
||||
c.Redirect(303, "/login#error=internal_error")
|
||||
return
|
||||
|
|
310
server/queue.go
310
server/queue.go
|
@ -1,182 +1,182 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
// import (
|
||||
// "encoding/json"
|
||||
// "io"
|
||||
// "net"
|
||||
// "strconv"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
// log "github.com/Sirupsen/logrus"
|
||||
// "github.com/gin-gonic/gin"
|
||||
// "github.com/gin-gonic/gin/binding"
|
||||
|
||||
"github.com/drone/drone/common"
|
||||
"github.com/drone/drone/eventbus"
|
||||
)
|
||||
// "github.com/drone/drone/common"
|
||||
// "github.com/drone/drone/eventbus"
|
||||
// )
|
||||
|
||||
// TODO (bradrydzewski) the callback URL should be signed.
|
||||
// TODO (bradrydzewski) we shouldn't need to fetch the Repo if specified in the URL path
|
||||
// TODO (bradrydzewski) use SetRepoLast to update the last repository
|
||||
// // TODO (bradrydzewski) the callback URL should be signed.
|
||||
// // TODO (bradrydzewski) we shouldn't need to fetch the Repo if specified in the URL path
|
||||
// // TODO (bradrydzewski) use SetRepoLast to update the last repository
|
||||
|
||||
// GET /queue/pull
|
||||
func PollBuild(c *gin.Context) {
|
||||
queue := ToQueue(c)
|
||||
store := ToDatastore(c)
|
||||
agent := &common.Agent{
|
||||
Addr: c.Request.RemoteAddr,
|
||||
}
|
||||
// // GET /queue/pull
|
||||
// func PollBuild(c *gin.Context) {
|
||||
// queue := ToQueue(c)
|
||||
// store := ToDatastore(c)
|
||||
// agent := &common.Agent{
|
||||
// Addr: c.Request.RemoteAddr,
|
||||
// }
|
||||
|
||||
// extact the host port and name and
|
||||
// replace with the default agent port (1999)
|
||||
host, _, err := net.SplitHostPort(agent.Addr)
|
||||
if err == nil {
|
||||
agent.Addr = host
|
||||
}
|
||||
agent.Addr = net.JoinHostPort(agent.Addr, "1999")
|
||||
// // extact the host port and name and
|
||||
// // replace with the default agent port (1999)
|
||||
// host, _, err := net.SplitHostPort(agent.Addr)
|
||||
// if err == nil {
|
||||
// agent.Addr = host
|
||||
// }
|
||||
// agent.Addr = net.JoinHostPort(agent.Addr, "1999")
|
||||
|
||||
log.Infof("agent connected and polling builds at %s", agent.Addr)
|
||||
// log.Infof("agent connected and polling builds at %s", agent.Addr)
|
||||
|
||||
work := queue.PullClose(c.Writer)
|
||||
if work == nil {
|
||||
c.AbortWithStatus(500)
|
||||
return
|
||||
}
|
||||
// work := queue.PullClose(c.Writer)
|
||||
// if work == nil {
|
||||
// c.AbortWithStatus(500)
|
||||
// return
|
||||
// }
|
||||
|
||||
// TODO (bradrydzewski) decide how we want to handle a failure here
|
||||
// still not sure exact behavior we want ...
|
||||
err = store.SetBuildAgent(work.Repo.FullName, work.Build.Number, agent)
|
||||
if err != nil {
|
||||
log.Errorf("error persisting build agent. %s", err)
|
||||
}
|
||||
// // TODO (bradrydzewski) decide how we want to handle a failure here
|
||||
// // still not sure exact behavior we want ...
|
||||
// err = store.SetBuildAgent(work.Repo.FullName, work.Build.Number, agent)
|
||||
// if err != nil {
|
||||
// log.Errorf("error persisting build agent. %s", err)
|
||||
// }
|
||||
|
||||
c.JSON(200, work)
|
||||
// c.JSON(200, work)
|
||||
|
||||
// acknowledge work received by the client
|
||||
queue.Ack(work)
|
||||
}
|
||||
// // acknowledge work received by the client
|
||||
// queue.Ack(work)
|
||||
// }
|
||||
|
||||
// GET /queue/push/:owner/:repo
|
||||
func PushBuild(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
bus := ToBus(c)
|
||||
in := &common.Build{}
|
||||
if !c.BindWith(in, binding.JSON) {
|
||||
return
|
||||
}
|
||||
build, err := store.Build(repo.FullName, in.Number)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
// // GET /queue/push/:owner/:repo
|
||||
// func PushBuild(c *gin.Context) {
|
||||
// store := ToDatastore(c)
|
||||
// repo := ToRepo(c)
|
||||
// bus := ToBus(c)
|
||||
// in := &common.Build{}
|
||||
// if !c.BindWith(in, binding.JSON) {
|
||||
// return
|
||||
// }
|
||||
// build, err := store.Build(repo.FullName, in.Number)
|
||||
// if err != nil {
|
||||
// c.Fail(404, err)
|
||||
// return
|
||||
// }
|
||||
|
||||
if in.State != common.StatePending && in.State != common.StateRunning {
|
||||
store.DelBuildAgent(repo.FullName, build.Number)
|
||||
}
|
||||
// if in.State != common.StatePending && in.State != common.StateRunning {
|
||||
// store.DelBuildAgent(repo.FullName, build.Number)
|
||||
// }
|
||||
|
||||
build.Duration = in.Duration
|
||||
build.Started = in.Started
|
||||
build.Finished = in.Finished
|
||||
build.State = in.State
|
||||
err = store.SetBuildState(repo.FullName, build)
|
||||
if err != nil {
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
// build.Duration = in.Duration
|
||||
// build.Started = in.Started
|
||||
// build.Finished = in.Finished
|
||||
// build.State = in.State
|
||||
// err = store.SetBuildState(repo.FullName, build)
|
||||
// if err != nil {
|
||||
// c.Fail(500, err)
|
||||
// return
|
||||
// }
|
||||
|
||||
if build.State != common.StatePending && build.State != common.StateRunning {
|
||||
if repo.Last == nil || build.Number >= repo.Last.Number {
|
||||
repo.Last = build
|
||||
store.SetRepo(repo)
|
||||
}
|
||||
}
|
||||
// if build.State != common.StatePending && build.State != common.StateRunning {
|
||||
// if repo.Last == nil || build.Number >= repo.Last.Number {
|
||||
// repo.Last = build
|
||||
// store.SetRepo(repo)
|
||||
// }
|
||||
// }
|
||||
|
||||
// <-- FIXME
|
||||
// for some reason the Repo and Build fail to marshal to JSON.
|
||||
// It has something to do with memory / pointers. So it goes away
|
||||
// if I just refetch these items. Needs to be fixed in the future,
|
||||
// but for now should be ok
|
||||
repo, err = store.Repo(repo.FullName)
|
||||
if err != nil {
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
build, err = store.Build(repo.FullName, in.Number)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
// END FIXME -->
|
||||
// // <-- FIXME
|
||||
// // for some reason the Repo and Build fail to marshal to JSON.
|
||||
// // It has something to do with memory / pointers. So it goes away
|
||||
// // if I just refetch these items. Needs to be fixed in the future,
|
||||
// // but for now should be ok
|
||||
// repo, err = store.Repo(repo.FullName)
|
||||
// if err != nil {
|
||||
// c.Fail(500, err)
|
||||
// return
|
||||
// }
|
||||
// build, err = store.Build(repo.FullName, in.Number)
|
||||
// if err != nil {
|
||||
// c.Fail(404, err)
|
||||
// return
|
||||
// }
|
||||
// // END FIXME -->
|
||||
|
||||
msg, err := json.Marshal(build)
|
||||
if err == nil {
|
||||
c.String(200, err.Error()) // we can ignore this error
|
||||
return
|
||||
}
|
||||
// msg, err := json.Marshal(build)
|
||||
// if err == nil {
|
||||
// c.String(200, err.Error()) // we can ignore this error
|
||||
// return
|
||||
// }
|
||||
|
||||
bus.Send(&eventbus.Event{
|
||||
Name: repo.FullName,
|
||||
Kind: eventbus.EventRepo,
|
||||
Msg: msg,
|
||||
})
|
||||
// bus.Send(&eventbus.Event{
|
||||
// Name: repo.FullName,
|
||||
// Kind: eventbus.EventRepo,
|
||||
// Msg: msg,
|
||||
// })
|
||||
|
||||
c.Writer.WriteHeader(200)
|
||||
}
|
||||
// c.Writer.WriteHeader(200)
|
||||
// }
|
||||
|
||||
// POST /queue/push/:owner/:repo/:build
|
||||
func PushTask(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
bus := ToBus(c)
|
||||
num, _ := strconv.Atoi(c.Params.ByName("build"))
|
||||
in := &common.Task{}
|
||||
if !c.BindWith(in, binding.JSON) {
|
||||
return
|
||||
}
|
||||
err := store.SetBuildTask(repo.FullName, num, in)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
build, err := store.Build(repo.FullName, num)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
// // POST /queue/push/:owner/:repo/:build
|
||||
// func PushTask(c *gin.Context) {
|
||||
// store := ToDatastore(c)
|
||||
// repo := ToRepo(c)
|
||||
// bus := ToBus(c)
|
||||
// num, _ := strconv.Atoi(c.Params.ByName("build"))
|
||||
// in := &common.Task{}
|
||||
// if !c.BindWith(in, binding.JSON) {
|
||||
// return
|
||||
// }
|
||||
// err := store.SetBuildTask(repo.FullName, num, in)
|
||||
// if err != nil {
|
||||
// c.Fail(404, err)
|
||||
// return
|
||||
// }
|
||||
// build, err := store.Build(repo.FullName, num)
|
||||
// if err != nil {
|
||||
// c.Fail(404, err)
|
||||
// return
|
||||
// }
|
||||
|
||||
msg, err := json.Marshal(build)
|
||||
if err == nil {
|
||||
c.String(200, err.Error()) // we can ignore this error
|
||||
return
|
||||
}
|
||||
// msg, err := json.Marshal(build)
|
||||
// if err == nil {
|
||||
// c.String(200, err.Error()) // we can ignore this error
|
||||
// return
|
||||
// }
|
||||
|
||||
bus.Send(&eventbus.Event{
|
||||
Name: repo.FullName,
|
||||
Kind: eventbus.EventRepo,
|
||||
Msg: msg,
|
||||
})
|
||||
// bus.Send(&eventbus.Event{
|
||||
// Name: repo.FullName,
|
||||
// Kind: eventbus.EventRepo,
|
||||
// Msg: msg,
|
||||
// })
|
||||
|
||||
c.Writer.WriteHeader(200)
|
||||
}
|
||||
// c.Writer.WriteHeader(200)
|
||||
// }
|
||||
|
||||
// POST /queue/push/:owner/:repo/:build/:task/logs
|
||||
func PushLogs(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
bnum, _ := strconv.Atoi(c.Params.ByName("build"))
|
||||
tnum, _ := strconv.Atoi(c.Params.ByName("task"))
|
||||
// // POST /queue/push/:owner/:repo/:build/:task/logs
|
||||
// func PushLogs(c *gin.Context) {
|
||||
// store := ToDatastore(c)
|
||||
// repo := ToRepo(c)
|
||||
// bnum, _ := strconv.Atoi(c.Params.ByName("build"))
|
||||
// tnum, _ := strconv.Atoi(c.Params.ByName("task"))
|
||||
|
||||
const maxBuffToRead int64 = 5000000 // 5MB.
|
||||
err := store.SetLogs(repo.FullName, bnum, tnum, io.LimitReader(c.Request.Body, maxBuffToRead))
|
||||
if err != nil {
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
c.Writer.WriteHeader(200)
|
||||
}
|
||||
// const maxBuffToRead int64 = 5000000 // 5MB.
|
||||
// err := store.SetLogs(repo.FullName, bnum, tnum, io.LimitReader(c.Request.Body, maxBuffToRead))
|
||||
// if err != nil {
|
||||
// c.Fail(500, err)
|
||||
// return
|
||||
// }
|
||||
// c.Writer.WriteHeader(200)
|
||||
// }
|
||||
|
||||
func GetQueue(c *gin.Context) {
|
||||
queue := ToQueue(c)
|
||||
items := queue.Items()
|
||||
c.JSON(200, items)
|
||||
}
|
||||
// func GetQueue(c *gin.Context) {
|
||||
// queue := ToQueue(c)
|
||||
// items := queue.Items()
|
||||
// c.JSON(200, items)
|
||||
// }
|
||||
|
|
119
server/repos.go
119
server/repos.go
|
@ -17,10 +17,10 @@ import (
|
|||
// additional repository meta-data.
|
||||
type repoResp struct {
|
||||
*common.Repo
|
||||
Perms *common.Perm `json:"permissions,omitempty"`
|
||||
Watch *common.Subscriber `json:"subscription,omitempty"`
|
||||
Keypair *common.Keypair `json:"keypair,omitempty"`
|
||||
Params map[string]string `json:"params,omitempty"`
|
||||
Perms *common.Perm `json:"permissions,omitempty"`
|
||||
Keypair *common.Keypair `json:"keypair,omitempty"`
|
||||
Params map[string]string `json:"params,omitempty"`
|
||||
Starred bool `json:"starred,omitempty"`
|
||||
}
|
||||
|
||||
// repoReq is a data structure used for receiving
|
||||
|
@ -31,11 +31,10 @@ type repoResp struct {
|
|||
// accept null values, effectively patching an existing
|
||||
// repository object with only the supplied fields.
|
||||
type repoReq struct {
|
||||
Disabled *bool `json:"disabled"`
|
||||
DisablePR *bool `json:"disable_prs"`
|
||||
DisableTag *bool `json:"disable_tags"`
|
||||
Trusted *bool `json:"privileged"`
|
||||
Timeout *int64 `json:"timeout"`
|
||||
PostCommit *bool `json:"post_commits"`
|
||||
PullRequest *bool `json:"pull_requests"`
|
||||
Trusted *bool `json:"privileged"`
|
||||
Timeout *int64 `json:"timeout"`
|
||||
|
||||
// optional private parameters can only be
|
||||
// supplied by the repository admin.
|
||||
|
@ -53,19 +52,8 @@ func GetRepo(c *gin.Context) {
|
|||
repo := ToRepo(c)
|
||||
user := ToUser(c)
|
||||
perm := ToPerm(c)
|
||||
data := repoResp{repo, perm, nil, nil, nil}
|
||||
// if the user is an administrator of the project
|
||||
// we should display the private parameter data
|
||||
// and keypair data.
|
||||
if perm.Push {
|
||||
data.Params, _ = store.RepoParams(repo.FullName)
|
||||
data := repoResp{repo, perm, nil, nil, false}
|
||||
|
||||
// note that we should only display the public key
|
||||
keypair, err := store.RepoKeypair(repo.FullName)
|
||||
if err == nil {
|
||||
data.Keypair = &common.Keypair{Public: keypair.Public}
|
||||
}
|
||||
}
|
||||
// if the user is authenticated, we should display
|
||||
// if she is watching the current repository.
|
||||
if user == nil {
|
||||
|
@ -73,9 +61,17 @@ func GetRepo(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// if the user is an administrator of the project
|
||||
// we should display the private parameter data
|
||||
// and keypair data.
|
||||
if perm.Push {
|
||||
data.Params = repo.Params
|
||||
data.Keypair = &common.Keypair{
|
||||
Public: repo.PublicKey,
|
||||
}
|
||||
}
|
||||
// check to see if the user is subscribing to the repo
|
||||
data.Watch = &common.Subscriber{}
|
||||
data.Watch.Subscribed, _ = store.Subscribed(user.Login, repo.FullName)
|
||||
data.Starred, _ = store.Starred(user, repo)
|
||||
|
||||
c.JSON(200, data)
|
||||
}
|
||||
|
@ -98,20 +94,14 @@ func PutRepo(c *gin.Context) {
|
|||
}
|
||||
|
||||
if in.Params != nil {
|
||||
err := store.SetRepoParams(repo.FullName, *in.Params)
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
return
|
||||
}
|
||||
repo.Params = *in.Params
|
||||
}
|
||||
if in.Disabled != nil {
|
||||
repo.Disabled = *in.Disabled
|
||||
|
||||
if in.PostCommit != nil {
|
||||
repo.PullRequest = *in.PullRequest
|
||||
}
|
||||
if in.DisablePR != nil {
|
||||
repo.DisablePR = *in.DisablePR
|
||||
}
|
||||
if in.DisableTag != nil {
|
||||
repo.DisableTag = *in.DisableTag
|
||||
if in.PullRequest != nil {
|
||||
repo.PullRequest = *in.PullRequest
|
||||
}
|
||||
if in.Trusted != nil && user.Admin {
|
||||
repo.Trusted = *in.Trusted
|
||||
|
@ -126,20 +116,12 @@ func PutRepo(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
data := repoResp{repo, perm, nil, nil, nil}
|
||||
data.Params, _ = store.RepoParams(repo.FullName)
|
||||
data.Keypair, _ = store.RepoKeypair(repo.FullName)
|
||||
|
||||
// check to see if the user is subscribing to the repo
|
||||
data.Watch = &common.Subscriber{}
|
||||
data.Watch.Subscribed, _ = store.Subscribed(user.Login, repo.FullName)
|
||||
|
||||
// scrub the private key from the keypair
|
||||
if data.Keypair != nil {
|
||||
data.Keypair = &common.Keypair{
|
||||
Public: data.Keypair.Public,
|
||||
}
|
||||
data := repoResp{repo, perm, nil, nil, false}
|
||||
data.Params = repo.Params
|
||||
data.Keypair = &common.Keypair{
|
||||
Public: repo.PublicKey,
|
||||
}
|
||||
data.Starred, _ = store.Starred(user, repo)
|
||||
|
||||
c.JSON(200, data)
|
||||
}
|
||||
|
@ -184,12 +166,6 @@ func PostRepo(c *gin.Context) {
|
|||
owner := c.Params.ByName("owner")
|
||||
name := c.Params.ByName("name")
|
||||
|
||||
_, err := store.Repo(owner + "/" + name)
|
||||
if err == nil {
|
||||
c.String(409, "Repository already exists")
|
||||
return
|
||||
}
|
||||
|
||||
// get the repository and user permissions
|
||||
// from the remote system.
|
||||
remote := ToRemote(c)
|
||||
|
@ -207,6 +183,13 @@ func PostRepo(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// error if the repository already exists
|
||||
_, err = store.RepoName(owner, name)
|
||||
if err == nil {
|
||||
c.String(409, "Repository already exists")
|
||||
return
|
||||
}
|
||||
|
||||
token := &common.Token{}
|
||||
token.Kind = common.TokenHook
|
||||
token.Label = r.FullName
|
||||
|
@ -224,7 +207,9 @@ func PostRepo(c *gin.Context) {
|
|||
|
||||
// set the repository owner to the
|
||||
// currently authenticated user.
|
||||
r.User = &common.Owner{Login: user.Login}
|
||||
r.UserID = user.ID
|
||||
r.PostCommit = true
|
||||
r.PullRequest = true
|
||||
|
||||
// generate an RSA key and add to the repo
|
||||
key, err := sshutil.GeneratePrivateKey()
|
||||
|
@ -232,9 +217,12 @@ func PostRepo(c *gin.Context) {
|
|||
c.Fail(400, err)
|
||||
return
|
||||
}
|
||||
keypair := &common.Keypair{}
|
||||
keypair.Public = sshutil.MarshalPublicKey(&key.PublicKey)
|
||||
keypair.Private = sshutil.MarshalPrivateKey(key)
|
||||
r.PublicKey = sshutil.MarshalPublicKey(&key.PublicKey)
|
||||
r.PrivateKey = sshutil.MarshalPrivateKey(key)
|
||||
keypair := &common.Keypair{
|
||||
Public: r.PublicKey,
|
||||
Private: r.PrivateKey,
|
||||
}
|
||||
|
||||
// activate the repository before we make any
|
||||
// local changes to the database.
|
||||
|
@ -245,18 +233,13 @@ func PostRepo(c *gin.Context) {
|
|||
}
|
||||
|
||||
// persist the repository
|
||||
err = store.SetRepoNotExists(user, r)
|
||||
err = store.AddRepo(r)
|
||||
if err != nil {
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
|
||||
// persisty the repository key pair
|
||||
err = store.SetRepoKeypair(r.FullName, keypair)
|
||||
if err != nil {
|
||||
c.Fail(500, err)
|
||||
return
|
||||
}
|
||||
store.AddStar(user, r)
|
||||
|
||||
c.JSON(200, r)
|
||||
}
|
||||
|
@ -271,7 +254,7 @@ func Unsubscribe(c *gin.Context) {
|
|||
repo := ToRepo(c)
|
||||
user := ToUser(c)
|
||||
|
||||
err := store.DelSubscriber(user.Login, repo.FullName)
|
||||
err := store.DelStar(user, repo)
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
} else {
|
||||
|
@ -289,11 +272,11 @@ func Subscribe(c *gin.Context) {
|
|||
repo := ToRepo(c)
|
||||
user := ToUser(c)
|
||||
|
||||
err := store.SetSubscriber(user.Login, repo.FullName)
|
||||
err := store.AddStar(user, repo)
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
} else {
|
||||
c.JSON(200, &common.Subscriber{Subscribed: true})
|
||||
c.Writer.WriteHeader(200)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -146,9 +146,9 @@ func SetUser(s session.Session) gin.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
u, err := ds.User(token.Login)
|
||||
user, err := ds.UserLogin(token.Login)
|
||||
if err == nil {
|
||||
c.Set("user", u)
|
||||
c.Set("user", user)
|
||||
}
|
||||
|
||||
// if session token we can proceed, otherwise
|
||||
|
@ -162,7 +162,7 @@ func SetUser(s session.Session) gin.HandlerFunc {
|
|||
// to verify the token we fetch from the datastore
|
||||
// and check to see if the token issued date matches
|
||||
// what we found in the jwt (in case the label is re-used)
|
||||
t, err := ds.Token(token.Login, token.Label)
|
||||
t, err := ds.TokenLabel(user, token.Label)
|
||||
if err != nil || t.Issued != token.Issued {
|
||||
c.AbortWithStatus(403)
|
||||
return
|
||||
|
@ -178,7 +178,7 @@ func SetRepo() gin.HandlerFunc {
|
|||
u := ToUser(c)
|
||||
owner := c.Params.ByName("owner")
|
||||
name := c.Params.ByName("name")
|
||||
r, err := ds.Repo(owner + "/" + name)
|
||||
r, err := ds.RepoName(owner, name)
|
||||
switch {
|
||||
case err != nil && u != nil:
|
||||
c.Fail(404, err)
|
||||
|
|
93
server/static/images/logo.svg
Normal file
93
server/static/images/logo.svg
Normal file
|
@ -0,0 +1,93 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="43"
|
||||
height="36.350704"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48.3.1 r9886"
|
||||
sodipodi:docname="drone-logo-no-circle.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="2.8"
|
||||
inkscape:cx="26.576205"
|
||||
inkscape:cy="-72.54425"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:snap-global="false"
|
||||
inkscape:window-width="1295"
|
||||
inkscape:window-height="744"
|
||||
inkscape:window-x="65"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2996"
|
||||
empspacing="5"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true"
|
||||
originx="-21.720779px"
|
||||
originy="-990.37188px" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-21.720779,-25.639593)">
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
id="path2998"
|
||||
sodipodi:cx="172.10474"
|
||||
sodipodi:cy="458.39249"
|
||||
sodipodi:rx="5.4295697"
|
||||
sodipodi:ry="5.0507627"
|
||||
d="m 177.53431,458.39249 c 0,2.78946 -2.43091,5.05076 -5.42957,5.05076 -2.99867,0 -5.42957,-2.2613 -5.42957,-5.05076 0,-2.78946 2.4309,-5.05077 5.42957,-5.05077 2.99866,0 5.42957,2.26131 5.42957,5.05077 z"
|
||||
transform="matrix(1.0129716,0,0,1.0889445,-131.11643,-452.42373)" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:0;stroke-miterlimit:4"
|
||||
d="m 43.220779,25.640247 c 9.60163,0.0752 20.51786,6.8438 21.5,19.6 l -13,0 c 0,0 -1.67472,-7.04733 -8.5,-7 -6.82528,0.0473 -8.5,7 -8.5,7 l -13,0 c 0.63161,-12.53073 11.36576,-19.67935 21.5,-19.6 z"
|
||||
id="rect3810"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="scczccs" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:0;stroke-miterlimit:4"
|
||||
d="m 43.310069,61.990247 c -7.159395,0.01905 -13.847588,-5.383347 -16.58929,-13.75 l 8,0 c 0,0 1.72575,6.96782 8.55103,6.92049 6.82528,-0.0473 8.44897,-6.92049 8.44897,-6.92049 l 8,0 c -1.783351,8.850973 -9.251314,13.730946 -16.41071,13.75 z"
|
||||
id="rect3810-1"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="scczccs" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
|
@ -1,43 +1,43 @@
|
|||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html ng-app="drone" lang="en">
|
||||
<head>
|
||||
<base href="/">
|
||||
<meta charset="utf-8">
|
||||
<meta name="author" content="Brad Rydzewski and the Drone Authors">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
<link rel="shortcut icon" href="/static/favicon.png"/>
|
||||
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600,300,700" rel="stylesheet" type="text/css" />
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/octicons/2.1.2/octicons.min.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body ng-cloak>
|
||||
<main role="main" ng-view></main>
|
||||
<head>
|
||||
<base href="/"/>
|
||||
<link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:200,300,400,600,700' rel='stylesheet' type='text/css'>
|
||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,600,300' rel='stylesheet' type='text/css'>
|
||||
<link href='http://fonts.googleapis.com/css?family=Montserrat:400,700' rel='stylesheet' type='text/css'>
|
||||
<link href='https://cdn.rawgit.com/zavoloklom/material-design-iconic-font/master/css/material-design-iconic-font.min.css' rel='stylesheet' type='text/css'>
|
||||
<link href='https://cdnjs.cloudflare.com/ajax/libs/octicons/2.1.2/octicons.min.css' rel='stylesheet' type='text/css'>
|
||||
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Droid+Sans+Mono" />
|
||||
<link href='/static/styles/drone.css' rel='stylesheet' type='text/css'>
|
||||
</head>
|
||||
<body ng-cloak>
|
||||
<div role="main" ng-view></div>
|
||||
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.8/angular.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.8/angular-route.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.8/angular-resource.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.6.0/moment.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-route.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-resource.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui/0.4.0/angular-ui.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.6.0/moment.min.js"></script>
|
||||
|
||||
<!-- main javascript application -->
|
||||
<script src="/static/scripts/term.js"></script>
|
||||
<script src="/static/scripts/drone.js"></script>
|
||||
<script src="/static/scripts/controllers/agents.js"></script>
|
||||
<script src="/static/scripts/controllers/repos.js"></script>
|
||||
<script src="/static/scripts/controllers/builds.js"></script>
|
||||
<script src="/static/scripts/controllers/users.js"></script>
|
||||
<!-- main javascript application -->
|
||||
<script src="/static/scripts/term.js"></script>
|
||||
<script src="/static/scripts/drone.js"></script>
|
||||
<script src="/static/scripts/controllers/agents.js"></script>
|
||||
<script src="/static/scripts/controllers/repos.js"></script>
|
||||
<script src="/static/scripts/controllers/builds.js"></script>
|
||||
<script src="/static/scripts/controllers/users.js"></script>
|
||||
|
||||
<script src="/static/scripts/services/agents.js"></script>
|
||||
<script src="/static/scripts/services/repos.js"></script>
|
||||
<script src="/static/scripts/services/builds.js"></script>
|
||||
<script src="/static/scripts/services/users.js"></script>
|
||||
<script src="/static/scripts/services/logs.js"></script>
|
||||
<script src="/static/scripts/services/tokens.js"></script>
|
||||
<script src="/static/scripts/services/feed.js"></script>
|
||||
<script src="/static/scripts/services/agents.js"></script>
|
||||
<script src="/static/scripts/services/repos.js"></script>
|
||||
<script src="/static/scripts/services/builds.js"></script>
|
||||
<script src="/static/scripts/services/users.js"></script>
|
||||
<script src="/static/scripts/services/logs.js"></script>
|
||||
<script src="/static/scripts/services/tokens.js"></script>
|
||||
<script src="/static/scripts/services/feed.js"></script>
|
||||
|
||||
<script src="/static/scripts/filters/filter.js"></script>
|
||||
<script src="/static/scripts/filters/gravatar.js"></script>
|
||||
<script src="/static/scripts/filters/time.js"></script>
|
||||
<script src="/static/scripts/filters/filter.js"></script>
|
||||
<script src="/static/scripts/filters/gravatar.js"></script>
|
||||
<script src="/static/scripts/filters/time.js"></script>
|
||||
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -31,13 +31,13 @@
|
|||
|
||||
$scope.watch = function(repo) {
|
||||
repos.watch(repo.full_name).then(function(payload) {
|
||||
$scope.repo.subscription = payload.data;
|
||||
$scope.repo.starred = true;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.unwatch = function(repo) {
|
||||
repos.unwatch(repo.full_name).then(function() {
|
||||
delete $scope.repo.subscription;
|
||||
$scope.repo.starred = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@
|
|||
var added = false;
|
||||
for (var i=0;i<$scope.builds.length;i++) {
|
||||
var build = $scope.builds[i];
|
||||
if (event.number !== build.number) {
|
||||
if (event.sequence !== build.sequence) {
|
||||
continue; // ignore
|
||||
}
|
||||
// update the build status
|
||||
|
@ -66,7 +66,7 @@
|
|||
*/
|
||||
function BuildCtrl($scope, $routeParams, $window, logs, builds, repos, users) {
|
||||
|
||||
var step = parseInt($routeParams.step) || 1;
|
||||
var step = parseInt($routeParams.step);
|
||||
var number = $routeParams.number;
|
||||
var owner = $routeParams.owner;
|
||||
var name = $routeParams.name;
|
||||
|
@ -110,7 +110,18 @@
|
|||
// Gets the build
|
||||
builds.get(fullName, number).then(function(payload){
|
||||
$scope.build = payload.data;
|
||||
$scope.task = payload.data.tasks[step-1];
|
||||
|
||||
// if only 1 build, select first
|
||||
if (!step && payload.data.builds.length === 1) {
|
||||
step = 1;
|
||||
|
||||
// else if only 1 step, but multiple builds
|
||||
// we should only render the list of builds
|
||||
} else if (!step) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.task = payload.data.builds[step-1];
|
||||
|
||||
if (['pending', 'killed'].indexOf($scope.task.state) !== -1) {
|
||||
// do nothing
|
||||
|
@ -135,7 +146,7 @@
|
|||
$scope.restart = function() {
|
||||
builds.restart(fullName, number).then(function(payload){
|
||||
$scope.build = payload.data;
|
||||
$scope.task = payload.data.tasks[step-1];
|
||||
$scope.task = payload.data.builds[step-1];
|
||||
}).catch(function(err){
|
||||
$scope.error = err;
|
||||
});
|
||||
|
@ -144,7 +155,7 @@
|
|||
$scope.cancel = function() {
|
||||
builds.cancel(fullName, number).then(function(payload){
|
||||
$scope.build = payload.data;
|
||||
$scope.task = payload.data.tasks[step-1];
|
||||
$scope.task = payload.data.builds[step-1];
|
||||
}).catch(function(err) {
|
||||
$scope.error = err;
|
||||
});
|
||||
|
@ -155,12 +166,12 @@
|
|||
};
|
||||
|
||||
repos.subscribe(fullName, function(event) {
|
||||
if (event.number !== parseInt(number)) {
|
||||
if (event.sequence !== parseInt(number)) {
|
||||
return; // ignore
|
||||
}
|
||||
// update the build
|
||||
$scope.build = event;
|
||||
$scope.task = event.tasks[step-1];
|
||||
$scope.task = event.builds[step-1];
|
||||
$scope.$apply();
|
||||
|
||||
// start streaming the current build
|
||||
|
|
67
server/static/scripts/controllers/commits.js
Normal file
67
server/static/scripts/controllers/commits.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
(function () {
|
||||
|
||||
/**
|
||||
* CommitsCtrl responsible for rendering the repo's
|
||||
* recent commit history.
|
||||
*/
|
||||
function CommitsCtrl($scope, $routeParams, builds, repos, users, logs) {
|
||||
|
||||
var owner = $routeParams.owner;
|
||||
var name = $routeParams.name;
|
||||
var fullName = owner+'/'+name;
|
||||
|
||||
// Gets the currently authenticated user
|
||||
users.getCached().then(function(payload){
|
||||
$scope.user = payload.data;
|
||||
});
|
||||
|
||||
// Gets a repository
|
||||
repos.get(fullName).then(function(payload){
|
||||
$scope.repo = payload.data;
|
||||
}).catch(function(err){
|
||||
$scope.error = err;
|
||||
});
|
||||
|
||||
// Gets a list of commits
|
||||
builds.list(fullName).then(function(payload){
|
||||
$scope.builds = angular.isArray(payload.data) ? payload.data : [];
|
||||
}).catch(function(err){
|
||||
$scope.error = err;
|
||||
});
|
||||
|
||||
$scope.watch = function(repo) {
|
||||
repos.watch(repo.full_name).then(function(payload) {
|
||||
$scope.repo.starred = true;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.unwatch = function(repo) {
|
||||
repos.unwatch(repo.full_name).then(function() {
|
||||
$scope.repo.starred = false;
|
||||
});
|
||||
}
|
||||
|
||||
repos.subscribe(fullName, function(event) {
|
||||
var added = false;
|
||||
for (var i=0;i<$scope.builds.length;i++) {
|
||||
var build = $scope.builds[i];
|
||||
if (event.sequence !== build.sequence) {
|
||||
continue; // ignore
|
||||
}
|
||||
// update the build status
|
||||
$scope.builds[i] = event;
|
||||
$scope.$apply();
|
||||
added = true;
|
||||
}
|
||||
|
||||
if (!added) {
|
||||
$scope.builds.push(event);
|
||||
$scope.$apply();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
angular
|
||||
.module('drone')
|
||||
.controller('CommitsCtrl', CommitsCtrl)
|
||||
})();
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
// Gets the user tokens
|
||||
tokens.list().then(function(payload){
|
||||
$scope.tokens = payload.data;
|
||||
$scope.tokens = payload.data || [];
|
||||
});
|
||||
|
||||
$scope.newToken={Label: ""};
|
||||
|
|
|
@ -1,10 +1,85 @@
|
|||
<h1>{{ repo.full_name }}/{{ build.number }}</h1>
|
||||
<header>
|
||||
<a href="/" class="logo"></a>
|
||||
<div class="title"></div>
|
||||
<div class="user">
|
||||
<span>{{ user.login }}</span>
|
||||
<img ng-src="{{ user.gravatar_id | gravatar }}" />
|
||||
</div>
|
||||
</header>
|
||||
<nav>
|
||||
<h1>{{ repo.owner }}<span>{{ repo.name }}</span><span>{{ build.sequence}}</span></h1>
|
||||
</nav>
|
||||
<main>
|
||||
|
||||
<section class="commit-section">
|
||||
<div class="commit-status">
|
||||
<div class="status {{ task.state }}" ng-if="task"></div>
|
||||
<div class="status {{ build.state }}" ng-if="!task"></div>
|
||||
</div>
|
||||
<div class="commit-meta">
|
||||
<h2>{{ build.message }}</h2>
|
||||
<h3>authored <span>{{ build.created_at | fromNow }}</span> by <span>@{{ build.author }}</span></h3>
|
||||
|
||||
<span class="octicon octicon-git-commit">{{ build.sha.substr(0,8) }}</span>
|
||||
<span class="octicon octicon-git-branch">{{ build.branch }}</span>
|
||||
<span class="octicon octicon-clock" style="background:#FFF;">{{ build.duration | toDuration }} duration</span>
|
||||
|
||||
<span class="octicon octicon-settings" style="color: #BDBDBD; background:#FFF;position:absolute;top:20px;right:10px;padding:0px;font-size:24px;opacity:0.8;margin:0px;"></span>
|
||||
<span class="md md-more-horiz" style="position:absolute;bottom:20px;right:20px;color:#BDBDBD;font-size:24px;opacity:0.8;margin:0px;"></span>
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
<section>
|
||||
<pre id="term" ng-if="task && task.state !== 'pending'">{{ logs }}</pre>
|
||||
</section>
|
||||
<section ng-if="build.builds.length === 1 || !task">
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Number</th>
|
||||
<th>Status</th>
|
||||
<th>Started</th>
|
||||
<th>Finished</th>
|
||||
<th>Duration</th>
|
||||
<th>Exit Code</th>
|
||||
<th>Matrix</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="task in build.builds">
|
||||
<td><a ng-href="{{ repo.full_name }}/{{ build.sequence }}/{{ task.sequence }}">{{ task.sequence }}</a></td>
|
||||
<td>{{ task.state }}</td>
|
||||
<td>{{ task.started_at | fromNow }}</td>
|
||||
<td>{{ task.finished_at | fromNow }}</td>
|
||||
<td>{{ task.duration | toDuration }}</td>
|
||||
<td>{{ task.exit_code }}</td>
|
||||
<td>{{ task.environment }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<button class="fab" ng-if="build.state === 'running'" ng-click="tail()"></button>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
<button ng-if="build.state !== 'pending' && build.state !== 'running'" ng-click="restart()">Restart</button>
|
||||
<button ng-if="build.state === 'pending' || build.state === 'running'" ng-click="cancel()">Cancel</button>
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
|
||||
<h1>{{ repo.full_name }}/{{ build.sequence }}</h1>
|
||||
|
||||
<a href="/{{ repo.full_name }}">Back</a>
|
||||
|
||||
<div>
|
||||
<button ng-if="build.state !== 'pending' && build.state !== 'running'" ng-click="restart()">Restart</button>
|
||||
<button ng-if="build.state === 'pending' || build.state === 'running'" ng-click="cancel()">Cancel</button>
|
||||
|
||||
</div>
|
||||
|
||||
<dl>
|
||||
|
@ -18,19 +93,22 @@
|
|||
<dd>{{ build.duration | toDuration }}</dd>
|
||||
|
||||
<dt>Type</dt>
|
||||
<dd>{{ build.head_commit ? "push" : "pull request" }}</dd>
|
||||
<dd>{{ build.pull_request ? "pull request" : "push" }}</dd>
|
||||
|
||||
<dt ng-if="build.pull_request">Pull Request</dt>
|
||||
<dd ng-if="build.pull_request">{{ build.pull_request }}</dd>
|
||||
|
||||
<dt>Ref</dt>
|
||||
<dd>{{ build | ref }}</dd>
|
||||
<dd>{{ build.ref }}</dd>
|
||||
|
||||
<dt>Sha</dt>
|
||||
<dd>{{ build | sha }}</dd>
|
||||
<dd>{{ build.sha }}</dd>
|
||||
|
||||
<dt>Author</dt>
|
||||
<dd>{{ build | author }}</dd>
|
||||
<dd>{{ build.author }}</dd>
|
||||
|
||||
<dt>Message</dt>
|
||||
<dd>{{ build | message }}</dd>
|
||||
<dd>{{ build.message }}</dd>
|
||||
</dl>
|
||||
|
||||
<hr>
|
||||
|
@ -55,32 +133,5 @@
|
|||
<dd>{{ task.environment }}</dd>
|
||||
</dl>
|
||||
|
||||
<hr>
|
||||
|
||||
<button ng-if="build.state === 'running'" ng-click="tail()">(un)Follow</button>
|
||||
<pre id="term" ng-if="build.state !== 'pending'">{{ logs }}</pre>
|
||||
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Number</th>
|
||||
<th>Status</th>
|
||||
<th>Started</th>
|
||||
<th>Finished</th>
|
||||
<th>Duration</th>
|
||||
<th>Exit Code</th>
|
||||
<th>Matrix</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="task in build.tasks">
|
||||
<td><a ng-href="{{ repo.full_name }}/{{ build.number }}/{{ task.number }}">{{ task.number }}</a></td>
|
||||
<td>{{ task.state }}</td>
|
||||
<td>{{ task.started_at | fromNow }}</td>
|
||||
<td>{{ task.finished_at | fromNow }}</td>
|
||||
<td>{{ task.duration | toDuration }}</td>
|
||||
<td>{{ task.exit_code }}</td>
|
||||
<td>{{ task.environment }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
-->
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
<a href="/">Back</a>
|
||||
<a ng-href="/{{ repo.full_name }}/edit">Settings</a>
|
||||
|
||||
<button ng-click="watch(repo)" ng-if="!repo.subscription || !repo.subscription.subscribed">Watch</button>
|
||||
<button ng-click="unwatch(repo)" ng-if="repo.subscription && repo.subscription.subscribed">Unwatch</button>
|
||||
<button ng-click="watch(repo)" ng-if="!repo.starred">Watch</button>
|
||||
<button ng-click="unwatch(repo)" ng-if="repo.starred">Unwatch</button>
|
||||
|
||||
<table border="1">
|
||||
<thead>
|
||||
|
@ -21,16 +21,16 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="build in builds | orderBy:'-number'">
|
||||
<td><a ng-href="/{{ repo.full_name }}/{{ build.number }}">{{ build.number }}</a></td>
|
||||
<tr ng-repeat="build in builds | orderBy:'-sequence'">
|
||||
<td><a ng-href="/{{ repo.full_name }}/{{ build.sequence }}">{{ build.sequence }}</a></td>
|
||||
<td>{{ build.state }}</td>
|
||||
<td>{{ build.started_at | fromNow }}</td>
|
||||
<td>{{ build.duration | toDuration }}</td>
|
||||
<td>{{ build.head_commit ? "push" : "pull request" }}</td>
|
||||
<td>{{ build | ref }}</td>
|
||||
<td>{{ build | sha }}</td>
|
||||
<td>{{ build | author }}</td>
|
||||
<td>{{ build | message }}</td>
|
||||
<td>{{ build.pull_request ? "pull request" : "push" }}</td>
|
||||
<td>{{ build.ref }}</td>
|
||||
<td>{{ build.sha }}</td>
|
||||
<td>{{ build.author }}</td>
|
||||
<td>{{ build.message }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
|
@ -4,16 +4,12 @@
|
|||
|
||||
<form>
|
||||
<div>
|
||||
<label>Disable Pushes</label>
|
||||
<input type="checkbox" ng-model="repo.disabled" />
|
||||
<label>Enable Pushes</label>
|
||||
<input type="checkbox" ng-model="repo.post_commits" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Disable PRs</label>
|
||||
<input type="checkbox" ng-model="repo.disabled_prs" />
|
||||
</div>
|
||||
<div>
|
||||
<label>Disable Tags</label>
|
||||
<input type="checkbox" ng-model="repo.disabled_tags" />
|
||||
<label>Enable Pull Requests</label>
|
||||
<input type="checkbox" ng-model="repo.pull_requests" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
590
server/static/styles/drone.css
Normal file
590
server/static/styles/drone.css
Normal file
|
@ -0,0 +1,590 @@
|
|||
html {
|
||||
font-family: sans-serif;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
menu,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
}
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
hr {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
}
|
||||
pre {
|
||||
overflow: auto;
|
||||
}
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
button {
|
||||
overflow: visible;
|
||||
}
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
button,
|
||||
html input[type="button"],
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
cursor: pointer;
|
||||
}
|
||||
button[disabled],
|
||||
html input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box;
|
||||
/* 1 */
|
||||
padding: 0;
|
||||
/* 2 */
|
||||
}
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield;
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
legend {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
optgroup {
|
||||
font-weight: bold;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
td,
|
||||
th {
|
||||
padding: 0;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
font-family: 'Open Sans';
|
||||
font-size: 14px;
|
||||
background: #EEE;
|
||||
}
|
||||
h1 {
|
||||
flex: 1 1 auto;
|
||||
display: inline;
|
||||
margin: 0px;
|
||||
line-height: 70px;
|
||||
font-size: 22px;
|
||||
font-weight: normal;
|
||||
padding-left: 40px;
|
||||
color: #9E9E9E;
|
||||
}
|
||||
h1 span {
|
||||
color: #212121;
|
||||
}
|
||||
h1 span:before {
|
||||
content: "/";
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
header {
|
||||
height: 70px;
|
||||
min-height: 70px;
|
||||
max-height: 70px;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
display: flex;
|
||||
}
|
||||
header nav {
|
||||
flex: 1 1 auto;
|
||||
display: inline;
|
||||
}
|
||||
header nav li {
|
||||
display: inline-block;
|
||||
line-height: 70px;
|
||||
}
|
||||
header menu {
|
||||
flex: 1 1 auto;
|
||||
display: inline;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
padding-right: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
header menu li {
|
||||
display: inline-block;
|
||||
line-height: 70px;
|
||||
}
|
||||
header menu li a {
|
||||
border-radius: 999em;
|
||||
padding: 7px 18px;
|
||||
border: 1px solid #E0E0E0;
|
||||
margin-left: 5px;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.44);
|
||||
text-decoration: none;
|
||||
}
|
||||
header menu li a.primary {
|
||||
border-color: #4CAF50;
|
||||
color: #4CAF50;
|
||||
}
|
||||
aside {
|
||||
width: 20%;
|
||||
max-width: 20%;
|
||||
min-width: 350px;
|
||||
background: #fafafa;
|
||||
box-shadow: -1px 0 1px -2px rgba(0, 0, 0, 0.9);
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
aside div {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||
margin-top: 35px;
|
||||
}
|
||||
aside div:first-child {
|
||||
margin-top: 25px;
|
||||
}
|
||||
aside div h3 {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.44);
|
||||
text-transform: uppercase;
|
||||
line-height: 20px;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
display: inline-block;
|
||||
margin: 0px;
|
||||
padding: 10px 0px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
aside ul.branches {
|
||||
color: rgba(0, 0, 0, 0.901961);
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
aside ul.branches li {
|
||||
list-style: none;
|
||||
vertical-align: middle;
|
||||
height: 30px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
aside ul.branches li:before {
|
||||
content: "\f299";
|
||||
font-family: "Material-Design-Iconic-Font";
|
||||
display: inline-block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #E0E0E0;
|
||||
color: rgba(0, 0, 0, 0.44);
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
text-align: center;
|
||||
line-height: 22px;
|
||||
}
|
||||
aside ul.branches li.success:before {
|
||||
border-color: #4CAF50;
|
||||
color: #4CAF50;
|
||||
}
|
||||
aside ul.branches li.failure:before {
|
||||
content: "\f29a";
|
||||
border-color: #F44336;
|
||||
color: #F44336;
|
||||
}
|
||||
aside ul.users {
|
||||
color: rgba(0, 0, 0, 0.901961);
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
aside ul.users li {
|
||||
list-style: none;
|
||||
vertical-align: middle;
|
||||
height: 30px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
aside ul.users li img {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
article {
|
||||
flex: 1 1 auto;
|
||||
box-sizing: border-box;
|
||||
padding: 40px;
|
||||
padding-left: 80px;
|
||||
}
|
||||
article section pre {
|
||||
background: #212121;
|
||||
color: #FFF;
|
||||
padding: 50px;
|
||||
font-family: 'Droid Sans Mono';
|
||||
position: relative;
|
||||
line-height: 20px;
|
||||
font-size: 13px;
|
||||
margin: 0px;
|
||||
margin-bottom: 20px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
.logo {
|
||||
height: 50px;
|
||||
max-height: 50px;
|
||||
line-height: 50px;
|
||||
width: 50px;
|
||||
margin-left: 10px;
|
||||
margin-top: 10px;
|
||||
margin-right: 10px;
|
||||
border-radius: 50%;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.logo:before {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 50px;
|
||||
background: url("../images/drone_32.svg") no-repeat center center;
|
||||
background-size: 34px;
|
||||
}
|
||||
header {
|
||||
background: #212121;
|
||||
height: 70px;
|
||||
min-height: 70px;
|
||||
max-height: 70px;
|
||||
display: flex;
|
||||
}
|
||||
nav {
|
||||
background: #FFF;
|
||||
height: 70px;
|
||||
min-height: 70px;
|
||||
max-height: 70px;
|
||||
display: flex;
|
||||
border-bottom: 1px solid #F5F5F5;
|
||||
border-bottom: 1px solid #EEEEEE;
|
||||
}
|
||||
.logo {
|
||||
width: 50px;
|
||||
margin-left: 10px;
|
||||
margin-top: 10px;
|
||||
margin-right: 10px;
|
||||
border-radius: 50%;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.logo:before {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 50px;
|
||||
background: url("../images/logo.svg") no-repeat center center;
|
||||
background-size: 34px;
|
||||
}
|
||||
.logo:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.title {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.user {
|
||||
text-align: right;
|
||||
line-height: 70px;
|
||||
}
|
||||
.user img {
|
||||
border-radius: 50%;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 20px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
.user span {
|
||||
font-size: 16px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
main {
|
||||
padding: 40px 80px 80px;
|
||||
}
|
||||
section {
|
||||
margin: 0px auto;
|
||||
max-width: 1000px;
|
||||
background: #FFF;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
section pre {
|
||||
background: #212121;
|
||||
color: #FFF;
|
||||
padding: 50px;
|
||||
font-family: 'Droid Sans Mono';
|
||||
position: relative;
|
||||
line-height: 20px;
|
||||
font-size: 13px;
|
||||
margin: 0px;
|
||||
margin-bottom: 20px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
.commit-section {
|
||||
display: flex;
|
||||
position: relative;
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
padding-left: 30px;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.commit-section .commit-status {
|
||||
width: 60px;
|
||||
min-width: 60px;
|
||||
max-width: 60px;
|
||||
}
|
||||
.commit-section .commit-meta {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.commit-section .commit-meta h2 {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
.commit-section .commit-meta .octicon {
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
margin: 0px;
|
||||
margin-top: 20px;
|
||||
color: #757575;
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
background: #eee;
|
||||
background: #F5F5F5;
|
||||
padding: 8px 18px;
|
||||
padding-left: 15px;
|
||||
color: #9E9E9E;
|
||||
}
|
||||
.commit-section .commit-meta .octicon:before {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.commit-section .commit-meta h2 small {
|
||||
color: #757575;
|
||||
margin-left: 10px;
|
||||
font-size: 90%;
|
||||
}
|
||||
.commit-section .commit-meta h3 {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
color: #757575;
|
||||
}
|
||||
.commit-section .commit-meta span {
|
||||
color: #212121;
|
||||
}
|
||||
.commit-section .commit-meta p {
|
||||
color: #424242;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
font-size: 14px;
|
||||
color: #757575;
|
||||
margin-top: 3px;
|
||||
}
|
||||
.commit-section .commit-meta p.tag {
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
margin: 0px;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 10px;
|
||||
color: #757575;
|
||||
display: block;
|
||||
text-align: right;
|
||||
margin-bottom: 5px;
|
||||
float: right;
|
||||
background: #eee;
|
||||
padding: 5px 20px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.fab {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 50px;
|
||||
background: rgba(158, 158, 158, 0.75);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
z-index: 2;
|
||||
border-radius: 50%;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
}
|
||||
.fab:before {
|
||||
content: "\f297";
|
||||
font-family: 'Material-Design-Iconic-Font';
|
||||
transform: rotate(90%);
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
width: 40px;
|
||||
display: block;
|
||||
color: #FFF;
|
||||
font-size: 22px;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
.status {
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
color: #FFF;
|
||||
font-size: 20px;
|
||||
font-family: "Open Sans";
|
||||
font-weight: 600;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.status:before {
|
||||
line-height: 35px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
font-family: 'Material-Design-Iconic-Font';
|
||||
}
|
||||
.success {
|
||||
background: #4CAF50;
|
||||
background: #FFF;
|
||||
border: 2px solid #4CAF50;
|
||||
}
|
||||
.success:before {
|
||||
content: "\f023";
|
||||
color: #4CAF50;
|
||||
}
|
||||
.failure {
|
||||
background: #F44336;
|
||||
background: #FFF;
|
||||
border: 2px solid #F44336;
|
||||
}
|
||||
.failure:before {
|
||||
content: "\f29a";
|
||||
color: #F44336;
|
||||
}
|
|
@ -30,13 +30,14 @@ func PostToken(c *gin.Context) {
|
|||
|
||||
token := &common.Token{}
|
||||
token.Label = in.Label
|
||||
token.Repos = in.Repos
|
||||
token.Scopes = in.Scopes
|
||||
token.UserID = user.ID
|
||||
// token.Repos = in.Repos
|
||||
// token.Scopes = in.Scopes
|
||||
token.Login = user.Login
|
||||
token.Kind = common.TokenUser
|
||||
token.Issued = time.Now().UTC().Unix()
|
||||
|
||||
err := store.SetToken(token)
|
||||
err := store.AddToken(token)
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
}
|
||||
|
@ -57,7 +58,7 @@ func DelToken(c *gin.Context) {
|
|||
user := ToUser(c)
|
||||
label := c.Params.ByName("label")
|
||||
|
||||
token, err := store.Token(user.Login, label)
|
||||
token, err := store.TokenLabel(user, label)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ func PutUserCurr(c *gin.Context) {
|
|||
func GetUserRepos(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
user := ToUser(c)
|
||||
repos, err := store.RepoList(user.Login)
|
||||
repos, err := store.RepoList(user)
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
} else {
|
||||
|
@ -67,7 +67,7 @@ func GetUserRepos(c *gin.Context) {
|
|||
func GetUserTokens(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
user := ToUser(c)
|
||||
tokens, err := store.TokenList(user.Login)
|
||||
tokens, err := store.TokenList(user)
|
||||
if err != nil {
|
||||
c.Fail(400, err)
|
||||
} else {
|
||||
|
|
|
@ -35,7 +35,7 @@ func PostUser(c *gin.Context) {
|
|||
user := &common.User{Login: name, Name: name}
|
||||
user.Token = c.Request.FormValue("token")
|
||||
user.Secret = c.Request.FormValue("secret")
|
||||
if err := store.SetUserNotExists(user); err != nil {
|
||||
if err := store.AddUser(user); err != nil {
|
||||
c.Fail(400, err)
|
||||
} else {
|
||||
c.JSON(201, user)
|
||||
|
@ -51,7 +51,7 @@ func PostUser(c *gin.Context) {
|
|||
func GetUser(c *gin.Context) {
|
||||
store := ToDatastore(c)
|
||||
name := c.Params.ByName("name")
|
||||
user, err := store.User(name)
|
||||
user, err := store.UserLogin(name)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
} else {
|
||||
|
@ -69,7 +69,7 @@ func PutUser(c *gin.Context) {
|
|||
store := ToDatastore(c)
|
||||
me := ToUser(c)
|
||||
name := c.Params.ByName("name")
|
||||
user, err := store.User(name)
|
||||
user, err := store.UserLogin(name)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
|
@ -106,7 +106,7 @@ func DeleteUser(c *gin.Context) {
|
|||
store := ToDatastore(c)
|
||||
me := ToUser(c)
|
||||
name := c.Params.ByName("name")
|
||||
user, err := store.User(name)
|
||||
user, err := store.UserLogin(name)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
|
|
19
server/ws.go
19
server/ws.go
|
@ -88,11 +88,11 @@ func GetRepoEvents(c *gin.Context) {
|
|||
}
|
||||
|
||||
func GetStream(c *gin.Context) {
|
||||
// store := ToDatastore(c)
|
||||
store := ToDatastore(c)
|
||||
repo := ToRepo(c)
|
||||
runner := ToRunner(c)
|
||||
build, _ := strconv.Atoi(c.Params.ByName("build"))
|
||||
task, _ := strconv.Atoi(c.Params.ByName("number"))
|
||||
commitseq, _ := strconv.Atoi(c.Params.ByName("build"))
|
||||
buildseq, _ := strconv.Atoi(c.Params.ByName("number"))
|
||||
|
||||
// agent, err := store.BuildAgent(repo.FullName, build)
|
||||
// if err != nil {
|
||||
|
@ -100,7 +100,18 @@ func GetStream(c *gin.Context) {
|
|||
// return
|
||||
// }
|
||||
|
||||
rc, err := runner.Logs(repo.FullName, build, task)
|
||||
commit, err := store.CommitSeq(repo, commitseq)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
build, err := store.BuildSeq(commit, buildseq)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
}
|
||||
|
||||
rc, err := runner.Logs(build)
|
||||
if err != nil {
|
||||
c.Fail(404, err)
|
||||
return
|
||||
|
|
|
@ -87,7 +87,8 @@ type Docker struct {
|
|||
// Database represents the configuration details used
|
||||
// to connect to the embedded Bolt database.
|
||||
type Database struct {
|
||||
Path string `toml:"path"`
|
||||
Driver string `toml:"driver"`
|
||||
Datasource string `toml:"datasource"`
|
||||
}
|
||||
|
||||
// Settings defines global settings for the Drone system.
|
||||
|
|
Loading…
Reference in a new issue