mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-26 02:10:31 +00:00
commit
f52feeceb1
93 changed files with 3577 additions and 4115 deletions
|
@ -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
|
|
@ -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
2
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
|
80
api/node.go
80
api/node.go
|
@ -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)
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package swagger
|
||||
|
||||
//go:generate go-bindata -pkg swagger -o swagger_gen.go files/
|
|
@ -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
481
drone/daemon.go
Normal 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
|
||||
|
||||
|
||||
---
|
||||
`
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
||||
---
|
||||
`
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
444
engine/engine.go
444
engine/engine.go
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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)
|
||||
}
|
115
engine/worker.go
115
engine/worker.go
|
@ -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
26
model/config.go
Normal 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
|
||||
}
|
|
@ -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
12
model/team.go
Normal 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"`
|
||||
}
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
337
remote/bitbucket/bitbucket_test.go
Normal file
337
remote/bitbucket/bitbucket_test.go
Normal 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
186
remote/bitbucket/convert.go
Normal 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
|
||||
}
|
195
remote/bitbucket/convert_test.go
Normal file
195
remote/bitbucket/convert_test.go
Normal 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")
|
||||
})
|
||||
})
|
||||
}
|
221
remote/bitbucket/fixtures/handler.go
Normal file
221
remote/bitbucket/fixtures/handler.go
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
164
remote/bitbucket/fixtures/hooks.go
Normal file
164
remote/bitbucket/fixtures/hooks.go
Normal 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"
|
||||
}
|
||||
}
|
||||
`
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
71
remote/bitbucket/parse.go
Normal 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
|
||||
}
|
104
remote/bitbucket/parse_test.go
Normal file
104
remote/bitbucket/parse_test.go
Normal 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")
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
|
|
109
remote/gogs/fixtures/handler.go
Normal file
109
remote/gogs/fixtures/handler.go
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
||||
`
|
|
@ -1,6 +1,6 @@
|
|||
package testdata
|
||||
package fixtures
|
||||
|
||||
var PushHook = `
|
||||
var HookPush = `
|
||||
{
|
||||
"ref": "refs/heads/master",
|
||||
"before": "4b2626259b5a97b6b4eab5e6cca66adb986b672b",
|
|
@ -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
183
remote/gogs/gogs_test.go
Normal 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",
|
||||
}
|
||||
)
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
}
|
||||
|
|
40
router/middleware/config.go
Normal file
40
router/middleware/config.go
Normal 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
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
}
|
||||
|
|
22
router/middleware/session/agent.go
Normal file
22
router/middleware/session/agent.go
Normal 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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
173
router/router.go
173
router/router.go
|
@ -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 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package web
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -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)},
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package web
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -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)},
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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})
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package api
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -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) {
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package api
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/drone/drone/model"
|
|
@ -1,4 +1,4 @@
|
|||
package api
|
||||
package server
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
|
@ -1,4 +1,4 @@
|
|||
package web
|
||||
package server
|
||||
|
||||
import (
|
||||
"strings"
|
|
@ -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"))
|
|
@ -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
85
server/swagger/swagger.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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, "")
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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=?"
|
|
@ -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-----
|
||||
`
|
|
@ -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=?
|
||||
`
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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});
|
||||
|
|
120
web/stream.go
120
web/stream.go
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue