You've already forked woodpecker
							
							
				mirror of
				https://github.com/woodpecker-ci/woodpecker.git
				synced 2025-10-30 23:27:39 +02:00 
			
		
		
		
	added new handlers and workers
This commit is contained in:
		
							
								
								
									
										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{} | ||||
| 		w.WriteHeader(http.StatusNotFound) | ||||
| 		return | ||||
| 	} | ||||
| 	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) | ||||
| } | ||||
|  | ||||
| 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. | ||||
| // GET /login/:host | ||||
| func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error { | ||||
| 	var host = r.FormValue(":host") | ||||
| // 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 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) | ||||
| 	) | ||||
|  | ||||
| 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{} | ||||
| 	// 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 | ||||
| 	} | ||||
|  | ||||
| 	// 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 | ||||
|  | ||||
| 	// 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{} | ||||
| 	if err := datastore.PutRepo(ctx, repo); err != nil { | ||||
| 		w.WriteHeader(http.StatusInternalServerError) | ||||
| 		return | ||||
| 	} | ||||
| 	w.WriteHeader(http.StatusNoContent) | ||||
| } | ||||
|  | ||||
| 	// 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. | ||||
| // 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{} | ||||
| // GetUserCurrent accepts a request to retrieve the | ||||
| // currently authenticated user from the datastore | ||||
| // and return in JSON format. | ||||
| // | ||||
| //     GET /api/user | ||||
| // | ||||
| 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. | ||||
| // 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{} | ||||
| // PutUser accepts a request to update the currently | ||||
| // authenticated User profile. | ||||
| // | ||||
| //     PUT /api/user | ||||
| // | ||||
| 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. | ||||
| // 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{} | ||||
| // 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 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. | ||||
| // 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{} | ||||
| // 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 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. | ||||
| // 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) | ||||
| 	switch { | ||||
| 	case user == nil: | ||||
| 		return notAuthorized{} | ||||
| 	case user.Admin == false: | ||||
| 		return forbidden{} | ||||
| 	} | ||||
| 	// get all users | ||||
| 	users, err := h.users.List() | ||||
| 	if err != nil { | ||||
| 		return internalServerError{err} | ||||
| 	} | ||||
|  | ||||
| 	return json.NewEncoder(w).Encode(users) | ||||
| } | ||||
|  | ||||
| // GetUser gets a user by hostname and login. | ||||
| // 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) | ||||
| 	switch { | ||||
| 	case user == nil: | ||||
| 		return notAuthorized{} | ||||
| 	case user.Admin == false: | ||||
| 		return forbidden{} | ||||
| 	} | ||||
| 	user, err := h.users.FindLogin(remote, login) | ||||
| 	if err != nil { | ||||
| 		return notFound{err} | ||||
| 	} | ||||
|  | ||||
| 	return json.NewEncoder(w).Encode(user) | ||||
| } | ||||
|  | ||||
| // PostUser registers a new user account. | ||||
| // 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) | ||||
| 	switch { | ||||
| 	case user == nil: | ||||
| 		return notAuthorized{} | ||||
| 	case user.Admin == false: | ||||
| 		return forbidden{} | ||||
| 	} | ||||
|  | ||||
| 	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. | ||||
| // GetUsers accepts a request to retrieve all users | ||||
| // from the datastore and return encoded in JSON format. | ||||
| // | ||||
| // 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) | ||||
| //     GET /api/users | ||||
| // | ||||
| 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 | ||||
| 	} | ||||
| 	account, err := h.users.FindLogin(remote, login) | ||||
| 	users, err := datastore.GetUserList(ctx) | ||||
| 	if err != nil { | ||||
| 		return notFound{err} | ||||
| 		w.WriteHeader(http.StatusInternalServerError) | ||||
| 		return | ||||
| 	} | ||||
| 	json.NewEncoder(w).Encode(users) | ||||
| } | ||||
|  | ||||
| 	// user cannot delete his / her own account | ||||
| // 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 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: | ||||
| 		w.WriteHeader(http.StatusUnauthorized) | ||||
| 		return | ||||
| 	case user.Admin == false: | ||||
| 		w.WriteHeader(http.StatusForbidden) | ||||
| 		return | ||||
| 	} | ||||
| 	user, err := datastore.GetUserLogin(ctx, host, login) | ||||
| 	if err != nil { | ||||
| 		w.WriteHeader(http.StatusNotFound) | ||||
| 		return | ||||
| 	} | ||||
| 	json.NewEncoder(w).Encode(user) | ||||
| } | ||||
|  | ||||
| // 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 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: | ||||
| 		w.WriteHeader(http.StatusUnauthorized) | ||||
| 		return | ||||
| 	case user.Admin == false: | ||||
| 		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) | ||||
| } | ||||
|  | ||||
| // 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 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: | ||||
| 		w.WriteHeader(http.StatusUnauthorized) | ||||
| 		return | ||||
| 	case user.Admin == false: | ||||
| 		w.WriteHeader(http.StatusForbidden) | ||||
| 		return | ||||
| 	} | ||||
| 	account, err := datastore.GetUserLogin(ctx, host, login) | ||||
| 	if err != nil { | ||||
| 		w.WriteHeader(http.StatusNotFound) | ||||
| 		return | ||||
| 	} | ||||
| 	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)) | ||||
| } | ||||
|   | ||||
							
								
								
									
										177
									
								
								server/main.go
									
									
									
									
									
								
							
							
						
						
									
										177
									
								
								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,86 +87,91 @@ 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() | ||||
| 	} else { | ||||
| 		for _, node := range nodes { | ||||
| 			println(node) | ||||
| 			worker.NewWorker(workerc, users, repos, commits, pubsub, &model.Server{Host: node}).Start() | ||||
| 	/* | ||||
| 		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() | ||||
| 		} else { | ||||
| 			for _, node := range nodes { | ||||
| 				println(node) | ||||
| 				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) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 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, "/") | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user