mirror of
https://github.com/harness/drone.git
synced 2025-05-09 07:59:38 +08:00
integrated plugin model for remotes
This commit is contained in:
parent
91f7a63d21
commit
aa9a8f4878
20
plugin/remote/bitbucket/init.go
Normal file
20
plugin/remote/bitbucket/init.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
21
plugin/remote/github/init.go
Normal file
21
plugin/remote/github/init.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
17
plugin/remote/gitlab/init.go
Normal file
17
plugin/remote/gitlab/init.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,27 @@ package remote
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"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 {
|
type Remote interface {
|
||||||
// GetName returns the name of this remote system.
|
// GetName returns the name of this remote system.
|
||||||
GetName() string
|
GetName() string
|
||||||
|
@ -42,11 +42,13 @@
|
|||||||
<script src="/scripts/controllers/setup.js"></script>
|
<script src="/scripts/controllers/setup.js"></script>
|
||||||
<script src="/scripts/controllers/sync.js"></script>
|
<script src="/scripts/controllers/sync.js"></script>
|
||||||
<script src="/scripts/controllers/main.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/auth.js"></script>
|
||||||
<script src="/scripts/services/conf.js"></script>
|
<script src="/scripts/services/conf.js"></script>
|
||||||
<script src="/scripts/services/repo.js"></script>
|
<script src="/scripts/services/repo.js"></script>
|
||||||
<script src="/scripts/services/user.js"></script>
|
<script src="/scripts/services/user.js"></script>
|
||||||
<script src="/scripts/services/feed.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/notify.js"></script>
|
||||||
<script src="/scripts/services/stdout.js"></script>
|
<script src="/scripts/services/stdout.js"></script>
|
||||||
<script src="/scripts/filters/filters.js"></script>
|
<script src="/scripts/filters/filters.js"></script>
|
||||||
|
@ -23,6 +23,7 @@ app.config(['$routeProvider', '$locationProvider', function($routeProvider, $loc
|
|||||||
})
|
})
|
||||||
.when('/login', {
|
.when('/login', {
|
||||||
templateUrl: '/views/login.html',
|
templateUrl: '/views/login.html',
|
||||||
|
controller: 'LoginController',
|
||||||
title: 'Login',
|
title: 'Login',
|
||||||
})
|
})
|
||||||
.when('/setup', {
|
.when('/setup', {
|
||||||
|
13
server/app/scripts/controllers/login.js
Normal file
13
server/app/scripts/controllers/login.js
Normal 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;
|
||||||
|
});
|
||||||
|
});
|
@ -1,14 +1,18 @@
|
|||||||
'use strict';
|
'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
|
// create a remote that will be populated
|
||||||
// and persisted to the database.
|
// and persisted to the database.
|
||||||
$scope.remote = {};
|
$scope.remote = {};
|
||||||
$scope.remote.type = $routeParams.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) {
|
switch($scope.remote.type) {
|
||||||
case undefined:
|
|
||||||
case 'github.com':
|
case 'github.com':
|
||||||
$scope.remote.type = "github.com"
|
$scope.remote.type = "github.com"
|
||||||
$scope.remote.url = "https://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) {
|
success(function(data, status, headers, config) {
|
||||||
delete $scope.failure;
|
delete $scope.failure;
|
||||||
$scope.remote = data;
|
$scope.remote = data;
|
||||||
console.log('success', $scope.remote);
|
$window.location.href="/login/"+data.type;
|
||||||
}).
|
}).
|
||||||
error(function(data, status, headers, config) {
|
error(function(data, status, headers, config) {
|
||||||
$scope.failure = data;
|
$scope.failure = data;
|
||||||
|
@ -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() {
|
angular.module('app').filter('unique', function() {
|
||||||
return function(input, key) {
|
return function(input, key) {
|
||||||
var unique = {};
|
var unique = {};
|
||||||
|
13
server/app/scripts/services/remote.js
Normal file
13
server/app/scripts/services/remote.js
Normal 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');
|
||||||
|
};
|
||||||
|
}]);
|
@ -650,6 +650,7 @@ nav div.options .pure-button i {
|
|||||||
-o-box-sizing: border-box;
|
-o-box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
#setuppage .pure-g,
|
||||||
#loginpage .pure-g {
|
#loginpage .pure-g {
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
border: 1px solid #DDD;
|
border: 1px solid #DDD;
|
||||||
@ -662,6 +663,7 @@ nav div.options .pure-button i {
|
|||||||
-o-box-sizing: border-box;
|
-o-box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
#setuppage .pure-g a,
|
||||||
#loginpage .pure-g a {
|
#loginpage .pure-g a {
|
||||||
display: block;
|
display: block;
|
||||||
background: #45494b;
|
background: #45494b;
|
||||||
@ -671,9 +673,11 @@ nav div.options .pure-button i {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
#setuppage .pure-g a:hover,
|
||||||
#loginpage .pure-g a:hover {
|
#loginpage .pure-g a:hover {
|
||||||
background: #262626;
|
background: #262626;
|
||||||
}
|
}
|
||||||
|
#setuppage .pure-g [class*="fa-"],
|
||||||
#loginpage .pure-g [class*="fa-"] {
|
#loginpage .pure-g [class*="fa-"] {
|
||||||
float: left;
|
float: left;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
@ -684,12 +688,74 @@ nav div.options .pure-button i {
|
|||||||
min-width: 27px;
|
min-width: 27px;
|
||||||
min-height: 20px;
|
min-height: 20px;
|
||||||
}
|
}
|
||||||
|
#setuppage .pure-g .pure-u-1 a,
|
||||||
#loginpage .pure-g .pure-u-1 a {
|
#loginpage .pure-g .pure-u-1 a {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
#setuppage .pure-g .pure-u-1:last-child a,
|
||||||
#loginpage .pure-g .pure-u-1:last-child a {
|
#loginpage .pure-g .pure-u-1:last-child a {
|
||||||
margin-bottom: 0px;
|
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 {
|
#syncpage {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -579,6 +579,7 @@ nav {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#setuppage,
|
||||||
#loginpage {
|
#loginpage {
|
||||||
.pure-g {
|
.pure-g {
|
||||||
padding: 30px;
|
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 {
|
#syncpage {
|
||||||
width:100%;
|
width:100%;
|
||||||
section {
|
section {
|
||||||
|
2
server/app/styles/drone.min.css
vendored
2
server/app/styles/drone.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1,28 +1,14 @@
|
|||||||
<article id="loginpage">
|
<article id="loginpage">
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1" ng-if="state == 1 && remotes.length != 0" ng-repeat="remote in remotes">
|
||||||
<a href="/login/github.com" target="_self">
|
<a href="/login/{{ remote.type }}" target="_self">
|
||||||
<i class="fa fa fa-github-square"></i> GitHub
|
<i class="fa {{ remote.type | remoteIcon }}"></i> {{ remote.type | remoteName }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="pure-u-1">
|
|
||||||
<a href="/login/enterprise.github.com" target="_self">
|
<div class="pure-u-1" ng-if="state == 1 && remotes.length == 0">
|
||||||
<i class="fa fa fa-github-square"></i> GitHub Enterprise
|
<a href="/setup">
|
||||||
</a>
|
<i class="fa fa-rocket"></i> Launch Setup Wizard
|
||||||
</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
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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">
|
<section>
|
||||||
<div class="col-xs-6 col-sm-3">
|
<div class="pure-g">
|
||||||
<table border="1">
|
<div class="pure-u-1">
|
||||||
<tr>
|
<a href="/setup/github.com">
|
||||||
<td><a href="/setup/github.com">GitHub</a></td>
|
<i class="fa fa-github-square"></i> GitHub
|
||||||
<td><a href="/setup/enterprise.github.com">GitHub Enterprise</a></td>
|
</a>
|
||||||
<td><a href="/setup/gitlab.com">Gitlab</a></td>
|
</div>
|
||||||
<td><a href="/setup/bitbucket.org">Bitbucket</a></td>
|
<div class="pure-u-1">
|
||||||
<td><a href="/setup/stash.atlassian.com">Stash</a></td>
|
<a href="/setup/enterprise.github.com">
|
||||||
</tr>
|
<i class="fa fa-github-square"></i> GitHub Enterprise
|
||||||
</table>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="pure-u-1">
|
||||||
<!-- primary column -->
|
<a href="/setup/bitbucket.org">
|
||||||
<div class="col-xs-12 col-sm-9">
|
<i class="fa fa-bitbucket-square"></i> Bitbucket
|
||||||
|
</a>
|
||||||
<div>
|
</div>
|
||||||
<label>Registration</label>
|
<div class="pure-u-1">
|
||||||
<input type="radio" ng-model="remote.register" ng-value="true" />
|
<a href="/setup/stash.atlassian.com">
|
||||||
<input type="radio" ng-model="remote.register" ng-value="false" />
|
<i class="fa fa-bitbucket-square"></i> Atlassian Stash
|
||||||
</div>
|
</a>
|
||||||
|
</div>
|
||||||
<div>
|
<div class="pure-u-1">
|
||||||
<label>URL</label>
|
<a href="/setup/gitlab.com">
|
||||||
<div ng-switch="remote.type">
|
<i class="fa fa-git-square"></i> GitLab
|
||||||
<input ng-switch-when="github.com" ng-model="remote.url" type="text" readonly />
|
</a>
|
||||||
<input ng-switch-when="bitbucket.org" ng-model="remote.url" type="text" readonly />
|
|
||||||
<input ng-switch-default ng-model="remote.url" type="text" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
|
||||||
<div ng-if="remote.type != 'gitlab.com'">
|
<article ng-if="remote.type != undefined" id="setuppage2">
|
||||||
<label>API</label>
|
<nav>
|
||||||
<div ng-switch="remote.type">
|
<a href="/setup">
|
||||||
<input ng-switch-when="github.com" ng-model="remote.api" type="text" readonly />
|
<span class="fa fa-arrow-left"></span>
|
||||||
<input ng-switch-when="bitbucket.org" ng-model="remote.api" type="text" readonly />
|
</a>
|
||||||
<input ng-switch-default ng-model="remote.api" type="text" />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div ng-if="remote.type != 'gitlab.com'">
|
<section>
|
||||||
<label>Client</label>
|
<div class="pure-g">
|
||||||
<input type="text" ng-model="remote.client" />
|
<div class="pure-u-1">
|
||||||
</div>
|
<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'">
|
<div ng-if="remote.type != 'github.com' && remote.type != 'bitbucket.org' ">
|
||||||
<label>Secret</label>
|
<label>API URL</label>
|
||||||
<input type="text" ng-model="remote.secret" />
|
<div ng-switch="remote.type">
|
||||||
</div>
|
<input ng-switch-default ng-model="remote.api" type="text" placeholder="https://www.foo.com/api" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div ng-if="remote.type != 'gitlab.com'">
|
||||||
<button ng-click="save()">Save</button>
|
<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>
|
||||||
</div>
|
</article>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
|
/*
|
||||||
import (
|
import (
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/drone/drone/shared/model"
|
"github.com/drone/drone/shared/model"
|
||||||
@ -33,3 +34,4 @@ func NewConfigManager(filename string) ConfigManager {
|
|||||||
func (c *configManager) Find() *model.Config {
|
func (c *configManager) Find() *model.Config {
|
||||||
return c.conf
|
return c.conf
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
@ -14,6 +14,9 @@ type RemoteManager interface {
|
|||||||
// FindHost finds the Remote by hostname.
|
// FindHost finds the Remote by hostname.
|
||||||
FindHost(name string) (*model.Remote, error)
|
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 finds all registered Remotes of the system.
|
||||||
List() ([]*model.Remote, error)
|
List() ([]*model.Remote, error)
|
||||||
|
|
||||||
@ -40,10 +43,19 @@ WHERE remote_host=?
|
|||||||
LIMIT 1
|
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.
|
// SQL query to retrieve a list of all Remotes.
|
||||||
const listRemoteQuery = `
|
const listRemoteQuery = `
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM remotes
|
FROM remotes
|
||||||
|
ORDER BY remote_type
|
||||||
`
|
`
|
||||||
|
|
||||||
// SQL statement to delete a Remote by ID.
|
// SQL statement to delete a Remote by ID.
|
||||||
@ -69,6 +81,12 @@ func (db *remoteManager) FindHost(host string) (*model.Remote, error) {
|
|||||||
return &dst, err
|
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) {
|
func (db *remoteManager) List() ([]*model.Remote, error) {
|
||||||
var dst []*model.Remote
|
var dst []*model.Remote
|
||||||
err := meddler.QueryAll(db, &dst, listRemoteQuery)
|
err := meddler.QueryAll(db, &dst, listRemoteQuery)
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/drone/drone/server/database"
|
"github.com/drone/drone/server/database"
|
||||||
"github.com/drone/drone/server/session"
|
"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/drone/drone/shared/model"
|
||||||
"github.com/gorilla/pat"
|
"github.com/gorilla/pat"
|
||||||
)
|
)
|
||||||
@ -16,10 +16,10 @@ type CommitHandler struct {
|
|||||||
repos database.RepoManager
|
repos database.RepoManager
|
||||||
commits database.CommitManager
|
commits database.CommitManager
|
||||||
sess session.Session
|
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}
|
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
|
||||||
// drop the items on the queue
|
// drop the items on the queue
|
||||||
go func() {
|
go func() {
|
||||||
h.queue <- &worker.Request{
|
h.queue <- &model.Request{
|
||||||
|
Host: httputil.GetURL(r),
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
Commit: c,
|
Commit: c,
|
||||||
}
|
}
|
||||||
|
@ -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))
|
|
||||||
}
|
|
@ -3,8 +3,9 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/drone/drone/plugin/remote"
|
||||||
"github.com/drone/drone/server/database"
|
"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/drone/drone/shared/model"
|
||||||
"github.com/gorilla/pat"
|
"github.com/gorilla/pat"
|
||||||
)
|
)
|
||||||
@ -13,12 +14,12 @@ type HookHandler struct {
|
|||||||
users database.UserManager
|
users database.UserManager
|
||||||
repos database.RepoManager
|
repos database.RepoManager
|
||||||
commits database.CommitManager
|
commits database.CommitManager
|
||||||
conf database.ConfigManager
|
remotes database.RemoteManager
|
||||||
queue chan *worker.Request
|
queue chan *model.Request
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHookHandler(users database.UserManager, repos database.RepoManager, commits database.CommitManager, conf database.ConfigManager, queue chan *worker.Request) *HookHandler {
|
func NewHookHandler(users database.UserManager, repos database.RepoManager, commits database.CommitManager, remotes database.RemoteManager, queue chan *model.Request) *HookHandler {
|
||||||
return &HookHandler{users, repos, commits, conf, queue}
|
return &HookHandler{users, repos, commits, remotes, queue}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostHook receives a post-commit hook from GitHub, Bitbucket, etc
|
// 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 {
|
func (h *HookHandler) PostHook(w http.ResponseWriter, r *http.Request) error {
|
||||||
host := r.FormValue(":host")
|
host := r.FormValue(":host")
|
||||||
|
|
||||||
// get the remote system's client.
|
remoteServer, err := h.remotes.FindType(host)
|
||||||
remote := h.conf.Find().GetRemote(host)
|
if err != nil {
|
||||||
if remote == nil {
|
return notFound{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
remotePlugin, ok := remote.Lookup(remoteServer.Type)
|
||||||
|
if !ok {
|
||||||
return notFound{}
|
return notFound{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the remote system's client.
|
||||||
|
plugin := remotePlugin(remoteServer)
|
||||||
|
|
||||||
// parse the hook payload
|
// parse the hook payload
|
||||||
hook, err := remote.GetHook(r)
|
hook, err := plugin.GetHook(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return badRequest{err}
|
return badRequest{err}
|
||||||
}
|
}
|
||||||
@ -47,7 +55,7 @@ func (h *HookHandler) PostHook(w http.ResponseWriter, r *http.Request) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fetch the repository from the database
|
// 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 {
|
if err != nil {
|
||||||
return notFound{}
|
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
|
// 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)
|
yml, err := client.GetScript(hook)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return badRequest{err}
|
return badRequest{err}
|
||||||
@ -91,7 +99,8 @@ func (h *HookHandler) PostHook(w http.ResponseWriter, r *http.Request) error {
|
|||||||
|
|
||||||
// drop the items on the queue
|
// drop the items on the queue
|
||||||
go func() {
|
go func() {
|
||||||
h.queue <- &worker.Request{
|
h.queue <- &model.Request{
|
||||||
|
Host: httputil.GetURL(r),
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
Commit: &c,
|
Commit: &c,
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/drone/drone/plugin/remote"
|
||||||
"github.com/drone/drone/server/database"
|
"github.com/drone/drone/server/database"
|
||||||
"github.com/drone/drone/server/session"
|
"github.com/drone/drone/server/session"
|
||||||
"github.com/drone/drone/shared/model"
|
"github.com/drone/drone/shared/model"
|
||||||
@ -15,12 +16,13 @@ type LoginHandler struct {
|
|||||||
users database.UserManager
|
users database.UserManager
|
||||||
repos database.RepoManager
|
repos database.RepoManager
|
||||||
perms database.PermManager
|
perms database.PermManager
|
||||||
conf database.ConfigManager
|
//conf database.ConfigManager
|
||||||
sess session.Session
|
sess session.Session
|
||||||
|
remotes database.RemoteManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLoginHandler(users database.UserManager, repos database.RepoManager, perms database.PermManager, sess session.Session, conf database.ConfigManager) *LoginHandler {
|
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}
|
return &LoginHandler{users, repos, perms /*conf,*/, sess, remotes}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLogin gets the login to the 3rd party remote system.
|
// 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")
|
host := r.FormValue(":host")
|
||||||
redirect := "/"
|
redirect := "/"
|
||||||
|
|
||||||
// get the remote system's client.
|
remoteServer, err := h.remotes.FindType(host)
|
||||||
remote := h.conf.Find().GetRemote(host)
|
if err != nil {
|
||||||
if remote == nil {
|
return notFound{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
remotePlugin, ok := remote.Lookup(remoteServer.Type)
|
||||||
|
if !ok {
|
||||||
return notFound{}
|
return notFound{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the remote system's client.
|
||||||
|
plugin := remotePlugin(remoteServer)
|
||||||
|
|
||||||
// authenticate the user
|
// authenticate the user
|
||||||
login, err := remote.GetLogin(w, r)
|
login, err := plugin.GetLogin(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return badRequest{err}
|
return badRequest{err}
|
||||||
} else if login == nil {
|
} 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
|
// if self-registration is disabled we should
|
||||||
// return a notAuthorized error. the only exception
|
// return a notAuthorized error. the only exception
|
||||||
// is if no users exist yet in the system we'll proceed.
|
// 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{}
|
return notAuthorized{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the user account
|
// 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.Name = login.Name
|
||||||
u.SetEmail(login.Email)
|
u.SetEmail(login.Email)
|
||||||
|
|
||||||
@ -102,7 +111,7 @@ func (h *LoginHandler) GetLogin(w http.ResponseWriter, r *http.Request) error {
|
|||||||
// its own package / sync utility.
|
// its own package / sync utility.
|
||||||
go func() {
|
go func() {
|
||||||
// list all repositories
|
// list all repositories
|
||||||
client := remote.GetClient(u.Access, u.Secret)
|
client := plugin.GetClient(u.Access, u.Secret)
|
||||||
repos, err := client.GetRepos("")
|
repos, err := client.GetRepos("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error syncing user account, listing repositories", u.Login, err)
|
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
|
// insert all repositories
|
||||||
for _, remoteRepo := range repos {
|
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.Private = remoteRepo.Private
|
||||||
repo.Host = remoteRepo.Host
|
repo.Host = remoteRepo.Host
|
||||||
repo.CloneURL = remoteRepo.Clone
|
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)
|
log.Println("Successfully syced repo.", u.Login+"/"+remoteRepo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
u.Synced = time.Now().Unix()
|
u.Synced = time.Now().UTC().Unix()
|
||||||
u.Syncing = false
|
u.Syncing = false
|
||||||
if err := h.users.Update(u); err != nil {
|
if err := h.users.Update(u); err != nil {
|
||||||
log.Println("Error syncing user account, updating sync date", u.Login, err)
|
log.Println("Error syncing user account, updating sync date", u.Login, err)
|
||||||
return
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,23 @@ func (h *RemoteHandler) GetRemotes(w http.ResponseWriter, r *http.Request) error
|
|||||||
return json.NewEncoder(w).Encode(remotes)
|
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.
|
// PostRemote creates a new remote.
|
||||||
// POST /api/remotes
|
// POST /api/remotes
|
||||||
func (h *RemoteHandler) PostRemote(w http.ResponseWriter, r *http.Request) error {
|
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 {
|
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
|
||||||
return badRequest{err}
|
return badRequest{err}
|
||||||
}
|
}
|
||||||
|
|
||||||
uri, err := url.Parse(in.URL)
|
uri, err := url.Parse(in.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return badRequest{err}
|
return badRequest{err}
|
||||||
@ -65,7 +81,8 @@ func (h *RemoteHandler) PostRemote(w http.ResponseWriter, r *http.Request) error
|
|||||||
in.Host = uri.Host
|
in.Host = uri.Host
|
||||||
|
|
||||||
// there is an edge case where, during installation, a user could attempt
|
// 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() {
|
if remote, err := h.remotes.FindHost(in.Host); err == nil && h.users.Exist() {
|
||||||
h.remotes.Delete(remote)
|
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)
|
return json.NewEncoder(w).Encode(&in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRemote delete the remote.
|
// PutRemote updates an existing remote.
|
||||||
// GET /api/remotes/:name
|
// PUT /api/remotes
|
||||||
func (h *RemoteHandler) DeleteRemote(w http.ResponseWriter, r *http.Request) error {
|
func (h *RemoteHandler) PutRemote(w http.ResponseWriter, r *http.Request) error {
|
||||||
host := r.FormValue(":host")
|
|
||||||
|
|
||||||
// get the user form the session
|
// get the user form the session
|
||||||
user := h.sess.User(r)
|
user := h.sess.User(r)
|
||||||
if user == nil || !user.Admin {
|
if user == nil || !user.Admin {
|
||||||
return notAuthorized{}
|
return notAuthorized{}
|
||||||
}
|
}
|
||||||
// get the remote
|
// unmarshal the remote from the payload
|
||||||
remote, err := h.remotes.FindHost(host)
|
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 {
|
if err != nil {
|
||||||
return notFound{err}
|
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}
|
return internalServerError{err}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
return json.NewEncoder(w).Encode(remote)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *RemoteHandler) Register(r *pat.Router) {
|
func (h *RemoteHandler) Register(r *pat.Router) {
|
||||||
r.Delete("/v1/remotes/:name", errorHandler(h.DeleteRemote))
|
r.Get("/v1/logins", errorHandler(h.GetRemoteLogins))
|
||||||
r.Post("/v1/remotes", errorHandler(h.PostRemote))
|
|
||||||
r.Get("/v1/remotes", errorHandler(h.GetRemotes))
|
r.Get("/v1/remotes", errorHandler(h.GetRemotes))
|
||||||
|
r.Post("/v1/remotes", errorHandler(h.PostRemote))
|
||||||
|
r.Put("/v1/remotes", errorHandler(h.PutRemote))
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/drone/drone/plugin/remote"
|
||||||
"github.com/drone/drone/server/database"
|
"github.com/drone/drone/server/database"
|
||||||
"github.com/drone/drone/server/session"
|
"github.com/drone/drone/server/session"
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
@ -14,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RepoHandler struct {
|
type RepoHandler struct {
|
||||||
conf database.ConfigManager
|
remotes database.RemoteManager
|
||||||
commits database.CommitManager
|
commits database.CommitManager
|
||||||
perms database.PermManager
|
perms database.PermManager
|
||||||
repos database.RepoManager
|
repos database.RepoManager
|
||||||
@ -22,8 +23,8 @@ type RepoHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewRepoHandler(repos database.RepoManager, commits database.CommitManager,
|
func NewRepoHandler(repos database.RepoManager, commits database.CommitManager,
|
||||||
perms database.PermManager, sess session.Session, conf database.ConfigManager) *RepoHandler {
|
perms database.PermManager, sess session.Session, remotes database.RemoteManager) *RepoHandler {
|
||||||
return &RepoHandler{conf, commits, perms, repos, sess}
|
return &RepoHandler{remotes, commits, perms, repos, sess}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRepo gets the named repository.
|
// 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)
|
repo.PrivateKey = sshutil.MarshalPrivateKey(key)
|
||||||
|
|
||||||
// get the remote and client
|
// get the remote and client
|
||||||
remote := h.conf.Find().GetRemote(host)
|
remoteServer, err := h.remotes.FindType(repo.Remote)
|
||||||
if remote == nil {
|
if err != nil {
|
||||||
|
return notFound{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
remotePlugin, ok := remote.Lookup(remoteServer.Type)
|
||||||
|
if !ok {
|
||||||
return notFound{}
|
return notFound{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the remote system's client.
|
||||||
|
plugin := remotePlugin(remoteServer)
|
||||||
|
|
||||||
// post commit hook url
|
// 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
|
// 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 {
|
if err := client.SetActive(owner, name, hook, repo.PublicKey); err != nil {
|
||||||
return badRequest{err}
|
return badRequest{err}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/drone/drone/server/database"
|
"github.com/drone/drone/server/database"
|
||||||
"github.com/drone/drone/server/pubsub"
|
"github.com/drone/drone/server/pubsub"
|
||||||
"github.com/drone/drone/server/session"
|
"github.com/drone/drone/server/session"
|
||||||
"github.com/drone/drone/server/worker"
|
|
||||||
"github.com/drone/drone/shared/model"
|
"github.com/drone/drone/shared/model"
|
||||||
"github.com/gorilla/pat"
|
"github.com/gorilla/pat"
|
||||||
|
|
||||||
@ -72,7 +71,7 @@ func (h *WsHandler) WsUser(w http.ResponseWriter, r *http.Request) error {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case msg := <-sub.Read():
|
case msg := <-sub.Read():
|
||||||
work, ok := msg.(*worker.Request)
|
work, ok := msg.(*model.Request)
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -203,7 +202,7 @@ func readWebsocket(ws *websocket.Conn) {
|
|||||||
// will be removed prior to release
|
// will be removed prior to release
|
||||||
func (h *WsHandler) Ping(w http.ResponseWriter, r *http.Request) error {
|
func (h *WsHandler) Ping(w http.ResponseWriter, r *http.Request) error {
|
||||||
channel := h.pubsub.Register("_global")
|
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"},
|
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"},
|
Commit: &model.Commit{ID: 1, Status: "Started", Branch: "master", Sha: "113f4917ff9174945388d86395f902cd154074cb", Message: "Remove branches by SCM hook", Author: "bradrydzewski", Gravatar: "8c58a0be77ee441bb8f8595b7f1b4e87"},
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,10 @@ import (
|
|||||||
"github.com/GeertJohan/go.rice"
|
"github.com/GeertJohan/go.rice"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"github.com/russross/meddler"
|
"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 (
|
var (
|
||||||
@ -79,7 +83,7 @@ func main() {
|
|||||||
commits := database.NewCommitManager(db)
|
commits := database.NewCommitManager(db)
|
||||||
servers := database.NewServerManager(db)
|
servers := database.NewServerManager(db)
|
||||||
remotes := database.NewRemoteManager(db)
|
remotes := database.NewRemoteManager(db)
|
||||||
configs := database.NewConfigManager(filepath.Join(home, "config.toml"))
|
//configs := database.NewConfigManager(filepath.Join(home, "config.toml"))
|
||||||
|
|
||||||
// message broker
|
// message broker
|
||||||
pubsub := pubsub.NewPubSub()
|
pubsub := pubsub.NewPubSub()
|
||||||
@ -87,10 +91,10 @@ func main() {
|
|||||||
// cancel all previously running builds
|
// cancel all previously running builds
|
||||||
go commits.CancelAll()
|
go commits.CancelAll()
|
||||||
|
|
||||||
queue := make(chan *worker.Request)
|
queue := make(chan *model.Request)
|
||||||
workers := make(chan chan *worker.Request)
|
workers := make(chan chan *model.Request)
|
||||||
worker.NewDispatch(queue, workers).Start()
|
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
|
// setup the session managers
|
||||||
sess := session.NewSession(users)
|
sess := session.NewSession(users)
|
||||||
@ -99,13 +103,13 @@ func main() {
|
|||||||
router := pat.New()
|
router := pat.New()
|
||||||
handler.NewUsersHandler(users, sess).Register(router)
|
handler.NewUsersHandler(users, sess).Register(router)
|
||||||
handler.NewUserHandler(users, repos, commits, sess).Register(router)
|
handler.NewUserHandler(users, repos, commits, sess).Register(router)
|
||||||
handler.NewHookHandler(users, repos, commits, configs, queue).Register(router)
|
handler.NewHookHandler(users, repos, commits, remotes, queue).Register(router)
|
||||||
handler.NewLoginHandler(users, repos, perms, sess, configs).Register(router)
|
handler.NewLoginHandler(users, repos, perms, sess, remotes).Register(router)
|
||||||
handler.NewCommitHandler(repos, commits, perms, sess, queue).Register(router)
|
handler.NewCommitHandler(repos, commits, perms, sess, queue).Register(router)
|
||||||
handler.NewBranchHandler(repos, commits, perms, sess).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.NewBadgeHandler(repos, commits).Register(router)
|
||||||
handler.NewConfigHandler(configs, sess).Register(router)
|
//handler.NewConfigHandler(configs, sess).Register(router)
|
||||||
handler.NewServerHandler(servers, sess).Register(router)
|
handler.NewServerHandler(servers, sess).Register(router)
|
||||||
handler.NewRemoteHandler(users, remotes, sess).Register(router)
|
handler.NewRemoteHandler(users, remotes, sess).Register(router)
|
||||||
handler.NewWsHandler(repos, commits, perms, sess, pubsub).Register(router)
|
handler.NewWsHandler(repos, commits, perms, sess, pubsub).Register(router)
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
package worker
|
package worker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/drone/drone/shared/model"
|
||||||
|
)
|
||||||
|
|
||||||
// http://nesv.github.io/golang/2014/02/25/worker-queues-in-go.html
|
// http://nesv.github.io/golang/2014/02/25/worker-queues-in-go.html
|
||||||
|
|
||||||
type Dispatch struct {
|
type Dispatch struct {
|
||||||
requests chan *Request
|
requests chan *model.Request
|
||||||
workers chan chan *Request
|
workers chan chan *model.Request
|
||||||
quit chan bool
|
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{
|
return &Dispatch{
|
||||||
requests: requests,
|
requests: requests,
|
||||||
workers: workers,
|
workers: workers,
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -24,25 +24,25 @@ type worker struct {
|
|||||||
users database.UserManager
|
users database.UserManager
|
||||||
repos database.RepoManager
|
repos database.RepoManager
|
||||||
commits database.CommitManager
|
commits database.CommitManager
|
||||||
config database.ConfigManager
|
//config database.ConfigManager
|
||||||
pubsub *pubsub.PubSub
|
pubsub *pubsub.PubSub
|
||||||
server *model.Server
|
server *model.Server
|
||||||
|
|
||||||
request chan *Request
|
request chan *model.Request
|
||||||
dispatch chan chan *Request
|
dispatch chan chan *model.Request
|
||||||
quit chan bool
|
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{
|
return &worker{
|
||||||
users: users,
|
users: users,
|
||||||
repos: repos,
|
repos: repos,
|
||||||
commits: commits,
|
commits: commits,
|
||||||
config: config,
|
//config: config,
|
||||||
pubsub: pubsub,
|
pubsub: pubsub,
|
||||||
server: server,
|
server: server,
|
||||||
dispatch: dispatch,
|
dispatch: dispatch,
|
||||||
request: make(chan *Request),
|
request: make(chan *model.Request),
|
||||||
quit: make(chan bool),
|
quit: make(chan bool),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,7 +59,7 @@ func (w *worker) Start() {
|
|||||||
select {
|
select {
|
||||||
case r := <-w.request:
|
case r := <-w.request:
|
||||||
// handle the request
|
// handle the request
|
||||||
r.server = w.server
|
r.Server = w.server
|
||||||
w.Execute(r)
|
w.Execute(r)
|
||||||
|
|
||||||
case <-w.quit:
|
case <-w.quit:
|
||||||
@ -78,7 +78,7 @@ func (w *worker) Stop() {
|
|||||||
// Execute executes the work Request, persists the
|
// Execute executes the work Request, persists the
|
||||||
// results to the database, and sends event messages
|
// results to the database, and sends event messages
|
||||||
// to the pubsub (for websocket updates on the website).
|
// 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
|
// mark the build as Started and update the database
|
||||||
r.Commit.Status = model.StatusStarted
|
r.Commit.Status = model.StatusStarted
|
||||||
r.Commit.Started = time.Now().UTC().Unix()
|
r.Commit.Started = time.Now().UTC().Unix()
|
||||||
@ -123,6 +123,11 @@ func (w *worker) Execute(r *Request) {
|
|||||||
dockerClient = docker.NewHost(w.server.Host)
|
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
|
// create an instance of the Docker builder
|
||||||
builder := build.New(dockerClient)
|
builder := build.New(dockerClient)
|
||||||
builder.Build = script
|
builder.Build = script
|
||||||
@ -162,5 +167,9 @@ func (w *worker) Execute(r *Request) {
|
|||||||
|
|
||||||
// todo(bradrydzewski) update github status API
|
// todo(bradrydzewski) update github status API
|
||||||
// todo(bradrydzewski) send email notifications
|
// todo(bradrydzewski) send email notifications
|
||||||
// todo(bradrydzewski) send other notifications
|
|
||||||
|
// send all "finished" notifications
|
||||||
|
if script.Notifications != nil {
|
||||||
|
script.Notifications.Send(r)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
9
shared/model/request.go
Normal 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:"-"`
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user