Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
d14ae26a64
25 changed files with 440 additions and 229 deletions
|
@ -90,12 +90,8 @@ func (c *Client) run(method, path string, in, out interface{}) error {
|
||||||
return err
|
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()
|
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)
|
// Check for an http error status (ie not 200 StatusOK)
|
||||||
switch resp.StatusCode {
|
switch resp.StatusCode {
|
||||||
|
@ -111,9 +107,9 @@ func (c *Client) run(method, path string, in, out interface{}) error {
|
||||||
return ErrInternalServer
|
return ErrInternalServer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshall the JSON response
|
// Decode the JSON response
|
||||||
if out != nil {
|
if out != nil {
|
||||||
return json.Unmarshal(body, out)
|
return json.NewDecoder(resp.Body).Decode(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -81,7 +81,7 @@ func (s *SSH) Write(f *buildfile.Buildfile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(s.Cmd) > 0 {
|
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))
|
f.WriteCmd(fmt.Sprintf(sshCmd, host[1], strings.SplitN(host[0], ":", 2)[0], s.Cmd))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ func TestSSHNoArtifact(t *testing.T) {
|
||||||
t.Error("Expect script not to contains scp command")
|
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")
|
t.Error("Expect script to contains ssh command")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
28
plugin/notify/notify_test.go
Normal file
28
plugin/notify/notify_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,9 +9,9 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
slackEndpoint = "https://%s.slack.com/services/hooks/incoming-webhook?token=%s"
|
slackEndpoint = "https://%s.slack.com/services/hooks/incoming-webhook?token=%s"
|
||||||
slackStartedMessage = "*Building* %s, commit <%s|%s>, author %s"
|
slackStartedMessage = "*Building* <%s|%s> (%s) by %s"
|
||||||
slackSuccessMessage = "*Success* %s, commit <%s|%s>, author %s"
|
slackSuccessMessage = "*Success* <%s|%s> (%s) by %s"
|
||||||
slackFailureMessage = "*Failed* %s, commit <%s|%s>, author %s"
|
slackFailureMessage = "*Failed* <%s|%s> (%s) by %s"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Slack struct {
|
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 {
|
func (s *Slack) getMessage(context *model.Request, message string) string {
|
||||||
url := getBuildUrl(context)
|
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 {
|
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 {
|
func (s *Slack) sendSuccess(context *model.Request) error {
|
||||||
|
|
|
@ -1,27 +1,57 @@
|
||||||
package notify
|
package notify
|
||||||
|
|
||||||
import (
|
import "testing"
|
||||||
"github.com/drone/drone/shared/model"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_getBuildUrl(t *testing.T) {
|
/*
|
||||||
c := &model.Request{
|
var request = &model.Request{
|
||||||
Host: "http://examplehost.com",
|
Host: "http://examplehost.com",
|
||||||
Repo: &model.Repo{
|
Repo: &model.Repo{
|
||||||
Host: "examplegit.com",
|
Host: "examplegit.com",
|
||||||
Owner: "owner",
|
Owner: "owner",
|
||||||
Name: "repo",
|
Name: "repo",
|
||||||
},
|
},
|
||||||
Commit: &model.Commit{
|
Commit: &model.Commit{
|
||||||
Sha: "abc",
|
Sha: "abc",
|
||||||
Branch: "example",
|
Branch: "example",
|
||||||
},
|
Status: "Started",
|
||||||
}
|
Message: "Test Commit",
|
||||||
expected := "http://examplehost.com/examplegit.com/owner/repo/example/abc"
|
Author: "Test User",
|
||||||
output := getBuildUrl(c)
|
},
|
||||||
|
User: &model.User{
|
||||||
|
Login: "TestUser",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
if output != expected {
|
var slackExpectedLink = "<owner/repo#abc|http://examplehost.com/examplegit.com/owner/repo/example/abc>"
|
||||||
t.Errorf("Failed to build url. Expected: %s, got %s", expected, output)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ func (r *Gitlab) GetRepos(user *model.User) ([]*model.Repo, error) {
|
||||||
CloneURL: item.HttpRepoUrl,
|
CloneURL: item.HttpRepoUrl,
|
||||||
GitURL: item.HttpRepoUrl,
|
GitURL: item.HttpRepoUrl,
|
||||||
SSHURL: item.SshRepoUrl,
|
SSHURL: item.SshRepoUrl,
|
||||||
|
URL: item.Url,
|
||||||
Role: &model.Perm{},
|
Role: &model.Perm{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
<script src="/static/scripts/controllers/conf.js"></script>
|
<script src="/static/scripts/controllers/conf.js"></script>
|
||||||
<script src="/static/scripts/controllers/home.js"></script>
|
<script src="/static/scripts/controllers/home.js"></script>
|
||||||
<script src="/static/scripts/controllers/repo.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/user.js"></script>
|
||||||
<script src="/static/scripts/controllers/users.js"></script>
|
<script src="/static/scripts/controllers/users.js"></script>
|
||||||
<script src="/static/scripts/controllers/setup.js"></script>
|
<script src="/static/scripts/controllers/setup.js"></script>
|
||||||
|
|
|
@ -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.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
|
// get the user details
|
||||||
$http({method: 'GET', url: '/api/user/repos'}).
|
$http({method: 'GET', url: '/api/user/repos'}).
|
||||||
success(function(data, status, headers, config) {
|
success(function(data, status, headers, config) {
|
||||||
|
@ -224,93 +237,3 @@ app.controller("AccountReposController", function($scope, $http, user) {
|
||||||
return $scope.remote == "" || $scope.remote == entry.remote;
|
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' });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
95
server/app/scripts/controllers/commit.js
Normal file
95
server/app/scripts/controllers/commit.js
Normal 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' });
|
||||||
|
};
|
||||||
|
});
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'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) {
|
feed.subscribe(function(item) {
|
||||||
// todo toast notification
|
// todo toast notification
|
||||||
|
@ -14,6 +14,19 @@ angular.module('app').controller("HomeController", function($scope, $http, feed)
|
||||||
console.log(data);
|
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'}).
|
$http({method: 'GET', url: '/api/user/repos'}).
|
||||||
success(function(data, status, headers, config) {
|
success(function(data, status, headers, config) {
|
||||||
$scope.repos = (typeof data==='string')?[]:data;
|
$scope.repos = (typeof data==='string')?[]:data;
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
'use strict';
|
'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() {
|
var stop = $interval(function() {
|
||||||
// todo(bradrydzewski) We should poll the user to see if the
|
// todo(bradrydzewski) We should poll the user to see if the
|
||||||
// sync process is complete, using the user.syncing variable.
|
// sync process is complete, using the user.syncing variable.
|
||||||
$interval.cancel(stop);
|
$interval.cancel(stop);
|
||||||
$location.path("/");
|
if (return_to != undefined) {
|
||||||
|
$location.$$search = {}
|
||||||
|
$location.path(return_to);
|
||||||
|
} else {
|
||||||
|
$location.path("/");
|
||||||
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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) {
|
this.subscribe = function (path, _callback) {
|
||||||
var callback = undefined;
|
|
||||||
var websocket = undefined;
|
|
||||||
var token = localStorage.getItem('access_token');
|
|
||||||
|
|
||||||
this.subscribe = function(path, _callback) {
|
|
||||||
callback = _callback;
|
callback = _callback;
|
||||||
|
|
||||||
var proto = ($window.location.protocol == 'https:' ? 'wss' : 'ws');
|
var proto = ($window.location.protocol === 'https:' ? 'wss' : 'ws'),
|
||||||
var route = [proto, "://", $window.location.host, '/api/stream/stdout/', path, '?access_token=', token].join('');
|
route = [proto, "://", $window.location.host, '/api/stream/stdout/', path, '?access_token=', token].join('');
|
||||||
|
|
||||||
websocket = new WebSocket(route);
|
websocket = new WebSocket(route);
|
||||||
websocket.onmessage = function(event) {
|
websocket.onmessage = function (event) {
|
||||||
if (callback != undefined) {
|
if (callback !== undefined) {
|
||||||
callback(event.data);
|
callback(event.data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
websocket.onclose = function(event) {
|
websocket.onclose = function (event) {
|
||||||
console.log('websocket closed at '+path);
|
console.log('websocket closed at ' + path);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
this.unsubscribe = function() {
|
this.unsubscribe = function () {
|
||||||
callback = undefined;
|
callback = undefined;
|
||||||
if (websocket != undefined) {
|
if (websocket !== undefined) {
|
||||||
console.log('unsubscribing websocket at '+websocket.url);
|
console.log('unsubscribing websocket at ' + websocket.url);
|
||||||
websocket.close();
|
websocket.close();
|
||||||
|
websocket = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}]);
|
}]);
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1229,6 +1229,11 @@ nav {
|
||||||
//border-left:1px solid #cdcece;
|
//border-left:1px solid #cdcece;
|
||||||
//box-shadow:-3px 0 0 rgba(0,0,0,.05);
|
//box-shadow:-3px 0 0 rgba(0,0,0,.05);
|
||||||
|
|
||||||
|
#sidebar-inner {
|
||||||
|
position: fixed;
|
||||||
|
width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {font-size:28px; font-weight:300;}
|
h1 {font-size:28px; font-weight:300;}
|
||||||
h2 {font-size:22px; font-weight:300; margin-bottom:20px;}
|
h2 {font-size:22px; font-weight:300; margin-bottom:20px;}
|
||||||
dl {padding-top:23px; border-top:1px solid #ddd; margin-top:5px;
|
dl {padding-top:23px; border-top:1px solid #ddd; margin-top:5px;
|
||||||
|
|
|
@ -1,37 +1,22 @@
|
||||||
<aside id="sidebar">
|
<aside id="sidebar">
|
||||||
<div class="result" data-result="{{ commit.status }}">
|
<div id="sidebar-inner">
|
||||||
<dl>
|
<div class="result" data-result="{{ commit.status }}">
|
||||||
<dd><span class="status">{{ commit.status }}</span></dd>
|
<dl>
|
||||||
<dd><strong>{{ commit.message }}</strong></dd>
|
<dd><span class="status">{{ commit.status }}</span></dd>
|
||||||
</dl>
|
<dd><strong>{{ commit.message }}</strong></dd>
|
||||||
</div>
|
</dl>
|
||||||
|
</div>
|
||||||
<dl ng-if="commit.duration != 0">
|
|
||||||
<dd><h1>{{ commit.duration | toDuration}}</h1></dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<dl>
|
<dl ng-if="commit.duration != 0">
|
||||||
<dd class="large" ng-if="repo.remote == 'gitlab.com' || repo.remote == 'github.com' || repo.remote == 'enterprise.github.com' ">
|
<dd><h1>{{ commit.duration | toDuration}}</h1></dd>
|
||||||
<strong>
|
</dl>
|
||||||
commit
|
|
||||||
<a href="http://{{ repo.host }}/{{ repo.owner }}/{{ repo.name }}/commit/{{ commit.sha }}" >{{ commit.sha | shortHash}}</a>
|
<dl ng-include="'/static/views/commit_detail.html'" ng-show="commit.pull_request.length == 0"></dl>
|
||||||
to <a href="http://{{ repo.host }}/{{ repo.owner }}/{{ repo.name }}/tree/{{ commit.branch }}">{{ commit.branch }}</a> branch
|
<dl ng-include="'/static/views/commit_detail_pr.html'" ng-show="commit.pull_request.length != 0"></dl>
|
||||||
</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>
|
|
||||||
<dd ng-if="commit.finished_at != 0">{{ commit.finished_at | fromNow }}</dd>
|
<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">Started {{ commit.started_at | fromNow }}</dd>
|
||||||
<dd ng-if="commit.finished_at == 0 && commit.started_at == 0">Created {{ commit.created_at}}</dd>
|
<dd ng-if="commit.finished_at == 0 && commit.started_at == 0">Created {{ commit.created_at}}</dd>
|
||||||
</dl>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div id="main" class="output">
|
<div id="main" class="output">
|
||||||
|
|
23
server/app/views/commit_detail.html
Normal file
23
server/app/views/commit_detail.html
Normal 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 -->
|
31
server/app/views/commit_detail_pr.html
Normal file
31
server/app/views/commit_detail_pr.html
Normal 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 -->
|
|
@ -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">
|
<article id="homepage">
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/"><span class="fa fa-th"></span></a>
|
<a href="/"><span class="fa fa-th"></span></a>
|
||||||
<a href="/">dashboard</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>
|
</nav>
|
||||||
|
|
||||||
<section ng-if="feed.length == 0">
|
<section ng-if="feed.length == 0">
|
||||||
|
|
|
@ -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">
|
<div id="repospage">
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/"><span class="fa fa-th"></span></a>
|
<a href="/"><span class="fa fa-th"></span></a>
|
||||||
<a href="/">repositories</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>
|
</nav>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
|
|
@ -4,12 +4,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/drone/drone/plugin/remote"
|
"github.com/drone/drone/plugin/remote"
|
||||||
"github.com/drone/drone/server/capability"
|
"github.com/drone/drone/server/capability"
|
||||||
"github.com/drone/drone/server/datastore"
|
"github.com/drone/drone/server/datastore"
|
||||||
"github.com/drone/drone/server/session"
|
"github.com/drone/drone/server/session"
|
||||||
|
"github.com/drone/drone/server/sync"
|
||||||
"github.com/drone/drone/shared/model"
|
"github.com/drone/drone/shared/model"
|
||||||
"github.com/goji/context"
|
"github.com/goji/context"
|
||||||
"github.com/zenazn/goji/web"
|
"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.Secret = login.Secret
|
||||||
u.Name = login.Name
|
u.Name = login.Name
|
||||||
u.SetEmail(login.Email)
|
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 {
|
if err := datastore.PutUser(ctx, u); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
@ -102,51 +103,8 @@ func GetLogin(c web.C, w http.ResponseWriter, r *http.Request) {
|
||||||
redirect = "/sync"
|
redirect = "/sync"
|
||||||
log.Println("sync user account.", u.Login)
|
log.Println("sync user account.", u.Login)
|
||||||
|
|
||||||
// sync inside a goroutine. This should eventually be moved to
|
// sync inside a goroutine
|
||||||
// its own package / sync utility.
|
go sync.SyncUser(ctx, u, remote)
|
||||||
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
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := session.GenerateToken(ctx, r, u)
|
token, err := session.GenerateToken(ctx, r, u)
|
||||||
|
|
|
@ -4,7 +4,9 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/drone/drone/plugin/remote"
|
||||||
"github.com/drone/drone/server/datastore"
|
"github.com/drone/drone/server/datastore"
|
||||||
|
"github.com/drone/drone/server/sync"
|
||||||
"github.com/drone/drone/shared/model"
|
"github.com/drone/drone/shared/model"
|
||||||
"github.com/goji/context"
|
"github.com/goji/context"
|
||||||
"github.com/zenazn/goji/web"
|
"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)
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -58,6 +58,7 @@ func New() *web.Mux {
|
||||||
user.Use(middleware.RequireUser)
|
user.Use(middleware.RequireUser)
|
||||||
user.Get("/api/user/feed", handler.GetUserFeed)
|
user.Get("/api/user/feed", handler.GetUserFeed)
|
||||||
user.Get("/api/user/repos", handler.GetUserRepos)
|
user.Get("/api/user/repos", handler.GetUserRepos)
|
||||||
|
user.Post("/api/user/sync", handler.PostUserSync)
|
||||||
user.Get("/api/user", handler.GetUserCurrent)
|
user.Get("/api/user", handler.GetUserCurrent)
|
||||||
user.Put("/api/user", handler.PutUser)
|
user.Put("/api/user", handler.PutUser)
|
||||||
mux.Handle("/api/user*", user)
|
mux.Handle("/api/user*", user)
|
||||||
|
|
55
server/sync/sync.go
Normal file
55
server/sync/sync.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -214,12 +214,8 @@ func (c *Client) do(method, path string, in, out interface{}) error {
|
||||||
return err
|
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()
|
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)
|
// Check for an http error status (ie not 200 StatusOK)
|
||||||
switch resp.StatusCode {
|
switch resp.StatusCode {
|
||||||
|
@ -233,9 +229,9 @@ func (c *Client) do(method, path string, in, out interface{}) error {
|
||||||
return ErrBadRequest
|
return ErrBadRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshall the JSON response
|
// Decode the JSON response
|
||||||
if out != nil {
|
if out != nil {
|
||||||
return json.Unmarshal(body, out)
|
return json.NewDecoder(resp.Body).Decode(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
Loading…
Reference in a new issue