Merge pull request #1607 from bradrydzewski/master

bump 0.5 version
This commit is contained in:
Brad Rydzewski 2016-05-02 12:55:31 -07:00
commit f52feeceb1
93 changed files with 3577 additions and 4115 deletions

View file

@ -1 +1 @@
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.Y_yR-WIrz_Shk94TlGh1mhLM5NocQeh1nRTz65Hfn8jo78WtVF9ZDUQsf6z2bpipTp4y0TmSjWHLvQHQf1O_LZ-AxLHkG3_XGqeWl7Jd3lE_xsBeWeOIrC3QKx8dNiyU0FKVCoPwfaMpjpXAFpG4ZesNfpTrxaaoT-0PBYYpbVvAi-lIhh5lqKSDlWRkrzR3pvzLPeY-Pq4yb-DE0wLzAOh5nasde3qIKo9VT2fnRQIPIIC6V4vNyi7EdYDhNcdgx9LCFKIYVkCPRRI1R9mG8tyjpLy47t0gs3y-Gcr8MyNE1lPlToX4JaQ4EKcZZAAf2CVCJ92s1Bp6CZWLiFt9aQ.RoWDoMbR2ICXQPlo.7d_JtqJvLPPAdmIMOoD961rO7KZXwY-Fz8GHx98hdHqr27igkoKm1BXqYqlh043wKkHwVaAy5jNAOUdweJKrb5YJGPdk3Lyh4ZpEDvcAiwPgwsf4_Q1Xhw9A1jSOyapFlaSAR2aKj_370yx0y8htgxTeXj5Nv5n7ZZ4ezYstU8xCF9JxzsBxOfFACqiyZr9ZJcm-47mlftcU1nZy1x9005Vq6oZv9FAk4zwKLpjMC3KC3H252PqbL_U-e9j1i5tlrcDiEkW8gLoMq_RbMj5J6w3j0u4eoUV5e-WfjentG5ZIf_e8c8-oKxXF2A1vdVdswzWhQWoIEs2KDqBWmSPsCNHKHE2t4grnDPxipMJByAUSsEE9_NVWRS2ofjw01YpQd8cRV6v-eKBlF-c1QD4ABqBu0iMYTEuScg.AAAOUFIZbiVDhUdpARCZ9g
eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.RPXRWzmKbZ6KShRzmd1adFofHAofk6weJrzfjEd0-gRoscpoHrbvGy-DOUVOjwfI41H52EBFV8L_-8uXdRKkCsLnzDpWYmbSKAkKLGDMAILlQcGr4-C_6_duP8nK6Lw2MuAeP8KACBnHVXaosks-ARr7bC5hzQBgJLMuxw3O40jkMS7J62T1Oo7CRkSlLpAIu0axmWQ6ZlfRhijyZUMAJTfegykgrMbpL-FlyDsRpHYkCC0Ny5tN2Q4Gocmxwgitv8_uNZqXYuOAxQXEwXTSyLkXhBdIDtNLDXkAmglTdCB7mis-SmOw4lxSEMAU20bFqOrtbOP_5jwIPoLO8FR4Fw.TU_hlTlqCRZdI-Jw.NBZUu2Wv6ZRzoRpcrEyg2V-me_XEAI22HT_BOJ1NS1bhPLWx1zCbu8hfWNI9RBUMvppSzFw6leeUXgFGqOjGquVvAOCBi0pKuPVW5jGkmv3kM43ciUzzR5MErAg_VPqQjKkV5RvsSu7gKHr6PTSOmc8hPU9JyfNNMUh4MGOHJnvv3I-oKJva3oOt-y9KPsuGLo-6hM1WbhYyPvcm3PSiBrKkOZM5f0_2nqcZZzHQ8gvo5BmzxcSAYVmKRo8_rLROMqT1fycnWg_4qsJuD6molP9b-88Vb0vrZ2jpvm-f_Cq2psSPPMQIcxSvlweO-dP7u0WvdLnOsIb-cq6HxqorTxtKSKcsGbqv66gLsThsa8KDivreyRFyHhTiKwTugv8Kw4Fxsfhj6hzbuI6Vy5Utyr6OJ30MRpg8kg.qKp-C7QNFaeU0HZJ6Qwp8g

View file

@ -35,7 +35,7 @@ publish:
password: $$DOCKER_PASS
email: $$DOCKER_EMAIL
repo: drone/drone
tag: [ "latest", "0.4.2" ]
tag: [ "0.5.0" ]
when:
repo: drone/drone
branch: master

2
.gitignore vendored
View file

@ -12,7 +12,7 @@ drone/drone
.env
temp/
api/swagger/files/*
server/swagger/files/*.json
# vendored repositories that we don't actually need
# to vendor. so exclude them

View file

@ -21,4 +21,4 @@ ADD drone/drone /drone
#RUN echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf
ENTRYPOINT ["/drone"]
CMD ["serve"]
CMD ["daemon"]

View file

@ -1,80 +0,0 @@
package api
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/drone/drone/engine"
"github.com/drone/drone/model"
"github.com/drone/drone/store"
)
func GetNodes(c *gin.Context) {
nodes, err := store.GetNodeList(c)
if err != nil {
c.String(400, err.Error())
} else {
c.JSON(200, nodes)
}
}
func GetNode(c *gin.Context) {
}
func PostNode(c *gin.Context) {
engine := engine.FromContext(c)
in := struct {
Addr string `json:"address"`
Arch string `json:"architecture"`
Cert string `json:"cert"`
Key string `json:"key"`
CA string `json:"ca"`
}{}
err := c.Bind(&in)
if err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}
node := &model.Node{}
node.Addr = in.Addr
node.Cert = in.Cert
node.Key = in.Key
node.CA = in.CA
node.Arch = "linux_amd64"
err = engine.Allocate(node)
if err != nil {
c.String(http.StatusBadRequest, err.Error())
return
}
err = store.CreateNode(c, node)
if err != nil {
c.AbortWithStatus(http.StatusInternalServerError)
return
}
c.IndentedJSON(http.StatusOK, node)
}
func DeleteNode(c *gin.Context) {
engine := engine.FromContext(c)
id, _ := strconv.Atoi(c.Param("node"))
node, err := store.GetNode(c, int64(id))
if err != nil {
c.AbortWithStatus(http.StatusNotFound)
return
}
err = store.DeleteNode(c, node)
if err != nil {
c.AbortWithStatus(http.StatusInternalServerError)
return
}
engine.Deallocate(node)
}

View file

@ -1,3 +0,0 @@
package swagger
//go:generate go-bindata -pkg swagger -o swagger_gen.go files/

View file

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

481
drone/daemon.go Normal file
View file

@ -0,0 +1,481 @@
package main
import (
"fmt"
"net/http"
"os"
"time"
"github.com/drone/drone/router"
"github.com/drone/drone/router/middleware"
"github.com/gin-gonic/contrib/ginrus"
"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.StringFlag{
EnvVar: "DRONE_GOGS_GIT_USERNAME",
Name: "gogs-git-username",
Usage: "gogs service account username",
},
cli.StringFlag{
EnvVar: "DRONE_GOGS_GIT_PASSWORD",
Name: "gogs-git-password",
Usage: "gogs service account password",
},
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.StringFlag{
EnvVar: "DRONE_GITLAB_GIT_USERNAME",
Name: "gitlab-git-username",
Usage: "gitlab service account username",
},
cli.StringFlag{
EnvVar: "DRONE_GITLAB_GIT_PASSWORD",
Name: "gitlab-git-password",
Usage: "gitlab service account password",
},
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",
},
cli.BoolFlag{
EnvVar: "DRONE_STASH_SKIP_VERIFY",
Name: "stash-skip-verify",
Usage: "stash skip ssl verification",
},
//
// 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)
}
// setup the server and start the listener
handler := router.Load(
ginrus.Ginrus(logrus.StandardLogger(), time.RFC3339, true),
middleware.Version,
middleware.Config(c),
middleware.Queue(c),
middleware.Stream(c),
middleware.Bus(c),
middleware.Cache(c),
middleware.Store(c),
middleware.Remote(c),
middleware.Agents(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"),
handler,
)
}
// start the server without tls enabled
return http.ListenAndServe(
c.String("server-addr"),
handler,
)
}
//
// 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 {
// var remote remote.Remote
// var err error
// switch {
// case c.Bool("github"):
// remote, err = setupGithub(c)
// case c.Bool("gitlab"):
// remote, err = setupGitlab(c)
// case c.Bool("bitbucket"):
// remote, err = setupBitbucket(c)
// case c.Bool("stash"):
// remote, err = setupStash(c)
// case c.Bool("gogs"):
// remote, err = setupGogs(c)
// default:
// err = fmt.Errorf("version control system not configured")
// }
// if err != nil {
// logrus.Fatalln(err)
// }
// return remote
// }
//
// func setupBitbucket(c *cli.Context) (remote.Remote, error) {
// return bitbucket.New(
// c.String("bitbucket-client"),
// c.String("bitbucket-server"),
// ), nil
// }
//
// func setupGogs(c *cli.Context) (remote.Remote, error) {
// return gogs.New(gogs.Opts{
// URL: c.String("gogs-server"),
// Username: c.String("gogs-git-username"),
// Password: c.String("gogs-git-password"),
// PrivateMode: c.Bool("gogs-private-mode"),
// SkipVerify: c.Bool("gogs-skip-verify"),
// })
// }
//
// func setupStash(c *cli.Context) (remote.Remote, error) {
// return bitbucketserver.New(bitbucketserver.Opts{
// URL: c.String("stash-server"),
// Username: c.String("stash-git-username"),
// Password: c.String("stash-git-password"),
// ConsumerKey: c.String("stash-consumer-key"),
// ConsumerRSA: c.String("stash-consumer-rsa"),
// SkipVerify: c.Bool("stash-skip-verify"),
// })
// }
//
// func setupGitlab(c *cli.Context) (remote.Remote, error) {
// return gitlab.New(gitlab.Opts{
// URL: c.String("gitlab-server"),
// Client: c.String("gitlab-client"),
// Secret: c.String("gitlab-sercret"),
// Username: c.String("gitlab-git-username"),
// Password: c.String("gitlab-git-password"),
// PrivateMode: c.Bool("gitlab-private-mode"),
// SkipVerify: c.Bool("gitlab-skip-verify"),
// })
// }
//
// func setupGithub(c *cli.Context) (remote.Remote, error) {
// return 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"),
// )
// }
//
// func setupConfig(c *cli.Context) *server.Config {
// return &server.Config{
// Open: c.Bool("open"),
// Yaml: c.String("yaml"),
// Secret: c.String("agent-secret"),
// Admins: sliceToMap(c.StringSlice("admin")),
// Orgs: sliceToMap(c.StringSlice("orgs")),
// }
// }
//
// func sliceToMap(s []string) map[string]bool {
// v := map[string]bool{}
// for _, ss := range s {
// v[ss] = true
// }
// return v
// }
//
// func printSecret(c *cli.Context) error {
// secret := c.String("agent-secret")
// if secret == "" {
// return fmt.Errorf("missing DRONE_AGENT_SECRET configuration parameter")
// }
// t := token.New(secret, "")
// s, err := t.Sign(secret)
// if err != nil {
// return fmt.Errorf("invalid value for DRONE_AGENT_SECRET. %s", s)
// }
//
// logrus.Infof("using agent secret %s", secret)
// logrus.Warnf("agents can connect with token %s", s)
// return nil
// }
var agreement = `
---
You are attempting to use the unstable channel. This build is experimental and
has known bugs and compatibility issues. It is not intended for general use.
Please consider using the latest stable release instead:
drone/drone:0.4.2
If you are attempting to build from source please use the latest stable tag:
v0.4.2
If you are interested in testing this experimental build AND assisting with
development you may proceed by setting the following environment:
I_UNDERSTAND_I_AM_USING_AN_UNSTABLE_VERSION=true
I_AGREE_TO_FIX_BUGS_AND_NOT_FILE_BUGS=true
---
`

View file

@ -4,7 +4,6 @@ import (
"os"
"github.com/drone/drone/drone/agent"
"github.com/drone/drone/drone/server"
"github.com/drone/drone/version"
"github.com/codegangsta/cli"
@ -12,7 +11,7 @@ import (
_ "github.com/joho/godotenv/autoload"
)
func main2() {
func main() {
envflag.Parse()
app := cli.NewApp()
@ -35,7 +34,7 @@ func main2() {
}
app.Commands = []cli.Command{
agent.AgentCmd,
server.ServeCmd,
DaemonCmd,
SignCmd,
SecretCmd,
}

View file

@ -1,62 +0,0 @@
package main
import (
"net/http"
"os"
"time"
"github.com/drone/drone/router"
"github.com/drone/drone/router/middleware"
"github.com/Sirupsen/logrus"
"github.com/gin-gonic/contrib/ginrus"
"github.com/ianschenck/envflag"
_ "github.com/joho/godotenv/autoload"
)
var (
addr = envflag.String("SERVER_ADDR", ":8000", "")
cert = envflag.String("SERVER_CERT", "", "")
key = envflag.String("SERVER_KEY", "", "")
debug = envflag.Bool("DEBUG", false, "")
)
func main() {
if os.Getenv("CANARY") == "true" {
main2()
return
}
envflag.Parse()
// debug level if requested by user
if *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(),
middleware.Engine(),
)
if *cert != "" {
logrus.Fatal(
http.ListenAndServeTLS(*addr, *cert, *key, handler),
)
} else {
logrus.Fatal(
http.ListenAndServe(*addr, handler),
)
}
}

View file

@ -1,130 +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(),
middleware.Engine(),
)
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, and 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 will need to set the following environment variables to proceed:
I_UNDERSTAND_I_AM_USING_AN_UNSTABLE_VERSION=true
I_AGREE_TO_FIX_BUGS_AND_NOT_FILE_BUGS=true
---
`

View file

@ -1,48 +0,0 @@
package engine
import (
"sync"
)
type eventbus struct {
sync.Mutex
subs map[chan *Event]bool
}
// New creates a new eventbus that manages a list of
// subscribers to which events are published.
func newEventbus() *eventbus {
return &eventbus{
subs: make(map[chan *Event]bool),
}
}
// Subscribe adds the channel to the list of
// subscribers. Each subscriber in the list will
// receive broadcast events.
func (b *eventbus) subscribe(c chan *Event) {
b.Lock()
b.subs[c] = true
b.Unlock()
}
// Unsubscribe removes the channel from the
// list of subscribers.
func (b *eventbus) unsubscribe(c chan *Event) {
b.Lock()
delete(b.subs, c)
b.Unlock()
}
// Send dispatches a message to all subscribers.
func (b *eventbus) send(event *Event) {
b.Lock()
defer b.Unlock()
for s := range b.subs {
go func(c chan *Event) {
defer recover()
c <- event
}(s)
}
}

View file

@ -1,50 +0,0 @@
package engine
import (
"testing"
. "github.com/franela/goblin"
)
func TestBus(t *testing.T) {
g := Goblin(t)
g.Describe("Event bus", func() {
g.It("Should unsubscribe", func() {
c1 := make(chan *Event)
c2 := make(chan *Event)
b := newEventbus()
b.subscribe(c1)
b.subscribe(c2)
g.Assert(len(b.subs)).Equal(2)
})
g.It("Should subscribe", func() {
c1 := make(chan *Event)
c2 := make(chan *Event)
b := newEventbus()
b.subscribe(c1)
b.subscribe(c2)
g.Assert(len(b.subs)).Equal(2)
b.unsubscribe(c1)
b.unsubscribe(c2)
g.Assert(len(b.subs)).Equal(0)
})
g.It("Should send", func() {
em := map[string]bool{"foo": true, "bar": true}
e1 := &Event{Name: "foo"}
e2 := &Event{Name: "bar"}
c := make(chan *Event)
b := newEventbus()
b.subscribe(c)
b.send(e1)
b.send(e2)
r1 := <-c
r2 := <-c
g.Assert(em[r1.Name]).Equal(true)
g.Assert(em[r2.Name]).Equal(true)
})
})
}

View file

@ -1,23 +0,0 @@
package engine
import (
"golang.org/x/net/context"
)
const key = "engine"
// Setter defines a context that enables setting values.
type Setter interface {
Set(string, interface{})
}
// FromContext returns the Engine associated with this context.
func FromContext(c context.Context) Engine {
return c.Value(key).(Engine)
}
// ToContext adds the Engine to this context if it supports
// the Setter interface.
func ToContext(c Setter, engine Engine) {
c.Set(key, engine)
}

View file

@ -1,444 +0,0 @@
package engine
import (
"bytes"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"runtime"
"time"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/stdcopy"
"github.com/drone/drone/model"
"github.com/drone/drone/shared/docker"
"github.com/drone/drone/store"
"github.com/samalba/dockerclient"
"golang.org/x/net/context"
)
type Engine interface {
Schedule(context.Context, *Task)
Cancel(int64, int64, *model.Node) error
Stream(int64, int64, *model.Node) (io.ReadCloser, error)
Deallocate(*model.Node)
Allocate(*model.Node) error
Subscribe(chan *Event)
Unsubscribe(chan *Event)
}
var (
// options to fetch the stdout and stderr logs
logOpts = &dockerclient.LogOptions{
Stdout: true,
Stderr: true,
}
// options to fetch the stdout and stderr logs
// by tailing the output.
logOptsTail = &dockerclient.LogOptions{
Follow: true,
Stdout: true,
Stderr: true,
}
// error when the system cannot find logs
errLogging = errors.New("Logs not available")
)
type engine struct {
bus *eventbus
updater *updater
pool *pool
envs []string
}
// Load creates a new build engine, loaded with registered nodes from the
// database. The registered nodes are added to the pool of nodes to immediately
// start accepting workloads.
func Load(s store.Store) Engine {
engine := &engine{}
engine.bus = newEventbus()
engine.pool = newPool()
engine.updater = &updater{engine.bus}
// quick fix to propagate HTTP_PROXY variables
// throughout the build environment.
var proxyVars = []string{"HTTP_PROXY", "http_proxy", "HTTPS_PROXY", "https_proxy", "NO_PROXY", "no_proxy"}
for _, proxyVar := range proxyVars {
proxyVal := os.Getenv(proxyVar)
if len(proxyVal) != 0 {
engine.envs = append(engine.envs, proxyVar+"="+proxyVal)
}
}
nodes, err := s.GetNodeList()
if err != nil {
log.Fatalf("failed to get nodes from database. %s", err)
}
for _, node := range nodes {
engine.pool.allocate(node)
log.Infof("registered docker daemon %s", node.Addr)
}
return engine
}
// Cancel cancels the job running on the specified Node.
func (e *engine) Cancel(build, job int64, node *model.Node) error {
client, err := newDockerClient(node.Addr, node.Cert, node.Key, node.CA)
if err != nil {
return err
}
id := fmt.Sprintf("drone_build_%d_job_%d", build, job)
return client.StopContainer(id, 30)
}
// Stream streams the job output from the specified Node.
func (e *engine) Stream(build, job int64, node *model.Node) (io.ReadCloser, error) {
client, err := newDockerClient(node.Addr, node.Cert, node.Key, node.CA)
if err != nil {
log.Errorf("cannot create Docker client for node %s", node.Addr)
return nil, err
}
id := fmt.Sprintf("drone_build_%d_job_%d", build, job)
log.Debugf("streaming container logs %s", id)
return client.ContainerLogs(id, logOptsTail)
}
// Subscribe subscribes the channel to all build events.
func (e *engine) Subscribe(c chan *Event) {
e.bus.subscribe(c)
}
// Unsubscribe unsubscribes the channel from all build events.
func (e *engine) Unsubscribe(c chan *Event) {
e.bus.unsubscribe(c)
}
func (e *engine) Allocate(node *model.Node) error {
// run the full build!
client, err := newDockerClient(node.Addr, node.Cert, node.Key, node.CA)
if err != nil {
log.Errorf("error creating docker client %s. %s.", node.Addr, err)
return err
}
version, err := client.Version()
if err != nil {
log.Errorf("error connecting to docker daemon %s. %s.", node.Addr, err)
return err
}
log.Infof("registered docker daemon %s running version %s", node.Addr, version.Version)
e.pool.allocate(node)
return nil
}
func (e *engine) Deallocate(n *model.Node) {
nodes := e.pool.list()
for _, node := range nodes {
if node.ID == n.ID {
log.Infof("un-registered docker daemon %s", node.Addr)
e.pool.deallocate(node)
break
}
}
}
func (e *engine) Schedule(c context.Context, req *Task) {
node := <-e.pool.reserve()
// since we are probably running in a go-routine
// make sure we recover from any panics so that
// a bug doesn't crash the whole system.
defer func() {
if err := recover(); err != nil {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
log.Errorf("panic running build: %v\n%s", err, string(buf))
}
e.pool.release(node)
}()
// update the node that was allocated to each job
func(id int64) {
for _, job := range req.Jobs {
job.NodeID = id
store.UpdateJob(c, job)
}
}(node.ID)
// run the full build!
client, err := newDockerClient(node.Addr, node.Cert, node.Key, node.CA)
if err != nil {
log.Errorln("error creating docker client", err)
}
// update the build state if any of the sub-tasks
// had a non-success status
req.Build.Started = time.Now().UTC().Unix()
req.Build.Status = model.StatusRunning
e.updater.SetBuild(c, req)
// run all bulid jobs
for _, job := range req.Jobs {
req.Job = job
e.runJob(c, req, e.updater, client)
}
// update overall status based on each job
req.Build.Status = model.StatusSuccess
for _, job := range req.Jobs {
if job.Status != model.StatusSuccess {
req.Build.Status = job.Status
break
}
}
req.Build.Finished = time.Now().UTC().Unix()
err = e.updater.SetBuild(c, req)
if err != nil {
log.Errorf("error updating build completion status. %s", err)
}
// run notifications
err = e.runJobNotify(req, client)
if err != nil {
log.Errorf("error executing notification step. %s", err)
}
}
func newDockerClient(addr, cert, key, ca string) (dockerclient.Client, error) {
var tlc *tls.Config
// create the Docket client TLS config
if len(cert) != 0 {
pem, err := tls.X509KeyPair([]byte(cert), []byte(key))
if err != nil {
log.Errorf("error loading X509 key pair. %s.", err)
return dockerclient.NewDockerClient(addr, nil)
}
// create the TLS configuration for secure
// docker communications.
tlc = &tls.Config{}
tlc.Certificates = []tls.Certificate{pem}
// use the certificate authority if provided.
// else don't use a certificate authority and set
// skip verify to true
if len(ca) != 0 {
log.Infof("creating docker client %s with CA", addr)
pool := x509.NewCertPool()
pool.AppendCertsFromPEM([]byte(ca))
tlc.RootCAs = pool
} else {
log.Infof("creating docker client %s WITHOUT CA", addr)
tlc.InsecureSkipVerify = true
}
}
// create the Docker client. In this version of Drone (alpha)
// we do not spread builds across clients, but this can and
// (probably) will change in the future.
return dockerclient.NewDockerClient(addr, tlc)
}
func (e *engine) runJob(c context.Context, r *Task, updater *updater, client dockerclient.Client) error {
name := fmt.Sprintf("drone_build_%d_job_%d", r.Build.ID, r.Job.ID)
defer func() {
if r.Job.Status == model.StatusRunning {
r.Job.Status = model.StatusError
r.Job.Finished = time.Now().UTC().Unix()
r.Job.ExitCode = 255
}
if r.Job.Status == model.StatusPending {
r.Job.Status = model.StatusError
r.Job.Started = time.Now().UTC().Unix()
r.Job.Finished = time.Now().UTC().Unix()
r.Job.ExitCode = 255
}
updater.SetJob(c, r)
client.KillContainer(name, "9")
client.RemoveContainer(name, true, true)
}()
// marks the task as running
r.Job.Status = model.StatusRunning
r.Job.Started = time.Now().UTC().Unix()
// encode the build payload to write to stdin
// when launching the build container
in, err := encodeToLegacyFormat(r)
if err != nil {
log.Errorf("failure to marshal work. %s", err)
return err
}
// CREATE AND START BUILD
args := DefaultBuildArgs
if r.Build.Event == model.EventPull {
args = DefaultPullRequestArgs
}
args = append(args, "--")
args = append(args, string(in))
conf := &dockerclient.ContainerConfig{
Image: DefaultAgent,
Entrypoint: DefaultEntrypoint,
Cmd: args,
Env: e.envs,
HostConfig: dockerclient.HostConfig{
Binds: []string{"/var/run/docker.sock:/var/run/docker.sock"},
MemorySwappiness: -1,
},
Volumes: map[string]struct{}{
"/var/run/docker.sock": {},
},
}
log.Infof("preparing container %s", name)
client.PullImage(conf.Image, nil)
_, err = docker.RunDaemon(client, conf, name)
if err != nil {
log.Errorf("error starting build container. %s", err)
return err
}
// UPDATE STATUS
err = updater.SetJob(c, r)
if err != nil {
log.Errorf("error updating job status as running. %s", err)
return err
}
// WAIT FOR OUTPUT
info, builderr := docker.Wait(client, name)
switch {
case info.State.Running:
// A build unblocked before actually being completed.
log.Errorf("incomplete build: %s", name)
r.Job.ExitCode = 1
r.Job.Status = model.StatusError
case info.State.ExitCode == 128:
r.Job.ExitCode = info.State.ExitCode
r.Job.Status = model.StatusKilled
case info.State.ExitCode == 130:
r.Job.ExitCode = info.State.ExitCode
r.Job.Status = model.StatusKilled
case builderr != nil:
r.Job.Status = model.StatusError
case info.State.ExitCode != 0:
r.Job.ExitCode = info.State.ExitCode
r.Job.Status = model.StatusFailure
default:
r.Job.Status = model.StatusSuccess
}
// send the logs to the datastore
var buf bytes.Buffer
rc, err := client.ContainerLogs(name, docker.LogOpts)
if err != nil && builderr != nil {
buf.WriteString("Error launching build")
buf.WriteString(builderr.Error())
} else if err != nil {
buf.WriteString("Error launching build")
buf.WriteString(err.Error())
log.Errorf("error opening connection to logs. %s", err)
return err
} else {
defer rc.Close()
stdcopy.StdCopy(&buf, &buf, io.LimitReader(rc, 5000000))
}
// update the task in the datastore
r.Job.Finished = time.Now().UTC().Unix()
err = updater.SetJob(c, r)
if err != nil {
log.Errorf("error updating job after completion. %s", err)
return err
}
err = updater.SetLogs(c, r, ioutil.NopCloser(&buf))
if err != nil {
log.Errorf("error updating logs. %s", err)
return err
}
log.Debugf("completed job %d with status %s.", r.Job.ID, r.Job.Status)
return nil
}
func (e *engine) runJobNotify(r *Task, client dockerclient.Client) error {
name := fmt.Sprintf("drone_build_%d_notify", r.Build.ID)
defer func() {
client.KillContainer(name, "9")
client.RemoveContainer(name, true, true)
}()
// encode the build payload to write to stdin
// when launching the build container
in, err := encodeToLegacyFormat(r)
if err != nil {
log.Errorf("failure to marshal work. %s", err)
return err
}
args := DefaultNotifyArgs
args = append(args, "--")
args = append(args, string(in))
conf := &dockerclient.ContainerConfig{
Image: DefaultAgent,
Entrypoint: DefaultEntrypoint,
Cmd: args,
Env: e.envs,
HostConfig: dockerclient.HostConfig{
Binds: []string{"/var/run/docker.sock:/var/run/docker.sock"},
MemorySwappiness: -1,
},
Volumes: map[string]struct{}{
"/var/run/docker.sock": {},
},
}
log.Infof("preparing container %s", name)
info, err := docker.Run(client, conf, name)
if err != nil {
log.Errorf("Error starting notification container %s. %s", name, err)
}
// for debugging purposes we print a failed notification executions
// output to the logs. Otherwise we have no way to troubleshoot failed
// notifications. This is temporary code until I've come up with
// a better solution.
if info != nil && info.State.ExitCode != 0 && log.GetLevel() >= log.InfoLevel {
var buf bytes.Buffer
rc, err := client.ContainerLogs(name, docker.LogOpts)
if err == nil {
defer rc.Close()
stdcopy.StdCopy(&buf, &buf, io.LimitReader(rc, 50000))
}
log.Infof("Notification container %s exited with %d", name, info.State.ExitCode)
log.Infoln(buf.String())
}
return err
}

View file

@ -1,86 +0,0 @@
package engine
import (
"sync"
"github.com/drone/drone/model"
)
type pool struct {
sync.Mutex
nodes map[*model.Node]bool
nodec chan *model.Node
}
func newPool() *pool {
return &pool{
nodes: make(map[*model.Node]bool),
nodec: make(chan *model.Node, 999),
}
}
// Allocate allocates a node to the pool to
// be available to accept work.
func (p *pool) allocate(n *model.Node) bool {
if p.isAllocated(n) {
return false
}
p.Lock()
p.nodes[n] = true
p.Unlock()
p.nodec <- n
return true
}
// IsAllocated is a helper function that returns
// true if the node is currently allocated to
// the pool.
func (p *pool) isAllocated(n *model.Node) bool {
p.Lock()
defer p.Unlock()
_, ok := p.nodes[n]
return ok
}
// Deallocate removes the node from the pool of
// available nodes. If the node is currently
// reserved and performing work it will finish,
// but no longer be given new work.
func (p *pool) deallocate(n *model.Node) {
p.Lock()
defer p.Unlock()
delete(p.nodes, n)
}
// List returns a list of all model.Nodes currently
// allocated to the pool.
func (p *pool) list() []*model.Node {
p.Lock()
defer p.Unlock()
var nodes []*model.Node
for n := range p.nodes {
nodes = append(nodes, n)
}
return nodes
}
// Reserve reserves the next available node to
// start doing work. Once work is complete, the
// node should be released back to the pool.
func (p *pool) reserve() <-chan *model.Node {
return p.nodec
}
// Release releases the node back to the pool
// of available nodes.
func (p *pool) release(n *model.Node) bool {
if !p.isAllocated(n) {
return false
}
p.nodec <- n
return true
}

View file

@ -1,89 +0,0 @@
package engine
import (
"testing"
"github.com/drone/drone/model"
"github.com/franela/goblin"
)
func TestPool(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Pool", func() {
g.It("Should allocate nodes", func() {
n := &model.Node{Addr: "unix:///var/run/docker.sock"}
pool := newPool()
pool.allocate(n)
g.Assert(len(pool.nodes)).Equal(1)
g.Assert(len(pool.nodec)).Equal(1)
g.Assert(pool.nodes[n]).Equal(true)
})
g.It("Should not re-allocate an allocated node", func() {
n := &model.Node{Addr: "unix:///var/run/docker.sock"}
pool := newPool()
g.Assert(pool.allocate(n)).Equal(true)
g.Assert(pool.allocate(n)).Equal(false)
})
g.It("Should reserve a node", func() {
n := &model.Node{Addr: "unix:///var/run/docker.sock"}
pool := newPool()
pool.allocate(n)
g.Assert(<-pool.reserve()).Equal(n)
})
g.It("Should release a node", func() {
n := &model.Node{Addr: "unix:///var/run/docker.sock"}
pool := newPool()
pool.allocate(n)
g.Assert(len(pool.nodec)).Equal(1)
g.Assert(<-pool.reserve()).Equal(n)
g.Assert(len(pool.nodec)).Equal(0)
pool.release(n)
g.Assert(len(pool.nodec)).Equal(1)
g.Assert(<-pool.reserve()).Equal(n)
g.Assert(len(pool.nodec)).Equal(0)
})
g.It("Should not release an unallocated node", func() {
n := &model.Node{Addr: "unix:///var/run/docker.sock"}
pool := newPool()
g.Assert(len(pool.nodes)).Equal(0)
g.Assert(len(pool.nodec)).Equal(0)
pool.release(n)
g.Assert(len(pool.nodes)).Equal(0)
g.Assert(len(pool.nodec)).Equal(0)
pool.release(nil)
g.Assert(len(pool.nodes)).Equal(0)
g.Assert(len(pool.nodec)).Equal(0)
})
g.It("Should list all allocated nodes", func() {
n1 := &model.Node{Addr: "unix:///var/run/docker.sock"}
n2 := &model.Node{Addr: "unix:///var/run/docker.sock"}
pool := newPool()
pool.allocate(n1)
pool.allocate(n2)
g.Assert(len(pool.nodes)).Equal(2)
g.Assert(len(pool.nodec)).Equal(2)
g.Assert(len(pool.list())).Equal(2)
})
g.It("Should remove a node", func() {
n1 := &model.Node{Addr: "unix:///var/run/docker.sock"}
n2 := &model.Node{Addr: "unix:///var/run/docker.sock"}
pool := newPool()
pool.allocate(n1)
pool.allocate(n2)
g.Assert(len(pool.nodes)).Equal(2)
pool.deallocate(n1)
pool.deallocate(n2)
g.Assert(len(pool.nodes)).Equal(0)
g.Assert(len(pool.list())).Equal(0)
})
})
}

View file

@ -1,24 +0,0 @@
package engine
import (
"github.com/drone/drone/model"
)
type Event struct {
Name string
Msg []byte
}
type Task struct {
User *model.User `json:"-"`
Repo *model.Repo `json:"repo"`
Build *model.Build `json:"build"`
BuildPrev *model.Build `json:"build_last"`
Jobs []*model.Job `json:"-"`
Job *model.Job `json:"job"`
Keys *model.Key `json:"keys"`
Netrc *model.Netrc `json:"netrc"`
Config string `json:"config"`
Secret string `json:"secret"`
System *model.System `json:"system"`
}

View file

@ -1,66 +0,0 @@
package engine
import (
"encoding/json"
"fmt"
"io"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/store"
"golang.org/x/net/context"
)
type updater struct {
bus *eventbus
}
func (u *updater) SetBuild(c context.Context, r *Task) error {
err := store.UpdateBuild(c, r.Build)
if err != nil {
return err
}
err = remote.FromContext(c).Status(r.User, r.Repo, r.Build, fmt.Sprintf("%s/%s/%d", r.System.Link, r.Repo.FullName, r.Build.Number))
if err != nil {
// log err
}
msg, err := json.Marshal(&payload{r.Build, r.Jobs})
if err != nil {
return err
}
u.bus.send(&Event{
Name: r.Repo.FullName,
Msg: msg,
})
return nil
}
func (u *updater) SetJob(c context.Context, r *Task) error {
err := store.UpdateJob(c, r.Job)
if err != nil {
return err
}
msg, err := json.Marshal(&payload{r.Build, r.Jobs})
if err != nil {
return err
}
u.bus.send(&Event{
Name: r.Repo.FullName,
Msg: msg,
})
return nil
}
func (u *updater) SetLogs(c context.Context, r *Task, rc io.ReadCloser) error {
return store.WriteLog(c, r.Job, rc)
}
type payload struct {
*model.Build
Jobs []*model.Job `json:"jobs"`
}

View file

@ -1,35 +0,0 @@
package engine
import (
"encoding/json"
)
func encodeToLegacyFormat(t *Task) ([]byte, error) {
// t.System.Plugins = append(t.System.Plugins, "plugins/*")
// s := map[string]interface{}{}
// s["repo"] = t.Repo
// s["config"] = t.Config
// s["secret"] = t.Secret
// s["job"] = t.Job
// s["system"] = t.System
// s["workspace"] = map[string]interface{}{
// "netrc": t.Netrc,
// "keys": t.Keys,
// }
// s["build"] = map[string]interface{}{
// "number": t.Build.Number,
// "status": t.Build.Status,
// "head_commit": map[string]interface{}{
// "sha": t.Build.Commit,
// "ref": t.Build.Ref,
// "branch": t.Build.Branch,
// "message": t.Build.Message,
// "author": map[string]interface{}{
// "login": t.Build.Author,
// "email": t.Build.Email,
// },
// },
// }
return json.Marshal(t)
}

View file

@ -1,115 +0,0 @@
package engine
import (
"fmt"
"io"
"github.com/drone/drone/shared/docker"
"github.com/samalba/dockerclient"
)
var (
// name of the build agent container.
DefaultAgent = "drone/drone-exec:latest"
// default name of the build agent executable
DefaultEntrypoint = []string{"/bin/drone-exec"}
// default argument to invoke build steps
DefaultBuildArgs = []string{"--pull", "--cache", "--clone", "--build", "--deploy"}
// default argument to invoke build steps
DefaultPullRequestArgs = []string{"--pull", "--cache", "--clone", "--build"}
// default arguments to invoke notify steps
DefaultNotifyArgs = []string{"--pull", "--notify"}
)
type worker struct {
client dockerclient.Client
build *dockerclient.ContainerInfo
notify *dockerclient.ContainerInfo
}
func newWorker(client dockerclient.Client) *worker {
return &worker{client: client}
}
// Build executes the clone, build and deploy steps.
func (w *worker) Build(name string, stdin []byte, pr bool) (_ int, err error) {
// the command line arguments passed into the
// build agent container.
args := DefaultBuildArgs
if pr {
args = DefaultPullRequestArgs
}
args = append(args, "--")
args = append(args, string(stdin))
conf := &dockerclient.ContainerConfig{
Image: DefaultAgent,
Entrypoint: DefaultEntrypoint,
Cmd: args,
HostConfig: dockerclient.HostConfig{
Binds: []string{"/var/run/docker.sock:/var/run/docker.sock"},
},
Volumes: map[string]struct{}{
"/var/run/docker.sock": {},
},
}
// TEMPORARY: always try to pull the new image for now
// since we'll be frequently updating the build image
// for the next few weeks
w.client.PullImage(conf.Image, nil)
w.build, err = docker.Run(w.client, conf, name)
if err != nil {
return 1, err
}
if w.build.State.OOMKilled {
return 1, fmt.Errorf("OOMKill received")
}
return w.build.State.ExitCode, err
}
// Notify executes the notification steps.
func (w *worker) Notify(stdin []byte) error {
args := DefaultNotifyArgs
args = append(args, "--")
args = append(args, string(stdin))
conf := &dockerclient.ContainerConfig{
Image: DefaultAgent,
Entrypoint: DefaultEntrypoint,
Cmd: args,
HostConfig: dockerclient.HostConfig{},
}
var err error
w.notify, err = docker.Run(w.client, conf, "")
return err
}
// Logs returns a multi-reader that fetches the logs
// from the build and deploy agents.
func (w *worker) Logs() (io.ReadCloser, error) {
if w.build == nil {
return nil, errLogging
}
return w.client.ContainerLogs(w.build.Id, logOpts)
}
// Remove stops and removes the build, deploy and
// notification agents created for the build task.
func (w *worker) Remove() {
if w.notify != nil {
w.client.KillContainer(w.notify.Id, "9")
w.client.RemoveContainer(w.notify.Id, true, true)
}
if w.build != nil {
w.client.KillContainer(w.build.Id, "9")
w.client.RemoveContainer(w.build.Id, true, true)
}
}

26
model/config.go Normal file
View file

@ -0,0 +1,26 @@
package model
// Config defines system configuration parameters.
type Config struct {
Open bool // Enables open registration
Yaml string // Customize the Yaml configuration file name
Shasum string // Customize the Yaml checksum file name
Secret string // Secret token used to authenticate agents
Admins map[string]bool // Administrative users
Orgs map[string]bool // Organization whitelist
}
// IsAdmin returns true if the user is a member of the administrator list.
func (c *Config) IsAdmin(user *User) bool {
return c.Admins[user.Login]
}
// IsMember returns true if the user is a member of the whitelisted teams.
func (c *Config) IsMember(teams []*Team) bool {
for _, team := range teams {
if c.Orgs[team.Login] {
return true
}
}
return false
}

View file

@ -1,36 +0,0 @@
package model
const (
Freebsd_386 uint = iota
Freebsd_amd64
Freebsd_arm
Linux_386
Linux_amd64
Linux_arm
Linux_arm64
Solaris_amd64
Windows_386
Windows_amd64
)
var Archs = map[string]uint{
"freebsd_386": Freebsd_386,
"freebsd_amd64": Freebsd_amd64,
"freebsd_arm": Freebsd_arm,
"linux_386": Linux_386,
"linux_amd64": Linux_amd64,
"linux_arm": Linux_arm,
"linux_arm64": Linux_arm64,
"solaris_amd64": Solaris_amd64,
"windows_386": Windows_386,
"windows_amd64": Windows_amd64,
}
type Node struct {
ID int64 `meddler:"node_id,pk" json:"id"`
Addr string `meddler:"node_addr" json:"address"`
Arch string `meddler:"node_arch" json:"architecture"`
Cert string `meddler:"node_cert" json:"-"`
Key string `meddler:"node_key" json:"-"`
CA string `meddler:"node_ca" json:"-"`
}

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

@ -32,11 +32,17 @@ type User struct {
Avatar string `json:"avatar_url" meddler:"user_avatar"`
// Activate indicates the user is active in the system.
Active bool `json:"active," meddler:"user_active"`
Active bool `json:"active" meddler:"user_active"`
// Admin indicates the user is a system administrator.
Admin bool `json:"admin," meddler:"user_admin"`
//
// NOTE: This is sourced from the DRONE_ADMINS environment variable and is no
// longer persisted in the database.
Admin bool `json:"admin,omitempty" meddler:"-"`
// Hash is a unique token used to sign tokens.
Hash string `json:"-" meddler:"user_hash"`
// DEPRECATED Admin indicates the user is a system administrator.
XAdmin bool `json:"-" meddler:"user_admin"`
}

View file

@ -1,133 +1,71 @@
package bitbucket
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/remote/bitbucket/internal"
"github.com/drone/drone/shared/httputil"
log "github.com/Sirupsen/logrus"
"golang.org/x/oauth2"
"golang.org/x/oauth2/bitbucket"
)
type Bitbucket struct {
// Bitbucket cloud endpoints.
const (
DefaultAPI = "https://api.bitbucket.org"
DefaultURL = "https://bitbucket.org"
)
type config struct {
API string
URL string
Client string
Secret string
Orgs []string
Open bool
}
func Load(config string) *Bitbucket {
// parse the remote DSN configuration string
url_, err := url.Parse(config)
if err != nil {
log.Fatalln("unable to parse remote dsn. %s", err)
// New returns a new remote Configuration for integrating with the Bitbucket
// repository hosting service at https://bitbucket.org
func New(client, secret string) remote.Remote {
return &config{
API: DefaultAPI,
URL: DefaultURL,
Client: client,
Secret: secret,
}
params := url_.Query()
url_.Path = ""
url_.RawQuery = ""
// create the Githbub remote using parameters from
// the parsed DSN configuration string.
bitbucket := Bitbucket{}
bitbucket.Client = params.Get("client_id")
bitbucket.Secret = params.Get("client_secret")
bitbucket.Orgs = params["orgs"]
bitbucket.Open, _ = strconv.ParseBool(params.Get("open"))
return &bitbucket
}
// Login authenticates the session and returns the
// remote user details.
func (bb *Bitbucket) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
// Login authenticates an account with Bitbucket using the oauth2 protocol. The
// Bitbucket account details are returned when the user is successfully authenticated.
func (c *config) Login(w http.ResponseWriter, r *http.Request) (*model.User, error) {
redirect := httputil.GetURL(r)
config := c.newConfig(redirect)
config := &oauth2.Config{
ClientID: bb.Client,
ClientSecret: bb.Secret,
Endpoint: bitbucket.Endpoint,
RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)),
}
// get the OAuth code
var code = req.FormValue("code")
code := r.FormValue("code")
if len(code) == 0 {
http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther)
return nil, false, nil
http.Redirect(w, r, config.AuthCodeURL("drone"), http.StatusSeeOther)
return nil, nil
}
var token, err = config.Exchange(oauth2.NoContext, code)
token, err := config.Exchange(oauth2.NoContext, code)
if err != nil {
return nil, false, fmt.Errorf("Error exchanging token. %s", err)
return nil, err
}
client := NewClient(config.Client(oauth2.NoContext, token))
client := internal.NewClient(c.API, config.Client(oauth2.NoContext, token))
curr, err := client.FindCurrent()
if err != nil {
return nil, false, err
return nil, err
}
// convers the current bitbucket user to the
// common drone user structure.
user := model.User{}
user.Login = curr.Login
user.Token = token.AccessToken
user.Secret = token.RefreshToken
user.Expiry = token.Expiry.UTC().Unix()
user.Avatar = curr.Links.Avatar.Href
// gets the primary, confirmed email from bitbucket
emails, err := client.ListEmail()
if err != nil {
return nil, false, err
}
for _, email := range emails.Values {
if email.IsPrimary && email.IsConfirmed {
user.Email = email.Email
break
}
}
// if the installation is restricted to a subset
// of organizations, get the orgs and verify the
// user is a member.
if len(bb.Orgs) != 0 {
resp, err := client.ListTeams(&ListTeamOpts{Page: 1, PageLen: 100, Role: "member"})
if err != nil {
return nil, false, err
}
var member bool
for _, team := range resp.Values {
for _, team_ := range bb.Orgs {
if team.Login == team_ {
member = true
break
}
}
}
if !member {
return nil, false, fmt.Errorf("User does not belong to correct org. Must belong to %v", bb.Orgs)
}
}
return &user, bb.Open, nil
return convertUser(curr, token), nil
}
// Auth authenticates the session and returns the remote user
// login for the given token and secret
func (bb *Bitbucket) Auth(token, secret string) (string, error) {
token_ := oauth2.Token{AccessToken: token, RefreshToken: secret}
client := NewClientToken(bb.Client, bb.Secret, &token_)
// Auth uses the Bitbucket oauth2 access token and refresh token to authenticate
// a session and return the Bitbucket account login.
func (c *config) Auth(token, secret string) (string, error) {
client := c.newClientToken(token, secret)
user, err := client.FindCurrent()
if err != nil {
return "", err
@ -135,84 +73,83 @@ func (bb *Bitbucket) Auth(token, secret string) (string, error) {
return user.Login, nil
}
// Refresh refreshes an oauth token and expiration for the given
// user. It returns true if the token was refreshed, false if the
// token was not refreshed, and error if it failed to refersh.
func (bb *Bitbucket) Refresh(user *model.User) (bool, error) {
config := &oauth2.Config{
ClientID: bb.Client,
ClientSecret: bb.Secret,
Endpoint: bitbucket.Endpoint,
}
// creates a token source with just the refresh token.
// this will ensure an access token is automatically
// requested.
// Refresh refreshes the Bitbucket oauth2 access token. If the token is
// refreshed the user is updated and a true value is returned.
func (c *config) Refresh(user *model.User) (bool, error) {
config := c.newConfig("")
source := config.TokenSource(
oauth2.NoContext, &oauth2.Token{RefreshToken: user.Secret})
// requesting the token automatically refreshes and
// returns a new access token.
token, err := source.Token()
if err != nil || len(token.AccessToken) == 0 {
return false, err
}
// update the user to include tne new access token
user.Token = token.AccessToken
user.Secret = token.RefreshToken
user.Expiry = token.Expiry.UTC().Unix()
return true, nil
}
// Repo fetches the named repository from the remote system.
func (bb *Bitbucket) Repo(u *model.User, owner, name string) (*model.Repo, error) {
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
client := NewClientToken(bb.Client, bb.Secret, &token)
// Teams returns a list of all team membership for the Bitbucket account.
func (c *config) Teams(u *model.User) ([]*model.Team, error) {
opts := &internal.ListTeamOpts{
PageLen: 100,
Role: "member",
}
resp, err := c.newClient(u).ListTeams(opts)
if err != nil {
return nil, err
}
return convertTeamList(resp.Values), nil
}
repo, err := client.FindRepo(owner, name)
// Repo returns the named Bitbucket repository.
func (c *config) Repo(u *model.User, owner, name string) (*model.Repo, error) {
repo, err := c.newClient(u).FindRepo(owner, name)
if err != nil {
return nil, err
}
return convertRepo(repo), nil
}
// Repos fetches a list of repos from the remote system.
func (bb *Bitbucket) Repos(u *model.User) ([]*model.RepoLite, error) {
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
client := NewClientToken(bb.Client, bb.Secret, &token)
var repos []*model.RepoLite
// Repos returns a list of all repositories for Bitbucket account, including
// organization repositories.
func (c *config) Repos(u *model.User) ([]*model.RepoLite, error) {
client := c.newClient(u)
// gets a list of all accounts to query, including the
// user's account and all team accounts.
logins := []string{u.Login}
resp, err := client.ListTeams(&ListTeamOpts{PageLen: 100, Role: "member"})
var all []*model.RepoLite
accounts := []string{u.Login}
resp, err := client.ListTeams(&internal.ListTeamOpts{
PageLen: 100,
Role: "member",
})
if err != nil {
return repos, err
return all, err
}
for _, team := range resp.Values {
logins = append(logins, team.Login)
accounts = append(accounts, team.Login)
}
// for each account, get the list of repos
for _, login := range logins {
repos_, err := client.ListReposAll(login)
for _, account := range accounts {
repos, err := client.ListReposAll(account)
if err != nil {
return repos, err
return all, err
}
for _, repo := range repos_ {
repos = append(repos, convertRepoLite(repo))
for _, repo := range repos {
all = append(all, convertRepoLite(repo))
}
}
return repos, nil
return all, nil
}
// Perm fetches the named repository permissions from
// the remote system for the specified user.
func (bb *Bitbucket) Perm(u *model.User, owner, name string) (*model.Perm, error) {
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
client := NewClientToken(bb.Client, bb.Secret, &token)
// Perm returns the user permissions for the named repository. Because Bitbucket
// does not have an endpoint to access user permissions, we attempt to fetch
// the repository hook list, which is restricted to administrators to calculate
// administrative access to a repository.
func (c *config) Perm(u *model.User, owner, name string) (*model.Perm, error) {
client := c.newClient(u)
perms := new(model.Perm)
_, err := client.FindRepo(owner, name)
@ -220,69 +157,72 @@ func (bb *Bitbucket) Perm(u *model.User, owner, name string) (*model.Perm, error
return perms, err
}
// if we've gotten this far we know that the user at
// least has read access to the repository.
perms.Pull = true
// if the user has access to the repository hooks we
// can deduce that the user has push and admin access.
_, err = client.ListHooks(owner, name, &ListOpts{})
_, err = client.ListHooks(owner, name, &internal.ListOpts{})
if err == nil {
perms.Push = true
perms.Admin = true
}
perms.Pull = true
return perms, nil
}
// File fetches a file from the remote repository and returns in string format.
func (bb *Bitbucket) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
client := NewClientToken(
bb.Client,
bb.Secret,
&oauth2.Token{
AccessToken: u.Token,
RefreshToken: u.Secret,
},
)
config, err := client.FindSource(r.Owner, r.Name, b.Commit, f)
// File fetches the file from the Bitbucket repository and returns its contents.
func (c *config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
config, err := c.newClient(u).FindSource(r.Owner, r.Name, b.Commit, f)
if err != nil {
return nil, err
}
return []byte(config.Data), err
}
// Status sends the commit status to the remote system.
// An example would be the GitHub pull request status.
func (bb *Bitbucket) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
client := NewClientToken(
bb.Client,
bb.Secret,
&oauth2.Token{
AccessToken: u.Token,
RefreshToken: u.Secret,
},
)
status := getStatus(b.Status)
desc := getDesc(b.Status)
data := BuildStatus{
State: status,
// Status creates a build status for the Bitbucket commit.
func (c *config) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
status := internal.BuildStatus{
State: convertStatus(b.Status),
Desc: convertDesc(b.Status),
Key: "Drone",
Url: link,
Desc: desc,
}
err := client.CreateStatus(r.Owner, r.Name, b.Commit, &data)
return err
return c.newClient(u).CreateStatus(r.Owner, r.Name, b.Commit, &status)
}
// Netrc returns a .netrc file that can be used to clone
// private repositories from a remote system.
func (bb *Bitbucket) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
// Activate activates the repository by registering repository push hooks with
// the Bitbucket repository. Prior to registering hook, previously created hooks
// are deleted.
func (c *config) Activate(u *model.User, r *model.Repo, link string) error {
rawurl, err := url.Parse(link)
if err != nil {
return err
}
c.Deactivate(u, r, link)
return c.newClient(u).CreateHook(r.Owner, r.Name, &internal.Hook{
Active: true,
Desc: rawurl.Host,
Events: []string{"repo:push"},
Url: link,
})
}
// Deactivate deactives the repository be removing repository push hooks from
// the Bitbucket repository.
func (c *config) Deactivate(u *model.User, r *model.Repo, link string) error {
client := c.newClient(u)
hooks, err := client.ListHooks(r.Owner, r.Name, &internal.ListOpts{})
if err != nil {
return err
}
hook := matchingHooks(hooks.Values, link)
if hook != nil {
return client.DeleteHook(r.Owner, r.Name, hook.Uuid)
}
return nil
}
// Netrc returns a netrc file capable of authenticating Bitbucket requests and
// cloning Bitbucket repositories.
func (c *config) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
return &model.Netrc{
Machine: "bitbucket.org",
Login: "x-token-auth",
@ -290,226 +230,54 @@ func (bb *Bitbucket) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
}, nil
}
// Activate activates a repository by creating the post-commit hook and
// adding the SSH deploy key, if applicable.
func (bb *Bitbucket) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
client := NewClientToken(
bb.Client,
bb.Secret,
&oauth2.Token{
AccessToken: u.Token,
RefreshToken: u.Secret,
},
)
linkurl, err := url.Parse(link)
if err != nil {
log.Errorf("malformed hook url %s. %s", link, err)
return err
}
// see if the hook already exists. If yes be sure to
// delete so that multiple messages aren't sent.
hooks, _ := client.ListHooks(r.Owner, r.Name, &ListOpts{})
for _, hook := range hooks.Values {
hookurl, err := url.Parse(hook.Url)
if err != nil {
continue
}
if hookurl.Host == linkurl.Host {
err = client.DeleteHook(r.Owner, r.Name, hook.Uuid)
if err != nil {
log.Errorf("unable to delete hook %s. %s", hookurl.Host, err)
}
break
}
}
err = client.CreateHook(r.Owner, r.Name, &Hook{
Active: true,
Desc: linkurl.Host,
Events: []string{"repo:push"},
Url: link,
})
if err != nil {
log.Errorf("unable to create hook %s. %s", link, err)
}
return err
// Hook parses the incoming Bitbucket hook and returns the Repository and
// Build details. If the hook is unsupported nil values are returned.
func (c *config) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
return parseHook(r)
}
// 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,
// helper function to return the bitbucket oauth2 client
func (c *config) newClient(u *model.User) *internal.Client {
return c.newClientToken(u.Token, u.Secret)
}
// helper function to return the bitbucket oauth2 client
func (c *config) newClientToken(token, secret string) *internal.Client {
return internal.NewClientToken(
c.API,
c.Client,
c.Secret,
&oauth2.Token{
AccessToken: u.Token,
RefreshToken: u.Secret,
AccessToken: token,
RefreshToken: secret,
},
)
}
linkurl, err := url.Parse(link)
// helper function to return the bitbucket oauth2 config
func (c *config) newConfig(redirect string) *oauth2.Config {
return &oauth2.Config{
ClientID: c.Client,
ClientSecret: c.Secret,
Endpoint: oauth2.Endpoint{
AuthURL: fmt.Sprintf("%s/site/oauth2/authorize", c.URL),
TokenURL: fmt.Sprintf("%s/site/oauth2/access_token", c.URL),
},
RedirectURL: fmt.Sprintf("%s/authorize", redirect),
}
}
// helper function to return matching hooks.
func matchingHooks(hooks []*internal.Hook, rawurl string) *internal.Hook {
link, err := url.Parse(rawurl)
if err != nil {
return err
return nil
}
// 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 {
for _, hook := range hooks {
hookurl, err := url.Parse(hook.Url)
if err != nil {
return err
}
if hookurl.Host == linkurl.Host {
client.DeleteHook(r.Owner, r.Name, hook.Uuid)
break
if err == nil && hookurl.Host == link.Host {
return hook
}
}
return nil
}
// Hook parses the post-commit hook from the Request body
// and returns the required data in a standard format.
func (bb *Bitbucket) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
switch r.Header.Get("X-Event-Key") {
case "repo:push":
return bb.pushHook(r)
case "pullrequest:created", "pullrequest:updated":
return bb.pullHook(r)
}
return nil, nil, nil
}
func (bb *Bitbucket) String() string {
return "bitbucket"
}
func (bb *Bitbucket) pushHook(r *http.Request) (*model.Repo, *model.Build, error) {
payload := []byte(r.FormValue("payload"))
if len(payload) == 0 {
defer r.Body.Close()
payload, _ = ioutil.ReadAll(r.Body)
}
hook := PushHook{}
err := json.Unmarshal(payload, &hook)
if err != nil {
return nil, nil, err
}
// the hook can container one or many changes. Since I don't
// fully understand this yet, we will just pick the first
// change that has branch information.
for _, change := range hook.Push.Changes {
// must have sha information
if change.New.Target.Hash == "" {
continue
}
// we only support tag and branch pushes for now
buildEventType := model.EventPush
buildRef := fmt.Sprintf("refs/heads/%s", change.New.Name)
if change.New.Type == "tag" || change.New.Type == "annotated_tag" || change.New.Type == "bookmark" {
buildEventType = model.EventTag
buildRef = fmt.Sprintf("refs/tags/%s", change.New.Name)
} else if change.New.Type != "branch" && change.New.Type != "named_branch" {
continue
}
// return the updated repository information and the
// build information.
return convertRepo(&hook.Repo), &model.Build{
Event: buildEventType,
Commit: change.New.Target.Hash,
Ref: buildRef,
Link: change.New.Target.Links.Html.Href,
Branch: change.New.Name,
Message: change.New.Target.Message,
Avatar: hook.Actor.Links.Avatar.Href,
Author: hook.Actor.Login,
Timestamp: change.New.Target.Date.UTC().Unix(),
}, nil
}
return nil, nil, nil
}
func (bb *Bitbucket) pullHook(r *http.Request) (*model.Repo, *model.Build, error) {
payload := []byte(r.FormValue("payload"))
if len(payload) == 0 {
defer r.Body.Close()
payload, _ = ioutil.ReadAll(r.Body)
}
hook := PullRequestHook{}
err := json.Unmarshal(payload, &hook)
if err != nil {
return nil, nil, err
}
if hook.PullRequest.State != "OPEN" {
return nil, nil, nil
}
return convertRepo(&hook.Repo), &model.Build{
Event: model.EventPull,
Commit: hook.PullRequest.Dest.Commit.Hash,
Ref: fmt.Sprintf("refs/heads/%s", hook.PullRequest.Dest.Branch.Name),
Refspec: fmt.Sprintf("https://bitbucket.org/%s.git", hook.PullRequest.Source.Repo.FullName),
Remote: cloneLink(hook.PullRequest.Dest.Repo),
Link: hook.PullRequest.Links.Html.Href,
Branch: hook.PullRequest.Dest.Branch.Name,
Message: hook.PullRequest.Desc,
Avatar: hook.Actor.Links.Avatar.Href,
Author: hook.Actor.Login,
Timestamp: hook.PullRequest.Updated.UTC().Unix(),
}, nil
}
const (
StatusPending = "INPROGRESS"
StatusSuccess = "SUCCESSFUL"
StatusFailure = "FAILED"
)
const (
DescPending = "this build is pending"
DescSuccess = "the build was successful"
DescFailure = "the build failed"
DescError = "oops, something went wrong"
)
// converts a Drone status to a BitBucket status.
func getStatus(status string) string {
switch status {
case model.StatusPending, model.StatusRunning:
return StatusPending
case model.StatusSuccess:
return StatusSuccess
case model.StatusFailure, model.StatusError, model.StatusKilled:
return StatusFailure
default:
return StatusFailure
}
}
// generates a description for the build based on
// the Drone status
func getDesc(status string) string {
switch status {
case model.StatusPending, model.StatusRunning:
return DescPending
case model.StatusSuccess:
return DescSuccess
case model.StatusFailure:
return DescFailure
case model.StatusError, model.StatusKilled:
return DescError
default:
return DescError
}
}

View file

@ -0,0 +1,337 @@
package bitbucket
import (
"bytes"
"net/http"
"net/http/httptest"
"testing"
"github.com/drone/drone/model"
"github.com/drone/drone/remote/bitbucket/fixtures"
"github.com/drone/drone/remote/bitbucket/internal"
"github.com/franela/goblin"
"github.com/gin-gonic/gin"
)
func Test_bitbucket(t *testing.T) {
gin.SetMode(gin.TestMode)
s := httptest.NewServer(fixtures.Handler())
c := &config{URL: s.URL, API: s.URL}
g := goblin.Goblin(t)
g.Describe("Bitbucket client", func() {
g.After(func() {
s.Close()
})
g.It("Should return client with default endpoint", func() {
remote := New("4vyW6b49Z", "a5012f6c6")
g.Assert(remote.(*config).URL).Equal(DefaultURL)
g.Assert(remote.(*config).API).Equal(DefaultAPI)
g.Assert(remote.(*config).Client).Equal("4vyW6b49Z")
g.Assert(remote.(*config).Secret).Equal("a5012f6c6")
})
g.It("Should return the netrc file", func() {
remote := New("", "")
netrc, _ := remote.Netrc(fakeUser, nil)
g.Assert(netrc.Machine).Equal("bitbucket.org")
g.Assert(netrc.Login).Equal("x-token-auth")
g.Assert(netrc.Password).Equal(fakeUser.Token)
})
g.Describe("Given an authorization request", func() {
g.It("Should redirect to authorize", func() {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "", nil)
_, err := c.Login(w, r)
g.Assert(err == nil).IsTrue()
g.Assert(w.Code).Equal(http.StatusSeeOther)
})
g.It("Should return authenticated user", func() {
r, _ := http.NewRequest("GET", "?code=code", nil)
u, err := c.Login(nil, r)
g.Assert(err == nil).IsTrue()
g.Assert(u.Login).Equal(fakeUser.Login)
g.Assert(u.Token).Equal("2YotnFZFEjr1zCsicMWpAA")
g.Assert(u.Secret).Equal("tGzv3JOkF0XG5Qx2TlKWIA")
})
g.It("Should handle failure to exchange code", func() {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "?code=code_bad_request", nil)
_, err := c.Login(w, r)
g.Assert(err != nil).IsTrue()
})
g.It("Should handle failure to resolve user", func() {
r, _ := http.NewRequest("GET", "?code=code_user_not_found", nil)
_, err := c.Login(nil, r)
g.Assert(err != nil).IsTrue()
})
})
g.Describe("Given an access token", func() {
g.It("Should return the authenticated user", func() {
login, err := c.Auth(
fakeUser.Token,
fakeUser.Secret,
)
g.Assert(err == nil).IsTrue()
g.Assert(login).Equal(fakeUser.Login)
})
g.It("Should handle a failure to resolve user", func() {
_, err := c.Auth(
fakeUserNotFound.Token,
fakeUserNotFound.Secret,
)
g.Assert(err != nil).IsTrue()
})
})
g.Describe("Given a refresh token", func() {
g.It("Should return a refresh access token", func() {
ok, err := c.Refresh(fakeUserRefresh)
g.Assert(err == nil).IsTrue()
g.Assert(ok).IsTrue()
g.Assert(fakeUserRefresh.Token).Equal("2YotnFZFEjr1zCsicMWpAA")
g.Assert(fakeUserRefresh.Secret).Equal("tGzv3JOkF0XG5Qx2TlKWIA")
})
g.It("Should handle an empty access token", func() {
ok, err := c.Refresh(fakeUserRefreshEmpty)
g.Assert(err == nil).IsTrue()
g.Assert(ok).IsFalse()
})
g.It("Should handle a failure to refresh", func() {
ok, err := c.Refresh(fakeUserRefreshFail)
g.Assert(err != nil).IsTrue()
g.Assert(ok).IsFalse()
})
})
g.Describe("When requesting a repository", func() {
g.It("Should return the details", func() {
repo, err := c.Repo(
fakeUser,
fakeRepo.Owner,
fakeRepo.Name,
)
g.Assert(err == nil).IsTrue()
g.Assert(repo.FullName).Equal(fakeRepo.FullName)
})
g.It("Should handle not found errors", func() {
_, err := c.Repo(
fakeUser,
fakeRepoNotFound.Owner,
fakeRepoNotFound.Name,
)
g.Assert(err != nil).IsTrue()
})
})
g.Describe("When requesting repository permissions", func() {
g.It("Should handle not found errors", func() {
_, err := c.Perm(
fakeUser,
fakeRepoNotFound.Owner,
fakeRepoNotFound.Name,
)
g.Assert(err != nil).IsTrue()
})
g.It("Should authorize read access", func() {
perm, err := c.Perm(
fakeUser,
fakeRepoNoHooks.Owner,
fakeRepoNoHooks.Name,
)
g.Assert(err == nil).IsTrue()
g.Assert(perm.Pull).IsTrue()
g.Assert(perm.Push).IsFalse()
g.Assert(perm.Admin).IsFalse()
})
g.It("Should authorize admin access", func() {
perm, err := c.Perm(
fakeUser,
fakeRepo.Owner,
fakeRepo.Name,
)
g.Assert(err == nil).IsTrue()
g.Assert(perm.Pull).IsTrue()
g.Assert(perm.Push).IsTrue()
g.Assert(perm.Admin).IsTrue()
})
})
g.Describe("When requesting user repositories", func() {
g.It("Should return the details", func() {
repos, err := c.Repos(fakeUser)
g.Assert(err == nil).IsTrue()
g.Assert(repos[0].FullName).Equal(fakeRepo.FullName)
})
g.It("Should handle organization not found errors", func() {
_, err := c.Repos(fakeUserNoTeams)
g.Assert(err != nil).IsTrue()
})
g.It("Should handle not found errors", func() {
_, err := c.Repos(fakeUserNoRepos)
g.Assert(err != nil).IsTrue()
})
})
g.Describe("When requesting user teams", func() {
g.It("Should return the details", func() {
teams, err := c.Teams(fakeUser)
g.Assert(err == nil).IsTrue()
g.Assert(teams[0].Login).Equal("superfriends")
g.Assert(teams[0].Avatar).Equal("http://i.imgur.com/ZygP55A.jpg")
})
g.It("Should handle not found error", func() {
_, err := c.Teams(fakeUserNoTeams)
g.Assert(err != nil).IsTrue()
})
})
g.Describe("When downloading a file", func() {
g.It("Should return the bytes", func() {
raw, err := c.File(fakeUser, fakeRepo, fakeBuild, "file")
g.Assert(err == nil).IsTrue()
g.Assert(len(raw) != 0).IsTrue()
})
g.It("Should handle not found error", func() {
_, err := c.File(fakeUser, fakeRepo, fakeBuild, "file_not_found")
g.Assert(err != nil).IsTrue()
})
})
g.Describe("When activating a repository", func() {
g.It("Should error when malformed hook", func() {
err := c.Activate(fakeUser, fakeRepo, "%gh&%ij")
g.Assert(err != nil).IsTrue()
})
g.It("Should create the hook", func() {
err := c.Activate(fakeUser, fakeRepo, "http://127.0.0.1")
g.Assert(err == nil).IsTrue()
})
})
g.Describe("When deactivating a repository", func() {
g.It("Should error when listing hooks fails", func() {
err := c.Deactivate(fakeUser, fakeRepoNoHooks, "http://127.0.0.1")
g.Assert(err != nil).IsTrue()
})
g.It("Should successfully remove hooks", func() {
err := c.Deactivate(fakeUser, fakeRepo, "http://127.0.0.1")
g.Assert(err == nil).IsTrue()
})
g.It("Should successfully deactivate when hook already removed", func() {
err := c.Deactivate(fakeUser, fakeRepoEmptyHook, "http://127.0.0.1")
g.Assert(err == nil).IsTrue()
})
})
g.Describe("Given a list of hooks", func() {
g.It("Should return the matching hook", func() {
hooks := []*internal.Hook{
{Url: "http://127.0.0.1/hook"},
}
hook := matchingHooks(hooks, "http://127.0.0.1/")
g.Assert(hook).Equal(hooks[0])
})
g.It("Should handle no matches", func() {
hooks := []*internal.Hook{
{Url: "http://localhost/hook"},
}
hook := matchingHooks(hooks, "http://127.0.0.1/")
g.Assert(hook == nil).IsTrue()
})
g.It("Should handle malformed hook urls", func() {
var hooks []*internal.Hook
hook := matchingHooks(hooks, "%gh&%ij")
g.Assert(hook == nil).IsTrue()
})
})
g.It("Should update the status", func() {
err := c.Status(fakeUser, fakeRepo, fakeBuild, "http://127.0.0.1")
g.Assert(err == nil).IsTrue()
})
g.It("Should parse the hook", func() {
buf := bytes.NewBufferString(fixtures.HookPush)
req, _ := http.NewRequest("POST", "/hook", buf)
req.Header = http.Header{}
req.Header.Set(hookEvent, hookPush)
r, _, err := c.Hook(req)
g.Assert(err == nil).IsTrue()
g.Assert(r.FullName).Equal("user_name/repo_name")
})
})
}
var (
fakeUser = &model.User{
Login: "superman",
Token: "cfcd2084",
}
fakeUserRefresh = &model.User{
Login: "superman",
Secret: "cfcd2084",
}
fakeUserRefreshFail = &model.User{
Login: "superman",
Secret: "refresh_token_not_found",
}
fakeUserRefreshEmpty = &model.User{
Login: "superman",
Secret: "refresh_token_is_empty",
}
fakeUserNotFound = &model.User{
Login: "superman",
Token: "user_not_found",
}
fakeUserNoTeams = &model.User{
Login: "superman",
Token: "teams_not_found",
}
fakeUserNoRepos = &model.User{
Login: "superman",
Token: "repos_not_found",
}
fakeRepo = &model.Repo{
Owner: "test_name",
Name: "repo_name",
FullName: "test_name/repo_name",
}
fakeRepoNotFound = &model.Repo{
Owner: "test_name",
Name: "repo_not_found",
FullName: "test_name/repo_not_found",
}
fakeRepoNoHooks = &model.Repo{
Owner: "test_name",
Name: "hooks_not_found",
FullName: "test_name/hooks_not_found",
}
fakeRepoEmptyHook = &model.Repo{
Owner: "test_name",
Name: "hook_empty",
FullName: "test_name/hook_empty",
}
fakeBuild = &model.Build{
Commit: "9ecad50",
}
)

186
remote/bitbucket/convert.go Normal file
View file

@ -0,0 +1,186 @@
package bitbucket
import (
"fmt"
"net/url"
"strings"
"github.com/drone/drone/model"
"github.com/drone/drone/remote/bitbucket/internal"
"golang.org/x/oauth2"
)
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"
)
// convertStatus is a helper function used to convert a Drone status to a
// Bitbucket commit status.
func convertStatus(status string) string {
switch status {
case model.StatusPending, model.StatusRunning:
return statusPending
case model.StatusSuccess:
return statusSuccess
default:
return statusFailure
}
}
// convertDesc is a helper function used to convert a Drone status to a
// Bitbucket status description.
func convertDesc(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
}
}
// convertRepo is a helper function used to convert a Bitbucket repository
// structure to the common Drone repository structure.
func convertRepo(from *internal.Repo) *model.Repo {
repo := model.Repo{
Clone: cloneLink(from),
Owner: strings.Split(from.FullName, "/")[0],
Name: strings.Split(from.FullName, "/")[1],
FullName: from.FullName,
Link: from.Links.Html.Href,
IsPrivate: from.IsPrivate,
Avatar: from.Owner.Links.Avatar.Href,
Kind: from.Scm,
Branch: "master",
}
if repo.Kind == model.RepoHg {
repo.Branch = "default"
}
return &repo
}
// cloneLink is a helper function that tries to extract the clone url from the
// repository object.
func cloneLink(repo *internal.Repo) string {
var clone string
// above we manually constructed the repository clone url. below we will
// iterate through the list of clone links and attempt to instead use the
// clone url provided by bitbucket.
for _, link := range repo.Links.Clone {
if link.Name == "https" {
clone = link.Href
}
}
// if no repository name is provided, we use the Html link. this excludes the
// .git suffix, but will still clone the repo.
if len(clone) == 0 {
clone = repo.Links.Html.Href
}
// if bitbucket tries to automatically populate the user in the url we must
// strip it out.
cloneurl, err := url.Parse(clone)
if err == nil {
cloneurl.User = nil
clone = cloneurl.String()
}
return clone
}
// convertRepoLite is a helper function used to convert a Bitbucket repository
// structure to the simplified Drone repository structure.
func convertRepoLite(from *internal.Repo) *model.RepoLite {
return &model.RepoLite{
Owner: strings.Split(from.FullName, "/")[0],
Name: strings.Split(from.FullName, "/")[1],
FullName: from.FullName,
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,
}
}
// convertPullHook is a helper function used to convert a Bitbucket pull request
// hook to the Drone build struct holding commit information.
func convertPullHook(from *internal.PullRequestHook) *model.Build {
return &model.Build{
Event: model.EventPull,
Commit: from.PullRequest.Dest.Commit.Hash,
Ref: fmt.Sprintf("refs/heads/%s", from.PullRequest.Dest.Branch.Name),
Remote: cloneLink(&from.PullRequest.Dest.Repo),
Link: from.PullRequest.Links.Html.Href,
Branch: from.PullRequest.Dest.Branch.Name,
Message: from.PullRequest.Desc,
Avatar: from.Actor.Links.Avatar.Href,
Author: from.Actor.Login,
Timestamp: from.PullRequest.Updated.UTC().Unix(),
}
}
// convertPushHook is a helper function used to convert a Bitbucket push
// hook to the Drone build struct holding commit information.
func convertPushHook(hook *internal.PushHook, change *internal.Change) *model.Build {
build := &model.Build{
Commit: change.New.Target.Hash,
Link: change.New.Target.Links.Html.Href,
Branch: change.New.Name,
Message: change.New.Target.Message,
Avatar: hook.Actor.Links.Avatar.Href,
Author: hook.Actor.Login,
Timestamp: change.New.Target.Date.UTC().Unix(),
}
switch change.New.Type {
case "tag", "annotated_tag", "bookmark":
build.Event = model.EventTag
build.Ref = fmt.Sprintf("refs/tags/%s", change.New.Name)
default:
build.Event = model.EventPush
build.Ref = fmt.Sprintf("refs/heads/%s", change.New.Name)
}
return build
}

View file

@ -0,0 +1,195 @@
package bitbucket
import (
"testing"
"time"
"github.com/drone/drone/model"
"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 converter", func() {
g.It("should convert passing status", func() {
g.Assert(convertStatus(model.StatusSuccess)).Equal(statusSuccess)
})
g.It("should convert pending status", func() {
g.Assert(convertStatus(model.StatusPending)).Equal(statusPending)
g.Assert(convertStatus(model.StatusRunning)).Equal(statusPending)
})
g.It("should convert failing status", func() {
g.Assert(convertStatus(model.StatusFailure)).Equal(statusFailure)
g.Assert(convertStatus(model.StatusKilled)).Equal(statusFailure)
g.Assert(convertStatus(model.StatusError)).Equal(statusFailure)
})
g.It("should convert passing desc", func() {
g.Assert(convertDesc(model.StatusSuccess)).Equal(descSuccess)
})
g.It("should convert pending desc", func() {
g.Assert(convertDesc(model.StatusPending)).Equal(descPending)
g.Assert(convertDesc(model.StatusRunning)).Equal(descPending)
})
g.It("should convert failing desc", func() {
g.Assert(convertDesc(model.StatusFailure)).Equal(descFailure)
})
g.It("should convert error desc", func() {
g.Assert(convertDesc(model.StatusKilled)).Equal(descError)
g.Assert(convertDesc(model.StatusError)).Equal(descError)
})
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")
})
g.It("should convert pull hook to build", func() {
hook := &internal.PullRequestHook{}
hook.Actor.Login = "octocat"
hook.Actor.Links.Avatar.Href = "https://..."
hook.PullRequest.Dest.Commit.Hash = "73f9c44d"
hook.PullRequest.Dest.Branch.Name = "master"
hook.PullRequest.Dest.Repo.Links.Html.Href = "https://bitbucket.org/foo/bar"
hook.PullRequest.Links.Html.Href = "https://bitbucket.org/foo/bar/pulls/5"
hook.PullRequest.Desc = "updated README"
hook.PullRequest.Updated = time.Now()
build := convertPullHook(hook)
g.Assert(build.Event).Equal(model.EventPull)
g.Assert(build.Author).Equal(hook.Actor.Login)
g.Assert(build.Avatar).Equal(hook.Actor.Links.Avatar.Href)
g.Assert(build.Commit).Equal(hook.PullRequest.Dest.Commit.Hash)
g.Assert(build.Branch).Equal(hook.PullRequest.Dest.Branch.Name)
g.Assert(build.Link).Equal(hook.PullRequest.Links.Html.Href)
g.Assert(build.Ref).Equal("refs/heads/master")
g.Assert(build.Message).Equal(hook.PullRequest.Desc)
g.Assert(build.Timestamp).Equal(hook.PullRequest.Updated.Unix())
})
g.It("should convert push hook to build", func() {
change := internal.Change{}
change.New.Target.Hash = "73f9c44d"
change.New.Name = "master"
change.New.Target.Links.Html.Href = "https://bitbucket.org/foo/bar/commits/73f9c44d"
change.New.Target.Message = "updated README"
change.New.Target.Date = time.Now()
hook := internal.PushHook{}
hook.Actor.Login = "octocat"
hook.Actor.Links.Avatar.Href = "https://..."
build := convertPushHook(&hook, &change)
g.Assert(build.Event).Equal(model.EventPush)
g.Assert(build.Author).Equal(hook.Actor.Login)
g.Assert(build.Avatar).Equal(hook.Actor.Links.Avatar.Href)
g.Assert(build.Commit).Equal(change.New.Target.Hash)
g.Assert(build.Branch).Equal(change.New.Name)
g.Assert(build.Link).Equal(change.New.Target.Links.Html.Href)
g.Assert(build.Ref).Equal("refs/heads/master")
g.Assert(build.Message).Equal(change.New.Target.Message)
g.Assert(build.Timestamp).Equal(change.New.Target.Date.Unix())
})
g.It("should convert tag hook to build", func() {
change := internal.Change{}
change.New.Name = "v1.0.0"
change.New.Type = "tag"
hook := internal.PushHook{}
build := convertPushHook(&hook, &change)
g.Assert(build.Event).Equal(model.EventTag)
g.Assert(build.Ref).Equal("refs/tags/v1.0.0")
})
})
}

View file

@ -0,0 +1,221 @@
package fixtures
import (
"net/http"
"github.com/gin-gonic/gin"
)
// Handler returns an http.Handler that is capable of handling a variety of mock
// Bitbucket requests and returning mock responses.
func Handler() http.Handler {
gin.SetMode(gin.TestMode)
e := gin.New()
e.POST("/site/oauth2/access_token", getOauth)
e.GET("/2.0/repositories/:owner/:name", getRepo)
e.GET("/2.0/repositories/:owner/:name/hooks", getRepoHooks)
e.GET("/1.0/repositories/:owner/:name/src/:commit/:file", getRepoFile)
e.DELETE("/2.0/repositories/:owner/:name/hooks/:hook", deleteRepoHook)
e.POST("/2.0/repositories/:owner/:name/hooks", createRepoHook)
e.POST("/2.0/repositories/:owner/:name/commit/:commit/statuses/build", createRepoStatus)
e.GET("/2.0/repositories/:owner", getUserRepos)
e.GET("/2.0/teams/", getUserTeams)
e.GET("/2.0/user/", getUser)
return e
}
func getOauth(c *gin.Context) {
switch c.PostForm("code") {
case "code_bad_request":
c.String(500, "")
return
case "code_user_not_found":
c.String(200, tokenNotFoundPayload)
return
}
switch c.PostForm("refresh_token") {
case "refresh_token_not_found":
c.String(404, "")
case "refresh_token_is_empty":
c.Header("Content-Type", "application/json")
c.String(200, "{}")
default:
c.Header("Content-Type", "application/json")
c.String(200, tokenPayload)
}
}
func getRepo(c *gin.Context) {
switch c.Param("name") {
case "not_found", "repo_unknown", "repo_not_found":
c.String(404, "")
default:
c.String(200, repoPayload)
}
}
func getRepoHooks(c *gin.Context) {
switch c.Param("name") {
case "hooks_not_found", "repo_no_hooks":
c.String(404, "")
case "hook_empty":
c.String(200, "{}")
default:
c.String(200, repoHookPayload)
}
}
func getRepoFile(c *gin.Context) {
switch c.Param("file") {
case "file_not_found":
c.String(404, "")
default:
c.String(200, repoFilePayload)
}
}
func createRepoStatus(c *gin.Context) {
switch c.Param("name") {
case "repo_not_found":
c.String(404, "")
default:
c.String(200, "")
}
}
func createRepoHook(c *gin.Context) {
c.String(200, "")
}
func deleteRepoHook(c *gin.Context) {
switch c.Param("name") {
case "hook_not_found":
c.String(404, "")
default:
c.String(200, "")
}
}
func getUser(c *gin.Context) {
switch c.Request.Header.Get("Authorization") {
case "Bearer user_not_found", "Bearer a87ff679":
c.String(404, "")
default:
c.String(200, userPayload)
}
}
func getUserTeams(c *gin.Context) {
switch c.Request.Header.Get("Authorization") {
case "Bearer teams_not_found", "Bearer c81e728d":
c.String(404, "")
default:
c.String(200, userTeamPayload)
}
}
func getUserRepos(c *gin.Context) {
switch c.Request.Header.Get("Authorization") {
case "Bearer repos_not_found", "Bearer 70efdf2e":
c.String(404, "")
default:
c.String(200, userRepoPayload)
}
}
const tokenPayload = `
{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"token_type":"Bearer",
"expires_in":3600
}
`
const tokenNotFoundPayload = `
{
"access_token":"user_not_found",
"refresh_token":"user_not_found",
"token_type":"Bearer",
"expires_in":3600
}
`
const repoPayload = `
{
"full_name": "test_name/repo_name",
"scm": "git",
"is_private": true
}
`
const repoHookPayload = `
{
"pagelen": 10,
"values": [
{
"uuid": "{afe61e14-2c5f-49e8-8b68-ad1fb55fc052}",
"url": "http://127.0.0.1"
}
],
"page": 1,
"size": 1
}
`
const repoFilePayload = `
{
"data": "{ platform: linux/amd64 }"
}
`
const userPayload = `
{
"username": "superman",
"links": {
"avatar": {
"href": "http:\/\/i.imgur.com\/ZygP55A.jpg"
}
},
"type": "user"
}
`
const userRepoPayload = `
{
"page": 1,
"pagelen": 10,
"size": 1,
"values": [
{
"links": {
"avatar": {
"href": "http:\/\/i.imgur.com\/ZygP55A.jpg"
}
},
"full_name": "test_name/repo_name",
"scm": "git",
"is_private": true
}
]
}
`
const userTeamPayload = `
{
"pagelen": 100,
"values": [
{
"username": "superfriends",
"links": {
"avatar": {
"href": "http:\/\/i.imgur.com\/ZygP55A.jpg"
}
},
"type": "team"
}
]
}
`

View file

@ -0,0 +1,164 @@
package fixtures
const HookPush = `
{
"actor": {
"username": "emmap1",
"links": {
"avatar": {
"href": "https:\/\/bitbucket-api-assetroot.s3.amazonaws.com\/c\/photos\/2015\/Feb\/26\/3613917261-0-emmap1-avatar_avatar.png"
}
}
},
"repository": {
"links": {
"html": {
"href": "https:\/\/api.bitbucket.org\/team_name\/repo_name"
},
"avatar": {
"href": "https:\/\/api-staging-assetroot.s3.amazonaws.com\/c\/photos\/2014\/Aug\/01\/bitbucket-logo-2629490769-3_avatar.png"
}
},
"full_name": "user_name\/repo_name",
"scm": "git",
"is_private": true
},
"push": {
"changes": [
{
"new": {
"type": "branch",
"name": "name-of-branch",
"target": {
"type": "commit",
"hash": "709d658dc5b6d6afcd46049c2f332ee3f515a67d",
"author": {
"username": "emmap1",
"links": {
"avatar": {
"href": "https:\/\/bitbucket-api-assetroot.s3.amazonaws.com\/c\/photos\/2015\/Feb\/26\/3613917261-0-emmap1-avatar_avatar.png"
}
}
},
"message": "new commit message\n",
"date": "2015-06-09T03:34:49+00:00"
}
}
}
]
}
}
`
const HookPushEmptyHash = `
{
"push": {
"changes": [
{
"new": {
"type": "branch",
"target": { "hash": "" }
}
}
]
}
}
`
const HookPull = `
{
"actor": {
"username": "emmap1",
"links": {
"avatar": {
"href": "https:\/\/bitbucket-api-assetroot.s3.amazonaws.com\/c\/photos\/2015\/Feb\/26\/3613917261-0-emmap1-avatar_avatar.png"
}
}
},
"pullrequest": {
"id": 1,
"title": "Title of pull request",
"description": "Description of pull request",
"state": "OPEN",
"author": {
"username": "emmap1",
"links": {
"avatar": {
"href": "https:\/\/bitbucket-api-assetroot.s3.amazonaws.com\/c\/photos\/2015\/Feb\/26\/3613917261-0-emmap1-avatar_avatar.png"
}
}
},
"source": {
"branch": {
"name": "branch2"
},
"commit": {
"hash": "d3022fc0ca3d"
},
"repository": {
"links": {
"html": {
"href": "https:\/\/api.bitbucket.org\/team_name\/repo_name"
},
"avatar": {
"href": "https:\/\/api-staging-assetroot.s3.amazonaws.com\/c\/photos\/2014\/Aug\/01\/bitbucket-logo-2629490769-3_avatar.png"
}
},
"full_name": "user_name\/repo_name",
"scm": "git",
"is_private": true
}
},
"destination": {
"branch": {
"name": "master"
},
"commit": {
"hash": "ce5965ddd289"
},
"repository": {
"links": {
"html": {
"href": "https:\/\/api.bitbucket.org\/team_name\/repo_name"
},
"avatar": {
"href": "https:\/\/api-staging-assetroot.s3.amazonaws.com\/c\/photos\/2014\/Aug\/01\/bitbucket-logo-2629490769-3_avatar.png"
}
},
"full_name": "user_name\/repo_name",
"scm": "git",
"is_private": true
}
},
"links": {
"self": {
"href": "https:\/\/api.bitbucket.org\/api\/2.0\/pullrequests\/pullrequest_id"
},
"html": {
"href": "https:\/\/api.bitbucket.org\/pullrequest_id"
}
}
},
"repository": {
"links": {
"html": {
"href": "https:\/\/api.bitbucket.org\/team_name\/repo_name"
},
"avatar": {
"href": "https:\/\/api-staging-assetroot.s3.amazonaws.com\/c\/photos\/2014\/Aug\/01\/bitbucket-logo-2629490769-3_avatar.png"
}
},
"full_name": "user_name\/repo_name",
"scm": "git",
"is_private": true
}
}
`
const HookMerged = `
{
"pullrequest": {
"state": "MERGED"
}
}
`

View file

@ -1,101 +0,0 @@
package bitbucket
import (
"net/url"
"strings"
"github.com/drone/drone/model"
)
// convertRepo is a helper function used to convert a Bitbucket
// repository structure to the common Drone repository structure.
func convertRepo(from *Repo) *model.Repo {
repo := model.Repo{
Owner: strings.Split(from.FullName, "/")[0],
Name: strings.Split(from.FullName, "/")[1],
FullName: from.FullName,
Link: from.Links.Html.Href,
IsPrivate: from.IsPrivate,
Avatar: from.Owner.Links.Avatar.Href,
Kind: from.Scm,
Branch: "master",
}
if repo.Kind == model.RepoHg {
repo.Branch = "default"
}
// in some cases, the owner of the repository is not
// provided, however, we do have the full name.
if len(repo.Owner) == 0 {
repo.Owner = strings.Split(repo.FullName, "/")[0]
}
// above we manually constructed the repository clone url.
// below we will iterate through the list of clone links and
// attempt to instead use the clone url provided by bitbucket.
for _, link := range from.Links.Clone {
if link.Name == "https" {
repo.Clone = link.Href
break
}
}
// if no repository name is provided, we use the Html link.
// this excludes the .git suffix, but will still clone the repo.
if len(repo.Clone) == 0 {
repo.Clone = repo.Link
}
// if bitbucket tries to automatically populate the user
// in the url we must strip it out.
clone, err := url.Parse(repo.Clone)
if err == nil {
clone.User = nil
repo.Clone = clone.String()
}
return &repo
}
// cloneLink is a helper function that tries to extract the
// clone url from the repository object.
func cloneLink(repo Repo) string {
var clone string
// above we manually constructed the repository clone url.
// below we will iterate through the list of clone links and
// attempt to instead use the clone url provided by bitbucket.
for _, link := range repo.Links.Clone {
if link.Name == "https" {
clone = link.Href
}
}
// if no repository name is provided, we use the Html link.
// this excludes the .git suffix, but will still clone the repo.
if len(clone) == 0 {
clone = repo.Links.Html.Href
}
// if bitbucket tries to automatically populate the user
// in the url we must strip it out.
cloneurl, err := url.Parse(clone)
if err == nil {
cloneurl.User = nil
clone = cloneurl.String()
}
return clone
}
// convertRepoLite is a helper function used to convert a Bitbucket
// repository structure to the simplified Drone repository structure.
func convertRepoLite(from *Repo) *model.RepoLite {
return &model.RepoLite{
Owner: strings.Split(from.FullName, "/")[0],
Name: strings.Split(from.FullName, "/")[1],
FullName: from.FullName,
Avatar: from.Owner.Links.Avatar.Href,
}
}

View file

@ -1,4 +1,4 @@
package bitbucket
package internal
import (
"bytes"
@ -20,8 +20,6 @@ const (
)
const (
base = "https://api.bitbucket.org"
pathUser = "%s/2.0/user/"
pathEmails = "%s/2.0/user/emails"
pathTeams = "%s/2.0/teams/?%s"
@ -35,52 +33,53 @@ const (
type Client struct {
*http.Client
base string
}
func NewClient(client *http.Client) *Client {
return &Client{client}
func NewClient(url string, client *http.Client) *Client {
return &Client{client, url}
}
func NewClientToken(client, secret string, token *oauth2.Token) *Client {
func NewClientToken(url, client, secret string, token *oauth2.Token) *Client {
config := &oauth2.Config{
ClientID: client,
ClientSecret: secret,
Endpoint: bitbucket.Endpoint,
}
return NewClient(config.Client(oauth2.NoContext, token))
return NewClient(url, config.Client(oauth2.NoContext, token))
}
func (c *Client) FindCurrent() (*Account, error) {
out := new(Account)
uri := fmt.Sprintf(pathUser, base)
uri := fmt.Sprintf(pathUser, c.base)
err := c.do(uri, get, nil, out)
return out, err
}
func (c *Client) ListEmail() (*EmailResp, error) {
out := new(EmailResp)
uri := fmt.Sprintf(pathEmails, base)
uri := fmt.Sprintf(pathEmails, c.base)
err := c.do(uri, get, nil, out)
return out, err
}
func (c *Client) ListTeams(opts *ListTeamOpts) (*AccountResp, error) {
out := new(AccountResp)
uri := fmt.Sprintf(pathTeams, base, opts.Encode())
uri := fmt.Sprintf(pathTeams, c.base, opts.Encode())
err := c.do(uri, get, nil, out)
return out, err
}
func (c *Client) FindRepo(owner, name string) (*Repo, error) {
out := new(Repo)
uri := fmt.Sprintf(pathRepo, base, owner, name)
uri := fmt.Sprintf(pathRepo, c.base, owner, name)
err := c.do(uri, get, nil, out)
return out, err
}
func (c *Client) ListRepos(account string, opts *ListOpts) (*RepoResp, error) {
out := new(RepoResp)
uri := fmt.Sprintf(pathRepos, base, account, opts.Encode())
uri := fmt.Sprintf(pathRepos, c.base, account, opts.Encode())
err := c.do(uri, get, nil, out)
return out, err
}
@ -105,37 +104,37 @@ func (c *Client) ListReposAll(account string) ([]*Repo, error) {
func (c *Client) FindHook(owner, name, id string) (*Hook, error) {
out := new(Hook)
uri := fmt.Sprintf(pathHook, base, owner, name, id)
uri := fmt.Sprintf(pathHook, c.base, owner, name, id)
err := c.do(uri, get, nil, out)
return out, err
}
func (c *Client) ListHooks(owner, name string, opts *ListOpts) (*HookResp, error) {
out := new(HookResp)
uri := fmt.Sprintf(pathHooks, base, owner, name, opts.Encode())
uri := fmt.Sprintf(pathHooks, c.base, owner, name, opts.Encode())
err := c.do(uri, get, nil, out)
return out, err
}
func (c *Client) CreateHook(owner, name string, hook *Hook) error {
uri := fmt.Sprintf(pathHooks, base, owner, name, "")
uri := fmt.Sprintf(pathHooks, c.base, owner, name, "")
return c.do(uri, post, hook, nil)
}
func (c *Client) DeleteHook(owner, name, id string) error {
uri := fmt.Sprintf(pathHook, base, owner, name, id)
uri := fmt.Sprintf(pathHook, c.base, owner, name, id)
return c.do(uri, del, nil, nil)
}
func (c *Client) FindSource(owner, name, revision, path string) (*Source, error) {
out := new(Source)
uri := fmt.Sprintf(pathSource, base, owner, name, revision, path)
uri := fmt.Sprintf(pathSource, c.base, owner, name, revision, path)
err := c.do(uri, get, nil, out)
return out, err
}
func (c *Client) CreateStatus(owner, name, revision string, status *BuildStatus) error {
uri := fmt.Sprintf(pathStatus, base, owner, name, revision)
uri := fmt.Sprintf(pathStatus, c.base, owner, name, revision)
return c.do(uri, post, status, nil)
}

View file

@ -1,4 +1,4 @@
package bitbucket
package internal
import (
"net/url"
@ -100,27 +100,29 @@ type Source struct {
Size int64 `json:"size"`
}
type Change struct {
New struct {
Type string `json:"type"`
Name string `json:"name"`
Target struct {
Type string `json:"type"`
Hash string `json:"hash"`
Message string `json:"message"`
Date time.Time `json:"date"`
Links Links `json:"links"`
Author struct {
Raw string `json:"raw"`
User Account `json:"user"`
} `json:"author"`
} `json:"target"`
} `json:"new"`
}
type PushHook struct {
Actor Account `json:"actor"`
Repo Repo `json:"repository"`
Push struct {
Changes []struct {
New struct {
Type string `json:"type"`
Name string `json:"name"`
Target struct {
Type string `json:"type"`
Hash string `json:"hash"`
Message string `json:"message"`
Date time.Time `json:"date"`
Links Links `json:"links"`
Author struct {
Raw string `json:"raw"`
User Account `json:"user"`
} `json:"author"`
} `json:"target"`
} `json:"new"`
} `json:"changes"`
Changes []Change `json:"changes"`
} `json:"push"`
}

71
remote/bitbucket/parse.go Normal file
View file

@ -0,0 +1,71 @@
package bitbucket
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/drone/drone/model"
"github.com/drone/drone/remote/bitbucket/internal"
)
const (
hookEvent = "X-Event-Key"
hookPush = "repo:push"
hookPullCreated = "pullrequest:created"
hookPullUpdated = "pullrequest:updated"
changeBranch = "branch"
changeNamedBranch = "named_branch"
stateMerged = "MERGED"
stateDeclined = "DECLINED"
stateOpen = "OPEN"
)
// parseHook parses a Bitbucket hook from an http.Request request and returns
// Repo and Build detail. If a hook type is unsupported nil values are returned.
func parseHook(r *http.Request) (*model.Repo, *model.Build, error) {
payload, _ := ioutil.ReadAll(r.Body)
switch r.Header.Get(hookEvent) {
case hookPush:
return parsePushHook(payload)
case hookPullCreated, hookPullUpdated:
return parsePullHook(payload)
}
return nil, nil, nil
}
// parsePushHook parses a push hook and returns the Repo and Build details.
// If the commit type is unsupported nil values are returned.
func parsePushHook(payload []byte) (*model.Repo, *model.Build, error) {
hook := internal.PushHook{}
err := json.Unmarshal(payload, &hook)
if err != nil {
return nil, nil, err
}
for _, change := range hook.Push.Changes {
if change.New.Target.Hash == "" {
continue
}
return convertRepo(&hook.Repo), convertPushHook(&hook, &change), nil
}
return nil, nil, nil
}
// parsePullHook parses a pull request hook and returns the Repo and Build
// details. If the pull request is closed nil values are returned.
func parsePullHook(payload []byte) (*model.Repo, *model.Build, error) {
hook := internal.PullRequestHook{}
if err := json.Unmarshal(payload, &hook); err != nil {
return nil, nil, err
}
if hook.PullRequest.State != stateOpen {
return nil, nil, nil
}
return convertRepo(&hook.Repo), convertPullHook(&hook), nil
}

View file

@ -0,0 +1,104 @@
package bitbucket
import (
"bytes"
"net/http"
"testing"
"github.com/drone/drone/remote/bitbucket/fixtures"
"github.com/franela/goblin"
)
func Test_parser(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Bitbucket parser", func() {
g.It("Should ignore unsupported hook", func() {
buf := bytes.NewBufferString(fixtures.HookPush)
req, _ := http.NewRequest("POST", "/hook", buf)
req.Header = http.Header{}
req.Header.Set(hookEvent, "issue:created")
r, b, err := parseHook(req)
g.Assert(r == nil).IsTrue()
g.Assert(b == nil).IsTrue()
g.Assert(err == nil).IsTrue()
})
g.Describe("Given a pull request hook payload", func() {
g.It("Should return err when malformed", func() {
buf := bytes.NewBufferString("[]")
req, _ := http.NewRequest("POST", "/hook", buf)
req.Header = http.Header{}
req.Header.Set(hookEvent, hookPullCreated)
_, _, err := parseHook(req)
g.Assert(err != nil).IsTrue()
})
g.It("Should return nil if not open", func() {
buf := bytes.NewBufferString(fixtures.HookMerged)
req, _ := http.NewRequest("POST", "/hook", buf)
req.Header = http.Header{}
req.Header.Set(hookEvent, hookPullCreated)
r, b, err := parseHook(req)
g.Assert(r == nil).IsTrue()
g.Assert(b == nil).IsTrue()
g.Assert(err == nil).IsTrue()
})
g.It("Should return pull request details", func() {
buf := bytes.NewBufferString(fixtures.HookPull)
req, _ := http.NewRequest("POST", "/hook", buf)
req.Header = http.Header{}
req.Header.Set(hookEvent, hookPullCreated)
r, b, err := parseHook(req)
g.Assert(err == nil).IsTrue()
g.Assert(r.FullName).Equal("user_name/repo_name")
g.Assert(b.Commit).Equal("ce5965ddd289")
})
})
g.Describe("Given a push hook payload", func() {
g.It("Should return err when malformed", func() {
buf := bytes.NewBufferString("[]")
req, _ := http.NewRequest("POST", "/hook", buf)
req.Header = http.Header{}
req.Header.Set(hookEvent, hookPush)
_, _, err := parseHook(req)
g.Assert(err != nil).IsTrue()
})
g.It("Should return nil if missing commit sha", func() {
buf := bytes.NewBufferString(fixtures.HookPushEmptyHash)
req, _ := http.NewRequest("POST", "/hook", buf)
req.Header = http.Header{}
req.Header.Set(hookEvent, hookPush)
r, b, err := parseHook(req)
g.Assert(r == nil).IsTrue()
g.Assert(b == nil).IsTrue()
g.Assert(err == nil).IsTrue()
})
g.It("Should return push details", func() {
buf := bytes.NewBufferString(fixtures.HookPush)
req, _ := http.NewRequest("POST", "/hook", buf)
req.Header = http.Header{}
req.Header.Set(hookEvent, hookPush)
r, b, err := parseHook(req)
g.Assert(err == nil).IsTrue()
g.Assert(r.FullName).Equal("user_name/repo_name")
g.Assert(b.Commit).Equal("709d658dc5b6d6afcd46049c2f332ee3f515a67d")
})
})
})
}

View file

@ -1,141 +1,150 @@
package bitbucketserver
// Requires the following to be set
// REMOTE_DRIVER=bitbucketserver
// REMOTE_CONFIG=https://{servername}?consumer_key={key added on the stash server for oath1}&git_username={username for clone}&git_password={password for clone}&consumer_rsa=/path/to/pem.file&open={not used yet}
// Configure application links in the bitbucket server --
// application url needs to be the base url to drone
// incoming auth needs to have the consumer key (same as the key in REMOTE_CONFIG)
// set the public key (public key from the private key added to /var/lib/bitbucketserver/private_key.pem name matters)
// consumer call back is the base url to drone plus /authorize/
// Needs a pem private key added to /var/lib/bitbucketserver/private_key.pem
// After that you should be good to go
// WARNING! This is an work-in-progress patch and does not yet conform to the coding,
// quality or security standards expected of this project. Please use with caution.
import (
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/drone/drone/model"
"github.com/mrjones/oauth"
"io/ioutil"
"net/http"
"net/url"
"strconv"
log "github.com/Sirupsen/logrus"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/mrjones/oauth"
)
type BitbucketServer struct {
// Opts defines configuration options.
type Opts struct {
URL string // Stash server url.
Username string // Git machine account username.
Password string // Git machine account password.
ConsumerKey string // Oauth1 consumer key.
ConsumerRSA string // Oauth1 consumer key file.
SkipVerify bool // Skip ssl verification.
}
// New returns a Remote implementation that integrates with Bitbucket Server,
// the on-premise edition of Bitbucket Cloud, formerly known as Stash.
func New(opts Opts) (remote.Remote, error) {
bb := &client{
URL: opts.URL,
ConsumerKey: opts.ConsumerKey,
ConsumerRSA: opts.ConsumerRSA,
GitUserName: opts.Username,
GitPassword: opts.Password,
}
switch {
case bb.GitUserName == "":
return nil, fmt.Errorf("Must have a git machine account username")
case bb.GitPassword == "":
return nil, fmt.Errorf("Must have a git machine account password")
case bb.ConsumerKey == "":
return nil, fmt.Errorf("Must have a oauth1 consumer key")
case bb.ConsumerRSA == "":
return nil, fmt.Errorf("Must have a oauth1 consumer key file")
}
keyfile, err := ioutil.ReadFile(bb.ConsumerRSA)
if err != nil {
return nil, err
}
block, _ := pem.Decode(keyfile)
bb.PrivateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
// TODO de-referencing is a bit weird and may not behave as expected, and could
// have race conditions. Instead store the parsed key (I already did this above)
// and then pass the parsed private key when creating the Bitbucket client.
bb.Consumer = *NewClient(bb.ConsumerRSA, bb.ConsumerKey, bb.URL)
return bb, nil
}
type client struct {
URL string
ConsumerKey string
GitUserName string
GitPassword string
ConsumerRSA string
Open bool
PrivateKey *rsa.PrivateKey
Consumer oauth.Consumer
}
func Load(config string) *BitbucketServer {
url_, err := url.Parse(config)
func (c *client) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
requestToken, url, err := c.Consumer.GetRequestTokenAndUrl("oob")
if err != nil {
log.Fatalln("unable to parse remote dsn. %s", err)
}
params := url_.Query()
url_.Path = ""
url_.RawQuery = ""
bitbucketserver := BitbucketServer{}
bitbucketserver.URL = url_.String()
bitbucketserver.GitUserName = params.Get("git_username")
if bitbucketserver.GitUserName == "" {
log.Fatalln("Must have a git_username")
}
bitbucketserver.GitPassword = params.Get("git_password")
if bitbucketserver.GitPassword == "" {
log.Fatalln("Must have a git_password")
}
bitbucketserver.ConsumerKey = params.Get("consumer_key")
if bitbucketserver.ConsumerKey == "" {
log.Fatalln("Must have a consumer_key")
}
bitbucketserver.ConsumerRSA = params.Get("consumer_rsa")
if bitbucketserver.ConsumerRSA == "" {
log.Fatalln("Must have a consumer_rsa")
}
bitbucketserver.Open, _ = strconv.ParseBool(params.Get("open"))
bitbucketserver.Consumer = *NewClient(bitbucketserver.ConsumerRSA, bitbucketserver.ConsumerKey, bitbucketserver.URL)
return &bitbucketserver
}
func (bs *BitbucketServer) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
log.Info("Starting to login for bitbucketServer")
log.Info("getting the requestToken")
requestToken, url, err := bs.Consumer.GetRequestTokenAndUrl("oob")
if err != nil {
log.Error(err)
return nil, err
}
var code = req.FormValue("oauth_verifier")
if len(code) == 0 {
log.Info("redirecting to %s", url)
http.Redirect(res, req, url, http.StatusSeeOther)
return nil, false, nil
return nil, nil
}
var request_oauth_token = req.FormValue("oauth_token")
requestToken.Token = request_oauth_token
accessToken, err := bs.Consumer.AuthorizeToken(requestToken, code)
requestToken.Token = req.FormValue("oauth_token")
accessToken, err := c.Consumer.AuthorizeToken(requestToken, code)
if err != nil {
log.Error(err)
return nil, err
}
client, err := bs.Consumer.MakeHttpClient(accessToken)
client, err := c.Consumer.MakeHttpClient(accessToken)
if err != nil {
log.Error(err)
return nil, err
}
response, err := client.Get(fmt.Sprintf("%s/plugins/servlet/applinks/whoami", bs.URL))
response, err := client.Get(fmt.Sprintf("%s/plugins/servlet/applinks/whoami", c.URL))
if err != nil {
log.Error(err)
return nil, err
}
defer response.Body.Close()
bits, err := ioutil.ReadAll(response.Body)
userName := string(bits)
if err != nil {
return nil, err
}
login := string(bits)
response1, err := client.Get(fmt.Sprintf("%s/rest/api/1.0/users/%s",bs.URL, userName))
// TODO errors should never be ignored like this
response1, err := client.Get(fmt.Sprintf("%s/rest/api/1.0/users/%s", c.URL, login))
contents, err := ioutil.ReadAll(response1.Body)
defer response1.Body.Close()
var mUser User
json.Unmarshal(contents, &mUser)
var mUser User // TODO prefixing with m* is not a common convention in Go
json.Unmarshal(contents, &mUser) // TODO should not ignore error
user := model.User{}
user.Login = userName
user.Email = mUser.EmailAddress
user.Token = accessToken.Token
user.Avatar = avatarLink(mUser.EmailAddress)
return &user, bs.Open, nil
return &model.User{
Login: login,
Email: mUser.EmailAddress,
Token: accessToken.Token,
Avatar: avatarLink(mUser.EmailAddress),
}, nil
}
func (bs *BitbucketServer) Auth(token, secret string) (string, error) {
log.Info("Staring to auth for bitbucketServer. %s", token)
if len(token) == 0 {
return "", fmt.Errorf("Hasn't logged in yet")
}
return token, nil
// Auth is not supported by the Stash driver.
func (*client) Auth(token, secret string) (string, error) {
return "", fmt.Errorf("Not Implemented")
}
func (bs *BitbucketServer) Repo(u *model.User, owner, name string) (*model.Repo, error) {
log.Info("Staring repo for bitbucketServer with user " + u.Login + " " + owner + " " + name)
// Teams is not supported by the Stash driver.
func (*client) Teams(u *model.User) ([]*model.Team, error) {
var teams []*model.Team
return teams, nil
}
client := NewClientWithToken(&bs.Consumer, u.Token)
func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := NewClientWithToken(&c.Consumer, u.Token)
url := fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s", c.URL, owner, name)
url := fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s",bs.URL,owner,name)
log.Info("Trying to get " + url)
response, err := client.Get(url)
if err != nil {
log.Error(err)
@ -145,40 +154,36 @@ func (bs *BitbucketServer) Repo(u *model.User, owner, name string) (*model.Repo,
bsRepo := BSRepo{}
json.Unmarshal(contents, &bsRepo)
cloneLink := ""
repoLink := ""
repo := &model.Repo{
Name: bsRepo.Slug,
Owner: bsRepo.Project.Key,
Branch: "master",
Kind: model.RepoGit,
IsPrivate: !bsRepo.Project.Public, // TODO(josmo) verify this is corrrect
FullName: fmt.Sprintf("%s/%s", bsRepo.Project.Key, bsRepo.Slug),
}
for _, item := range bsRepo.Links.Clone {
if item.Name == "http" {
cloneLink = item.Href
repo.Clone = item.Href
}
}
for _, item := range bsRepo.Links.Self {
if item.Href != "" {
repoLink = item.Href
repo.Link = item.Href
}
}
//TODO: get the real allow tag+ infomration
repo := &model.Repo{}
repo.Clone = cloneLink
repo.Link = repoLink
repo.Name = bsRepo.Slug
repo.Owner = bsRepo.Project.Key
repo.AllowPush = true
repo.FullName = fmt.Sprintf("%s/%s",bsRepo.Project.Key,bsRepo.Slug)
repo.Branch = "master"
repo.Kind = model.RepoGit
return repo, nil
}
func (bs *BitbucketServer) Repos(u *model.User) ([]*model.RepoLite, error) {
log.Info("Staring repos for bitbucketServer " + u.Login)
func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) {
var repos = []*model.RepoLite{}
client := NewClientWithToken(&bs.Consumer, u.Token)
client := NewClientWithToken(&c.Consumer, u.Token)
response, err := client.Get(fmt.Sprintf("%s/rest/api/1.0/repos?limit=10000",bs.URL))
response, err := client.Get(fmt.Sprintf("%s/rest/api/1.0/repos?limit=10000", c.URL))
if err != nil {
log.Error(err)
}
@ -198,10 +203,8 @@ func (bs *BitbucketServer) Repos(u *model.User) ([]*model.RepoLite, error) {
return repos, nil
}
func (bs *BitbucketServer) Perm(u *model.User, owner, repo string) (*model.Perm, error) {
//TODO: find the real permissions
log.Info("Staring perm for bitbucketServer")
func (c *client) Perm(u *model.User, owner, repo string) (*model.Perm, error) {
// TODO need to fetch real permissions here
perms := new(model.Perm)
perms.Pull = true
perms.Admin = true
@ -209,11 +212,11 @@ func (bs *BitbucketServer) Perm(u *model.User, owner, repo string) (*model.Perm,
return perms, nil
}
func (bs *BitbucketServer) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
log.Info(fmt.Sprintf("Staring file for bitbucketServer login: %s repo: %s buildevent: %s string: %s", u.Login, r.Name, b.Event, f))
client := NewClientWithToken(&bs.Consumer, u.Token)
fileURL := fmt.Sprintf("%s/projects/%s/repos/%s/browse/%s?raw", bs.URL, r.Owner, r.Name, f)
client := NewClientWithToken(&c.Consumer, u.Token)
fileURL := fmt.Sprintf("%s/projects/%s/repos/%s/browse/%s?raw", c.URL, r.Owner, r.Name, f)
log.Info(fileURL)
response, err := client.Get(fileURL)
if err != nil {
@ -231,28 +234,26 @@ func (bs *BitbucketServer) File(u *model.User, r *model.Repo, b *model.Build, f
return responseBytes, nil
}
func (bs *BitbucketServer) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
log.Info("Staring status for bitbucketServer")
// Status is not supported by the Gogs driver.
func (*client) Status(*model.User, *model.Repo, *model.Build, string) error {
return nil
}
func (bs *BitbucketServer) Netrc(user *model.User, r *model.Repo) (*model.Netrc, error) {
log.Info("Starting the Netrc lookup")
u, err := url.Parse(bs.URL)
func (c *client) Netrc(user *model.User, r *model.Repo) (*model.Netrc, error) {
u, err := url.Parse(c.URL) // TODO strip port from url
if err != nil {
return nil, err
}
return &model.Netrc{
Machine: u.Host,
Login: bs.GitUserName,
Password: bs.GitPassword,
Login: c.GitUserName,
Password: c.GitPassword,
}, nil
}
func (bs *BitbucketServer) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
log.Info(fmt.Sprintf("Staring activate for bitbucketServer user: %s repo: %s key: %s link: %s", u.Login, r.Name, k, link))
client := NewClientWithToken(&bs.Consumer, u.Token)
hook, err := bs.CreateHook(client, r.Owner, r.Name, "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook", link)
func (c *client) Activate(u *model.User, r *model.Repo, link string) error {
client := NewClientWithToken(&c.Consumer, u.Token)
hook, err := c.CreateHook(client, r.Owner, r.Name, "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook", link)
if err != nil {
return err
}
@ -260,68 +261,56 @@ func (bs *BitbucketServer) Activate(u *model.User, r *model.Repo, k *model.Key,
return nil
}
func (bs *BitbucketServer) Deactivate(u *model.User, r *model.Repo, link string) error {
log.Info(fmt.Sprintf("Staring deactivating for bitbucketServer user: %s repo: %s link: %s", u.Login, r.Name, link))
client := NewClientWithToken(&bs.Consumer, u.Token)
err := bs.DeleteHook(client, r.Owner, r.Name, "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook", link)
func (c *client) Deactivate(u *model.User, r *model.Repo, link string) error {
client := NewClientWithToken(&c.Consumer, u.Token)
err := c.DeleteHook(client, r.Owner, r.Name, "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook", link)
if err != nil {
return err
}
return nil
}
func (bs *BitbucketServer) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
log.Info("Staring hook for bitbucketServer")
defer r.Body.Close()
contents, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Info(err)
func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
hook := new(postHook)
if err := json.NewDecoder(r.Body).Decode(hook); err != nil {
return nil, nil, err
}
var hookPost postHook
json.Unmarshal(contents, &hookPost)
build := &model.Build{
Event: model.EventPush,
Ref: hook.RefChanges[0].RefID, // TODO check for index Values
Author: hook.Changesets.Values[0].ToCommit.Author.EmailAddress, // TODO check for index Values
Commit: hook.RefChanges[0].ToHash, // TODO check for index value
Avatar: avatarLink(hook.Changesets.Values[0].ToCommit.Author.EmailAddress),
}
buildModel := &model.Build{}
buildModel.Event = model.EventPush
buildModel.Ref = hookPost.RefChanges[0].RefID
buildModel.Author = hookPost.Changesets.Values[0].ToCommit.Author.EmailAddress
buildModel.Commit = hookPost.RefChanges[0].ToHash
buildModel.Avatar = avatarLink(hookPost.Changesets.Values[0].ToCommit.Author.EmailAddress)
repo := &model.Repo{
Name: hook.Repository.Slug,
Owner: hook.Repository.Project.Key,
FullName: fmt.Sprintf("%s/%s", hook.Repository.Project.Key, hook.Repository.Slug),
Branch: "master",
Kind: model.RepoGit,
}
//All you really need is the name and owner. That's what creates the lookup key, so it needs to match the repo info. Just an FYI
repo := &model.Repo{}
repo.Name = hookPost.Repository.Slug
repo.Owner = hookPost.Repository.Project.Key
repo.AllowTag = false
repo.AllowDeploy = false
repo.AllowPull = false
repo.AllowPush = true
repo.FullName = fmt.Sprintf("%s/%s",hookPost.Repository.Project.Key,hookPost.Repository.Slug)
repo.Branch = "master"
repo.Kind = model.RepoGit
return repo, buildModel, nil
}
func (bs *BitbucketServer) String() string {
return "bitbucketserver"
return repo, build, nil
}
type HookDetail struct {
Key string `"json:key"`
Name string `"json:name"`
Type string `"json:type"`
Description string `"json:description"`
Version string `"json:version"`
ConfigFormKey string `"json:configFormKey"`
Key string `json:"key"`
Name string `json:"name"`
Type string `json:"type"`
Description string `json:"description"`
Version string `json:"version"`
ConfigFormKey string `json:"configFormKey"`
}
type Hook struct {
Enabled bool `"json:enabled"`
Details *HookDetail `"json:details"`
Enabled bool `json:"enabled"`
Details *HookDetail `json:"details"`
}
// Enable hook for named repository
func (bs *BitbucketServer) CreateHook(client *http.Client, project, slug, hook_key, link string) (*Hook, error) {
func (bs *client) CreateHook(client *http.Client, project, slug, hook_key, link string) (*Hook, error) {
// Set hook
hookBytes := []byte(fmt.Sprintf(`{"hook-url-0":"%s"}`, link))
@ -336,7 +325,7 @@ func (bs *BitbucketServer) CreateHook(client *http.Client, project, slug, hook_k
}
// Disable hook for named repository
func (bs *BitbucketServer) DeleteHook(client *http.Client, project, slug, hook_key, link string) error {
func (bs *client) DeleteHook(client *http.Client, project, slug, hook_key, link string) error {
enablePath := fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s/enabled",
project, slug, hook_key)
doDelete(client, bs.URL+enablePath)

View file

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

View file

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

View file

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

View file

@ -4,30 +4,63 @@ import (
"crypto/tls"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/shared/httputil"
"github.com/drone/drone/shared/oauth2"
"github.com/drone/drone/shared/token"
"github.com/drone/drone/remote/gitlab/client"
)
const (
DefaultScope = "api"
)
const DefaultScope = "api"
// Opts defines configuration options.
type Opts struct {
URL string // Gogs server url.
Client string // Oauth2 client id.
Secret string // Oauth2 client secret.
Username string // Optional machine account username.
Password string // Optional machine account password.
PrivateMode bool // Gogs is running in private mode.
SkipVerify bool // Skip ssl verification.
}
// New returns a Remote implementation that integrates with Gitlab, an open
// source Git service. See https://gitlab.com
func New(opts Opts) (remote.Remote, error) {
url, err := url.Parse(opts.URL)
if err != nil {
return nil, err
}
host, _, err := net.SplitHostPort(url.Host)
if err == nil {
url.Host = host
}
return &Gitlab{
URL: opts.URL,
Client: opts.Client,
Secret: opts.Secret,
Machine: url.Host,
Username: opts.Username,
Password: opts.Password,
PrivateMode: opts.PrivateMode,
SkipVerify: opts.SkipVerify,
}, nil
}
type Gitlab struct {
URL string
Client string
Secret string
AllowedOrgs []string
CloneMode string
Open bool
Machine string
Username string
Password string
PrivateMode bool
SkipVerify bool
HideArchives bool
@ -46,17 +79,17 @@ func Load(config string) *Gitlab {
gitlab.URL = url_.String()
gitlab.Client = params.Get("client_id")
gitlab.Secret = params.Get("client_secret")
gitlab.AllowedOrgs = params["orgs"]
// gitlab.AllowedOrgs = params["orgs"]
gitlab.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify"))
gitlab.HideArchives, _ = strconv.ParseBool(params.Get("hide_archives"))
gitlab.Open, _ = strconv.ParseBool(params.Get("open"))
// gitlab.Open, _ = strconv.ParseBool(params.Get("open"))
switch params.Get("clone_mode") {
case "oauth":
gitlab.CloneMode = "oauth"
default:
gitlab.CloneMode = "token"
}
// switch params.Get("clone_mode") {
// case "oauth":
// gitlab.CloneMode = "oauth"
// default:
// gitlab.CloneMode = "token"
// }
// this is a temp workaround
gitlab.Search, _ = strconv.ParseBool(params.Get("search"))
@ -66,7 +99,7 @@ func Load(config string) *Gitlab {
// Login authenticates the session and returns the
// remote user details.
func (g *Gitlab) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
func (g *Gitlab) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
var config = &oauth2.Config{
ClientId: g.Client,
@ -86,41 +119,41 @@ func (g *Gitlab) Login(res http.ResponseWriter, req *http.Request) (*model.User,
var code = req.FormValue("code")
if len(code) == 0 {
http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther)
return nil, false, nil
return nil, nil
}
var trans = &oauth2.Transport{Config: config, Transport: trans_}
var token_, err = trans.Exchange(code)
if err != nil {
return nil, false, fmt.Errorf("Error exchanging token. %s", err)
return nil, fmt.Errorf("Error exchanging token. %s", err)
}
client := NewClient(g.URL, token_.AccessToken, g.SkipVerify)
login, err := client.CurrentUser()
if err != nil {
return nil, false, err
return nil, err
}
if len(g.AllowedOrgs) != 0 {
groups, err := client.AllGroups()
if err != nil {
return nil, false, fmt.Errorf("Could not check org membership. %s", err)
}
var member bool
for _, group := range groups {
for _, allowedOrg := range g.AllowedOrgs {
if group.Path == allowedOrg {
member = true
break
}
}
}
if !member {
return nil, false, fmt.Errorf("User does not belong to correct group. Must belong to %v", g.AllowedOrgs)
}
}
// if len(g.AllowedOrgs) != 0 {
// groups, err := client.AllGroups()
// if err != nil {
// return nil, fmt.Errorf("Could not check org membership. %s", err)
// }
//
// var member bool
// for _, group := range groups {
// for _, allowedOrg := range g.AllowedOrgs {
// if group.Path == allowedOrg {
// member = true
// break
// }
// }
// }
//
// if !member {
// return nil, false, fmt.Errorf("User does not belong to correct group. Must belong to %v", g.AllowedOrgs)
// }
// }
user := &model.User{}
user.Login = login.Username
@ -134,7 +167,7 @@ func (g *Gitlab) Login(res http.ResponseWriter, req *http.Request) (*model.User,
user.Avatar = g.URL + "/" + login.AvatarUrl
}
return user, g.Open, nil
return user, nil
}
func (g *Gitlab) Auth(token, secret string) (string, error) {
@ -146,6 +179,21 @@ func (g *Gitlab) Auth(token, secret string) (string, error) {
return login.Username, nil
}
func (g *Gitlab) Teams(u *model.User) ([]*model.Team, error) {
client := NewClient(g.URL, u.Token, g.SkipVerify)
groups, err := client.AllGroups()
if err != nil {
return nil, err
}
var teams []*model.Team
for _, group := range groups {
teams = append(teams, &model.Team{
Login: group.Name,
})
}
return teams, nil
}
// Repo fetches the named repository from the remote system.
func (g *Gitlab) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := NewClient(g.URL, u.Token, g.SkipVerify)
@ -284,29 +332,47 @@ func (g *Gitlab) Status(u *model.User, repo *model.Repo, b *model.Build, link st
// Netrc returns a .netrc file that can be used to clone
// private repositories from a remote system.
func (g *Gitlab) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
url_, err := url.Parse(g.URL)
if err != nil {
return nil, err
}
netrc := &model.Netrc{}
netrc.Machine = url_.Host
// func (g *Gitlab) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
// url_, err := url.Parse(g.URL)
// if err != nil {
// return nil, err
// }
// netrc := &model.Netrc{}
// netrc.Machine = url_.Host
//
// switch g.CloneMode {
// case "oauth":
// netrc.Login = "oauth2"
// netrc.Password = u.Token
// case "token":
// t := token.New(token.HookToken, r.FullName)
// netrc.Login = "drone-ci-token"
// netrc.Password, err = t.Sign(r.Hash)
// }
// return netrc, err
// }
switch g.CloneMode {
case "oauth":
netrc.Login = "oauth2"
netrc.Password = u.Token
case "token":
t := token.New(token.HookToken, r.FullName)
netrc.Login = "drone-ci-token"
netrc.Password, err = t.Sign(r.Hash)
// Netrc returns a netrc file capable of authenticating Gitlab requests and
// cloning Gitlab repositories. The netrc will use the global machine account
// when configured.
func (g *Gitlab) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
if g.Password != "" {
return &model.Netrc{
Login: g.Username,
Password: g.Password,
Machine: g.Machine,
}, nil
}
return netrc, err
return &model.Netrc{
Login: "oauth2",
Password: u.Token,
Machine: g.Machine,
}, nil
}
// Activate activates a repository by adding a Post-commit hook and
// a Public Deploy key, if applicable.
func (g *Gitlab) Activate(user *model.User, repo *model.Repo, k *model.Key, link string) error {
func (g *Gitlab) Activate(user *model.User, repo *model.Repo, link string) error {
var client = NewClient(g.URL, user.Token, g.SkipVerify)
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
if err != nil {

View file

@ -94,13 +94,13 @@ func Test_Gitlab(t *testing.T) {
// Test activate method
g.Describe("Activate", func() {
g.It("Should be success", func() {
err := gitlab.Activate(&user, &repo, &model.Key{}, "http://example.com/api/hook/test/test?access_token=token")
err := gitlab.Activate(&user, &repo, "http://example.com/api/hook/test/test?access_token=token")
g.Assert(err == nil).IsTrue()
})
g.It("Should be failed, when token not given", func() {
err := gitlab.Activate(&user, &repo, &model.Key{}, "http://example.com/api/hook/test/test")
err := gitlab.Activate(&user, &repo, "http://example.com/api/hook/test/test")
g.Assert(err != nil).IsTrue()
})

View file

@ -0,0 +1,109 @@
package fixtures
import (
"net/http"
"github.com/gin-gonic/gin"
)
// Handler returns an http.Handler that is capable of handling a variety of mock
// Bitbucket requests and returning mock responses.
func Handler() http.Handler {
gin.SetMode(gin.TestMode)
e := gin.New()
e.GET("/api/v1/repos/:owner/:name", getRepo)
e.GET("/api/v1/repos/:owner/:name/raw/:commit/:file", getRepoFile)
e.POST("/api/v1/repos/:owner/:name/hooks", createRepoHook)
e.GET("/api/v1/user/repos", getUserRepos)
return e
}
func getRepo(c *gin.Context) {
switch c.Param("name") {
case "repo_not_found":
c.String(404, "")
default:
c.String(200, repoPayload)
}
}
func getRepoFile(c *gin.Context) {
switch c.Param("file") {
case "file_not_found":
c.String(404, "")
default:
c.String(200, repoFilePayload)
}
}
func createRepoHook(c *gin.Context) {
in := struct {
Type string `json:"type"`
Conf struct {
Type string `json:"content_type"`
URL string `json:"url"`
} `json:"config"`
}{}
c.BindJSON(&in)
if in.Type != "gogs" ||
in.Conf.Type != "json" ||
in.Conf.URL != "http://localhost" {
c.String(500, "")
return
}
c.String(200, "{}")
}
func getUserRepos(c *gin.Context) {
switch c.Request.Header.Get("Authorization") {
case "token repos_not_found":
c.String(404, "")
default:
c.String(200, userRepoPayload)
}
}
const repoPayload = `
{
"owner": {
"username": "test_name",
"email": "octocat@github.com",
"avatar_url": "https:\/\/secure.gravatar.com\/avatar\/8c58a0be77ee441bb8f8595b7f1b4e87"
},
"full_name": "test_name\/repo_name",
"private": true,
"html_url": "http:\/\/localhost\/test_name\/repo_name",
"clone_url": "http:\/\/localhost\/test_name\/repo_name.git",
"permissions": {
"admin": true,
"push": true,
"pull": true
}
}
`
const repoFilePayload = `{ platform: linux/amd64 }`
const userRepoPayload = `
[
{
"owner": {
"username": "test_name",
"email": "octocat@github.com",
"avatar_url": "https:\/\/secure.gravatar.com\/avatar\/8c58a0be77ee441bb8f8595b7f1b4e87"
},
"full_name": "test_name\/repo_name",
"private": true,
"html_url": "http:\/\/localhost\/test_name\/repo_name",
"clone_url": "http:\/\/localhost\/test_name\/repo_name.git",
"permissions": {
"admin": true,
"push": true,
"pull": true
}
}
]
`

View file

@ -1,6 +1,6 @@
package testdata
package fixtures
var PushHook = `
var HookPush = `
{
"ref": "refs/heads/master",
"before": "4b2626259b5a97b6b4eab5e6cca66adb986b672b",

View file

@ -6,189 +6,187 @@ import (
"net"
"net/http"
"net/url"
"strconv"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/gogits/go-gogs-client"
log "github.com/Sirupsen/logrus"
)
type Gogs struct {
// Opts defines configuration options.
type Opts struct {
URL string // Gogs server url.
Username string // Optional machine account username.
Password string // Optional machine account password.
PrivateMode bool // Gogs is running in private mode.
SkipVerify bool // Skip ssl verification.
}
type client struct {
URL string
Open bool
Machine string
Username string
Password string
PrivateMode bool
SkipVerify bool
}
func Load(config string) *Gogs {
// parse the remote DSN configuration string
url_, err := url.Parse(config)
// New returns a Remote implementation that integrates with Gogs, an open
// source Git service written in Go. See https://gogs.io/
func New(opts Opts) (remote.Remote, error) {
url, err := url.Parse(opts.URL)
if err != nil {
log.Fatalln("unable to parse remote dsn. %s", err)
return nil, err
}
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
host, _, err := net.SplitHostPort(url.Host)
if err == nil {
url.Host = host
}
return &client{
URL: opts.URL,
Machine: url.Host,
Username: opts.Username,
Password: opts.Password,
PrivateMode: opts.PrivateMode,
SkipVerify: opts.SkipVerify,
}, nil
}
// Login authenticates the session and returns the
// remote user details.
func (g *Gogs) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
// Login authenticates an account with Gogs using basic authenticaiton. The
// Gogs account details are returned when the user is successfully authenticated.
func (c *client) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
var (
username = req.FormValue("username")
password = req.FormValue("password")
)
// if the username or password doesn't exist we re-direct
// the user to the login screen.
// if the username or password is empty we re-direct to the login screen.
if len(username) == 0 || len(password) == 0 {
http.Redirect(res, req, "/login/form", http.StatusSeeOther)
return nil, false, nil
return nil, nil
}
client := NewGogsClient(g.URL, "", g.SkipVerify)
client := c.newClient()
// try to fetch drone token if it exists
var accessToken string
tokens, err := client.ListAccessTokens(username, password)
if err != nil {
return nil, false, err
}
for _, token := range tokens {
if token.Name == "drone" {
accessToken = token.Sha1
break
if err == nil {
for _, token := range tokens {
if token.Name == "drone" {
accessToken = token.Sha1
break
}
}
}
// if drone token not found, create it
if accessToken == "" {
token, err := client.CreateAccessToken(username, password, gogs.CreateAccessTokenOption{Name: "drone"})
if err != nil {
return nil, false, err
token, terr := client.CreateAccessToken(
username,
password,
gogs.CreateAccessTokenOption{Name: "drone"},
)
if terr != nil {
return nil, terr
}
accessToken = token.Sha1
}
client = NewGogsClient(g.URL, accessToken, g.SkipVerify)
userInfo, err := client.GetUserInfo(username)
if err != nil {
return nil, false, err
}
user := model.User{}
user.Token = accessToken
user.Login = userInfo.UserName
user.Email = userInfo.Email
user.Avatar = expandAvatar(g.URL, userInfo.AvatarUrl)
return &user, g.Open, nil
}
// Auth authenticates the session and returns the remote user
// login for the given token and secret
func (g *Gogs) Auth(token, secret string) (string, error) {
return "", fmt.Errorf("Method not supported")
}
// Repo fetches the named repository from the remote system.
func (g *Gogs) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
repos_, err := client.ListMyRepos()
client = c.newClientToken(accessToken)
account, err := client.GetUserInfo(username)
if err != nil {
return nil, err
}
fullName := owner + "/" + name
for _, repo := range repos_ {
if repo.FullName == fullName {
return toRepo(repo), nil
}
}
return nil, fmt.Errorf("Not Found")
return &model.User{
Token: accessToken,
Login: account.UserName,
Email: account.Email,
Avatar: expandAvatar(c.URL, account.AvatarUrl),
}, nil
}
// Repos fetches a list of repos from the remote system.
func (g *Gogs) Repos(u *model.User) ([]*model.RepoLite, error) {
// Auth is not supported by the Gogs driver.
func (c *client) Auth(token, secret string) (string, error) {
return "", fmt.Errorf("Not Implemented")
}
// Teams is not supported by the Gogs driver.
func (c *client) Teams(u *model.User) ([]*model.Team, error) {
var empty []*model.Team
return empty, nil
}
// Repo returns the named Gogs repository.
func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := c.newClientToken(u.Token)
repo, err := client.GetRepo(owner, name)
if err != nil {
return nil, err
}
return toRepo(repo), nil
}
// Repos returns a list of all repositories for the Gogs account, including
// organization repositories.
func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) {
repos := []*model.RepoLite{}
client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
repos_, err := client.ListMyRepos()
client := c.newClientToken(u.Token)
all, err := client.ListMyRepos()
if err != nil {
return repos, err
}
for _, repo := range repos_ {
for _, repo := range all {
repos = append(repos, toRepoLite(repo))
}
return repos, err
}
// Perm fetches the named repository permissions from
// the remote system for the specified user.
func (g *Gogs) Perm(u *model.User, owner, name string) (*model.Perm, error) {
client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
repos_, err := client.ListMyRepos()
// Perm returns the user permissions for the named Gogs repository.
func (c *client) Perm(u *model.User, owner, name string) (*model.Perm, error) {
client := c.newClientToken(u.Token)
repo, err := client.GetRepo(owner, name)
if err != nil {
return nil, err
}
fullName := owner + "/" + name
for _, repo := range repos_ {
if repo.FullName == fullName {
return toPerm(repo.Permissions), nil
}
}
return nil, fmt.Errorf("Not Found")
return toPerm(repo.Permissions), nil
}
// 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) {
client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
// File fetches the file from the Gogs repository and returns its contents.
func (c *client) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
client := c.newClientToken(u.Token)
cfg, err := client.GetFile(r.Owner, r.Name, b.Commit, f)
return cfg, err
}
// Status sends the commit status to the remote system.
// An example would be the GitHub pull request status.
func (g *Gogs) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
return fmt.Errorf("Not Implemented")
// Status is not supported by the Gogs driver.
func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
return nil
}
// Netrc returns a .netrc file that can be used to clone
// private repositories from a remote system.
func (g *Gogs) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
url_, err := url.Parse(g.URL)
if err != nil {
return nil, err
}
host, _, err := net.SplitHostPort(url_.Host)
if err == nil {
url_.Host = host
// Netrc returns a netrc file capable of authenticating Gogs requests and
// cloning Gogs repositories. The netrc will use the global machine account
// when configured.
func (c *client) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
if c.Password != "" {
return &model.Netrc{
Login: c.Username,
Password: c.Password,
Machine: c.Machine,
}, nil
}
return &model.Netrc{
Login: u.Token,
Password: "x-oauth-basic",
Machine: url_.Host,
Machine: c.Machine,
}, nil
}
// Activate activates a repository by creating the post-commit hook and
// adding the SSH deploy key, if applicable.
func (g *Gogs) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
// Activate activates the repository by registering post-commit hooks with
// the Gogs repository.
func (c *client) Activate(u *model.User, r *model.Repo, link string) error {
config := map[string]string{
"url": link,
"secret": r.Hash,
@ -200,20 +198,19 @@ func (g *Gogs) Activate(u *model.User, r *model.Repo, k *model.Key, link string)
Active: true,
}
client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
client := c.newClientToken(u.Token)
_, err := client.CreateRepoHook(r.Owner, r.Name, hook)
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 (g *Gogs) Deactivate(u *model.User, r *model.Repo, link string) error {
return fmt.Errorf("Not Implemented")
// Deactivate is not supported by the Gogs driver.
func (c *client) Deactivate(u *model.User, r *model.Repo, link string) error {
return nil
}
// Hook parses the post-commit hook from the Request body
// and returns the required data in a standard format.
func (g *Gogs) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
// Hook parses the incoming Gogs hook and returns the Repository and Build
// details. If the hook is unsupported nil values are returned.
func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
var (
err error
repo *model.Repo
@ -222,7 +219,7 @@ func (g *Gogs) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
switch r.Header.Get("X-Gogs-Event") {
case "push":
var push *PushHook
var push *pushHook
push, err = parsePush(r.Body)
if err == nil {
repo = repoFromPush(push)
@ -232,20 +229,20 @@ func (g *Gogs) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
return repo, build, err
}
// NewClient initializes and returns a API client.
func NewGogsClient(url, token string, skipVerify bool) *gogs.Client {
sslClient := &http.Client{}
c := gogs.NewClient(url, token)
// helper function to return the Gogs client
func (c *client) newClient() *gogs.Client {
return c.newClientToken("")
}
if skipVerify {
sslClient.Transport = &http.Transport{
// helper function to return the Gogs client
func (c *client) newClientToken(token string) *gogs.Client {
client := gogs.NewClient(c.URL, token)
if c.SkipVerify {
httpClient := &http.Client{}
httpClient.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
c.SetHTTPClient(sslClient)
client.SetHTTPClient(httpClient)
}
return c
}
func (g *Gogs) String() string {
return "gogs"
return client
}

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

@ -0,0 +1,183 @@
package gogs
import (
"net/http/httptest"
"testing"
"github.com/drone/drone/model"
"github.com/drone/drone/remote/gogs/fixtures"
"github.com/franela/goblin"
"github.com/gin-gonic/gin"
)
func Test_gogs(t *testing.T) {
gin.SetMode(gin.TestMode)
s := httptest.NewServer(fixtures.Handler())
c, _ := New(Opts{
URL: s.URL,
SkipVerify: true,
})
g := goblin.Goblin(t)
g.Describe("Gogs", func() {
g.After(func() {
s.Close()
})
g.Describe("Creating a remote", func() {
g.It("Should return client with specified options", func() {
remote, _ := New(Opts{
URL: "http://localhost:8080",
Username: "someuser",
Password: "password",
SkipVerify: true,
PrivateMode: true,
})
g.Assert(remote.(*client).URL).Equal("http://localhost:8080")
g.Assert(remote.(*client).Machine).Equal("localhost")
g.Assert(remote.(*client).Username).Equal("someuser")
g.Assert(remote.(*client).Password).Equal("password")
g.Assert(remote.(*client).SkipVerify).Equal(true)
g.Assert(remote.(*client).PrivateMode).Equal(true)
})
g.It("Should handle malformed url", func() {
_, err := New(Opts{URL: "%gh&%ij"})
g.Assert(err != nil).IsTrue()
})
})
g.Describe("Generating a netrc file", func() {
g.It("Should return a netrc with the user token", func() {
remote, _ := New(Opts{
URL: "http://gogs.com",
})
netrc, _ := remote.Netrc(fakeUser, nil)
g.Assert(netrc.Machine).Equal("gogs.com")
g.Assert(netrc.Login).Equal(fakeUser.Token)
g.Assert(netrc.Password).Equal("x-oauth-basic")
})
g.It("Should return a netrc with the machine account", func() {
remote, _ := New(Opts{
URL: "http://gogs.com",
Username: "someuser",
Password: "password",
})
netrc, _ := remote.Netrc(nil, nil)
g.Assert(netrc.Machine).Equal("gogs.com")
g.Assert(netrc.Login).Equal("someuser")
g.Assert(netrc.Password).Equal("password")
})
})
g.Describe("Requesting a repository", func() {
g.It("Should return the repository details", func() {
repo, err := c.Repo(fakeUser, fakeRepo.Owner, fakeRepo.Name)
g.Assert(err == nil).IsTrue()
g.Assert(repo.Owner).Equal(fakeRepo.Owner)
g.Assert(repo.Name).Equal(fakeRepo.Name)
g.Assert(repo.FullName).Equal(fakeRepo.Owner + "/" + fakeRepo.Name)
g.Assert(repo.IsPrivate).IsTrue()
g.Assert(repo.Clone).Equal("http://localhost/test_name/repo_name.git")
g.Assert(repo.Link).Equal("http://localhost/test_name/repo_name")
})
g.It("Should handle a not found error", func() {
_, err := c.Repo(fakeUser, fakeRepoNotFound.Owner, fakeRepoNotFound.Name)
g.Assert(err != nil).IsTrue()
})
})
g.Describe("Requesting repository permissions", func() {
g.It("Should return the permission details", func() {
perm, err := c.Perm(fakeUser, fakeRepo.Owner, fakeRepo.Name)
g.Assert(err == nil).IsTrue()
g.Assert(perm.Admin).IsTrue()
g.Assert(perm.Push).IsTrue()
g.Assert(perm.Pull).IsTrue()
})
g.It("Should handle a not found error", func() {
_, err := c.Perm(fakeUser, fakeRepoNotFound.Owner, fakeRepoNotFound.Name)
g.Assert(err != nil).IsTrue()
})
})
g.Describe("Requesting a repository list", func() {
g.It("Should return the repository list", func() {
repos, err := c.Repos(fakeUser)
g.Assert(err == nil).IsTrue()
g.Assert(repos[0].Owner).Equal(fakeRepo.Owner)
g.Assert(repos[0].Name).Equal(fakeRepo.Name)
g.Assert(repos[0].FullName).Equal(fakeRepo.Owner + "/" + fakeRepo.Name)
})
g.It("Should handle a not found error", func() {
_, err := c.Repos(fakeUserNoRepos)
g.Assert(err != nil).IsTrue()
})
})
g.It("Should register repositroy hooks", func() {
err := c.Activate(fakeUser, fakeRepo, "http://localhost")
g.Assert(err == nil).IsTrue()
})
g.It("Should return a repository file", func() {
raw, err := c.File(fakeUser, fakeRepo, fakeBuild, ".drone.yml")
g.Assert(err == nil).IsTrue()
g.Assert(string(raw)).Equal("{ platform: linux/amd64 }")
})
g.Describe("Given an authentication request", func() {
g.It("Should redirect to login form")
g.It("Should create an access token")
g.It("Should handle an access token error")
g.It("Should return the authenticated user")
})
g.Describe("Given a repository hook", func() {
g.It("Should skip non-push events")
g.It("Should return push details")
g.It("Should handle a parsing error")
})
g.It("Should return no-op for usupporeted features", func() {
_, err1 := c.Auth("octocat", "4vyW6b49Z")
_, err2 := c.Teams(nil)
err3 := c.Status(nil, nil, nil, "")
err4 := c.Deactivate(nil, nil, "")
g.Assert(err1 != nil).IsTrue()
g.Assert(err2 == nil).IsTrue()
g.Assert(err3 == nil).IsTrue()
g.Assert(err4 == nil).IsTrue()
})
})
}
var (
fakeUser = &model.User{
Login: "someuser",
Token: "cfcd2084",
}
fakeUserNoRepos = &model.User{
Login: "someuser",
Token: "repos_not_found",
}
fakeRepo = &model.Repo{
Owner: "test_name",
Name: "repo_name",
FullName: "test_name/repo_name",
}
fakeRepoNotFound = &model.Repo{
Owner: "test_name",
Name: "repo_not_found",
FullName: "test_name/repo_not_found",
}
fakeBuild = &model.Build{
Commit: "9ecad50",
}
)

View file

@ -12,8 +12,7 @@ import (
"github.com/gogits/go-gogs-client"
)
// helper function that converts a Gogs repository
// to a Drone repository.
// helper function that converts a Gogs repository to a Drone repository.
func toRepoLite(from *gogs.Repository) *model.RepoLite {
name := strings.Split(from.FullName, "/")[1]
avatar := expandAvatar(
@ -28,8 +27,7 @@ func toRepoLite(from *gogs.Repository) *model.RepoLite {
}
}
// helper function that converts a Gogs repository
// to a Drone repository.
// helper function that converts a Gogs repository to a Drone repository.
func toRepo(from *gogs.Repository) *model.Repo {
name := strings.Split(from.FullName, "/")[1]
avatar := expandAvatar(
@ -49,8 +47,7 @@ func toRepo(from *gogs.Repository) *model.Repo {
}
}
// helper function that converts a Gogs permission
// to a Drone permission.
// helper function that converts a Gogs permission to a Drone permission.
func toPerm(from gogs.Permission) *model.Perm {
return &model.Perm{
Pull: from.Pull,
@ -59,11 +56,10 @@ func toPerm(from gogs.Permission) *model.Perm {
}
}
// helper function that extracts the Build data
// from a Gogs push hook
func buildFromPush(hook *PushHook) *model.Build {
// helper function that extracts the Build data from a Gogs push hook
func buildFromPush(hook *pushHook) *model.Build {
avatar := expandAvatar(
hook.Repo.Url,
hook.Repo.URL,
fixMalformedAvatar(hook.Sender.Avatar),
)
return &model.Build{
@ -79,9 +75,8 @@ func buildFromPush(hook *PushHook) *model.Build {
}
}
// helper function that extracts the Repository data
// from a Gogs push hook
func repoFromPush(hook *PushHook) *model.Repo {
// helper function that extracts the Repository data from a Gogs push hook
func repoFromPush(hook *pushHook) *model.Repo {
fullName := fmt.Sprintf(
"%s/%s",
hook.Repo.Owner.Username,
@ -91,20 +86,19 @@ func repoFromPush(hook *PushHook) *model.Repo {
Name: hook.Repo.Name,
Owner: hook.Repo.Owner.Username,
FullName: fullName,
Link: hook.Repo.Url,
Link: hook.Repo.URL,
}
}
// helper function that parses a push hook from
// a read closer.
func parsePush(r io.Reader) (*PushHook, error) {
push := new(PushHook)
// helper function that parses a push hook from a read closer.
func parsePush(r io.Reader) (*pushHook, error) {
push := new(pushHook)
err := json.NewDecoder(r).Decode(push)
return push, err
}
// fixMalformedAvatar is a helper function that fixes
// an avatar url if malformed (known bug with gogs)
// fixMalformedAvatar is a helper function that fixes an avatar url if malformed
// (currently a known bug with gogs)
func fixMalformedAvatar(url string) string {
index := strings.Index(url, "///")
if index != -1 {
@ -117,16 +111,16 @@ func fixMalformedAvatar(url string) string {
return url
}
// expandAvatar is a helper function that converts
// a relative avatar URL to the abosolute url.
// expandAvatar is a helper function that converts a relative avatar URL to the
// abosolute url.
func expandAvatar(repo, rawurl string) string {
if !strings.HasPrefix(rawurl, "/avatars/") {
return rawurl
}
url_, err := url.Parse(repo)
url, err := url.Parse(repo)
if err != nil {
return rawurl
}
url_.Path = rawurl
return url_.String()
url.Path = rawurl
return url.String()
}

View file

@ -5,7 +5,7 @@ import (
"testing"
"github.com/drone/drone/model"
"github.com/drone/drone/remote/gogs/testdata"
"github.com/drone/drone/remote/gogs/fixtures"
"github.com/franela/goblin"
"github.com/gogits/go-gogs-client"
@ -17,7 +17,7 @@ func Test_parse(t *testing.T) {
g.Describe("Gogs", func() {
g.It("Should parse push hook payload", func() {
buf := bytes.NewBufferString(testdata.PushHook)
buf := bytes.NewBufferString(fixtures.HookPush)
hook, err := parsePush(buf)
g.Assert(err == nil).IsTrue()
g.Assert(hook.Ref).Equal("refs/heads/master")
@ -25,7 +25,7 @@ func Test_parse(t *testing.T) {
g.Assert(hook.Before).Equal("4b2626259b5a97b6b4eab5e6cca66adb986b672b")
g.Assert(hook.Compare).Equal("http://gogs.golang.org/gordon/hello-world/compare/4b2626259b5a97b6b4eab5e6cca66adb986b672b...ef98532add3b2feb7a137426bba1248724367df5")
g.Assert(hook.Repo.Name).Equal("hello-world")
g.Assert(hook.Repo.Url).Equal("http://gogs.golang.org/gordon/hello-world")
g.Assert(hook.Repo.URL).Equal("http://gogs.golang.org/gordon/hello-world")
g.Assert(hook.Repo.Owner.Name).Equal("gordon")
g.Assert(hook.Repo.Owner.Email).Equal("gordon@golang.org")
g.Assert(hook.Repo.Owner.Username).Equal("gordon")
@ -38,7 +38,7 @@ func Test_parse(t *testing.T) {
})
g.It("Should return a Build struct from a push hook", func() {
buf := bytes.NewBufferString(testdata.PushHook)
buf := bytes.NewBufferString(fixtures.HookPush)
hook, _ := parsePush(buf)
build := buildFromPush(hook)
g.Assert(build.Event).Equal(model.EventPush)
@ -53,13 +53,13 @@ func Test_parse(t *testing.T) {
})
g.It("Should return a Repo struct from a push hook", func() {
buf := bytes.NewBufferString(testdata.PushHook)
buf := bytes.NewBufferString(fixtures.HookPush)
hook, _ := parsePush(buf)
repo := repoFromPush(hook)
g.Assert(repo.Name).Equal(hook.Repo.Name)
g.Assert(repo.Owner).Equal(hook.Repo.Owner.Username)
g.Assert(repo.FullName).Equal("gordon/hello-world")
g.Assert(repo.Link).Equal(hook.Repo.Url)
g.Assert(repo.Link).Equal(hook.Repo.URL)
})
g.It("Should return a Perm struct from a Gogs Perm", func() {

View file

@ -1,6 +1,6 @@
package gogs
type PushHook struct {
type pushHook struct {
Ref string `json:"ref"`
Before string `json:"before"`
After string `json:"after"`
@ -15,7 +15,7 @@ type PushHook struct {
Repo struct {
ID int64 `json:"id"`
Name string `json:"name"`
Url string `json:"url"`
URL string `json:"url"`
Private bool `json:"private"`
Owner struct {
Name string `json:"name"`
@ -27,7 +27,7 @@ type PushHook struct {
Commits []struct {
ID string `json:"id"`
Message string `json:"message"`
Url string `json:"url"`
URL string `json:"url"`
} `json:"commits"`
Sender struct {

View file

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

View file

@ -13,12 +13,15 @@ import (
type Remote interface {
// Login authenticates the session and returns the
// remote user details.
Login(w http.ResponseWriter, r *http.Request) (*model.User, bool, error)
Login(w http.ResponseWriter, r *http.Request) (*model.User, error)
// Auth authenticates the session and returns the remote user
// login for the given token and secret
Auth(token, secret string) (string, error)
// Teams fetches a list of team memberships from the remote system.
Teams(u *model.User) ([]*model.Team, error)
// Repo fetches the named repository from the remote system.
Repo(u *model.User, owner, repo string) (*model.Repo, error)
@ -41,29 +44,28 @@ type Remote interface {
// private repositories from a remote system.
Netrc(u *model.User, r *model.Repo) (*model.Netrc, error)
// Activate activates a repository by creating the post-commit hook and
// adding the SSH deploy key, if applicable.
Activate(u *model.User, r *model.Repo, k *model.Key, link string) error
// Activate activates a repository by creating the post-commit hook.
Activate(u *model.User, r *model.Repo, link string) error
// Deactivate removes a repository by removing all the post-commit hooks
// which are equal to link and removing the SSH deploy key.
// Deactivate deactivates a repository by removing all previously created
// post-commit hooks matching the given link.
Deactivate(u *model.User, r *model.Repo, link string) error
// Hook parses the post-commit hook from the Request body
// and returns the required data in a standard format.
// Hook parses the post-commit hook from the Request body and returns the
// required data in a standard format.
Hook(r *http.Request) (*model.Repo, *model.Build, error)
}
// Refresher refreshes an oauth token and expiration for the given user. It
// returns true if the token was refreshed, false if the token was not refreshed,
// and error if it failed to refersh.
type Refresher interface {
// Refresh refreshes an oauth token and expiration for the given
// user. It returns true if the token was refreshed, false if the
// token was not refreshed, and error if it failed to refersh.
Refresh(*model.User) (bool, error)
}
// Login authenticates the session and returns the
// remote user details.
func Login(c context.Context, w http.ResponseWriter, r *http.Request) (*model.User, bool, error) {
func Login(c context.Context, w http.ResponseWriter, r *http.Request) (*model.User, error) {
return FromContext(c).Login(w, r)
}
@ -73,6 +75,11 @@ func Auth(c context.Context, token, secret string) (string, error) {
return FromContext(c).Auth(token, secret)
}
// Teams fetches a list of team memberships from the remote system.
func Teams(c context.Context, u *model.User) ([]*model.Team, error) {
return FromContext(c).Teams(u)
}
// Repo fetches the named repository from the remote system.
func Repo(c context.Context, u *model.User, owner, repo string) (*model.Repo, error) {
return FromContext(c).Repo(u, owner, repo)
@ -108,8 +115,8 @@ func Netrc(c context.Context, u *model.User, r *model.Repo) (*model.Netrc, error
// Activate activates a repository by creating the post-commit hook and
// adding the SSH deploy key, if applicable.
func Activate(c context.Context, u *model.User, r *model.Repo, k *model.Key, link string) error {
return FromContext(c).Activate(u, r, k, link)
func Activate(c context.Context, u *model.User, r *model.Repo, link string) error {
return FromContext(c).Activate(u, r, link)
}
// Deactivate removes a repository by removing all the post-commit hooks

View file

@ -1,45 +1,33 @@
package middleware
import (
"github.com/codegangsta/cli"
"github.com/drone/drone/shared/token"
"github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin"
"github.com/ianschenck/envflag"
)
var (
secret = envflag.String("AGENT_SECRET", "", "")
noauth = envflag.Bool("AGENT_NO_AUTH", false, "")
)
const agentKey = "agent"
// Agent is a middleware function that initializes the authorization middleware
// Agents is a middleware function that initializes the authorization middleware
// for agents to connect to the queue.
func AgentMust() gin.HandlerFunc {
if *secret == "" {
logrus.Fatalf("please provide the agent secret to authenticate agent requests")
func Agents(cli *cli.Context) gin.HandlerFunc {
secret := cli.String("agent-secret")
if secret == "" {
logrus.Fatalf("failed to generate token from DRONE_AGENT_SECRET")
}
t := token.New(token.AgentToken, "")
s, err := t.Sign(*secret)
t := token.New(secret, "")
s, err := t.Sign(secret)
if err != nil {
logrus.Fatalf("invalid agent secret. %s", err)
logrus.Fatalf("failed to generate token from DRONE_AGENT_SECRET. %s", err)
}
logrus.Infof("using agent secret %s", *secret)
logrus.Infof("using agent secret %s", secret)
logrus.Warnf("agents can connect with token %s", s)
return func(c *gin.Context) {
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
return *secret, nil
})
if err != nil {
c.AbortWithError(403, err)
} else if parsed.Kind != token.AgentToken {
c.AbortWithStatus(403)
} else {
c.Next()
}
c.Set(agentKey, secret)
}
}

View file

@ -2,13 +2,16 @@ package middleware
import (
"github.com/drone/drone/bus"
"github.com/codegangsta/cli"
"github.com/gin-gonic/gin"
)
func Bus() gin.HandlerFunc {
bus_ := bus.New()
// Bus is a middleware function that initializes the Event Bus and attaches to
// the context of every http.Request.
func Bus(cli *cli.Context) gin.HandlerFunc {
v := bus.New()
return func(c *gin.Context) {
bus.ToContext(c, bus_)
c.Next()
bus.ToContext(c, v)
}
}

View file

@ -1,22 +1,24 @@
package middleware
import (
"time"
"github.com/drone/drone/cache"
"github.com/codegangsta/cli"
"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)
func Cache(cli *cli.Context) gin.HandlerFunc {
v := setupCache(cli)
return func(c *gin.Context) {
cache.ToContext(c, cc)
c.Next()
cache.ToContext(c, v)
}
}
// helper function to create the cache from the CLI context.
func setupCache(c *cli.Context) cache.Cache {
return cache.NewTTL(
c.Duration("cache-ttl"),
)
}

View file

@ -0,0 +1,40 @@
package middleware
import (
"github.com/drone/drone/model"
"github.com/codegangsta/cli"
"github.com/gin-gonic/gin"
)
const configKey = "config"
// Config is a middleware function that initializes the Configuration and
// attaches to the context of every http.Request.
func Config(cli *cli.Context) gin.HandlerFunc {
v := setupConfig(cli)
return func(c *gin.Context) {
c.Set(configKey, v)
}
}
// helper function to create the configuration from the CLI context.
func setupConfig(c *cli.Context) *model.Config {
return &model.Config{
Open: c.Bool("open"),
Yaml: c.String("yaml"),
Shasum: c.String("yaml") + ".sig",
Secret: c.String("agent-secret"),
Admins: sliceToMap(c.StringSlice("admin")),
Orgs: sliceToMap(c.StringSlice("orgs")),
}
}
// helper function to convert a string slice to a map.
func sliceToMap(s []string) map[string]bool {
v := map[string]bool{}
for _, ss := range s {
v[ss] = true
}
return v
}

View file

@ -1,28 +0,0 @@
package middleware
import (
"sync"
"github.com/drone/drone/engine"
"github.com/drone/drone/store"
"github.com/gin-gonic/gin"
)
// Engine is a middleware function that initializes the Engine and attaches to
// the context of every http.Request.
func Engine() gin.HandlerFunc {
var once sync.Once
var engine_ engine.Engine
return func(c *gin.Context) {
once.Do(func() {
store_ := store.FromContext(c)
engine_ = engine.Load(store_)
})
engine.ToContext(c, engine_)
c.Next()
}
}

View file

@ -2,13 +2,16 @@ package middleware
import (
"github.com/drone/drone/queue"
"github.com/codegangsta/cli"
"github.com/gin-gonic/gin"
)
func Queue() gin.HandlerFunc {
queue_ := queue.New()
// Queue is a middleware function that initializes the Queue and attaches to
// the context of every http.Request.
func Queue(cli *cli.Context) gin.HandlerFunc {
v := queue.New()
return func(c *gin.Context) {
queue.ToContext(c, queue_)
c.Next()
queue.ToContext(c, v)
}
}

View file

@ -1,48 +1,102 @@
package middleware
import (
"fmt"
"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"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/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")
func Remote(c *cli.Context) gin.HandlerFunc {
v, err := setupRemote(c)
if err != nil {
logrus.Fatalln(err)
}
return func(c *gin.Context) {
remote.ToContext(c, remote_)
c.Next()
remote.ToContext(c, v)
}
}
// helper function to setup the remote from the CLI arguments.
func setupRemote(c *cli.Context) (remote.Remote, error) {
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:
return nil, fmt.Errorf("version control system not configured")
}
}
// helper function to setup the Bitbucket remote from the CLI arguments.
func setupBitbucket(c *cli.Context) (remote.Remote, error) {
return bitbucket.New(
c.String("bitbucket-client"),
c.String("bitbucket-server"),
), nil
}
// helper function to setup the Gogs remote from the CLI arguments.
func setupGogs(c *cli.Context) (remote.Remote, error) {
return gogs.New(gogs.Opts{
URL: c.String("gogs-server"),
Username: c.String("gogs-git-username"),
Password: c.String("gogs-git-password"),
PrivateMode: c.Bool("gogs-private-mode"),
SkipVerify: c.Bool("gogs-skip-verify"),
})
}
// helper function to setup the Stash remote from the CLI arguments.
func setupStash(c *cli.Context) (remote.Remote, error) {
return bitbucketserver.New(bitbucketserver.Opts{
URL: c.String("stash-server"),
Username: c.String("stash-git-username"),
Password: c.String("stash-git-password"),
ConsumerKey: c.String("stash-consumer-key"),
ConsumerRSA: c.String("stash-consumer-rsa"),
SkipVerify: c.Bool("stash-skip-verify"),
})
}
// helper function to setup the Gitlab remote from the CLI arguments.
func setupGitlab(c *cli.Context) (remote.Remote, error) {
return gitlab.New(gitlab.Opts{
URL: c.String("gitlab-server"),
Client: c.String("gitlab-client"),
Secret: c.String("gitlab-sercret"),
Username: c.String("gitlab-git-username"),
Password: c.String("gitlab-git-password"),
PrivateMode: c.Bool("gitlab-private-mode"),
SkipVerify: c.Bool("gitlab-skip-verify"),
})
}
// helper function to setup the GitHub remote from the CLI arguments.
func setupGithub(c *cli.Context) (remote.Remote, error) {
return 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"),
)
}

View file

@ -0,0 +1,22 @@
package session
import (
"github.com/drone/drone/shared/token"
"github.com/gin-gonic/gin"
)
// AuthorizeAgent authorizes requsts from build agents to access the queue.
func AuthorizeAgent(c *gin.Context) {
secret := c.MustGet("agent").(string)
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
return secret, nil
})
if err != nil {
c.AbortWithError(403, err)
} else if parsed.Kind != token.AgentToken {
c.AbortWithStatus(403)
} else {
c.Next()
}
}

View file

@ -44,6 +44,10 @@ func SetUser() gin.HandlerFunc {
return user.Hash, err
})
if err == nil {
confv := c.MustGet("config")
if conf, ok := confv.(*model.Config); ok {
user.Admin = conf.IsAdmin(user)
}
c.Set("user", user)
// if this is a session token (ie not the API token)

View file

@ -1,29 +1,27 @@
package middleware
import (
"github.com/codegangsta/cli"
"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)
func Store(cli *cli.Context) gin.HandlerFunc {
v := setupStore(cli)
return func(c *gin.Context) {
store.ToContext(c, db)
store.ToContext(c, v)
c.Next()
}
}
// helper function to create the datastore from the CLI context.
func setupStore(c *cli.Context) store.Store {
return datastore.New(
c.String("driver"),
c.String("datasource"),
)
}

View file

@ -2,13 +2,16 @@ package middleware
import (
"github.com/drone/drone/stream"
"github.com/codegangsta/cli"
"github.com/gin-gonic/gin"
)
func Stream() gin.HandlerFunc {
stream_ := stream.New()
// Stream is a middleware function that initializes the Stream and attaches to
// the context of every http.Request.
func Stream(cli *cli.Context) gin.HandlerFunc {
v := stream.New()
return func(c *gin.Context) {
stream.ToContext(c, stream_)
c.Next()
stream.ToContext(c, v)
}
}

View file

@ -5,10 +5,8 @@ import (
"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.
// 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

@ -2,22 +2,20 @@ package router
import (
"net/http"
"os"
"strings"
"github.com/gin-gonic/gin"
"github.com/drone/drone/api"
"github.com/drone/drone/router/middleware"
"github.com/drone/drone/router/middleware/header"
"github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/router/middleware/token"
"github.com/drone/drone/server"
"github.com/drone/drone/static"
"github.com/drone/drone/template"
"github.com/drone/drone/web"
)
func Load(middlewares ...gin.HandlerFunc) http.Handler {
func Load(middleware ...gin.HandlerFunc) http.Handler {
e := gin.New()
e.Use(gin.Recovery())
@ -27,22 +25,21 @@ func Load(middlewares ...gin.HandlerFunc) http.Handler {
e.Use(header.NoCache)
e.Use(header.Options)
e.Use(header.Secure)
e.Use(middlewares...)
e.Use(middleware...)
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)
e.GET("/", server.ShowIndex)
e.GET("/repos", server.ShowAllRepos)
e.GET("/login", server.ShowLogin)
e.GET("/login/form", server.ShowLoginForm)
e.GET("/logout", server.GetLogout)
// TODO below will Go away with React UI
settings := e.Group("/settings")
{
settings.Use(session.MustUser())
settings.GET("/profile", web.ShowUser)
settings.GET("/people", session.MustAdmin(), web.ShowUsers)
settings.GET("/nodes", session.MustAdmin(), web.ShowNodes)
settings.GET("/profile", server.ShowUser)
}
repo := e.Group("/repos/:owner/:name")
{
@ -50,50 +47,43 @@ func Load(middlewares ...gin.HandlerFunc) http.Handler {
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.GET("", server.ShowRepo)
repo.GET("/builds/:number", server.ShowBuild)
repo.GET("/builds/:number/:job", server.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)
repo_settings.GET("", session.MustPush, server.ShowRepoConf)
repo_settings.GET("/encrypt", session.MustPush, server.ShowRepoEncrypt)
repo_settings.GET("/badges", server.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)
user.GET("", server.GetSelf)
user.GET("/feed", server.GetFeed)
user.GET("/repos", server.GetRepos)
user.GET("/repos/remote", server.GetRemoteRepos)
user.POST("/token", server.PostToken)
user.DELETE("/token", server.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)
}
nodes := e.Group("/api/nodes")
{
nodes.Use(session.MustAdmin())
nodes.GET("", api.GetNodes)
nodes.POST("", api.PostNode)
nodes.DELETE("/:node", api.DeleteNode)
users.GET("", server.GetUsers)
users.POST("", server.PostUser)
users.GET("/:login", server.GetUser)
users.PATCH("/:login", server.PatchUser)
users.DELETE("/:login", server.DeleteUser)
}
repos := e.Group("/api/repos/:owner/:name")
{
repos.POST("", api.PostRepo)
repos.POST("", server.PostRepo)
repo := repos.Group("")
{
@ -101,37 +91,32 @@ func Load(middlewares ...gin.HandlerFunc) http.Handler {
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.GET("", server.GetRepo)
repo.GET("/builds", server.GetBuilds)
repo.GET("/builds/:number", server.GetBuild)
repo.GET("/logs/:number/:job", server.GetBuildLogs)
repo.POST("/sign", session.MustPush, server.Sign)
repo.POST("/secrets", session.MustPush, api.PostSecret)
repo.DELETE("/secrets/:secret", session.MustPush, api.DeleteSecret)
// requires authenticated user
repo.POST("/encrypt", session.MustUser(), api.PostSecure)
repo.POST("/secrets", session.MustPush, server.PostSecret)
repo.DELETE("/secrets/:secret", session.MustPush, server.DeleteSecret)
// requires push permissions
repo.PATCH("", session.MustPush, api.PatchRepo)
repo.DELETE("", session.MustPush, api.DeleteRepo)
repo.PATCH("", session.MustPush, server.PatchRepo)
repo.DELETE("", session.MustPush, server.DeleteRepo)
repo.POST("/builds/:number", session.MustPush, api.PostBuild)
repo.DELETE("/builds/:number/:job", session.MustPush, api.DeleteBuild)
repo.POST("/builds/:number", session.MustPush, server.PostBuild)
repo.DELETE("/builds/:number/:job", session.MustPush, server.DeleteBuild)
}
}
badges := e.Group("/api/badges/:owner/:name")
{
badges.GET("/status.svg", web.GetBadge)
badges.GET("/cc.xml", web.GetCC)
badges.GET("/status.svg", server.GetBadge)
badges.GET("/cc.xml", server.GetCC)
}
e.POST("/hook", web.PostHook)
e.POST("/api/hook", web.PostHook)
e.POST("/hook", server.PostHook)
e.POST("/api/hook", server.PostHook)
stream := e.Group("/api/stream")
{
@ -139,57 +124,53 @@ func Load(middlewares ...gin.HandlerFunc) http.Handler {
stream.Use(session.SetPerm())
stream.Use(session.MustPull)
if os.Getenv("CANARY") == "true" {
stream.GET("/:owner/:name", web.GetRepoEvents2)
stream.GET("/:owner/:name/:build/:number", web.GetStream2)
} else {
stream.GET("/:owner/:name", web.GetRepoEvents)
stream.GET("/:owner/:name/:build/:number", web.GetStream)
}
}
bots := e.Group("/bots")
{
bots.Use(session.MustUser())
bots.POST("/slack", web.Slack)
bots.POST("/slack/:command", web.Slack)
stream.GET("/:owner/:name", server.GetRepoEvents)
stream.GET("/:owner/:name/:build/:number", server.GetStream)
}
auth := e.Group("/authorize")
{
auth.GET("", web.GetLogin)
auth.POST("", web.GetLogin)
auth.POST("/token", web.GetLoginToken)
auth.GET("", server.GetLogin)
auth.POST("", server.GetLogin)
auth.POST("/token", server.GetLoginToken)
}
queue := e.Group("/api/queue")
{
if os.Getenv("CANARY") == "true" {
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)
}
queue.Use(session.AuthorizeAgent)
queue.POST("/pull", server.Pull)
queue.POST("/pull/:os/:arch", server.Pull)
queue.POST("/wait/:id", server.Wait)
queue.POST("/stream/:id", server.Stream)
queue.POST("/status/:id", server.Update)
}
gitlab := e.Group("/gitlab/:owner/:name")
{
gitlab.Use(session.SetRepo())
gitlab.GET("/commits/:sha", web.GetCommit)
gitlab.GET("/pulls/:number", web.GetPullRequest)
// DELETE THESE
// gitlab := e.Group("/gitlab/:owner/:name")
// {
// gitlab.Use(session.SetRepo())
// gitlab.GET("/commits/:sha", GetCommit)
// gitlab.GET("/pulls/:number", GetPullRequest)
//
// redirects := gitlab.Group("/redirect")
// {
// redirects.GET("/commits/:sha", RedirectSha)
// redirects.GET("/pulls/:number", RedirectPullRequest)
// }
// }
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", Slack)
// bots.POST("/slack/:command", 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 {

View file

@ -1,4 +1,4 @@
package web
package server
import (
"fmt"

View file

@ -1,18 +1,13 @@
package api
package server
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/drone/drone/bus"
"github.com/drone/drone/engine"
"github.com/drone/drone/queue"
"github.com/drone/drone/remote"
"github.com/drone/drone/shared/httputil"
@ -24,21 +19,6 @@ import (
"github.com/drone/drone/router/middleware/session"
)
var (
droneYml = os.Getenv("BUILD_CONFIG_FILE")
droneSec string
)
func init() {
if droneYml == "" {
droneYml = ".drone.yml"
}
droneSec = fmt.Sprintf("%s.sec", strings.TrimSuffix(droneYml, filepath.Ext(droneYml)))
if os.Getenv("CANARY") == "true" {
droneSec = fmt.Sprintf("%s.sig", droneYml)
}
}
func GetBuilds(c *gin.Context) {
repo := session.Repo(c)
builds, err := store.GetBuildList(c, repo)
@ -135,7 +115,6 @@ func GetBuildLogs(c *gin.Context) {
}
func DeleteBuild(c *gin.Context) {
engine_ := engine.FromContext(c)
repo := session.Repo(c)
// parse the build number and job sequence number from
@ -155,17 +134,8 @@ func DeleteBuild(c *gin.Context) {
return
}
if os.Getenv("CANARY") == "true" {
bus.Publish(c, bus.NewEvent(bus.Cancelled, repo, build, job))
return
}
node, err := store.GetNode(c, job.NodeID)
if err != nil {
c.AbortWithError(404, err)
return
}
engine_.Cancel(build.ID, job.ID, node)
bus.Publish(c, bus.NewEvent(bus.Cancelled, repo, build, job))
c.String(204, "")
}
func PostBuild(c *gin.Context) {
@ -205,7 +175,8 @@ func PostBuild(c *gin.Context) {
}
// fetch the .drone.yml file from the database
raw, err := remote_.File(user, repo, build, droneYml)
config := ToConfig(c)
raw, err := remote_.File(user, repo, build, config.Yaml)
if err != nil {
log.Errorf("failure to get build config for %s. %s", repo.FullName, err)
c.AbortWithError(404, err)
@ -213,12 +184,11 @@ func PostBuild(c *gin.Context) {
}
// Fetch secrets file but don't exit on error as it's optional
sec, err := remote_.File(user, repo, build, droneSec)
sec, err := remote_.File(user, repo, build, config.Shasum)
if err != nil {
log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err)
}
key, _ := store.GetKey(c, repo)
netrc, err := remote_.Netrc(user, repo)
if err != nil {
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
@ -296,72 +266,42 @@ func PostBuild(c *gin.Context) {
log.Errorf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
}
// IMPORTANT. PLEASE READ
//
// The below code uses a feature flag to switch between the current
// build engine and the exerimental 0.5 build engine. This can be
// enabled using with the environment variable CANARY=true
var signed bool
var verified bool
if os.Getenv("CANARY") == "true" {
var signed bool
var verified bool
signature, err := jose.ParseSigned(string(sec))
signature, err := jose.ParseSigned(string(sec))
if err != nil {
log.Debugf("cannot parse .drone.yml.sig file. %s", err)
} else if len(sec) == 0 {
log.Debugf("cannot parse .drone.yml.sig file. empty file")
} else {
signed = true
output, err := signature.Verify([]byte(repo.Hash))
if err != nil {
log.Debugf("cannot parse .drone.yml.sig file. %s", err)
} else if len(sec) == 0 {
log.Debugf("cannot parse .drone.yml.sig file. empty file")
log.Debugf("cannot verify .drone.yml.sig file. %s", err)
} else if string(output) != string(raw) {
log.Debugf("cannot verify .drone.yml.sig file. no match. %q <> %q", string(output), string(raw))
} else {
signed = true
output, err := signature.Verify([]byte(repo.Hash))
if err != nil {
log.Debugf("cannot verify .drone.yml.sig file. %s", err)
} else if string(output) != string(raw) {
log.Debugf("cannot verify .drone.yml.sig file. no match. %q <> %q", string(output), string(raw))
} else {
verified = true
}
verified = true
}
log.Debugf(".drone.yml is signed=%v and verified=%v", signed, verified)
bus.Publish(c, bus.NewBuildEvent(bus.Enqueued, repo, build))
for _, job := range jobs {
queue.Publish(c, &queue.Work{
Signed: signed,
Verified: verified,
User: user,
Repo: repo,
Build: build,
BuildLast: last,
Job: job,
Netrc: netrc,
Yaml: string(raw),
Secrets: secs,
System: &model.System{Link: httputil.GetURL(c.Request)},
})
}
return // EXIT NOT TO AVOID THE 0.4 ENGINE CODE BELOW
}
engine_ := engine.FromContext(c)
go engine_.Schedule(c.Copy(), &engine.Task{
User: user,
Repo: repo,
Build: build,
BuildPrev: last,
Jobs: jobs,
Keys: key,
Netrc: netrc,
Config: string(raw),
Secret: string(sec),
System: &model.System{
Link: httputil.GetURL(c.Request),
Plugins: strings.Split(os.Getenv("PLUGIN_FILTER"), " "),
Globals: strings.Split(os.Getenv("PLUGIN_PARAMS"), " "),
Escalates: strings.Split(os.Getenv("ESCALATE_FILTER"), " "),
},
})
log.Debugf(".drone.yml is signed=%v and verified=%v", signed, verified)
bus.Publish(c, bus.NewBuildEvent(bus.Enqueued, repo, build))
for _, job := range jobs {
queue.Publish(c, &queue.Work{
Signed: signed,
Verified: verified,
User: user,
Repo: repo,
Build: build,
BuildLast: last,
Job: job,
Netrc: netrc,
Yaml: string(raw),
Secrets: secs,
System: &model.System{Link: httputil.GetURL(c.Request)},
})
}
}

View file

@ -1,4 +1,4 @@
package web
package server
import (
"fmt"

View file

@ -1,18 +1,14 @@
package web
package server
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/gin-gonic/gin"
"github.com/square/go-jose"
log "github.com/Sirupsen/logrus"
"github.com/drone/drone/bus"
"github.com/drone/drone/engine"
"github.com/drone/drone/model"
"github.com/drone/drone/queue"
"github.com/drone/drone/remote"
@ -22,21 +18,6 @@ import (
"github.com/drone/drone/yaml"
)
var (
droneYml = os.Getenv("BUILD_CONFIG_FILE")
droneSec string
)
func init() {
if droneYml == "" {
droneYml = ".drone.yml"
}
droneSec = fmt.Sprintf("%s.sec", strings.TrimSuffix(droneYml, filepath.Ext(droneYml)))
if os.Getenv("CANARY") == "true" {
droneSec = fmt.Sprintf("%s.sig", droneYml)
}
}
var skipRe = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`)
func PostHook(c *gin.Context) {
@ -141,13 +122,14 @@ func PostHook(c *gin.Context) {
}
// fetch the build file from the database
raw, err := remote_.File(user, repo, build, droneYml)
config := ToConfig(c)
raw, err := remote_.File(user, repo, build, config.Yaml)
if err != nil {
log.Errorf("failure to get build config for %s. %s", repo.FullName, err)
c.AbortWithError(404, err)
return
}
sec, err := remote_.File(user, repo, build, droneSec)
sec, err := remote_.File(user, repo, build, config.Shasum)
if err != nil {
log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err)
// NOTE we don't exit on failure. The sec file is optional
@ -168,8 +150,6 @@ func PostHook(c *gin.Context) {
return
}
key, _ := store.GetKey(c, repo)
// verify the branches can be built vs skipped
branches := yaml.ParseBranch(raw)
if !branches.Matches(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
@ -214,71 +194,43 @@ func PostHook(c *gin.Context) {
log.Errorf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
}
// IMPORTANT. PLEASE READ
//
// The below code uses a feature flag to switch between the current
// build engine and the exerimental 0.5 build engine. This can be
// enabled using with the environment variable CANARY=true
var signed bool
var verified bool
if os.Getenv("CANARY") == "true" {
var signed bool
var verified bool
signature, err := jose.ParseSigned(string(sec))
signature, err := jose.ParseSigned(string(sec))
if err != nil {
log.Debugf("cannot parse .drone.yml.sig file. %s", err)
} else if len(sec) == 0 {
log.Debugf("cannot parse .drone.yml.sig file. empty file")
} else {
signed = true
output, err := signature.Verify([]byte(repo.Hash))
if err != nil {
log.Debugf("cannot parse .drone.yml.sig file. %s", err)
} else if len(sec) == 0 {
log.Debugf("cannot parse .drone.yml.sig file. empty file")
log.Debugf("cannot verify .drone.yml.sig file. %s", err)
} else if string(output) != string(raw) {
log.Debugf("cannot verify .drone.yml.sig file. no match")
} else {
signed = true
output, err := signature.Verify([]byte(repo.Hash))
if err != nil {
log.Debugf("cannot verify .drone.yml.sig file. %s", err)
} else if string(output) != string(raw) {
log.Debugf("cannot verify .drone.yml.sig file. no match")
} else {
verified = true
}
verified = true
}
log.Debugf(".drone.yml is signed=%v and verified=%v", signed, verified)
bus.Publish(c, bus.NewBuildEvent(bus.Enqueued, repo, build))
for _, job := range jobs {
queue.Publish(c, &queue.Work{
Signed: signed,
Verified: verified,
User: user,
Repo: repo,
Build: build,
BuildLast: last,
Job: job,
Netrc: netrc,
Yaml: string(raw),
Secrets: secs,
System: &model.System{Link: httputil.GetURL(c.Request)},
})
}
return // EXIT NOT TO AVOID THE 0.4 ENGINE CODE BELOW
}
engine_ := engine.FromContext(c)
go engine_.Schedule(c.Copy(), &engine.Task{
User: user,
Repo: repo,
Build: build,
BuildPrev: last,
Jobs: jobs,
Keys: key,
Netrc: netrc,
Config: string(raw),
Secret: string(sec),
System: &model.System{
Link: httputil.GetURL(c.Request),
Plugins: strings.Split(os.Getenv("PLUGIN_FILTER"), " "),
Globals: strings.Split(os.Getenv("PLUGIN_PARAMS"), " "),
Escalates: strings.Split(os.Getenv("ESCALATE_FILTER"), " "),
},
})
log.Debugf(".drone.yml is signed=%v and verified=%v", signed, verified)
bus.Publish(c, bus.NewBuildEvent(bus.Enqueued, repo, build))
for _, job := range jobs {
queue.Publish(c, &queue.Work{
Signed: signed,
Verified: verified,
User: user,
Repo: repo,
Build: build,
BuildLast: last,
Job: job,
Netrc: netrc,
Yaml: string(raw),
Secrets: secs,
System: &model.System{Link: httputil.GetURL(c.Request)},
})
}
}

View file

@ -1,100 +1,106 @@
package web
package server
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
log "github.com/Sirupsen/logrus"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/shared/crypto"
"github.com/drone/drone/shared/httputil"
"github.com/drone/drone/shared/token"
"github.com/drone/drone/store"
"github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin"
)
func GetLogin(c *gin.Context) {
remote := remote.FromContext(c)
// when dealing with redirects we may need
// to adjust the content type. I cannot, however,
// remember why, so need to revisit this line.
// when dealing with redirects we may need to adjust the content type. I
// cannot, however, remember why, so need to revisit this line.
c.Writer.Header().Del("Content-Type")
tmpuser, open, err := remote.Login(c.Writer, c.Request)
tmpuser, err := remote.Login(c, c.Writer, c.Request)
if err != nil {
log.Errorf("cannot authenticate user. %s", err)
logrus.Errorf("cannot authenticate user. %s", err)
c.Redirect(303, "/login?error=oauth_error")
return
}
// this will happen when the user is redirected by
// the remote provide as part of the oauth dance.
// this will happen when the user is redirected by the remote provider as
// part of the authorization workflow.
if tmpuser == nil {
return
}
config := ToConfig(c)
// get the user from the database
u, err := store.GetUserLogin(c, tmpuser.Login)
if err != nil {
count, err := store.GetUserCount(c)
if err != nil {
log.Errorf("cannot register %s. %s", tmpuser.Login, err)
c.Redirect(303, "/login?error=internal_error")
return
}
// if self-registration is disabled we should
// return a notAuthorized error. the only exception
// is if no users exist yet in the system we'll proceed.
if !open && count != 0 {
log.Errorf("cannot register %s. registration closed", tmpuser.Login)
// if self-registration is disabled we should return a not authorized error
if !config.Open {
logrus.Errorf("cannot register %s. registration closed", tmpuser.Login)
c.Redirect(303, "/login?error=access_denied")
return
}
// create the user account
u = &model.User{}
u.Login = tmpuser.Login
u.Token = tmpuser.Token
u.Secret = tmpuser.Secret
u.Email = tmpuser.Email
u.Avatar = tmpuser.Avatar
u.Hash = crypto.Rand()
u = &model.User{
Login: tmpuser.Login,
Token: tmpuser.Token,
Secret: tmpuser.Secret,
Email: tmpuser.Email,
Avatar: tmpuser.Avatar,
Hash: crypto.Rand(),
}
// insert the user into the database
if err := store.CreateUser(c, u); err != nil {
log.Errorf("cannot insert %s. %s", u.Login, err)
logrus.Errorf("cannot insert %s. %s", u.Login, err)
c.Redirect(303, "/login?error=internal_error")
return
}
// if this is the first user, they
// should be an admin.
if count == 0 {
u.Admin = true
}
}
// update the user meta data and authorization
// data and cache in the datastore.
// update the user meta data and authorization data.
u.Token = tmpuser.Token
u.Secret = tmpuser.Secret
u.Email = tmpuser.Email
u.Avatar = tmpuser.Avatar
if err := store.UpdateUser(c, u); err != nil {
log.Errorf("cannot update %s. %s", u.Login, err)
logrus.Errorf("cannot update %s. %s", u.Login, err)
c.Redirect(303, "/login?error=internal_error")
return
}
if len(config.Orgs) != 0 {
teams, terr := remote.Teams(c, u)
if terr != nil {
logrus.Errorf("cannot verify team membership for %s. %s.", tmpuser.Login, terr)
c.Redirect(303, "/login?error=access_denied")
return
}
var member bool
for _, team := range teams {
if config.Orgs[team.Login] {
member = true
break
}
}
if !member {
logrus.Errorf("cannot verify team membership for %s. %s.", tmpuser.Login, terr)
c.Redirect(303, "/login?error=access_denied")
return
}
}
exp := time.Now().Add(time.Hour * 72).Unix()
token := token.New(token.SessToken, u.Login)
tokenstr, err := token.SignExpires(u.Hash, exp)
if err != nil {
log.Errorf("cannot create token for %s. %s", u.Login, err)
logrus.Errorf("cannot create token for %s. %s", u.Login, err)
c.Redirect(303, "/login?error=internal_error")
return
}
@ -109,15 +115,12 @@ func GetLogin(c *gin.Context) {
}
func GetLogout(c *gin.Context) {
httputil.DelCookie(c.Writer, c.Request, "user_sess")
httputil.DelCookie(c.Writer, c.Request, "user_last")
c.Redirect(303, "/login")
}
func GetLoginToken(c *gin.Context) {
remote := remote.FromContext(c)
in := &tokenPayload{}
err := c.Bind(in)
if err != nil {
@ -125,7 +128,7 @@ func GetLoginToken(c *gin.Context) {
return
}
login, err := remote.Auth(in.Access, in.Refresh)
login, err := remote.Auth(c, in.Access, in.Refresh)
if err != nil {
c.AbortWithError(http.StatusUnauthorized, err)
return
@ -156,3 +159,9 @@ type tokenPayload struct {
Refresh string `json:"refresh_token,omitempty"`
Expires int64 `json:"expires_in,omitempty"`
}
// ToConfig returns the config from the Context
func ToConfig(c *gin.Context) *model.Config {
v := c.MustGet("config")
return v.(*model.Config)
}

View file

@ -1,4 +1,4 @@
package web
package server
import (
"net/http"
@ -30,7 +30,7 @@ func ShowIndex(c *gin.Context) {
}
// filter to only show the currently active ones
activeRepos, err := store.GetRepoListOf(c,repos)
activeRepos, err := store.GetRepoListOf(c, repos)
if err != nil {
c.String(400, err.Error())
return
@ -83,26 +83,6 @@ func ShowUser(c *gin.Context) {
})
}
func ShowUsers(c *gin.Context) {
user := session.User(c)
if !user.Admin {
c.AbortWithStatus(http.StatusForbidden)
return
}
users, _ := store.GetUserList(c)
token, _ := token.New(
token.CsrfToken,
user.Login,
).Sign(user.Hash)
c.HTML(200, "users.html", gin.H{
"User": user,
"Users": users,
"Csrf": token,
})
}
func ShowRepo(c *gin.Context) {
user := session.User(c)
repo := session.Repo(c)
@ -136,7 +116,6 @@ func ShowRepoConf(c *gin.Context) {
user := session.User(c)
repo := session.Repo(c)
key, _ := store.GetKey(c, repo)
token, _ := token.New(
token.CsrfToken,
@ -146,7 +125,6 @@ func ShowRepoConf(c *gin.Context) {
c.HTML(200, "repo_config.html", gin.H{
"User": user,
"Repo": repo,
"Key": key,
"Csrf": token,
"Link": httputil.GetURL(c.Request),
})
@ -227,10 +205,3 @@ func ShowBuild(c *gin.Context) {
"Csrf": csrf,
})
}
func ShowNodes(c *gin.Context) {
user := session.User(c)
nodes, _ := store.GetNodeList(c)
token, _ := token.New(token.CsrfToken, user.Login).Sign(user.Hash)
c.HTML(http.StatusOK, "nodes.html", gin.H{"User": user, "Nodes": nodes, "Csrf": token})
}

View file

@ -1,4 +1,4 @@
package api
package server
import (
"fmt"

View file

@ -1,16 +1,12 @@
package api
package server
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"github.com/gin-gonic/gin"
"gopkg.in/yaml.v2"
"github.com/drone/drone/cache"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/shared/crypto"
@ -74,19 +70,9 @@ func PostRepo(c *gin.Context) {
sig,
)
// generate an RSA key and add to the repo
key, err := crypto.GeneratePrivateKey()
if err != nil {
c.String(500, err.Error())
return
}
keys := new(model.Key)
keys.Public = string(crypto.MarshalPublicKey(&key.PublicKey))
keys.Private = string(crypto.MarshalPrivateKey(key))
// activate the repository before we make any
// local changes to the database.
err = remote.Activate(user, r, keys, link)
err = remote.Activate(user, r, link)
if err != nil {
c.String(500, err.Error())
return
@ -98,12 +84,6 @@ func PostRepo(c *gin.Context) {
c.String(500, err.Error())
return
}
keys.RepoID = r.ID
err = store.CreateKey(c, keys)
if err != nil {
c.String(500, err.Error())
return
}
c.JSON(200, r)
}
@ -157,45 +137,6 @@ func GetRepo(c *gin.Context) {
c.JSON(http.StatusOK, session.Repo(c))
}
func GetRepoKey(c *gin.Context) {
repo := session.Repo(c)
keys, err := store.GetKey(c, repo)
if err != nil {
c.String(404, "Error fetching repository key")
} else {
c.String(http.StatusOK, keys.Public)
}
}
func PostRepoKey(c *gin.Context) {
repo := session.Repo(c)
keys, err := store.GetKey(c, repo)
if err != nil {
c.String(404, "Error fetching repository key")
return
}
body, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
c.String(500, "Error reading private key from body. %s", err)
return
}
pkey := crypto.UnmarshalPrivateKey(body)
if pkey == nil {
c.String(500, "Cannot unmarshal private key. Invalid format.")
return
}
keys.Public = string(crypto.MarshalPublicKey(&pkey.PublicKey))
keys.Private = string(crypto.MarshalPrivateKey(pkey))
err = store.UpdateKey(c, keys)
if err != nil {
c.String(500, "Error updating repository key")
return
}
c.String(201, keys.Public)
}
func DeleteRepo(c *gin.Context) {
remote := remote.FromContext(c)
repo := session.Repo(c)
@ -210,44 +151,3 @@ func DeleteRepo(c *gin.Context) {
remote.Deactivate(user, repo, httputil.GetURL(c.Request))
c.Writer.WriteHeader(http.StatusOK)
}
func PostSecure(c *gin.Context) {
repo := session.Repo(c)
in, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
// we found some strange characters included in
// the yaml file when entered into a browser textarea.
// these need to be removed
in = bytes.Replace(in, []byte{'\xA0'}, []byte{' '}, -1)
// make sure the Yaml is valid format to prevent
// a malformed value from being used in the build
err = yaml.Unmarshal(in, &yaml.MapSlice{})
if err != nil {
c.String(http.StatusBadRequest, err.Error())
return
}
key, err := store.GetKey(c, repo)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
// encrypts using go-jose
out, err := crypto.Encrypt(string(in), key.Private)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
c.String(http.StatusOK, out)
}
func PostReactivate(c *gin.Context) {
}

View file

@ -1,4 +1,4 @@
package api
package server
import (
"github.com/drone/drone/model"

View file

@ -1,4 +1,4 @@
package api
package server
import (
"io/ioutil"

View file

@ -1,4 +1,4 @@
package web
package server
import (
"strings"

View file

@ -1,4 +1,4 @@
package web
package server
import (
"bufio"
@ -19,14 +19,9 @@ import (
"github.com/manucorporat/sse"
)
// IMPORTANT. PLEASE READ
//
// This file containers experimental streaming features for the 0.5
// release. These can be enabled with the feature flag CANARY=true
// GetRepoEvents will upgrade the connection to a Websocket and will stream
// event updates to the browser.
func GetRepoEvents2(c *gin.Context) {
func GetRepoEvents(c *gin.Context) {
repo := session.Repo(c)
c.Writer.Header().Set("Content-Type", "text/event-stream")
@ -70,7 +65,7 @@ func GetRepoEvents2(c *gin.Context) {
})
}
func GetStream2(c *gin.Context) {
func GetStream(c *gin.Context) {
repo := session.Repo(c)
buildn, _ := strconv.Atoi(c.Param("build"))

View file

@ -11,6 +11,7 @@
// - application/json
//
// swagger:meta
package api
package swagger
//go:generate swagger generate spec -o swagger/files/swagger.json
//go:generate swagger generate spec -o files/swagger.json
//go:generate go-bindata -pkg swagger -o swagger_gen.go files/

85
server/swagger/swagger.go Normal file
View file

@ -0,0 +1,85 @@
package swagger
import (
"net/http"
"github.com/drone/drone/model"
)
// swagger:route GET /users/{login} user getUser
//
// Get the user with the matching login.
//
// Responses:
// 200: user
//
func userFind(w http.ResponseWriter, r *http.Request) {}
// swagger:route GET /user user getCurrentUser
//
// Get the currently authenticated user.
//
// Responses:
// 200: user
//
func userCurrent(w http.ResponseWriter, r *http.Request) {}
// swagger:route GET /users user getUserList
//
// Get the list of all registered users.
//
// Responses:
// 200: user
//
func userList(w http.ResponseWriter, r *http.Request) {}
// swagger:route GET /user/feed user getUserFeed
//
// Get the currently authenticated user's build feed.
//
// Responses:
// 200: feed
//
func userFeed(w http.ResponseWriter, r *http.Request) {}
// swagger:route DELETE /users/{login} user deleteUserLogin
//
// Delete the user with the matching login.
//
// Responses:
// 200: user
//
func userDelete(w http.ResponseWriter, r *http.Request) {}
// swagger:route GET /user/repos user getUserRepos
//
// Get the currently authenticated user's active repository list.
//
// Responses:
// 200: repos
//
func repoList(w http.ResponseWriter, r *http.Request) {}
// swagger:response user
type userResp struct {
// in: body
Body model.User
}
// swagger:response users
type usersResp struct {
// in: body
Body []model.User
}
// swagger:response feed
type feedResp struct {
// in: body
Body []model.Feed
}
// swagger:response repos
type reposResp struct {
// in: body
Body []model.Repo
}

View file

@ -1,4 +1,4 @@
package api
package server
import (
"net/http"
@ -6,31 +6,16 @@ import (
"github.com/gin-gonic/gin"
"github.com/drone/drone/cache"
"github.com/drone/drone/model"
"github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/shared/crypto"
"github.com/drone/drone/shared/token"
"github.com/drone/drone/store"
)
// swagger:route GET /user user getUser
//
// Get the currently authenticated user.
//
// Responses:
// 200: user
//
func GetSelf(c *gin.Context) {
c.JSON(200, session.User(c))
}
// swagger:route GET /user/feed user getUserFeed
//
// Get the currently authenticated user's build feed.
//
// Responses:
// 200: feed
//
func GetFeed(c *gin.Context) {
repos, err := cache.GetRepos(c, session.User(c))
if err != nil {
@ -46,13 +31,6 @@ func GetFeed(c *gin.Context) {
c.JSON(200, feed)
}
// swagger:route GET /user/repos user getUserRepos
//
// Get the currently authenticated user's active repository list.
//
// Responses:
// 200: repos
//
func GetRepos(c *gin.Context) {
repos, err := cache.GetRepos(c, session.User(c))
if err != nil {
@ -105,27 +83,3 @@ func DeleteToken(c *gin.Context) {
}
c.String(http.StatusOK, tokenstr)
}
// swagger:response user
type userResp struct {
// in: body
Body model.User
}
// swagger:response users
type usersResp struct {
// in: body
Body []model.User
}
// swagger:response feed
type feedResp struct {
// in: body
Body []model.Feed
}
// swagger:response repos
type reposResp struct {
// in: body
Body []model.Repo
}

View file

@ -1,4 +1,4 @@
package api
package server
import (
"net/http"
@ -10,13 +10,6 @@ import (
"github.com/drone/drone/store"
)
// swagger:route GET /users user getUserList
//
// Get the list of all registered users.
//
// Responses:
// 200: user
//
func GetUsers(c *gin.Context) {
users, err := store.GetUserList(c)
if err != nil {
@ -26,20 +19,13 @@ func GetUsers(c *gin.Context) {
}
}
// swagger:route GET /users/{login} user getUserLogin
//
// Get the user with the matching login.
//
// Responses:
// 200: user
//
func GetUser(c *gin.Context) {
user, err := store.GetUserLogin(c, c.Param("login"))
if err != nil {
c.String(404, "Cannot find user. %s", err)
} else {
c.JSON(200, user)
return
}
c.JSON(200, user)
}
func PatchUser(c *gin.Context) {
@ -74,31 +60,20 @@ func PostUser(c *gin.Context) {
c.String(http.StatusBadRequest, err.Error())
return
}
user := &model.User{}
user.Login = in.Login
user.Email = in.Email
user.Admin = in.Admin
user.Avatar = in.Avatar
user.Active = true
user.Hash = crypto.Rand()
err = store.CreateUser(c, user)
if err != nil {
user := &model.User{
Active: true,
Login: in.Login,
Email: in.Email,
Avatar: in.Avatar,
Hash: crypto.Rand(),
}
if err = store.CreateUser(c, user); err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
c.JSON(http.StatusOK, user)
}
// swagger:route DELETE /users/{login} user deleteUserLogin
//
// Delete the user with the matching login.
//
// Responses:
// 200: user
//
func DeleteUser(c *gin.Context) {
user, err := store.GetUserLogin(c, c.Param("login"))
if err != nil {
@ -107,7 +82,7 @@ func DeleteUser(c *gin.Context) {
}
if err = store.DeleteUser(c, user); err != nil {
c.String(500, "Error deleting user. %s", err)
} else {
c.String(200, "")
return
}
c.String(200, "")
}

View file

@ -1,97 +0,0 @@
package docker
import (
"errors"
log "github.com/Sirupsen/logrus"
"github.com/samalba/dockerclient"
)
var (
LogOpts = &dockerclient.LogOptions{
Stdout: true,
Stderr: true,
}
LogOptsTail = &dockerclient.LogOptions{
Follow: true,
Stdout: true,
Stderr: true,
}
)
// Run creates the docker container, pulling images if necessary, starts
// the container and blocks until the container exits, returning the exit
// information.
func Run(client dockerclient.Client, conf *dockerclient.ContainerConfig, name string) (*dockerclient.ContainerInfo, error) {
info, err := RunDaemon(client, conf, name)
if err != nil {
return nil, err
}
return Wait(client, info.Id)
}
// RunDaemon creates the docker container, pulling images if necessary, starts
// the container and returns the container information. It does not wait for
// the container to exit.
func RunDaemon(client dockerclient.Client, conf *dockerclient.ContainerConfig, name string) (*dockerclient.ContainerInfo, error) {
// attempts to create the container
id, err := client.CreateContainer(conf, name, nil)
if err != nil {
// and pull the image and re-create if that fails
err = client.PullImage(conf.Image, nil)
if err != nil {
return nil, err
}
id, err = client.CreateContainer(conf, name, nil)
if err != nil {
client.RemoveContainer(id, true, true)
return nil, err
}
}
// fetches the container information
info, err := client.InspectContainer(id)
if err != nil {
client.RemoveContainer(id, true, true)
return nil, err
}
// starts the container
err = client.StartContainer(id, &conf.HostConfig)
if err != nil {
client.RemoveContainer(id, true, true)
return nil, err
}
return info, err
}
// Wait blocks until the named container exits, returning the exit information.
func Wait(client dockerclient.Client, name string) (*dockerclient.ContainerInfo, error) {
defer func() {
client.StopContainer(name, 5)
client.KillContainer(name, "9")
}()
for attempts := 0; attempts < 5; attempts++ {
done := client.Wait(name)
<-done
info, err := client.InspectContainer(name)
if err != nil {
return nil, err
}
if !info.State.Running {
return info, nil
}
log.Debugf("attempting to resume waiting after %d attempts.\n", attempts)
}
return nil, errors.New("reached maximum wait attempts")
}

View file

@ -1,31 +0,0 @@
package datastore
import (
"github.com/drone/drone/model"
"github.com/russross/meddler"
)
func (db *datastore) GetKey(repo *model.Repo) (*model.Key, error) {
var key = new(model.Key)
var err = meddler.QueryRow(db, key, rebind(keyQuery), repo.ID)
return key, err
}
func (db *datastore) CreateKey(key *model.Key) error {
return meddler.Save(db, keyTable, key)
}
func (db *datastore) UpdateKey(key *model.Key) error {
return meddler.Save(db, keyTable, key)
}
func (db *datastore) DeleteKey(key *model.Key) error {
var _, err = db.Exec(rebind(keyDeleteStmt), key.ID)
return err
}
const keyTable = "keys"
const keyQuery = "SELECT * FROM `keys` WHERE key_repo_id=? LIMIT 1"
const keyDeleteStmt = "DELETE FROM `keys` WHERE key_id=?"

View file

@ -1,114 +0,0 @@
package datastore
import (
"testing"
"github.com/drone/drone/model"
"github.com/franela/goblin"
)
func TestKeys(t *testing.T) {
db := openTest()
defer db.Close()
s := From(db)
g := goblin.Goblin(t)
g.Describe("Keys", func() {
// before each test be sure to purge the package
// table data from the database.
g.BeforeEach(func() {
db.Exec(rebind("DELETE FROM `keys`"))
})
g.It("Should create a key", func() {
key := model.Key{
RepoID: 1,
Public: fakePublicKey,
Private: fakePrivateKey,
}
err := s.CreateKey(&key)
g.Assert(err == nil).IsTrue()
g.Assert(key.ID != 0).IsTrue()
})
g.It("Should update a key", func() {
key := model.Key{
RepoID: 1,
Public: fakePublicKey,
Private: fakePrivateKey,
}
err := s.CreateKey(&key)
g.Assert(err == nil).IsTrue()
g.Assert(key.ID != 0).IsTrue()
key.Private = ""
key.Public = ""
err1 := s.UpdateKey(&key)
getkey, err2 := s.GetKey(&model.Repo{ID: 1})
g.Assert(err1 == nil).IsTrue()
g.Assert(err2 == nil).IsTrue()
g.Assert(key.ID).Equal(getkey.ID)
g.Assert(key.Public).Equal(getkey.Public)
g.Assert(key.Private).Equal(getkey.Private)
})
g.It("Should get a key", func() {
key := model.Key{
RepoID: 1,
Public: fakePublicKey,
Private: fakePrivateKey,
}
err := s.CreateKey(&key)
g.Assert(err == nil).IsTrue()
g.Assert(key.ID != 0).IsTrue()
getkey, err := s.GetKey(&model.Repo{ID: 1})
g.Assert(err == nil).IsTrue()
g.Assert(key.ID).Equal(getkey.ID)
g.Assert(key.Public).Equal(getkey.Public)
g.Assert(key.Private).Equal(getkey.Private)
})
g.It("Should delete a key", func() {
key := model.Key{
RepoID: 1,
Public: fakePublicKey,
Private: fakePrivateKey,
}
err1 := s.CreateKey(&key)
err2 := s.DeleteKey(&key)
g.Assert(err1 == nil).IsTrue()
g.Assert(err2 == nil).IsTrue()
_, err := s.GetKey(&model.Repo{ID: 1})
g.Assert(err == nil).IsFalse()
})
})
}
var fakePublicKey = `
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0
FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/
3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQAB
-----END PUBLIC KEY-----
`
var fakePrivateKey = `
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp
wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5
1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh
3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2
pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX
GukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il
AkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF
L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k
X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl
U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ
37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0=
-----END RSA PRIVATE KEY-----
`

View file

@ -1,48 +0,0 @@
package datastore
import (
"github.com/drone/drone/model"
"github.com/russross/meddler"
)
func (db *datastore) GetNode(id int64) (*model.Node, error) {
var node = new(model.Node)
var err = meddler.Load(db, nodeTable, node, id)
return node, err
}
func (db *datastore) GetNodeList() ([]*model.Node, error) {
var nodes = []*model.Node{}
var err = meddler.QueryAll(db, &nodes, rebind(nodeListQuery))
return nodes, err
}
func (db *datastore) CreateNode(node *model.Node) error {
return meddler.Insert(db, nodeTable, node)
}
func (db *datastore) UpdateNode(node *model.Node) error {
return meddler.Update(db, nodeTable, node)
}
func (db *datastore) DeleteNode(node *model.Node) error {
var _, err = db.Exec(rebind(nodeDeleteStmt), node.ID)
return err
}
const nodeTable = "nodes"
const nodeListQuery = `
SELECT *
FROM nodes
ORDER BY node_addr
`
const nodeCountQuery = `
SELECT COUNT(*) FROM nodes
`
const nodeDeleteStmt = `
DELETE FROM nodes
WHERE node_id=?
`

View file

@ -1,101 +0,0 @@
package datastore
import (
"testing"
"github.com/drone/drone/model"
"github.com/franela/goblin"
)
func TestNodes(t *testing.T) {
db := openTest()
defer db.Close()
s := From(db)
g := goblin.Goblin(t)
g.Describe("Nodes", func() {
// before each test be sure to purge the package
// table data from the database.
g.BeforeEach(func() {
db.Exec("DELETE FROM nodes")
})
g.It("Should create a node", func() {
node := model.Node{
Addr: "unix:///var/run/docker/docker.sock",
Arch: "linux_amd64",
}
err := s.CreateNode(&node)
g.Assert(err == nil).IsTrue()
g.Assert(node.ID != 0).IsTrue()
})
g.It("Should update a node", func() {
node := model.Node{
Addr: "unix:///var/run/docker/docker.sock",
Arch: "linux_amd64",
}
err := s.CreateNode(&node)
g.Assert(err == nil).IsTrue()
g.Assert(node.ID != 0).IsTrue()
node.Addr = "unix:///var/run/docker.sock"
err1 := s.UpdateNode(&node)
getnode, err2 := s.GetNode(node.ID)
g.Assert(err1 == nil).IsTrue()
g.Assert(err2 == nil).IsTrue()
g.Assert(node.ID).Equal(getnode.ID)
g.Assert(node.Addr).Equal(getnode.Addr)
g.Assert(node.Arch).Equal(getnode.Arch)
})
g.It("Should get a node", func() {
node := model.Node{
Addr: "unix:///var/run/docker/docker.sock",
Arch: "linux_amd64",
}
err := s.CreateNode(&node)
g.Assert(err == nil).IsTrue()
g.Assert(node.ID != 0).IsTrue()
getnode, err := s.GetNode(node.ID)
g.Assert(err == nil).IsTrue()
g.Assert(node.ID).Equal(getnode.ID)
g.Assert(node.Addr).Equal(getnode.Addr)
g.Assert(node.Arch).Equal(getnode.Arch)
})
g.It("Should get a node list", func() {
node1 := model.Node{
Addr: "unix:///var/run/docker/docker.sock",
Arch: "linux_amd64",
}
node2 := model.Node{
Addr: "unix:///var/run/docker.sock",
Arch: "linux_386",
}
s.CreateNode(&node1)
s.CreateNode(&node2)
nodes, err := s.GetNodeList()
g.Assert(err == nil).IsTrue()
g.Assert(len(nodes)).Equal(2)
})
g.It("Should delete a node", func() {
node := model.Node{
Addr: "unix:///var/run/docker/docker.sock",
Arch: "linux_amd64",
}
err1 := s.CreateNode(&node)
err2 := s.DeleteNode(&node)
g.Assert(err1 == nil).IsTrue()
g.Assert(err2 == nil).IsTrue()
_, err := s.GetNode(node.ID)
g.Assert(err == nil).IsFalse()
})
})
}

View file

@ -58,7 +58,6 @@ func TestUsers(t *testing.T) {
Email: "foo@bar.com",
Avatar: "b9015b0857e16ac4d94a0ffd9a0b79c8",
Active: true,
Admin: true,
}
s.CreateUser(&user)
@ -71,7 +70,6 @@ func TestUsers(t *testing.T) {
g.Assert(user.Email).Equal(getuser.Email)
g.Assert(user.Avatar).Equal(getuser.Avatar)
g.Assert(user.Active).Equal(getuser.Active)
g.Assert(user.Admin).Equal(getuser.Admin)
})
g.It("Should Get a User By Login", func() {

View file

@ -54,18 +54,6 @@ type Store interface {
// DeleteRepo deletes a user repository.
DeleteRepo(*model.Repo) error
// GetKey gets a key by unique repository ID.
GetKey(*model.Repo) (*model.Key, error)
// CreateKey creates a new key.
CreateKey(*model.Key) error
// UpdateKey updates a user key.
UpdateKey(*model.Key) error
// DeleteKey deletes a user key.
DeleteKey(*model.Key) error
// GetSecretList gets a list of repository secrets
GetSecretList(*model.Repo) ([]*model.Secret, error)
@ -125,21 +113,6 @@ type Store interface {
// WriteLog writes the job logs to the datastore.
WriteLog(*model.Job, io.Reader) error
// GetNode gets a build node from the datastore.
GetNode(id int64) (*model.Node, error)
// GetNodeList gets a build node list from the datastore.
GetNodeList() ([]*model.Node, error)
// CreateNode add a new build node to the datastore.
CreateNode(*model.Node) error
// UpdateNode updates a build node in the datastore.
UpdateNode(*model.Node) error
// DeleteNode removes a build node from the datastore.
DeleteNode(*model.Node) error
}
// GetUser gets a user by unique ID.
@ -207,22 +180,6 @@ func DeleteRepo(c context.Context, repo *model.Repo) error {
return FromContext(c).DeleteRepo(repo)
}
func GetKey(c context.Context, repo *model.Repo) (*model.Key, error) {
return FromContext(c).GetKey(repo)
}
func CreateKey(c context.Context, key *model.Key) error {
return FromContext(c).CreateKey(key)
}
func UpdateKey(c context.Context, key *model.Key) error {
return FromContext(c).UpdateKey(key)
}
func DeleteKey(c context.Context, key *model.Key) error {
return FromContext(c).DeleteKey(key)
}
func GetSecretList(c context.Context, r *model.Repo) ([]*model.Secret, error) {
return FromContext(c).GetSecretList(r)
}
@ -343,23 +300,3 @@ func ReadLog(c context.Context, job *model.Job) (io.ReadCloser, error) {
func WriteLog(c context.Context, job *model.Job, r io.Reader) error {
return FromContext(c).WriteLog(job, r)
}
func GetNode(c context.Context, id int64) (*model.Node, error) {
return FromContext(c).GetNode(id)
}
func GetNodeList(c context.Context) ([]*model.Node, error) {
return FromContext(c).GetNodeList()
}
func CreateNode(c context.Context, node *model.Node) error {
return FromContext(c).CreateNode(node)
}
func UpdateNode(c context.Context, node *model.Node) error {
return FromContext(c).UpdateNode(node)
}
func DeleteNode(c context.Context, node *model.Node) error {
return FromContext(c).DeleteNode(node)
}

View file

@ -34,9 +34,6 @@ html
i.material-icons expand_more
div.dropdown-menu.dropdown-menu-right
a.dropdown-item[href="/settings/profile"] Profile
if User.Admin
a.dropdown-item[href="/settings/people"] People
a.dropdown-item[href="/settings/nodes"] Nodes
a.dropdown-item[href="/logout"] Logout

View file

@ -65,10 +65,6 @@ block content
else
input#trusted[type="checkbox"][hidden="hidden"]
label.switch[for="trusted"]
div.row
div.col-md-3 Public Key
div.col-md-9
pre #{Key.Public} #{Repo.Owner}-#{Repo.Name}@drone
div.row
div.col-md-12
div.alert.alert-danger
@ -78,4 +74,4 @@ block content
block append scripts
script
var view = new RepoConfigViewModel(#{Repo.FullName});
var view = new RepoConfigViewModel(#{Repo.FullName});

View file

@ -1,120 +0,0 @@
package web
import (
"io"
"strconv"
"github.com/gin-gonic/gin"
"github.com/docker/docker/pkg/stdcopy"
"github.com/drone/drone/engine"
"github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/store"
log "github.com/Sirupsen/logrus"
"github.com/manucorporat/sse"
)
// GetRepoEvents will upgrade the connection to a Websocket and will stream
// event updates to the browser.
func GetRepoEvents(c *gin.Context) {
engine_ := engine.FromContext(c)
repo := session.Repo(c)
c.Writer.Header().Set("Content-Type", "text/event-stream")
eventc := make(chan *engine.Event, 1)
engine_.Subscribe(eventc)
defer func() {
engine_.Unsubscribe(eventc)
close(eventc)
log.Infof("closed event stream")
}()
c.Stream(func(w io.Writer) bool {
select {
case event := <-eventc:
if event == nil {
log.Infof("nil event received")
return false
}
if event.Name == repo.FullName {
log.Debugf("received message %s", event.Name)
sse.Encode(w, sse.Event{
Event: "message",
Data: string(event.Msg),
})
}
case <-c.Writer.CloseNotify():
return false
}
return true
})
}
func GetStream(c *gin.Context) {
engine_ := engine.FromContext(c)
repo := session.Repo(c)
buildn, _ := strconv.Atoi(c.Param("build"))
jobn, _ := strconv.Atoi(c.Param("number"))
c.Writer.Header().Set("Content-Type", "text/event-stream")
build, err := store.GetBuildNumber(c, repo, buildn)
if err != nil {
log.Debugln("stream cannot get build number.", err)
c.AbortWithError(404, err)
return
}
job, err := store.GetJobNumber(c, build, jobn)
if err != nil {
log.Debugln("stream cannot get job number.", err)
c.AbortWithError(404, err)
return
}
node, err := store.GetNode(c, job.NodeID)
if err != nil {
log.Debugln("stream cannot get node.", err)
c.AbortWithError(404, err)
return
}
rc, err := engine_.Stream(build.ID, job.ID, node)
if err != nil {
c.AbortWithError(404, err)
return
}
defer func() {
rc.Close()
}()
go func() {
defer func() {
recover()
}()
<-c.Writer.CloseNotify()
rc.Close()
}()
rw := &StreamWriter{c.Writer, 0}
stdcopy.StdCopy(rw, rw, rc)
}
type StreamWriter struct {
writer gin.ResponseWriter
count int
}
func (w *StreamWriter) Write(data []byte) (int, error) {
var err = sse.Encode(w.writer, sse.Event{
Id: strconv.Itoa(w.count),
Event: "message",
Data: string(data),
})
w.writer.Flush()
w.count += len(data)
return len(data), err
}

View file

@ -1,70 +0,0 @@
package checksum
import (
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"fmt"
"io"
"strings"
)
// Check is a calculates and verifies a file checksum. This supports the sha1,
// sha256 and sha512 values.
func Check(in, checksum string) bool {
hash, size, _ := split(checksum)
// if a byte size is provided for the
// Yaml file it must match.
if size > 0 && int64(len(in)) != size {
return false
}
switch len(hash) {
case 64:
return sha256sum(in) == hash
case 128:
return sha512sum(in) == hash
case 40:
return sha1sum(in) == hash
}
return false
}
func sha1sum(in string) string {
h := sha1.New()
io.WriteString(h, in)
return fmt.Sprintf("%x", h.Sum(nil))
}
func sha256sum(in string) string {
h := sha256.New()
io.WriteString(h, in)
return fmt.Sprintf("%x", h.Sum(nil))
}
func sha512sum(in string) string {
h := sha512.New()
io.WriteString(h, in)
return fmt.Sprintf("%x", h.Sum(nil))
}
func split(in string) (string, int64, string) {
var hash string
var name string
var size int64
// the checksum might be split into multiple
// sections including the file size and name.
switch strings.Count(in, " ") {
case 1:
fmt.Sscanf(in, "%s %s", &hash, &name)
case 2:
fmt.Sscanf(in, "%s %d %s", &hash, &size, &name)
default:
hash = in
}
return hash, size, name
}

View file

@ -1,97 +0,0 @@
package checksum
import (
"testing"
"github.com/franela/goblin"
)
func TestParse(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Shasum", func() {
g.It("Should parse the shasum string", func() {
hash, _, _ := split("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15")
g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15")
})
g.It("Should parse a two-part shasum string", func() {
hash, _, name := split("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 .drone.yml")
g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15")
g.Assert(name).Equal(".drone.yml")
})
g.It("Should parse a three-part shasum string", func() {
hash, size, name := split("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 42 .drone.yml")
g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15")
g.Assert(name).Equal(".drone.yml")
g.Assert(size).Equal(int64(42))
})
g.It("Should calc a sha1 sum", func() {
hash := sha1sum("foo\n")
g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15")
})
g.It("Should calc a sha256 sum", func() {
hash := sha256sum("foo\n")
g.Assert(hash).Equal("b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c")
})
g.It("Should calc a sha512 sum", func() {
hash := sha512sum("foo\n")
g.Assert(hash).Equal("0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6")
})
g.It("Should calc a sha1 sum", func() {
hash := sha1sum("foo\n")
g.Assert(hash).Equal("f1d2d2f924e986ac86fdf7b36c94bcdf32beec15")
})
g.It("Should validate sha1 sum with file size", func() {
ok := Check("foo\n", "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 4 -")
g.Assert(ok).IsTrue()
})
g.It("Should validate sha256 sum with file size", func() {
ok := Check("foo\n", "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c 4 -")
g.Assert(ok).IsTrue()
})
g.It("Should validate sha512 sum with file size", func() {
ok := Check("foo\n", "0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6 4 -")
g.Assert(ok).IsTrue()
})
g.It("Should fail validation if incorrect sha1", func() {
ok := Check("bar\n", "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 4 -")
g.Assert(ok).IsFalse()
})
g.It("Should fail validation if incorrect sha256", func() {
ok := Check("bar\n", "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c 4 -")
g.Assert(ok).IsFalse()
})
g.It("Should fail validation if incorrect sha512", func() {
ok := Check("bar\n", "0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6 4 -")
g.Assert(ok).IsFalse()
})
g.It("Should return false if file size mismatch", func() {
ok := Check("foo\n", "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 12 -")
g.Assert(ok).IsFalse()
})
g.It("Should return false if invalid checksum string", func() {
ok := Check("foo\n", "f1d2d2f924e986ac86fdf7b36c94bcdf32beec15234")
g.Assert(ok).IsFalse()
})
g.It("Should return false if empty checksum", func() {
ok := Check("foo\n", "")
g.Assert(ok).IsFalse()
})
})
}