refactoring input and configuration

This commit is contained in:
Brad Rydzewski 2016-04-29 12:39:56 -07:00
parent 4d4003a9a1
commit 082570fb5b
34 changed files with 1784 additions and 1345 deletions

View file

@ -1,13 +1,11 @@
package api
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"github.com/gin-gonic/gin"
"gopkg.in/yaml.v2"
"github.com/drone/drone/cache"
"github.com/drone/drone/model"
@ -210,44 +208,3 @@ func DeleteRepo(c *gin.Context) {
remote.Deactivate(user, repo, httputil.GetURL(c.Request))
c.Writer.WriteHeader(http.StatusOK)
}
func PostSecure(c *gin.Context) {
repo := session.Repo(c)
in, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
// we found some strange characters included in
// the yaml file when entered into a browser textarea.
// these need to be removed
in = bytes.Replace(in, []byte{'\xA0'}, []byte{' '}, -1)
// make sure the Yaml is valid format to prevent
// a malformed value from being used in the build
err = yaml.Unmarshal(in, &yaml.MapSlice{})
if err != nil {
c.String(http.StatusBadRequest, err.Error())
return
}
key, err := store.GetKey(c, repo)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
// encrypts using go-jose
out, err := crypto.Encrypt(string(in), key.Private)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.String(http.StatusOK, out)
}
func PostReactivate(c *gin.Context) {
}

View file

@ -141,9 +141,9 @@ func start(c *cli.Context) {
c.String("drone-token"),
)
tls, _ := dockerclient.TLSConfigFromCertPath(c.String("docker-cert-path"))
if c.Bool("docker-host") {
tls.InsecureSkipVerify = true
tls, err := dockerclient.TLSConfigFromCertPath(c.String("docker-cert-path"))
if err == nil {
tls.InsecureSkipVerify = c.Bool("docker-tls-verify")
}
docker, err := dockerclient.NewDockerClient(c.String("docker-host"), tls)
if err != nil {

457
drone/daemon.go Normal file
View file

@ -0,0 +1,457 @@
package main
import (
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/drone/drone/bus"
"github.com/drone/drone/cache"
"github.com/drone/drone/queue"
"github.com/drone/drone/remote"
"github.com/drone/drone/remote/bitbucket"
"github.com/drone/drone/remote/bitbucketserver"
"github.com/drone/drone/remote/github"
"github.com/drone/drone/remote/gitlab"
"github.com/drone/drone/remote/gogs"
"github.com/drone/drone/server"
"github.com/drone/drone/shared/token"
"github.com/drone/drone/store"
"github.com/drone/drone/store/datastore"
"github.com/drone/drone/stream"
"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
)
// DaemonCmd is the exported command for starting the drone server daemon.
var DaemonCmd = cli.Command{
Name: "daemon",
Usage: "starts the drone server daemon",
Action: func(c *cli.Context) {
if err := start(c); err != nil {
logrus.Fatal(err)
}
},
Flags: []cli.Flag{
cli.BoolFlag{
EnvVar: "DRONE_DEBUG",
Name: "debug",
Usage: "start the server in debug mode",
},
cli.StringFlag{
EnvVar: "DRONE_SERVER_ADDR",
Name: "server-addr",
Usage: "server address",
Value: ":8000",
},
cli.StringFlag{
EnvVar: "DRONE_SERVER_CERT",
Name: "server-cert",
Usage: "server ssl cert",
},
cli.StringFlag{
EnvVar: "DRONE_SERVER_KEY",
Name: "server-key",
Usage: "server ssl key",
},
cli.StringSliceFlag{
EnvVar: "DRONE_ADMIN",
Name: "admin",
Usage: "list of admin users",
},
cli.StringSliceFlag{
EnvVar: "DRONE_ORGS",
Name: "orgs",
Usage: "list of approved organizations",
},
cli.BoolFlag{
EnvVar: "DRONE_OPEN",
Name: "open",
Usage: "open user registration",
},
cli.StringFlag{
EnvVar: "DRONE_YAML",
Name: "yaml",
Usage: "build configuraton file name",
Value: ".drone.yml",
},
cli.DurationFlag{
EnvVar: "DRONE_CACHE_TTY",
Name: "cache-tty",
Usage: "cache duration",
Value: time.Minute * 15,
},
cli.StringFlag{
EnvVar: "DRONE_AGENT_SECRET",
Name: "agent-secret",
Usage: "agent secret passcode",
},
cli.StringFlag{
EnvVar: "DRONE_DATABASE_DRIVER,DATABASE_DRIVER",
Name: "driver",
Usage: "database driver",
Value: "sqite3",
},
cli.StringFlag{
EnvVar: "DRONE_DATABASE_DATASOURCE,DATABASE_CONFIG",
Name: "datasource",
Usage: "database driver configuration string",
Value: "drone.sqlite",
},
cli.BoolFlag{
EnvVar: "DRONE_GITHUB",
Name: "github",
Usage: "github driver is enabled",
},
cli.StringFlag{
EnvVar: "DRONE_GITHUB_URL",
Name: "github-server",
Usage: "github server address",
Value: "https://github.com",
},
cli.StringFlag{
EnvVar: "DRONE_GITHUB_CLIENT",
Name: "github-client",
Usage: "github oauth2 client id",
},
cli.StringFlag{
EnvVar: "DRONE_GITHUB_SECRET",
Name: "github-sercret",
Usage: "github oauth2 client secret",
},
cli.StringSliceFlag{
EnvVar: "DRONE_GITHUB_SCOPE",
Name: "github-scope",
Usage: "github oauth scope",
Value: &cli.StringSlice{
"repo",
"repo:status",
"user:email",
"read:org",
},
},
cli.BoolTFlag{
EnvVar: "DRONE_GITHUB_MERGE_REF",
Name: "github-merge-ref",
Usage: "github pull requests use merge ref",
},
cli.BoolFlag{
EnvVar: "DRONE_GITHUB_PRIVATE_MODE",
Name: "github-private-mode",
Usage: "github is running in private mode",
},
cli.BoolFlag{
EnvVar: "DRONE_GITHUB_SKIP_VERIFY",
Name: "github-skip-verify",
Usage: "github skip ssl verification",
},
cli.BoolFlag{
EnvVar: "DRONE_GOGS",
Name: "gogs",
Usage: "gogs driver is enabled",
},
cli.StringFlag{
EnvVar: "DRONE_GOGS_URL",
Name: "gogs-server",
Usage: "gogs server address",
Value: "https://github.com",
},
cli.BoolFlag{
EnvVar: "DRONE_GOGS_PRIVATE_MODE",
Name: "gogs-private-mode",
Usage: "gogs private mode enabled",
},
cli.BoolFlag{
EnvVar: "DRONE_GOGS_SKIP_VERIFY",
Name: "gogs-skip-verify",
Usage: "gogs skip ssl verification",
},
cli.BoolFlag{
EnvVar: "DRONE_BITBUCKET",
Name: "bitbucket",
Usage: "bitbucket driver is enabled",
},
cli.StringFlag{
EnvVar: "DRONE_BITBUCKET_CLIENT",
Name: "bitbucket-client",
Usage: "bitbucket oauth2 client id",
},
cli.StringFlag{
EnvVar: "DRONE_BITBUCKET_SECRET",
Name: "bitbucket-secret",
Usage: "bitbucket oauth2 client secret",
},
cli.BoolFlag{
EnvVar: "DRONE_GITLAB",
Name: "gitlab",
Usage: "gitlab driver is enabled",
},
cli.StringFlag{
EnvVar: "DRONE_GITLAB_URL",
Name: "gitlab-server",
Usage: "gitlab server address",
Value: "https://gitlab.com",
},
cli.StringFlag{
EnvVar: "DRONE_GITLAB_CLIENT",
Name: "gitlab-client",
Usage: "gitlab oauth2 client id",
},
cli.StringFlag{
EnvVar: "DRONE_GITLAB_SECRET",
Name: "gitlab-sercret",
Usage: "gitlab oauth2 client secret",
},
cli.BoolFlag{
EnvVar: "DRONE_GITLAB_SKIP_VERIFY",
Name: "gitlab-skip-verify",
Usage: "gitlab skip ssl verification",
},
cli.BoolFlag{
EnvVar: "DRONE_GITLAB_PRIVATE_MODE",
Name: "gitlab-private-mode",
Usage: "gitlab is running in private mode",
},
cli.BoolFlag{
EnvVar: "DRONE_STASH",
Name: "stash",
Usage: "stash driver is enabled",
},
cli.StringFlag{
EnvVar: "DRONE_STASH_URL",
Name: "stash-server",
Usage: "stash server address",
},
cli.StringFlag{
EnvVar: "DRONE_STASH_CONSUMER_KEY",
Name: "stash-consumer-key",
Usage: "stash oauth1 consumer key",
},
cli.StringFlag{
EnvVar: "DRONE_STASH_CONSUMER_RSA",
Name: "stash-consumer-rsa",
Usage: "stash oauth1 private key file",
},
cli.StringFlag{
EnvVar: "DRONE_STASH_GIT_USERNAME",
Name: "stash-git-username",
Usage: "stash service account username",
},
cli.StringFlag{
EnvVar: "DRONE_STASH_GIT_PASSWORD",
Name: "stash-git-password",
Usage: "stash service account password",
},
//
// remove these eventually
//
cli.BoolFlag{
Name: "agreement.ack",
EnvVar: "I_UNDERSTAND_I_AM_USING_AN_UNSTABLE_VERSION",
Usage: "agree to terms of use.",
},
cli.BoolFlag{
Name: "agreement.fix",
EnvVar: "I_AGREE_TO_FIX_BUGS_AND_NOT_FILE_BUGS",
Usage: "agree to terms of use.",
},
},
}
func start(c *cli.Context) error {
if c.Bool("agreement.ack") == false || c.Bool("agreement.fix") == false {
fmt.Println(agreement)
os.Exit(1)
}
// debug level if requested by user
if c.Bool("debug") {
logrus.SetLevel(logrus.DebugLevel)
} else {
logrus.SetLevel(logrus.WarnLevel)
}
// print the agent secret to the console
// TODO(bradrydzewski) this overall approach should be re-considered
if err := printSecret(c); err != nil {
return err
}
// setup the server and start the listener
server := server.Server{
Bus: setupBus(c),
Cache: setupCache(c),
Config: setupConfig(c),
Queue: setupQueue(c),
Remote: setupRemote(c),
Stream: setupStream(c),
Store: setupStore(c),
}
// start the server with tls enabled
if c.String("server-cert") != "" {
return http.ListenAndServeTLS(
c.String("server-addr"),
c.String("server-cert"),
c.String("server-key"),
server.Handler(),
)
}
// start the server without tls enabled
return http.ListenAndServe(
c.String("server-addr"),
server.Handler(),
)
}
func setupConfig(c *cli.Context) *server.Config {
return &server.Config{
Open: c.Bool("open"),
Yaml: c.String("yaml"),
Secret: c.String("agent-secret"),
Admins: c.StringSlice("admin"),
Orgs: c.StringSlice("orgs"),
}
}
func setupCache(c *cli.Context) cache.Cache {
return cache.NewTTL(
c.Duration("cache-ttl"),
)
}
func setupBus(c *cli.Context) bus.Bus {
return bus.New()
}
func setupQueue(c *cli.Context) queue.Queue {
return queue.New()
}
func setupStream(c *cli.Context) stream.Stream {
return stream.New()
}
func setupStore(c *cli.Context) store.Store {
return datastore.New(
c.String("driver"),
c.String("datasource"),
)
}
func setupRemote(c *cli.Context) remote.Remote {
switch {
case c.Bool("github"):
return setupGithub(c)
case c.Bool("gitlab"):
return setupGitlab(c)
case c.Bool("bitbucket"):
return setupBitbucket(c)
case c.Bool("stash"):
return setupStash(c)
case c.Bool("gogs"):
return setupGogs(c)
default:
logrus.Fatalln("version control system not configured")
return nil
}
}
func setupBitbucket(c *cli.Context) remote.Remote {
return bitbucket.New(
c.String("bitbucket-client"),
c.String("bitbucket-server"),
)
}
func setupGogs(c *cli.Context) remote.Remote {
return gogs.New(
c.String("gogs-server"),
c.Bool("gogs-private-mode"),
c.Bool("gogs-skip-verify"),
)
}
func setupStash(c *cli.Context) remote.Remote {
return bitbucketserver.New(
c.String("stash-server"),
c.String("stash-consumer-key"),
c.String("stash-consumer-rsa"),
c.String("stash-git-username"),
c.String("stash-git-password"),
)
}
func setupGitlab(c *cli.Context) remote.Remote {
return gitlab.New(
c.String("gitlab-server"),
c.String("gitlab-client"),
c.String("gitlab-sercret"),
c.Bool("gitlab-private-mode"),
c.Bool("gitlab-skip-verify"),
)
}
func setupGithub(c *cli.Context) remote.Remote {
g, err := github.New(
c.String("github-server"),
c.String("github-client"),
c.String("github-sercret"),
c.StringSlice("github-scope"),
c.Bool("github-private-mode"),
c.Bool("github-skip-verify"),
c.BoolT("github-merge-ref"),
)
if err != nil {
log.Fatalln(err)
}
return g
}
func printSecret(c *cli.Context) error {
secret := c.String("agent-secret")
if secret == "" {
return fmt.Errorf("missing DRONE_AGENT_SECRET configuration parameter")
}
t := token.New(secret, "")
s, err := t.Sign(secret)
if err != nil {
return fmt.Errorf("invalid value for DRONE_AGENT_SECRET. %s", s)
}
logrus.Infof("using agent secret %s", secret)
logrus.Warnf("agents can connect with token %s", s)
return nil
}
var agreement = `
---
You are attempting to use the unstable channel. This build is experimental and
has known bugs and compatibility issues. It is not intended for general use.
Please consider using the latest stable release instead:
drone/drone:0.4.2
If you are attempting to build from source please use the latest stable tag:
v0.4.2
If you are interested in testing this experimental build AND assisting with
development you may proceed by setting the following environment:
I_UNDERSTAND_I_AM_USING_AN_UNSTABLE_VERSION=true
I_AGREE_TO_FIX_BUGS_AND_NOT_FILE_BUGS=true
---
`

View file

@ -4,7 +4,6 @@ import (
"os"
"github.com/drone/drone/drone/agent"
"github.com/drone/drone/drone/server"
"github.com/drone/drone/version"
"github.com/codegangsta/cli"
@ -35,7 +34,7 @@ func main() {
}
app.Commands = []cli.Command{
agent.AgentCmd,
server.ServeCmd,
DaemonCmd,
SignCmd,
SecretCmd,
}

View file

@ -1,129 +0,0 @@
package server
import (
"net/http"
"os"
"time"
"github.com/drone/drone/router"
"github.com/drone/drone/router/middleware"
"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/gin-gonic/contrib/ginrus"
)
// ServeCmd is the exported command for starting the drone server.
var ServeCmd = cli.Command{
Name: "serve",
Usage: "starts the drone server",
Action: func(c *cli.Context) {
if err := start(c); err != nil {
logrus.Fatal(err)
}
},
Flags: []cli.Flag{
cli.StringFlag{
EnvVar: "SERVER_ADDR",
Name: "server-addr",
Usage: "server address",
Value: ":8000",
},
cli.StringFlag{
EnvVar: "SERVER_CERT",
Name: "server-cert",
Usage: "server ssl cert",
},
cli.StringFlag{
EnvVar: "SERVER_KEY",
Name: "server-key",
Usage: "server ssl key",
},
cli.BoolFlag{
EnvVar: "DEBUG",
Name: "debug",
Usage: "start the server in debug mode",
},
cli.BoolFlag{
EnvVar: "EXPERIMENTAL",
Name: "experimental",
Usage: "start the server with experimental features",
},
cli.BoolFlag{
Name: "agreement.ack",
EnvVar: "I_UNDERSTAND_I_AM_USING_AN_UNSTABLE_VERSION",
Usage: "agree to terms of use.",
},
cli.BoolFlag{
Name: "agreement.fix",
EnvVar: "I_AGREE_TO_FIX_BUGS_AND_NOT_FILE_BUGS",
Usage: "agree to terms of use.",
},
},
}
func start(c *cli.Context) error {
if c.Bool("agreement.ack") == false || c.Bool("agreement.fix") == false {
println(agreement)
os.Exit(1)
}
// debug level if requested by user
if c.Bool("debug") {
logrus.SetLevel(logrus.DebugLevel)
} else {
logrus.SetLevel(logrus.WarnLevel)
}
// setup the server and start the listener
handler := router.Load(
ginrus.Ginrus(logrus.StandardLogger(), time.RFC3339, true),
middleware.Version,
middleware.Queue(),
middleware.Stream(),
middleware.Bus(),
middleware.Cache(),
middleware.Store(),
middleware.Remote(),
)
if c.String("server-cert") != "" {
return http.ListenAndServeTLS(
c.String("server-addr"),
c.String("server-cert"),
c.String("server-key"),
handler,
)
}
return http.ListenAndServe(
c.String("server-addr"),
handler,
)
}
var agreement = `
---
You are attempting to use the unstable channel. This build is experimental and
has known bugs and compatibility issues. It is not intended for general use.
Please consider using the latest stable release instead:
drone/drone:0.4.2
If you are attempting to build from source please use the latest stable tag:
v0.4.2
If you are interested in testing this experimental build AND assisting with
development you may proceed by setting the following environment:
I_UNDERSTAND_I_AM_USING_AN_UNSTABLE_VERSION=true
I_AGREE_TO_FIX_BUGS_AND_NOT_FILE_BUGS=true
---
`

12
model/team.go Normal file
View file

@ -0,0 +1,12 @@
package model
// Team represents a team or organization in the remote version control system.
//
// swagger:model user
type Team struct {
// Login is the username for this team.
Login string `json:"login"`
// the avatar url for this team.
Avatar string `json:"avatar_url"`
}

View file

@ -6,128 +6,79 @@ import (
"io/ioutil"
"net/http"
"net/url"
"strconv"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/remote/bitbucket/internal"
"github.com/drone/drone/shared/httputil"
log "github.com/Sirupsen/logrus"
"golang.org/x/oauth2"
"golang.org/x/oauth2/bitbucket"
)
type Bitbucket struct {
type config struct {
Client string
Secret string
Orgs []string
Open bool
}
func Load(config string) *Bitbucket {
// parse the remote DSN configuration string
url_, err := url.Parse(config)
if err != nil {
log.Fatalln("unable to parse remote dsn. %s", err)
// New returns a new remote Configuration for integrating with the Bitbucket
// repository hosting service at https://bitbucket.org
func New(client, secret string) remote.Remote {
return &config{
Client: client,
Secret: secret,
}
params := url_.Query()
url_.Path = ""
url_.RawQuery = ""
// create the Githbub remote using parameters from
// the parsed DSN configuration string.
bitbucket := Bitbucket{}
bitbucket.Client = params.Get("client_id")
bitbucket.Secret = params.Get("client_secret")
bitbucket.Orgs = params["orgs"]
bitbucket.Open, _ = strconv.ParseBool(params.Get("open"))
return &bitbucket
}
// Login authenticates the session and returns the
// remote user details.
func (bb *Bitbucket) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
// helper function to return the bitbucket oauth2 client
func (c *config) newClient(u *model.User) *internal.Client {
return internal.NewClientToken(
c.Client,
c.Secret,
&oauth2.Token{
AccessToken: u.Token,
RefreshToken: u.Secret,
},
)
}
func (c *config) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
config := &oauth2.Config{
ClientID: bb.Client,
ClientSecret: bb.Secret,
ClientID: c.Client,
ClientSecret: c.Secret,
Endpoint: bitbucket.Endpoint,
RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)),
}
// get the OAuth code
var code = req.FormValue("code")
if len(code) == 0 {
http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther)
return nil, false, nil
return nil, nil
}
var token, err = config.Exchange(oauth2.NoContext, code)
if err != nil {
return nil, false, fmt.Errorf("Error exchanging token. %s", err)
return nil, err
}
client := NewClient(config.Client(oauth2.NoContext, token))
client := internal.NewClient(config.Client(oauth2.NoContext, token))
curr, err := client.FindCurrent()
if err != nil {
return nil, false, err
return nil, err
}
// convers the current bitbucket user to the
// common drone user structure.
user := model.User{}
user.Login = curr.Login
user.Token = token.AccessToken
user.Secret = token.RefreshToken
user.Expiry = token.Expiry.UTC().Unix()
user.Avatar = curr.Links.Avatar.Href
// gets the primary, confirmed email from bitbucket
emails, err := client.ListEmail()
if err != nil {
return nil, false, err
}
for _, email := range emails.Values {
if email.IsPrimary && email.IsConfirmed {
user.Email = email.Email
break
}
}
// if the installation is restricted to a subset
// of organizations, get the orgs and verify the
// user is a member.
if len(bb.Orgs) != 0 {
resp, err := client.ListTeams(&ListTeamOpts{Page: 1, PageLen: 100, Role: "member"})
if err != nil {
return nil, false, err
}
var member bool
for _, team := range resp.Values {
for _, team_ := range bb.Orgs {
if team.Login == team_ {
member = true
break
}
}
}
if !member {
return nil, false, fmt.Errorf("User does not belong to correct org. Must belong to %v", bb.Orgs)
}
}
return &user, bb.Open, nil
return convertUser(curr, token), nil
}
// Auth authenticates the session and returns the remote user
// login for the given token and secret
func (bb *Bitbucket) Auth(token, secret string) (string, error) {
token_ := oauth2.Token{AccessToken: token, RefreshToken: secret}
client := NewClientToken(bb.Client, bb.Secret, &token_)
func (c *config) Auth(token, secret string) (string, error) {
client := internal.NewClientToken(
c.Client,
c.Secret,
&oauth2.Token{
AccessToken: token,
RefreshToken: secret,
},
)
user, err := client.FindCurrent()
if err != nil {
return "", err
@ -135,13 +86,10 @@ func (bb *Bitbucket) Auth(token, secret string) (string, error) {
return user.Login, nil
}
// Refresh refreshes an oauth token and expiration for the given
// user. It returns true if the token was refreshed, false if the
// token was not refreshed, and error if it failed to refersh.
func (bb *Bitbucket) Refresh(user *model.User) (bool, error) {
func (c *config) Refresh(user *model.User) (bool, error) {
config := &oauth2.Config{
ClientID: bb.Client,
ClientSecret: bb.Secret,
ClientID: c.Client,
ClientSecret: c.Secret,
Endpoint: bitbucket.Endpoint,
}
@ -165,28 +113,35 @@ func (bb *Bitbucket) Refresh(user *model.User) (bool, error) {
return true, nil
}
// Repo fetches the named repository from the remote system.
func (bb *Bitbucket) Repo(u *model.User, owner, name string) (*model.Repo, error) {
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
client := NewClientToken(bb.Client, bb.Secret, &token)
func (c *config) Teams(u *model.User) ([]*model.Team, error) {
opts := &internal.ListTeamOpts{
PageLen: 100,
Role: "member",
}
resp, err := c.newClient(u).ListTeams(opts)
if err != nil {
return nil, err
}
return convertTeamList(resp.Values), nil
}
repo, err := client.FindRepo(owner, name)
func (c *config) Repo(u *model.User, owner, name string) (*model.Repo, error) {
repo, err := c.newClient(u).FindRepo(owner, name)
if err != nil {
return nil, err
}
return convertRepo(repo), nil
}
// Repos fetches a list of repos from the remote system.
func (bb *Bitbucket) Repos(u *model.User) ([]*model.RepoLite, error) {
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
client := NewClientToken(bb.Client, bb.Secret, &token)
func (c *config) Repos(u *model.User) ([]*model.RepoLite, error) {
client := c.newClient(u)
var repos []*model.RepoLite
// gets a list of all accounts to query, including the
// user's account and all team accounts.
logins := []string{u.Login}
resp, err := client.ListTeams(&ListTeamOpts{PageLen: 100, Role: "member"})
resp, err := client.ListTeams(&internal.ListTeamOpts{PageLen: 100, Role: "member"})
if err != nil {
return repos, err
}
@ -208,11 +163,8 @@ func (bb *Bitbucket) Repos(u *model.User) ([]*model.RepoLite, error) {
return repos, nil
}
// Perm fetches the named repository permissions from
// the remote system for the specified user.
func (bb *Bitbucket) Perm(u *model.User, owner, name string) (*model.Perm, error) {
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
client := NewClientToken(bb.Client, bb.Secret, &token)
func (c *config) Perm(u *model.User, owner, name string) (*model.Perm, error) {
client := c.newClient(u)
perms := new(model.Perm)
_, err := client.FindRepo(owner, name)
@ -220,69 +172,39 @@ func (bb *Bitbucket) Perm(u *model.User, owner, name string) (*model.Perm, error
return perms, err
}
// if we've gotten this far we know that the user at
// least has read access to the repository.
// if we've gotten this far we know that the user at least has read access
// to the repository.
perms.Pull = true
// if the user has access to the repository hooks we
// can deduce that the user has push and admin access.
_, err = client.ListHooks(owner, name, &ListOpts{})
// if the user has access to the repository hooks we can deduce that the user
// has push and admin access.
_, err = client.ListHooks(owner, name, &internal.ListOpts{})
if err == nil {
perms.Push = true
perms.Admin = true
}
return perms, nil
}
// File fetches a file from the remote repository and returns in string format.
func (bb *Bitbucket) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
client := NewClientToken(
bb.Client,
bb.Secret,
&oauth2.Token{
AccessToken: u.Token,
RefreshToken: u.Secret,
},
)
config, err := client.FindSource(r.Owner, r.Name, b.Commit, f)
func (c *config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
config, err := c.newClient(u).FindSource(r.Owner, r.Name, b.Commit, f)
if err != nil {
return nil, err
}
return []byte(config.Data), err
}
// Status sends the commit status to the remote system.
// An example would be the GitHub pull request status.
func (bb *Bitbucket) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
client := NewClientToken(
bb.Client,
bb.Secret,
&oauth2.Token{
AccessToken: u.Token,
RefreshToken: u.Secret,
},
)
status := getStatus(b.Status)
desc := getDesc(b.Status)
data := BuildStatus{
State: status,
func (c *config) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
status := internal.BuildStatus{
State: getStatus(b.Status),
Desc: getDesc(b.Status),
Key: "Drone",
Url: link,
Desc: desc,
}
err := client.CreateStatus(r.Owner, r.Name, b.Commit, &data)
return err
return c.newClient(u).CreateStatus(r.Owner, r.Name, b.Commit, &status)
}
// Netrc returns a .netrc file that can be used to clone
// private repositories from a remote system.
func (bb *Bitbucket) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
func (c *config) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
return &model.Netrc{
Machine: "bitbucket.org",
Login: "x-token-auth",
@ -290,113 +212,73 @@ func (bb *Bitbucket) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
}, nil
}
// Activate activates a repository by creating the post-commit hook and
// adding the SSH deploy key, if applicable.
func (bb *Bitbucket) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
client := NewClientToken(
bb.Client,
bb.Secret,
&oauth2.Token{
AccessToken: u.Token,
RefreshToken: u.Secret,
},
)
linkurl, err := url.Parse(link)
func (c *config) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
rawurl, err := url.Parse(link)
if err != nil {
log.Errorf("malformed hook url %s. %s", link, err)
return err
}
// see if the hook already exists. If yes be sure to
// delete so that multiple messages aren't sent.
hooks, _ := client.ListHooks(r.Owner, r.Name, &ListOpts{})
// deletes any previously created hooks
if err := c.Deactivate(u, r, link); err != nil {
// we can live with failure here. Things happen and manually scrubbing
// hooks is certinaly not the end of the world.
}
return c.newClient(u).CreateHook(r.Owner, r.Name, &internal.Hook{
Active: true,
Desc: rawurl.Host,
Events: []string{"repo:push"},
Url: link,
})
}
func (c *config) Deactivate(u *model.User, r *model.Repo, link string) error {
client := c.newClient(u)
linkurl, err := url.Parse(link)
if err != nil {
return err
}
hooks, err := client.ListHooks(r.Owner, r.Name, &internal.ListOpts{})
if err != nil {
return nil // we can live with undeleted hooks
}
for _, hook := range hooks.Values {
hookurl, err := url.Parse(hook.Url)
if err != nil {
continue
}
if hookurl.Host == linkurl.Host {
err = client.DeleteHook(r.Owner, r.Name, hook.Uuid)
if err != nil {
log.Errorf("unable to delete hook %s. %s", hookurl.Host, err)
}
break
}
}
err = client.CreateHook(r.Owner, r.Name, &Hook{
Active: true,
Desc: linkurl.Host,
Events: []string{"repo:push"},
Url: link,
})
if err != nil {
log.Errorf("unable to create hook %s. %s", link, err)
}
return err
}
// Deactivate removes a repository by removing all the post-commit hooks
// which are equal to link and removing the SSH deploy key.
func (bb *Bitbucket) Deactivate(u *model.User, r *model.Repo, link string) error {
client := NewClientToken(
bb.Client,
bb.Secret,
&oauth2.Token{
AccessToken: u.Token,
RefreshToken: u.Secret,
},
)
linkurl, err := url.Parse(link)
if err != nil {
return err
}
// see if the hook already exists. If yes be sure to
// delete so that multiple messages aren't sent.
hooks, _ := client.ListHooks(r.Owner, r.Name, &ListOpts{})
for _, hook := range hooks.Values {
hookurl, err := url.Parse(hook.Url)
if err != nil {
return err
}
if hookurl.Host == linkurl.Host {
client.DeleteHook(r.Owner, r.Name, hook.Uuid)
break
break // we can live with undeleted hooks
}
}
return nil
}
// Hook parses the post-commit hook from the Request body
// and returns the required data in a standard format.
func (bb *Bitbucket) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
func (c *config) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
switch r.Header.Get("X-Event-Key") {
case "repo:push":
return bb.pushHook(r)
return c.pushHook(r)
case "pullrequest:created", "pullrequest:updated":
return bb.pullHook(r)
return c.pullHook(r)
}
return nil, nil, nil
}
func (bb *Bitbucket) String() string {
return "bitbucket"
}
func (bb *Bitbucket) pushHook(r *http.Request) (*model.Repo, *model.Build, error) {
func (c *config) pushHook(r *http.Request) (*model.Repo, *model.Build, error) {
payload := []byte(r.FormValue("payload"))
if len(payload) == 0 {
defer r.Body.Close()
payload, _ = ioutil.ReadAll(r.Body)
}
hook := PushHook{}
hook := internal.PushHook{}
err := json.Unmarshal(payload, &hook)
if err != nil {
return nil, nil, err
@ -423,6 +305,7 @@ func (bb *Bitbucket) pushHook(r *http.Request) (*model.Repo, *model.Build, error
// return the updated repository information and the
// build information.
// TODO(bradrydzewski) uses unit tested conversion function
return convertRepo(&hook.Repo), &model.Build{
Event: buildEventType,
Commit: change.New.Target.Hash,
@ -439,14 +322,14 @@ func (bb *Bitbucket) pushHook(r *http.Request) (*model.Repo, *model.Build, error
return nil, nil, nil
}
func (bb *Bitbucket) pullHook(r *http.Request) (*model.Repo, *model.Build, error) {
func (c *config) pullHook(r *http.Request) (*model.Repo, *model.Build, error) {
payload := []byte(r.FormValue("payload"))
if len(payload) == 0 {
defer r.Body.Close()
payload, _ = ioutil.ReadAll(r.Body)
}
hook := PullRequestHook{}
hook := internal.PullRequestHook{}
err := json.Unmarshal(payload, &hook)
if err != nil {
return nil, nil, err
@ -455,12 +338,13 @@ func (bb *Bitbucket) pullHook(r *http.Request) (*model.Repo, *model.Build, error
return nil, nil, nil
}
// TODO(bradrydzewski) uses unit tested conversion function
return convertRepo(&hook.Repo), &model.Build{
Event: model.EventPull,
Commit: hook.PullRequest.Dest.Commit.Hash,
Ref: fmt.Sprintf("refs/heads/%s", hook.PullRequest.Dest.Branch.Name),
Refspec: fmt.Sprintf("https://bitbucket.org/%s.git", hook.PullRequest.Source.Repo.FullName),
Remote: cloneLink(hook.PullRequest.Dest.Repo),
Remote: cloneLink(&hook.PullRequest.Dest.Repo),
Link: hook.PullRequest.Links.Html.Href,
Branch: hook.PullRequest.Dest.Branch.Name,
Message: hook.PullRequest.Desc,
@ -469,47 +353,3 @@ func (bb *Bitbucket) pullHook(r *http.Request) (*model.Repo, *model.Build, error
Timestamp: hook.PullRequest.Updated.UTC().Unix(),
}, nil
}
const (
StatusPending = "INPROGRESS"
StatusSuccess = "SUCCESSFUL"
StatusFailure = "FAILED"
)
const (
DescPending = "this build is pending"
DescSuccess = "the build was successful"
DescFailure = "the build failed"
DescError = "oops, something went wrong"
)
// converts a Drone status to a BitBucket status.
func getStatus(status string) string {
switch status {
case model.StatusPending, model.StatusRunning:
return StatusPending
case model.StatusSuccess:
return StatusSuccess
case model.StatusFailure, model.StatusError, model.StatusKilled:
return StatusFailure
default:
return StatusFailure
}
}
// generates a description for the build based on
// the Drone status
func getDesc(status string) string {
switch status {
case model.StatusPending, model.StatusRunning:
return DescPending
case model.StatusSuccess:
return DescSuccess
case model.StatusFailure:
return DescFailure
case model.StatusError, model.StatusKilled:
return DescError
default:
return DescError
}
}

40
remote/bitbucket/const.go Normal file
View file

@ -0,0 +1,40 @@
package bitbucket
import "github.com/drone/drone/model"
const (
statusPending = "INPROGRESS"
statusSuccess = "SUCCESSFUL"
statusFailure = "FAILED"
)
const (
descPending = "this build is pending"
descSuccess = "the build was successful"
descFailure = "the build failed"
descError = "oops, something went wrong"
)
func getStatus(status string) string {
switch status {
case model.StatusPending, model.StatusRunning:
return statusPending
case model.StatusSuccess:
return statusSuccess
default:
return statusFailure
}
}
func getDesc(status string) string {
switch status {
case model.StatusPending, model.StatusRunning:
return descPending
case model.StatusSuccess:
return descSuccess
case model.StatusFailure:
return descFailure
default:
return descError
}
}

View file

@ -0,0 +1,43 @@
package bitbucket
import (
"testing"
"github.com/drone/drone/model"
"github.com/franela/goblin"
)
func Test_status(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Bitbucket status", func() {
g.It("should return passing", func() {
g.Assert(getStatus(model.StatusSuccess)).Equal(statusSuccess)
})
g.It("should return pending", func() {
g.Assert(getStatus(model.StatusPending)).Equal(statusPending)
g.Assert(getStatus(model.StatusRunning)).Equal(statusPending)
})
g.It("should return failing", func() {
g.Assert(getStatus(model.StatusFailure)).Equal(statusFailure)
g.Assert(getStatus(model.StatusKilled)).Equal(statusFailure)
g.Assert(getStatus(model.StatusError)).Equal(statusFailure)
})
g.It("should return passing desc", func() {
g.Assert(getDesc(model.StatusSuccess)).Equal(descSuccess)
})
g.It("should return pending desc", func() {
g.Assert(getDesc(model.StatusPending)).Equal(descPending)
g.Assert(getDesc(model.StatusRunning)).Equal(descPending)
})
g.It("should return failing desc", func() {
g.Assert(getDesc(model.StatusFailure)).Equal(descFailure)
})
g.It("should return error desc", func() {
g.Assert(getDesc(model.StatusKilled)).Equal(descError)
g.Assert(getDesc(model.StatusError)).Equal(descError)
})
})
}

View file

@ -5,12 +5,16 @@ import (
"strings"
"github.com/drone/drone/model"
"github.com/drone/drone/remote/bitbucket/internal"
"golang.org/x/oauth2"
)
// convertRepo is a helper function used to convert a Bitbucket
// repository structure to the common Drone repository structure.
func convertRepo(from *Repo) *model.Repo {
// convertRepo is a helper function used to convert a Bitbucket repository
// structure to the common Drone repository structure.
func convertRepo(from *internal.Repo) *model.Repo {
repo := model.Repo{
Clone: cloneLink(from),
Owner: strings.Split(from.FullName, "/")[0],
Name: strings.Split(from.FullName, "/")[1],
FullName: from.FullName,
@ -20,66 +24,34 @@ func convertRepo(from *Repo) *model.Repo {
Kind: from.Scm,
Branch: "master",
}
if repo.Kind == model.RepoHg {
repo.Branch = "default"
}
// in some cases, the owner of the repository is not
// provided, however, we do have the full name.
if len(repo.Owner) == 0 {
repo.Owner = strings.Split(repo.FullName, "/")[0]
}
// above we manually constructed the repository clone url.
// below we will iterate through the list of clone links and
// attempt to instead use the clone url provided by bitbucket.
for _, link := range from.Links.Clone {
if link.Name == "https" {
repo.Clone = link.Href
break
}
}
// if no repository name is provided, we use the Html link.
// this excludes the .git suffix, but will still clone the repo.
if len(repo.Clone) == 0 {
repo.Clone = repo.Link
}
// if bitbucket tries to automatically populate the user
// in the url we must strip it out.
clone, err := url.Parse(repo.Clone)
if err == nil {
clone.User = nil
repo.Clone = clone.String()
}
return &repo
}
// cloneLink is a helper function that tries to extract the
// clone url from the repository object.
func cloneLink(repo Repo) string {
// cloneLink is a helper function that tries to extract the clone url from the
// repository object.
func cloneLink(repo *internal.Repo) string {
var clone string
// above we manually constructed the repository clone url.
// below we will iterate through the list of clone links and
// attempt to instead use the clone url provided by bitbucket.
// above we manually constructed the repository clone url. below we will
// iterate through the list of clone links and attempt to instead use the
// clone url provided by bitbucket.
for _, link := range repo.Links.Clone {
if link.Name == "https" {
clone = link.Href
}
}
// if no repository name is provided, we use the Html link.
// this excludes the .git suffix, but will still clone the repo.
// if no repository name is provided, we use the Html link. this excludes the
// .git suffix, but will still clone the repo.
if len(clone) == 0 {
clone = repo.Links.Html.Href
}
// if bitbucket tries to automatically populate the user
// in the url we must strip it out.
// if bitbucket tries to automatically populate the user in the url we must
// strip it out.
cloneurl, err := url.Parse(clone)
if err == nil {
cloneurl.User = nil
@ -89,9 +61,9 @@ func cloneLink(repo Repo) string {
return clone
}
// convertRepoLite is a helper function used to convert a Bitbucket
// repository structure to the simplified Drone repository structure.
func convertRepoLite(from *Repo) *model.RepoLite {
// convertRepoLite is a helper function used to convert a Bitbucket repository
// structure to the simplified Drone repository structure.
func convertRepoLite(from *internal.Repo) *model.RepoLite {
return &model.RepoLite{
Owner: strings.Split(from.FullName, "/")[0],
Name: strings.Split(from.FullName, "/")[1],
@ -99,3 +71,34 @@ func convertRepoLite(from *Repo) *model.RepoLite {
Avatar: from.Owner.Links.Avatar.Href,
}
}
// convertUser is a helper function used to convert a Bitbucket user account
// structure to the Drone User structure.
func convertUser(from *internal.Account, token *oauth2.Token) *model.User {
return &model.User{
Login: from.Login,
Token: token.AccessToken,
Secret: token.RefreshToken,
Expiry: token.Expiry.UTC().Unix(),
Avatar: from.Links.Avatar.Href,
}
}
// convertTeamList is a helper function used to convert a Bitbucket team list
// structure to the Drone Team structure.
func convertTeamList(from []*internal.Account) []*model.Team {
var teams []*model.Team
for _, team := range from {
teams = append(teams, convertTeam(team))
}
return teams
}
// convertTeam is a helper function used to convert a Bitbucket team account
// structure to the Drone Team structure.
func convertTeam(from *internal.Account) *model.Team {
return &model.Team{
Login: from.Login,
Avatar: from.Links.Avatar.Href,
}
}

View file

@ -0,0 +1,102 @@
package bitbucket
import (
"testing"
"time"
"github.com/drone/drone/remote/bitbucket/internal"
"github.com/franela/goblin"
"golang.org/x/oauth2"
)
func Test_helper(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Bitbucket", func() {
g.It("should convert repository lite", func() {
from := &internal.Repo{}
from.FullName = "octocat/hello-world"
from.Owner.Links.Avatar.Href = "http://..."
to := convertRepoLite(from)
g.Assert(to.Avatar).Equal(from.Owner.Links.Avatar.Href)
g.Assert(to.FullName).Equal(from.FullName)
g.Assert(to.Owner).Equal("octocat")
g.Assert(to.Name).Equal("hello-world")
})
g.It("should convert repository", func() {
from := &internal.Repo{
FullName: "octocat/hello-world",
IsPrivate: true,
Scm: "hg",
}
from.Owner.Links.Avatar.Href = "http://..."
from.Links.Html.Href = "https://bitbucket.org/foo/bar"
to := convertRepo(from)
g.Assert(to.Avatar).Equal(from.Owner.Links.Avatar.Href)
g.Assert(to.FullName).Equal(from.FullName)
g.Assert(to.Owner).Equal("octocat")
g.Assert(to.Name).Equal("hello-world")
g.Assert(to.Branch).Equal("default")
g.Assert(to.Kind).Equal(from.Scm)
g.Assert(to.IsPrivate).Equal(from.IsPrivate)
g.Assert(to.Clone).Equal(from.Links.Html.Href)
g.Assert(to.Link).Equal(from.Links.Html.Href)
})
g.It("should convert team", func() {
from := &internal.Account{Login: "octocat"}
from.Links.Avatar.Href = "http://..."
to := convertTeam(from)
g.Assert(to.Avatar).Equal(from.Links.Avatar.Href)
g.Assert(to.Login).Equal(from.Login)
})
g.It("should convert team list", func() {
from := &internal.Account{Login: "octocat"}
from.Links.Avatar.Href = "http://..."
to := convertTeamList([]*internal.Account{from})
g.Assert(to[0].Avatar).Equal(from.Links.Avatar.Href)
g.Assert(to[0].Login).Equal(from.Login)
})
g.It("should convert user", func() {
token := &oauth2.Token{
AccessToken: "foo",
RefreshToken: "bar",
Expiry: time.Now(),
}
user := &internal.Account{Login: "octocat"}
user.Links.Avatar.Href = "http://..."
result := convertUser(user, token)
g.Assert(result.Avatar).Equal(user.Links.Avatar.Href)
g.Assert(result.Login).Equal(user.Login)
g.Assert(result.Token).Equal(token.AccessToken)
g.Assert(result.Token).Equal(token.AccessToken)
g.Assert(result.Secret).Equal(token.RefreshToken)
g.Assert(result.Expiry).Equal(token.Expiry.UTC().Unix())
})
g.It("should use clone url", func() {
repo := &internal.Repo{}
repo.Links.Clone = append(repo.Links.Clone, internal.Link{
Name: "https",
Href: "https://bitbucket.org/foo/bar.git",
})
link := cloneLink(repo)
g.Assert(link).Equal(repo.Links.Clone[0].Href)
})
g.It("should build clone url", func() {
repo := &internal.Repo{}
repo.Links.Html.Href = "https://foo:bar@bitbucket.org/foo/bar.git"
link := cloneLink(repo)
g.Assert(link).Equal("https://bitbucket.org/foo/bar.git")
})
})
}

View file

@ -1,4 +1,4 @@
package bitbucket
package internal
import (
"bytes"

View file

@ -1,4 +1,4 @@
package bitbucket
package internal
import (
"net/url"

View file

@ -14,13 +14,15 @@ package bitbucketserver
import (
"encoding/json"
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/drone/drone/model"
"github.com/mrjones/oauth"
"io/ioutil"
"net/http"
"net/url"
"strconv"
log "github.com/Sirupsen/logrus"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/mrjones/oauth"
)
type BitbucketServer struct {
@ -33,6 +35,19 @@ type BitbucketServer struct {
Consumer oauth.Consumer
}
func New(url, key, rsa, username, password string) remote.Remote {
bb := &BitbucketServer{
URL: url,
ConsumerKey: key,
GitUserName: username,
GitPassword: password,
ConsumerRSA: rsa,
}
bb.Consumer = *NewClient(bb.ConsumerRSA, bb.ConsumerKey, bb.URL)
return bb
}
func Load(config string) *BitbucketServer {
url_, err := url.Parse(config)
@ -105,7 +120,7 @@ func (bs *BitbucketServer) Login(res http.ResponseWriter, req *http.Request) (*m
bits, err := ioutil.ReadAll(response.Body)
userName := string(bits)
response1, err := client.Get(fmt.Sprintf("%s/rest/api/1.0/users/%s",bs.URL, userName))
response1, err := client.Get(fmt.Sprintf("%s/rest/api/1.0/users/%s", bs.URL, userName))
contents, err := ioutil.ReadAll(response1.Body)
defer response1.Body.Close()
var mUser User
@ -134,7 +149,7 @@ func (bs *BitbucketServer) Repo(u *model.User, owner, name string) (*model.Repo,
client := NewClientWithToken(&bs.Consumer, u.Token)
url := fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s",bs.URL,owner,name)
url := fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s", bs.URL, owner, name)
log.Info("Trying to get " + url)
response, err := client.Get(url)
if err != nil {
@ -165,7 +180,7 @@ func (bs *BitbucketServer) Repo(u *model.User, owner, name string) (*model.Repo,
repo.Name = bsRepo.Slug
repo.Owner = bsRepo.Project.Key
repo.AllowPush = true
repo.FullName = fmt.Sprintf("%s/%s",bsRepo.Project.Key,bsRepo.Slug)
repo.FullName = fmt.Sprintf("%s/%s", bsRepo.Project.Key, bsRepo.Slug)
repo.Branch = "master"
repo.Kind = model.RepoGit
@ -178,7 +193,7 @@ func (bs *BitbucketServer) Repos(u *model.User) ([]*model.RepoLite, error) {
client := NewClientWithToken(&bs.Consumer, u.Token)
response, err := client.Get(fmt.Sprintf("%s/rest/api/1.0/repos?limit=10000",bs.URL))
response, err := client.Get(fmt.Sprintf("%s/rest/api/1.0/repos?limit=10000", bs.URL))
if err != nil {
log.Error(err)
}
@ -296,7 +311,7 @@ func (bs *BitbucketServer) Hook(r *http.Request) (*model.Repo, *model.Build, err
repo.AllowDeploy = false
repo.AllowPull = false
repo.AllowPush = true
repo.FullName = fmt.Sprintf("%s/%s",hookPost.Repository.Project.Key,hookPost.Repository.Slug)
repo.FullName = fmt.Sprintf("%s/%s", hookPost.Repository.Project.Key, hookPost.Repository.Slug)
repo.Branch = "master"
repo.Kind = model.RepoGit
@ -307,17 +322,17 @@ func (bs *BitbucketServer) String() string {
}
type HookDetail struct {
Key string `"json:key"`
Name string `"json:name"`
Type string `"json:type"`
Description string `"json:description"`
Version string `"json:version"`
ConfigFormKey string `"json:configFormKey"`
Key string `json:"key"`
Name string `json:"name"`
Type string `json:"type"`
Description string `json:"description"`
Version string `json:"version"`
ConfigFormKey string `json:"configFormKey"`
}
type Hook struct {
Enabled bool `"json:enabled"`
Details *HookDetail `"json:details"`
Enabled bool `json:"enabled"`
Details *HookDetail `json:"details"`
}
// Enable hook for named repository

85
remote/cache.go Normal file
View file

@ -0,0 +1,85 @@
package remote
import (
"time"
"github.com/drone/drone/model"
)
// WithCache returns a the parent Remote with a front-end Cache. Remote items
// are cached for duration d.
func WithCache(r Remote, d time.Duration) Remote {
return r
}
// Cacher implements purge functionality so that we can evict stale data and
// force a refresh. The indended use case is when the repository list is out
// of date and requires manual refresh.
type Cacher interface {
Purge(*model.User)
}
// Because the cache is so closely tied to the remote we should just include
// them in the same package together. The below code are stubs for merging
// the Cache with the Remote package.
type cache struct {
Remote
}
func (c *cache) Repos(u *model.User) ([]*model.RepoLite, error) {
// key := fmt.Sprintf("repos:%s",
// user.Login,
// )
// // if we fetch from the cache we can return immediately
// val, err := Get(c, key)
// if err == nil {
// return val.([]*model.RepoLite), nil
// }
// // else we try to grab from the remote system and
// // populate our cache.
// repos, err := remote.Repos(c, user)
// if err != nil {
// return nil, err
// }
//
// Set(c, key, repos)
// return repos, nil
return nil, nil
}
func (c *cache) Perm(u *model.User, owner, repo string) (*model.Perm, error) {
// key := fmt.Sprintf("perms:%s:%s/%s",
// user.Login,
// owner,
// name,
// )
// // if we fetch from the cache we can return immediately
// val, err := Get(c, key)
// if err == nil {
// return val.(*model.Perm), nil
// }
// // else we try to grab from the remote system and
// // populate our cache.
// perm, err := remote.Perm(c, user, owner, name)
// if err != nil {
// return nil, err
// }
// Set(c, key, perm)
// return perm, nil
return nil, nil
}
func (c *cache) Purge(*model.User) {
return
}
func (c *cache) Refresh(u *model.User) (bool, error) {
if r, ok := c.Remote.(Refresher); ok {
return r.Refresh(u)
}
return false, nil
}
var _ Remote = &cache{}
var _ Refresher = &cache{}

View file

@ -11,22 +11,18 @@ import (
"strings"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/shared/httputil"
"github.com/drone/drone/shared/oauth2"
log "github.com/Sirupsen/logrus"
"github.com/google/go-github/github"
)
const (
DefaultURL = "https://github.com"
DefaultAPI = "https://api.github.com"
DefaultScope = "repo,repo:status,user:email"
DefaultMergeRef = "merge"
DefaultURL = "https://github.com" // Default GitHub URL
DefaultAPI = "https://api.github.com" // Default GitHub API URL
)
var githubDeployRegex = regexp.MustCompile(".+/deployments/(\\d+)")
type Github struct {
URL string
API string
@ -34,58 +30,35 @@ type Github struct {
Secret string
Scope string
MergeRef string
Orgs []string
Open bool
PrivateMode bool
SkipVerify bool
GitSSH bool
}
func Load(config string) *Github {
// parse the remote DSN configuration string
url_, err := url.Parse(config)
if err != nil {
log.Fatalln("unable to parse remote dsn. %s", err)
func New(url, client, secret string, scope []string, private, skipverify, mergeref bool) (remote.Remote, error) {
remote := &Github{
URL: strings.TrimSuffix(url, "/"),
Client: client,
Secret: secret,
Scope: strings.Join(scope, ","),
PrivateMode: private,
SkipVerify: skipverify,
MergeRef: "head",
}
params := url_.Query()
url_.Path = ""
url_.RawQuery = ""
// create the Githbub remote using parameters from
// the parsed DSN configuration string.
github := Github{}
github.URL = url_.String()
github.Client = params.Get("client_id")
github.Secret = params.Get("client_secret")
github.Scope = params.Get("scope")
github.Orgs = params["orgs"]
github.PrivateMode, _ = strconv.ParseBool(params.Get("private_mode"))
github.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify"))
github.Open, _ = strconv.ParseBool(params.Get("open"))
github.GitSSH, _ = strconv.ParseBool(params.Get("ssh"))
github.MergeRef = params.Get("merge_ref")
if github.URL == DefaultURL {
github.API = DefaultAPI
if remote.URL == DefaultURL {
remote.API = DefaultAPI
} else {
github.API = github.URL + "/api/v3/"
remote.API = remote.URL + "/api/v3/"
}
if mergeref {
remote.MergeRef = "merge"
}
if github.Scope == "" {
github.Scope = DefaultScope
}
if github.MergeRef == "" {
github.MergeRef = DefaultMergeRef
}
return &github
return remote, nil
}
// Login authenticates the session and returns the
// remote user details.
func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
// Login authenticates the session and returns the remote user details.
func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
var config = &oauth2.Config{
ClientId: g.Client,
@ -101,7 +74,7 @@ func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User,
if len(code) == 0 {
var random = GetRandom()
http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther)
return nil, false, nil
return nil, nil
}
var trans = &oauth2.Transport{
@ -117,23 +90,13 @@ func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User,
}
var token, err = trans.Exchange(code)
if err != nil {
return nil, false, fmt.Errorf("Error exchanging token. %s", err)
return nil, fmt.Errorf("Error exchanging token. %s", err)
}
var client = NewClient(g.API, token.AccessToken, g.SkipVerify)
var useremail, errr = GetUserEmail(client)
if errr != nil {
return nil, false, fmt.Errorf("Error retrieving user or verified email. %s", errr)
}
if len(g.Orgs) > 0 {
allowedOrg, err := UserBelongsToOrg(client, g.Orgs)
if err != nil {
return nil, false, fmt.Errorf("Could not check org membership. %s", err)
}
if !allowedOrg {
return nil, false, fmt.Errorf("User does not belong to correct org. Must belong to %v", g.Orgs)
}
return nil, fmt.Errorf("Error retrieving user or verified email. %s", errr)
}
user := model.User{}
@ -141,7 +104,7 @@ func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User,
user.Email = *useremail.Email
user.Token = token.AccessToken
user.Avatar = *useremail.AvatarURL
return &user, g.Open, nil
return &user, nil
}
// Auth authenticates the session and returns the remote user
@ -155,37 +118,52 @@ func (g *Github) Auth(token, secret string) (string, error) {
return *user.Login, nil
}
// Repo fetches the named repository from the remote system.
func (g *Github) Repo(u *model.User, owner, name string) (*model.Repo, error) {
func (g *Github) Teams(u *model.User) ([]*model.Team, error) {
client := NewClient(g.API, u.Token, g.SkipVerify)
repo_, err := GetRepo(client, owner, name)
orgs, err := GetOrgs(client)
if err != nil {
return nil, err
}
repo := &model.Repo{}
repo.Owner = owner
repo.Name = name
repo.FullName = *repo_.FullName
repo.Link = *repo_.HTMLURL
repo.IsPrivate = *repo_.Private
repo.Clone = *repo_.CloneURL
repo.Branch = "master"
repo.Avatar = *repo_.Owner.AvatarURL
repo.Kind = model.RepoGit
var teams []*model.Team
for _, org := range orgs {
teams = append(teams, &model.Team{
Login: *org.Login,
Avatar: *org.AvatarURL,
})
}
return teams, nil
}
if repo_.DefaultBranch != nil {
repo.Branch = *repo_.DefaultBranch
// Repo fetches the named repository from the remote system.
func (g *Github) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := NewClient(g.API, u.Token, g.SkipVerify)
r, err := GetRepo(client, owner, name)
if err != nil {
return nil, err
}
repo := &model.Repo{
Owner: owner,
Name: name,
FullName: *r.FullName,
Link: *r.HTMLURL,
IsPrivate: *r.Private,
Clone: *r.CloneURL,
Avatar: *r.Owner.AvatarURL,
Kind: model.RepoGit,
}
if r.DefaultBranch != nil {
repo.Branch = *r.DefaultBranch
} else {
repo.Branch = "master"
}
if g.PrivateMode {
repo.IsPrivate = true
}
if g.GitSSH && repo.IsPrivate {
repo.Clone = *repo_.SSHURL
}
return repo, err
}
@ -257,8 +235,10 @@ func repoStatus(client *github.Client, r *model.Repo, b *model.Build, link strin
return err
}
var reDeploy = regexp.MustCompile(".+/deployments/(\\d+)")
func deploymentStatus(client *github.Client, r *model.Repo, b *model.Build, link string) error {
matches := githubDeployRegex.FindStringSubmatch(b.Link)
matches := reDeploy.FindStringSubmatch(b.Link)
// if the deployment was not triggered from github, don't send a deployment status
if len(matches) != 2 {
return nil
@ -294,21 +274,7 @@ func (g *Github) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
// adding the SSH deploy key, if applicable.
func (g *Github) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
client := NewClient(g.API, u.Token, g.SkipVerify)
title, err := GetKeyTitle(link)
if err != nil {
return err
}
// if the CloneURL is using the SSHURL then we know that
// we need to add an SSH key to GitHub.
if r.IsPrivate || g.PrivateMode {
_, err = CreateUpdateKey(client, r.Owner, r.Name, title, k.Public)
if err != nil {
return err
}
}
_, err = CreateUpdateHook(client, r.Owner, r.Name, link)
_, err := CreateUpdateHook(client, r.Owner, r.Name, link)
return err
}
@ -316,18 +282,6 @@ func (g *Github) Activate(u *model.User, r *model.Repo, k *model.Key, link strin
// which are equal to link and removing the SSH deploy key.
func (g *Github) Deactivate(u *model.User, r *model.Repo, link string) error {
client := NewClient(g.API, u.Token, g.SkipVerify)
title, err := GetKeyTitle(link)
if err != nil {
return err
}
// remove the deploy-key if it is installed remote.
if r.IsPrivate || g.PrivateMode {
if err := DeleteKey(client, r.Owner, r.Name, title); err != nil {
return err
}
}
return DeleteHook(client, r.Owner, r.Name, link)
}

View file

@ -46,31 +46,32 @@ func TestHook(t *testing.T) {
})
}
func TestLoad(t *testing.T) {
conf := "https://github.com?client_id=client&client_secret=secret&scope=scope1,scope2"
g := Load(conf)
if g.URL != "https://github.com" {
t.Errorf("g.URL = %q; want https://github.com", g.URL)
}
if g.Client != "client" {
t.Errorf("g.Client = %q; want client", g.Client)
}
if g.Secret != "secret" {
t.Errorf("g.Secret = %q; want secret", g.Secret)
}
if g.Scope != "scope1,scope2" {
t.Errorf("g.Scope = %q; want scope1,scope2", g.Scope)
}
if g.API != DefaultAPI {
t.Errorf("g.API = %q; want %q", g.API, DefaultAPI)
}
if g.MergeRef != DefaultMergeRef {
t.Errorf("g.MergeRef = %q; want %q", g.MergeRef, DefaultMergeRef)
}
g = Load("")
if g.Scope != DefaultScope {
t.Errorf("g.Scope = %q; want %q", g.Scope, DefaultScope)
}
}
//
// func TestLoad(t *testing.T) {
// conf := "https://github.com?client_id=client&client_secret=secret&scope=scope1,scope2"
//
// g := Load(conf)
// if g.URL != "https://github.com" {
// t.Errorf("g.URL = %q; want https://github.com", g.URL)
// }
// if g.Client != "client" {
// t.Errorf("g.Client = %q; want client", g.Client)
// }
// if g.Secret != "secret" {
// t.Errorf("g.Secret = %q; want secret", g.Secret)
// }
// if g.Scope != "scope1,scope2" {
// t.Errorf("g.Scope = %q; want scope1,scope2", g.Scope)
// }
// if g.API != DefaultAPI {
// t.Errorf("g.API = %q; want %q", g.API, DefaultAPI)
// }
// if g.MergeRef != DefaultMergeRef {
// t.Errorf("g.MergeRef = %q; want %q", g.MergeRef, DefaultMergeRef)
// }
//
// g = Load("")
// if g.Scope != DefaultScope {
// t.Errorf("g.Scope = %q; want %q", g.Scope, DefaultScope)
// }
// }

View file

@ -72,53 +72,6 @@ func GetRepo(client *github.Client, owner, repo string) (*github.Repository, err
return r, err
}
// GetAllRepos is a helper function that returns an aggregated list
// of all user and organization repositories.
func GetAllRepos(client *github.Client) ([]github.Repository, error) {
orgs, err := GetOrgs(client)
if err != nil {
return nil, err
}
repos, err := GetUserRepos(client)
if err != nil {
return nil, err
}
for _, org := range orgs {
list, err := GetOrgRepos(client, *org.Login)
if err != nil {
return nil, err
}
repos = append(repos, list...)
}
return repos, nil
}
// GetSubscriptions is a helper function that returns an aggregated list
// of all user and organization repositories.
// func GetSubscriptions(client *github.Client) ([]github.Repository, error) {
// var repos []github.Repository
// var opts = github.ListOptions{}
// opts.PerPage = 100
// opts.Page = 1
// // loop through user repository list
// for opts.Page > 0 {
// list, resp, err := client.Activity.ListWatched(""), &opts)
// if err != nil {
// return nil, err
// }
// repos = append(repos, list...)
// // increment the next page to retrieve
// opts.Page = resp.NextPage
// }
// return repos, nil
// }
// GetUserRepos is a helper function that returns a list of
// all user repositories. Paginated results are aggregated into
// a single list.
@ -143,30 +96,6 @@ func GetUserRepos(client *github.Client) ([]github.Repository, error) {
return repos, nil
}
// GetOrgRepos is a helper function that returns a list of
// all org repositories. Paginated results are aggregated into
// a single list.
func GetOrgRepos(client *github.Client, org string) ([]github.Repository, error) {
var repos []github.Repository
var opts = github.RepositoryListByOrgOptions{}
opts.PerPage = 100
opts.Page = 1
// loop through user repository list
for opts.Page > 0 {
list, resp, err := client.Repositories.ListByOrg(org, &opts)
if err != nil {
return nil, err
}
repos = append(repos, list...)
// increment the next page to retrieve
opts.Page = resp.NextPage
}
return repos, nil
}
// GetOrgs is a helper function that returns a list of
// all orgs that a user belongs to.
func GetOrgs(client *github.Client) ([]github.Organization, error) {
@ -250,70 +179,6 @@ func CreateUpdateHook(client *github.Client, owner, name, url string) (*github.H
return CreateHook(client, owner, name, url)
}
// GetKey is a heper function that retrieves a public Key by
// title. To do this, it will retrieve a list of all keys
// and iterate through the list.
func GetKey(client *github.Client, owner, name, title string) (*github.Key, error) {
keys, _, err := client.Repositories.ListKeys(owner, name, nil)
if err != nil {
return nil, err
}
for _, key := range keys {
if *key.Title == title {
return &key, nil
}
}
return nil, nil
}
// GetKeyTitle is a helper function that generates a title for the
// RSA public key based on the username and domain name.
func GetKeyTitle(rawurl string) (string, error) {
var uri, err = url.Parse(rawurl)
if err != nil {
return "", err
}
return fmt.Sprintf("drone@%s", uri.Host), nil
}
// DeleteKey is a helper function that deletes a deploy key
// for the specified repository.
func DeleteKey(client *github.Client, owner, name, title string) error {
var k, err = GetKey(client, owner, name, title)
if err != nil {
return err
}
if k == nil {
return nil
}
_, err = client.Repositories.DeleteKey(owner, name, *k.ID)
return err
}
// CreateKey is a helper function that creates a deploy key
// for the specified repository.
func CreateKey(client *github.Client, owner, name, title, key string) (*github.Key, error) {
var k = new(github.Key)
k.Title = github.String(title)
k.Key = github.String(key)
created, _, err := client.Repositories.CreateKey(owner, name, k)
return created, err
}
// CreateUpdateKey is a helper function that creates a deployment key
// for the specified repository if it does not already exist, otherwise
// it updates the existing key
func CreateUpdateKey(client *github.Client, owner, name, title, key string) (*github.Key, error) {
var k, _ = GetKey(client, owner, name, title)
if k != nil {
k.Title = github.String(title)
k.Key = github.String(key)
client.Repositories.DeleteKey(owner, name, *k.ID)
}
return CreateKey(client, owner, name, title, key)
}
// GetFile is a heper function that retrieves a file from
// GitHub and returns its contents in byte array format.
func GetFile(client *github.Client, owner, name, path, ref string) ([]byte, error) {
@ -344,25 +209,3 @@ func GetPayload(req *http.Request) []byte {
}
return []byte(payload)
}
// UserBelongsToOrg returns true if the currently authenticated user is a
// member of any of the organizations provided.
func UserBelongsToOrg(client *github.Client, permittedOrgs []string) (bool, error) {
userOrgs, err := GetOrgs(client)
if err != nil {
return false, err
}
userOrgSet := make(map[string]struct{}, len(userOrgs))
for _, org := range userOrgs {
userOrgSet[*org.Login] = struct{}{}
}
for _, org := range permittedOrgs {
if _, ok := userOrgSet[org]; ok {
return true, nil
}
}
return false, nil
}

View file

@ -10,6 +10,7 @@ import (
"strings"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/shared/httputil"
"github.com/drone/drone/shared/oauth2"
"github.com/drone/drone/shared/token"
@ -34,6 +35,16 @@ type Gitlab struct {
Search bool
}
func New(url, client, secret string, private, skipverify bool) remote.Remote {
return &Gitlab{
URL: url,
Client: client,
Secret: secret,
PrivateMode: private,
SkipVerify: skipverify,
}
}
func Load(config string) *Gitlab {
url_, err := url.Parse(config)
if err != nil {

View file

@ -6,44 +6,33 @@ import (
"net"
"net/http"
"net/url"
"strconv"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/gogits/go-gogs-client"
log "github.com/Sirupsen/logrus"
)
type Gogs struct {
// Remote defines a remote implementation that integrates with Gogs, an open
// source Git service written in Go. See https://gogs.io/
type Remote struct {
URL string
Open bool
PrivateMode bool
SkipVerify bool
}
func Load(config string) *Gogs {
// parse the remote DSN configuration string
url_, err := url.Parse(config)
if err != nil {
log.Fatalln("unable to parse remote dsn. %s", err)
// New returns a Remote implementation that integrates with Gogs, an open
// source Git service written in Go. See https://gogs.io/
func New(url string, private, skipverify bool) remote.Remote {
return &Remote{
URL: url,
PrivateMode: private,
SkipVerify: skipverify,
}
params := url_.Query()
url_.RawQuery = ""
// create the Githbub remote using parameters from
// the parsed DSN configuration string.
gogs := Gogs{}
gogs.URL = url_.String()
gogs.PrivateMode, _ = strconv.ParseBool(params.Get("private_mode"))
gogs.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify"))
gogs.Open, _ = strconv.ParseBool(params.Get("open"))
return &gogs
}
// Login authenticates the session and returns the
// remote user details.
func (g *Gogs) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
// Login authenticates the session and returns the authenticated user.
func (g *Remote) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
var (
username = req.FormValue("username")
password = req.FormValue("password")
@ -91,17 +80,17 @@ func (g *Gogs) Login(res http.ResponseWriter, req *http.Request) (*model.User, b
user.Login = userInfo.UserName
user.Email = userInfo.Email
user.Avatar = expandAvatar(g.URL, userInfo.AvatarUrl)
return &user, g.Open, nil
return &user, false, nil
}
// Auth authenticates the session and returns the remote user
// login for the given token and secret
func (g *Gogs) Auth(token, secret string) (string, error) {
func (g *Remote) Auth(token, secret string) (string, error) {
return "", fmt.Errorf("Method not supported")
}
// Repo fetches the named repository from the remote system.
func (g *Gogs) Repo(u *model.User, owner, name string) (*model.Repo, error) {
func (g *Remote) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
repos_, err := client.ListMyRepos()
if err != nil {
@ -119,7 +108,7 @@ func (g *Gogs) Repo(u *model.User, owner, name string) (*model.Repo, error) {
}
// Repos fetches a list of repos from the remote system.
func (g *Gogs) Repos(u *model.User) ([]*model.RepoLite, error) {
func (g *Remote) Repos(u *model.User) ([]*model.RepoLite, error) {
repos := []*model.RepoLite{}
client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
@ -137,7 +126,7 @@ func (g *Gogs) Repos(u *model.User) ([]*model.RepoLite, error) {
// Perm fetches the named repository permissions from
// the remote system for the specified user.
func (g *Gogs) Perm(u *model.User, owner, name string) (*model.Perm, error) {
func (g *Remote) Perm(u *model.User, owner, name string) (*model.Perm, error) {
client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
repos_, err := client.ListMyRepos()
if err != nil {
@ -156,7 +145,7 @@ func (g *Gogs) Perm(u *model.User, owner, name string) (*model.Perm, error) {
}
// File fetches a file from the remote repository and returns in string format.
func (g *Gogs) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
func (g *Remote) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
cfg, err := client.GetFile(r.Owner, r.Name, b.Commit, f)
return cfg, err
@ -164,13 +153,13 @@ func (g *Gogs) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]b
// Status sends the commit status to the remote system.
// An example would be the GitHub pull request status.
func (g *Gogs) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
func (g *Remote) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
return fmt.Errorf("Not Implemented")
}
// Netrc returns a .netrc file that can be used to clone
// private repositories from a remote system.
func (g *Gogs) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
func (g *Remote) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
url_, err := url.Parse(g.URL)
if err != nil {
return nil, err
@ -188,7 +177,7 @@ func (g *Gogs) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
// Activate activates a repository by creating the post-commit hook and
// adding the SSH deploy key, if applicable.
func (g *Gogs) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
func (g *Remote) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
config := map[string]string{
"url": link,
"secret": r.Hash,
@ -207,13 +196,13 @@ func (g *Gogs) Activate(u *model.User, r *model.Repo, k *model.Key, link string)
// Deactivate removes a repository by removing all the post-commit hooks
// which are equal to link and removing the SSH deploy key.
func (g *Gogs) Deactivate(u *model.User, r *model.Repo, link string) error {
func (g *Remote) Deactivate(u *model.User, r *model.Repo, link string) error {
return fmt.Errorf("Not Implemented")
}
// Hook parses the post-commit hook from the Request body
// and returns the required data in a standard format.
func (g *Gogs) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
func (g *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
var (
err error
repo *model.Repo
@ -246,6 +235,6 @@ func NewGogsClient(url, token string, skipVerify bool) *gogs.Client {
return c
}
func (g *Gogs) String() string {
func (g *Remote) String() string {
return "gogs"
}

View file

@ -1,42 +1,32 @@
package mock
import "github.com/stretchr/testify/mock"
import (
"net/http"
import "net/http"
import "github.com/drone/drone/model"
"github.com/drone/drone/model"
"github.com/stretchr/testify/mock"
)
// This is an autogenerated mock type for the Remote type
type Remote struct {
mock.Mock
}
func (_m *Remote) Login(w http.ResponseWriter, r *http.Request) (*model.User, bool, error) {
ret := _m.Called(w, r)
// Activate provides a mock function with given fields: u, r, k, link
func (_m *Remote) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
ret := _m.Called(u, r, k, link)
var r0 *model.User
if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) *model.User); ok {
r0 = rf(w, r)
var r0 error
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, *model.Key, string) error); ok {
r0 = rf(u, r, k, link)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.User)
}
r0 = ret.Error(0)
}
var r1 bool
if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) bool); ok {
r1 = rf(w, r)
} else {
r1 = ret.Get(1).(bool)
}
var r2 error
if rf, ok := ret.Get(2).(func(http.ResponseWriter, *http.Request) error); ok {
r2 = rf(w, r)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
return r0
}
// Auth provides a mock function with given fields: token, secret
func (_m *Remote) Auth(token string, secret string) (string, error) {
ret := _m.Called(token, secret)
@ -56,69 +46,22 @@ func (_m *Remote) Auth(token string, secret string) (string, error) {
return r0, r1
}
func (_m *Remote) Repo(u *model.User, owner string, repo string) (*model.Repo, error) {
ret := _m.Called(u, owner, repo)
var r0 *model.Repo
if rf, ok := ret.Get(0).(func(*model.User, string, string) *model.Repo); ok {
r0 = rf(u, owner, repo)
// Deactivate provides a mock function with given fields: u, r, link
func (_m *Remote) Deactivate(u *model.User, r *model.Repo, link string) error {
ret := _m.Called(u, r, link)
var r0 error
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, string) error); ok {
r0 = rf(u, r, link)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Repo)
}
r0 = ret.Error(0)
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.User, string, string) error); ok {
r1 = rf(u, owner, repo)
} else {
r1 = ret.Error(1)
}
return r0, r1
return r0
}
func (_m *Remote) Repos(u *model.User) ([]*model.RepoLite, error) {
ret := _m.Called(u)
var r0 []*model.RepoLite
if rf, ok := ret.Get(0).(func(*model.User) []*model.RepoLite); ok {
r0 = rf(u)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.RepoLite)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.User) error); ok {
r1 = rf(u)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
func (_m *Remote) Perm(u *model.User, owner string, repo string) (*model.Perm, error) {
ret := _m.Called(u, owner, repo)
var r0 *model.Perm
if rf, ok := ret.Get(0).(func(*model.User, string, string) *model.Perm); ok {
r0 = rf(u, owner, repo)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Perm)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.User, string, string) error); ok {
r1 = rf(u, owner, repo)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// File provides a mock function with given fields: u, r, b, f
func (_m *Remote) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
ret := _m.Called(u, r, b, f)
@ -140,63 +83,8 @@ func (_m *Remote) File(u *model.User, r *model.Repo, b *model.Build, f string) (
return r0, r1
}
func (_m *Remote) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
ret := _m.Called(u, r, b, link)
var r0 error
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, *model.Build, string) error); ok {
r0 = rf(u, r, b, link)
} else {
r0 = ret.Error(0)
}
return r0
}
func (_m *Remote) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
ret := _m.Called(u, r)
var r0 *model.Netrc
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo) *model.Netrc); ok {
r0 = rf(u, r)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Netrc)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.User, *model.Repo) error); ok {
r1 = rf(u, r)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
func (_m *Remote) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
ret := _m.Called(u, r, k, link)
var r0 error
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, *model.Key, string) error); ok {
r0 = rf(u, r, k, link)
} else {
r0 = ret.Error(0)
}
return r0
}
func (_m *Remote) Deactivate(u *model.User, r *model.Repo, link string) error {
ret := _m.Called(u, r, link)
var r0 error
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, string) error); ok {
r0 = rf(u, r, link)
} else {
r0 = ret.Error(0)
}
return r0
}
// Hook provides a mock function with given fields: r
func (_m *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
ret := _m.Called(r)
@ -227,3 +115,155 @@ func (_m *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
return r0, r1, r2
}
// Login provides a mock function with given fields: w, r
func (_m *Remote) Login(w http.ResponseWriter, r *http.Request) (*model.User, error) {
ret := _m.Called(w, r)
var r0 *model.User
if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) *model.User); ok {
r0 = rf(w, r)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.User)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) error); ok {
r1 = rf(w, r)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Netrc provides a mock function with given fields: u, r
func (_m *Remote) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
ret := _m.Called(u, r)
var r0 *model.Netrc
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo) *model.Netrc); ok {
r0 = rf(u, r)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Netrc)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.User, *model.Repo) error); ok {
r1 = rf(u, r)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Perm provides a mock function with given fields: u, owner, repo
func (_m *Remote) Perm(u *model.User, owner string, repo string) (*model.Perm, error) {
ret := _m.Called(u, owner, repo)
var r0 *model.Perm
if rf, ok := ret.Get(0).(func(*model.User, string, string) *model.Perm); ok {
r0 = rf(u, owner, repo)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Perm)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.User, string, string) error); ok {
r1 = rf(u, owner, repo)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repo provides a mock function with given fields: u, owner, repo
func (_m *Remote) Repo(u *model.User, owner string, repo string) (*model.Repo, error) {
ret := _m.Called(u, owner, repo)
var r0 *model.Repo
if rf, ok := ret.Get(0).(func(*model.User, string, string) *model.Repo); ok {
r0 = rf(u, owner, repo)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Repo)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.User, string, string) error); ok {
r1 = rf(u, owner, repo)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Repos provides a mock function with given fields: u
func (_m *Remote) Repos(u *model.User) ([]*model.RepoLite, error) {
ret := _m.Called(u)
var r0 []*model.RepoLite
if rf, ok := ret.Get(0).(func(*model.User) []*model.RepoLite); ok {
r0 = rf(u)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.RepoLite)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.User) error); ok {
r1 = rf(u)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Status provides a mock function with given fields: u, r, b, link
func (_m *Remote) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
ret := _m.Called(u, r, b, link)
var r0 error
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, *model.Build, string) error); ok {
r0 = rf(u, r, b, link)
} else {
r0 = ret.Error(0)
}
return r0
}
// Teams provides a mock function with given fields: u
func (_m *Remote) Teams(u *model.User) ([]*model.Team, error) {
ret := _m.Called(u)
var r0 []*model.Team
if rf, ok := ret.Get(0).(func(*model.User) []*model.Team); ok {
r0 = rf(u)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Team)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*model.User) error); ok {
r1 = rf(u)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View file

@ -13,12 +13,15 @@ import (
type Remote interface {
// Login authenticates the session and returns the
// remote user details.
Login(w http.ResponseWriter, r *http.Request) (*model.User, bool, error)
Login(w http.ResponseWriter, r *http.Request) (*model.User, error)
// Auth authenticates the session and returns the remote user
// login for the given token and secret
Auth(token, secret string) (string, error)
// Teams fetches a list of team memberships from the remote system.
Teams(u *model.User) ([]*model.Team, error)
// Repo fetches the named repository from the remote system.
Repo(u *model.User, owner, repo string) (*model.Repo, error)
@ -49,21 +52,21 @@ type Remote interface {
// which are equal to link and removing the SSH deploy key.
Deactivate(u *model.User, r *model.Repo, link string) error
// Hook parses the post-commit hook from the Request body
// and returns the required data in a standard format.
// Hook parses the post-commit hook from the Request body and returns the
// required data in a standard format.
Hook(r *http.Request) (*model.Repo, *model.Build, error)
}
// Refresher refreshes an oauth token and expiration for the given user. It
// returns true if the token was refreshed, false if the token was not refreshed,
// and error if it failed to refersh.
type Refresher interface {
// Refresh refreshes an oauth token and expiration for the given
// user. It returns true if the token was refreshed, false if the
// token was not refreshed, and error if it failed to refersh.
Refresh(*model.User) (bool, error)
}
// Login authenticates the session and returns the
// remote user details.
func Login(c context.Context, w http.ResponseWriter, r *http.Request) (*model.User, bool, error) {
func Login(c context.Context, w http.ResponseWriter, r *http.Request) (*model.User, error) {
return FromContext(c).Login(w, r)
}
@ -73,6 +76,11 @@ func Auth(c context.Context, token, secret string) (string, error) {
return FromContext(c).Auth(token, secret)
}
// Teams fetches a list of team memberships from the remote system.
func Teams(c context.Context, u *model.User) ([]*model.Team, error) {
return FromContext(c).Teams(u)
}
// Repo fetches the named repository from the remote system.
func Repo(c context.Context, u *model.User, owner, repo string) (*model.Repo, error) {
return FromContext(c).Repo(u, owner, repo)

View file

@ -9,7 +9,7 @@ import (
)
var (
secret = envflag.String("AGENT_SECRET", "", "")
secret = envflag.String("DRONE_AGENT_SECRET", "", "")
noauth = envflag.Bool("AGENT_NO_AUTH", false, "")
)

View file

@ -1,14 +0,0 @@
package middleware
import (
"github.com/drone/drone/bus"
"github.com/gin-gonic/gin"
)
func Bus() gin.HandlerFunc {
bus_ := bus.New()
return func(c *gin.Context) {
bus.ToContext(c, bus_)
c.Next()
}
}

View file

@ -1,22 +0,0 @@
package middleware
import (
"time"
"github.com/drone/drone/cache"
"github.com/gin-gonic/gin"
"github.com/ianschenck/envflag"
)
var ttl = envflag.Duration("CACHE_TTL", time.Minute*15, "")
// Cache is a middleware function that initializes the Cache and attaches to
// the context of every http.Request.
func Cache() gin.HandlerFunc {
cc := cache.NewTTL(*ttl)
return func(c *gin.Context) {
cache.ToContext(c, cc)
c.Next()
}
}

View file

@ -1,14 +0,0 @@
package middleware
import (
"github.com/drone/drone/queue"
"github.com/gin-gonic/gin"
)
func Queue() gin.HandlerFunc {
queue_ := queue.New()
return func(c *gin.Context) {
queue.ToContext(c, queue_)
c.Next()
}
}

View file

@ -1,48 +0,0 @@
package middleware
import (
"github.com/drone/drone/remote"
"github.com/drone/drone/remote/bitbucket"
"github.com/drone/drone/remote/github"
"github.com/drone/drone/remote/gitlab"
"github.com/drone/drone/remote/gogs"
"github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin"
"github.com/ianschenck/envflag"
"github.com/drone/drone/remote/bitbucketserver"
)
var (
driver = envflag.String("REMOTE_DRIVER", "", "")
config = envflag.String("REMOTE_CONFIG", "", "")
)
// Remote is a middleware function that initializes the Remote and attaches to
// the context of every http.Request.
func Remote() gin.HandlerFunc {
logrus.Infof("using remote driver %s", *driver)
logrus.Infof("using remote config %s", *config)
var remote_ remote.Remote
switch *driver {
case "github":
remote_ = github.Load(*config)
case "bitbucket":
remote_ = bitbucket.Load(*config)
case "gogs":
remote_ = gogs.Load(*config)
case "gitlab":
remote_ = gitlab.Load(*config)
case "bitbucketserver":
remote_ = bitbucketserver.Load(*config)
default:
logrus.Fatalln("remote configuration not found")
}
return func(c *gin.Context) {
remote.ToContext(c, remote_)
c.Next()
}
}

View file

@ -1,29 +0,0 @@
package middleware
import (
"github.com/drone/drone/store"
"github.com/drone/drone/store/datastore"
"github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin"
"github.com/ianschenck/envflag"
)
var (
database = envflag.String("DATABASE_DRIVER", "sqlite3", "")
datasource = envflag.String("DATABASE_CONFIG", "drone.sqlite", "")
)
// Store is a middleware function that initializes the Datastore and attaches to
// the context of every http.Request.
func Store() gin.HandlerFunc {
db := datastore.New(*database, *datasource)
logrus.Infof("using database driver %s", *database)
logrus.Infof("using database config %s", *datasource)
return func(c *gin.Context) {
store.ToContext(c, db)
c.Next()
}
}

View file

@ -1,14 +0,0 @@
package middleware
import (
"github.com/drone/drone/stream"
"github.com/gin-gonic/gin"
)
func Stream() gin.HandlerFunc {
stream_ := stream.New()
return func(c *gin.Context) {
stream.ToContext(c, stream_)
c.Next()
}
}

View file

@ -1,14 +0,0 @@
package middleware
import (
"github.com/drone/drone/version"
"github.com/gin-gonic/gin"
)
// Version is a middleware function that appends the Drone
// version information to the HTTP response. This is intended
// for debugging and troubleshooting.
func Version(c *gin.Context) {
c.Header("X-DRONE-VERSION", version.Version)
c.Next()
}

View file

@ -1,200 +1,201 @@
package router
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/drone/drone/api"
"github.com/drone/drone/router/middleware"
"github.com/drone/drone/router/middleware/header"
"github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/router/middleware/token"
"github.com/drone/drone/static"
"github.com/drone/drone/template"
"github.com/drone/drone/web"
)
func Load(middlewares ...gin.HandlerFunc) http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.SetHTMLTemplate(template.Load())
e.StaticFS("/static", static.FileSystem())
e.Use(header.NoCache)
e.Use(header.Options)
e.Use(header.Secure)
e.Use(middlewares...)
e.Use(session.SetUser())
e.Use(token.Refresh)
e.GET("/", web.ShowIndex)
e.GET("/repos", web.ShowAllRepos)
e.GET("/login", web.ShowLogin)
e.GET("/login/form", web.ShowLoginForm)
e.GET("/logout", web.GetLogout)
settings := e.Group("/settings")
{
settings.Use(session.MustUser())
settings.GET("/profile", web.ShowUser)
}
repo := e.Group("/repos/:owner/:name")
{
repo.Use(session.SetRepo())
repo.Use(session.SetPerm())
repo.Use(session.MustPull)
repo.GET("", web.ShowRepo)
repo.GET("/builds/:number", web.ShowBuild)
repo.GET("/builds/:number/:job", web.ShowBuild)
repo_settings := repo.Group("/settings")
{
repo_settings.GET("", session.MustPush, web.ShowRepoConf)
repo_settings.GET("/encrypt", session.MustPush, web.ShowRepoEncrypt)
repo_settings.GET("/badges", web.ShowRepoBadges)
}
}
user := e.Group("/api/user")
{
user.Use(session.MustUser())
user.GET("", api.GetSelf)
user.GET("/feed", api.GetFeed)
user.GET("/repos", api.GetRepos)
user.GET("/repos/remote", api.GetRemoteRepos)
user.POST("/token", api.PostToken)
user.DELETE("/token", api.DeleteToken)
}
users := e.Group("/api/users")
{
users.Use(session.MustAdmin())
users.GET("", api.GetUsers)
users.POST("", api.PostUser)
users.GET("/:login", api.GetUser)
users.PATCH("/:login", api.PatchUser)
users.DELETE("/:login", api.DeleteUser)
}
repos := e.Group("/api/repos/:owner/:name")
{
repos.POST("", api.PostRepo)
repo := repos.Group("")
{
repo.Use(session.SetRepo())
repo.Use(session.SetPerm())
repo.Use(session.MustPull)
repo.GET("", api.GetRepo)
repo.GET("/key", api.GetRepoKey)
repo.POST("/key", api.PostRepoKey)
repo.GET("/builds", api.GetBuilds)
repo.GET("/builds/:number", api.GetBuild)
repo.GET("/logs/:number/:job", api.GetBuildLogs)
repo.POST("/sign", session.MustPush, api.Sign)
repo.POST("/secrets", session.MustPush, api.PostSecret)
repo.DELETE("/secrets/:secret", session.MustPush, api.DeleteSecret)
// requires authenticated user
repo.POST("/encrypt", session.MustUser(), api.PostSecure)
// requires push permissions
repo.PATCH("", session.MustPush, api.PatchRepo)
repo.DELETE("", session.MustPush, api.DeleteRepo)
repo.POST("/builds/:number", session.MustPush, api.PostBuild)
repo.DELETE("/builds/:number/:job", session.MustPush, api.DeleteBuild)
}
}
badges := e.Group("/api/badges/:owner/:name")
{
badges.GET("/status.svg", web.GetBadge)
badges.GET("/cc.xml", web.GetCC)
}
e.POST("/hook", web.PostHook)
e.POST("/api/hook", web.PostHook)
stream := e.Group("/api/stream")
{
stream.Use(session.SetRepo())
stream.Use(session.SetPerm())
stream.Use(session.MustPull)
stream.GET("/:owner/:name", web.GetRepoEvents)
stream.GET("/:owner/:name/:build/:number", web.GetStream)
}
bots := e.Group("/bots")
{
bots.Use(session.MustUser())
bots.POST("/slack", web.Slack)
bots.POST("/slack/:command", web.Slack)
}
auth := e.Group("/authorize")
{
auth.GET("", web.GetLogin)
auth.POST("", web.GetLogin)
auth.POST("/token", web.GetLoginToken)
}
queue := e.Group("/api/queue")
{
queue.Use(middleware.AgentMust())
queue.POST("/pull", api.Pull)
queue.POST("/pull/:os/:arch", api.Pull)
queue.POST("/wait/:id", api.Wait)
queue.POST("/stream/:id", api.Stream)
queue.POST("/status/:id", api.Update)
}
gitlab := e.Group("/gitlab/:owner/:name")
{
gitlab.Use(session.SetRepo())
gitlab.GET("/commits/:sha", web.GetCommit)
gitlab.GET("/pulls/:number", web.GetPullRequest)
redirects := gitlab.Group("/redirect")
{
redirects.GET("/commits/:sha", web.RedirectSha)
redirects.GET("/pulls/:number", web.RedirectPullRequest)
}
}
return normalize(e)
}
// normalize is a helper function to work around the following
// issue with gin. https://github.com/gin-gonic/gin/issues/388
func normalize(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(r.URL.Path, "/")[1:]
switch parts[0] {
case "settings", "bots", "repos", "api", "login", "logout", "", "authorize", "hook", "static", "gitlab":
// no-op
default:
if len(parts) > 2 && parts[2] != "settings" {
parts = append(parts[:2], append([]string{"builds"}, parts[2:]...)...)
}
// prefix the URL with /repo so that it
// can be effectively routed.
parts = append([]string{"", "repos"}, parts...)
// reconstruct the path
r.URL.Path = strings.Join(parts, "/")
}
h.ServeHTTP(w, r)
})
}
//
// import (
// "net/http"
// "strings"
//
// "github.com/gin-gonic/gin"
//
// "github.com/drone/drone/api"
// "github.com/drone/drone/router/middleware"
// "github.com/drone/drone/router/middleware/header"
// "github.com/drone/drone/router/middleware/session"
// "github.com/drone/drone/router/middleware/token"
// "github.com/drone/drone/static"
// "github.com/drone/drone/template"
// "github.com/drone/drone/web"
// )
//
// func Load(middlewares ...gin.HandlerFunc) http.Handler {
// e := gin.New()
// e.Use(gin.Recovery())
//
// e.SetHTMLTemplate(template.Load())
// e.StaticFS("/static", static.FileSystem())
//
// e.Use(header.NoCache)
// e.Use(header.Options)
// e.Use(header.Secure)
// e.Use(middlewares...)
// e.Use(session.SetUser())
// e.Use(token.Refresh)
//
// e.GET("/", web.ShowIndex)
// e.GET("/repos", web.ShowAllRepos)
// e.GET("/login", web.ShowLogin)
// e.GET("/login/form", web.ShowLoginForm)
// e.GET("/logout", web.GetLogout)
//
// settings := e.Group("/settings")
// {
// settings.Use(session.MustUser())
// settings.GET("/profile", web.ShowUser)
// }
// repo := e.Group("/repos/:owner/:name")
// {
// repo.Use(session.SetRepo())
// repo.Use(session.SetPerm())
// repo.Use(session.MustPull)
//
// repo.GET("", web.ShowRepo)
// repo.GET("/builds/:number", web.ShowBuild)
// repo.GET("/builds/:number/:job", web.ShowBuild)
//
// repo_settings := repo.Group("/settings")
// {
// repo_settings.GET("", session.MustPush, web.ShowRepoConf)
// repo_settings.GET("/encrypt", session.MustPush, web.ShowRepoEncrypt)
// repo_settings.GET("/badges", web.ShowRepoBadges)
// }
// }
//
// user := e.Group("/api/user")
// {
// user.Use(session.MustUser())
// user.GET("", api.GetSelf)
// user.GET("/feed", api.GetFeed)
// user.GET("/repos", api.GetRepos)
// user.GET("/repos/remote", api.GetRemoteRepos)
// user.POST("/token", api.PostToken)
// user.DELETE("/token", api.DeleteToken)
// }
//
// users := e.Group("/api/users")
// {
// users.Use(session.MustAdmin())
// users.GET("", api.GetUsers)
// users.POST("", api.PostUser)
// users.GET("/:login", api.GetUser)
// users.PATCH("/:login", api.PatchUser)
// users.DELETE("/:login", api.DeleteUser)
// }
//
// repos := e.Group("/api/repos/:owner/:name")
// {
// repos.POST("", api.PostRepo)
//
// repo := repos.Group("")
// {
// repo.Use(session.SetRepo())
// repo.Use(session.SetPerm())
// repo.Use(session.MustPull)
//
// repo.GET("", api.GetRepo)
// repo.GET("/key", api.GetRepoKey)
// repo.POST("/key", api.PostRepoKey)
// repo.GET("/builds", api.GetBuilds)
// repo.GET("/builds/:number", api.GetBuild)
// repo.GET("/logs/:number/:job", api.GetBuildLogs)
// repo.POST("/sign", session.MustPush, api.Sign)
//
// repo.POST("/secrets", session.MustPush, api.PostSecret)
// repo.DELETE("/secrets/:secret", session.MustPush, api.DeleteSecret)
//
// // requires authenticated user
// repo.POST("/encrypt", session.MustUser(), api.PostSecure)
//
// // requires push permissions
// repo.PATCH("", session.MustPush, api.PatchRepo)
// repo.DELETE("", session.MustPush, api.DeleteRepo)
//
// repo.POST("/builds/:number", session.MustPush, api.PostBuild)
// repo.DELETE("/builds/:number/:job", session.MustPush, api.DeleteBuild)
// }
// }
//
// badges := e.Group("/api/badges/:owner/:name")
// {
// badges.GET("/status.svg", web.GetBadge)
// badges.GET("/cc.xml", web.GetCC)
// }
//
// e.POST("/hook", web.PostHook)
// e.POST("/api/hook", web.PostHook)
//
// stream := e.Group("/api/stream")
// {
// stream.Use(session.SetRepo())
// stream.Use(session.SetPerm())
// stream.Use(session.MustPull)
//
// stream.GET("/:owner/:name", web.GetRepoEvents)
// stream.GET("/:owner/:name/:build/:number", web.GetStream)
// }
//
// bots := e.Group("/bots")
// {
// bots.Use(session.MustUser())
// bots.POST("/slack", web.Slack)
// bots.POST("/slack/:command", web.Slack)
// }
//
// auth := e.Group("/authorize")
// {
// auth.GET("", web.GetLogin)
// auth.POST("", web.GetLogin)
// auth.POST("/token", web.GetLoginToken)
// }
//
// queue := e.Group("/api/queue")
// {
// queue.Use(middleware.AgentMust())
// queue.POST("/pull", api.Pull)
// queue.POST("/pull/:os/:arch", api.Pull)
// queue.POST("/wait/:id", api.Wait)
// queue.POST("/stream/:id", api.Stream)
// queue.POST("/status/:id", api.Update)
// }
//
// gitlab := e.Group("/gitlab/:owner/:name")
// {
// gitlab.Use(session.SetRepo())
// gitlab.GET("/commits/:sha", web.GetCommit)
// gitlab.GET("/pulls/:number", web.GetPullRequest)
//
// redirects := gitlab.Group("/redirect")
// {
// redirects.GET("/commits/:sha", web.RedirectSha)
// redirects.GET("/pulls/:number", web.RedirectPullRequest)
// }
// }
//
// return normalize(e)
// }
//
// // normalize is a helper function to work around the following
// // issue with gin. https://github.com/gin-gonic/gin/issues/388
// func normalize(h http.Handler) http.Handler {
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//
// parts := strings.Split(r.URL.Path, "/")[1:]
// switch parts[0] {
// case "settings", "bots", "repos", "api", "login", "logout", "", "authorize", "hook", "static", "gitlab":
// // no-op
// default:
//
// if len(parts) > 2 && parts[2] != "settings" {
// parts = append(parts[:2], append([]string{"builds"}, parts[2:]...)...)
// }
//
// // prefix the URL with /repo so that it
// // can be effectively routed.
// parts = append([]string{"", "repos"}, parts...)
//
// // reconstruct the path
// r.URL.Path = strings.Join(parts, "/")
// }
//
// h.ServeHTTP(w, r)
// })
// }

79
server/handler.go Normal file
View file

@ -0,0 +1,79 @@
package server
import (
"github.com/drone/drone/bus"
"github.com/drone/drone/cache"
"github.com/drone/drone/queue"
"github.com/drone/drone/remote"
"github.com/drone/drone/store"
"github.com/drone/drone/stream"
"github.com/drone/drone/version"
"github.com/gin-gonic/gin"
)
// HandlerCache returns a HandlerFunc that passes a Cache to the Context.
func HandlerCache(v cache.Cache) gin.HandlerFunc {
return func(c *gin.Context) {
cache.ToContext(c, v)
}
}
// HandlerBus returns a HandlerFunc that passes a Bus to the Context.
func HandlerBus(v bus.Bus) gin.HandlerFunc {
return func(c *gin.Context) {
bus.ToContext(c, v)
}
}
// HandlerStore returns a HandlerFunc that passes a Store to the Context.
func HandlerStore(v store.Store) gin.HandlerFunc {
return func(c *gin.Context) {
store.ToContext(c, v)
}
}
// HandlerQueue returns a HandlerFunc that passes a Queue to the Context.
func HandlerQueue(v queue.Queue) gin.HandlerFunc {
return func(c *gin.Context) {
queue.ToContext(c, v)
}
}
// HandlerStream returns a HandlerFunc that passes a Stream to the Context.
func HandlerStream(v stream.Stream) gin.HandlerFunc {
return func(c *gin.Context) {
stream.ToContext(c, v)
}
}
// HandlerRemote returns a HandlerFunc that passes a Remote to the Context.
func HandlerRemote(v remote.Remote) gin.HandlerFunc {
return func(c *gin.Context) {
remote.ToContext(c, v)
}
}
// HandlerConfig returns a HandlerFunc that passes server Config to the Context.
func HandlerConfig(v *Config) gin.HandlerFunc {
const k = "config"
return func(c *gin.Context) {
c.Set(k, v)
}
}
// HandlerVersion returns a HandlerFunc that writes the Version information to
// the http.Response as a the X-Drone-Version header.
func HandlerVersion() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("X-Drone-Version", version.Version)
}
}
// HandlerAgent returns a HandlerFunc that passes an Agent token to the Context.
func HandlerAgent(v string) gin.HandlerFunc {
const k = "agent"
return func(c *gin.Context) {
c.Set(k, v)
}
}

1
server/handler_test.go Normal file
View file

@ -0,0 +1 @@
package server

243
server/server.go Normal file
View file

@ -0,0 +1,243 @@
package server
import (
"net/http"
"strings"
"time"
"github.com/Sirupsen/logrus"
"github.com/drone/drone/api"
"github.com/drone/drone/bus"
"github.com/drone/drone/cache"
"github.com/drone/drone/queue"
"github.com/drone/drone/remote"
"github.com/drone/drone/router/middleware"
"github.com/drone/drone/router/middleware/header"
"github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/router/middleware/token"
"github.com/drone/drone/static"
"github.com/drone/drone/store"
"github.com/drone/drone/stream"
"github.com/drone/drone/template"
"github.com/drone/drone/web"
"github.com/gin-gonic/contrib/ginrus"
"github.com/gin-gonic/gin"
)
// Config defines system configuration parameters.
type Config struct {
Open bool // Enables open registration
Yaml string // Customize the Yaml configuration file name
Secret string // Secret token used to authenticate agents
Admins []string // Administrative users
Orgs []string // Organization whitelist
}
// Server defines the server configuration.
type Server struct {
Bus bus.Bus
Cache cache.Cache
Queue queue.Queue
Remote remote.Remote
Stream stream.Stream
Store store.Store
Config *Config
}
// Handler returns an http.Handler for servering Drone requests.
func (s *Server) Handler() http.Handler {
e := gin.New()
e.Use(gin.Recovery())
e.SetHTMLTemplate(template.Load())
e.StaticFS("/static", static.FileSystem())
e.Use(header.NoCache)
e.Use(header.Options)
e.Use(header.Secure)
e.Use(
ginrus.Ginrus(logrus.StandardLogger(), time.RFC3339, true),
HandlerVersion(),
HandlerQueue(s.Queue),
HandlerStream(s.Stream),
HandlerBus(s.Bus),
HandlerCache(s.Cache),
HandlerStore(s.Store),
HandlerRemote(s.Remote),
HandlerConfig(s.Config),
)
e.Use(session.SetUser())
e.Use(token.Refresh)
e.GET("/", web.ShowIndex)
e.GET("/repos", web.ShowAllRepos)
e.GET("/login", web.ShowLogin)
e.GET("/login/form", web.ShowLoginForm)
e.GET("/logout", web.GetLogout)
// TODO below will Go away with React UI
settings := e.Group("/settings")
{
settings.Use(session.MustUser())
settings.GET("/profile", web.ShowUser)
}
repo := e.Group("/repos/:owner/:name")
{
repo.Use(session.SetRepo())
repo.Use(session.SetPerm())
repo.Use(session.MustPull)
repo.GET("", web.ShowRepo)
repo.GET("/builds/:number", web.ShowBuild)
repo.GET("/builds/:number/:job", web.ShowBuild)
repo_settings := repo.Group("/settings")
{
repo_settings.GET("", session.MustPush, web.ShowRepoConf)
repo_settings.GET("/encrypt", session.MustPush, web.ShowRepoEncrypt)
repo_settings.GET("/badges", web.ShowRepoBadges)
}
}
// TODO above will Go away with React UI
user := e.Group("/api/user")
{
user.Use(session.MustUser())
user.GET("", api.GetSelf)
user.GET("/feed", api.GetFeed)
user.GET("/repos", api.GetRepos)
user.GET("/repos/remote", api.GetRemoteRepos)
user.POST("/token", api.PostToken)
user.DELETE("/token", api.DeleteToken)
}
users := e.Group("/api/users")
{
users.Use(session.MustAdmin())
users.GET("", api.GetUsers)
users.POST("", api.PostUser)
users.GET("/:login", api.GetUser)
users.PATCH("/:login", api.PatchUser)
users.DELETE("/:login", api.DeleteUser)
}
repos := e.Group("/api/repos/:owner/:name")
{
repos.POST("", api.PostRepo)
repo := repos.Group("")
{
repo.Use(session.SetRepo())
repo.Use(session.SetPerm())
repo.Use(session.MustPull)
repo.GET("", api.GetRepo)
repo.GET("/key", api.GetRepoKey)
repo.POST("/key", api.PostRepoKey)
repo.GET("/builds", api.GetBuilds)
repo.GET("/builds/:number", api.GetBuild)
repo.GET("/logs/:number/:job", api.GetBuildLogs)
repo.POST("/sign", session.MustPush, api.Sign)
repo.POST("/secrets", session.MustPush, api.PostSecret)
repo.DELETE("/secrets/:secret", session.MustPush, api.DeleteSecret)
// requires push permissions
repo.PATCH("", session.MustPush, api.PatchRepo)
repo.DELETE("", session.MustPush, api.DeleteRepo)
repo.POST("/builds/:number", session.MustPush, api.PostBuild)
repo.DELETE("/builds/:number/:job", session.MustPush, api.DeleteBuild)
}
}
badges := e.Group("/api/badges/:owner/:name")
{
badges.GET("/status.svg", web.GetBadge)
badges.GET("/cc.xml", web.GetCC)
}
e.POST("/hook", web.PostHook)
e.POST("/api/hook", web.PostHook)
stream := e.Group("/api/stream")
{
stream.Use(session.SetRepo())
stream.Use(session.SetPerm())
stream.Use(session.MustPull)
stream.GET("/:owner/:name", web.GetRepoEvents)
stream.GET("/:owner/:name/:build/:number", web.GetStream)
}
auth := e.Group("/authorize")
{
auth.GET("", web.GetLogin)
auth.POST("", web.GetLogin)
auth.POST("/token", web.GetLoginToken)
}
queue := e.Group("/api/queue")
{
queue.Use(middleware.AgentMust())
queue.POST("/pull", api.Pull)
queue.POST("/pull/:os/:arch", api.Pull)
queue.POST("/wait/:id", api.Wait)
queue.POST("/stream/:id", api.Stream)
queue.POST("/status/:id", api.Update)
}
// DELETE THESE
// gitlab := e.Group("/gitlab/:owner/:name")
// {
// gitlab.Use(session.SetRepo())
// gitlab.GET("/commits/:sha", web.GetCommit)
// gitlab.GET("/pulls/:number", web.GetPullRequest)
//
// redirects := gitlab.Group("/redirect")
// {
// redirects.GET("/commits/:sha", web.RedirectSha)
// redirects.GET("/pulls/:number", web.RedirectPullRequest)
// }
// }
// bots := e.Group("/bots")
// {
// bots.Use(session.MustUser())
// bots.POST("/slack", web.Slack)
// bots.POST("/slack/:command", web.Slack)
// }
return normalize(e)
}
// THIS HACK JOB IS GOING AWAY SOON.
//
// normalize is a helper function to work around the following
// issue with gin. https://github.com/gin-gonic/gin/issues/388
func normalize(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(r.URL.Path, "/")[1:]
switch parts[0] {
case "settings", "bots", "repos", "api", "login", "logout", "", "authorize", "hook", "static", "gitlab":
// no-op
default:
if len(parts) > 2 && parts[2] != "settings" {
parts = append(parts[:2], append([]string{"builds"}, parts[2:]...)...)
}
// prefix the URL with /repo so that it
// can be effectively routed.
parts = append([]string{"", "repos"}, parts...)
// reconstruct the path
r.URL.Path = strings.Join(parts, "/")
}
h.ServeHTTP(w, r)
})
}