integrated plugin model for remotes

This commit is contained in:
Brad Rydzewski 2014-07-12 19:01:58 -07:00
parent 91f7a63d21
commit aa9a8f4878
30 changed files with 552 additions and 297 deletions

View file

@ -0,0 +1,20 @@
package bitbucket
import (
"github.com/drone/drone/plugin/remote"
"github.com/drone/drone/shared/model"
)
func init() {
remote.Register(model.RemoteBitbucket, plugin)
}
func plugin(remote *model.Remote) remote.Remote {
return &Bitbucket{
URL: remote.URL,
API: remote.API,
Client: remote.Client,
Secret: remote.Secret,
Enabled: remote.Open,
}
}

View file

@ -0,0 +1,21 @@
package github
import (
"github.com/drone/drone/plugin/remote"
"github.com/drone/drone/shared/model"
)
func init() {
remote.Register(model.RemoteGithub, plugin)
remote.Register(model.RemoteGithubEnterprise, plugin)
}
func plugin(remote *model.Remote) remote.Remote {
return &Github{
URL: remote.URL,
API: remote.API,
Client: remote.Client,
Secret: remote.Secret,
Enabled: remote.Open,
}
}

View file

@ -0,0 +1,17 @@
package gitlab
import (
"github.com/drone/drone/plugin/remote"
"github.com/drone/drone/shared/model"
)
func init() {
remote.Register(model.RemoteGitlab, plugin)
}
func plugin(remote *model.Remote) remote.Remote {
return &Gitlab{
URL: remote.URL,
Enabled: remote.Open,
}
}

View file

@ -2,8 +2,27 @@ package remote
import (
"net/http"
"github.com/drone/drone/shared/model"
)
// Defines a model for integrating (or pluggin in) remote version
// control systems, such as GitHub and Bitbucket.
type Plugin func(*model.Remote) Remote
var plugins = map[string]Plugin{}
// Register registers a new plugin.
func Register(name string, plugin Plugin) {
plugins[name] = plugin
}
// Lookup retrieves the plugin for the remote.
func Lookup(name string) (Plugin, bool) {
plugin, ok := plugins[name]
return plugin, ok
}
type Remote interface {
// GetName returns the name of this remote system.
GetName() string

View file

@ -42,11 +42,13 @@
<script src="/scripts/controllers/setup.js"></script>
<script src="/scripts/controllers/sync.js"></script>
<script src="/scripts/controllers/main.js"></script>
<script src="/scripts/controllers/login.js"></script>
<script src="/scripts/services/auth.js"></script>
<script src="/scripts/services/conf.js"></script>
<script src="/scripts/services/repo.js"></script>
<script src="/scripts/services/user.js"></script>
<script src="/scripts/services/feed.js"></script>
<script src="/scripts/services/remote.js"></script>
<script src="/scripts/services/notify.js"></script>
<script src="/scripts/services/stdout.js"></script>
<script src="/scripts/filters/filters.js"></script>

View file

@ -23,6 +23,7 @@ app.config(['$routeProvider', '$locationProvider', function($routeProvider, $loc
})
.when('/login', {
templateUrl: '/views/login.html',
controller: 'LoginController',
title: 'Login',
})
.when('/setup', {

View file

@ -0,0 +1,13 @@
'use strict';
angular.module('app').controller("LoginController", function($scope, $http, remotes) {
$scope.state=0
$scope.user = remotes.getLogins().success(function (data) {
$scope.remotes = (typeof data==="string")?[]:data;
$scope.state = 1;
})
.error(function (error) {
$scope.remotes = [];
$scope.state = 1;
});
});

View file

@ -1,14 +1,18 @@
'use strict';
angular.module('app').controller("SetupController", function($scope, $http, $routeParams) {
angular.module('app').controller("SetupController", function($scope, $http, $routeParams, $window) {
// create a remote that will be populated
// and persisted to the database.
$scope.remote = {};
$scope.remote.type = $routeParams.remote;
$scope.remote.register = true;
$scope.remote.register = false;
$scope.window = $window
// pre-populate the form if the remote
// type is selected and is a cloud service
// with a known URL and standard configuration.
switch($scope.remote.type) {
case undefined:
case 'github.com':
$scope.remote.type = "github.com"
$scope.remote.url = "https://github.com";
@ -26,7 +30,7 @@ angular.module('app').controller("SetupController", function($scope, $http, $rou
success(function(data, status, headers, config) {
delete $scope.failure;
$scope.remote = data;
console.log('success', $scope.remote);
$window.location.href="/login/"+data.type;
}).
error(function(data, status, headers, config) {
$scope.failure = data;

View file

@ -64,6 +64,31 @@ angular.module('app').filter('badgeMarkup', function() {
}
});
angular.module('app').filter('remoteName', function() {
return function(name) {
switch (name) {
case 'gitlab.com' : return 'GitLab';
case 'github.com' : return 'GitHub';
case 'enterprise.github.com' : return 'GitHub Enterprise';
case 'bitbucket.org' : return 'Bitbucket';
case 'stash.atlassian.com' : return 'Atlassian Stash';
}
}
});
angular.module('app').filter('remoteIcon', function() {
return function(name) {
switch (name) {
case 'gitlab.com' : return 'fa-git-square';
case 'github.com' : return 'fa-github-square';
case 'enterprise.github.com' : return 'fa-github-square';
case 'bitbucket.org' : return 'fa-bitbucket-square';
case 'stash.atlassian.com' : return 'fa-bitbucket-square';
}
}
});
angular.module('app').filter('unique', function() {
return function(input, key) {
var unique = {};

View file

@ -0,0 +1,13 @@
'use strict';
// Service facilitates interaction with the remote API.
angular.module('app').service('remotes', ['$http', function($http) {
this.get = function() {
return $http.get('/v1/remotes');
};
this.getLogins = function() {
return $http.get('/v1/logins');
};
}]);

View file

@ -650,6 +650,7 @@ nav div.options .pure-button i {
-o-box-sizing: border-box;
box-sizing: border-box;
}
#setuppage .pure-g,
#loginpage .pure-g {
padding: 30px;
border: 1px solid #DDD;
@ -662,6 +663,7 @@ nav div.options .pure-button i {
-o-box-sizing: border-box;
box-sizing: border-box;
}
#setuppage .pure-g a,
#loginpage .pure-g a {
display: block;
background: #45494b;
@ -671,9 +673,11 @@ nav div.options .pure-button i {
border-radius: 5px;
text-decoration: none;
}
#setuppage .pure-g a:hover,
#loginpage .pure-g a:hover {
background: #262626;
}
#setuppage .pure-g [class*="fa-"],
#loginpage .pure-g [class*="fa-"] {
float: left;
font-size: 20px;
@ -684,12 +688,74 @@ nav div.options .pure-button i {
min-width: 27px;
min-height: 20px;
}
#setuppage .pure-g .pure-u-1 a,
#loginpage .pure-g .pure-u-1 a {
margin-bottom: 10px;
}
#setuppage .pure-g .pure-u-1:last-child a,
#loginpage .pure-g .pure-u-1:last-child a {
margin-bottom: 0px;
}
#setuppage2 {
margin-bottom: 50px;
}
#setuppage2 section {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
}
#setuppage2 section .pure-g {
padding: 30px;
border: 1px solid #DDD;
max-width: 400px;
margin: 0px auto;
margin-top: 50px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
}
#setuppage2 section label {
display: inline-block;
}
#setuppage2 section input[type='text'] {
margin-top: 5px;
margin-bottom: 10px;
box-shadow: none;
width: 100%;
}
#setuppage2 section .pure-button-primary {
color: #FFF;
background: #4ab1ce;
padding: 10px 20px;
margin-top: 20px;
width: 100%;
}
#setuppage2 section .tip h2 {
font-size: 16px;
margin-bottom: 20px;
}
#setuppage2 section .tip dd {
font-weight: bold;
color: #666;
margin-top: 15px;
margin-bottom: 5px;
}
#setuppage2 section .tip dt {
padding: .5em .6em;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
}
#syncpage {
width: 100%;
}

View file

@ -579,6 +579,7 @@ nav {
}
}
#setuppage,
#loginpage {
.pure-g {
padding: 30px;
@ -622,6 +623,57 @@ nav {
}
}
#setuppage2 {
margin-bottom:50px;
section {
.border_box;
.pure-g {
padding: 30px;
border: 1px solid #DDD;
max-width:400px;
margin:0px auto;
margin-top:50px;
.border_box;
}
label {
display:inline-block;
}
input[type='text'] {
margin-top:5px;
margin-bottom:10px;
box-shadow:none;
width:100%;
}
.pure-button-primary {
color:#FFF;
background: @link2;
padding:10px 20px;
margin-top:20px;
width:100%;
}
.tip {
h2 {
font-size: 16px;
margin-bottom: 20px;
}
dd {
font-weight:bold;
color:#666;
margin-top:15px;
margin-bottom:5px;
}
dt {
padding: .5em .6em;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
.border_box;
width: 100%;
}
}
}
}
#syncpage {
width:100%;
section {

File diff suppressed because one or more lines are too long

View file

@ -1,28 +1,14 @@
<article id="loginpage">
<div class="pure-g">
<div class="pure-u-1">
<a href="/login/github.com" target="_self">
<i class="fa fa fa-github-square"></i> GitHub
<div class="pure-u-1" ng-if="state == 1 && remotes.length != 0" ng-repeat="remote in remotes">
<a href="/login/{{ remote.type }}" target="_self">
<i class="fa {{ remote.type | remoteIcon }}"></i> {{ remote.type | remoteName }}
</a>
</div>
<div class="pure-u-1">
<a href="/login/enterprise.github.com" target="_self">
<i class="fa fa fa-github-square"></i> GitHub Enterprise
</a>
</div>
<div class="pure-u-1">
<a href="/login/bitbucket.org" target="_self">
<i class="fa fa fa-bitbucket-square"></i> Bitbucket
</a>
</div>
<div class="pure-u-1">
<a href="/login/stash.atlassian.com" target="_self">
<i class="fa fa fa-bitbucket-square"></i> Stash
</a>
</div>
<div class="pure-u-1">
<a href="/login/gitlab.com" target="_self">
<i class="fa fa fa-sign-in"></i> Gitlab
<div class="pure-u-1" ng-if="state == 1 && remotes.length == 0">
<a href="/setup">
<i class="fa fa-rocket"></i> Launch Setup Wizard
</a>
</div>
</div>

View file

@ -1,57 +1,105 @@
<h1>First Time Setup</h1>
<article id="setuppage" ng-if="remote.type == undefined">
<nav>
<a href="#"><span class="fa fa-th"></span></a>
<a href="#">Choose a Remote System</a>
</nav>
<div class="row">
<div class="col-xs-6 col-sm-3">
<table border="1">
<tr>
<td><a href="/setup/github.com">GitHub</a></td>
<td><a href="/setup/enterprise.github.com">GitHub Enterprise</a></td>
<td><a href="/setup/gitlab.com">Gitlab</a></td>
<td><a href="/setup/bitbucket.org">Bitbucket</a></td>
<td><a href="/setup/stash.atlassian.com">Stash</a></td>
</tr>
</table>
</div>
<!-- primary column -->
<div class="col-xs-12 col-sm-9">
<div>
<label>Registration</label>
<input type="radio" ng-model="remote.register" ng-value="true" />
<input type="radio" ng-model="remote.register" ng-value="false" />
</div>
<div>
<label>URL</label>
<div ng-switch="remote.type">
<input ng-switch-when="github.com" ng-model="remote.url" type="text" readonly />
<input ng-switch-when="bitbucket.org" ng-model="remote.url" type="text" readonly />
<input ng-switch-default ng-model="remote.url" type="text" />
<section>
<div class="pure-g">
<div class="pure-u-1">
<a href="/setup/github.com">
<i class="fa fa-github-square"></i> GitHub
</a>
</div>
<div class="pure-u-1">
<a href="/setup/enterprise.github.com">
<i class="fa fa-github-square"></i> GitHub Enterprise
</a>
</div>
<div class="pure-u-1">
<a href="/setup/bitbucket.org">
<i class="fa fa-bitbucket-square"></i> Bitbucket
</a>
</div>
<div class="pure-u-1">
<a href="/setup/stash.atlassian.com">
<i class="fa fa-bitbucket-square"></i> Atlassian Stash
</a>
</div>
<div class="pure-u-1">
<a href="/setup/gitlab.com">
<i class="fa fa-git-square"></i> GitLab
</a>
</div>
</div>
</section>
</article>
<div ng-if="remote.type != 'gitlab.com'">
<label>API</label>
<div ng-switch="remote.type">
<input ng-switch-when="github.com" ng-model="remote.api" type="text" readonly />
<input ng-switch-when="bitbucket.org" ng-model="remote.api" type="text" readonly />
<input ng-switch-default ng-model="remote.api" type="text" />
<article ng-if="remote.type != undefined" id="setuppage2">
<nav>
<a href="/setup">
<span class="fa fa-arrow-left"></span>
</a>
<a href="/setup">Configure {{ remote.type | remoteName }}</a>
</nav>
<section ng-if-"remote.type == 'github.com' || remote.type == 'enterprise.github.com' ">
<div class="pure-g">
<div class="pure-u-1 tip">
<h2>Register with {{ remote.type | remoteName }}</h2>
<dl>
<dd>Homepage URL</dd>
<dt>{{ window.location.protocol }}//{{ window.location.host }}</dt>
</dl>
<dl>
<dd>Authorization callback URL</dd>
<dt>{{ window.location.protocol }}//{{ window.location.host }}/login/{{ remote.type }}</dt>
</dl>
</div>
</div>
</section>
<div ng-if="remote.type != 'gitlab.com'">
<label>Client</label>
<input type="text" ng-model="remote.client" />
</div>
<section>
<div class="pure-g">
<div class="pure-u-1">
<div class="pure-form">
<div ng-if="remote.type != 'github.com' && remote.type != 'bitbucket.org' ">
<label>URL</label>
<div ng-switch="remote.type">
<input ng-switch-default ng-model="remote.url" type="text" placeholder="https://www.foo.com" />
</div>
</div>
<div ng-if="remote.type != 'gitlab.com'">
<label>Secret</label>
<input type="text" ng-model="remote.secret" />
</div>
<div ng-if="remote.type != 'github.com' && remote.type != 'bitbucket.org' ">
<label>API URL</label>
<div ng-switch="remote.type">
<input ng-switch-default ng-model="remote.api" type="text" placeholder="https://www.foo.com/api" />
</div>
</div>
<div>
<button ng-click="save()">Save</button>
<div ng-if="remote.type != 'gitlab.com'">
<label>OAuth Client</label>
<div>
<input type="text" ng-model="remote.client" />
</div>
</div>
<div ng-if="remote.type != 'gitlab.com'">
<label>OAuth Secret</label>
<div>
<input type="text" ng-model="remote.secret" />
</div>
</div>
<div class="toggle">
<input type="checkbox" ng-model="remote.register" id="register" />
<label for="register"></label>
<span>Enable Self-Registration</span>
</div>
<button ng-click="save()" class="pure-button pure-button-primary">Save and Login</button>
</div>
</div>
</div>
</div>
</div>
</section>
</article>

View file

@ -1,5 +1,6 @@
package database
/*
import (
"github.com/BurntSushi/toml"
"github.com/drone/drone/shared/model"
@ -33,3 +34,4 @@ func NewConfigManager(filename string) ConfigManager {
func (c *configManager) Find() *model.Config {
return c.conf
}
*/

View file

@ -14,6 +14,9 @@ type RemoteManager interface {
// FindHost finds the Remote by hostname.
FindHost(name string) (*model.Remote, error)
// FindHost finds the Remote by type.
FindType(t string) (*model.Remote, error)
// List finds all registered Remotes of the system.
List() ([]*model.Remote, error)
@ -40,10 +43,19 @@ WHERE remote_host=?
LIMIT 1
`
// SQL query to retrieve a Remote by remote login.
const findRemoteTypeQuery = `
SELECT *
FROM remotes
WHERE remote_type=?
LIMIT 1
`
// SQL query to retrieve a list of all Remotes.
const listRemoteQuery = `
SELECT *
FROM remotes
ORDER BY remote_type
`
// SQL statement to delete a Remote by ID.
@ -69,6 +81,12 @@ func (db *remoteManager) FindHost(host string) (*model.Remote, error) {
return &dst, err
}
func (db *remoteManager) FindType(t string) (*model.Remote, error) {
dst := model.Remote{}
err := meddler.QueryRow(db, &dst, findRemoteTypeQuery, t)
return &dst, err
}
func (db *remoteManager) List() ([]*model.Remote, error) {
var dst []*model.Remote
err := meddler.QueryAll(db, &dst, listRemoteQuery)

View file

@ -6,7 +6,7 @@ import (
"github.com/drone/drone/server/database"
"github.com/drone/drone/server/session"
"github.com/drone/drone/server/worker"
"github.com/drone/drone/shared/httputil"
"github.com/drone/drone/shared/model"
"github.com/gorilla/pat"
)
@ -16,10 +16,10 @@ type CommitHandler struct {
repos database.RepoManager
commits database.CommitManager
sess session.Session
queue chan *worker.Request
queue chan *model.Request
}
func NewCommitHandler(repos database.RepoManager, commits database.CommitManager, perms database.PermManager, sess session.Session, queue chan *worker.Request) *CommitHandler {
func NewCommitHandler(repos database.RepoManager, commits database.CommitManager, perms database.PermManager, sess session.Session, queue chan *model.Request) *CommitHandler {
return &CommitHandler{perms, repos, commits, sess, queue}
}
@ -160,7 +160,8 @@ func (h *CommitHandler) PostCommit(w http.ResponseWriter, r *http.Request) error
// drop the items on the queue
// drop the items on the queue
go func() {
h.queue <- &worker.Request{
h.queue <- &model.Request{
Host: httputil.GetURL(r),
Repo: repo,
Commit: c,
}

View file

@ -1,35 +0,0 @@
package handler
import (
"encoding/json"
"net/http"
"github.com/drone/drone/server/database"
"github.com/drone/drone/server/session"
"github.com/gorilla/pat"
)
type ConfigHandler struct {
conf database.ConfigManager
sess session.Session
}
func NewConfigHandler(conf database.ConfigManager, sess session.Session) *ConfigHandler {
return &ConfigHandler{conf, sess}
}
// GetConfig gets the system configuration details.
// GET /api/config
func (h *ConfigHandler) GetConfig(w http.ResponseWriter, r *http.Request) error {
// get the user form the session
user := h.sess.User(r)
if user == nil || !user.Admin {
return notAuthorized{}
}
return json.NewEncoder(w).Encode(h.conf.Find())
}
func (h *ConfigHandler) Register(r *pat.Router) {
r.Get("/v1/config", errorHandler(h.GetConfig))
}

View file

@ -3,8 +3,9 @@ package handler
import (
"net/http"
"github.com/drone/drone/plugin/remote"
"github.com/drone/drone/server/database"
"github.com/drone/drone/server/worker"
"github.com/drone/drone/shared/httputil"
"github.com/drone/drone/shared/model"
"github.com/gorilla/pat"
)
@ -13,12 +14,12 @@ type HookHandler struct {
users database.UserManager
repos database.RepoManager
commits database.CommitManager
conf database.ConfigManager
queue chan *worker.Request
remotes database.RemoteManager
queue chan *model.Request
}
func NewHookHandler(users database.UserManager, repos database.RepoManager, commits database.CommitManager, conf database.ConfigManager, queue chan *worker.Request) *HookHandler {
return &HookHandler{users, repos, commits, conf, queue}
func NewHookHandler(users database.UserManager, repos database.RepoManager, commits database.CommitManager, remotes database.RemoteManager, queue chan *model.Request) *HookHandler {
return &HookHandler{users, repos, commits, remotes, queue}
}
// PostHook receives a post-commit hook from GitHub, Bitbucket, etc
@ -26,14 +27,21 @@ func NewHookHandler(users database.UserManager, repos database.RepoManager, comm
func (h *HookHandler) PostHook(w http.ResponseWriter, r *http.Request) error {
host := r.FormValue(":host")
// get the remote system's client.
remote := h.conf.Find().GetRemote(host)
if remote == nil {
remoteServer, err := h.remotes.FindType(host)
if err != nil {
return notFound{err}
}
remotePlugin, ok := remote.Lookup(remoteServer.Type)
if !ok {
return notFound{}
}
// get the remote system's client.
plugin := remotePlugin(remoteServer)
// parse the hook payload
hook, err := remote.GetHook(r)
hook, err := plugin.GetHook(r)
if err != nil {
return badRequest{err}
}
@ -47,7 +55,7 @@ func (h *HookHandler) PostHook(w http.ResponseWriter, r *http.Request) error {
}
// fetch the repository from the database
repo, err := h.repos.FindName(remote.GetHost(), hook.Owner, hook.Repo)
repo, err := h.repos.FindName(plugin.GetHost(), hook.Owner, hook.Repo)
if err != nil {
return notFound{}
}
@ -66,7 +74,7 @@ func (h *HookHandler) PostHook(w http.ResponseWriter, r *http.Request) error {
}
// featch the .drone.yml file from the database
client := remote.GetClient(user.Access, user.Secret)
client := plugin.GetClient(user.Access, user.Secret)
yml, err := client.GetScript(hook)
if err != nil {
return badRequest{err}
@ -91,7 +99,8 @@ func (h *HookHandler) PostHook(w http.ResponseWriter, r *http.Request) error {
// drop the items on the queue
go func() {
h.queue <- &worker.Request{
h.queue <- &model.Request{
Host: httputil.GetURL(r),
Repo: repo,
Commit: &c,
}

View file

@ -5,6 +5,7 @@ import (
"net/http"
"time"
"github.com/drone/drone/plugin/remote"
"github.com/drone/drone/server/database"
"github.com/drone/drone/server/session"
"github.com/drone/drone/shared/model"
@ -15,12 +16,13 @@ type LoginHandler struct {
users database.UserManager
repos database.RepoManager
perms database.PermManager
conf database.ConfigManager
sess session.Session
//conf database.ConfigManager
sess session.Session
remotes database.RemoteManager
}
func NewLoginHandler(users database.UserManager, repos database.RepoManager, perms database.PermManager, sess session.Session, conf database.ConfigManager) *LoginHandler {
return &LoginHandler{users, repos, perms, conf, sess}
func NewLoginHandler(users database.UserManager, repos database.RepoManager, perms database.PermManager, sess session.Session /*conf database.ConfigManager,*/, remotes database.RemoteManager) *LoginHandler {
return &LoginHandler{users, repos, perms /*conf,*/, sess, remotes}
}
// GetLogin gets the login to the 3rd party remote system.
@ -29,14 +31,21 @@ func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
host := r.FormValue(":host")
redirect := "/"
// get the remote system's client.
remote := h.conf.Find().GetRemote(host)
if remote == nil {
remoteServer, err := h.remotes.FindType(host)
if err != nil {
return notFound{err}
}
remotePlugin, ok := remote.Lookup(remoteServer.Type)
if !ok {
return notFound{}
}
// get the remote system's client.
plugin := remotePlugin(remoteServer)
// authenticate the user
login, err := remote.GetLogin(w, r)
login, err := plugin.GetLogin(w, r)
if err != nil {
return badRequest{err}
} else if login == nil {
@ -51,12 +60,12 @@ func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
// 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.conf.Find().Registration == false && h.users.Exist() {
if remoteServer.Open == false && h.users.Exist() {
return notAuthorized{}
}
// create the user account
u = model.NewUser(remote.GetName(), login.Login, login.Email)
u = model.NewUser(plugin.GetName(), login.Login, login.Email)
u.Name = login.Name
u.SetEmail(login.Email)
@ -102,7 +111,7 @@ func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
// its own package / sync utility.
go func() {
// list all repositories
client := remote.GetClient(u.Access, u.Secret)
client := plugin.GetClient(u.Access, u.Secret)
repos, err := client.GetRepos("")
if err != nil {
log.Println("Error syncing user account, listing repositories", u.Login, err)
@ -111,7 +120,7 @@ func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
// insert all repositories
for _, remoteRepo := range repos {
repo, _ := model.NewRepo(remote.GetName(), remoteRepo.Owner, remoteRepo.Name)
repo, _ := model.NewRepo(plugin.GetName(), remoteRepo.Owner, remoteRepo.Name)
repo.Private = remoteRepo.Private
repo.Host = remoteRepo.Host
repo.CloneURL = remoteRepo.Clone
@ -131,13 +140,13 @@ func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
}
log.Println("Successfully syced repo.", u.Login+"/"+remoteRepo.Name)
}
u.Synced = time.Now().Unix()
u.Syncing = false
if err := h.users.Update(u); err != nil {
log.Println("Error syncing user account, updating sync date", u.Login, err)
return
}
u.Synced = time.Now().UTC().Unix()
u.Syncing = false
if err := h.users.Update(u); err != nil {
log.Println("Error syncing user account, updating sync date", u.Login, err)
return
}
}()
}

View file

@ -38,6 +38,23 @@ func (h *RemoteHandler) GetRemotes(w http.ResponseWriter, r *http.Request) error
return json.NewEncoder(w).Encode(remotes)
}
// GetRemoteLogins gets all remote logins.
// GET /api/remotes/logins
func (h *RemoteHandler) GetRemoteLogins(w http.ResponseWriter, r *http.Request) error {
remotes, err := h.remotes.List()
if err != nil {
return internalServerError{err}
}
var logins []interface{}
for _, remote := range remotes {
logins = append(logins, struct {
Type string `json:"type"`
Host string `json:"host"`
}{remote.Type, remote.Host})
}
return json.NewEncoder(w).Encode(&logins)
}
// PostRemote creates a new remote.
// POST /api/remotes
func (h *RemoteHandler) PostRemote(w http.ResponseWriter, r *http.Request) error {
@ -57,7 +74,6 @@ func (h *RemoteHandler) PostRemote(w http.ResponseWriter, r *http.Request) error
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
return badRequest{err}
}
uri, err := url.Parse(in.URL)
if err != nil {
return badRequest{err}
@ -65,7 +81,8 @@ func (h *RemoteHandler) PostRemote(w http.ResponseWriter, r *http.Request) error
in.Host = uri.Host
// there is an edge case where, during installation, a user could attempt
// to add the same result multiple times.
// to add the same result multiple times. In this case we will delete
// the old remote prior to adding the new one.
if remote, err := h.remotes.FindHost(in.Host); err == nil && h.users.Exist() {
h.remotes.Delete(remote)
}
@ -78,31 +95,50 @@ func (h *RemoteHandler) PostRemote(w http.ResponseWriter, r *http.Request) error
return json.NewEncoder(w).Encode(&in)
}
// DeleteRemote delete the remote.
// GET /api/remotes/:name
func (h *RemoteHandler) DeleteRemote(w http.ResponseWriter, r *http.Request) error {
host := r.FormValue(":host")
// PutRemote updates an existing remote.
// PUT /api/remotes
func (h *RemoteHandler) PutRemote(w http.ResponseWriter, r *http.Request) error {
// get the user form the session
user := h.sess.User(r)
if user == nil || !user.Admin {
return notAuthorized{}
}
// get the remote
remote, err := h.remotes.FindHost(host)
// unmarshal the remote from the payload
defer r.Body.Close()
in := model.Remote{}
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
return badRequest{err}
}
uri, err := url.Parse(in.URL)
if err != nil {
return badRequest{err}
}
in.Host = uri.Host
// retrieve the remote and return an error if not exists
remote, err := h.remotes.FindHost(in.Host)
if err != nil {
return notFound{err}
}
if err := h.remotes.Delete(remote); err != nil {
// update the remote details
remote.API = in.API
remote.URL = in.URL
remote.Host = in.Host
remote.Client = in.Client
remote.Secret = in.Secret
// insert the remote in the database
if err := h.remotes.Update(remote); err != nil {
return internalServerError{err}
}
w.WriteHeader(http.StatusNoContent)
return nil
return json.NewEncoder(w).Encode(remote)
}
func (h *RemoteHandler) Register(r *pat.Router) {
r.Delete("/v1/remotes/:name", errorHandler(h.DeleteRemote))
r.Post("/v1/remotes", errorHandler(h.PostRemote))
r.Get("/v1/logins", errorHandler(h.GetRemoteLogins))
r.Get("/v1/remotes", errorHandler(h.GetRemotes))
r.Post("/v1/remotes", errorHandler(h.PostRemote))
r.Put("/v1/remotes", errorHandler(h.PutRemote))
}

View file

@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"github.com/drone/drone/plugin/remote"
"github.com/drone/drone/server/database"
"github.com/drone/drone/server/session"
"github.com/drone/drone/shared/httputil"
@ -14,7 +15,7 @@ import (
)
type RepoHandler struct {
conf database.ConfigManager
remotes database.RemoteManager
commits database.CommitManager
perms database.PermManager
repos database.RepoManager
@ -22,8 +23,8 @@ type RepoHandler struct {
}
func NewRepoHandler(repos database.RepoManager, commits database.CommitManager,
perms database.PermManager, sess session.Session, conf database.ConfigManager) *RepoHandler {
return &RepoHandler{conf, commits, perms, repos, sess}
perms database.PermManager, sess session.Session, remotes database.RemoteManager) *RepoHandler {
return &RepoHandler{remotes, commits, perms, repos, sess}
}
// GetRepo gets the named repository.
@ -105,16 +106,24 @@ func (h *RepoHandler) PostRepo(w http.ResponseWriter, r *http.Request) error {
repo.PrivateKey = sshutil.MarshalPrivateKey(key)
// get the remote and client
remote := h.conf.Find().GetRemote(host)
if remote == nil {
remoteServer, err := h.remotes.FindType(repo.Remote)
if err != nil {
return notFound{err}
}
remotePlugin, ok := remote.Lookup(remoteServer.Type)
if !ok {
return notFound{}
}
// get the remote system's client.
plugin := remotePlugin(remoteServer)
// post commit hook url
hook := fmt.Sprintf("%s://%s/v1/hook/%s", httputil.GetScheme(r), httputil.GetHost(r), remote.GetName())
hook := fmt.Sprintf("%s://%s/v1/hook/%s", httputil.GetScheme(r), httputil.GetHost(r), plugin.GetName())
// activate the repository in the remote system
client := remote.GetClient(user.Access, user.Secret)
client := plugin.GetClient(user.Access, user.Secret)
if err := client.SetActive(owner, name, hook, repo.PublicKey); err != nil {
return badRequest{err}
}

View file

@ -9,7 +9,6 @@ import (
"github.com/drone/drone/server/database"
"github.com/drone/drone/server/pubsub"
"github.com/drone/drone/server/session"
"github.com/drone/drone/server/worker"
"github.com/drone/drone/shared/model"
"github.com/gorilla/pat"
@ -72,7 +71,7 @@ func (h *WsHandler) WsUser(w http.ResponseWriter, r *http.Request) error {
for {
select {
case msg := <-sub.Read():
work, ok := msg.(*worker.Request)
work, ok := msg.(*model.Request)
if !ok {
break
}
@ -203,7 +202,7 @@ func readWebsocket(ws *websocket.Conn) {
// will be removed prior to release
func (h *WsHandler) Ping(w http.ResponseWriter, r *http.Request) error {
channel := h.pubsub.Register("_global")
msg := worker.Request{
msg := model.Request{
Repo: &model.Repo{ID: 1, Private: false, Host: "github.com", Owner: "drone", Name: "drone"},
Commit: &model.Commit{ID: 1, Status: "Started", Branch: "master", Sha: "113f4917ff9174945388d86395f902cd154074cb", Message: "Remove branches by SCM hook", Author: "bradrydzewski", Gravatar: "8c58a0be77ee441bb8f8595b7f1b4e87"},
}

View file

@ -25,6 +25,10 @@ import (
"github.com/GeertJohan/go.rice"
_ "github.com/mattn/go-sqlite3"
"github.com/russross/meddler"
_ "github.com/drone/drone/plugin/remote/bitbucket"
_ "github.com/drone/drone/plugin/remote/github"
_ "github.com/drone/drone/plugin/remote/gitlab"
)
var (
@ -79,7 +83,7 @@ func main() {
commits := database.NewCommitManager(db)
servers := database.NewServerManager(db)
remotes := database.NewRemoteManager(db)
configs := database.NewConfigManager(filepath.Join(home, "config.toml"))
//configs := database.NewConfigManager(filepath.Join(home, "config.toml"))
// message broker
pubsub := pubsub.NewPubSub()
@ -87,10 +91,10 @@ func main() {
// cancel all previously running builds
go commits.CancelAll()
queue := make(chan *worker.Request)
workers := make(chan chan *worker.Request)
queue := make(chan *model.Request)
workers := make(chan chan *model.Request)
worker.NewDispatch(queue, workers).Start()
worker.NewWorker(workers, users, repos, commits, configs, pubsub, &model.Server{}).Start()
worker.NewWorker(workers, users, repos, commits, pubsub, &model.Server{}).Start()
// setup the session managers
sess := session.NewSession(users)
@ -99,13 +103,13 @@ func main() {
router := pat.New()
handler.NewUsersHandler(users, sess).Register(router)
handler.NewUserHandler(users, repos, commits, sess).Register(router)
handler.NewHookHandler(users, repos, commits, configs, queue).Register(router)
handler.NewLoginHandler(users, repos, perms, sess, configs).Register(router)
handler.NewHookHandler(users, repos, commits, remotes, queue).Register(router)
handler.NewLoginHandler(users, repos, perms, sess, remotes).Register(router)
handler.NewCommitHandler(repos, commits, perms, sess, queue).Register(router)
handler.NewBranchHandler(repos, commits, perms, sess).Register(router)
handler.NewRepoHandler(repos, commits, perms, sess, configs).Register(router)
handler.NewRepoHandler(repos, commits, perms, sess, remotes).Register(router)
handler.NewBadgeHandler(repos, commits).Register(router)
handler.NewConfigHandler(configs, sess).Register(router)
//handler.NewConfigHandler(configs, sess).Register(router)
handler.NewServerHandler(servers, sess).Register(router)
handler.NewRemoteHandler(users, remotes, sess).Register(router)
handler.NewWsHandler(repos, commits, perms, sess, pubsub).Register(router)

View file

@ -1,14 +1,18 @@
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 *Request
workers chan chan *Request
requests chan *model.Request
workers chan chan *model.Request
quit chan bool
}
func NewDispatch(requests chan *Request, workers chan chan *Request) *Dispatch {
func NewDispatch(requests chan *model.Request, workers chan chan *model.Request) *Dispatch {
return &Dispatch{
requests: requests,
workers: workers,

View file

@ -1,12 +0,0 @@
package worker
import (
"github.com/drone/drone/shared/model"
)
type Request struct {
User *model.User `json:"-"`
Repo *model.Repo `json:"repo"`
Commit *model.Commit `json:"commit"`
server *model.Server
}

View file

@ -24,25 +24,25 @@ type worker struct {
users database.UserManager
repos database.RepoManager
commits database.CommitManager
config database.ConfigManager
pubsub *pubsub.PubSub
server *model.Server
//config database.ConfigManager
pubsub *pubsub.PubSub
server *model.Server
request chan *Request
dispatch chan chan *Request
request chan *model.Request
dispatch chan chan *model.Request
quit chan bool
}
func NewWorker(dispatch chan chan *Request, users database.UserManager, repos database.RepoManager, commits database.CommitManager, config database.ConfigManager, pubsub *pubsub.PubSub, server *model.Server) Worker {
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,
users: users,
repos: repos,
commits: commits,
//config: config,
pubsub: pubsub,
server: server,
dispatch: dispatch,
request: make(chan *Request),
request: make(chan *model.Request),
quit: make(chan bool),
}
}
@ -59,7 +59,7 @@ func (w *worker) Start() {
select {
case r := <-w.request:
// handle the request
r.server = w.server
r.Server = w.server
w.Execute(r)
case <-w.quit:
@ -78,7 +78,7 @@ func (w *worker) Stop() {
// 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 *Request) {
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()
@ -123,6 +123,11 @@ func (w *worker) Execute(r *Request) {
dockerClient = docker.NewHost(w.server.Host)
}
// send all "started" notifications
if script.Notifications != nil {
script.Notifications.Send(r)
}
// create an instance of the Docker builder
builder := build.New(dockerClient)
builder.Build = script
@ -162,5 +167,9 @@ func (w *worker) Execute(r *Request) {
// todo(bradrydzewski) update github status API
// todo(bradrydzewski) send email notifications
// todo(bradrydzewski) send other notifications
// send all "finished" notifications
if script.Notifications != nil {
script.Notifications.Send(r)
}
}

View file

@ -1,94 +0,0 @@
package model
import (
"github.com/drone/drone/plugin/remote"
"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/plugin/remote/stash"
"github.com/drone/drone/plugin/smtp"
)
type Config struct {
// Hostname of the server, eg drone.io
//Host string `json:"host"`
// Scheme of the server, eg https
//Scheme string `json:"scheme"`
// Registration with a value of True allows developers
// to register themselves. If false, must be approved
// or invited by the system administrator.
Registration bool `json:"registration"`
// SMTP stores configuration details for connecting with
// and smtp server to send email notifications.
SMTP *smtp.SMTP `json:"smtp"`
// Bitbucket stores configuration details for communicating
// with the bitbucket.org public cloud service.
Bitbucket *bitbucket.Bitbucket `json:"bitbucket"`
// Github stores configuration details for communicating
// with the github.com public cloud service.
Github *github.Github `json:"github"`
// GithubEnterprise stores configuration details for
// communicating with a private Github installation.
GithubEnterprise *github.Github `json:"githubEnterprise"`
// Gitlab stores configuration details for communicating
// with a private gitlab installation.
Gitlab *gitlab.Gitlab `json:"gitlab"`
// Stash stores configuration details for communicating
// with a private Atlassian Stash installation.
Stash *stash.Stash `json:"stash"`
}
// GetRemote is a helper function that will return the
// remote plugin name based on the specified hostname.
func (c *Config) GetRemote(name string) remote.Remote {
// first attempt to get the remote instance
// by the unique plugin name (ie enterprise.github.com)
switch name {
case c.Github.GetName():
return c.Github
case c.Bitbucket.GetName():
return c.Bitbucket
case c.GithubEnterprise.GetName():
return c.GithubEnterprise
case c.Gitlab.GetName():
return c.Gitlab
case c.Stash.GetName():
return c.Stash
}
// else attempt to get the remote instance
// by the hostname (ie github.drone.io)
switch {
case c.Github.IsMatch(name):
return c.Github
case c.Bitbucket.IsMatch(name):
return c.Bitbucket
case c.GithubEnterprise.IsMatch(name):
return c.GithubEnterprise
case c.Gitlab.IsMatch(name):
return c.Gitlab
case c.Stash.IsMatch(name):
return c.Stash
}
// else none found
return nil
}
// GetClient is a helper function taht will return the named
// remote plugin client, used to interact with the remote system.
func (c *Config) GetClient(name, access, secret string) remote.Client {
remote := c.GetRemote(name)
if remote == nil {
return nil
}
return remote.GetClient(access, secret)
}

9
shared/model/request.go Normal file
View file

@ -0,0 +1,9 @@
package model
type Request struct {
Host string `json:"-"`
User *User `json:"-"`
Repo *Repo `json:"repo"`
Commit *Commit `json:"commit"`
Server *Server `json:"-"`
}