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 package api
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gopkg.in/yaml.v2"
"github.com/drone/drone/cache" "github.com/drone/drone/cache"
"github.com/drone/drone/model" "github.com/drone/drone/model"
@ -210,44 +208,3 @@ func DeleteRepo(c *gin.Context) {
remote.Deactivate(user, repo, httputil.GetURL(c.Request)) remote.Deactivate(user, repo, httputil.GetURL(c.Request))
c.Writer.WriteHeader(http.StatusOK) 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"), c.String("drone-token"),
) )
tls, _ := dockerclient.TLSConfigFromCertPath(c.String("docker-cert-path")) tls, err := dockerclient.TLSConfigFromCertPath(c.String("docker-cert-path"))
if c.Bool("docker-host") { if err == nil {
tls.InsecureSkipVerify = true tls.InsecureSkipVerify = c.Bool("docker-tls-verify")
} }
docker, err := dockerclient.NewDockerClient(c.String("docker-host"), tls) docker, err := dockerclient.NewDockerClient(c.String("docker-host"), tls)
if err != nil { 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" "os"
"github.com/drone/drone/drone/agent" "github.com/drone/drone/drone/agent"
"github.com/drone/drone/drone/server"
"github.com/drone/drone/version" "github.com/drone/drone/version"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
@ -35,7 +34,7 @@ func main() {
} }
app.Commands = []cli.Command{ app.Commands = []cli.Command{
agent.AgentCmd, agent.AgentCmd,
server.ServeCmd, DaemonCmd,
SignCmd, SignCmd,
SecretCmd, 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" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strconv"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/remote/bitbucket/internal"
"github.com/drone/drone/shared/httputil" "github.com/drone/drone/shared/httputil"
log "github.com/Sirupsen/logrus"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/bitbucket" "golang.org/x/oauth2/bitbucket"
) )
type Bitbucket struct { type config struct {
Client string Client string
Secret string Secret string
Orgs []string
Open bool
} }
func Load(config string) *Bitbucket { // New returns a new remote Configuration for integrating with the Bitbucket
// repository hosting service at https://bitbucket.org
// parse the remote DSN configuration string func New(client, secret string) remote.Remote {
url_, err := url.Parse(config) return &config{
if err != nil { Client: client,
log.Fatalln("unable to parse remote dsn. %s", err) 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 // helper function to return the bitbucket oauth2 client
// remote user details. func (c *config) newClient(u *model.User) *internal.Client {
func (bb *Bitbucket) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) { 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{ config := &oauth2.Config{
ClientID: bb.Client, ClientID: c.Client,
ClientSecret: bb.Secret, ClientSecret: c.Secret,
Endpoint: bitbucket.Endpoint, Endpoint: bitbucket.Endpoint,
RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)), RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)),
} }
// get the OAuth code
var code = req.FormValue("code") var code = req.FormValue("code")
if len(code) == 0 { if len(code) == 0 {
http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther) http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther)
return nil, false, nil return nil, nil
} }
var token, err = config.Exchange(oauth2.NoContext, code) var token, err = config.Exchange(oauth2.NoContext, code)
if err != nil { 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() curr, err := client.FindCurrent()
if err != nil { if err != nil {
return nil, false, err return nil, err
} }
// convers the current bitbucket user to the return convertUser(curr, token), nil
// 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 func (c *config) Auth(token, secret string) (string, error) {
// of organizations, get the orgs and verify the client := internal.NewClientToken(
// user is a member. c.Client,
if len(bb.Orgs) != 0 { c.Secret,
resp, err := client.ListTeams(&ListTeamOpts{Page: 1, PageLen: 100, Role: "member"}) &oauth2.Token{
if err != nil { AccessToken: token,
return nil, false, err RefreshToken: secret,
} },
)
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
}
// 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_)
user, err := client.FindCurrent() user, err := client.FindCurrent()
if err != nil { if err != nil {
return "", err return "", err
@ -135,13 +86,10 @@ func (bb *Bitbucket) Auth(token, secret string) (string, error) {
return user.Login, nil return user.Login, nil
} }
// Refresh refreshes an oauth token and expiration for the given func (c *config) Refresh(user *model.User) (bool, error) {
// 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) {
config := &oauth2.Config{ config := &oauth2.Config{
ClientID: bb.Client, ClientID: c.Client,
ClientSecret: bb.Secret, ClientSecret: c.Secret,
Endpoint: bitbucket.Endpoint, Endpoint: bitbucket.Endpoint,
} }
@ -165,28 +113,35 @@ func (bb *Bitbucket) Refresh(user *model.User) (bool, error) {
return true, nil return true, nil
} }
// Repo fetches the named repository from the remote system. func (c *config) Teams(u *model.User) ([]*model.Team, error) {
func (bb *Bitbucket) Repo(u *model.User, owner, name string) (*model.Repo, error) { opts := &internal.ListTeamOpts{
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret} PageLen: 100,
client := NewClientToken(bb.Client, bb.Secret, &token) 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 { if err != nil {
return nil, err return nil, err
} }
return convertRepo(repo), nil return convertRepo(repo), nil
} }
// Repos fetches a list of repos from the remote system. func (c *config) Repos(u *model.User) ([]*model.RepoLite, error) {
func (bb *Bitbucket) Repos(u *model.User) ([]*model.RepoLite, error) { client := c.newClient(u)
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
client := NewClientToken(bb.Client, bb.Secret, &token)
var repos []*model.RepoLite var repos []*model.RepoLite
// gets a list of all accounts to query, including the // gets a list of all accounts to query, including the
// user's account and all team accounts. // user's account and all team accounts.
logins := []string{u.Login} 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 { if err != nil {
return repos, err return repos, err
} }
@ -208,11 +163,8 @@ func (bb *Bitbucket) Repos(u *model.User) ([]*model.RepoLite, error) {
return repos, nil return repos, nil
} }
// Perm fetches the named repository permissions from func (c *config) Perm(u *model.User, owner, name string) (*model.Perm, error) {
// the remote system for the specified user. client := c.newClient(u)
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)
perms := new(model.Perm) perms := new(model.Perm)
_, err := client.FindRepo(owner, name) _, 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 return perms, err
} }
// if we've gotten this far we know that the user at // if we've gotten this far we know that the user at least has read access
// least has read access to the repository. // to the repository.
perms.Pull = true perms.Pull = true
// if the user has access to the repository hooks we // if the user has access to the repository hooks we can deduce that the user
// can deduce that the user has push and admin access. // has push and admin access.
_, err = client.ListHooks(owner, name, &ListOpts{}) _, err = client.ListHooks(owner, name, &internal.ListOpts{})
if err == nil { if err == nil {
perms.Push = true perms.Push = true
perms.Admin = true perms.Admin = true
} }
return perms, nil return perms, nil
} }
// File fetches a file from the remote repository and returns in string format. func (c *config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
func (bb *Bitbucket) 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)
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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return []byte(config.Data), err return []byte(config.Data), err
} }
// Status sends the commit status to the remote system. func (c *config) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
// An example would be the GitHub pull request status. status := internal.BuildStatus{
func (bb *Bitbucket) Status(u *model.User, r *model.Repo, b *model.Build, link string) error { State: getStatus(b.Status),
client := NewClientToken( Desc: getDesc(b.Status),
bb.Client,
bb.Secret,
&oauth2.Token{
AccessToken: u.Token,
RefreshToken: u.Secret,
},
)
status := getStatus(b.Status)
desc := getDesc(b.Status)
data := BuildStatus{
State: status,
Key: "Drone", Key: "Drone",
Url: link, Url: link,
Desc: desc, }
return c.newClient(u).CreateStatus(r.Owner, r.Name, b.Commit, &status)
} }
err := client.CreateStatus(r.Owner, r.Name, b.Commit, &data) func (c *config) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
return err
}
// 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) {
return &model.Netrc{ return &model.Netrc{
Machine: "bitbucket.org", Machine: "bitbucket.org",
Login: "x-token-auth", Login: "x-token-auth",
@ -290,113 +212,73 @@ func (bb *Bitbucket) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
}, nil }, nil
} }
// Activate activates a repository by creating the post-commit hook and func (c *config) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
// adding the SSH deploy key, if applicable. rawurl, err := url.Parse(link)
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)
if err != nil { if err != nil {
log.Errorf("malformed hook url %s. %s", link, err)
return err return err
} }
// see if the hook already exists. If yes be sure to // deletes any previously created hooks
// delete so that multiple messages aren't sent. if err := c.Deactivate(u, r, link); err != nil {
hooks, _ := client.ListHooks(r.Owner, r.Name, &ListOpts{}) // 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 { for _, hook := range hooks.Values {
hookurl, err := url.Parse(hook.Url) hookurl, err := url.Parse(hook.Url)
if err != nil { if err != nil {
continue 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 { if hookurl.Host == linkurl.Host {
client.DeleteHook(r.Owner, r.Name, hook.Uuid) client.DeleteHook(r.Owner, r.Name, hook.Uuid)
break break // we can live with undeleted hooks
} }
} }
return nil return nil
} }
// Hook parses the post-commit hook from the Request body func (c *config) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
// and returns the required data in a standard format.
func (bb *Bitbucket) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
switch r.Header.Get("X-Event-Key") { switch r.Header.Get("X-Event-Key") {
case "repo:push": case "repo:push":
return bb.pushHook(r) return c.pushHook(r)
case "pullrequest:created", "pullrequest:updated": case "pullrequest:created", "pullrequest:updated":
return bb.pullHook(r) return c.pullHook(r)
} }
return nil, nil, nil return nil, nil, nil
} }
func (bb *Bitbucket) String() string { func (c *config) pushHook(r *http.Request) (*model.Repo, *model.Build, error) {
return "bitbucket"
}
func (bb *Bitbucket) pushHook(r *http.Request) (*model.Repo, *model.Build, error) {
payload := []byte(r.FormValue("payload")) payload := []byte(r.FormValue("payload"))
if len(payload) == 0 { if len(payload) == 0 {
defer r.Body.Close() defer r.Body.Close()
payload, _ = ioutil.ReadAll(r.Body) payload, _ = ioutil.ReadAll(r.Body)
} }
hook := PushHook{} hook := internal.PushHook{}
err := json.Unmarshal(payload, &hook) err := json.Unmarshal(payload, &hook)
if err != nil { if err != nil {
return nil, nil, err 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 // return the updated repository information and the
// build information. // build information.
// TODO(bradrydzewski) uses unit tested conversion function
return convertRepo(&hook.Repo), &model.Build{ return convertRepo(&hook.Repo), &model.Build{
Event: buildEventType, Event: buildEventType,
Commit: change.New.Target.Hash, 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 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")) payload := []byte(r.FormValue("payload"))
if len(payload) == 0 { if len(payload) == 0 {
defer r.Body.Close() defer r.Body.Close()
payload, _ = ioutil.ReadAll(r.Body) payload, _ = ioutil.ReadAll(r.Body)
} }
hook := PullRequestHook{} hook := internal.PullRequestHook{}
err := json.Unmarshal(payload, &hook) err := json.Unmarshal(payload, &hook)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -455,12 +338,13 @@ func (bb *Bitbucket) pullHook(r *http.Request) (*model.Repo, *model.Build, error
return nil, nil, nil return nil, nil, nil
} }
// TODO(bradrydzewski) uses unit tested conversion function
return convertRepo(&hook.Repo), &model.Build{ return convertRepo(&hook.Repo), &model.Build{
Event: model.EventPull, Event: model.EventPull,
Commit: hook.PullRequest.Dest.Commit.Hash, Commit: hook.PullRequest.Dest.Commit.Hash,
Ref: fmt.Sprintf("refs/heads/%s", hook.PullRequest.Dest.Branch.Name), Ref: fmt.Sprintf("refs/heads/%s", hook.PullRequest.Dest.Branch.Name),
Refspec: fmt.Sprintf("https://bitbucket.org/%s.git", hook.PullRequest.Source.Repo.FullName), 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, Link: hook.PullRequest.Links.Html.Href,
Branch: hook.PullRequest.Dest.Branch.Name, Branch: hook.PullRequest.Dest.Branch.Name,
Message: hook.PullRequest.Desc, 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(), Timestamp: hook.PullRequest.Updated.UTC().Unix(),
}, nil }, 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" "strings"
"github.com/drone/drone/model" "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 // convertRepo is a helper function used to convert a Bitbucket repository
// repository structure to the common Drone repository structure. // structure to the common Drone repository structure.
func convertRepo(from *Repo) *model.Repo { func convertRepo(from *internal.Repo) *model.Repo {
repo := model.Repo{ repo := model.Repo{
Clone: cloneLink(from),
Owner: strings.Split(from.FullName, "/")[0], Owner: strings.Split(from.FullName, "/")[0],
Name: strings.Split(from.FullName, "/")[1], Name: strings.Split(from.FullName, "/")[1],
FullName: from.FullName, FullName: from.FullName,
@ -20,66 +24,34 @@ func convertRepo(from *Repo) *model.Repo {
Kind: from.Scm, Kind: from.Scm,
Branch: "master", Branch: "master",
} }
if repo.Kind == model.RepoHg { if repo.Kind == model.RepoHg {
repo.Branch = "default" 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 return &repo
} }
// cloneLink is a helper function that tries to extract the // cloneLink is a helper function that tries to extract the clone url from the
// clone url from the repository object. // repository object.
func cloneLink(repo Repo) string { func cloneLink(repo *internal.Repo) string {
var clone string var clone string
// above we manually constructed the repository clone url. // above we manually constructed the repository clone url. below we will
// below we will iterate through the list of clone links and // iterate through the list of clone links and attempt to instead use the
// attempt to instead use the clone url provided by bitbucket. // clone url provided by bitbucket.
for _, link := range repo.Links.Clone { for _, link := range repo.Links.Clone {
if link.Name == "https" { if link.Name == "https" {
clone = link.Href clone = link.Href
} }
} }
// if no repository name is provided, we use the Html link. // if no repository name is provided, we use the Html link. this excludes the
// this excludes the .git suffix, but will still clone the repo. // .git suffix, but will still clone the repo.
if len(clone) == 0 { if len(clone) == 0 {
clone = repo.Links.Html.Href clone = repo.Links.Html.Href
} }
// if bitbucket tries to automatically populate the user // if bitbucket tries to automatically populate the user in the url we must
// in the url we must strip it out. // strip it out.
cloneurl, err := url.Parse(clone) cloneurl, err := url.Parse(clone)
if err == nil { if err == nil {
cloneurl.User = nil cloneurl.User = nil
@ -89,9 +61,9 @@ func cloneLink(repo Repo) string {
return clone return clone
} }
// convertRepoLite is a helper function used to convert a Bitbucket // convertRepoLite is a helper function used to convert a Bitbucket repository
// repository structure to the simplified Drone repository structure. // structure to the simplified Drone repository structure.
func convertRepoLite(from *Repo) *model.RepoLite { func convertRepoLite(from *internal.Repo) *model.RepoLite {
return &model.RepoLite{ return &model.RepoLite{
Owner: strings.Split(from.FullName, "/")[0], Owner: strings.Split(from.FullName, "/")[0],
Name: strings.Split(from.FullName, "/")[1], Name: strings.Split(from.FullName, "/")[1],
@ -99,3 +71,34 @@ func convertRepoLite(from *Repo) *model.RepoLite {
Avatar: from.Owner.Links.Avatar.Href, 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 ( import (
"bytes" "bytes"

View File

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

View File

@ -14,13 +14,15 @@ package bitbucketserver
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
log "github.com/Sirupsen/logrus"
"github.com/drone/drone/model"
"github.com/mrjones/oauth"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
log "github.com/Sirupsen/logrus"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/mrjones/oauth"
) )
type BitbucketServer struct { type BitbucketServer struct {
@ -33,6 +35,19 @@ type BitbucketServer struct {
Consumer oauth.Consumer 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 { func Load(config string) *BitbucketServer {
url_, err := url.Parse(config) url_, err := url.Parse(config)
@ -307,17 +322,17 @@ func (bs *BitbucketServer) String() string {
} }
type HookDetail struct { type HookDetail struct {
Key string `"json:key"` Key string `json:"key"`
Name string `"json:name"` Name string `json:"name"`
Type string `"json:type"` Type string `json:"type"`
Description string `"json:description"` Description string `json:"description"`
Version string `"json:version"` Version string `json:"version"`
ConfigFormKey string `"json:configFormKey"` ConfigFormKey string `json:"configFormKey"`
} }
type Hook struct { type Hook struct {
Enabled bool `"json:enabled"` Enabled bool `json:"enabled"`
Details *HookDetail `"json:details"` Details *HookDetail `json:"details"`
} }
// Enable hook for named repository // 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" "strings"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/shared/httputil" "github.com/drone/drone/shared/httputil"
"github.com/drone/drone/shared/oauth2" "github.com/drone/drone/shared/oauth2"
log "github.com/Sirupsen/logrus"
"github.com/google/go-github/github" "github.com/google/go-github/github"
) )
const ( const (
DefaultURL = "https://github.com" DefaultURL = "https://github.com" // Default GitHub URL
DefaultAPI = "https://api.github.com" DefaultAPI = "https://api.github.com" // Default GitHub API URL
DefaultScope = "repo,repo:status,user:email"
DefaultMergeRef = "merge"
) )
var githubDeployRegex = regexp.MustCompile(".+/deployments/(\\d+)")
type Github struct { type Github struct {
URL string URL string
API string API string
@ -34,58 +30,35 @@ type Github struct {
Secret string Secret string
Scope string Scope string
MergeRef string MergeRef string
Orgs []string
Open bool
PrivateMode bool PrivateMode bool
SkipVerify bool SkipVerify bool
GitSSH bool
} }
func Load(config string) *Github { func New(url, client, secret string, scope []string, private, skipverify, mergeref bool) (remote.Remote, error) {
remote := &Github{
// parse the remote DSN configuration string URL: strings.TrimSuffix(url, "/"),
url_, err := url.Parse(config) Client: client,
if err != nil { Secret: secret,
log.Fatalln("unable to parse remote dsn. %s", err) Scope: strings.Join(scope, ","),
PrivateMode: private,
SkipVerify: skipverify,
MergeRef: "head",
} }
params := url_.Query()
url_.Path = ""
url_.RawQuery = ""
// create the Githbub remote using parameters from if remote.URL == DefaultURL {
// the parsed DSN configuration string. remote.API = DefaultAPI
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
} else { } else {
github.API = github.URL + "/api/v3/" remote.API = remote.URL + "/api/v3/"
}
if mergeref {
remote.MergeRef = "merge"
} }
if github.Scope == "" { return remote, nil
github.Scope = DefaultScope
} }
if github.MergeRef == "" { // Login authenticates the session and returns the remote user details.
github.MergeRef = DefaultMergeRef func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
}
return &github
}
// Login authenticates the session and returns the
// remote user details.
func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
var config = &oauth2.Config{ var config = &oauth2.Config{
ClientId: g.Client, ClientId: g.Client,
@ -101,7 +74,7 @@ func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User,
if len(code) == 0 { if len(code) == 0 {
var random = GetRandom() var random = GetRandom()
http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther) http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther)
return nil, false, nil return nil, nil
} }
var trans = &oauth2.Transport{ 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) var token, err = trans.Exchange(code)
if err != nil { 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 client = NewClient(g.API, token.AccessToken, g.SkipVerify)
var useremail, errr = GetUserEmail(client) var useremail, errr = GetUserEmail(client)
if errr != nil { if errr != nil {
return nil, false, fmt.Errorf("Error retrieving user or verified email. %s", errr) return nil, 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)
}
} }
user := model.User{} user := model.User{}
@ -141,7 +104,7 @@ func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User,
user.Email = *useremail.Email user.Email = *useremail.Email
user.Token = token.AccessToken user.Token = token.AccessToken
user.Avatar = *useremail.AvatarURL user.Avatar = *useremail.AvatarURL
return &user, g.Open, nil return &user, nil
} }
// Auth authenticates the session and returns the remote user // 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 return *user.Login, nil
} }
// Repo fetches the named repository from the remote system. func (g *Github) Teams(u *model.User) ([]*model.Team, error) {
func (g *Github) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := NewClient(g.API, u.Token, g.SkipVerify) client := NewClient(g.API, u.Token, g.SkipVerify)
repo_, err := GetRepo(client, owner, name) orgs, err := GetOrgs(client)
if err != nil { if err != nil {
return nil, err return nil, err
} }
repo := &model.Repo{} var teams []*model.Team
repo.Owner = owner for _, org := range orgs {
repo.Name = name teams = append(teams, &model.Team{
repo.FullName = *repo_.FullName Login: *org.Login,
repo.Link = *repo_.HTMLURL Avatar: *org.AvatarURL,
repo.IsPrivate = *repo_.Private })
repo.Clone = *repo_.CloneURL }
repo.Branch = "master" return teams, nil
repo.Avatar = *repo_.Owner.AvatarURL }
repo.Kind = model.RepoGit
if repo_.DefaultBranch != nil { // Repo fetches the named repository from the remote system.
repo.Branch = *repo_.DefaultBranch 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 { if g.PrivateMode {
repo.IsPrivate = true repo.IsPrivate = true
} }
if g.GitSSH && repo.IsPrivate {
repo.Clone = *repo_.SSHURL
}
return repo, err return repo, err
} }
@ -257,8 +235,10 @@ func repoStatus(client *github.Client, r *model.Repo, b *model.Build, link strin
return err return err
} }
var reDeploy = regexp.MustCompile(".+/deployments/(\\d+)")
func deploymentStatus(client *github.Client, r *model.Repo, b *model.Build, link string) error { 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 the deployment was not triggered from github, don't send a deployment status
if len(matches) != 2 { if len(matches) != 2 {
return nil 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. // adding the SSH deploy key, if applicable.
func (g *Github) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error { func (g *Github) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
client := NewClient(g.API, u.Token, g.SkipVerify) client := NewClient(g.API, u.Token, g.SkipVerify)
title, err := GetKeyTitle(link) _, err := CreateUpdateHook(client, r.Owner, r.Name, 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)
return err 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. // which are equal to link and removing the SSH deploy key.
func (g *Github) Deactivate(u *model.User, r *model.Repo, link string) error { func (g *Github) Deactivate(u *model.User, r *model.Repo, link string) error {
client := NewClient(g.API, u.Token, g.SkipVerify) 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) 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" // 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" { // g := Load(conf)
t.Errorf("g.URL = %q; want https://github.com", g.URL) // 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.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.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.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.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) // if g.MergeRef != DefaultMergeRef {
} // t.Errorf("g.MergeRef = %q; want %q", g.MergeRef, DefaultMergeRef)
// }
g = Load("") //
if g.Scope != DefaultScope { // g = Load("")
t.Errorf("g.Scope = %q; want %q", g.Scope, DefaultScope) // 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 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 // GetUserRepos is a helper function that returns a list of
// all user repositories. Paginated results are aggregated into // all user repositories. Paginated results are aggregated into
// a single list. // a single list.
@ -143,30 +96,6 @@ func GetUserRepos(client *github.Client) ([]github.Repository, error) {
return repos, nil 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 // GetOrgs is a helper function that returns a list of
// all orgs that a user belongs to. // all orgs that a user belongs to.
func GetOrgs(client *github.Client) ([]github.Organization, error) { 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) 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 // GetFile is a heper function that retrieves a file from
// GitHub and returns its contents in byte array format. // GitHub and returns its contents in byte array format.
func GetFile(client *github.Client, owner, name, path, ref string) ([]byte, error) { 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) 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" "strings"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/shared/httputil" "github.com/drone/drone/shared/httputil"
"github.com/drone/drone/shared/oauth2" "github.com/drone/drone/shared/oauth2"
"github.com/drone/drone/shared/token" "github.com/drone/drone/shared/token"
@ -34,6 +35,16 @@ type Gitlab struct {
Search bool 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 { func Load(config string) *Gitlab {
url_, err := url.Parse(config) url_, err := url.Parse(config)
if err != nil { if err != nil {

View File

@ -6,44 +6,33 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"strconv"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/gogits/go-gogs-client" "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 URL string
Open bool Open bool
PrivateMode bool PrivateMode bool
SkipVerify bool SkipVerify bool
} }
func Load(config string) *Gogs { // New returns a Remote implementation that integrates with Gogs, an open
// parse the remote DSN configuration string // source Git service written in Go. See https://gogs.io/
url_, err := url.Parse(config) func New(url string, private, skipverify bool) remote.Remote {
if err != nil { return &Remote{
log.Fatalln("unable to parse remote dsn. %s", err) 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 // Login authenticates the session and returns the authenticated user.
// remote user details. func (g *Remote) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
func (g *Gogs) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
var ( var (
username = req.FormValue("username") username = req.FormValue("username")
password = req.FormValue("password") 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.Login = userInfo.UserName
user.Email = userInfo.Email user.Email = userInfo.Email
user.Avatar = expandAvatar(g.URL, userInfo.AvatarUrl) 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 // Auth authenticates the session and returns the remote user
// login for the given token and secret // 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") return "", fmt.Errorf("Method not supported")
} }
// Repo fetches the named repository from the remote system. // 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) client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
repos_, err := client.ListMyRepos() repos_, err := client.ListMyRepos()
if err != nil { 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. // 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{} repos := []*model.RepoLite{}
client := NewGogsClient(g.URL, u.Token, g.SkipVerify) 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 // Perm fetches the named repository permissions from
// the remote system for the specified user. // 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) client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
repos_, err := client.ListMyRepos() repos_, err := client.ListMyRepos()
if err != nil { 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. // 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) client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
cfg, err := client.GetFile(r.Owner, r.Name, b.Commit, f) cfg, err := client.GetFile(r.Owner, r.Name, b.Commit, f)
return cfg, err 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. // Status sends the commit status to the remote system.
// An example would be the GitHub pull request status. // 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") return fmt.Errorf("Not Implemented")
} }
// Netrc returns a .netrc file that can be used to clone // Netrc returns a .netrc file that can be used to clone
// private repositories from a remote system. // 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) url_, err := url.Parse(g.URL)
if err != nil { if err != nil {
return nil, err 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 // Activate activates a repository by creating the post-commit hook and
// adding the SSH deploy key, if applicable. // 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{ config := map[string]string{
"url": link, "url": link,
"secret": r.Hash, "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 // Deactivate removes a repository by removing all the post-commit hooks
// which are equal to link and removing the SSH deploy key. // 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") return fmt.Errorf("Not Implemented")
} }
// Hook parses the post-commit hook from the Request body // Hook parses the post-commit hook from the Request body
// and returns the required data in a standard format. // 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 ( var (
err error err error
repo *model.Repo repo *model.Repo
@ -246,6 +235,6 @@ func NewGogsClient(url, token string, skipVerify bool) *gogs.Client {
return c return c
} }
func (g *Gogs) String() string { func (g *Remote) String() string {
return "gogs" return "gogs"
} }

View File

@ -1,42 +1,32 @@
package mock package mock
import "github.com/stretchr/testify/mock" import (
"net/http"
import "net/http" "github.com/drone/drone/model"
import "github.com/drone/drone/model" "github.com/stretchr/testify/mock"
)
// This is an autogenerated mock type for the Remote type
type Remote struct { type Remote struct {
mock.Mock mock.Mock
} }
func (_m *Remote) Login(w http.ResponseWriter, r *http.Request) (*model.User, bool, error) { // Activate provides a mock function with given fields: u, r, k, link
ret := _m.Called(w, r) 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 var r0 error
if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) *model.User); ok { if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, *model.Key, string) error); ok {
r0 = rf(w, r) r0 = rf(u, r, k, link)
} else { } else {
if ret.Get(0) != nil { r0 = ret.Error(0)
r0 = ret.Get(0).(*model.User)
}
} }
var r1 bool return r0
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 // Auth provides a mock function with given fields: token, secret
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
}
func (_m *Remote) Auth(token string, secret string) (string, error) { func (_m *Remote) Auth(token string, secret string) (string, error) {
ret := _m.Called(token, secret) ret := _m.Called(token, secret)
@ -56,69 +46,22 @@ func (_m *Remote) Auth(token string, secret string) (string, error) {
return r0, r1 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 // Deactivate provides a mock function with given fields: u, r, link
if rf, ok := ret.Get(0).(func(*model.User, string, string) *model.Repo); ok { func (_m *Remote) Deactivate(u *model.User, r *model.Repo, link string) error {
r0 = rf(u, owner, repo) 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 { } else {
if ret.Get(0) != nil { r0 = ret.Error(0)
r0 = ret.Get(0).(*model.Repo)
}
} }
var r1 error return r0
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) 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
}
func (_m *Remote) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) { func (_m *Remote) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
ret := _m.Called(u, r, b, f) 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 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 // Hook provides a mock function with given fields: r
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
}
func (_m *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) { func (_m *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
ret := _m.Called(r) ret := _m.Called(r)
@ -227,3 +115,155 @@ func (_m *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
return r0, r1, r2 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 { type Remote interface {
// Login authenticates the session and returns the // Login authenticates the session and returns the
// remote user details. // 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 // Auth authenticates the session and returns the remote user
// login for the given token and secret // login for the given token and secret
Auth(token, secret string) (string, error) 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 fetches the named repository from the remote system.
Repo(u *model.User, owner, repo string) (*model.Repo, error) 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. // which are equal to link and removing the SSH deploy key.
Deactivate(u *model.User, r *model.Repo, link string) error Deactivate(u *model.User, r *model.Repo, link string) error
// Hook parses the post-commit hook from the Request body // Hook parses the post-commit hook from the Request body and returns the
// and returns the required data in a standard format. // required data in a standard format.
Hook(r *http.Request) (*model.Repo, *model.Build, error) 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 { 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) Refresh(*model.User) (bool, error)
} }
// Login authenticates the session and returns the // Login authenticates the session and returns the
// remote user details. // 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) 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) 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. // Repo fetches the named repository from the remote system.
func Repo(c context.Context, u *model.User, owner, repo string) (*model.Repo, error) { func Repo(c context.Context, u *model.User, owner, repo string) (*model.Repo, error) {
return FromContext(c).Repo(u, owner, repo) return FromContext(c).Repo(u, owner, repo)

View File

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