added new handlers and workers
This commit is contained in:
parent
8993a71df2
commit
21f9aec808
45 changed files with 1522 additions and 3330 deletions
2
Makefile
2
Makefile
|
@ -21,7 +21,7 @@ install:
|
|||
install -t /usr/local/bin debian/drone/usr/local/bin/droned
|
||||
|
||||
run:
|
||||
@go run server/main.go
|
||||
@go run server/main.go --config=$$HOME/.drone/config.toml
|
||||
|
||||
clean:
|
||||
find . -name "*.out" -delete
|
||||
|
|
5
server/capability/const.go
Normal file
5
server/capability/const.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package capability
|
||||
|
||||
const (
|
||||
Registration = "REGISTRATION"
|
||||
)
|
|
@ -1,252 +0,0 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
type CommitManager interface {
|
||||
// Find finds the commit by ID.
|
||||
Find(id int64) (*model.Commit, error)
|
||||
|
||||
// FindSha finds the commit for the branch and sha.
|
||||
FindSha(repo int64, branch, sha string) (*model.Commit, error)
|
||||
|
||||
// FindLatest finds the most recent commit for the branch.
|
||||
FindLatest(repo int64, branch string) (*model.Commit, error)
|
||||
|
||||
// FindOutput finds the commit's output.
|
||||
FindOutput(commit int64) ([]byte, error)
|
||||
|
||||
// List finds recent commits for the repository
|
||||
List(repo int64) ([]*model.Commit, error)
|
||||
|
||||
// ListBranch finds recent commits for the repository and branch.
|
||||
ListBranch(repo int64, branch string) ([]*model.Commit, error)
|
||||
|
||||
// ListBranches finds most recent commit for each branch.
|
||||
//ListBranches(repo int64) ([]*model.Commit, error)
|
||||
|
||||
// ListUser finds most recent commits for a user.
|
||||
ListUser(repo int64) ([]*model.CommitRepo, error)
|
||||
|
||||
// Insert persists the commit to the datastore.
|
||||
Insert(commit *model.Commit) error
|
||||
|
||||
// Update persists changes to the commit to the datastore.
|
||||
Update(commit *model.Commit) error
|
||||
|
||||
// UpdateOutput persists a commit's stdout to the datastore.
|
||||
UpdateOutput(commit *model.Commit, out []byte) error
|
||||
|
||||
// Delete removes the commit from the datastore.
|
||||
Delete(commit *model.Commit) error
|
||||
|
||||
// CancelAll will update the status of all Started or Pending
|
||||
// builds to a status of Killed (cancelled).
|
||||
CancelAll() error
|
||||
}
|
||||
|
||||
// commitManager manages a list of commits in a SQL database.
|
||||
type commitManager struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
// NewCommitManager initiales a new CommitManager intended to
|
||||
// manage and persist commits.
|
||||
func NewCommitManager(db *sql.DB) CommitManager {
|
||||
return &commitManager{db}
|
||||
}
|
||||
|
||||
// SQL query to retrieve the latest Commits for each branch.
|
||||
const listBranchesQuery = `
|
||||
SELECT *
|
||||
FROM commits
|
||||
WHERE commit_id IN (
|
||||
SELECT MAX(commit_id)
|
||||
FROM commits
|
||||
WHERE repo_id=?
|
||||
AND commit_status NOT IN ('Started', 'Pending')
|
||||
GROUP BY commit_branch)
|
||||
ORDER BY commit_branch ASC
|
||||
`
|
||||
|
||||
// SQL query to retrieve the latest Commits for a specific branch.
|
||||
const listBranchQuery = `
|
||||
SELECT *
|
||||
FROM commits
|
||||
WHERE repo_id=?
|
||||
AND commit_branch=?
|
||||
ORDER BY commit_id DESC
|
||||
LIMIT 20
|
||||
`
|
||||
|
||||
// SQL query to retrieve the latest Commits for a user's repositories.
|
||||
//const listUserCommitsQuery = `
|
||||
//SELECT r.repo_remote, r.repo_host, r.repo_owner, r.repo_name, c.*
|
||||
//FROM commits c, repos r, perms p
|
||||
//WHERE c.repo_id=r.repo_id
|
||||
//AND r.repo_id=p.repo_id
|
||||
//AND p.user_id=?
|
||||
//AND c.commit_status NOT IN ('Started', 'Pending')
|
||||
//ORDER BY commit_id DESC
|
||||
//LIMIT 20
|
||||
//`
|
||||
|
||||
const listUserCommitsQuery = `
|
||||
SELECT r.repo_remote, r.repo_host, r.repo_owner, r.repo_name, c.*
|
||||
FROM commits c, repos r
|
||||
WHERE c.repo_id=r.repo_id
|
||||
AND c.commit_id IN (
|
||||
SELECT max(c.commit_id)
|
||||
FROM commits c, repos r, perms p
|
||||
WHERE c.repo_id=r.repo_id
|
||||
AND r.repo_id=p.repo_id
|
||||
AND p.user_id=?
|
||||
AND c.commit_id
|
||||
AND c.commit_status NOT IN ('Started', 'Pending')
|
||||
GROUP BY r.repo_id
|
||||
) ORDER BY c.commit_created DESC LIMIT 5;
|
||||
`
|
||||
|
||||
// SQL query to retrieve the latest Commits across all branches.
|
||||
const listCommitsQuery = `
|
||||
SELECT *
|
||||
FROM commits
|
||||
WHERE repo_id=?
|
||||
ORDER BY commit_id DESC
|
||||
LIMIT 20
|
||||
`
|
||||
|
||||
// SQL query to retrieve a Commit by branch and sha.
|
||||
const findCommitQuery = `
|
||||
SELECT *
|
||||
FROM commits
|
||||
WHERE repo_id=?
|
||||
AND commit_branch=?
|
||||
AND commit_sha=?
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
// SQL query to retrieve the most recent Commit for a branch.
|
||||
const findLatestCommitQuery = `
|
||||
SELECT *
|
||||
FROM commits
|
||||
WHERE commit_id IN (
|
||||
SELECT MAX(commit_id)
|
||||
FROM commits
|
||||
WHERE repo_id=?
|
||||
AND commit_branch=?)
|
||||
`
|
||||
|
||||
// SQL query to retrieve a Commit's stdout.
|
||||
const findOutputQuery = `
|
||||
SELECT output_raw
|
||||
FROM output
|
||||
WHERE commit_id = ?
|
||||
`
|
||||
|
||||
// SQL statement to insert a Commit's stdout.
|
||||
const insertOutputStmt = `
|
||||
INSERT INTO output (commit_id, output_raw) values (?,?);
|
||||
`
|
||||
|
||||
// SQL statement to update a Commit's stdout.
|
||||
const updateOutputStmt = `
|
||||
UPDATE output SET output_raw = ? WHERE commit_id = ?;
|
||||
`
|
||||
|
||||
// SQL statement to delete a Commit by ID.
|
||||
const deleteCommitStmt = `
|
||||
DELETE FROM commits WHERE commit_id = ?;
|
||||
`
|
||||
|
||||
// SQL statement to cancel all running Commits.
|
||||
const cancelCommitStmt = `
|
||||
UPDATE commits SET
|
||||
commit_status = ?,
|
||||
commit_started = ?,
|
||||
commit_finished = ?
|
||||
WHERE commit_status IN ('Started', 'Pending');
|
||||
`
|
||||
|
||||
func (db *commitManager) Find(id int64) (*model.Commit, error) {
|
||||
dst := model.Commit{}
|
||||
err := meddler.Load(db, "commits", &dst, id)
|
||||
return &dst, err
|
||||
}
|
||||
|
||||
func (db *commitManager) FindSha(repo int64, branch, sha string) (*model.Commit, error) {
|
||||
dst := model.Commit{}
|
||||
err := meddler.QueryRow(db, &dst, findCommitQuery, repo, branch, sha)
|
||||
return &dst, err
|
||||
}
|
||||
|
||||
func (db *commitManager) FindLatest(repo int64, branch string) (*model.Commit, error) {
|
||||
dst := model.Commit{}
|
||||
err := meddler.QueryRow(db, &dst, findLatestCommitQuery, repo, branch)
|
||||
return &dst, err
|
||||
}
|
||||
|
||||
func (db *commitManager) FindOutput(commit int64) ([]byte, error) {
|
||||
var dst string
|
||||
err := db.QueryRow(findOutputQuery, commit).Scan(&dst)
|
||||
return []byte(dst), err
|
||||
}
|
||||
|
||||
func (db *commitManager) List(repo int64) ([]*model.Commit, error) {
|
||||
var dst []*model.Commit
|
||||
err := meddler.QueryAll(db, &dst, listCommitsQuery, repo)
|
||||
return dst, err
|
||||
}
|
||||
|
||||
func (db *commitManager) ListBranch(repo int64, branch string) ([]*model.Commit, error) {
|
||||
var dst []*model.Commit
|
||||
err := meddler.QueryAll(db, &dst, listBranchQuery, repo, branch)
|
||||
return dst, err
|
||||
}
|
||||
|
||||
func (db *commitManager) ListBranches(repo int64) ([]*model.Commit, error) {
|
||||
var dst []*model.Commit
|
||||
err := meddler.QueryAll(db, &dst, listBranchesQuery, repo)
|
||||
return dst, err
|
||||
}
|
||||
|
||||
func (db *commitManager) ListUser(user int64) ([]*model.CommitRepo, error) {
|
||||
var dst []*model.CommitRepo
|
||||
err := meddler.QueryAll(db, &dst, listUserCommitsQuery, user)
|
||||
return dst, err
|
||||
}
|
||||
|
||||
func (db *commitManager) Insert(commit *model.Commit) error {
|
||||
commit.Created = time.Now().Unix()
|
||||
commit.Updated = time.Now().Unix()
|
||||
return meddler.Insert(db, "commits", commit)
|
||||
}
|
||||
|
||||
func (db *commitManager) Update(commit *model.Commit) error {
|
||||
commit.Updated = time.Now().Unix()
|
||||
return meddler.Update(db, "commits", commit)
|
||||
}
|
||||
|
||||
func (db *commitManager) UpdateOutput(commit *model.Commit, out []byte) error {
|
||||
_, err := db.Exec(insertOutputStmt, commit.ID, out)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
_, err = db.Exec(updateOutputStmt, out, commit.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *commitManager) Delete(commit *model.Commit) error {
|
||||
_, err := db.Exec(deleteCommitStmt, commit.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *commitManager) CancelAll() error {
|
||||
_, err := db.Exec(cancelCommitStmt, model.StatusKilled, time.Now().Unix(), time.Now().Unix())
|
||||
return err
|
||||
}
|
|
@ -1,282 +0,0 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/shared/model"
|
||||
)
|
||||
|
||||
func TestCommitFind(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
commits := NewCommitManager(db)
|
||||
commit, err := commits.Find(3)
|
||||
if err != nil {
|
||||
t.Errorf("Want Commit from ID, got %s", err)
|
||||
}
|
||||
|
||||
testCommit(t, commit)
|
||||
}
|
||||
|
||||
func TestCommitFindSha(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
commits := NewCommitManager(db)
|
||||
commit, err := commits.FindSha(2, "master", "7253f6545caed41fb8f5a6fcdb3abc0b81fa9dbf")
|
||||
if err != nil {
|
||||
t.Errorf("Want Commit from SHA, got %s", err)
|
||||
}
|
||||
|
||||
testCommit(t, commit)
|
||||
}
|
||||
|
||||
func TestCommitFindLatest(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
commits := NewCommitManager(db)
|
||||
commit, err := commits.FindLatest(2, "master")
|
||||
if err != nil {
|
||||
t.Errorf("Want Latest Commit, got %s", err)
|
||||
}
|
||||
|
||||
testCommit(t, commit)
|
||||
}
|
||||
|
||||
func TestCommitFindOutput(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
commits := NewCommitManager(db)
|
||||
out, err := commits.FindOutput(1)
|
||||
if err != nil {
|
||||
t.Errorf("Want Commit stdout, got %s", err)
|
||||
}
|
||||
|
||||
var want, got = "sample console output", string(out)
|
||||
if want != got {
|
||||
t.Errorf("Want stdout %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitList(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
commits := NewCommitManager(db)
|
||||
list, err := commits.List(2)
|
||||
if err != nil {
|
||||
t.Errorf("Want List from RepoID, got %s", err)
|
||||
}
|
||||
|
||||
var got, want = len(list), 3
|
||||
if got != want {
|
||||
t.Errorf("Want List size %v, got %v", want, got)
|
||||
}
|
||||
|
||||
testCommit(t, list[0])
|
||||
}
|
||||
|
||||
func TestCommitListBranch(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
commits := NewCommitManager(db)
|
||||
list, err := commits.ListBranch(2, "master")
|
||||
if err != nil {
|
||||
t.Errorf("Want List from RepoID, got %s", err)
|
||||
}
|
||||
|
||||
var got, want = len(list), 2
|
||||
if got != want {
|
||||
t.Errorf("Want List size %v, got %v", want, got)
|
||||
}
|
||||
|
||||
testCommit(t, list[0])
|
||||
}
|
||||
|
||||
func TestCommitListBranches(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
commits := NewCommitManager(db)
|
||||
list, err := commits.ListBranches(2)
|
||||
if err != nil {
|
||||
t.Errorf("Want Branch List from RepoID, got %s", err)
|
||||
}
|
||||
|
||||
var got, want = len(list), 2
|
||||
if got != want {
|
||||
t.Errorf("Want List size %v, got %v", want, got)
|
||||
}
|
||||
|
||||
testCommit(t, list[1])
|
||||
}
|
||||
|
||||
func TestCommitInsert(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
commit := model.Commit{RepoID: 3, Branch: "foo", Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac"}
|
||||
commits := NewCommitManager(db)
|
||||
if err := commits.Insert(&commit); err != nil {
|
||||
t.Errorf("Want Commit created, got %s", err)
|
||||
}
|
||||
|
||||
// verify that it is ok to add same sha for different branch
|
||||
var err = commits.Insert(&model.Commit{RepoID: 3, Branch: "bar", Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac"})
|
||||
if err != nil {
|
||||
t.Errorf("Want Commit created, got %s", err)
|
||||
}
|
||||
|
||||
// verify unique remote + remote id constraint
|
||||
err = commits.Insert(&model.Commit{RepoID: 3, Branch: "bar", Sha: "85f8c029b902ed9400bc600bac301a0aadb144ac"})
|
||||
if err == nil {
|
||||
t.Error("Want unique constraint violated")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCommitUpdate(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
commits := NewCommitManager(db)
|
||||
commit, err := commits.Find(5)
|
||||
if err != nil {
|
||||
t.Errorf("Want Commit from ID, got %s", err)
|
||||
}
|
||||
|
||||
// update the commit's access token
|
||||
commit.Status = "Success"
|
||||
commit.Finished = time.Now().Unix()
|
||||
commit.Duration = 999
|
||||
if err := commits.Update(commit); err != nil {
|
||||
t.Errorf("Want Commit updated, got %s", err)
|
||||
}
|
||||
|
||||
updated, _ := commits.Find(5)
|
||||
var got, want = updated.Status, "Success"
|
||||
if got != want {
|
||||
t.Errorf("Want updated Status %v, got %v", want, got)
|
||||
}
|
||||
|
||||
var gotInt64, wantInt64 = updated.ID, commit.ID
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want commit ID %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
|
||||
gotInt64, wantInt64 = updated.Duration, commit.Duration
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want updated Duration %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
|
||||
gotInt64, wantInt64 = updated.Finished, commit.Finished
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want updated Finished %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitDelete(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
commits := NewCommitManager(db)
|
||||
commit, err := commits.Find(1)
|
||||
if err != nil {
|
||||
t.Errorf("Want Commit from ID, got %s", err)
|
||||
}
|
||||
|
||||
// delete the commit
|
||||
if err := commits.Delete(commit); err != nil {
|
||||
t.Errorf("Want Commit deleted, got %s", err)
|
||||
}
|
||||
|
||||
// check to see if the deleted commit is actually gone
|
||||
if _, err := commits.Find(1); err != sql.ErrNoRows {
|
||||
t.Errorf("Want ErrNoRows, got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// testCommit is a helper function that compares the commit
|
||||
// to an expected set of fixed field values.
|
||||
func testCommit(t *testing.T, commit *model.Commit) {
|
||||
var got, want = commit.Status, "Success"
|
||||
if got != want {
|
||||
t.Errorf("Want Status %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = commit.Sha, "7253f6545caed41fb8f5a6fcdb3abc0b81fa9dbf"
|
||||
if got != want {
|
||||
t.Errorf("Want Sha %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = commit.Branch, "master"
|
||||
if got != want {
|
||||
t.Errorf("Want Branch %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = commit.PullRequest, "5"
|
||||
if got != want {
|
||||
t.Errorf("Want PullRequest %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = commit.Author, "drcooper@caltech.edu"
|
||||
if got != want {
|
||||
t.Errorf("Want Author %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = commit.Gravatar, "ab23a88a3ed77ecdfeb894c0eaf2817a"
|
||||
if got != want {
|
||||
t.Errorf("Want Gravatar %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = commit.Timestamp, "Wed Apr 23 01:02:38 2014 -0700"
|
||||
if got != want {
|
||||
t.Errorf("Want Timestamp %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = commit.Message, "a commit message"
|
||||
if got != want {
|
||||
t.Errorf("Want Message %v, got %v", want, got)
|
||||
}
|
||||
|
||||
var gotInt64, wantInt64 = commit.ID, int64(3)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want ID %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
|
||||
gotInt64, wantInt64 = commit.RepoID, int64(2)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want RepoID %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
|
||||
gotInt64, wantInt64 = commit.Created, int64(1398065343)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want Created %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
|
||||
gotInt64, wantInt64 = commit.Updated, int64(1398065344)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want Updated %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
|
||||
gotInt64, wantInt64 = commit.Started, int64(1398065345)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want Started %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
|
||||
gotInt64, wantInt64 = commit.Finished, int64(1398069999)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want Finished %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
|
||||
gotInt64, wantInt64 = commit.Duration, int64(854)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want Duration %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
}
|
|
@ -1,161 +0,0 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
type PermManager interface {
|
||||
// Grant will grant the user read, write and admin persmissions
|
||||
// to the specified repository.
|
||||
Grant(u *model.User, r *model.Repo, read, write, admin bool) error
|
||||
|
||||
// Revoke will revoke all user permissions to the specified repository.
|
||||
Revoke(u *model.User, r *model.Repo) error
|
||||
|
||||
// Find returns the user's permission to access the specified repository.
|
||||
Find(u *model.User, r *model.Repo) *model.Perm
|
||||
|
||||
// Read returns true if the specified user has read
|
||||
// access to the repository.
|
||||
Read(u *model.User, r *model.Repo) (bool, error)
|
||||
|
||||
// Write returns true if the specified user has write
|
||||
// access to the repository.
|
||||
//Write(u *model.User, r *model.Repo) (bool, error)
|
||||
|
||||
// Admin returns true if the specified user is an
|
||||
// administrator of the repository.
|
||||
Admin(u *model.User, r *model.Repo) (bool, error)
|
||||
|
||||
// Member returns true if the specified user is a
|
||||
// collaborator on the repository.
|
||||
//Member(u *model.User, r *model.Repo) (bool, error)
|
||||
}
|
||||
|
||||
// permManager manages user permissions to access repositories.
|
||||
type permManager struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
// SQL query to retrieve a user's permission to
|
||||
// access a repository.
|
||||
const findPermQuery = `
|
||||
SELECT *
|
||||
FROM perms
|
||||
WHERE user_id=?
|
||||
AND repo_id=?
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
// SQL statement to delete a permission.
|
||||
const deletePermStmt = `
|
||||
DELETE FROM perms WHERE user_id=? AND repo_id=?
|
||||
`
|
||||
|
||||
// NewManager initiales a new PermManager intended to
|
||||
// manage user permission and access control.
|
||||
func NewPermManager(db *sql.DB) PermManager {
|
||||
return &permManager{db}
|
||||
}
|
||||
|
||||
// Grant will grant the user read, write and admin persmissions
|
||||
// to the specified repository.
|
||||
func (db *permManager) Grant(u *model.User, r *model.Repo, read, write, admin bool) error {
|
||||
// attempt to get existing permissions from the database
|
||||
perm, err := db.find(u, r)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
|
||||
// if this is a new permission set the user ID,
|
||||
// repository ID and created timestamp.
|
||||
if perm.ID == 0 {
|
||||
perm.UserID = u.ID
|
||||
perm.RepoID = r.ID
|
||||
perm.Created = time.Now().Unix()
|
||||
}
|
||||
|
||||
// set all the permission values
|
||||
perm.Read = read
|
||||
perm.Write = write
|
||||
perm.Admin = admin
|
||||
perm.Updated = time.Now().Unix()
|
||||
|
||||
// update the database
|
||||
return meddler.Save(db, "perms", perm)
|
||||
}
|
||||
|
||||
// Revoke will revoke all user permissions to the specified repository.
|
||||
func (db *permManager) Revoke(u *model.User, r *model.Repo) error {
|
||||
_, err := db.Exec(deletePermStmt, u.ID, r.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *permManager) Find(u *model.User, r *model.Repo) *model.Perm {
|
||||
// if the user is a gues they should only be granted
|
||||
// read access to public repositories.
|
||||
switch {
|
||||
case u == nil && r.Private:
|
||||
return &model.Perm{
|
||||
Guest: true,
|
||||
Read: false,
|
||||
Write: false,
|
||||
Admin: false}
|
||||
case u == nil && !r.Private:
|
||||
return &model.Perm{
|
||||
Guest: true,
|
||||
Read: true,
|
||||
Write: false,
|
||||
Admin: false}
|
||||
}
|
||||
|
||||
// if the user is authenticated we'll retireive the
|
||||
// permission details from the database.
|
||||
perm, err := db.find(u, r)
|
||||
if err != nil && perm.ID != 0 {
|
||||
return perm
|
||||
}
|
||||
|
||||
switch {
|
||||
// if the user is a system admin grant super access.
|
||||
case u.Admin == true:
|
||||
perm.Read = true
|
||||
perm.Write = true
|
||||
perm.Admin = true
|
||||
perm.Guest = true
|
||||
|
||||
// if the repo is public, grant read access only.
|
||||
case r.Private == false:
|
||||
perm.Read = true
|
||||
perm.Guest = true
|
||||
}
|
||||
|
||||
return perm
|
||||
}
|
||||
|
||||
func (db *permManager) Read(u *model.User, r *model.Repo) (bool, error) {
|
||||
return db.Find(u, r).Read, nil
|
||||
}
|
||||
|
||||
func (db *permManager) Write(u *model.User, r *model.Repo) (bool, error) {
|
||||
return db.Find(u, r).Write, nil
|
||||
}
|
||||
|
||||
func (db *permManager) Admin(u *model.User, r *model.Repo) (bool, error) {
|
||||
return db.Find(u, r).Admin, nil
|
||||
}
|
||||
|
||||
func (db *permManager) Member(u *model.User, r *model.Repo) (bool, error) {
|
||||
perm := db.Find(u, r)
|
||||
return perm.Read && !perm.Guest, nil
|
||||
}
|
||||
|
||||
func (db *permManager) find(u *model.User, r *model.Repo) (*model.Perm, error) {
|
||||
var dst = model.Perm{}
|
||||
var err = meddler.QueryRow(db, &dst, findPermQuery, u.ID, r.ID)
|
||||
return &dst, err
|
||||
}
|
|
@ -1,310 +0,0 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/shared/model"
|
||||
)
|
||||
|
||||
func Test_find(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
manager := NewPermManager(db).(*permManager)
|
||||
perm, err := manager.find(&model.User{ID: 101}, &model.Repo{ID: 200})
|
||||
if err != nil {
|
||||
t.Errorf("Want permission, got %s", err)
|
||||
}
|
||||
|
||||
var got, want = perm.ID, int64(1)
|
||||
if got != want {
|
||||
t.Errorf("Want ID %d, got %d", got, want)
|
||||
}
|
||||
|
||||
got, want = perm.UserID, int64(101)
|
||||
if got != want {
|
||||
t.Errorf("Want Created %d, got %d", got, want)
|
||||
}
|
||||
|
||||
got, want = perm.RepoID, int64(200)
|
||||
if got != want {
|
||||
t.Errorf("Want Created %d, got %d", got, want)
|
||||
}
|
||||
|
||||
got, want = perm.Created, int64(1398065343)
|
||||
if got != want {
|
||||
t.Errorf("Want Created %d, got %d", got, want)
|
||||
}
|
||||
|
||||
got, want = perm.Updated, int64(1398065344)
|
||||
if got != want {
|
||||
t.Errorf("Want Updated %d, got %d", got, want)
|
||||
}
|
||||
|
||||
var gotBool, wantBool = perm.Read, true
|
||||
if gotBool != wantBool {
|
||||
t.Errorf("Want Read %v, got %v", gotBool, wantBool)
|
||||
}
|
||||
|
||||
gotBool, wantBool = perm.Write, true
|
||||
if gotBool != wantBool {
|
||||
t.Errorf("Want Read %v, got %v", gotBool, wantBool)
|
||||
}
|
||||
|
||||
gotBool, wantBool = perm.Admin, true
|
||||
if gotBool != wantBool {
|
||||
t.Errorf("Want Read %v, got %v", gotBool, wantBool)
|
||||
}
|
||||
|
||||
// test that we get the appropriate error message when
|
||||
// no permissions are found in the database.
|
||||
_, err = manager.find(&model.User{ID: 102}, &model.Repo{ID: 201})
|
||||
if err != sql.ErrNoRows {
|
||||
t.Errorf("Want ErrNoRows, got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermFind(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
manager := NewPermManager(db).(*permManager)
|
||||
|
||||
u := model.User{ID: 101, Admin: false}
|
||||
r := model.Repo{ID: 201, Private: false}
|
||||
|
||||
// public repos should always be accessible
|
||||
if perm := manager.Find(&u, &r); !perm.Read {
|
||||
t.Errorf("Public repos should always be READ accessible")
|
||||
}
|
||||
|
||||
// public repos should always be accessible, even to guest users
|
||||
if perm := manager.Find(nil, &r); !perm.Read || perm.Write || perm.Admin {
|
||||
t.Errorf("Public repos should always be READ accessible, even to nil users")
|
||||
}
|
||||
|
||||
// private repos should not be accessible to nil users
|
||||
r.Private = true
|
||||
if perm := manager.Find(nil, &r); perm.Read || perm.Write || perm.Admin {
|
||||
t.Errorf("Private repos should not be READ accessible to nil users")
|
||||
}
|
||||
|
||||
// private repos should not be accessible to users without a row in the perm table.
|
||||
r.Private = true
|
||||
if perm := manager.Find(&u, &r); perm.Read || perm.Write || perm.Admin {
|
||||
t.Errorf("Private repos should not be READ accessible to users without a row in the perm table.")
|
||||
}
|
||||
|
||||
// private repos should be accessible to admins
|
||||
r.Private = true
|
||||
u.Admin = true
|
||||
if perm := manager.Find(&u, &r); !perm.Read || !perm.Write || !perm.Admin {
|
||||
t.Errorf("Private repos should be READ accessible to admins")
|
||||
}
|
||||
|
||||
// private repos should be accessible to users with rows in the perm table.
|
||||
r.ID = 200
|
||||
r.Private = true
|
||||
u.Admin = false
|
||||
if perm := manager.Find(&u, &r); !perm.Read {
|
||||
t.Errorf("Private repos should be READ accessible to users with rows in the perm table.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermRead(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
var manager = NewPermManager(db)
|
||||
|
||||
// dummy admin and repo
|
||||
u := model.User{ID: 101, Admin: false}
|
||||
r := model.Repo{ID: 201, Private: false}
|
||||
|
||||
// public repos should always be accessible
|
||||
if read, err := manager.Read(&u, &r); !read || err != nil {
|
||||
t.Errorf("Public repos should always be READ accessible")
|
||||
}
|
||||
|
||||
// public repos should always be accessible, even to guest users
|
||||
if read, err := manager.Read(nil, &r); !read || err != nil {
|
||||
t.Errorf("Public repos should always be READ accessible, even to nil users")
|
||||
}
|
||||
|
||||
// private repos should not be accessible to nil users
|
||||
r.Private = true
|
||||
if read, err := manager.Read(nil, &r); read || err != nil {
|
||||
t.Errorf("Private repos should not be READ accessible to nil users")
|
||||
}
|
||||
|
||||
// private repos should not be accessible to users without a row in the perm table.
|
||||
r.Private = true
|
||||
if read, _ := manager.Read(&u, &r); read {
|
||||
t.Errorf("Private repos should not be READ accessible to users without a row in the perm table.")
|
||||
}
|
||||
|
||||
// private repos should be accessible to admins
|
||||
r.Private = true
|
||||
u.Admin = true
|
||||
if read, err := manager.Read(&u, &r); !read || err != nil {
|
||||
t.Errorf("Private repos should be READ accessible to admins")
|
||||
}
|
||||
|
||||
// private repos should be accessible to users with rows in the perm table.
|
||||
r.ID = 200
|
||||
r.Private = true
|
||||
u.Admin = false
|
||||
if read, err := manager.Read(&u, &r); !read || err != nil {
|
||||
t.Errorf("Private repos should be READ accessible to users with rows in the perm table.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermWrite(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
var manager = NewPermManager(db)
|
||||
|
||||
// dummy admin and repo
|
||||
u := model.User{ID: 101, Admin: false}
|
||||
r := model.Repo{ID: 201, Private: false}
|
||||
|
||||
// repos should not be accessible to nil users
|
||||
r.Private = true
|
||||
if write, err := manager.Write(nil, &r); write || err != nil {
|
||||
t.Errorf("Repos should not be WRITE accessible to nil users")
|
||||
}
|
||||
|
||||
// repos should not be accessible to users without a row in the perm table.
|
||||
if write, _ := manager.Write(&u, &r); write {
|
||||
t.Errorf("Repos should not be WRITE accessible to users without a row in the perm table.")
|
||||
}
|
||||
|
||||
// repos should be accessible to admins
|
||||
u.Admin = true
|
||||
if write, err := manager.Write(&u, &r); !write || err != nil {
|
||||
t.Errorf("Repos should be WRITE accessible to admins")
|
||||
}
|
||||
|
||||
// repos should be accessible to users with rows in the perm table.
|
||||
r.ID = 200
|
||||
u.Admin = false
|
||||
if write, err := manager.Write(&u, &r); !write || err != nil {
|
||||
t.Errorf("Repos should be WRITE accessible to users with rows in the perm table.")
|
||||
}
|
||||
|
||||
// repos should not be accessible to users with a row in the perm table, but write=false
|
||||
u.ID = 103
|
||||
u.Admin = false
|
||||
if write, err := manager.Write(&u, &r); write || err != nil {
|
||||
t.Errorf("Repos should not be WRITE accessible to users with perm.Write=false.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermAdmin(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
var manager = NewPermManager(db)
|
||||
|
||||
// dummy admin and repo
|
||||
u := model.User{ID: 101, Admin: false}
|
||||
r := model.Repo{ID: 201, Private: false}
|
||||
|
||||
// repos should not be accessible to nil users
|
||||
r.Private = true
|
||||
if admin, err := manager.Admin(nil, &r); admin || err != nil {
|
||||
t.Errorf("Repos should not be ADMIN accessible to nil users")
|
||||
}
|
||||
|
||||
// repos should not be accessible to users without a row in the perm table.
|
||||
if admin, _ := manager.Admin(&u, &r); admin {
|
||||
t.Errorf("Repos should not be ADMIN accessible to users without a row in the perm table.")
|
||||
}
|
||||
|
||||
// repos should be accessible to admins
|
||||
u.Admin = true
|
||||
if admin, err := manager.Admin(&u, &r); !admin || err != nil {
|
||||
t.Errorf("Repos should be ADMIN accessible to admins")
|
||||
}
|
||||
|
||||
// repos should be accessible to users with rows in the perm table.
|
||||
r.ID = 200
|
||||
u.Admin = false
|
||||
if admin, err := manager.Admin(&u, &r); !admin || err != nil {
|
||||
t.Errorf("Repos should be ADMIN accessible to users with rows in the perm table.")
|
||||
}
|
||||
|
||||
// repos should not be accessible to users with a row in the perm table, but admin=false
|
||||
u.ID = 103
|
||||
u.Admin = false
|
||||
if admin, err := manager.Admin(&u, &r); admin || err != nil {
|
||||
t.Errorf("Repos should not be ADMIN accessible to users with perm.Admin=false.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermRevoke(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
// dummy admin and repo
|
||||
u := model.User{ID: 101}
|
||||
r := model.Repo{ID: 200}
|
||||
|
||||
manager := NewPermManager(db).(*permManager)
|
||||
admin, err := manager.Admin(&u, &r)
|
||||
if !admin || err != nil {
|
||||
t.Errorf("Want Admin permission, got Admin %v, error %s", admin, err)
|
||||
}
|
||||
|
||||
// revoke permissions
|
||||
if err := manager.Revoke(&u, &r); err != nil {
|
||||
t.Errorf("Want revoked permissions, got %s", err)
|
||||
}
|
||||
|
||||
perm, err := manager.find(&u, &r)
|
||||
if perm.Admin == true || err != sql.ErrNoRows {
|
||||
t.Errorf("Expected revoked permission, got Admin %v, error %v", perm.Admin, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermGrant(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
// dummy admin and repo
|
||||
u := model.User{ID: 104}
|
||||
r := model.Repo{ID: 200}
|
||||
|
||||
manager := NewPermManager(db).(*permManager)
|
||||
if err := manager.Grant(&u, &r, true, true, true); err != nil {
|
||||
t.Errorf("Want permissions granted, got %s", err)
|
||||
}
|
||||
|
||||
// add new permissions
|
||||
perm, err := manager.find(&u, &r)
|
||||
if err != nil {
|
||||
t.Errorf("Want permission, got %s", err)
|
||||
} else if perm.Read != true {
|
||||
t.Errorf("Want Read permission True, got %v", perm.Read)
|
||||
} else if perm.Write != true {
|
||||
t.Errorf("Want Write permission True, got %v", perm.Write)
|
||||
} else if perm.Admin != true {
|
||||
t.Errorf("Want Admin permission True, got %v", perm.Admin)
|
||||
}
|
||||
|
||||
// update permissions
|
||||
if err := manager.Grant(&u, &r, false, false, false); err != nil {
|
||||
t.Errorf("Want permissions granted, got %s", err)
|
||||
}
|
||||
|
||||
// add new permissions
|
||||
perm, err = manager.find(&u, &r)
|
||||
if err != nil {
|
||||
t.Errorf("Want permission updated, got %s", err)
|
||||
} else if perm.Read != false {
|
||||
t.Errorf("Want Read permission False, got %v", perm.Read)
|
||||
} else if perm.Write != false {
|
||||
t.Errorf("Want Write permission False, got %v", perm.Write)
|
||||
} else if perm.Admin != false {
|
||||
t.Errorf("Want Admin permission False, got %v", perm.Admin)
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
type RepoManager interface {
|
||||
// Find retrieves the Repo by ID.
|
||||
Find(id int64) (*model.Repo, error)
|
||||
|
||||
// FindName retrieves the Repo by the remote, owner and name.
|
||||
FindName(remote, owner, name string) (*model.Repo, error)
|
||||
|
||||
// Insert persists a new Repo to the datastore.
|
||||
Insert(repo *model.Repo) error
|
||||
|
||||
// Insert persists a modified Repo to the datastore.
|
||||
Update(repo *model.Repo) error
|
||||
|
||||
// Delete removes a Repo from the datastore.
|
||||
Delete(repo *model.Repo) error
|
||||
|
||||
// List retrieves all repositories from the datastore.
|
||||
List(user int64) ([]*model.Repo, error)
|
||||
|
||||
// List retrieves all public repositories from the datastore.
|
||||
//ListPublic(user int64) ([]*Repo, error)
|
||||
}
|
||||
|
||||
func NewRepoManager(db *sql.DB) RepoManager {
|
||||
return &repoManager{db}
|
||||
}
|
||||
|
||||
type repoManager struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
func (db *repoManager) Find(id int64) (*model.Repo, error) {
|
||||
const query = "select * from repos where repo_id = ?"
|
||||
var repo = model.Repo{}
|
||||
var err = meddler.QueryRow(db, &repo, query, id)
|
||||
return &repo, err
|
||||
}
|
||||
|
||||
func (db *repoManager) FindName(remote, owner, name string) (*model.Repo, error) {
|
||||
const query = "select * from repos where repo_host = ? and repo_owner = ? and repo_name = ?"
|
||||
var repo = model.Repo{}
|
||||
var err = meddler.QueryRow(db, &repo, query, remote, owner, name)
|
||||
return &repo, err
|
||||
}
|
||||
|
||||
func (db *repoManager) List(user int64) ([]*model.Repo, error) {
|
||||
const query = "select * from repos where repo_id IN (select repo_id from perms where user_id = ?)"
|
||||
var repos []*model.Repo
|
||||
err := meddler.QueryAll(db, &repos, query, user)
|
||||
return repos, err
|
||||
}
|
||||
|
||||
func (db *repoManager) Insert(repo *model.Repo) error {
|
||||
repo.Created = time.Now().Unix()
|
||||
repo.Updated = time.Now().Unix()
|
||||
return meddler.Insert(db, "repos", repo)
|
||||
}
|
||||
|
||||
func (db *repoManager) Update(repo *model.Repo) error {
|
||||
repo.Updated = time.Now().Unix()
|
||||
return meddler.Update(db, "repos", repo)
|
||||
}
|
||||
|
||||
func (db *repoManager) Delete(repo *model.Repo) error {
|
||||
const stmt = "delete from repos where repo_id = ?"
|
||||
_, err := db.Exec(stmt, repo.ID)
|
||||
return err
|
||||
}
|
|
@ -1,226 +0,0 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/shared/model"
|
||||
)
|
||||
|
||||
func TestRepoFind(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
repos := NewRepoManager(db)
|
||||
repo, err := repos.Find(1)
|
||||
if err != nil {
|
||||
t.Errorf("Want Repo from ID, got %s", err)
|
||||
}
|
||||
|
||||
testRepo(t, repo)
|
||||
}
|
||||
|
||||
func TestRepoFindName(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
repos := NewRepoManager(db)
|
||||
user, err := repos.FindName("github.com", "lhofstadter", "lenwoloppali")
|
||||
if err != nil {
|
||||
t.Errorf("Want Repo by Name, got %s", err)
|
||||
}
|
||||
|
||||
testRepo(t, user)
|
||||
}
|
||||
|
||||
func TestRepoList(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
repos := NewRepoManager(db)
|
||||
all, err := repos.List(1)
|
||||
if err != nil {
|
||||
t.Errorf("Want Repos, got %s", err)
|
||||
}
|
||||
|
||||
var got, want = len(all), 2
|
||||
if got != want {
|
||||
t.Errorf("Want %v Repos, got %v", want, got)
|
||||
}
|
||||
|
||||
testRepo(t, all[0])
|
||||
}
|
||||
|
||||
func TestRepoInsert(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
repo, _ := model.NewRepo("github.com", "mrwolowitz", "lenwoloppali")
|
||||
repos := NewRepoManager(db)
|
||||
if err := repos.Insert(repo); err != nil {
|
||||
t.Errorf("Want Repo created, got %s", err)
|
||||
}
|
||||
|
||||
// verify unique remote + owner + name login constraint
|
||||
var err = repos.Insert(&model.Repo{Host: repo.Host, Owner: repo.Owner, Name: repo.Name})
|
||||
if err == nil {
|
||||
t.Error("Want unique constraint violated")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoUpdate(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
repos := NewRepoManager(db)
|
||||
repo, err := repos.Find(1)
|
||||
if err != nil {
|
||||
t.Errorf("Want Repo from ID, got %s", err)
|
||||
}
|
||||
|
||||
// update the repo's access token
|
||||
repo.Active = false
|
||||
repo.Private = false
|
||||
repo.Privileged = false
|
||||
repo.PostCommit = false
|
||||
repo.PullRequest = false
|
||||
if err := repos.Update(repo); err != nil {
|
||||
t.Errorf("Want Repo updated, got %s", err)
|
||||
}
|
||||
|
||||
updated, _ := repos.Find(1)
|
||||
var got, want = updated.Active, repo.Active
|
||||
if got != want {
|
||||
t.Errorf("Want updated Active %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = updated.Private, repo.Private
|
||||
if got != want {
|
||||
t.Errorf("Want updated Private %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = updated.Privileged, repo.Privileged
|
||||
if got != want {
|
||||
t.Errorf("Want updated Privileged %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = updated.PostCommit, repo.PostCommit
|
||||
if got != want {
|
||||
t.Errorf("Want updated PostCommit %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = updated.PullRequest, repo.PullRequest
|
||||
if got != want {
|
||||
t.Errorf("Want updated PullRequest %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoDelete(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
repos := NewRepoManager(db)
|
||||
repo, err := repos.Find(1)
|
||||
if err != nil {
|
||||
t.Errorf("Want Repo from ID, got %s", err)
|
||||
}
|
||||
|
||||
// delete the repo
|
||||
if err := repos.Delete(repo); err != nil {
|
||||
t.Errorf("Want Repo deleted, got %s", err)
|
||||
}
|
||||
|
||||
// check to see if the deleted repo is actually gone
|
||||
if _, err := repos.Find(1); err != sql.ErrNoRows {
|
||||
t.Errorf("Want ErrNoRows, got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// testRepo is a helper function that compares the repo
|
||||
// to an expected set of fixed field values.
|
||||
func testRepo(t *testing.T, repo *model.Repo) {
|
||||
var got, want = repo.Remote, "github.com"
|
||||
if got != want {
|
||||
t.Errorf("Want Remote %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = repo.Host, "github.com"
|
||||
if got != want {
|
||||
t.Errorf("Want Host %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = repo.Owner, "lhofstadter"
|
||||
if got != want {
|
||||
t.Errorf("Want Owner %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = repo.Name, "lenwoloppali"
|
||||
if got != want {
|
||||
t.Errorf("Want Name %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = repo.CloneURL, "git://github.com/lhofstadter/lenwoloppali.git"
|
||||
if got != want {
|
||||
t.Errorf("Want URL %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = repo.PublicKey, "publickey"
|
||||
if got != want {
|
||||
t.Errorf("Want PublicKey %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = repo.PrivateKey, "privatekey"
|
||||
if got != want {
|
||||
t.Errorf("Want PrivateKey %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = repo.Params, "params"
|
||||
if got != want {
|
||||
t.Errorf("Want Params %v, got %v", want, got)
|
||||
}
|
||||
|
||||
var gotBool, wantBool = repo.Active, true
|
||||
if gotBool != wantBool {
|
||||
t.Errorf("Want Active %v, got %v", wantBool, gotBool)
|
||||
}
|
||||
|
||||
gotBool, wantBool = repo.Private, true
|
||||
if gotBool != wantBool {
|
||||
t.Errorf("Want Private %v, got %v", wantBool, gotBool)
|
||||
}
|
||||
|
||||
gotBool, wantBool = repo.Privileged, true
|
||||
if gotBool != wantBool {
|
||||
t.Errorf("Want Privileged %v, got %v", wantBool, gotBool)
|
||||
}
|
||||
|
||||
gotBool, wantBool = repo.PostCommit, true
|
||||
if gotBool != wantBool {
|
||||
t.Errorf("Want PostCommit %v, got %v", wantBool, gotBool)
|
||||
}
|
||||
|
||||
gotBool, wantBool = repo.PullRequest, true
|
||||
if gotBool != wantBool {
|
||||
t.Errorf("Want PullRequest %v, got %v", wantBool, gotBool)
|
||||
}
|
||||
|
||||
var gotInt64, wantInt64 = repo.ID, int64(1)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want ID %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
|
||||
gotInt64, wantInt64 = repo.Created, int64(1398065343)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want Created %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
|
||||
gotInt64, wantInt64 = repo.Updated, int64(1398065344)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want Updated %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
|
||||
gotInt64, wantInt64 = repo.Timeout, int64(900)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want Timeout %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
)
|
||||
|
||||
// statements to setup our database
|
||||
var stmts = []string{`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
user_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,user_remote VARCHAR(255)
|
||||
,user_login VARCHAR(255)
|
||||
,user_access VARCHAR(255)
|
||||
,user_secret VARCHAR(255)
|
||||
,user_name VARCHAR(255)
|
||||
,user_email VARCHAR(255)
|
||||
,user_gravatar VARCHAR(255)
|
||||
,user_token VARCHAR(255)
|
||||
,user_admin BOOLEAN
|
||||
,user_active BOOLEAN
|
||||
,user_syncing BOOLEAN
|
||||
,user_created INTEGER
|
||||
,user_updated INTEGER
|
||||
,user_synced INTEGER
|
||||
,UNIQUE(user_token)
|
||||
,UNIQUE(user_remote, user_login)
|
||||
);`, `
|
||||
CREATE TABLE IF NOT EXISTS perms (
|
||||
perm_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,user_id INTEGER
|
||||
,repo_id INTEGER
|
||||
,perm_read BOOLEAN
|
||||
,perm_write BOOLEAN
|
||||
,perm_admin BOOLEAN
|
||||
,perm_created INTEGER
|
||||
,perm_updated INTEGER
|
||||
,UNIQUE (repo_id, user_id)
|
||||
);`, `
|
||||
CREATE TABLE IF NOT EXISTS repos (
|
||||
repo_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,user_id INTEGER
|
||||
,repo_remote VARCHAR(255)
|
||||
,repo_host VARCHAR(255)
|
||||
,repo_owner VARCHAR(255)
|
||||
,repo_name VARCHAR(255)
|
||||
,repo_url VARCHAR(1024)
|
||||
,repo_clone_url VARCHAR(255)
|
||||
,repo_git_url VARCHAR(255)
|
||||
,repo_ssh_url VARCHAR(255)
|
||||
,repo_active BOOLEAN
|
||||
,repo_private BOOLEAN
|
||||
,repo_privileged BOOLEAN
|
||||
,repo_post_commit BOOLEAN
|
||||
,repo_pull_request BOOLEAN
|
||||
,repo_public_key VARCHAR(4000)
|
||||
,repo_private_key VARCHAR(4000)
|
||||
,repo_params VARCHAR(4000)
|
||||
,repo_timeout INTEGER
|
||||
,repo_created INTEGER
|
||||
,repo_updated INTEGER
|
||||
,UNIQUE(repo_host, repo_owner, repo_name)
|
||||
);`, `
|
||||
CREATE TABLE IF NOT EXISTS commits (
|
||||
commit_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,repo_id INTEGER
|
||||
,commit_status VARCHAR(255)
|
||||
,commit_started INTEGER
|
||||
,commit_finished INTEGER
|
||||
,commit_duration INTEGER
|
||||
,commit_sha 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(255)
|
||||
,commit_yaml VARCHAR(4000)
|
||||
,commit_created INTEGER
|
||||
,commit_updated INTEGER
|
||||
,UNIQUE(commit_sha, commit_branch, repo_id)
|
||||
);`, `
|
||||
CREATE TABLE IF NOT EXISTS output (
|
||||
output_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,commit_id INTEGER
|
||||
,output_raw BLOB
|
||||
,UNIQUE(commit_id)
|
||||
);`, `
|
||||
CREATE TABLE IF NOT EXISTS remotes (
|
||||
remote_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,remote_type VARCHAR(255)
|
||||
,remote_host VARCHAR(255)
|
||||
,remote_url VARCHAR(255)
|
||||
,remote_api VARCHAR(255)
|
||||
,remote_client VARCHAR(255)
|
||||
,remote_secret VARCHAR(255)
|
||||
,remote_open BOOLEAN
|
||||
,UNIQUE(remote_host)
|
||||
,UNIQUE(remote_type)
|
||||
);`, `
|
||||
CREATE TABLE IF NOT EXISTS servers (
|
||||
server_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,server_name VARCHAR(255)
|
||||
,server_host VARCHAR(255)
|
||||
,server_user VARCHAR(255)
|
||||
,server_pass VARCHAR(255)
|
||||
,server_cert VARCHAR(4000)
|
||||
,UNIQUE(server_name)
|
||||
);`, `
|
||||
CREATE TABLE IF NOT EXISTS smtp (
|
||||
smtp_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||
,smtp_from VARCHAR(255)
|
||||
,smtp_host VARCHAR(255)
|
||||
,smtp_port VARCHAR(255)
|
||||
,smtp_user VARCHAR(255)
|
||||
,smtp_pass VARCHAR(255)
|
||||
);`,
|
||||
}
|
||||
|
||||
func Load(db *sql.DB) {
|
||||
// execute all setup commands
|
||||
for _, stmt := range stmts {
|
||||
if _, err := db.Exec(stmt); err != nil {
|
||||
// exit on failure since this should never happen
|
||||
log.Fatalf("Error generating database schema. %s\n%s", err, stmt)
|
||||
}
|
||||
}
|
||||
}
|
57
server/database/testdata/testdata.go
vendored
57
server/database/testdata/testdata.go
vendored
|
@ -1,57 +0,0 @@
|
|||
package testdata
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var stmts = []string{
|
||||
// insert user entries
|
||||
"insert into users values (1, 'github.com', 'smellypooper', 'f0b461ca586c27872b43a0685cbc2847', '976f22a5eef7caacb7e678d6c52f49b1', 'Dr. Cooper', 'drcooper@caltech.edu', 'b9015b0857e16ac4d94a0ffd9a0b79c8', 'e42080dddf012c718e476da161d21ad5', 1, 1, 0, 1398065343, 1398065344, 1398065345);",
|
||||
"insert into users values (2, 'github.com', 'lhofstadter', 'e4105c3059ac4c466594932dc9a4ffb2', '2257216903d9cd0d3d24772132febf52', 'Dr. Hofstadter', 'leanard@caltech.edu', '23dde632fdece6880f4ff03bb20f05d7', 'a5ad0d75f317f0b0a5dfdb68e5a3079e', 1, 1, 0, 1398065343, 1398065344, 1398065345);",
|
||||
"insert into users values (3, 'gitlab.com', 'browndynamite', '4821477cc26a0c8c80c6c9b568d98e32', '1dd52c37cf5c63fe5abfd047b5b74a31', 'Dr. Koothrappali', 'rajesh@caltech.edu', 'f9133051f480b7ea88848b9f0a079dae', '7a50ede04637d4a8fce532c7d511226b', 1, 1, 0, 1398065343, 1398065344, 1398065345);",
|
||||
"insert into users values (4, 'github.com', 'mrwolowitz', '1f6a80bde960e6913bf9b7e61eadd068', '74c40472494ba7f9f6c3ae061ff799ed', 'Mr. Wolowitz', 'wolowitz@caltech.edu', 'ea250570c794d84dc583421bb717be82', '3bd7e7d7411b2978e45919c9ad419984', 1, 1, 0, 1398065343, 1398065344, 1398065345);",
|
||||
|
||||
// insert repository entries
|
||||
"insert into repos values (1, 0, 'github.com', 'github.com', 'lhofstadter', 'lenwoloppali', '', 'git://github.com/lhofstadter/lenwoloppali.git', '', '', 1, 1, 1, 1, 1, 'publickey', 'privatekey', 'params', 900, 1398065343, 1398065344);",
|
||||
"insert into repos values (2, 0, 'github.com', 'github.com', 'browndynamite', 'lenwoloppali', '', 'git://github.com/browndynamite/lenwoloppali.git', '', '', 1, 1, 1, 1, 1, 'publickey', 'privatekey', 'params', 900, 1398065343, 1398065344);",
|
||||
"insert into repos values (3, 0, 'gitlab.com', 'gitlab.com', 'browndynamite', 'lenwoloppali', '', 'git://gitlab.com/browndynamite/lenwoloppali.git', '', '', 1, 1, 1, 1, 1, 'publickey', 'privatekey', 'params', 900, 1398065343, 1398065344);",
|
||||
|
||||
// insert user + repository permission entries
|
||||
"insert into perms values (1, 101, 200, 1, 1, 1, 1398065343, 1398065344);",
|
||||
"insert into perms values (2, 102, 200, 1, 1, 0, 1398065343, 1398065344);",
|
||||
"insert into perms values (3, 103, 200, 1, 0, 0, 1398065343, 1398065344);",
|
||||
"insert into perms values (4, 1, 1, 1, 1, 1, 1398065343, 1398065344);",
|
||||
"insert into perms values (5, 1, 2, 1, 1, 0, 1398065343, 1398065344);",
|
||||
|
||||
// insert commit entries
|
||||
"insert into commits values (1, 2, 'Success', 1398065345, 1398069999, 854, '4e81eca185897c2d0cf81f5bc12623550c2ef952', 'dev', '3', 'drcooper@caltech.edu', 'ab23a88a3ed77ecdfeb894c0eaf2817a', 'Wed Apr 23 01:00:00 2014 -0700', 'a commit message', '', 1398065343, 1398065344);",
|
||||
"insert into commits values (2, 2, 'Success', 1398065345, 1398069999, 854, '4e81eca185897c2d0cf81f5bc12623550c2ef952', 'master', '4', 'drcooper@caltech.edu', 'ab23a88a3ed77ecdfeb894c0eaf2817a', 'Wed Apr 23 01:01:00 2014 -0700', 'a commit message', '', 1398065343, 1398065344);",
|
||||
"insert into commits values (3, 2, 'Success', 1398065345, 1398069999, 854, '7253f6545caed41fb8f5a6fcdb3abc0b81fa9dbf', 'master', '5', 'drcooper@caltech.edu', 'ab23a88a3ed77ecdfeb894c0eaf2817a', 'Wed Apr 23 01:02:38 2014 -0700', 'a commit message', '', 1398065343, 1398065344);",
|
||||
"insert into commits values (4, 1, 'Success', 1398065345, 1398069999, 854, 'd12c9e5a11982f71796ad909c93551b16fba053e', 'dev', '', 'drcooper@caltech.edu', 'ab23a88a3ed77ecdfeb894c0eaf2817a', 'Wed Apr 23 02:00:00 2014 -0700', 'a commit message', '', 1398065343, 1398065344);",
|
||||
"insert into commits values (5, 1, 'Started', 1398065345, 0, 0, '85f8c029b902ed9400bc600bac301a0aadb144ac', 'master', '', 'drcooper@caltech.edu', 'ab23a88a3ed77ecdfeb894c0eaf2817a', 'Wed Apr 23 03:00:00 2014 -0700', 'a commit message', '', 1398065343, 1398065344);",
|
||||
|
||||
// insert commit console output
|
||||
"insert into output values (1, 1, 'sample console output');",
|
||||
"insert into output values (2, 2, 'sample console output.....');",
|
||||
|
||||
// insert server entries
|
||||
"insert into servers values (1, 'docker1', 'tcp://127.0.0.1:4243', 'root', 'pa55word', '/path/to/cert.key');",
|
||||
"insert into servers values (2, 'docker2', 'tcp://127.0.0.1:4243', 'root', 'pa55word', '/path/to/cert.key');",
|
||||
|
||||
// insert remote entries
|
||||
"insert into remotes values (1, 'enterprise.github.com', 'github.drone.io', 'https://github.drone.io', 'https://github.drone.io/v3/api', 'f0b461ca586c27872b43a0685cbc2847', '976f22a5eef7caacb7e678d6c52f49b1', '1');",
|
||||
"insert into remotes values (2, 'github.com', 'github.com', 'https://github.io', 'https://api.github.com', 'a0b461ca586c27872b43a0685cbc2847', 'a76f22a5eef7caacb7e678d6c52f49b1', '0');",
|
||||
}
|
||||
|
||||
// Load will populate the database with a fixed dataset for
|
||||
// unit testing purposes.
|
||||
func Load(db *sql.DB) {
|
||||
// loop through insert statements and execute
|
||||
for _, stmt := range stmts {
|
||||
_, err := db.Exec(stmt)
|
||||
if err != nil {
|
||||
fmt.Printf("Error executing Query %s\n %s\n", stmt, err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package testdatabase
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
|
||||
// database drivers that may be tested
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
var (
|
||||
driver = env("TEST_DB_DRIVER", "sqlite3")
|
||||
source = env("TEST_DB_SOURCE", ":memory:")
|
||||
)
|
||||
|
||||
// Open opens a new database connection using a test
|
||||
// database environment, specified using the `$TEST_DB_DRIVER`
|
||||
// and `$TEST_DB_SOURCE` environment variables.
|
||||
func Open() (*sql.DB, error) {
|
||||
return sql.Open(driver, source)
|
||||
}
|
||||
|
||||
// helper function that retrieves the environment variable
|
||||
// if exists, else returns a default value.
|
||||
func env(name, def string) string {
|
||||
value := os.Getenv(name)
|
||||
if len(value) == 0 {
|
||||
value = def
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/russross/meddler"
|
||||
)
|
||||
|
||||
type UserManager interface {
|
||||
// Find finds the User by ID.
|
||||
Find(id int64) (*model.User, error)
|
||||
|
||||
// FindLogin finds the User by remote login.
|
||||
FindLogin(remote, login string) (*model.User, error)
|
||||
|
||||
// FindToken finds the User by token.
|
||||
FindToken(token string) (*model.User, error)
|
||||
|
||||
// List finds all registered users of the system.
|
||||
List() ([]*model.User, error)
|
||||
|
||||
// Insert persists the User to the datastore.
|
||||
Insert(user *model.User) error
|
||||
|
||||
// Update persists changes to the User to the datastore.
|
||||
Update(user *model.User) error
|
||||
|
||||
// Delete removes the User from the datastore.
|
||||
Delete(user *model.User) error
|
||||
|
||||
// Exist returns true if Users exist in the system.
|
||||
Exist() bool
|
||||
}
|
||||
|
||||
// userManager manages a list of users in a SQL database.
|
||||
type userManager struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
// SQL query to retrieve a User by remote login.
|
||||
const findUserLoginQuery = `
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE user_remote=?
|
||||
AND user_login=?
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
// SQL query to retrieve a User by remote login.
|
||||
const findUserTokenQuery = `
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE user_token=?
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
// SQL query to retrieve a list of all users.
|
||||
const listUserQuery = `
|
||||
SELECT *
|
||||
FROM users
|
||||
ORDER BY user_name ASC
|
||||
`
|
||||
|
||||
// SQL statement to delete a User by ID.
|
||||
const deleteUserStmt = `
|
||||
DELETE FROM users WHERE user_id=?
|
||||
`
|
||||
|
||||
// SQL statement to check if users exist.
|
||||
const confirmUserStmt = `
|
||||
select 1 from users limit 1
|
||||
`
|
||||
|
||||
// NewUserManager initiales a new UserManager intended to
|
||||
// manage and persist commits.
|
||||
func NewUserManager(db *sql.DB) UserManager {
|
||||
return &userManager{db}
|
||||
}
|
||||
|
||||
func (db *userManager) Find(id int64) (*model.User, error) {
|
||||
dst := model.User{}
|
||||
err := meddler.Load(db, "users", &dst, id)
|
||||
return &dst, err
|
||||
}
|
||||
|
||||
func (db *userManager) FindLogin(remote, login string) (*model.User, error) {
|
||||
dst := model.User{}
|
||||
err := meddler.QueryRow(db, &dst, findUserLoginQuery, remote, login)
|
||||
return &dst, err
|
||||
}
|
||||
|
||||
func (db *userManager) FindToken(token string) (*model.User, error) {
|
||||
dst := model.User{}
|
||||
err := meddler.QueryRow(db, &dst, findUserTokenQuery, token)
|
||||
return &dst, err
|
||||
}
|
||||
|
||||
func (db *userManager) List() ([]*model.User, error) {
|
||||
var dst []*model.User
|
||||
err := meddler.QueryAll(db, &dst, listUserQuery)
|
||||
return dst, err
|
||||
}
|
||||
|
||||
func (db *userManager) Insert(user *model.User) error {
|
||||
user.Created = time.Now().Unix()
|
||||
user.Updated = time.Now().Unix()
|
||||
return meddler.Insert(db, "users", user)
|
||||
}
|
||||
|
||||
func (db *userManager) Update(user *model.User) error {
|
||||
user.Updated = time.Now().Unix()
|
||||
return meddler.Update(db, "users", user)
|
||||
}
|
||||
|
||||
func (db *userManager) Delete(user *model.User) error {
|
||||
_, err := db.Exec(deleteUserStmt, user.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *userManager) Exist() bool {
|
||||
row := db.QueryRow(confirmUserStmt)
|
||||
var result int
|
||||
row.Scan(&result)
|
||||
return result == 1
|
||||
}
|
|
@ -1,235 +0,0 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/server/database/schema"
|
||||
"github.com/drone/drone/server/database/testdata"
|
||||
"github.com/drone/drone/server/database/testdatabase"
|
||||
"github.com/drone/drone/shared/model"
|
||||
)
|
||||
|
||||
// in-memory database instance for unit testing
|
||||
var db *sql.DB
|
||||
|
||||
// setup the test database and test fixtures
|
||||
func setup() {
|
||||
db, _ = testdatabase.Open()
|
||||
schema.Load(db)
|
||||
testdata.Load(db)
|
||||
}
|
||||
|
||||
// teardown the test database
|
||||
func teardown() {
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func TestUserFind(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
users := NewUserManager(db)
|
||||
user, err := users.Find(1)
|
||||
if err != nil {
|
||||
t.Errorf("Want User from ID, got %s", err)
|
||||
}
|
||||
|
||||
testUser(t, user)
|
||||
}
|
||||
|
||||
func TestUserFindLogin(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
users := NewUserManager(db)
|
||||
user, err := users.FindLogin("github.com", "smellypooper")
|
||||
if err != nil {
|
||||
t.Errorf("Want User from Login, got %s", err)
|
||||
}
|
||||
|
||||
testUser(t, user)
|
||||
}
|
||||
|
||||
func TestUserFindToken(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
users := NewUserManager(db)
|
||||
user, err := users.FindToken("e42080dddf012c718e476da161d21ad5")
|
||||
if err != nil {
|
||||
t.Errorf("Want User from Token, got %s", err)
|
||||
}
|
||||
|
||||
testUser(t, user)
|
||||
}
|
||||
|
||||
func TestUserList(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
users := NewUserManager(db)
|
||||
all, err := users.List()
|
||||
if err != nil {
|
||||
t.Errorf("Want Users, got %s", err)
|
||||
}
|
||||
|
||||
var got, want = len(all), 4
|
||||
if got != want {
|
||||
t.Errorf("Want %v Users, got %v", want, got)
|
||||
}
|
||||
|
||||
testUser(t, all[0])
|
||||
}
|
||||
|
||||
func TestUserInsert(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
user := model.NewUser("github.com", "winkle", "winkle@caltech.edu")
|
||||
users := NewUserManager(db)
|
||||
if err := users.Insert(user); err != nil {
|
||||
t.Errorf("Want User created, got %s", err)
|
||||
}
|
||||
|
||||
var got, want = user.ID, int64(5)
|
||||
if want != got {
|
||||
t.Errorf("Want User ID %v, got %v", want, got)
|
||||
}
|
||||
|
||||
// verify unique remote + remote login constraint
|
||||
var err = users.Insert(&model.User{Remote: user.Remote, Login: user.Login, Token: "f71eb4a81a2cca56035dd7f6f2942e41"})
|
||||
if err == nil {
|
||||
t.Error("Want Token unique constraint violated")
|
||||
}
|
||||
|
||||
// verify unique token constraint
|
||||
err = users.Insert(&model.User{Remote: "gitlab.com", Login: user.Login, Token: user.Token})
|
||||
if err == nil {
|
||||
t.Error("Want Token unique constraint violated")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserUpdate(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
users := NewUserManager(db)
|
||||
user, err := users.Find(4)
|
||||
if err != nil {
|
||||
t.Errorf("Want User from ID, got %s", err)
|
||||
}
|
||||
|
||||
// update the user's access token
|
||||
user.Access = "fc47f37716fa04e9dfa9ac7eb22b5718"
|
||||
user.Secret = "d1c65427c978f2c9ad4baed72628dba0"
|
||||
if err := users.Update(user); err != nil {
|
||||
t.Errorf("Want User updated, got %s", err)
|
||||
}
|
||||
|
||||
updated, _ := users.Find(4)
|
||||
var got, want = updated.Access, user.Access
|
||||
if got != want {
|
||||
t.Errorf("Want updated Access %s, got %s", want, got)
|
||||
}
|
||||
|
||||
got, want = updated.Secret, user.Secret
|
||||
if got != want {
|
||||
t.Errorf("Want updated Secret %s, got %s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserDelete(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
users := NewUserManager(db)
|
||||
user, err := users.Find(1)
|
||||
if err != nil {
|
||||
t.Errorf("Want User from ID, got %s", err)
|
||||
}
|
||||
|
||||
// delete the user
|
||||
if err := users.Delete(user); err != nil {
|
||||
t.Errorf("Want User deleted, got %s", err)
|
||||
}
|
||||
|
||||
// check to see if the deleted user is actually gone
|
||||
if _, err := users.Find(1); err != sql.ErrNoRows {
|
||||
t.Errorf("Want ErrNoRows, got %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// testUser is a helper function that compares the user
|
||||
// to an expected set of fixed field values.
|
||||
func testUser(t *testing.T, user *model.User) {
|
||||
var got, want = user.Login, "smellypooper"
|
||||
if got != want {
|
||||
t.Errorf("Want Token %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = user.Remote, "github.com"
|
||||
if got != want {
|
||||
t.Errorf("Want Token %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = user.Access, "f0b461ca586c27872b43a0685cbc2847"
|
||||
if got != want {
|
||||
t.Errorf("Want Access Token %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = user.Secret, "976f22a5eef7caacb7e678d6c52f49b1"
|
||||
if got != want {
|
||||
t.Errorf("Want Token Secret %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = user.Name, "Dr. Cooper"
|
||||
if got != want {
|
||||
t.Errorf("Want Name %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = user.Email, "drcooper@caltech.edu"
|
||||
if got != want {
|
||||
t.Errorf("Want Email %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = user.Gravatar, "b9015b0857e16ac4d94a0ffd9a0b79c8"
|
||||
if got != want {
|
||||
t.Errorf("Want Gravatar %v, got %v", want, got)
|
||||
}
|
||||
|
||||
got, want = user.Token, "e42080dddf012c718e476da161d21ad5"
|
||||
if got != want {
|
||||
t.Errorf("Want Token %v, got %v", want, got)
|
||||
}
|
||||
|
||||
var gotBool, wantBool = user.Active, true
|
||||
if gotBool != wantBool {
|
||||
t.Errorf("Want Active %v, got %v", wantBool, gotBool)
|
||||
}
|
||||
|
||||
gotBool, wantBool = user.Admin, true
|
||||
if gotBool != wantBool {
|
||||
t.Errorf("Want Admin %v, got %v", wantBool, gotBool)
|
||||
}
|
||||
|
||||
var gotInt64, wantInt64 = user.ID, int64(1)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want ID %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
|
||||
gotInt64, wantInt64 = user.Created, int64(1398065343)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want Created %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
|
||||
gotInt64, wantInt64 = user.Updated, int64(1398065344)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want Updated %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
|
||||
gotInt64, wantInt64 = user.Synced, int64(1398065345)
|
||||
if gotInt64 != wantInt64 {
|
||||
t.Errorf("Want Synced %v, got %v", wantInt64, gotInt64)
|
||||
}
|
||||
}
|
|
@ -72,8 +72,8 @@ func mustConnectTest() *sql.DB {
|
|||
return db
|
||||
}
|
||||
|
||||
// New returns a new DataStore
|
||||
func New(db *sql.DB) datastore.Datastore {
|
||||
// New returns a new Datastore
|
||||
func NewDatastore(db *sql.DB) datastore.Datastore {
|
||||
return struct {
|
||||
*Userstore
|
||||
*Permstore
|
||||
|
|
|
@ -32,7 +32,7 @@ func GetPerm(c context.Context, user *model.User, repo *model.Repo) (*model.Perm
|
|||
Read: false,
|
||||
Write: false,
|
||||
Admin: false}, nil
|
||||
case user == nil && !reop.Private:
|
||||
case user == nil && !repo.Private:
|
||||
return &model.Perm{
|
||||
Guest: true,
|
||||
Read: true,
|
||||
|
|
|
@ -3,12 +3,12 @@ package handler
|
|||
import (
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/server/database"
|
||||
"github.com/drone/drone/server/datastore"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/gorilla/pat"
|
||||
"github.com/goji/context"
|
||||
"github.com/zenazn/goji/web"
|
||||
)
|
||||
|
||||
// badges that indicate the current build status for a repository
|
||||
|
@ -21,56 +21,40 @@ var (
|
|||
badgeNone = []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="75" height="18"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-opacity=".3"/><stop offset="1" stop-opacity=".5"/></linearGradient><rect rx="4" width="75" height="18" fill="#555"/><rect rx="4" x="37" width="38" height="18" fill="#9f9f9f"/><path fill="#9f9f9f" d="M37 0h4v18h-4z"/><rect rx="4" width="75" height="18" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="13" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="12">build</text><text x="55" y="13" fill="#010101" fill-opacity=".3">none</text><text x="55" y="12">none</text></g></svg>`)
|
||||
)
|
||||
|
||||
type BadgeHandler struct {
|
||||
commits database.CommitManager
|
||||
repos database.RepoManager
|
||||
}
|
||||
// GetBadge accepts a request to retrieve the named
|
||||
// repo and branhes latest build details from the datastore
|
||||
// and return an SVG badges representing the build results.
|
||||
//
|
||||
// GET /api/badge/:host/:owner/:name/status.svg
|
||||
//
|
||||
func GetBadge(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var (
|
||||
host = c.URLParams["host"]
|
||||
owner = c.URLParams["owner"]
|
||||
name = c.URLParams["name"]
|
||||
branch = c.URLParams["branch"]
|
||||
)
|
||||
|
||||
func NewBadgeHandler(repos database.RepoManager, commits database.CommitManager) *BadgeHandler {
|
||||
return &BadgeHandler{commits, repos}
|
||||
}
|
||||
|
||||
// GetStatus gets the build status badge.
|
||||
// GET /v1/badge/:host/:owner/:name/status.svg
|
||||
func (h *BadgeHandler) GetStatus(w http.ResponseWriter, r *http.Request) error {
|
||||
host, owner, name := parseRepo(r)
|
||||
branch := r.FormValue("branch")
|
||||
|
||||
// github has insanely aggressive caching so we'll set almost
|
||||
// every parameter possible to try to prevent caching.
|
||||
w.Header().Set("Content-Type", "image/svg+xml")
|
||||
w.Header().Add("Cache-Control", "no-cache")
|
||||
w.Header().Add("Cache-Control", "no-store")
|
||||
w.Header().Add("Cache-Control", "max-age=0")
|
||||
w.Header().Add("Cache-Control", "must-revalidate")
|
||||
w.Header().Add("Cache-Control", "value")
|
||||
w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
|
||||
w.Header().Set("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
|
||||
|
||||
// get the repository from the database
|
||||
arepo, err := h.repos.FindName(host, owner, name)
|
||||
repo, err := datastore.GetRepoName(ctx, host, owner, name)
|
||||
if err != nil {
|
||||
w.Write(badgeNone)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// if no branch, use the default
|
||||
if len(branch) == 0 {
|
||||
branch = model.DefaultBranch
|
||||
}
|
||||
|
||||
// get the latest commit
|
||||
c, _ := h.commits.FindLatest(arepo.ID, branch)
|
||||
commit, _ := datastore.GetCommitLast(ctx, repo, branch)
|
||||
|
||||
// if no commit was found then display
|
||||
// the 'none' badge
|
||||
if c == nil {
|
||||
// the 'none' badge, instead of throwing
|
||||
// an error response
|
||||
if commit == nil {
|
||||
w.Write(badgeNone)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// determine which badge to load
|
||||
switch c.Status {
|
||||
switch commit.Status {
|
||||
case model.StatusSuccess:
|
||||
w.Write(badgeSuccess)
|
||||
case model.StatusFailure:
|
||||
|
@ -82,40 +66,33 @@ func (h *BadgeHandler) GetStatus(w http.ResponseWriter, r *http.Request) error {
|
|||
default:
|
||||
w.Write(badgeNone)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCoverage gets the build status badge.
|
||||
// GET /v1/badges/:host/:owner/:name/coverage.svg
|
||||
func (h *BadgeHandler) GetCoverage(w http.ResponseWriter, r *http.Request) error {
|
||||
return notImplemented{}
|
||||
}
|
||||
|
||||
func (h *BadgeHandler) GetCC(w http.ResponseWriter, r *http.Request) error {
|
||||
host, owner, name := parseRepo(r)
|
||||
|
||||
// get the repository from the database
|
||||
repo, err := h.repos.FindName(host, owner, name)
|
||||
// GetCC accepts a request to retrieve the latest build
|
||||
// status for the given repository from the datastore and
|
||||
// in CCTray XML format.
|
||||
//
|
||||
// GET /api/badge/:host/:owner/:name/cc.xml
|
||||
//
|
||||
func GetCC(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var (
|
||||
host = c.URLParams["host"]
|
||||
owner = c.URLParams["owner"]
|
||||
name = c.URLParams["name"]
|
||||
)
|
||||
repo, err := datastore.GetRepoName(ctx, host, owner, name)
|
||||
if err != nil {
|
||||
return notFound{err}
|
||||
w.Write(badgeNone)
|
||||
return
|
||||
}
|
||||
|
||||
// get the latest commits for the repo
|
||||
commits, err := h.commits.List(repo.ID)
|
||||
commits, err := datastore.GetCommitList(ctx, repo)
|
||||
if err != nil || len(commits) == 0 {
|
||||
return notFound{}
|
||||
}
|
||||
commit := commits[0]
|
||||
|
||||
// generate the URL for the repository
|
||||
url := httputil.GetURL(r) + "/" + repo.Host + "/" + repo.Owner + "/" + repo.Name
|
||||
proj := model.NewCC(repo, commit, url)
|
||||
return xml.NewEncoder(w).Encode(proj)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
func (h *BadgeHandler) Register(r *pat.Router) {
|
||||
r.Get("/v1/badge/{host}/{owner}/{name}/coverage.svg", errorHandler(h.GetCoverage))
|
||||
r.Get("/v1/badge/{host}/{owner}/{name}/status.svg", errorHandler(h.GetStatus))
|
||||
r.Get("/v1/badge/{host}/{owner}/{name}/cc.xml", errorHandler(h.GetCC))
|
||||
var link = httputil.GetURL(r) + "/" + repo.Host + "/" + repo.Owner + "/" + repo.Name
|
||||
var cc = model.NewCC(repo, commits[0], link)
|
||||
xml.NewEncoder(w).Encode(cc)
|
||||
}
|
||||
|
|
|
@ -4,139 +4,55 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/drone/drone/server/database"
|
||||
"github.com/drone/drone/server/session"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/gorilla/pat"
|
||||
"github.com/drone/drone/server/datastore"
|
||||
"github.com/goji/context"
|
||||
"github.com/zenazn/goji/web"
|
||||
)
|
||||
|
||||
type CommitHandler struct {
|
||||
users database.UserManager
|
||||
perms database.PermManager
|
||||
repos database.RepoManager
|
||||
commits database.CommitManager
|
||||
sess session.Session
|
||||
queue chan *model.Request
|
||||
}
|
||||
// GetCommitList accepts a request to retrieve a list
|
||||
// of recent commits by Repo, and retur in JSON format.
|
||||
//
|
||||
// GET /api/repos/:host/:owner/:name/commits
|
||||
//
|
||||
func GetCommitList(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var repo = ToRepo(c)
|
||||
|
||||
func NewCommitHandler(users database.UserManager, repos database.RepoManager, commits database.CommitManager, perms database.PermManager, sess session.Session, queue chan *model.Request) *CommitHandler {
|
||||
return &CommitHandler{users, perms, repos, commits, sess, queue}
|
||||
}
|
||||
|
||||
// GetFeed gets recent commits for the repository and branch
|
||||
// GET /v1/repos/{host}/{owner}/{name}/branches/{branch}/commits
|
||||
func (h *CommitHandler) GetFeed(w http.ResponseWriter, r *http.Request) error {
|
||||
var host, owner, name = parseRepo(r)
|
||||
var branch = r.FormValue(":branch")
|
||||
|
||||
// get the user form the session.
|
||||
user := h.sess.User(r)
|
||||
|
||||
// get the repository from the database.
|
||||
repo, err := h.repos.FindName(host, owner, name)
|
||||
switch {
|
||||
case err != nil && user == nil:
|
||||
return notAuthorized{}
|
||||
case err != nil && user != nil:
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
// user must have read access to the repository.
|
||||
ok, _ := h.perms.Read(user, repo)
|
||||
switch {
|
||||
case ok == false && user == nil:
|
||||
return notAuthorized{}
|
||||
case ok == false && user != nil:
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
commits, err := h.commits.ListBranch(repo.ID, branch)
|
||||
commits, err := datastore.GetCommitList(ctx, repo)
|
||||
if err != nil {
|
||||
return notFound{err}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(commits)
|
||||
json.NewEncoder(w).Encode(commits)
|
||||
}
|
||||
|
||||
// GetCommit gets the commit for the repository, branch and sha.
|
||||
// GET /v1/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}
|
||||
func (h *CommitHandler) GetCommit(w http.ResponseWriter, r *http.Request) error {
|
||||
var host, owner, name = parseRepo(r)
|
||||
var branch = r.FormValue(":branch")
|
||||
var sha = r.FormValue(":commit")
|
||||
// GetCommit accepts a request to retrieve a commit
|
||||
// from the datastore for the given repository, branch and
|
||||
// commit hash.
|
||||
//
|
||||
// GET /api/repos/:host/:owner/:name/branches/:branch/commits/:commit
|
||||
//
|
||||
func GetCommit(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var (
|
||||
branch = c.URLParams["branch"]
|
||||
hash = c.URLParams["commit"]
|
||||
repo = ToRepo(c)
|
||||
)
|
||||
|
||||
// get the user form the session.
|
||||
user := h.sess.User(r)
|
||||
|
||||
// get the repository from the database.
|
||||
repo, err := h.repos.FindName(host, owner, name)
|
||||
switch {
|
||||
case err != nil && user == nil:
|
||||
return notAuthorized{}
|
||||
case err != nil && user != nil:
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
// user must have read access to the repository.
|
||||
ok, _ := h.perms.Read(user, repo)
|
||||
switch {
|
||||
case ok == false && user == nil:
|
||||
return notAuthorized{}
|
||||
case ok == false && user != nil:
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
commit, err := h.commits.FindSha(repo.ID, branch, sha)
|
||||
commit, err := datastore.GetCommitSha(ctx, repo, branch, hash)
|
||||
if err != nil {
|
||||
return notFound{err}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(commit)
|
||||
json.NewEncoder(w).Encode(commit)
|
||||
}
|
||||
|
||||
// GetCommitOutput gets the commit's stdout.
|
||||
// GET /v1/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}/console
|
||||
func (h *CommitHandler) GetCommitOutput(w http.ResponseWriter, r *http.Request) error {
|
||||
var host, owner, name = parseRepo(r)
|
||||
var branch = r.FormValue(":branch")
|
||||
var sha = r.FormValue(":commit")
|
||||
|
||||
// get the user form the session.
|
||||
user := h.sess.User(r)
|
||||
|
||||
// get the repository from the database.
|
||||
repo, err := h.repos.FindName(host, owner, name)
|
||||
switch {
|
||||
case err != nil && user == nil:
|
||||
return notAuthorized{}
|
||||
case err != nil && user != nil:
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
// user must have read access to the repository.
|
||||
ok, _ := h.perms.Read(user, repo)
|
||||
switch {
|
||||
case ok == false && user == nil:
|
||||
return notAuthorized{}
|
||||
case ok == false && user != nil:
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
commit, err := h.commits.FindSha(repo.ID, branch, sha)
|
||||
if err != nil {
|
||||
return notFound{err}
|
||||
}
|
||||
|
||||
output, err := h.commits.FindOutput(commit.ID)
|
||||
if err != nil {
|
||||
return notFound{err}
|
||||
}
|
||||
|
||||
w.Write(output)
|
||||
return nil
|
||||
}
|
||||
func PostCommit(c web.C, w http.ResponseWriter, r *http.Request) {}
|
||||
|
||||
/*
|
||||
// PostCommit gets the commit for the repository and schedules to re-build.
|
||||
// GET /v1/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}
|
||||
func (h *CommitHandler) PostCommit(w http.ResponseWriter, r *http.Request) error {
|
||||
|
@ -201,10 +117,4 @@ func (h *CommitHandler) PostCommit(w http.ResponseWriter, r *http.Request) error
|
|||
w.WriteHeader(http.StatusOK)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *CommitHandler) Register(r *pat.Router) {
|
||||
r.Get("/v1/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}/console", errorHandler(h.GetCommitOutput))
|
||||
r.Get("/v1/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}", errorHandler(h.GetCommit))
|
||||
r.Post("/v1/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit}", errorHandler(h.PostCommit)).Queries("action", "rebuild")
|
||||
r.Get("/v1/repos/{host}/{owner}/{name}/branches/{branch}/commits", errorHandler(h.GetFeed))
|
||||
}
|
||||
*/
|
||||
|
|
51
server/handler/context.go
Normal file
51
server/handler/context.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/zenazn/goji/web"
|
||||
)
|
||||
|
||||
// ToUser returns the User from the current
|
||||
// request context. If the User does not exist
|
||||
// a nil value is returned.
|
||||
func ToUser(c web.C) *model.User {
|
||||
var v = c.Env["user"]
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
u, ok := v.(*model.User)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// ToRepo returns the Repo from the current
|
||||
// request context. If the Repo does not exist
|
||||
// a nil value is returned.
|
||||
func ToRepo(c web.C) *model.Repo {
|
||||
var v = c.Env["repo"]
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
r, ok := v.(*model.Repo)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ToRole returns the Role from the current
|
||||
// request context. If the Role does not exist
|
||||
// a nil value is returned.
|
||||
func ToRole(c web.C) *model.Perm {
|
||||
var v = c.Env["role"]
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
p, ok := v.(*model.Perm)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return p
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// badRequest is handled by setting the status code in the reply to StatusBadRequest.
|
||||
type badRequest struct{ error }
|
||||
|
||||
// notFound is handled by setting the status code in the reply to StatusNotFound.
|
||||
type notFound struct{ error }
|
||||
|
||||
// notAuthorized is handled by setting the status code in the reply to StatusNotAuthorized.
|
||||
type notAuthorized struct{ error }
|
||||
|
||||
// notImplemented is handled by setting the status code in the reply to StatusNotImplemented.
|
||||
type notImplemented struct{ error }
|
||||
|
||||
// forbidden is handled by setting the status code in the reply to StatusForbidden.
|
||||
type forbidden struct{ error }
|
||||
|
||||
// internalServerError is handled by setting the status code in the reply to StatusInternalServerError.
|
||||
type internalServerError struct{ error }
|
||||
|
||||
// errorHandler wraps a function returning an error by handling the error and returning a http.Handler.
|
||||
// If the error is of the one of the types defined above, it is handled as described for every type.
|
||||
// If the error is of another type, it is considered as an internal error and its message is logged.
|
||||
func errorHandler(f func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// serve the request
|
||||
err := f(w, r)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// log the url for debugging purposes
|
||||
log.Println(r.Method, r.URL.Path)
|
||||
|
||||
switch err.(type) {
|
||||
case badRequest:
|
||||
log.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
case notFound:
|
||||
http.Error(w, "Not Found", http.StatusNotFound)
|
||||
case notAuthorized:
|
||||
http.Error(w, "Not Authorized", http.StatusUnauthorized)
|
||||
case notImplemented:
|
||||
http.Error(w, "Not Implemented", http.StatusForbidden)
|
||||
case forbidden:
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
case internalServerError:
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
default:
|
||||
log.Println(err)
|
||||
http.Error(w, "oops", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,121 +1,3 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/drone/drone/plugin/remote"
|
||||
"github.com/drone/drone/server/database"
|
||||
"github.com/drone/drone/shared/build/script"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/gorilla/pat"
|
||||
)
|
||||
|
||||
type HookHandler struct {
|
||||
users database.UserManager
|
||||
repos database.RepoManager
|
||||
commits database.CommitManager
|
||||
queue chan *model.Request
|
||||
}
|
||||
|
||||
func NewHookHandler(users database.UserManager, repos database.RepoManager, commits database.CommitManager, queue chan *model.Request) *HookHandler {
|
||||
return &HookHandler{users, repos, commits, queue}
|
||||
}
|
||||
|
||||
// PostHook receives a post-commit hook from GitHub, Bitbucket, etc
|
||||
// GET /hook/:host
|
||||
func (h *HookHandler) PostHook(w http.ResponseWriter, r *http.Request) error {
|
||||
var host = r.FormValue(":host")
|
||||
var remote = remote.Lookup(host)
|
||||
if remote == nil {
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
// parse the hook payload
|
||||
hook, err := remote.ParseHook(r)
|
||||
if err != nil {
|
||||
return badRequest{err}
|
||||
}
|
||||
|
||||
// in some cases we have neither a hook nor error. An example
|
||||
// would be GitHub sending a ping request to the URL, in which
|
||||
// case we'll just exit quiely with an 'OK'
|
||||
if hook == nil || strings.Contains(hook.Message, "[CI SKIP]") {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetch the repository from the database
|
||||
repo, err := h.repos.FindName(remote.GetHost(), hook.Owner, hook.Repo)
|
||||
if err != nil {
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
if repo.Active == false ||
|
||||
(repo.PostCommit == false && len(hook.PullRequest) == 0) ||
|
||||
(repo.PullRequest == false && len(hook.PullRequest) != 0) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetch the user from the database that owns this repo
|
||||
user, err := h.users.Find(repo.UserID)
|
||||
if err != nil {
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
// featch the .drone.yml file from the database
|
||||
yml, err := remote.GetScript(user, repo, hook)
|
||||
if err != nil {
|
||||
return badRequest{err}
|
||||
}
|
||||
|
||||
// verify the commit hooks branch matches the list of approved
|
||||
// branches (unless it is a pull request). Note that we don't really
|
||||
// care if parsing the yaml fails here.
|
||||
s, _ := script.ParseBuild(string(yml), map[string]string{})
|
||||
if len(hook.PullRequest) == 0 && !s.MatchBranch(hook.Branch) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return nil
|
||||
}
|
||||
|
||||
c := model.Commit{
|
||||
RepoID: repo.ID,
|
||||
Status: model.StatusEnqueue,
|
||||
Sha: hook.Sha,
|
||||
Branch: hook.Branch,
|
||||
PullRequest: hook.PullRequest,
|
||||
Timestamp: hook.Timestamp,
|
||||
Message: hook.Message,
|
||||
Config: string(yml)}
|
||||
c.SetAuthor(hook.Author)
|
||||
// inser the commit into the database
|
||||
if err := h.commits.Insert(&c); err != nil {
|
||||
return badRequest{err}
|
||||
}
|
||||
|
||||
//fmt.Printf("%s", yml)
|
||||
owner, err := h.users.Find(repo.UserID)
|
||||
if err != nil {
|
||||
return badRequest{err}
|
||||
}
|
||||
|
||||
// drop the items on the queue
|
||||
go func() {
|
||||
h.queue <- &model.Request{
|
||||
User: owner,
|
||||
Host: httputil.GetURL(r),
|
||||
Repo: repo,
|
||||
Commit: &c,
|
||||
}
|
||||
}()
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HookHandler) Register(r *pat.Router) {
|
||||
r.Post("/v1/hook/{host}", errorHandler(h.PostHook))
|
||||
r.Put("/v1/hook/{host}", errorHandler(h.PostHook))
|
||||
}
|
||||
// PostHook
|
||||
|
|
|
@ -6,52 +6,53 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/drone/drone/plugin/remote"
|
||||
"github.com/drone/drone/server/database"
|
||||
"github.com/drone/drone/server/capability"
|
||||
"github.com/drone/drone/server/datastore"
|
||||
"github.com/drone/drone/server/session"
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/gorilla/pat"
|
||||
"github.com/goji/context"
|
||||
"github.com/zenazn/goji/web"
|
||||
)
|
||||
|
||||
type LoginHandler struct {
|
||||
users database.UserManager
|
||||
repos database.RepoManager
|
||||
perms database.PermManager
|
||||
sess session.Session
|
||||
open bool
|
||||
}
|
||||
|
||||
func NewLoginHandler(users database.UserManager, repos database.RepoManager, perms database.PermManager, sess session.Session, open bool) *LoginHandler {
|
||||
return &LoginHandler{users, repos, perms, sess, open}
|
||||
}
|
||||
|
||||
// GetLogin gets the login to the 3rd party remote system.
|
||||
// GetLogin accepts a request to authorize the user and to
|
||||
// return a valid OAuth2 access token. The access token is
|
||||
// returned as url segment #access_token
|
||||
//
|
||||
// GET /login/:host
|
||||
func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
|
||||
var host = r.FormValue(":host")
|
||||
//
|
||||
func GetLogin(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var host = c.URLParams["host"]
|
||||
var redirect = "/"
|
||||
var remote = remote.Lookup(host)
|
||||
if remote == nil {
|
||||
return notFound{}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// authenticate the user
|
||||
login, err := remote.Authorize(w, r)
|
||||
if err != nil {
|
||||
return badRequest{err}
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
} else if login == nil {
|
||||
// in this case we probably just redirected
|
||||
// the user, so we can exit with no error
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// get the user from the database
|
||||
u, err := h.users.FindLogin(host, login.Login)
|
||||
u, err := datastore.GetUserLogin(ctx, host, login.Login)
|
||||
if err != nil {
|
||||
// if self-registration is disabled we should
|
||||
// return a notAuthorized error. the only exception
|
||||
// is if no users exist yet in the system we'll proceed.
|
||||
if h.open == false && h.users.Exist() {
|
||||
return notAuthorized{}
|
||||
if capability.Enabled(ctx, capability.Registration) == false {
|
||||
users, err := datastore.GetUserList(ctx)
|
||||
if err != nil || len(users) != 0 {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// create the user account
|
||||
|
@ -60,8 +61,9 @@ func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
|
|||
u.SetEmail(login.Email)
|
||||
|
||||
// insert the user into the database
|
||||
if err := h.users.Insert(u); err != nil {
|
||||
return badRequest{err}
|
||||
if err := datastore.PostUser(ctx, u); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// if this is the first user, they
|
||||
|
@ -78,8 +80,9 @@ func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
|
|||
u.Name = login.Name
|
||||
u.SetEmail(login.Email)
|
||||
u.Syncing = true //u.IsStale() // todo (badrydzewski) should not always sync
|
||||
if err := h.users.Update(u); err != nil {
|
||||
return badRequest{err}
|
||||
if err := datastore.PutUser(ctx, u); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// look at the last synchronized date to determine if
|
||||
|
@ -109,10 +112,10 @@ func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
|
|||
// insert all repositories
|
||||
for _, repo := range repos {
|
||||
var role = repo.Role
|
||||
if err := h.repos.Insert(repo); err != nil {
|
||||
if err := datastore.PostRepo(ctx, repo); err != nil {
|
||||
// typically we see a failure because the repository already exists
|
||||
// in which case, we can retrieve the existing record to get the ID.
|
||||
repo, err = h.repos.FindName(repo.Host, repo.Owner, repo.Name)
|
||||
repo, err = datastore.GetRepoName(ctx, repo.Host, repo.Owner, repo.Name)
|
||||
if err != nil {
|
||||
log.Println("Error adding repo.", u.Login, repo.Name, err)
|
||||
continue
|
||||
|
@ -120,7 +123,14 @@ func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
|
|||
}
|
||||
|
||||
// add user permissions
|
||||
if err := h.perms.Grant(u, repo, role.Read, role.Write, role.Admin); err != nil {
|
||||
perm := model.Perm{
|
||||
UserID: u.ID,
|
||||
RepoID: repo.ID,
|
||||
Read: role.Read,
|
||||
Write: role.Write,
|
||||
Admin: role.Admin,
|
||||
}
|
||||
if err := datastore.PostPerm(ctx, &perm); err != nil {
|
||||
log.Println("Error adding permissions.", u.Login, repo.Name, err)
|
||||
continue
|
||||
}
|
||||
|
@ -130,31 +140,19 @@ func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
|
|||
|
||||
u.Synced = time.Now().UTC().Unix()
|
||||
u.Syncing = false
|
||||
if err := h.users.Update(u); err != nil {
|
||||
if err := datastore.PutUser(ctx, u); err != nil {
|
||||
log.Println("Error syncing user account, updating sync date", u.Login, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// (re)-create the user session
|
||||
h.sess.SetUser(w, r, u)
|
||||
token, err := session.GenerateToken(ctx, r, u)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
redirect = redirect + "#access_token=" + token
|
||||
|
||||
// redirect the user to their dashboard
|
||||
http.Redirect(w, r, redirect, http.StatusSeeOther)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLogout terminates the current user session
|
||||
// GET /logout
|
||||
func (h *LoginHandler) GetLogout(w http.ResponseWriter, r *http.Request) error {
|
||||
h.sess.Clear(w, r)
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *LoginHandler) Register(r *pat.Router) {
|
||||
r.Get("/login/{host}", errorHandler(h.GetLogin))
|
||||
r.Post("/login/{host}", errorHandler(h.GetLogin))
|
||||
r.Get("/logout", errorHandler(h.GetLogout))
|
||||
}
|
||||
|
|
36
server/handler/output.go
Normal file
36
server/handler/output.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/drone/drone/server/blobstore"
|
||||
"github.com/goji/context"
|
||||
"github.com/zenazn/goji/web"
|
||||
)
|
||||
|
||||
// GetOutput gets the commit's stdout.
|
||||
//
|
||||
// GET /api/repos/:host/:owner/:name/branches/:branch/commits/:commit/console
|
||||
//
|
||||
func GetOutput(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var (
|
||||
host = c.URLParams["host"]
|
||||
owner = c.URLParams["owner"]
|
||||
name = c.URLParams["name"]
|
||||
branch = c.URLParams["branch"]
|
||||
hash = c.URLParams["commit"]
|
||||
)
|
||||
|
||||
path := filepath.Join(host, owner, name, branch, hash)
|
||||
rc, err := blobstore.GetReader(ctx, path)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
defer rc.Close()
|
||||
io.Copy(w, rc)
|
||||
}
|
|
@ -6,96 +6,73 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/drone/drone/plugin/remote"
|
||||
"github.com/drone/drone/server/database"
|
||||
"github.com/drone/drone/server/session"
|
||||
"github.com/drone/drone/server/datastore"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/drone/drone/shared/sshutil"
|
||||
"github.com/gorilla/pat"
|
||||
"github.com/goji/context"
|
||||
"github.com/zenazn/goji/web"
|
||||
)
|
||||
|
||||
type RepoHandler struct {
|
||||
commits database.CommitManager
|
||||
perms database.PermManager
|
||||
repos database.RepoManager
|
||||
sess session.Session
|
||||
// GetRepo accepts a request to retrieve a commit
|
||||
// from the datastore for the given repository, branch and
|
||||
// commit hash.
|
||||
//
|
||||
// GET /api/repos/:host/:owner/:name
|
||||
//
|
||||
func GetRepo(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
admin = r.FormValue("admin")
|
||||
role = ToRole(c)
|
||||
repo = ToRepo(c)
|
||||
)
|
||||
|
||||
// if the user is not requesting (or cannot access)
|
||||
// admin data then we just return the repo as-is
|
||||
if len(admin) == 0 || role.Admin == false {
|
||||
json.NewEncoder(w).Encode(repo)
|
||||
return
|
||||
}
|
||||
|
||||
func NewRepoHandler(repos database.RepoManager, commits database.CommitManager,
|
||||
perms database.PermManager, sess session.Session) *RepoHandler {
|
||||
return &RepoHandler{commits, perms, repos, sess}
|
||||
}
|
||||
|
||||
// GetRepo gets the named repository.
|
||||
// GET /v1/repos/:host/:owner/:name
|
||||
func (h *RepoHandler) GetRepo(w http.ResponseWriter, r *http.Request) error {
|
||||
var host, owner, name = parseRepo(r)
|
||||
var admin = r.FormValue("admin")
|
||||
|
||||
// get the user form the session.
|
||||
user := h.sess.User(r)
|
||||
|
||||
// get the repository from the database.
|
||||
repo, err := h.repos.FindName(host, owner, name)
|
||||
switch {
|
||||
case err != nil && user == nil:
|
||||
return notAuthorized{}
|
||||
case err != nil && user != nil:
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
// user must have read access to the repository.
|
||||
repo.Role = h.perms.Find(user, repo)
|
||||
switch {
|
||||
case repo.Role.Read == false && user == nil:
|
||||
return notAuthorized{}
|
||||
case repo.Role.Read == false && user != nil:
|
||||
return notFound{}
|
||||
}
|
||||
// if the user is not requesting admin data we can
|
||||
// return exactly what we have.
|
||||
if len(admin) == 0 {
|
||||
return json.NewEncoder(w).Encode(repo)
|
||||
}
|
||||
|
||||
// ammend the response to include data that otherwise
|
||||
// would be excluded from json serialization, assuming
|
||||
// the user is actually an admin of the repo.
|
||||
if ok, _ := h.perms.Admin(user, repo); !ok {
|
||||
return notFound{err}
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(struct {
|
||||
// else we should return restricted fields
|
||||
json.NewEncoder(w).Encode(struct {
|
||||
*model.Repo
|
||||
PublicKey string `json:"public_key"`
|
||||
Params string `json:"params"`
|
||||
}{repo, repo.PublicKey, repo.Params})
|
||||
}
|
||||
|
||||
// PostRepo activates the named repository.
|
||||
// POST /v1/repos/:host/:owner/:name
|
||||
func (h *RepoHandler) PostRepo(w http.ResponseWriter, r *http.Request) error {
|
||||
var host, owner, name = parseRepo(r)
|
||||
// DelRepo accepts a request to inactivate the named
|
||||
// repository. This will disable all builds in the system
|
||||
// for this repository.
|
||||
//
|
||||
// DEL /api/repos/:host/:owner/:name
|
||||
//
|
||||
func DelRepo(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var repo = ToRepo(c)
|
||||
|
||||
// get the user form the session.
|
||||
user := h.sess.User(r)
|
||||
if user == nil {
|
||||
return notAuthorized{}
|
||||
// disable everything
|
||||
repo.Active = false
|
||||
repo.PullRequest = false
|
||||
repo.PostCommit = false
|
||||
|
||||
if err := datastore.PutRepo(ctx, repo); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// get the repo from the database
|
||||
repo, err := h.repos.FindName(host, owner, name)
|
||||
switch {
|
||||
case err != nil && user == nil:
|
||||
return notAuthorized{}
|
||||
case err != nil && user != nil:
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
// user must have admin access to the repository.
|
||||
if ok, _ := h.perms.Admin(user, repo); !ok {
|
||||
return notFound{err}
|
||||
}
|
||||
// PostRepo accapets a request to activate the named repository
|
||||
// in the datastore. It returns a 201 status created if successful
|
||||
//
|
||||
// POST /api/repos/:host/:owner/:name
|
||||
//
|
||||
func PostRepo(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var repo = ToRepo(c)
|
||||
var user = ToUser(c)
|
||||
|
||||
// update the repo active flag and fields
|
||||
repo.Active = true
|
||||
|
@ -104,62 +81,47 @@ func (h *RepoHandler) PostRepo(w http.ResponseWriter, r *http.Request) error {
|
|||
repo.UserID = user.ID
|
||||
repo.Timeout = 3600 // default to 1 hour
|
||||
|
||||
// generate the rsa key
|
||||
// generates the rsa key
|
||||
key, err := sshutil.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return internalServerError{err}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// marshal the public and private key values
|
||||
repo.PublicKey = sshutil.MarshalPublicKey(&key.PublicKey)
|
||||
repo.PrivateKey = sshutil.MarshalPrivateKey(key)
|
||||
|
||||
var remote = remote.Lookup(host)
|
||||
var remote = remote.Lookup(repo.Host)
|
||||
if remote == nil {
|
||||
return notFound{}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// post commit hook url
|
||||
hook := fmt.Sprintf("%s://%s/v1/hook/%s", httputil.GetScheme(r), httputil.GetHost(r), remote.GetKind())
|
||||
|
||||
// activate the repository in the remote system
|
||||
// setup the post-commit hook with the remote system and
|
||||
// if necessary, register the public key
|
||||
var hook = fmt.Sprintf("%s/v1/hook/%s", httputil.GetURL(r), repo.Remote)
|
||||
if err := remote.Activate(user, repo, hook); err != nil {
|
||||
return badRequest{err}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// update the status in the database
|
||||
if err := h.repos.Update(repo); err != nil {
|
||||
return badRequest{err}
|
||||
if err := datastore.PutRepo(ctx, repo); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
return json.NewEncoder(w).Encode(repo)
|
||||
json.NewEncoder(w).Encode(repo)
|
||||
}
|
||||
|
||||
// PutRepo updates the named repository.
|
||||
// PUT /v1/repos/:host/:owner/:name
|
||||
func (h *RepoHandler) PutRepo(w http.ResponseWriter, r *http.Request) error {
|
||||
var host, owner, name = parseRepo(r)
|
||||
|
||||
// get the user form the session.
|
||||
user := h.sess.User(r)
|
||||
if user == nil {
|
||||
return notAuthorized{}
|
||||
}
|
||||
|
||||
// get the repo from the database
|
||||
repo, err := h.repos.FindName(host, owner, name)
|
||||
switch {
|
||||
case err != nil && user == nil:
|
||||
return notAuthorized{}
|
||||
case err != nil && user != nil:
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
// user must have admin access to the repository.
|
||||
if ok, _ := h.perms.Admin(user, repo); !ok {
|
||||
return notFound{err}
|
||||
}
|
||||
// PutRepo accapets a request to update the named repository
|
||||
// in the datastore. It expects a JSON input and returns the
|
||||
// updated repository in JSON format if successful.
|
||||
//
|
||||
// PUT /api/repos/:host/:owner/:name
|
||||
//
|
||||
func PutRepo(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var repo = ToRepo(c)
|
||||
var user = ToUser(c)
|
||||
|
||||
// unmarshal the repository from the payload
|
||||
defer r.Body.Close()
|
||||
|
@ -173,28 +135,22 @@ func (h *RepoHandler) PutRepo(w http.ResponseWriter, r *http.Request) error {
|
|||
PrivateKey *string `json:"private_key"`
|
||||
}{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
||||
return badRequest{err}
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// update the private/secure parameters
|
||||
if in.Params != nil {
|
||||
repo.Params = *in.Params
|
||||
}
|
||||
// update the post commit flag
|
||||
if in.PostCommit != nil {
|
||||
repo.PostCommit = *in.PostCommit
|
||||
}
|
||||
// update the pull request flag
|
||||
if in.PullRequest != nil {
|
||||
repo.PullRequest = *in.PullRequest
|
||||
}
|
||||
// update the privileged flag. This can only be updated by
|
||||
// the system administrator
|
||||
if in.Privileged != nil && user.Admin {
|
||||
repo.Privileged = *in.Privileged
|
||||
}
|
||||
// update the timeout. This can only be updated by
|
||||
// the system administrator
|
||||
if in.Timeout != nil && user.Admin {
|
||||
repo.Timeout = *in.Timeout
|
||||
}
|
||||
|
@ -202,93 +158,9 @@ func (h *RepoHandler) PutRepo(w http.ResponseWriter, r *http.Request) error {
|
|||
repo.PublicKey = *in.PublicKey
|
||||
repo.PrivateKey = *in.PrivateKey
|
||||
}
|
||||
|
||||
// update the repository
|
||||
if err := h.repos.Update(repo); err != nil {
|
||||
return badRequest{err}
|
||||
if err := datastore.PutRepo(ctx, repo); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(repo)
|
||||
}
|
||||
|
||||
// DeleteRepo deletes the named repository.
|
||||
// DEL /v1/repos/:host/:owner/:name
|
||||
func (h *RepoHandler) DeleteRepo(w http.ResponseWriter, r *http.Request) error {
|
||||
var host, owner, name = parseRepo(r)
|
||||
|
||||
// get the user form the session.
|
||||
user := h.sess.User(r)
|
||||
if user == nil {
|
||||
return notAuthorized{}
|
||||
}
|
||||
|
||||
// get the repo from the database
|
||||
repo, err := h.repos.FindName(host, owner, name)
|
||||
switch {
|
||||
case err != nil && user == nil:
|
||||
return notAuthorized{}
|
||||
case err != nil && user != nil:
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
// user must have admin access to the repository.
|
||||
if ok, _ := h.perms.Admin(user, repo); !ok {
|
||||
return notFound{err}
|
||||
}
|
||||
|
||||
// update the repo active flag and fields.
|
||||
repo.Active = false
|
||||
repo.PullRequest = false
|
||||
repo.PostCommit = false
|
||||
|
||||
// insert the new repository
|
||||
if err := h.repos.Update(repo); err != nil {
|
||||
return badRequest{err}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFeed gets the most recent commits across all branches
|
||||
// GET /v1/repos/{host}/{owner}/{name}/feed
|
||||
func (h *RepoHandler) GetFeed(w http.ResponseWriter, r *http.Request) error {
|
||||
var host, owner, name = parseRepo(r)
|
||||
|
||||
// get the user form the session.
|
||||
user := h.sess.User(r)
|
||||
|
||||
// get the repository from the database.
|
||||
repo, err := h.repos.FindName(host, owner, name)
|
||||
switch {
|
||||
case err != nil && user == nil:
|
||||
return notAuthorized{}
|
||||
case err != nil && user != nil:
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
// user must have read access to the repository.
|
||||
ok, _ := h.perms.Read(user, repo)
|
||||
switch {
|
||||
case ok == false && user == nil:
|
||||
return notAuthorized{}
|
||||
case ok == false && user != nil:
|
||||
return notFound{}
|
||||
}
|
||||
|
||||
// lists the most recent commits across all branches.
|
||||
commits, err := h.commits.List(repo.ID)
|
||||
if err != nil {
|
||||
return notFound{err}
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(commits)
|
||||
}
|
||||
|
||||
func (h *RepoHandler) Register(r *pat.Router) {
|
||||
r.Get("/v1/repos/{host}/{owner}/{name}/feed", errorHandler(h.GetFeed))
|
||||
r.Get("/v1/repos/{host}/{owner}/{name}", errorHandler(h.GetRepo))
|
||||
r.Put("/v1/repos/{host}/{owner}/{name}", errorHandler(h.PutRepo))
|
||||
r.Post("/v1/repos/{host}/{owner}/{name}", errorHandler(h.PostRepo))
|
||||
r.Delete("/v1/repos/{host}/{owner}/{name}", errorHandler(h.DeleteRepo))
|
||||
json.NewEncoder(w).Encode(repo)
|
||||
}
|
||||
|
|
|
@ -4,112 +4,110 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/drone/drone/server/database"
|
||||
"github.com/drone/drone/server/session"
|
||||
"github.com/drone/drone/server/datastore"
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/gorilla/pat"
|
||||
"github.com/goji/context"
|
||||
"github.com/zenazn/goji/web"
|
||||
)
|
||||
|
||||
type UserHandler struct {
|
||||
commits database.CommitManager
|
||||
repos database.RepoManager
|
||||
users database.UserManager
|
||||
sess session.Session
|
||||
}
|
||||
|
||||
func NewUserHandler(users database.UserManager, repos database.RepoManager, commits database.CommitManager, sess session.Session) *UserHandler {
|
||||
return &UserHandler{commits, repos, users, sess}
|
||||
}
|
||||
|
||||
// GetUser gets the authenticated user.
|
||||
// GetUserCurrent accepts a request to retrieve the
|
||||
// currently authenticated user from the datastore
|
||||
// and return in JSON format.
|
||||
//
|
||||
// GET /api/user
|
||||
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) error {
|
||||
// get the user form the session
|
||||
u := h.sess.User(r)
|
||||
if u == nil {
|
||||
return notAuthorized{}
|
||||
//
|
||||
func GetUserCurrent(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var user = ToUser(c)
|
||||
if user == nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
// Normally the Token would not be serialized to json.
|
||||
// In this case it is appropriate because the user is
|
||||
// requesting their own data, and will need to display
|
||||
// the Token on the website.
|
||||
// return private data for the currently authenticated
|
||||
// user, specifically, their auth token.
|
||||
data := struct {
|
||||
*model.User
|
||||
Token string `json:"token"`
|
||||
}{u, u.Token}
|
||||
return json.NewEncoder(w).Encode(&data)
|
||||
}{user, user.Token}
|
||||
json.NewEncoder(w).Encode(&data)
|
||||
}
|
||||
|
||||
// PutUser updates the authenticated user.
|
||||
// PutUser accepts a request to update the currently
|
||||
// authenticated User profile.
|
||||
//
|
||||
// PUT /api/user
|
||||
func (h *UserHandler) PutUser(w http.ResponseWriter, r *http.Request) error {
|
||||
// get the user form the session
|
||||
u := h.sess.User(r)
|
||||
if u == nil {
|
||||
return notAuthorized{}
|
||||
//
|
||||
func PutUser(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var user = ToUser(c)
|
||||
if user == nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// unmarshal the repository from the payload
|
||||
defer r.Body.Close()
|
||||
in := model.User{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
||||
return badRequest{err}
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// update the user email
|
||||
if len(in.Email) != 0 {
|
||||
u.SetEmail(in.Email)
|
||||
user.SetEmail(in.Email)
|
||||
}
|
||||
// update the user full name
|
||||
if len(in.Name) != 0 {
|
||||
u.Name = in.Name
|
||||
user.Name = in.Name
|
||||
}
|
||||
|
||||
// update the database
|
||||
if err := h.users.Update(u); err != nil {
|
||||
return internalServerError{err}
|
||||
if err := datastore.PutUser(ctx, user); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(u)
|
||||
json.NewEncoder(w).Encode(user)
|
||||
}
|
||||
|
||||
// GetRepos gets the authenticated user's repositories.
|
||||
// GetRepos accepts a request to get the currently
|
||||
// authenticated user's repository list from the datastore,
|
||||
// encoded and returned in JSON format.
|
||||
//
|
||||
// GET /api/user/repos
|
||||
func (h *UserHandler) GetRepos(w http.ResponseWriter, r *http.Request) error {
|
||||
// get the user from the session
|
||||
u := h.sess.User(r)
|
||||
if u == nil {
|
||||
return notAuthorized{}
|
||||
//
|
||||
func GetUserRepos(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var user = ToUser(c)
|
||||
if user == nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// get the user repositories
|
||||
repos, err := h.repos.List(u.ID)
|
||||
repos, err := datastore.GetRepoList(ctx, user)
|
||||
if err != nil {
|
||||
return badRequest{err}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
return json.NewEncoder(w).Encode(&repos)
|
||||
json.NewEncoder(w).Encode(&repos)
|
||||
}
|
||||
|
||||
// GetFeed gets the authenticated user's commit feed.
|
||||
// GetUserFeed accepts a request to get the user's latest
|
||||
// build feed, across all repositories, from the datastore.
|
||||
// The results are encoded and returned in JSON format.
|
||||
//
|
||||
// GET /api/user/feed
|
||||
func (h *UserHandler) GetFeed(w http.ResponseWriter, r *http.Request) error {
|
||||
// get the user from the session
|
||||
u := h.sess.User(r)
|
||||
if u == nil {
|
||||
return notAuthorized{}
|
||||
//
|
||||
func GetUserFeed(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var user = ToUser(c)
|
||||
if user == nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// get the user commits
|
||||
commits, err := h.commits.ListUser(u.ID)
|
||||
repos, err := datastore.GetCommitListUser(ctx, user)
|
||||
if err != nil {
|
||||
return badRequest{err}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
return json.NewEncoder(w).Encode(&commits)
|
||||
}
|
||||
|
||||
func (h *UserHandler) Register(r *pat.Router) {
|
||||
r.Get("/v1/user/repos", errorHandler(h.GetRepos))
|
||||
r.Get("/v1/user/feed", errorHandler(h.GetFeed))
|
||||
r.Get("/v1/user", errorHandler(h.GetUser))
|
||||
r.Put("/v1/user", errorHandler(h.PutUser))
|
||||
json.NewEncoder(w).Encode(&repos)
|
||||
}
|
||||
|
|
|
@ -4,124 +4,127 @@ import (
|
|||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/drone/drone/server/database"
|
||||
"github.com/drone/drone/server/session"
|
||||
"github.com/drone/drone/server/datastore"
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/gorilla/pat"
|
||||
"github.com/goji/context"
|
||||
"github.com/zenazn/goji/web"
|
||||
)
|
||||
|
||||
type UsersHandler struct {
|
||||
users database.UserManager
|
||||
sess session.Session
|
||||
}
|
||||
|
||||
func NewUsersHandler(users database.UserManager, sess session.Session) *UsersHandler {
|
||||
return &UsersHandler{users, sess}
|
||||
}
|
||||
|
||||
// GetUsers gets all users.
|
||||
// GetUsers accepts a request to retrieve all users
|
||||
// from the datastore and return encoded in JSON format.
|
||||
//
|
||||
// GET /api/users
|
||||
func (h *UsersHandler) GetUsers(w http.ResponseWriter, r *http.Request) error {
|
||||
// get the user form the session
|
||||
user := h.sess.User(r)
|
||||
//
|
||||
func GetUserList(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var user = ToUser(c)
|
||||
switch {
|
||||
case user == nil:
|
||||
return notAuthorized{}
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
case user.Admin == false:
|
||||
return forbidden{}
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
// get all users
|
||||
users, err := h.users.List()
|
||||
users, err := datastore.GetUserList(ctx)
|
||||
if err != nil {
|
||||
return internalServerError{err}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(users)
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(users)
|
||||
}
|
||||
|
||||
// GetUser gets a user by hostname and login.
|
||||
// GetUser accepts a request to retrieve a user by hostname
|
||||
// and login from the datastore and return encoded in JSON
|
||||
// format.
|
||||
//
|
||||
// GET /api/users/:host/:login
|
||||
func (h *UsersHandler) GetUser(w http.ResponseWriter, r *http.Request) error {
|
||||
remote := r.FormValue(":host")
|
||||
login := r.FormValue(":login")
|
||||
|
||||
// get the user form the session
|
||||
user := h.sess.User(r)
|
||||
//
|
||||
func GetUser(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var (
|
||||
user = ToUser(c)
|
||||
host = c.URLParams["host"]
|
||||
login = c.URLParams["login"]
|
||||
)
|
||||
switch {
|
||||
case user == nil:
|
||||
return notAuthorized{}
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
case user.Admin == false:
|
||||
return forbidden{}
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
user, err := h.users.FindLogin(remote, login)
|
||||
user, err := datastore.GetUserLogin(ctx, host, login)
|
||||
if err != nil {
|
||||
return notFound{err}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(user)
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(user)
|
||||
}
|
||||
|
||||
// PostUser registers a new user account.
|
||||
// PostUser accepts a request to create a new user in the
|
||||
// system. The created user account is returned in JSON
|
||||
// format if successful.
|
||||
//
|
||||
// POST /api/users/:host/:login
|
||||
func (h *UsersHandler) PostUser(w http.ResponseWriter, r *http.Request) error {
|
||||
remote := r.FormValue(":host")
|
||||
login := r.FormValue(":login")
|
||||
|
||||
// get the user form the session
|
||||
user := h.sess.User(r)
|
||||
//
|
||||
func PostUser(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var (
|
||||
user = ToUser(c)
|
||||
host = c.URLParams["host"]
|
||||
login = c.URLParams["login"]
|
||||
)
|
||||
switch {
|
||||
case user == nil:
|
||||
return notAuthorized{}
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
case user.Admin == false:
|
||||
return forbidden{}
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
account := model.NewUser(host, login, "")
|
||||
if err := datastore.PostUser(ctx, account); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(account)
|
||||
}
|
||||
|
||||
account := model.NewUser(remote, login, "")
|
||||
if err := h.users.Insert(account); err != nil {
|
||||
return badRequest{err}
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(account)
|
||||
}
|
||||
|
||||
// DeleteUser gets a user by hostname and login and deletes
|
||||
// from the system.
|
||||
// DeleteUser accepts a request to delete the specified
|
||||
// user account from the system. A successful request will
|
||||
// respond with an OK 200 status.
|
||||
//
|
||||
// DELETE /api/users/:host/:login
|
||||
func (h *UsersHandler) DeleteUser(w http.ResponseWriter, r *http.Request) error {
|
||||
remote := r.FormValue(":host")
|
||||
login := r.FormValue(":login")
|
||||
|
||||
// get the user form the session
|
||||
user := h.sess.User(r)
|
||||
//
|
||||
func DelUser(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(c)
|
||||
var (
|
||||
user = ToUser(c)
|
||||
host = c.URLParams["host"]
|
||||
login = c.URLParams["login"]
|
||||
)
|
||||
switch {
|
||||
case user == nil:
|
||||
return notAuthorized{}
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
case user.Admin == false:
|
||||
return forbidden{}
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
account, err := h.users.FindLogin(remote, login)
|
||||
account, err := datastore.GetUserLogin(ctx, host, login)
|
||||
if err != nil {
|
||||
return notFound{err}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// user cannot delete his / her own account
|
||||
if account.ID == user.ID {
|
||||
return badRequest{}
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.users.Delete(account); err != nil {
|
||||
return badRequest{err}
|
||||
if err := datastore.DelUser(ctx, account); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// return a 200 indicating deletion complete
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *UsersHandler) Register(r *pat.Router) {
|
||||
r.Delete("/v1/users/{host}/{login}", errorHandler(h.DeleteUser))
|
||||
r.Post("/v1/users/{host}/{login}", errorHandler(h.PostUser))
|
||||
r.Get("/v1/users/{host}/{login}", errorHandler(h.GetUser))
|
||||
r.Get("/v1/users", errorHandler(h.GetUsers))
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func parseRepo(r *http.Request) (host string, owner string, name string) {
|
||||
host = r.FormValue(":host")
|
||||
owner = r.FormValue(":owner")
|
||||
name = r.FormValue(":name")
|
||||
return
|
||||
}
|
||||
|
||||
func parseBranch(r *http.Request) (branch string) {
|
||||
return r.FormValue(":branch")
|
||||
}
|
||||
|
||||
func parseCommit(r *http.Request) (commit string) {
|
||||
return r.FormValue(":commit")
|
||||
}
|
|
@ -1,204 +1 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/server/database"
|
||||
"github.com/drone/drone/server/pubsub"
|
||||
"github.com/drone/drone/server/session"
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/gorilla/pat"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
// Time allowed to write the message to the client.
|
||||
writeWait = 10 * time.Second
|
||||
|
||||
// Time allowed to read the next pong message from the client.
|
||||
pongWait = 60 * time.Second
|
||||
|
||||
// Send pings to client with this period. Must be less than pongWait.
|
||||
pingPeriod = (pongWait * 9) / 10
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}
|
||||
|
||||
type WsHandler struct {
|
||||
pubsub *pubsub.PubSub
|
||||
commits database.CommitManager
|
||||
perms database.PermManager
|
||||
repos database.RepoManager
|
||||
sess session.Session
|
||||
}
|
||||
|
||||
func NewWsHandler(repos database.RepoManager, commits database.CommitManager, perms database.PermManager, sess session.Session, pubsub *pubsub.PubSub) *WsHandler {
|
||||
return &WsHandler{pubsub, commits, perms, repos, sess}
|
||||
}
|
||||
|
||||
// WsUser will upgrade the connection to a Websocket and will stream
|
||||
// all events to the browser pertinent to the authenticated user. If the user
|
||||
// is not authenticated, only public events are streamed.
|
||||
func (h *WsHandler) WsUser(w http.ResponseWriter, r *http.Request) error {
|
||||
// get the user form the session
|
||||
user := h.sess.UserCookie(r)
|
||||
|
||||
// upgrade the websocket
|
||||
ws, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return badRequest{err}
|
||||
}
|
||||
|
||||
// register a channel for global events
|
||||
channel := h.pubsub.Register("_global")
|
||||
sub := channel.Subscribe()
|
||||
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
sub.Close()
|
||||
ws.Close()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case msg := <-sub.Read():
|
||||
work, ok := msg.(*model.Request)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
// user must have read access to the repository
|
||||
// in order to pass this message along
|
||||
if role := h.perms.Find(user, work.Repo); !role.Read {
|
||||
break
|
||||
}
|
||||
|
||||
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
err := ws.WriteJSON(work)
|
||||
if err != nil {
|
||||
ws.Close()
|
||||
return
|
||||
}
|
||||
case <-sub.CloseNotify():
|
||||
ws.Close()
|
||||
return
|
||||
case <-ticker.C:
|
||||
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
err := ws.WriteMessage(websocket.PingMessage, []byte{})
|
||||
if err != nil {
|
||||
ws.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
readWebsocket(ws)
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// WsConsole will upgrade the connection to a Websocket and will stream
|
||||
// the build output to the browser.
|
||||
func (h *WsHandler) WsConsole(w http.ResponseWriter, r *http.Request) error {
|
||||
var commitID, _ = strconv.Atoi(r.FormValue(":id"))
|
||||
|
||||
commit, err := h.commits.Find(int64(commitID))
|
||||
if err != nil {
|
||||
return notFound{err}
|
||||
}
|
||||
repo, err := h.repos.Find(commit.RepoID)
|
||||
if err != nil {
|
||||
return notFound{err}
|
||||
}
|
||||
user := h.sess.UserCookie(r)
|
||||
if ok, _ := h.perms.Read(user, repo); !ok {
|
||||
return notFound{err}
|
||||
}
|
||||
|
||||
// find a channel that we can subscribe to
|
||||
// and listen for stream updates.
|
||||
channel := h.pubsub.Lookup(commit.ID)
|
||||
if channel == nil {
|
||||
return notFound{}
|
||||
}
|
||||
sub := channel.Subscribe()
|
||||
defer sub.Close()
|
||||
|
||||
// upgrade the websocket
|
||||
ws, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return badRequest{err}
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
ws.Close()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case msg := <-sub.Read():
|
||||
data, ok := msg.([]byte)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
err := ws.WriteMessage(websocket.TextMessage, data)
|
||||
if err != nil {
|
||||
log.Printf("websocket for commit %d closed. Err: %s\n", commitID, err)
|
||||
ws.Close()
|
||||
return
|
||||
}
|
||||
case <-sub.CloseNotify():
|
||||
log.Printf("websocket for commit %d closed by client\n", commitID)
|
||||
ws.Close()
|
||||
return
|
||||
case <-ticker.C:
|
||||
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
err := ws.WriteMessage(websocket.PingMessage, []byte{})
|
||||
if err != nil {
|
||||
log.Printf("websocket for commit %d closed. Err: %s\n", commitID, err)
|
||||
ws.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
readWebsocket(ws)
|
||||
return nil
|
||||
}
|
||||
|
||||
// readWebsocket will block while reading the websocket data
|
||||
func readWebsocket(ws *websocket.Conn) {
|
||||
defer ws.Close()
|
||||
ws.SetReadLimit(512)
|
||||
ws.SetReadDeadline(time.Now().Add(pongWait))
|
||||
ws.SetPongHandler(func(string) error {
|
||||
ws.SetReadDeadline(time.Now().Add(pongWait))
|
||||
return nil
|
||||
})
|
||||
for {
|
||||
_, _, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WsHandler) Register(r *pat.Router) {
|
||||
r.Get("/ws/user", errorHandler(h.WsUser))
|
||||
r.Get("/ws/stdout/{id}", errorHandler(h.WsConsole))
|
||||
}
|
||||
|
|
161
server/main.go
161
server/main.go
|
@ -5,28 +5,32 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/drone/config"
|
||||
"github.com/drone/drone/server/database"
|
||||
"github.com/drone/drone/server/database/schema"
|
||||
//"github.com/drone/drone/server/database"
|
||||
"github.com/drone/drone/server/handler"
|
||||
"github.com/drone/drone/server/pubsub"
|
||||
"github.com/drone/drone/server/session"
|
||||
"github.com/drone/drone/server/worker"
|
||||
"github.com/drone/drone/server/middleware"
|
||||
//"github.com/drone/drone/server/pubsub"
|
||||
//"github.com/drone/drone/server/session"
|
||||
//"github.com/drone/drone/server/worker"
|
||||
"github.com/drone/drone/shared/build/log"
|
||||
"github.com/drone/drone/shared/model"
|
||||
//"github.com/drone/drone/shared/model"
|
||||
|
||||
"github.com/gorilla/pat"
|
||||
//"github.com/justinas/nosurf"
|
||||
"github.com/GeertJohan/go.rice"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/russross/meddler"
|
||||
//"github.com/GeertJohan/go.rice"
|
||||
|
||||
"code.google.com/p/go.net/context"
|
||||
webcontext "github.com/goji/context"
|
||||
"github.com/zenazn/goji"
|
||||
"github.com/zenazn/goji/web"
|
||||
|
||||
_ "github.com/drone/drone/plugin/notify/email"
|
||||
"github.com/drone/drone/plugin/remote/bitbucket"
|
||||
"github.com/drone/drone/plugin/remote/github"
|
||||
"github.com/drone/drone/plugin/remote/gitlab"
|
||||
"github.com/drone/drone/server/blobstore"
|
||||
"github.com/drone/drone/server/datastore"
|
||||
"github.com/drone/drone/server/datastore/database"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -58,6 +62,8 @@ var (
|
|||
open bool
|
||||
|
||||
nodes StringArr
|
||||
|
||||
db *sql.DB
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -65,12 +71,8 @@ func main() {
|
|||
|
||||
flag.StringVar(&conf, "config", "", "")
|
||||
flag.StringVar(&prefix, "prefix", "DRONE_", "")
|
||||
flag.StringVar(&port, "port", ":8080", "")
|
||||
flag.StringVar(&driver, "driver", "sqlite3", "")
|
||||
flag.StringVar(&datasource, "datasource", "drone.sqlite", "")
|
||||
flag.StringVar(&sslcert, "sslcert", "", "")
|
||||
flag.StringVar(&sslkey, "sslkey", "", "")
|
||||
flag.IntVar(&workers, "workers", runtime.NumCPU(), "")
|
||||
flag.Parse()
|
||||
|
||||
config.Var(&nodes, "worker-nodes")
|
||||
|
@ -85,29 +87,57 @@ func main() {
|
|||
github.Register()
|
||||
gitlab.Register()
|
||||
|
||||
// setup the database
|
||||
meddler.Default = meddler.SQLite
|
||||
db, _ := sql.Open(driver, datasource)
|
||||
schema.Load(db)
|
||||
// setup the database and cancel all pending
|
||||
// commits in the system.
|
||||
db = database.MustConnect(driver, datasource)
|
||||
go database.NewCommitstore(db).KillCommits()
|
||||
|
||||
// setup the database managers
|
||||
repos := database.NewRepoManager(db)
|
||||
users := database.NewUserManager(db)
|
||||
perms := database.NewPermManager(db)
|
||||
commits := database.NewCommitManager(db)
|
||||
goji.Get("/api/auth/:host", handler.GetLogin)
|
||||
goji.Get("/api/badge/:host/:owner/:name/status.svg", handler.GetBadge)
|
||||
goji.Get("/api/badge/:host/:owner/:name/cc.xml", handler.GetCC)
|
||||
//goji.Get("/api/hook", handler.PostHook)
|
||||
//goji.Put("/api/hook", handler.PostHook)
|
||||
//goji.Post("/api/hook", handler.PostHook)
|
||||
|
||||
// message broker
|
||||
pubsub := pubsub.NewPubSub()
|
||||
repos := web.New()
|
||||
repos.Use(middleware.SetRepo)
|
||||
repos.Use(middleware.RequireRepoRead)
|
||||
repos.Use(middleware.RequireRepoAdmin)
|
||||
repos.Get("/api/repos/:host/:owner/:name/branches/:branch/commits/:commit/console", handler.GetOutput)
|
||||
repos.Get("/api/repos/:host/:owner/:name/branches/:branch/commits/:commit", handler.GetCommit)
|
||||
repos.Post("/api/repos/:host/:owner/:name/branches/:branch/commits/:commit", handler.PostCommit)
|
||||
repos.Get("/api/repos/:host/:owner/:name/commits", handler.GetCommitList)
|
||||
repos.Get("/api/repos/:host/:owner/:name", handler.GetRepo)
|
||||
repos.Put("/api/repos/:host/:owner/:name", handler.PutRepo)
|
||||
repos.Post("/api/repos/:host/:owner/:name", handler.PostRepo)
|
||||
repos.Delete("/api/repos/:host/:owner/:name", handler.DelRepo)
|
||||
goji.Handle("/api/repos/:host/:owner/:name*", repos)
|
||||
|
||||
// cancel all previously running builds
|
||||
go commits.CancelAll()
|
||||
users := web.New()
|
||||
users.Use(middleware.RequireUserAdmin)
|
||||
users.Get("/api/users/:host/:login", handler.GetUser)
|
||||
users.Post("/api/users/:host/:login", handler.PostUser)
|
||||
users.Delete("/api/users/:host/:login", handler.DelUser)
|
||||
users.Get("/api/users", handler.GetUserList)
|
||||
goji.Handle("/api/users*", users)
|
||||
|
||||
queue := make(chan *model.Request)
|
||||
workerc := make(chan chan *model.Request)
|
||||
worker.NewDispatch(queue, workerc).Start()
|
||||
user := web.New()
|
||||
user.Use(middleware.RequireUser)
|
||||
user.Get("/api/user/feed", handler.GetUserFeed)
|
||||
user.Get("/api/user/repos", handler.GetUserRepos)
|
||||
user.Get("/api/user", handler.GetUserCurrent)
|
||||
user.Put("/api/user", handler.PutUser)
|
||||
goji.Handle("/api/user*", user)
|
||||
|
||||
// Add middleware and serve
|
||||
goji.Use(ContextMiddleware)
|
||||
goji.Use(middleware.SetHeaders)
|
||||
goji.Use(middleware.SetUser)
|
||||
goji.Serve()
|
||||
|
||||
// if no worker nodes are specified than start 2 workers
|
||||
// using the default DOCKER_HOST
|
||||
/*
|
||||
if nodes == nil || len(nodes) == 0 {
|
||||
worker.NewWorker(workerc, users, repos, commits, pubsub, &model.Server{}).Start()
|
||||
worker.NewWorker(workerc, users, repos, commits, pubsub, &model.Server{}).Start()
|
||||
|
@ -117,54 +147,31 @@ func main() {
|
|||
worker.NewWorker(workerc, users, repos, commits, pubsub, &model.Server{Host: node}).Start()
|
||||
}
|
||||
}
|
||||
|
||||
// setup the session managers
|
||||
sess := session.NewSession(users)
|
||||
|
||||
// setup the router and register routes
|
||||
router := pat.New()
|
||||
handler.NewUsersHandler(users, sess).Register(router)
|
||||
handler.NewUserHandler(users, repos, commits, sess).Register(router)
|
||||
handler.NewHookHandler(users, repos, commits, queue).Register(router)
|
||||
handler.NewLoginHandler(users, repos, perms, sess, open).Register(router)
|
||||
handler.NewCommitHandler(users, repos, commits, perms, sess, queue).Register(router)
|
||||
handler.NewRepoHandler(repos, commits, perms, sess).Register(router)
|
||||
handler.NewBadgeHandler(repos, commits).Register(router)
|
||||
handler.NewWsHandler(repos, commits, perms, sess, pubsub).Register(router)
|
||||
|
||||
box := rice.MustFindBox("app/")
|
||||
fserver := http.FileServer(box.HTTPBox())
|
||||
index, _ := box.Bytes("index.html")
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case strings.HasPrefix(r.URL.Path, "/favicon.ico"),
|
||||
strings.HasPrefix(r.URL.Path, "/scripts/"),
|
||||
strings.HasPrefix(r.URL.Path, "/styles/"),
|
||||
strings.HasPrefix(r.URL.Path, "/views/"):
|
||||
// serve static conent
|
||||
fserver.ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/logout"),
|
||||
strings.HasPrefix(r.URL.Path, "/login/"),
|
||||
strings.HasPrefix(r.URL.Path, "/v1/"),
|
||||
strings.HasPrefix(r.URL.Path, "/ws/"):
|
||||
// standard header variables that should be set, for good measure.
|
||||
w.Header().Add("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate")
|
||||
w.Header().Add("X-Frame-Options", "DENY")
|
||||
w.Header().Add("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Add("X-XSS-Protection", "1; mode=block")
|
||||
// serve dynamic content
|
||||
router.ServeHTTP(w, r)
|
||||
default:
|
||||
w.Write(index)
|
||||
}
|
||||
})
|
||||
*/
|
||||
|
||||
// start webserver using HTTPS or HTTP
|
||||
if len(sslcert) != 0 {
|
||||
panic(http.ListenAndServeTLS(port, sslcert, sslkey, nil))
|
||||
} else {
|
||||
panic(http.ListenAndServe(port, nil))
|
||||
//if len(sslcert) != 0 {
|
||||
// panic(http.ListenAndServeTLS(port, sslcert, sslkey, nil))
|
||||
//} else {
|
||||
//panic(http.ListenAndServe(port, nil))
|
||||
//}
|
||||
}
|
||||
|
||||
// ContextMiddleware creates a new go.net/context and
|
||||
// injects into the current goji context.
|
||||
func ContextMiddleware(c *web.C, h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.Background()
|
||||
ctx = datastore.NewContext(ctx, database.NewDatastore(db))
|
||||
ctx = blobstore.NewContext(ctx, database.NewBlobstore(db))
|
||||
//ctx = pool.NewContext(ctx, workers)
|
||||
//ctx = director.NewContext(ctx, worker)
|
||||
|
||||
// add the context to the goji web context
|
||||
webcontext.Set(c, ctx)
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
type StringArr []string
|
||||
|
|
69
server/middleware/context.go
Normal file
69
server/middleware/context.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/zenazn/goji/web"
|
||||
)
|
||||
|
||||
// UserToC sets the User in the current
|
||||
// web context.
|
||||
func UserToC(c *web.C, user *model.User) {
|
||||
c.Env["user"] = user
|
||||
}
|
||||
|
||||
// RepoToC sets the User in the current
|
||||
// web context.
|
||||
func RepoToC(c *web.C, repo *model.Repo) {
|
||||
c.Env["repo"] = repo
|
||||
}
|
||||
|
||||
// RoleToC sets the User in the current
|
||||
// web context.
|
||||
func RoleToC(c *web.C, role *model.Perm) {
|
||||
c.Env["role"] = role
|
||||
}
|
||||
|
||||
// ToUser returns the User from the current
|
||||
// request context. If the User does not exist
|
||||
// a nil value is returned.
|
||||
func ToUser(c *web.C) *model.User {
|
||||
var v = c.Env["user"]
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
u, ok := v.(*model.User)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// ToRepo returns the Repo from the current
|
||||
// request context. If the Repo does not exist
|
||||
// a nil value is returned.
|
||||
func ToRepo(c *web.C) *model.Repo {
|
||||
var v = c.Env["repo"]
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
r, ok := v.(*model.Repo)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ToRole returns the Role from the current
|
||||
// request context. If the Role does not exist
|
||||
// a nil value is returned.
|
||||
func ToRole(c *web.C) *model.Perm {
|
||||
var v = c.Env["role"]
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
p, ok := v.(*model.Perm)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return p
|
||||
}
|
28
server/middleware/header.go
Normal file
28
server/middleware/header.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/zenazn/goji/web"
|
||||
)
|
||||
|
||||
// SetHeaders is a middleware function that applies
|
||||
// default headers and caching rules to each request.
|
||||
func SetHeaders(c *web.C, h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Add("X-Frame-Options", "DENY")
|
||||
w.Header().Add("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Add("X-XSS-Protection", "1; mode=block")
|
||||
w.Header().Add("Cache-Control", "no-cache")
|
||||
w.Header().Add("Cache-Control", "no-store")
|
||||
w.Header().Add("Cache-Control", "max-age=0")
|
||||
w.Header().Add("Cache-Control", "must-revalidate")
|
||||
w.Header().Add("Cache-Control", "value")
|
||||
w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
|
||||
w.Header().Set("Expires", "Thu, 01 Jan 1970 00:00:00 GMT")
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
103
server/middleware/repo.go
Normal file
103
server/middleware/repo.go
Normal file
|
@ -0,0 +1,103 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/drone/drone/server/datastore"
|
||||
"github.com/goji/context"
|
||||
"github.com/zenazn/goji/web"
|
||||
)
|
||||
|
||||
// SetRepo is a middleware function that retrieves
|
||||
// the repository and stores in the context.
|
||||
func SetRepo(c *web.C, h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = context.FromC(*c)
|
||||
host = c.URLParams["host"]
|
||||
owner = c.URLParams["owner"]
|
||||
name = c.URLParams["name"]
|
||||
user = ToUser(c)
|
||||
)
|
||||
|
||||
repo, err := datastore.GetRepoName(ctx, host, owner, name)
|
||||
switch {
|
||||
case err != nil && user == nil:
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
case err != nil && user != nil:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
role, _ := datastore.GetPerm(ctx, user, repo)
|
||||
RepoToC(c, repo)
|
||||
RoleToC(c, role)
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
// RequireRepoRead is a middleware function that verifies
|
||||
// the user has read access to the repository.
|
||||
func RequireRepoRead(c *web.C, h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
role = ToRole(c)
|
||||
user = ToUser(c)
|
||||
)
|
||||
switch {
|
||||
case role == nil:
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
case user == nil && role.Read == false:
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
case user == nil && role.Read == false:
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
case user != nil && role.Read == false:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
// RequireRepoAdmin is a middleware function that verifies
|
||||
// the user has admin access to the repository.
|
||||
func RequireRepoAdmin(c *web.C, h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
role = ToRole(c)
|
||||
user = ToUser(c)
|
||||
)
|
||||
|
||||
// Admin access is only rquired for POST, PUT, DELETE methods.
|
||||
// If this is a GET request we can proceed immediately.
|
||||
if r.Method == "GET" {
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case role == nil:
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
case user == nil && role.Admin == false:
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
case user != nil && role.Read == false && role.Admin == false:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
case user != nil && role.Read == true && role.Admin == false:
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
default:
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
57
server/middleware/user.go
Normal file
57
server/middleware/user.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/drone/drone/server/session"
|
||||
"github.com/goji/context"
|
||||
"github.com/zenazn/goji/web"
|
||||
)
|
||||
|
||||
// SetUser is a middleware function that retrieves
|
||||
// the currently authenticated user from the request
|
||||
// and stores in the context.
|
||||
func SetUser(c *web.C, h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
var ctx = context.FromC(*c)
|
||||
var user = session.GetUser(ctx, r)
|
||||
if user != nil && user.ID != 0 {
|
||||
UserToC(c, user)
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
// RequireUser is a middleware function that verifies
|
||||
// there is a currently authenticated user stored in
|
||||
// the context.
|
||||
func RequireUser(c *web.C, h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
if ToUser(c) == nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
// RequireUserAdmin is a middleware function that verifies
|
||||
// there is a currently authenticated user stored in
|
||||
// the context with ADMIN privilege.
|
||||
func RequireUserAdmin(c *web.C, h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
var user = ToUser(c)
|
||||
switch {
|
||||
case user == nil:
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
case user != nil && !user.Admin:
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
|
@ -2,97 +2,67 @@ package session
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/server/database"
|
||||
"code.google.com/p/go.net/context"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/drone/drone/server/datastore"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/shared/model"
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
// stores sessions using secure cookies.
|
||||
var cookies = sessions.NewCookieStore(
|
||||
securecookie.GenerateRandomKey(64))
|
||||
// secret key used to create jwt
|
||||
var secret = securecookie.GenerateRandomKey(32)
|
||||
|
||||
// stores sessions using secure cookies.
|
||||
var xsrftoken = string(securecookie.GenerateRandomKey(32))
|
||||
|
||||
type Session interface {
|
||||
User(r *http.Request) *model.User
|
||||
UserToken(r *http.Request) *model.User
|
||||
UserCookie(r *http.Request) *model.User
|
||||
SetUser(w http.ResponseWriter, r *http.Request, u *model.User)
|
||||
Clear(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
type session struct {
|
||||
users database.UserManager
|
||||
}
|
||||
|
||||
func NewSession(users database.UserManager) Session {
|
||||
return &session{
|
||||
users: users,
|
||||
}
|
||||
}
|
||||
|
||||
// User gets the currently authenticated user.
|
||||
func (s *session) User(r *http.Request) *model.User {
|
||||
// GetUser gets the currently authenticated user for the
|
||||
// http.Request. The user details will be stored as either
|
||||
// a simple API token or JWT bearer token.
|
||||
func GetUser(c context.Context, r *http.Request) *model.User {
|
||||
var token = r.FormValue("access_token")
|
||||
switch {
|
||||
case r.FormValue("access_token") == "":
|
||||
return s.UserCookie(r)
|
||||
case r.FormValue("access_token") != "":
|
||||
return s.UserToken(r)
|
||||
}
|
||||
case len(token) == 0:
|
||||
return nil
|
||||
case len(token) == 32:
|
||||
return getUserToken(c, r)
|
||||
default:
|
||||
return getUserBearer(c, r)
|
||||
}
|
||||
}
|
||||
|
||||
// UserXsrf gets the currently authenticated user and
|
||||
// validates the xsrf session token, if necessary.
|
||||
func (s *session) UserXsrf(r *http.Request) *model.User {
|
||||
user := s.User(r)
|
||||
if user == nil || r.FormValue("access_token") != "" {
|
||||
return user
|
||||
}
|
||||
if !httputil.CheckXsrf(r, xsrftoken, user.Login) {
|
||||
return nil
|
||||
// GenerateToken generates a JWT token for the user session
|
||||
// that can be appended to the #access_token segment to
|
||||
// facilitate client-based OAuth2.
|
||||
func GenerateToken(c context.Context, r *http.Request, user *model.User) (string, error) {
|
||||
token := jwt.New(jwt.GetSigningMethod("HS256"))
|
||||
token.Claims["user_id"] = user.ID
|
||||
token.Claims["audience"] = httputil.GetURL(r)
|
||||
token.Claims["expires"] = time.Now().UTC().Add(time.Hour * 72).Unix()
|
||||
return token.SignedString(secret)
|
||||
}
|
||||
|
||||
// getUserToken gets the currently authenticated user for the given
|
||||
// auth token.
|
||||
func getUserToken(c context.Context, r *http.Request) *model.User {
|
||||
var token = r.FormValue("access_token")
|
||||
var user, _ = datastore.GetUserToken(c, token)
|
||||
return user
|
||||
}
|
||||
|
||||
// UserToken gets the currently authenticated user for the given auth token.
|
||||
func (s *session) UserToken(r *http.Request) *model.User {
|
||||
token := r.FormValue("access_token")
|
||||
user, _ := s.users.FindToken(token)
|
||||
return user
|
||||
}
|
||||
|
||||
// UserCookie gets the currently authenticated user from the secure cookie session.
|
||||
func (s *session) UserCookie(r *http.Request) *model.User {
|
||||
sess, err := cookies.Get(r, "_sess")
|
||||
if err != nil {
|
||||
// getUserBearer gets the currently authenticated user for the given
|
||||
// bearer token (JWT)
|
||||
func getUserBearer(c context.Context, r *http.Request) *model.User {
|
||||
var tokenstr = r.FormValue("access_token")
|
||||
var token, err = jwt.Parse(tokenstr, func(t *jwt.Token) (interface{}, error) {
|
||||
return secret, nil
|
||||
})
|
||||
if err != nil || token.Valid {
|
||||
return nil
|
||||
}
|
||||
// get the uid from the session
|
||||
value, ok := sess.Values["uid"]
|
||||
var userid, ok = token.Claims["user_id"].(int64)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
// get the user from the database
|
||||
user, _ := s.users.Find(value.(int64))
|
||||
var user, _ = datastore.GetUser(c, userid)
|
||||
return user
|
||||
}
|
||||
|
||||
// SetUser writes the specified username to the session.
|
||||
func (s *session) SetUser(w http.ResponseWriter, r *http.Request, u *model.User) {
|
||||
sess, _ := cookies.Get(r, "_sess")
|
||||
sess.Values["uid"] = u.ID
|
||||
sess.Save(r, w)
|
||||
httputil.SetXsrf(w, r, xsrftoken, u.Login)
|
||||
}
|
||||
|
||||
// Clear removes the user from the session.
|
||||
func (s *session) Clear(w http.ResponseWriter, r *http.Request) {
|
||||
sess, _ := cookies.Get(r, "_sess")
|
||||
delete(sess.Values, "uid")
|
||||
sess.Save(r, w)
|
||||
}
|
||||
|
|
31
server/worker/context.go
Normal file
31
server/worker/context.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package worker
|
||||
|
||||
import (
|
||||
"code.google.com/p/go.net/context"
|
||||
)
|
||||
|
||||
const reqkey = "worker"
|
||||
|
||||
// NewContext returns a Context whose Value method returns the
|
||||
// application's worker queue.
|
||||
func NewContext(parent context.Context, worker Worker) context.Context {
|
||||
return &wrapper{parent, worker}
|
||||
}
|
||||
|
||||
type wrapper struct {
|
||||
context.Context
|
||||
worker Worker
|
||||
}
|
||||
|
||||
// Value returns the named key from the context.
|
||||
func (c *wrapper) Value(key interface{}) interface{} {
|
||||
if key == reqkey {
|
||||
return c.worker
|
||||
}
|
||||
return c.Context.Value(key)
|
||||
}
|
||||
|
||||
// FromContext returns the worker queue associated with this context.
|
||||
func FromContext(c context.Context) Worker {
|
||||
return c.Value(reqkey).(Worker)
|
||||
}
|
12
server/worker/director/context.go
Normal file
12
server/worker/director/context.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package director
|
||||
|
||||
import (
|
||||
"code.google.com/p/go.net/context"
|
||||
"github.com/drone/drone/server/worker"
|
||||
)
|
||||
|
||||
// NewContext returns a Context whose Value method returns
|
||||
// the director.
|
||||
func NewContext(parent context.Context, w worker.Worker) context.Context {
|
||||
return worker.NewContext(parent, w)
|
||||
}
|
117
server/worker/director/director.go
Normal file
117
server/worker/director/director.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
package director
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"code.google.com/p/go.net/context"
|
||||
"github.com/drone/drone/server/worker"
|
||||
"github.com/drone/drone/server/worker/pool"
|
||||
)
|
||||
|
||||
// Director manages workloads and delegates to workers.
|
||||
type Director struct {
|
||||
sync.Mutex
|
||||
|
||||
pending map[*worker.Work]bool
|
||||
started map[*worker.Work]worker.Worker
|
||||
}
|
||||
|
||||
func New() *Director {
|
||||
return &Director{
|
||||
pending: make(map[*worker.Work]bool),
|
||||
started: make(map[*worker.Work]worker.Worker),
|
||||
}
|
||||
}
|
||||
|
||||
// Do processes the work request async.
|
||||
func (d *Director) Do(c context.Context, work *worker.Work) {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
|
||||
d.do(c, work)
|
||||
}
|
||||
|
||||
// do is a blocking function that waits for an
|
||||
// available worker to process work.
|
||||
func (d *Director) do(c context.Context, work *worker.Work) {
|
||||
d.markPending(work)
|
||||
var pool = pool.FromContext(c)
|
||||
var worker = <-pool.Reserve()
|
||||
|
||||
// var worker worker.Worker
|
||||
//
|
||||
// // waits for an available worker. This is a blocking
|
||||
// // operation and will reject any nil workers to avoid
|
||||
// // a potential panic.
|
||||
// select {
|
||||
// case worker = <-pool.Reserve():
|
||||
// if worker != nil {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
d.markStarted(work, worker)
|
||||
worker.Do(c, work)
|
||||
d.markComplete(work)
|
||||
pool.Release(worker)
|
||||
}
|
||||
|
||||
// GetStarted returns a list of all jobs that
|
||||
// are assigned and being worked on.
|
||||
func (d *Director) GetStarted() []*worker.Work {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
started := []*worker.Work{}
|
||||
for work, _ := range d.started {
|
||||
started = append(started, work)
|
||||
}
|
||||
return started
|
||||
}
|
||||
|
||||
// GetPending returns a list of all work that
|
||||
// is pending assignment to a worker.
|
||||
func (d *Director) GetPending() []*worker.Work {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
pending := []*worker.Work{}
|
||||
for work, _ := range d.pending {
|
||||
pending = append(pending, work)
|
||||
}
|
||||
return pending
|
||||
}
|
||||
|
||||
// GetAssignments returns a list of assignments. The
|
||||
// assignment type is a structure that stores the
|
||||
// work being performed and the assigned worker.
|
||||
func (d *Director) GetAssignemnts() []*worker.Assignment {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
assignments := []*worker.Assignment{}
|
||||
for work, _worker := range d.started {
|
||||
assignment := &worker.Assignment{work, _worker}
|
||||
assignments = append(assignments, assignment)
|
||||
}
|
||||
return assignments
|
||||
}
|
||||
|
||||
func (d *Director) markPending(work *worker.Work) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
delete(d.started, work)
|
||||
d.pending[work] = true
|
||||
}
|
||||
|
||||
func (d *Director) markStarted(work *worker.Work, worker worker.Worker) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
delete(d.pending, work)
|
||||
d.started[work] = worker
|
||||
}
|
||||
|
||||
func (d *Director) markComplete(work *worker.Work) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
delete(d.pending, work)
|
||||
delete(d.started, work)
|
||||
}
|
97
server/worker/director/director_test.go
Normal file
97
server/worker/director/director_test.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package director
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.google.com/p/go.net/context"
|
||||
"github.com/drone/drone/server/worker"
|
||||
"github.com/drone/drone/server/worker/pool"
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestDirector(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Director", func() {
|
||||
|
||||
g.It("Should mark work as pending", func() {
|
||||
d := New()
|
||||
d.markPending(&worker.Work{})
|
||||
d.markPending(&worker.Work{})
|
||||
g.Assert(len(d.GetPending())).Equal(2)
|
||||
})
|
||||
|
||||
g.It("Should mark work as started", func() {
|
||||
d := New()
|
||||
w1 := worker.Work{}
|
||||
w2 := worker.Work{}
|
||||
d.markPending(&w1)
|
||||
d.markPending(&w2)
|
||||
g.Assert(len(d.GetPending())).Equal(2)
|
||||
d.markStarted(&w1, &mockWorker{})
|
||||
g.Assert(len(d.GetStarted())).Equal(1)
|
||||
g.Assert(len(d.GetPending())).Equal(1)
|
||||
d.markStarted(&w2, &mockWorker{})
|
||||
g.Assert(len(d.GetStarted())).Equal(2)
|
||||
g.Assert(len(d.GetPending())).Equal(0)
|
||||
})
|
||||
|
||||
g.It("Should mark work as complete", func() {
|
||||
d := New()
|
||||
w1 := worker.Work{}
|
||||
w2 := worker.Work{}
|
||||
d.markStarted(&w1, &mockWorker{})
|
||||
d.markStarted(&w2, &mockWorker{})
|
||||
g.Assert(len(d.GetStarted())).Equal(2)
|
||||
d.markComplete(&w1)
|
||||
g.Assert(len(d.GetStarted())).Equal(1)
|
||||
d.markComplete(&w2)
|
||||
g.Assert(len(d.GetStarted())).Equal(0)
|
||||
})
|
||||
|
||||
g.It("Should get work assignments", func() {
|
||||
d := New()
|
||||
w1 := worker.Work{}
|
||||
w2 := worker.Work{}
|
||||
d.markStarted(&w1, &mockWorker{})
|
||||
d.markStarted(&w2, &mockWorker{})
|
||||
g.Assert(len(d.GetAssignemnts())).Equal(2)
|
||||
})
|
||||
|
||||
g.It("Should recover from a panic", func() {
|
||||
d := New()
|
||||
d.Do(nil, nil)
|
||||
g.Assert(true).Equal(true)
|
||||
})
|
||||
|
||||
g.It("Should distribute work to worker", func() {
|
||||
work := &worker.Work{}
|
||||
workr := &mockWorker{}
|
||||
c := context.Background()
|
||||
p := pool.New()
|
||||
p.Allocate(workr)
|
||||
c = pool.NewContext(c, p)
|
||||
|
||||
d := New()
|
||||
d.do(c, work)
|
||||
g.Assert(workr.work).Equal(work) // verify mock worker gets work
|
||||
})
|
||||
|
||||
g.It("Should add director to context", func() {
|
||||
d := New()
|
||||
c := context.Background()
|
||||
c = NewContext(c, d)
|
||||
g.Assert(worker.FromContext(c)).Equal(d)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// fake worker for testing purpose only
|
||||
type mockWorker struct {
|
||||
name string
|
||||
work *worker.Work
|
||||
}
|
||||
|
||||
func (m *mockWorker) Do(c context.Context, w *worker.Work) {
|
||||
m.work = w
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
package worker
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/shared/model"
|
||||
)
|
||||
|
||||
// http://nesv.github.io/golang/2014/02/25/worker-queues-in-go.html
|
||||
|
||||
type Dispatch struct {
|
||||
requests chan *model.Request
|
||||
workers chan chan *model.Request
|
||||
quit chan bool
|
||||
}
|
||||
|
||||
func NewDispatch(requests chan *model.Request, workers chan chan *model.Request) *Dispatch {
|
||||
return &Dispatch{
|
||||
requests: requests,
|
||||
workers: workers,
|
||||
quit: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
// Start tells the dispatcher to start listening
|
||||
// for work requests and dispatching to workers.
|
||||
func (d *Dispatch) Start() {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
// pickup a request from the queue
|
||||
case request := <-d.requests:
|
||||
go func() {
|
||||
// find an available worker and
|
||||
// send the request to that worker
|
||||
worker := <-d.workers
|
||||
worker <- request
|
||||
}()
|
||||
// listen for a signal to exit
|
||||
case <-d.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
// Stop tells the dispatcher to stop listening for new
|
||||
// work requests.
|
||||
func (d *Dispatch) Stop() {
|
||||
go func() { d.quit <- true }()
|
||||
}
|
146
server/worker/docker/docker.go
Normal file
146
server/worker/docker/docker.go
Normal file
|
@ -0,0 +1,146 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"code.google.com/p/go-uuid/uuid"
|
||||
"code.google.com/p/go.net/context"
|
||||
"github.com/drone/drone/plugin/notify"
|
||||
"github.com/drone/drone/server/blobstore"
|
||||
"github.com/drone/drone/server/datastore"
|
||||
"github.com/drone/drone/server/worker"
|
||||
"github.com/drone/drone/shared/build"
|
||||
"github.com/drone/drone/shared/build/docker"
|
||||
"github.com/drone/drone/shared/build/git"
|
||||
"github.com/drone/drone/shared/build/repo"
|
||||
"github.com/drone/drone/shared/build/script"
|
||||
"github.com/drone/drone/shared/model"
|
||||
)
|
||||
|
||||
const dockerKind = "docker"
|
||||
|
||||
type Docker struct {
|
||||
UUID string `json:"uuid"`
|
||||
Kind string `json:"type"`
|
||||
Created int64 `json:"created"`
|
||||
|
||||
docker *docker.Client
|
||||
}
|
||||
|
||||
func New() *Docker {
|
||||
return &Docker{
|
||||
UUID: uuid.New(),
|
||||
Kind: dockerKind,
|
||||
Created: time.Now().UTC().Unix(),
|
||||
docker: docker.New(),
|
||||
//docker.NewHost(w.server.Host)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Docker) Do(c context.Context, r *worker.Work) {
|
||||
|
||||
// ensure that we can recover from any panics to
|
||||
// avoid bringing down the entire application.
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
log.Printf("%s: %s", e, debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
// mark the build as Started and update the database
|
||||
r.Commit.Status = model.StatusStarted
|
||||
r.Commit.Started = time.Now().UTC().Unix()
|
||||
datastore.PutCommit(c, r.Commit)
|
||||
|
||||
// notify all listeners that the build is started
|
||||
//commitc := w.pubsub.Register("_global")
|
||||
//commitc.Publish(r)
|
||||
//stdoutc := w.pubsub.RegisterOpts(r.Commit.ID, pubsub.ConsoleOpts)
|
||||
//defer stdoutc.Close()
|
||||
|
||||
// create a special buffer that will also
|
||||
// write to a websocket channel
|
||||
var buf bytes.Buffer //:= pubsub.NewBuffer(stdoutc)
|
||||
|
||||
// parse the parameters and build script. The script has already
|
||||
// been parsed in the hook, so we can be confident it will succeed.
|
||||
// that being said, we should clean this up
|
||||
params, err := r.Repo.ParamMap()
|
||||
if err != nil {
|
||||
log.Printf("Error parsing PARAMS for %s/%s, Err: %s", r.Repo.Owner, r.Repo.Name, err.Error())
|
||||
}
|
||||
script, err := script.ParseBuild(r.Commit.Config, params)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing YAML for %s/%s, Err: %s", r.Repo.Owner, r.Repo.Name, err.Error())
|
||||
}
|
||||
|
||||
// append private parameters to the environment
|
||||
// variable section of the .drone.yml file, iff
|
||||
// this is not a pull request (for security purposes)
|
||||
if params != nil && (r.Repo.Private || len(r.Commit.PullRequest) == 0) {
|
||||
for k, v := range params {
|
||||
script.Env = append(script.Env, k+"="+v)
|
||||
}
|
||||
}
|
||||
|
||||
path := r.Repo.Host + "/" + r.Repo.Owner + "/" + r.Repo.Name
|
||||
repo := &repo.Repo{
|
||||
Name: path,
|
||||
Path: r.Repo.CloneURL,
|
||||
Branch: r.Commit.Branch,
|
||||
Commit: r.Commit.Sha,
|
||||
PR: r.Commit.PullRequest,
|
||||
Dir: filepath.Join("/var/cache/drone/src", git.GitPath(script.Git, path)),
|
||||
Depth: git.GitDepth(script.Git),
|
||||
}
|
||||
|
||||
// send all "started" notifications
|
||||
if script.Notifications == nil {
|
||||
script.Notifications = ¬ify.Notification{}
|
||||
}
|
||||
//script.Notifications.Send(r)
|
||||
|
||||
// create an instance of the Docker builder
|
||||
builder := build.New(d.docker)
|
||||
builder.Build = script
|
||||
builder.Repo = repo
|
||||
builder.Stdout = &buf
|
||||
builder.Key = []byte(r.Repo.PrivateKey)
|
||||
builder.Timeout = time.Duration(r.Repo.Timeout) * time.Second
|
||||
builder.Privileged = r.Repo.Privileged
|
||||
|
||||
// run the build
|
||||
err = builder.Run()
|
||||
|
||||
// update the build status based on the results
|
||||
// from the build runner.
|
||||
switch {
|
||||
case err != nil:
|
||||
r.Commit.Status = model.StatusError
|
||||
log.Printf("Error building %s, Err: %s", r.Commit.Sha, err)
|
||||
buf.WriteString(err.Error())
|
||||
case builder.BuildState == nil:
|
||||
r.Commit.Status = model.StatusFailure
|
||||
case builder.BuildState.ExitCode != 0:
|
||||
r.Commit.Status = model.StatusFailure
|
||||
default:
|
||||
r.Commit.Status = model.StatusSuccess
|
||||
}
|
||||
|
||||
// calcualte the build finished and duration details and
|
||||
// update the commit
|
||||
r.Commit.Finished = time.Now().UTC().Unix()
|
||||
r.Commit.Duration = (r.Commit.Finished - r.Commit.Started)
|
||||
datastore.PutCommit(c, r.Commit)
|
||||
blobstore.Put(c, filepath.Join(r.Repo.Host, r.Repo.Owner, r.Repo.Name, r.Commit.Branch, r.Commit.Sha), buf.Bytes())
|
||||
|
||||
// notify all listeners that the build is finished
|
||||
//commitc.Publish(r)
|
||||
|
||||
// send all "finished" notifications
|
||||
//script.Notifications.Send(r)
|
||||
}
|
31
server/worker/pool/context.go
Normal file
31
server/worker/pool/context.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package pool
|
||||
|
||||
import (
|
||||
"code.google.com/p/go.net/context"
|
||||
)
|
||||
|
||||
const reqkey = "pool"
|
||||
|
||||
// NewContext returns a Context whose Value method returns the
|
||||
// worker pool.
|
||||
func NewContext(parent context.Context, pool *Pool) context.Context {
|
||||
return &wrapper{parent, pool}
|
||||
}
|
||||
|
||||
type wrapper struct {
|
||||
context.Context
|
||||
pool *Pool
|
||||
}
|
||||
|
||||
// Value returns the named key from the context.
|
||||
func (c *wrapper) Value(key interface{}) interface{} {
|
||||
if key == reqkey {
|
||||
return c.pool
|
||||
}
|
||||
return c.Context.Value(key)
|
||||
}
|
||||
|
||||
// FromContext returns the pool assigned to the context.
|
||||
func FromContext(c context.Context) *Pool {
|
||||
return c.Value(reqkey).(*Pool)
|
||||
}
|
89
server/worker/pool/pool.go
Normal file
89
server/worker/pool/pool.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package pool
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/drone/drone/server/worker"
|
||||
)
|
||||
|
||||
// TODO (bradrydzewski) ability to cancel work.
|
||||
// TODO (bradrydzewski) ability to remove a worker.
|
||||
|
||||
type Pool struct {
|
||||
sync.Mutex
|
||||
workers map[worker.Worker]bool
|
||||
workerc chan worker.Worker
|
||||
}
|
||||
|
||||
func New() *Pool {
|
||||
return &Pool{
|
||||
workers: make(map[worker.Worker]bool),
|
||||
workerc: make(chan worker.Worker, 999),
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate allocates a worker to the pool to
|
||||
// be available to accept work.
|
||||
func (p *Pool) Allocate(w worker.Worker) bool {
|
||||
if p.IsAllocated(w) {
|
||||
return false
|
||||
}
|
||||
|
||||
p.Lock()
|
||||
p.workers[w] = true
|
||||
p.Unlock()
|
||||
|
||||
p.workerc <- w
|
||||
return true
|
||||
}
|
||||
|
||||
// IsAllocated is a helper function that returns
|
||||
// true if the worker is currently allocated to
|
||||
// the Pool.
|
||||
func (p *Pool) IsAllocated(w worker.Worker) bool {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
_, ok := p.workers[w]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Deallocate removes the worker from the pool of
|
||||
// available workers. If the worker is currently
|
||||
// reserved and performing work it will finish,
|
||||
// but no longer be given new work.
|
||||
func (p *Pool) Deallocate(w worker.Worker) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
delete(p.workers, w)
|
||||
}
|
||||
|
||||
// List returns a list of all Workers currently
|
||||
// allocated to the Pool.
|
||||
func (p *Pool) List() []worker.Worker {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
var workers []worker.Worker
|
||||
for w, _ := range p.workers {
|
||||
workers = append(workers, w)
|
||||
}
|
||||
return workers
|
||||
}
|
||||
|
||||
// Reserve reserves the next available worker to
|
||||
// start doing work. Once work is complete, the
|
||||
// worker should be released back to the pool.
|
||||
func (p *Pool) Reserve() <-chan worker.Worker {
|
||||
return p.workerc
|
||||
}
|
||||
|
||||
// Release releases the worker back to the pool
|
||||
// of available workers.
|
||||
func (p *Pool) Release(w worker.Worker) bool {
|
||||
if !p.IsAllocated(w) {
|
||||
return false
|
||||
}
|
||||
|
||||
p.workerc <- w
|
||||
return true
|
||||
}
|
103
server/worker/pool/pool_test.go
Normal file
103
server/worker/pool/pool_test.go
Normal file
|
@ -0,0 +1,103 @@
|
|||
package pool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.google.com/p/go.net/context"
|
||||
"github.com/drone/drone/server/worker"
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func TestPool(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Pool", func() {
|
||||
|
||||
g.It("Should allocate workers", func() {
|
||||
w := mockWorker{}
|
||||
pool := New()
|
||||
pool.Allocate(&w)
|
||||
g.Assert(len(pool.workers)).Equal(1)
|
||||
g.Assert(len(pool.workerc)).Equal(1)
|
||||
g.Assert(pool.workers[&w]).Equal(true)
|
||||
})
|
||||
|
||||
g.It("Should not re-allocate an allocated worker", func() {
|
||||
w := mockWorker{}
|
||||
pool := New()
|
||||
g.Assert(pool.Allocate(&w)).Equal(true)
|
||||
g.Assert(pool.Allocate(&w)).Equal(false)
|
||||
})
|
||||
|
||||
g.It("Should reserve a worker", func() {
|
||||
w := mockWorker{}
|
||||
pool := New()
|
||||
pool.Allocate(&w)
|
||||
g.Assert(<-pool.Reserve()).Equal(&w)
|
||||
})
|
||||
|
||||
g.It("Should release a worker", func() {
|
||||
w := mockWorker{}
|
||||
pool := New()
|
||||
pool.Allocate(&w)
|
||||
g.Assert(len(pool.workerc)).Equal(1)
|
||||
g.Assert(<-pool.Reserve()).Equal(&w)
|
||||
g.Assert(len(pool.workerc)).Equal(0)
|
||||
pool.Release(&w)
|
||||
g.Assert(len(pool.workerc)).Equal(1)
|
||||
g.Assert(<-pool.Reserve()).Equal(&w)
|
||||
g.Assert(len(pool.workerc)).Equal(0)
|
||||
})
|
||||
|
||||
g.It("Should not release an unallocated worker", func() {
|
||||
w := mockWorker{}
|
||||
pool := New()
|
||||
g.Assert(len(pool.workers)).Equal(0)
|
||||
g.Assert(len(pool.workerc)).Equal(0)
|
||||
pool.Release(&w)
|
||||
g.Assert(len(pool.workers)).Equal(0)
|
||||
g.Assert(len(pool.workerc)).Equal(0)
|
||||
pool.Release(nil)
|
||||
g.Assert(len(pool.workers)).Equal(0)
|
||||
g.Assert(len(pool.workerc)).Equal(0)
|
||||
})
|
||||
|
||||
g.It("Should list all allocated workers", func() {
|
||||
w1 := mockWorker{}
|
||||
w2 := mockWorker{}
|
||||
pool := New()
|
||||
pool.Allocate(&w1)
|
||||
pool.Allocate(&w2)
|
||||
g.Assert(len(pool.workers)).Equal(2)
|
||||
g.Assert(len(pool.workerc)).Equal(2)
|
||||
g.Assert(len(pool.List())).Equal(2)
|
||||
})
|
||||
|
||||
g.It("Should remove a worker", func() {
|
||||
w1 := mockWorker{}
|
||||
w2 := mockWorker{}
|
||||
pool := New()
|
||||
pool.Allocate(&w1)
|
||||
pool.Allocate(&w2)
|
||||
g.Assert(len(pool.workers)).Equal(2)
|
||||
pool.Deallocate(&w1)
|
||||
pool.Deallocate(&w2)
|
||||
g.Assert(len(pool.workers)).Equal(0)
|
||||
g.Assert(len(pool.List())).Equal(0)
|
||||
})
|
||||
|
||||
g.It("Should add / retrieve from context", func() {
|
||||
c := context.Background()
|
||||
p := New()
|
||||
c = NewContext(c, p)
|
||||
g.Assert(FromContext(c)).Equal(p)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// fake worker for testing purpose only
|
||||
type mockWorker struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (*mockWorker) Do(c context.Context, w *worker.Work) {}
|
14
server/worker/work.go
Normal file
14
server/worker/work.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package worker
|
||||
|
||||
import "github.com/drone/drone/shared/model"
|
||||
|
||||
type Work struct {
|
||||
User *model.User
|
||||
Repo *model.Repo
|
||||
Commit *model.Commit
|
||||
}
|
||||
|
||||
type Assignment struct {
|
||||
Work *Work
|
||||
Worker Worker
|
||||
}
|
|
@ -1,184 +1,15 @@
|
|||
package worker
|
||||
|
||||
import (
|
||||
"log"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/plugin/notify"
|
||||
"github.com/drone/drone/server/database"
|
||||
"github.com/drone/drone/server/pubsub"
|
||||
"github.com/drone/drone/shared/build"
|
||||
"github.com/drone/drone/shared/build/docker"
|
||||
"github.com/drone/drone/shared/build/git"
|
||||
"github.com/drone/drone/shared/build/repo"
|
||||
"github.com/drone/drone/shared/build/script"
|
||||
"github.com/drone/drone/shared/model"
|
||||
"code.google.com/p/go.net/context"
|
||||
)
|
||||
|
||||
type Worker interface {
|
||||
Start() // Start instructs the worker to start processing requests
|
||||
Stop() // Stop instructions the worker to stop processing requests
|
||||
Do(context.Context, *Work)
|
||||
}
|
||||
|
||||
type worker struct {
|
||||
users database.UserManager
|
||||
repos database.RepoManager
|
||||
commits database.CommitManager
|
||||
//config database.ConfigManager
|
||||
pubsub *pubsub.PubSub
|
||||
server *model.Server
|
||||
|
||||
request chan *model.Request
|
||||
dispatch chan chan *model.Request
|
||||
quit chan bool
|
||||
}
|
||||
|
||||
func NewWorker(dispatch chan chan *model.Request, users database.UserManager, repos database.RepoManager, commits database.CommitManager /*config database.ConfigManager,*/, pubsub *pubsub.PubSub, server *model.Server) Worker {
|
||||
return &worker{
|
||||
users: users,
|
||||
repos: repos,
|
||||
commits: commits,
|
||||
//config: config,
|
||||
pubsub: pubsub,
|
||||
server: server,
|
||||
dispatch: dispatch,
|
||||
request: make(chan *model.Request),
|
||||
quit: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
// Start tells the worker to start listening and
|
||||
// accepting new work requests.
|
||||
func (w *worker) Start() {
|
||||
go func() {
|
||||
for {
|
||||
// register our queue with the dispatch
|
||||
// queue to start accepting work.
|
||||
go func() { w.dispatch <- w.request }()
|
||||
|
||||
select {
|
||||
case r := <-w.request:
|
||||
// handle the request
|
||||
r.Server = w.server
|
||||
w.Execute(r)
|
||||
|
||||
case <-w.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop tells the worker to stop listening for new
|
||||
// work requests.
|
||||
func (w *worker) Stop() {
|
||||
go func() { w.quit <- true }()
|
||||
}
|
||||
|
||||
// Execute executes the work Request, persists the
|
||||
// results to the database, and sends event messages
|
||||
// to the pubsub (for websocket updates on the website).
|
||||
func (w *worker) Execute(r *model.Request) {
|
||||
// mark the build as Started and update the database
|
||||
r.Commit.Status = model.StatusStarted
|
||||
r.Commit.Started = time.Now().UTC().Unix()
|
||||
w.commits.Update(r.Commit)
|
||||
|
||||
// notify all listeners that the build is started
|
||||
commitc := w.pubsub.Register("_global")
|
||||
commitc.Publish(r)
|
||||
stdoutc := w.pubsub.RegisterOpts(r.Commit.ID, pubsub.ConsoleOpts)
|
||||
defer stdoutc.Close()
|
||||
|
||||
// create a special buffer that will also
|
||||
// write to a websocket channel
|
||||
buf := pubsub.NewBuffer(stdoutc)
|
||||
|
||||
// parse the parameters and build script. The script has already
|
||||
// been parsed in the hook, so we can be confident it will succeed.
|
||||
// that being said, we should clean this up
|
||||
params, err := r.Repo.ParamMap()
|
||||
if err != nil {
|
||||
log.Printf("Error parsing PARAMS for %s/%s, Err: %s", r.Repo.Owner, r.Repo.Name, err.Error())
|
||||
}
|
||||
script, err := script.ParseBuild(r.Commit.Config, params)
|
||||
if err != nil {
|
||||
log.Printf("Error parsing YAML for %s/%s, Err: %s", r.Repo.Owner, r.Repo.Name, err.Error())
|
||||
}
|
||||
|
||||
// append private parameters to the environment
|
||||
// variable section of the .drone.yml file, iff
|
||||
// this is not a pull request (for security purposes)
|
||||
if params != nil && (r.Repo.Private || len(r.Commit.PullRequest) == 0) {
|
||||
for k, v := range params {
|
||||
script.Env = append(script.Env, k+"="+v)
|
||||
}
|
||||
}
|
||||
|
||||
path := r.Repo.Host + "/" + r.Repo.Owner + "/" + r.Repo.Name
|
||||
repo := &repo.Repo{
|
||||
Name: path,
|
||||
Path: r.Repo.CloneURL,
|
||||
Branch: r.Commit.Branch,
|
||||
Commit: r.Commit.Sha,
|
||||
PR: r.Commit.PullRequest,
|
||||
Dir: filepath.Join("/var/cache/drone/src", git.GitPath(script.Git, path)),
|
||||
Depth: git.GitDepth(script.Git),
|
||||
}
|
||||
|
||||
// Instantiate a new Docker client
|
||||
var dockerClient *docker.Client
|
||||
switch {
|
||||
case len(w.server.Host) == 0:
|
||||
dockerClient = docker.New()
|
||||
default:
|
||||
dockerClient = docker.NewHost(w.server.Host)
|
||||
}
|
||||
|
||||
// send all "started" notifications
|
||||
if script.Notifications == nil {
|
||||
script.Notifications = ¬ify.Notification{}
|
||||
}
|
||||
script.Notifications.Send(r)
|
||||
|
||||
// create an instance of the Docker builder
|
||||
builder := build.New(dockerClient)
|
||||
builder.Build = script
|
||||
builder.Repo = repo
|
||||
builder.Stdout = buf
|
||||
builder.Key = []byte(r.Repo.PrivateKey)
|
||||
builder.Timeout = time.Duration(r.Repo.Timeout) * time.Second
|
||||
builder.Privileged = r.Repo.Privileged
|
||||
|
||||
// run the build
|
||||
err = builder.Run()
|
||||
|
||||
// update the build status based on the results
|
||||
// from the build runner.
|
||||
switch {
|
||||
case err != nil:
|
||||
r.Commit.Status = model.StatusError
|
||||
log.Printf("Error building %s, Err: %s", r.Commit.Sha, err)
|
||||
buf.WriteString(err.Error())
|
||||
case builder.BuildState == nil:
|
||||
r.Commit.Status = model.StatusFailure
|
||||
case builder.BuildState.ExitCode != 0:
|
||||
r.Commit.Status = model.StatusFailure
|
||||
default:
|
||||
r.Commit.Status = model.StatusSuccess
|
||||
}
|
||||
|
||||
// calcualte the build finished and duration details and
|
||||
// update the commit
|
||||
r.Commit.Finished = time.Now().UTC().Unix()
|
||||
r.Commit.Duration = (r.Commit.Finished - r.Commit.Started)
|
||||
w.commits.Update(r.Commit)
|
||||
w.commits.UpdateOutput(r.Commit, buf.Bytes())
|
||||
|
||||
// notify all listeners that the build is finished
|
||||
commitc.Publish(r)
|
||||
|
||||
// send all "finished" notifications
|
||||
script.Notifications.Send(r)
|
||||
// Do retrieves a worker from the session and uses
|
||||
// it to get work done.
|
||||
func Do(c context.Context, w *Work) {
|
||||
FromContext(c).Do(c, w)
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@ package httputil
|
|||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.google.com/p/xsrftoken"
|
||||
)
|
||||
|
||||
// IsHttps is a helper function that evaluates the http.Request
|
||||
|
@ -105,26 +103,3 @@ func DelCookie(w http.ResponseWriter, r *http.Request, name string) {
|
|||
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
|
||||
// SetXsrf writes the cookie value.
|
||||
func SetXsrf(w http.ResponseWriter, r *http.Request, token, login string) {
|
||||
cookie := http.Cookie{
|
||||
Name: "XSRF-TOKEN",
|
||||
Value: xsrftoken.Generate(token, login, "/"),
|
||||
Path: "/",
|
||||
Domain: r.URL.Host,
|
||||
HttpOnly: false,
|
||||
Secure: IsHttps(r),
|
||||
}
|
||||
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
|
||||
// CheckXsrf verifies the xsrf value.
|
||||
func CheckXsrf(r *http.Request, token, login string) bool {
|
||||
if r.Method == "GET" {
|
||||
return true
|
||||
}
|
||||
return xsrftoken.Valid(
|
||||
r.Header.Get("X-XSRF-TOKEN"), token, login, "/")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue