Merge pull request #666 from compressed/gogs

Add gogs support
This commit is contained in:
Brad Rydzewski 2014-11-18 21:42:30 -08:00
commit 7e2b03e20f
12 changed files with 265 additions and 11 deletions

183
plugin/remote/gogs/gogs.go Normal file
View file

@ -0,0 +1,183 @@
package gogs
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
"time"
"github.com/drone/drone/shared/model"
"github.com/gogits/go-gogs-client"
)
type Gogs struct {
URL string
Secret string
}
func New(url string, secret string) *Gogs {
return &Gogs{URL: url, Secret: secret}
}
// Authorize handles Gogs authorization
func (r *Gogs) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) {
var username = req.FormValue("username")
var password = req.FormValue("password")
var client = gogs.NewClient(r.URL, "")
// try to fetch drone token if it exists
var accessToken = ""
tokens, err := client.ListAccessTokens(username, password)
if err != nil {
return nil, err
}
for _, token := range tokens {
if token.Name == "drone" {
accessToken = token.Sha1
break
}
}
// if drone token not found, create it
if accessToken == "" {
token, err := client.CreateAccessToken(username, password, gogs.CreateAccessTokenOption{Name: "drone"})
if err != nil {
return nil, err
}
accessToken = token.Sha1
}
// update client
client = gogs.NewClient(r.URL, accessToken)
// fetch user information
user, err := client.GetUserInfo(username)
if err != nil {
return nil, err
}
var login = new(model.Login)
login.Name = user.FullName
login.Email = user.Email
login.Access = accessToken
login.Login = username
return login, nil
}
// GetKind returns the internal identifier of this remote Gogs instance
func (r *Gogs) GetKind() string {
return model.RemoteGogs
}
// GetHost returns the hostname of this remote Gogs instance
func (r *Gogs) GetHost() string {
uri, _ := url.Parse(r.URL)
return uri.Host
}
// GetRepos fetches all repositories that the specified
// user has access to in the remote system.
func (r *Gogs) GetRepos(user *model.User) ([]*model.Repo, error) {
var repos []*model.Repo
var remote = r.GetKind()
var hostname = r.GetHost()
var client = gogs.NewClient(r.URL, user.Access)
gogsRepos, err := client.ListMyRepos()
if err != nil {
return nil, err
}
for _, repo := range gogsRepos {
var repoName = strings.Split(repo.FullName, "/")
if len(repoName) < 2 {
log.Println("invalid repo full_name", repo.FullName)
continue
}
var owner = repoName[0]
var name = repoName[1]
var repo = model.Repo{
UserID: user.ID,
Remote: remote,
Host: hostname,
Owner: owner,
Name: name,
Private: repo.Private,
CloneURL: repo.CloneUrl,
GitURL: repo.CloneUrl,
SSHURL: repo.SshUrl,
URL: repo.HtmlUrl,
Role: &model.Perm{
Admin: repo.Permissions.Admin,
Write: repo.Permissions.Push,
Read: repo.Permissions.Pull,
},
}
repos = append(repos, &repo)
}
return repos, err
}
// GetScript fetches the build script (.drone.yml) from the remote
// repository and returns a byte array
func (r *Gogs) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error) {
var client = gogs.NewClient(r.URL, user.Access)
return client.GetFile(repo.Owner, repo.Name, hook.Sha, ".drone.yml")
}
// Activate activates a repository
func (r *Gogs) Activate(user *model.User, repo *model.Repo, link string) error {
var client = gogs.NewClient(r.URL, user.Access)
var config = map[string]string{
"url": link,
"secret": r.Secret,
"content_type": "json",
}
var hook = gogs.CreateHookOption{
Type: "gogs",
Config: config,
Active: true,
}
_, err := client.CreateRepoHook(repo.Owner, repo.Name, hook)
return err
}
// ParseHook parses the post-commit hook from the Request body
// and returns the required data in a standard format.
func (r *Gogs) ParseHook(req *http.Request) (*model.Hook, error) {
defer req.Body.Close()
var payloadbytes, _ = ioutil.ReadAll(req.Body)
var payload, err = gogs.ParseHook(payloadbytes)
if err != nil {
return nil, err
}
// verify the payload has the minimum amount of required data.
if payload.Repo == nil || payload.Commits == nil || len(payload.Commits) == 0 {
return nil, fmt.Errorf("Invalid Gogs post-commit Hook. Missing Repo or Commit data.")
}
if payload.Secret != r.Secret {
return nil, fmt.Errorf("Payload secret does not match stored secret")
}
return &model.Hook{
Owner: payload.Repo.Owner.UserName,
Repo: payload.Repo.Name,
Sha: payload.Commits[0].Id,
Branch: payload.Branch(),
Author: payload.Commits[0].Author.UserName,
Timestamp: time.Now().UTC().String(),
Message: payload.Commits[0].Message,
}, nil
}

View file

@ -0,0 +1,23 @@
package gogs
import (
"github.com/drone/config"
"github.com/drone/drone/plugin/remote"
)
var (
gogsUrl = config.String("gogs-url", "")
gogsSecret = config.String("gogs-secret", "")
)
// Registers the Gogs plugin using the default
// settings from the config file or environment
// variables.
func Register() {
if len(*gogsUrl) == 0 {
return
}
remote.Register(
New(*gogsUrl, *gogsSecret),
)
}

View file

@ -52,6 +52,10 @@ app.config(['$routeProvider', '$locationProvider', '$httpProvider', function($ro
templateUrl: '/static/views/login_gitlab.html', templateUrl: '/static/views/login_gitlab.html',
title: 'GitLab Login', title: 'GitLab Login',
}) })
.when('/gogs', {
templateUrl: '/static/views/login_gogs.html',
title: 'Gogs Setup',
})
.when('/setup', { .when('/setup', {
templateUrl: '/static/views/setup.html', templateUrl: '/static/views/setup.html',
controller: 'SetupController', controller: 'SetupController',
@ -234,6 +238,6 @@ app.controller("AccountReposController", function($scope, $http, $location, user
return true; return true;
}; };
$scope.byRemote = function(entry){ $scope.byRemote = function(entry){
return $scope.remote == "" || $scope.remote == entry.remote; return $scope.remote == "" || $scope.remote == entry.remote;
}; };
}); });

View file

@ -30,6 +30,9 @@ angular.module('app').controller("ConfigController", function($scope, $http, rem
case 'stash.atlassian.com': case 'stash.atlassian.com':
$scope.stash = remote; $scope.stash = remote;
break; break;
case 'gogs':
$scope.gogs = remote;
break;
} }
} }
}) })

View file

@ -144,6 +144,7 @@
case 'enterprise.github.com' : return 'GitHub Enterprise'; case 'enterprise.github.com' : return 'GitHub Enterprise';
case 'bitbucket.org' : return 'Bitbucket'; case 'bitbucket.org' : return 'Bitbucket';
case 'stash.atlassian.com' : return 'Atlassian Stash'; case 'stash.atlassian.com' : return 'Atlassian Stash';
case 'gogs' : return 'Gogs';
} }
} }
} }
@ -160,6 +161,7 @@
case 'enterprise.github.com' : return 'fa-github-square'; case 'enterprise.github.com' : return 'fa-github-square';
case 'bitbucket.org' : return 'fa-bitbucket-square'; case 'bitbucket.org' : return 'fa-bitbucket-square';
case 'stash.atlassian.com' : return 'fa-bitbucket-square'; case 'stash.atlassian.com' : return 'fa-bitbucket-square';
case 'gogs' : return 'fa-git-square';
} }
} }
} }
@ -203,4 +205,4 @@
.filter('toDuration', toDuration) .filter('toDuration', toDuration)
.filter('unique', unique); .filter('unique', unique);
})(); })();

View file

@ -16,8 +16,17 @@
</strong> </strong>
</dd> </dd>
<!-- /BITBUCKET --> <!-- /BITBUCKET -->
<!-- GOGS -->
<dd class="large" ng-if="repo.remote == 'gogs' ">
<strong>
commit
<a href="{{ repo.url }}/commit/{{ commit.sha }}" >{{ commit.sha | shortHash}}</a>
to <a href="{{ repo.url }}/src/{{ commit.branch }}">{{ commit.branch }}</a> branch
</strong>
</dd>
<!-- /GOGS -->
<!-- STASH --> <!-- STASH -->
<dd class="large" ng-if="repo.remote != 'gitlab.com' && repo.remote != 'github.com' && repo.remote != 'enterprise.github.com' && repo.remote != 'bitbucket.org' "> <dd class="large" ng-if="repo.remote != 'gitlab.com' && repo.remote != 'github.com' && repo.remote != 'enterprise.github.com' && repo.remote != 'bitbucket.org' && repo.remote != 'gogs' ">
<strong>commit <u>{{ commit.sha | shortHash}}</u> to <u>{{ commit.branch }}</u> branch</strong> <strong>commit <u>{{ commit.sha | shortHash}}</u> to <u>{{ commit.branch }}</u> branch</strong>
</dd> </dd>
<!-- /STASH --> <!-- /STASH -->

View file

@ -16,14 +16,14 @@
</div> </div>
</div> </div>
<div ng-if="remote.type != 'github.com' && remote.type != 'bitbucket.org' "> <div ng-if="remote.type != 'github.com' && remote.type != 'bitbucket.org' && remote.type != 'gogs' ">
<label>API URL</label> <label>API URL</label>
<div ng-switch="remote.type"> <div ng-switch="remote.type">
<input ng-switch-default ng-model="remote.api" type="text" placeholder="https://www.foo.com/api" /> <input ng-switch-default ng-model="remote.api" type="text" placeholder="https://www.foo.com/api" />
</div> </div>
</div> </div>
<div ng-if="remote.type != 'gitlab.com'"> <div ng-if="remote.type != 'gitlab.com' && remote.type != 'gogs' ">
<label>OAuth Client</label> <label>OAuth Client</label>
<div> <div>
<input type="text" ng-model="remote.client" /> <input type="text" ng-model="remote.client" />

View file

@ -1,4 +1,4 @@
<!-- <!--
minor modifications to the style that only apply to this view minor modifications to the style that only apply to this view
--> -->
<style> <style>
@ -11,12 +11,15 @@ minor modifications to the style that only apply to this view
<article id="loginpage"> <article id="loginpage">
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1" ng-if="state == 1 && remotes.length != 0" ng-repeat="remote in remotes"> <div class="pure-u-1" ng-if="state == 1 && remotes.length != 0" ng-repeat="remote in remotes">
<a ng-href="/api/auth/{{ remote.type }}" target="_self" ng-if="remote.type != 'gitlab.com' "> <a ng-href="/api/auth/{{ remote.type }}" target="_self" ng-if="remote.type != 'gitlab.com' && remote.type != 'gogs' ">
<i class="fa {{ remote.type | remoteIcon }}"></i> {{ remote.type | remoteName }} <i class="fa {{ remote.type | remoteIcon }}"></i> {{ remote.type | remoteName }}
</a> </a>
<a ng-href="/gitlab" ng-if="remote.type == 'gitlab.com' "> <a ng-href="/gitlab" ng-if="remote.type == 'gitlab.com' ">
<i class="fa {{ remote.type | remoteIcon }}"></i> {{ remote.type | remoteName }} <i class="fa {{ remote.type | remoteIcon }}"></i> {{ remote.type | remoteName }}
</a> </a>
<a ng-href="/gogs" ng-if="remote.type == 'gogs' ">
<i class="fa {{ remote.type | remoteIcon }}"></i> {{ remote.type | remoteName }}
</a>
</div> </div>
<div class="pure-u-1" ng-if="state == 1 && remotes.length == 0"> <div class="pure-u-1" ng-if="state == 1 && remotes.length == 0">
@ -25,4 +28,4 @@ minor modifications to the style that only apply to this view
</a> </a>
</div> </div>
</div> </div>
</article> </article>

View file

@ -0,0 +1,23 @@
<!--
minor modifications to the style that only apply to this view
-->
<style>
#container { padding-top: 155px; }
#header { height: 150px; }
#header .user { display:none; }
#header .brand { margin-top:55px ; }
</style>
<article id="loginpage">
<form class="pure-g" method="POST" action="/api/auth/gogs">
<div class="pure-u-1">
<input type="text" name="username" placeholder="Username" />
</div>
<div class="pure-u-1">
<input type="password" name="password" placeholder="Password" />
</div>
<div class="pure-u-1">
<input type="submit" value="Gogs Login" />
</div>
</form>
</article>

View file

@ -25,6 +25,7 @@
<option value="gitlab.com">GitLab</option> <option value="gitlab.com">GitLab</option>
<option value="bitbucket.org">Bitbucket</option> <option value="bitbucket.org">Bitbucket</option>
<option value="stash.atlassian.com">Stash</option> <option value="stash.atlassian.com">Stash</option>
<option value="gogs">Gogs</option>
</select> </select>
<label for="username">Username</label> <label for="username">Username</label>
@ -35,4 +36,4 @@
</div> </div>
</div> </div>
</section> </section>
</article> </article>

View file

@ -24,6 +24,7 @@ import (
"github.com/drone/drone/plugin/remote/bitbucket" "github.com/drone/drone/plugin/remote/bitbucket"
"github.com/drone/drone/plugin/remote/github" "github.com/drone/drone/plugin/remote/github"
"github.com/drone/drone/plugin/remote/gitlab" "github.com/drone/drone/plugin/remote/gitlab"
"github.com/drone/drone/plugin/remote/gogs"
"github.com/drone/drone/server/blobstore" "github.com/drone/drone/server/blobstore"
"github.com/drone/drone/server/capability" "github.com/drone/drone/server/capability"
"github.com/drone/drone/server/datastore" "github.com/drone/drone/server/datastore"
@ -97,6 +98,7 @@ func main() {
bitbucket.Register() bitbucket.Register()
github.Register() github.Register()
gitlab.Register() gitlab.Register()
gogs.Register()
caps = map[string]bool{} caps = map[string]bool{}
caps[capability.Registration] = *open caps[capability.Registration] = *open

View file

@ -6,6 +6,7 @@ const (
RemoteGithubEnterprise = "enterprise.github.com" RemoteGithubEnterprise = "enterprise.github.com"
RemoteBitbucket = "bitbucket.org" RemoteBitbucket = "bitbucket.org"
RemoteStash = "stash.atlassian.com" RemoteStash = "stash.atlassian.com"
RemoteGogs = "gogs"
) )
type Remote struct { type Remote struct {