Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Brad Rydzewski 2014-10-28 20:57:49 -07:00
commit d14ae26a64
25 changed files with 440 additions and 229 deletions

View file

@ -90,12 +90,8 @@ func (c *Client) run(method, path string, in, out interface{}) error {
return err
}
// Read the bytes from the body (make sure we defer close the body)
// make sure we defer close the body
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
// Check for an http error status (ie not 200 StatusOK)
switch resp.StatusCode {
@ -111,9 +107,9 @@ func (c *Client) run(method, path string, in, out interface{}) error {
return ErrInternalServer
}
// Unmarshall the JSON response
// Decode the JSON response
if out != nil {
return json.Unmarshal(body, out)
return json.NewDecoder(resp.Body).Decode(out)
}
return nil

View file

@ -81,7 +81,7 @@ func (s *SSH) Write(f *buildfile.Buildfile) {
}
if len(s.Cmd) > 0 {
sshCmd := "ssh -o StrictHostKeyChecking=no -p %s %s %s"
sshCmd := "ssh -o StrictHostKeyChecking=no -p %s %s \"%s\""
f.WriteCmd(fmt.Sprintf(sshCmd, host[1], strings.SplitN(host[0], ":", 2)[0], s.Cmd))
}
}

View file

@ -70,7 +70,7 @@ func TestSSHNoArtifact(t *testing.T) {
t.Error("Expect script not to contains scp command")
}
if !strings.Contains(bscr, "ssh -o StrictHostKeyChecking=no -p 22 user@test.example.com /opt/bin/redeploy.sh") {
if !strings.Contains(bscr, "ssh -o StrictHostKeyChecking=no -p 22 user@test.example.com \"/opt/bin/redeploy.sh\"") {
t.Error("Expect script to contains ssh command")
}
}

View file

@ -0,0 +1,28 @@
package notify
import (
"testing"
"github.com/drone/drone/shared/model"
)
func Test_getBuildUrl(t *testing.T) {
c := &model.Request{
Host: "http://examplehost.com",
Repo: &model.Repo{
Host: "examplegit.com",
Owner: "owner",
Name: "repo",
},
Commit: &model.Commit{
Sha: "abc",
Branch: "example",
},
}
expected := "http://examplehost.com/examplegit.com/owner/repo/example/abc"
output := getBuildUrl(c)
if output != expected {
t.Errorf("Failed to build url. Expected: %s, got %s", expected, output)
}
}

View file

@ -9,9 +9,9 @@ import (
const (
slackEndpoint = "https://%s.slack.com/services/hooks/incoming-webhook?token=%s"
slackStartedMessage = "*Building* %s, commit <%s|%s>, author %s"
slackSuccessMessage = "*Success* %s, commit <%s|%s>, author %s"
slackFailureMessage = "*Failed* %s, commit <%s|%s>, author %s"
slackStartedMessage = "*Building* <%s|%s> (%s) by %s"
slackSuccessMessage = "*Success* <%s|%s> (%s) by %s"
slackFailureMessage = "*Failed* <%s|%s> (%s) by %s"
)
type Slack struct {
@ -39,11 +39,14 @@ func (s *Slack) Send(context *model.Request) error {
func (s *Slack) getMessage(context *model.Request, message string) string {
url := getBuildUrl(context)
return fmt.Sprintf(message, context.Repo.Name, url, context.Commit.ShaShort(), context.Commit.Author)
// drone/drone#3333333
linktext := context.Repo.Owner + "/" + context.Repo.Name + "#" + context.Commit.ShaShort()
return fmt.Sprintf(message, linktext, url, context.Commit.Branch, context.Commit.Author)
}
func (s *Slack) sendStarted(context *model.Request) error {
return s.send(s.getMessage(context, slackStartedMessage), "warning")
return s.send(s.getMessage(context, slackStartedMessage)+"\n - "+context.Commit.Message, "warning")
}
func (s *Slack) sendSuccess(context *model.Request) error {

View file

@ -1,27 +1,57 @@
package notify
import (
"github.com/drone/drone/shared/model"
"testing"
)
import "testing"
func Test_getBuildUrl(t *testing.T) {
c := &model.Request{
Host: "http://examplehost.com",
Repo: &model.Repo{
Host: "examplegit.com",
Owner: "owner",
Name: "repo",
},
Commit: &model.Commit{
Sha: "abc",
Branch: "example",
},
}
expected := "http://examplehost.com/examplegit.com/owner/repo/example/abc"
output := getBuildUrl(c)
/*
var request = &model.Request{
Host: "http://examplehost.com",
Repo: &model.Repo{
Host: "examplegit.com",
Owner: "owner",
Name: "repo",
},
Commit: &model.Commit{
Sha: "abc",
Branch: "example",
Status: "Started",
Message: "Test Commit",
Author: "Test User",
},
User: &model.User{
Login: "TestUser",
},
}
*/
if output != expected {
t.Errorf("Failed to build url. Expected: %s, got %s", expected, output)
var slackExpectedLink = "<owner/repo#abc|http://examplehost.com/examplegit.com/owner/repo/example/abc>"
var slackExpectedBase = slackExpectedLink + " (example) by Test User"
func Test_slackStartedMessage(t *testing.T) {
actual := (&Slack{}).getMessage(request, slackStartedMessage)
expected := "*Building* " + slackExpectedBase
if actual != expected {
t.Errorf("Invalid getStarted message for Slack. Expected %v, got %v", expected, actual)
}
}
func Test_slackSuccessMessage(t *testing.T) {
actual := (&Slack{}).getMessage(request, slackSuccessMessage)
expected := "*Success* " + slackExpectedBase
if actual != expected {
t.Errorf("Invalid getStarted message for Slack. Expected %v, got %v", expected, actual)
}
}
func Test_slackFailureMessage(t *testing.T) {
actual := (&Slack{}).getMessage(request, slackFailureMessage)
expected := "*Failed* " + slackExpectedBase
if actual != expected {
t.Errorf("Invalid getStarted message for Slack. Expected %v, got %v", expected, actual)
}
}

View file

@ -75,6 +75,7 @@ func (r *Gitlab) GetRepos(user *model.User) ([]*model.Repo, error) {
CloneURL: item.HttpRepoUrl,
GitURL: item.HttpRepoUrl,
SSHURL: item.SshRepoUrl,
URL: item.Url,
Role: &model.Perm{},
}

View file

@ -37,6 +37,7 @@
<script src="/static/scripts/controllers/conf.js"></script>
<script src="/static/scripts/controllers/home.js"></script>
<script src="/static/scripts/controllers/repo.js"></script>
<script src="/static/scripts/controllers/commit.js"></script>
<script src="/static/scripts/controllers/user.js"></script>
<script src="/static/scripts/controllers/users.js"></script>
<script src="/static/scripts/controllers/setup.js"></script>

View file

@ -198,10 +198,23 @@ app.run(['$location', '$rootScope', '$routeParams', 'feed', 'stdout', function($
app.controller("AccountReposController", function($scope, $http, user) {
app.controller("AccountReposController", function($scope, $http, $location, user) {
$scope.user = user;
$scope.syncUser = function() {
$http({method: 'POST', url: '/api/user/sync' }).success(function(data){
$location.search('return_to', $location.$$path).path('/sync')
}).error(function(data, status){
if (status == 409) {
$scope.msg = 'already'
} else {
$scope.msg = 'bad'
}
$scope.$apply();
});
}
// get the user details
$http({method: 'GET', url: '/api/user/repos'}).
success(function(data, status, headers, config) {
@ -224,93 +237,3 @@ app.controller("AccountReposController", function($scope, $http, user) {
return $scope.remote == "" || $scope.remote == entry.remote;
};
});
app.controller("CommitController", function($scope, $http, $route, $routeParams, stdout, feed) {
var remote = $routeParams.remote;
var owner = $routeParams.owner;
var name = $routeParams.name;
var branch = $routeParams.branch;
var commit = $routeParams.commit;
$scope.console='';
var handleOutput = function(id, clearConsole) {
var lineFormatter = new Drone.LineFormatter();
var el = document.querySelector('#output');
if(clearConsole === true) {
el.innerHTML = '';
}
stdout.subscribe(id, function(out){
angular.element(el).append(lineFormatter.format(out));
if ($scope.following) {
window.scrollTo(0, document.body.scrollHeight);
}
});
}
feed.subscribe(function(item) {
if (item.commit.sha == commit &&
item.commit.branch == branch) {
if(item.commit.status == "Started") {
handleOutput(item.commit.id, true);
}
$scope.commit = item.commit;
$scope.$apply();
} else {
// we trigger an toast notification so the
// user is aware another build started
}
});
// load the repo meta-data
$http({method: 'GET', url: '/api/repos/'+remote+'/'+owner+"/"+name}).
success(function(data, status, headers, config) {
$scope.repo = data;
}).
error(function(data, status, headers, config) {
console.log(data);
});
// load the repo commit data
$http({method: 'GET', url: '/api/repos/'+remote+'/'+owner+"/"+name+"/branches/"+branch+"/commits/"+commit}).
success(function(data, status, headers, config) {
$scope.commit = data;
if (data.status!='Started' && data.status!='Pending') {
$http({method: 'GET', url: '/api/repos/'+remote+'/'+owner+"/"+name+"/branches/"+branch+"/commits/"+commit+"/console"}).
success(function(data, status, headers, config) {
var lineFormatter = new Drone.LineFormatter();
var el = document.querySelector('#output');
angular.element(el).append(lineFormatter.format(data));
}).
error(function(data, status, headers, config) {
console.log(data);
});
return;
}
handleOutput(data.id, false);
}).
error(function(data, status, headers, config) {
console.log(data);
});
$scope.following = false;
$scope.follow = function() {
$scope.following = true;
window.scrollTo(0, document.body.scrollHeight);
}
$scope.unfollow = function() {
$scope.following = false;
}
$scope.rebuildCommit = function() {
$http({method: 'POST', url: '/api/repos/'+remote+'/'+owner+'/'+name+'/'+'branches/'+branch+'/'+'commits/'+commit+'?action=rebuild' });
}
});

View file

@ -0,0 +1,95 @@
/*global angular, Drone, console */
angular.module('app').controller("CommitController", function ($scope, $http, $route, $routeParams, stdout, feed) {
'use strict';
var remote = $routeParams.remote,
owner = $routeParams.owner,
name = $routeParams.name,
branch = $routeParams.branch,
commit = $routeParams.commit,
// Create lineFormatter and outputElement since we need them anyway.
lineFormatter = new Drone.LineFormatter(),
outputElement = angular.element(document.querySelector('#output'));
var connectRemoteConsole = function (id) {
// Clear console output if connecting to new remote console (rebuild)
if (!outputElement.html() !== 0) {
outputElement.empty();
}
// Subscribe to stdout of the remote build
stdout.subscribe(id, function (out) {
// Append new output to console
outputElement.append(lineFormatter.format(out));
// Scroll if following
if ($scope.following) {
window.scrollTo(0, document.body.scrollHeight);
}
});
};
// Subscribe to feed so we can update gui if changes to the commit happen. (Build finished, Rebuild triggered, change from Pending to Started)
feed.subscribe(function (item) {
// If event is part of the active commit currently showing.
if (item.commit.sha === commit &&
item.commit.branch === branch) {
// If new status is Started, connect to remote console to get live output
if (item.commit.status === "Started") {
connectRemoteConsole(item.commit.id);
}
$scope.commit = item.commit;
$scope.$apply();
} else {
// we trigger an toast notification so the
// user is aware another build started
}
});
// Load the repo meta-data
$http({method: 'GET', url: '/api/repos/' + remote + '/' + owner + "/" + name}).
success(function (data, status, headers, config) {
$scope.repo = data;
}).
error(function (data, status, headers, config) {
console.log(data);
});
// Load the repo commit data
$http({method: 'GET', url: '/api/repos/' + remote + '/' + owner + "/" + name + "/branches/" + branch + "/commits/" + commit}).
success(function (data, status, headers, config) {
$scope.commit = data;
// If build has already finished, load console output from database
if (data.status !== 'Started' && data.status !== 'Pending') {
$http({method: 'GET', url: '/api/repos/' + remote + '/' + owner + "/" + name + "/branches/" + branch + "/commits/" + commit + "/console"}).
success(function (data, status, headers, config) {
outputElement.append(lineFormatter.format(data));
}).
error(function (data, status, headers, config) {
console.log(data);
});
return;
// If build is currently running, connect to remote console;
} else if (data.status === 'Started') {
connectRemoteConsole(data.id);
}
}).
error(function (data, status, headers, config) {
console.log(data);
});
$scope.following = false;
$scope.follow = function () {
$scope.following = true;
window.scrollTo(0, document.body.scrollHeight);
};
$scope.unfollow = function () {
$scope.following = false;
};
$scope.rebuildCommit = function () {
$http({method: 'POST', url: '/api/repos/' + remote + '/' + owner + '/' + name + '/branches/' + branch + '/commits/' + commit + '?action=rebuild' });
};
});

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('app').controller("HomeController", function($scope, $http, feed) {
angular.module('app').controller("HomeController", function($scope, $http, $location, feed) {
feed.subscribe(function(item) {
// todo toast notification
@ -14,6 +14,19 @@ angular.module('app').controller("HomeController", function($scope, $http, feed)
console.log(data);
});
$scope.syncUser = function() {
$http({method: 'POST', url: '/api/user/sync' }).success(function(data){
$location.search('return_to', $location.$$path).path('/sync')
}).error(function(data, status){
if (status == 409) {
$scope.msg = 'already'
} else {
$scope.msg = 'bad'
}
$scope.$apply();
});
}
$http({method: 'GET', url: '/api/user/repos'}).
success(function(data, status, headers, config) {
$scope.repos = (typeof data==='string')?[]:data;

View file

@ -1,10 +1,16 @@
'use strict';
angular.module('app').controller("SyncController", function($scope, $http, $interval, $location, users) {
angular.module('app').controller("SyncController", function($scope, $http, $interval, $location, $routeParams, users) {
var return_to = $routeParams.return_to
var stop = $interval(function() {
// todo(bradrydzewski) We should poll the user to see if the
// sync process is complete, using the user.syncing variable.
$interval.cancel(stop);
$location.path("/");
if (return_to != undefined) {
$location.$$search = {}
$location.path(return_to);
} else {
$location.path("/");
}
}, 5000);
});

View file

@ -1,32 +1,34 @@
'use strict';
/*global angular, WebSocket, localStorage, console */
angular.module('app').service('stdout', ['$window', function ($window) {
'use strict';
var callback,
websocket,
token = localStorage.getItem('access_token');
angular.module('app').service('stdout', ['$window', function($window) {
var callback = undefined;
var websocket = undefined;
var token = localStorage.getItem('access_token');
this.subscribe = function(path, _callback) {
this.subscribe = function (path, _callback) {
callback = _callback;
var proto = ($window.location.protocol == 'https:' ? 'wss' : 'ws');
var route = [proto, "://", $window.location.host, '/api/stream/stdout/', path, '?access_token=', token].join('');
var proto = ($window.location.protocol === 'https:' ? 'wss' : 'ws'),
route = [proto, "://", $window.location.host, '/api/stream/stdout/', path, '?access_token=', token].join('');
websocket = new WebSocket(route);
websocket.onmessage = function(event) {
if (callback != undefined) {
websocket.onmessage = function (event) {
if (callback !== undefined) {
callback(event.data);
}
};
websocket.onclose = function(event) {
console.log('websocket closed at '+path);
websocket.onclose = function (event) {
console.log('websocket closed at ' + path);
};
};
this.unsubscribe = function() {
this.unsubscribe = function () {
callback = undefined;
if (websocket != undefined) {
console.log('unsubscribing websocket at '+websocket.url);
if (websocket !== undefined) {
console.log('unsubscribing websocket at ' + websocket.url);
websocket.close();
websocket = undefined;
}
};
}]);

File diff suppressed because one or more lines are too long

View file

@ -1229,6 +1229,11 @@ nav {
//border-left:1px solid #cdcece;
//box-shadow:-3px 0 0 rgba(0,0,0,.05);
#sidebar-inner {
position: fixed;
width: 180px;
}
h1 {font-size:28px; font-weight:300;}
h2 {font-size:22px; font-weight:300; margin-bottom:20px;}
dl {padding-top:23px; border-top:1px solid #ddd; margin-top:5px;

View file

@ -1,37 +1,22 @@
<aside id="sidebar">
<div class="result" data-result="{{ commit.status }}">
<dl>
<dd><span class="status">{{ commit.status }}</span></dd>
<dd><strong>{{ commit.message }}</strong></dd>
</dl>
</div>
<dl ng-if="commit.duration != 0">
<dd><h1>{{ commit.duration | toDuration}}</h1></dd>
</dl>
<div id="sidebar-inner">
<div class="result" data-result="{{ commit.status }}">
<dl>
<dd><span class="status">{{ commit.status }}</span></dd>
<dd><strong>{{ commit.message }}</strong></dd>
</dl>
</div>
<dl>
<dd class="large" ng-if="repo.remote == 'gitlab.com' || repo.remote == 'github.com' || repo.remote == 'enterprise.github.com' ">
<strong>
commit
<a href="http://{{ repo.host }}/{{ repo.owner }}/{{ repo.name }}/commit/{{ commit.sha }}" >{{ commit.sha | shortHash}}</a>
to <a href="http://{{ repo.host }}/{{ repo.owner }}/{{ repo.name }}/tree/{{ commit.branch }}">{{ commit.branch }}</a> branch
</strong>
</dd>
<dd class="large" ng-if="repo.remote == 'bitbucket.org' ">
<strong>
commit
<a href="http://{{ repo.host }}/{{ repo.owner }}/{{ repo.name }}/commits/{{ commit.sha }}" >{{ commit.sha | shortHash}}</a>
to <a href="http://{{ repo.host }}/{{ repo.owner }}/{{ repo.name }}/src/?at={{ commit.branch }}">{{ commit.branch }}</a> branch
</strong>
</dd>
<dd class="large" ng-if="repo.remote != 'gitlab.com' && repo.remote != 'github.com' && repo.remote != 'enterprise.github.com' && repo.remote != 'bitbucket.org' ">
<strong>commit <u>{{ commit.sha | shortHash}}</u> to <u>{{ commit.branch }}</u> branch</strong>
</dd>
<dl ng-if="commit.duration != 0">
<dd><h1>{{ commit.duration | toDuration}}</h1></dd>
</dl>
<dl ng-include="'/static/views/commit_detail.html'" ng-show="commit.pull_request.length == 0"></dl>
<dl ng-include="'/static/views/commit_detail_pr.html'" ng-show="commit.pull_request.length != 0"></dl>
<dd ng-if="commit.finished_at != 0">{{ commit.finished_at | fromNow }}</dd>
<dd ng-if="commit.finished_at == 0 && commit.started_at != 0">Started {{ commit.started_at | fromNow }}</dd>
<dd ng-if="commit.finished_at == 0 && commit.started_at == 0">Created {{ commit.created_at}}</dd>
</dl>
</div>
</aside>
<div id="main" class="output">

View file

@ -0,0 +1,23 @@
<!-- GITHUB && GITLAB -->
<dd class="large" ng-if="repo.remote == 'gitlab.com' || repo.remote == 'github.com' || repo.remote == 'enterprise.github.com' ">
<strong>
commit
<a href="{{ repo.url }}/commit/{{ commit.sha }}" >{{ commit.sha | shortHash}}</a>
to <a href="{{ repo.url }}/tree/{{ commit.branch }}">{{ commit.branch }}</a> branch
</strong>
</dd>
<!-- /GITHUB && GITLAB -->
<!-- BITBUCKET -->
<dd class="large" ng-if="repo.remote == 'bitbucket.org' ">
<strong>
commit
<a href="{{ repo.url }}/commits/{{ commit.sha }}" >{{ commit.sha | shortHash}}</a>
to <a href="{{ repo.url }}/src/?at={{ commit.branch }}">{{ commit.branch }}</a> branch
</strong>
</dd>
<!-- /BITBUCKET -->
<!-- STASH -->
<dd class="large" ng-if="repo.remote != 'gitlab.com' && repo.remote != 'github.com' && repo.remote != 'enterprise.github.com' && repo.remote != 'bitbucket.org' ">
<strong>commit <u>{{ commit.sha | shortHash}}</u> to <u>{{ commit.branch }}</u> branch</strong>
</dd>
<!-- /STASH -->

View file

@ -0,0 +1,31 @@
<!-- GITHUB -->
<dd class="large" ng-if="repo.remote == 'github.com' || repo.remote == 'enterprise.github.com' ">
<strong>
Pull Request
<a href="{{ repo.url }}/pull/{{ commit.pull_request }}" >#{{ commit.pull_request }}</a>
</strong>
</dd>
<!-- /GITHUB -->
<!-- GITLAB -->
<dd class="large" ng-if="repo.remote == 'gitlab.com'">
<strong>
Pull Request
<a href="{{ repo.url }}/merge_requests/{{ commit.pull_request }}" >#{{ commit.pull_request }}</a>
</strong>
</dd>
<!-- /GITLAB -->
<!-- BITBUCKET -->
<dd class="large" ng-if="repo.remote == 'bitbucket.org' ">
<strong>
Pull Request
<a href="{{ repo.url }}/pull-requests/{{ commit.pull_request }}" >#{{ commit.pull_request }}</a>
</strong>
</dd>
<!-- /BITBUCKET -->
<!-- STASH -->
<dd class="large" ng-if="repo.remote != 'gitlab.com' && repo.remote != 'github.com' && repo.remote != 'enterprise.github.com' && repo.remote != 'bitbucket.org' ">
<strong>
Pull Request #{{ commit.pull_request }}
</strong>
</dd>
<!-- /STASH -->

View file

@ -1,7 +1,18 @@
<div class="toast" ng-if="msg != undefined">
<span ng-if="msg == 'already'">sync already runned</span>
<span ng-if="msg == 'bad'">bad response</span>
</div>
<article id="homepage">
<nav>
<a href="/"><span class="fa fa-th"></span></a>
<a href="/">dashboard</a>
<div class="options ng-scope">
<a class="pure-button pure-button-primary" ng-click="syncUser()" href="#">
<i class="fa fa-refresh"></i>
<span>Sync</span>
</a>
</div>
</nav>
<section ng-if="feed.length == 0">

View file

@ -1,7 +1,19 @@
<div class="toast" ng-if="msg != undefined">
<span ng-if="msg == 'already'">sync already runned</span>
<span ng-if="msg == 'bad'">bad response</span>
</div>
<div id="repospage">
<nav>
<a href="/"><span class="fa fa-th"></span></a>
<a href="/">repositories</a>
<div class="options ng-scope">
<a class="pure-button pure-button-primary" ng-click="syncUser()" href="#">
<i class="fa fa-refresh"></i>
<span>Sync</span>
</a>
</div>
</nav>
<section>

View file

@ -4,12 +4,12 @@ import (
"encoding/json"
"log"
"net/http"
"time"
"github.com/drone/drone/plugin/remote"
"github.com/drone/drone/server/capability"
"github.com/drone/drone/server/datastore"
"github.com/drone/drone/server/session"
"github.com/drone/drone/server/sync"
"github.com/drone/drone/shared/model"
"github.com/goji/context"
"github.com/zenazn/goji/web"
@ -83,7 +83,8 @@ func GetLogin(c web.C, w http.ResponseWriter, r *http.Request) {
u.Secret = login.Secret
u.Name = login.Name
u.SetEmail(login.Email)
u.Syncing = true //u.IsStale() // todo (badrydzewski) should not always sync
u.Syncing = u.IsStale()
if err := datastore.PutUser(ctx, u); err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadRequest)
@ -102,51 +103,8 @@ func GetLogin(c web.C, w http.ResponseWriter, r *http.Request) {
redirect = "/sync"
log.Println("sync user account.", u.Login)
// sync inside a goroutine. This should eventually be moved to
// its own package / sync utility.
go func() {
repos, err := remote.GetRepos(u)
if err != nil {
log.Println("Error syncing user account, listing repositories", u.Login, err)
return
}
// insert all repositories
for _, repo := range repos {
var role = repo.Role
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 = datastore.GetRepoName(ctx, repo.Host, repo.Owner, repo.Name)
if err != nil {
log.Println("Error adding repo.", u.Login, repo.Name, err)
continue
}
}
// add user permissions
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
}
log.Println("Successfully syced repo.", u.Login+"/"+repo.Name)
}
u.Synced = time.Now().UTC().Unix()
u.Syncing = false
if err := datastore.PutUser(ctx, u); err != nil {
log.Println("Error syncing user account, updating sync date", u.Login, err)
return
}
}()
// sync inside a goroutine
go sync.SyncUser(ctx, u, remote)
}
token, err := session.GenerateToken(ctx, r, u)

View file

@ -4,7 +4,9 @@ import (
"encoding/json"
"net/http"
"github.com/drone/drone/plugin/remote"
"github.com/drone/drone/server/datastore"
"github.com/drone/drone/server/sync"
"github.com/drone/drone/shared/model"
"github.com/goji/context"
"github.com/zenazn/goji/web"
@ -111,3 +113,37 @@ func GetUserFeed(c web.C, w http.ResponseWriter, r *http.Request) {
}
json.NewEncoder(w).Encode(&repos)
}
// PostUserSync accepts a request to post user sync
//
// POST /api/user/sync
//
func PostUserSync(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
}
var remote = remote.Lookup(user.Remote)
if remote == nil {
w.WriteHeader(http.StatusNotFound)
return
}
if user.Syncing {
w.WriteHeader(http.StatusConflict)
return
}
user.Syncing = true
if err := datastore.PutUser(ctx, user); err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
go sync.SyncUser(ctx, user, remote)
w.WriteHeader(http.StatusNoContent)
return
}

View file

@ -58,6 +58,7 @@ func New() *web.Mux {
user.Use(middleware.RequireUser)
user.Get("/api/user/feed", handler.GetUserFeed)
user.Get("/api/user/repos", handler.GetUserRepos)
user.Post("/api/user/sync", handler.PostUserSync)
user.Get("/api/user", handler.GetUserCurrent)
user.Put("/api/user", handler.PutUser)
mux.Handle("/api/user*", user)

55
server/sync/sync.go Normal file
View file

@ -0,0 +1,55 @@
package sync
import (
"log"
"time"
"code.google.com/p/go.net/context"
"github.com/drone/drone/plugin/remote"
"github.com/drone/drone/server/datastore"
"github.com/drone/drone/shared/model"
)
func SyncUser(ctx context.Context, user *model.User, remote remote.Remote) {
repos, err := remote.GetRepos(user)
if err != nil {
log.Println("Error syncing user account, listing repositories", user.Login, err)
return
}
// insert all repositories
for _, repo := range repos {
var role = repo.Role
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 = datastore.GetRepoName(ctx, repo.Host, repo.Owner, repo.Name)
if err != nil {
log.Println("Error adding repo.", user.Login, repo.Name, err)
continue
}
}
// add user permissions
perm := model.Perm{
UserID: user.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.", user.Login, repo.Name, err)
continue
}
log.Println("Successfully syced repo.", user.Login+"/"+repo.Name)
}
user.Synced = time.Now().UTC().Unix()
user.Syncing = false
if err := datastore.PutUser(ctx, user); err != nil {
log.Println("Error syncing user account, updating sync date", user.Login, err)
return
}
}

View file

@ -214,12 +214,8 @@ func (c *Client) do(method, path string, in, out interface{}) error {
return err
}
// Read the bytes from the body (make sure we defer close the body)
// make sure we defer close the body
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
// Check for an http error status (ie not 200 StatusOK)
switch resp.StatusCode {
@ -233,9 +229,9 @@ func (c *Client) do(method, path string, in, out interface{}) error {
return ErrBadRequest
}
// Unmarshall the JSON response
// Decode the JSON response
if out != nil {
return json.Unmarshal(body, out)
return json.NewDecoder(resp.Body).Decode(out)
}
return nil