backported 0.4 changes to existing database

This commit is contained in:
Brad Rydzewski 2015-05-11 00:45:31 -07:00
parent d07c0cb80d
commit 85256d3a22
69 changed files with 3731 additions and 2884 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"`

View file

@ -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
View 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
View 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"`
}

View file

@ -1,7 +1,6 @@
package common
type Hook struct {
Repo *Repo
Commit *Commit
PullRequest *PullRequest
Repo *Repo
Commit *Commit
}

View file

@ -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
View 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"`
}

View file

@ -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"`
}

View file

@ -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
View 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 = ?;
`

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

View file

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

View file

@ -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;
`

View file

@ -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
View 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 = ?
`

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

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

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

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

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

View file

@ -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, &params)
})
// // 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 = ?`
)

View file

@ -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(&params)
// 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
View 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=?
`

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

View file

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

View file

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

View file

@ -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=?
`

View file

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

View file

@ -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 ?
`

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"`
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

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

View file

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

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

View file

@ -18,7 +18,7 @@
// Gets the user tokens
tokens.list().then(function(payload){
$scope.tokens = payload.data;
$scope.tokens = payload.data || [];
});
$scope.newToken={Label: ""};

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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