mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-23 18:31:00 +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
|
password: $$DOCKER_PASS
|
||||||
email: $$DOCKER_EMAIL
|
email: $$DOCKER_EMAIL
|
||||||
repo: drone/drone
|
repo: drone/drone
|
||||||
tag: [ "latest", "0.4.2" ]
|
tag: [ "0.5.0" ]
|
||||||
when:
|
when:
|
||||||
repo: drone/drone
|
repo: drone/drone
|
||||||
branch: master
|
branch: master
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -12,7 +12,7 @@ drone/drone
|
||||||
.env
|
.env
|
||||||
temp/
|
temp/
|
||||||
|
|
||||||
api/swagger/files/*
|
server/swagger/files/*.json
|
||||||
|
|
||||||
# vendored repositories that we don't actually need
|
# vendored repositories that we don't actually need
|
||||||
# to vendor. so exclude them
|
# 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
|
#RUN echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf
|
||||||
|
|
||||||
ENTRYPOINT ["/drone"]
|
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"),
|
c.String("drone-token"),
|
||||||
)
|
)
|
||||||
|
|
||||||
tls, _ := dockerclient.TLSConfigFromCertPath(c.String("docker-cert-path"))
|
tls, err := dockerclient.TLSConfigFromCertPath(c.String("docker-cert-path"))
|
||||||
if c.Bool("docker-host") {
|
if err == nil {
|
||||||
tls.InsecureSkipVerify = true
|
tls.InsecureSkipVerify = c.Bool("docker-tls-verify")
|
||||||
}
|
}
|
||||||
docker, err := dockerclient.NewDockerClient(c.String("docker-host"), tls)
|
docker, err := dockerclient.NewDockerClient(c.String("docker-host"), tls)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
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"
|
"os"
|
||||||
|
|
||||||
"github.com/drone/drone/drone/agent"
|
"github.com/drone/drone/drone/agent"
|
||||||
"github.com/drone/drone/drone/server"
|
|
||||||
"github.com/drone/drone/version"
|
"github.com/drone/drone/version"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
|
@ -12,7 +11,7 @@ import (
|
||||||
_ "github.com/joho/godotenv/autoload"
|
_ "github.com/joho/godotenv/autoload"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main2() {
|
func main() {
|
||||||
envflag.Parse()
|
envflag.Parse()
|
||||||
|
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
|
@ -35,7 +34,7 @@ func main2() {
|
||||||
}
|
}
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
agent.AgentCmd,
|
agent.AgentCmd,
|
||||||
server.ServeCmd,
|
DaemonCmd,
|
||||||
SignCmd,
|
SignCmd,
|
||||||
SecretCmd,
|
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"`
|
Avatar string `json:"avatar_url" meddler:"user_avatar"`
|
||||||
|
|
||||||
// Activate indicates the user is active in the system.
|
// 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 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 is a unique token used to sign tokens.
|
||||||
Hash string `json:"-" meddler:"user_hash"`
|
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
|
package bitbucket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/remote"
|
||||||
|
"github.com/drone/drone/remote/bitbucket/internal"
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
"golang.org/x/oauth2/bitbucket"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
Client string
|
||||||
Secret string
|
Secret string
|
||||||
Orgs []string
|
|
||||||
Open bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load(config string) *Bitbucket {
|
// New returns a new remote Configuration for integrating with the Bitbucket
|
||||||
|
// repository hosting service at https://bitbucket.org
|
||||||
// parse the remote DSN configuration string
|
func New(client, secret string) remote.Remote {
|
||||||
url_, err := url.Parse(config)
|
return &config{
|
||||||
if err != nil {
|
API: DefaultAPI,
|
||||||
log.Fatalln("unable to parse remote dsn. %s", err)
|
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
|
// Login authenticates an account with Bitbucket using the oauth2 protocol. The
|
||||||
// remote user details.
|
// Bitbucket account details are returned when the user is successfully authenticated.
|
||||||
func (bb *Bitbucket) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
|
func (c *config) Login(w http.ResponseWriter, r *http.Request) (*model.User, error) {
|
||||||
|
redirect := httputil.GetURL(r)
|
||||||
|
config := c.newConfig(redirect)
|
||||||
|
|
||||||
config := &oauth2.Config{
|
code := r.FormValue("code")
|
||||||
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")
|
|
||||||
if len(code) == 0 {
|
if len(code) == 0 {
|
||||||
http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther)
|
http.Redirect(w, r, config.AuthCodeURL("drone"), http.StatusSeeOther)
|
||||||
return nil, false, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var token, err = config.Exchange(oauth2.NoContext, code)
|
token, err := config.Exchange(oauth2.NoContext, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, fmt.Errorf("Error exchanging token. %s", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client := NewClient(config.Client(oauth2.NoContext, token))
|
client := internal.NewClient(c.API, config.Client(oauth2.NoContext, token))
|
||||||
curr, err := client.FindCurrent()
|
curr, err := client.FindCurrent()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return convertUser(curr, token), nil
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth authenticates the session and returns the remote user
|
// Auth uses the Bitbucket oauth2 access token and refresh token to authenticate
|
||||||
// login for the given token and secret
|
// a session and return the Bitbucket account login.
|
||||||
func (bb *Bitbucket) Auth(token, secret string) (string, error) {
|
func (c *config) Auth(token, secret string) (string, error) {
|
||||||
token_ := oauth2.Token{AccessToken: token, RefreshToken: secret}
|
client := c.newClientToken(token, secret)
|
||||||
client := NewClientToken(bb.Client, bb.Secret, &token_)
|
|
||||||
|
|
||||||
user, err := client.FindCurrent()
|
user, err := client.FindCurrent()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -135,84 +73,83 @@ func (bb *Bitbucket) Auth(token, secret string) (string, error) {
|
||||||
return user.Login, nil
|
return user.Login, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh refreshes an oauth token and expiration for the given
|
// Refresh refreshes the Bitbucket oauth2 access token. If the token is
|
||||||
// user. It returns true if the token was refreshed, false if the
|
// refreshed the user is updated and a true value is returned.
|
||||||
// token was not refreshed, and error if it failed to refersh.
|
func (c *config) Refresh(user *model.User) (bool, error) {
|
||||||
func (bb *Bitbucket) Refresh(user *model.User) (bool, error) {
|
config := c.newConfig("")
|
||||||
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.
|
|
||||||
source := config.TokenSource(
|
source := config.TokenSource(
|
||||||
oauth2.NoContext, &oauth2.Token{RefreshToken: user.Secret})
|
oauth2.NoContext, &oauth2.Token{RefreshToken: user.Secret})
|
||||||
|
|
||||||
// requesting the token automatically refreshes and
|
|
||||||
// returns a new access token.
|
|
||||||
token, err := source.Token()
|
token, err := source.Token()
|
||||||
if err != nil || len(token.AccessToken) == 0 {
|
if err != nil || len(token.AccessToken) == 0 {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the user to include tne new access token
|
|
||||||
user.Token = token.AccessToken
|
user.Token = token.AccessToken
|
||||||
user.Secret = token.RefreshToken
|
user.Secret = token.RefreshToken
|
||||||
user.Expiry = token.Expiry.UTC().Unix()
|
user.Expiry = token.Expiry.UTC().Unix()
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repo fetches the named repository from the remote system.
|
// Teams returns a list of all team membership for the Bitbucket account.
|
||||||
func (bb *Bitbucket) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
func (c *config) Teams(u *model.User) ([]*model.Team, error) {
|
||||||
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
|
opts := &internal.ListTeamOpts{
|
||||||
client := NewClientToken(bb.Client, bb.Secret, &token)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return convertRepo(repo), nil
|
return convertRepo(repo), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repos fetches a list of repos from the remote system.
|
// Repos returns a list of all repositories for Bitbucket account, including
|
||||||
func (bb *Bitbucket) Repos(u *model.User) ([]*model.RepoLite, error) {
|
// organization repositories.
|
||||||
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
|
func (c *config) Repos(u *model.User) ([]*model.RepoLite, error) {
|
||||||
client := NewClientToken(bb.Client, bb.Secret, &token)
|
client := c.newClient(u)
|
||||||
var repos []*model.RepoLite
|
|
||||||
|
|
||||||
// gets a list of all accounts to query, including the
|
var all []*model.RepoLite
|
||||||
// user's account and all team accounts.
|
|
||||||
logins := []string{u.Login}
|
accounts := []string{u.Login}
|
||||||
resp, err := client.ListTeams(&ListTeamOpts{PageLen: 100, Role: "member"})
|
resp, err := client.ListTeams(&internal.ListTeamOpts{
|
||||||
|
PageLen: 100,
|
||||||
|
Role: "member",
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return repos, err
|
return all, err
|
||||||
}
|
}
|
||||||
for _, team := range resp.Values {
|
for _, team := range resp.Values {
|
||||||
logins = append(logins, team.Login)
|
accounts = append(accounts, team.Login)
|
||||||
}
|
}
|
||||||
|
|
||||||
// for each account, get the list of repos
|
for _, account := range accounts {
|
||||||
for _, login := range logins {
|
repos, err := client.ListReposAll(account)
|
||||||
repos_, err := client.ListReposAll(login)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return repos, err
|
return all, err
|
||||||
}
|
}
|
||||||
for _, repo := range repos_ {
|
for _, repo := range repos {
|
||||||
repos = append(repos, convertRepoLite(repo))
|
all = append(all, convertRepoLite(repo))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return all, nil
|
||||||
return repos, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perm fetches the named repository permissions from
|
// Perm returns the user permissions for the named repository. Because Bitbucket
|
||||||
// the remote system for the specified user.
|
// does not have an endpoint to access user permissions, we attempt to fetch
|
||||||
func (bb *Bitbucket) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
// the repository hook list, which is restricted to administrators to calculate
|
||||||
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
|
// administrative access to a repository.
|
||||||
client := NewClientToken(bb.Client, bb.Secret, &token)
|
func (c *config) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
||||||
|
client := c.newClient(u)
|
||||||
|
|
||||||
perms := new(model.Perm)
|
perms := new(model.Perm)
|
||||||
_, err := client.FindRepo(owner, name)
|
_, 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
|
return perms, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we've gotten this far we know that the user at
|
_, err = client.ListHooks(owner, name, &internal.ListOpts{})
|
||||||
// least has read access to the repository.
|
|
||||||
perms.Pull = true
|
|
||||||
|
|
||||||
// if the user has access to the repository hooks we
|
|
||||||
// can deduce that the user has push and admin access.
|
|
||||||
_, err = client.ListHooks(owner, name, &ListOpts{})
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
perms.Push = true
|
perms.Push = true
|
||||||
perms.Admin = true
|
perms.Admin = true
|
||||||
}
|
}
|
||||||
|
perms.Pull = true
|
||||||
return perms, nil
|
return perms, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// File fetches a file from the remote repository and returns in string format.
|
// File fetches the file from the Bitbucket repository and returns its contents.
|
||||||
func (bb *Bitbucket) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
|
func (c *config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
|
||||||
client := NewClientToken(
|
config, err := c.newClient(u).FindSource(r.Owner, r.Name, b.Commit, f)
|
||||||
bb.Client,
|
|
||||||
bb.Secret,
|
|
||||||
&oauth2.Token{
|
|
||||||
AccessToken: u.Token,
|
|
||||||
RefreshToken: u.Secret,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
config, err := client.FindSource(r.Owner, r.Name, b.Commit, f)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return []byte(config.Data), err
|
return []byte(config.Data), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status sends the commit status to the remote system.
|
// Status creates a build status for the Bitbucket commit.
|
||||||
// An example would be the GitHub pull request status.
|
func (c *config) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||||
func (bb *Bitbucket) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
status := internal.BuildStatus{
|
||||||
client := NewClientToken(
|
State: convertStatus(b.Status),
|
||||||
bb.Client,
|
Desc: convertDesc(b.Status),
|
||||||
bb.Secret,
|
|
||||||
&oauth2.Token{
|
|
||||||
AccessToken: u.Token,
|
|
||||||
RefreshToken: u.Secret,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
status := getStatus(b.Status)
|
|
||||||
desc := getDesc(b.Status)
|
|
||||||
|
|
||||||
data := BuildStatus{
|
|
||||||
State: status,
|
|
||||||
Key: "Drone",
|
Key: "Drone",
|
||||||
Url: link,
|
Url: link,
|
||||||
Desc: desc,
|
|
||||||
}
|
}
|
||||||
|
return c.newClient(u).CreateStatus(r.Owner, r.Name, b.Commit, &status)
|
||||||
err := client.CreateStatus(r.Owner, r.Name, b.Commit, &data)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Netrc returns a .netrc file that can be used to clone
|
// Activate activates the repository by registering repository push hooks with
|
||||||
// private repositories from a remote system.
|
// the Bitbucket repository. Prior to registering hook, previously created hooks
|
||||||
func (bb *Bitbucket) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
// 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{
|
return &model.Netrc{
|
||||||
Machine: "bitbucket.org",
|
Machine: "bitbucket.org",
|
||||||
Login: "x-token-auth",
|
Login: "x-token-auth",
|
||||||
|
@ -290,226 +230,54 @@ func (bb *Bitbucket) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate activates a repository by creating the post-commit hook and
|
// Hook parses the incoming Bitbucket hook and returns the Repository and
|
||||||
// adding the SSH deploy key, if applicable.
|
// Build details. If the hook is unsupported nil values are returned.
|
||||||
func (bb *Bitbucket) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
|
func (c *config) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
client := NewClientToken(
|
return parseHook(r)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deactivate removes a repository by removing all the post-commit hooks
|
// helper function to return the bitbucket oauth2 client
|
||||||
// which are equal to link and removing the SSH deploy key.
|
func (c *config) newClient(u *model.User) *internal.Client {
|
||||||
func (bb *Bitbucket) Deactivate(u *model.User, r *model.Repo, link string) error {
|
return c.newClientToken(u.Token, u.Secret)
|
||||||
client := NewClientToken(
|
}
|
||||||
bb.Client,
|
|
||||||
bb.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{
|
&oauth2.Token{
|
||||||
AccessToken: u.Token,
|
AccessToken: token,
|
||||||
RefreshToken: u.Secret,
|
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 {
|
if err != nil {
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
for _, hook := range hooks {
|
||||||
// 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)
|
hookurl, err := url.Parse(hook.Url)
|
||||||
if err != nil {
|
if err == nil && hookurl.Host == link.Host {
|
||||||
return err
|
return hook
|
||||||
}
|
|
||||||
if hookurl.Host == linkurl.Host {
|
|
||||||
client.DeleteHook(r.Owner, r.Name, hook.Uuid)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -20,8 +20,6 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
base = "https://api.bitbucket.org"
|
|
||||||
|
|
||||||
pathUser = "%s/2.0/user/"
|
pathUser = "%s/2.0/user/"
|
||||||
pathEmails = "%s/2.0/user/emails"
|
pathEmails = "%s/2.0/user/emails"
|
||||||
pathTeams = "%s/2.0/teams/?%s"
|
pathTeams = "%s/2.0/teams/?%s"
|
||||||
|
@ -35,52 +33,53 @@ const (
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
*http.Client
|
*http.Client
|
||||||
|
base string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(client *http.Client) *Client {
|
func NewClient(url string, client *http.Client) *Client {
|
||||||
return &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{
|
config := &oauth2.Config{
|
||||||
ClientID: client,
|
ClientID: client,
|
||||||
ClientSecret: secret,
|
ClientSecret: secret,
|
||||||
Endpoint: bitbucket.Endpoint,
|
Endpoint: bitbucket.Endpoint,
|
||||||
}
|
}
|
||||||
return NewClient(config.Client(oauth2.NoContext, token))
|
return NewClient(url, config.Client(oauth2.NoContext, token))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) FindCurrent() (*Account, error) {
|
func (c *Client) FindCurrent() (*Account, error) {
|
||||||
out := new(Account)
|
out := new(Account)
|
||||||
uri := fmt.Sprintf(pathUser, base)
|
uri := fmt.Sprintf(pathUser, c.base)
|
||||||
err := c.do(uri, get, nil, out)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ListEmail() (*EmailResp, error) {
|
func (c *Client) ListEmail() (*EmailResp, error) {
|
||||||
out := new(EmailResp)
|
out := new(EmailResp)
|
||||||
uri := fmt.Sprintf(pathEmails, base)
|
uri := fmt.Sprintf(pathEmails, c.base)
|
||||||
err := c.do(uri, get, nil, out)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ListTeams(opts *ListTeamOpts) (*AccountResp, error) {
|
func (c *Client) ListTeams(opts *ListTeamOpts) (*AccountResp, error) {
|
||||||
out := new(AccountResp)
|
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)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) FindRepo(owner, name string) (*Repo, error) {
|
func (c *Client) FindRepo(owner, name string) (*Repo, error) {
|
||||||
out := new(Repo)
|
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)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ListRepos(account string, opts *ListOpts) (*RepoResp, error) {
|
func (c *Client) ListRepos(account string, opts *ListOpts) (*RepoResp, error) {
|
||||||
out := new(RepoResp)
|
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)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
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) {
|
func (c *Client) FindHook(owner, name, id string) (*Hook, error) {
|
||||||
out := new(Hook)
|
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)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ListHooks(owner, name string, opts *ListOpts) (*HookResp, error) {
|
func (c *Client) ListHooks(owner, name string, opts *ListOpts) (*HookResp, error) {
|
||||||
out := new(HookResp)
|
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)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) CreateHook(owner, name string, hook *Hook) error {
|
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)
|
return c.do(uri, post, hook, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) DeleteHook(owner, name, id string) error {
|
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)
|
return c.do(uri, del, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) FindSource(owner, name, revision, path string) (*Source, error) {
|
func (c *Client) FindSource(owner, name, revision, path string) (*Source, error) {
|
||||||
out := new(Source)
|
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)
|
err := c.do(uri, get, nil, out)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) CreateStatus(owner, name, revision string, status *BuildStatus) error {
|
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)
|
return c.do(uri, post, status, nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package bitbucket
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -100,27 +100,29 @@ type Source struct {
|
||||||
Size int64 `json:"size"`
|
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 {
|
type PushHook struct {
|
||||||
Actor Account `json:"actor"`
|
Actor Account `json:"actor"`
|
||||||
Repo Repo `json:"repository"`
|
Repo Repo `json:"repository"`
|
||||||
Push struct {
|
Push struct {
|
||||||
Changes []struct {
|
Changes []Change `json:"changes"`
|
||||||
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"`
|
|
||||||
} `json:"push"`
|
} `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
|
package bitbucketserver
|
||||||
|
|
||||||
// Requires the following to be set
|
// WARNING! This is an work-in-progress patch and does not yet conform to the coding,
|
||||||
// REMOTE_DRIVER=bitbucketserver
|
// quality or security standards expected of this project. Please use with caution.
|
||||||
// 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
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/drone/drone/model"
|
|
||||||
"github.com/mrjones/oauth"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
|
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
|
URL string
|
||||||
ConsumerKey string
|
ConsumerKey string
|
||||||
GitUserName string
|
GitUserName string
|
||||||
GitPassword string
|
GitPassword string
|
||||||
ConsumerRSA string
|
ConsumerRSA string
|
||||||
Open bool
|
PrivateKey *rsa.PrivateKey
|
||||||
Consumer oauth.Consumer
|
Consumer oauth.Consumer
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load(config string) *BitbucketServer {
|
func (c *client) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
|
||||||
|
requestToken, url, err := c.Consumer.GetRequestTokenAndUrl("oob")
|
||||||
url_, err := url.Parse(config)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("unable to parse remote dsn. %s", err)
|
return nil, 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var code = req.FormValue("oauth_verifier")
|
var code = req.FormValue("oauth_verifier")
|
||||||
if len(code) == 0 {
|
if len(code) == 0 {
|
||||||
log.Info("redirecting to %s", url)
|
|
||||||
http.Redirect(res, req, url, http.StatusSeeOther)
|
http.Redirect(res, req, url, http.StatusSeeOther)
|
||||||
return nil, false, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var request_oauth_token = req.FormValue("oauth_token")
|
requestToken.Token = req.FormValue("oauth_token")
|
||||||
requestToken.Token = request_oauth_token
|
accessToken, err := c.Consumer.AuthorizeToken(requestToken, code)
|
||||||
accessToken, err := bs.Consumer.AuthorizeToken(requestToken, code)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := bs.Consumer.MakeHttpClient(accessToken)
|
client, err := c.Consumer.MakeHttpClient(accessToken)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
log.Error(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
bits, err := ioutil.ReadAll(response.Body)
|
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)
|
contents, err := ioutil.ReadAll(response1.Body)
|
||||||
defer response1.Body.Close()
|
defer response1.Body.Close()
|
||||||
var mUser User
|
var mUser User // TODO prefixing with m* is not a common convention in Go
|
||||||
json.Unmarshal(contents, &mUser)
|
json.Unmarshal(contents, &mUser) // TODO should not ignore error
|
||||||
|
|
||||||
user := model.User{}
|
return &model.User{
|
||||||
user.Login = userName
|
Login: login,
|
||||||
user.Email = mUser.EmailAddress
|
Email: mUser.EmailAddress,
|
||||||
user.Token = accessToken.Token
|
Token: accessToken.Token,
|
||||||
|
Avatar: avatarLink(mUser.EmailAddress),
|
||||||
user.Avatar = avatarLink(mUser.EmailAddress)
|
}, nil
|
||||||
|
|
||||||
return &user, bs.Open, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *BitbucketServer) Auth(token, secret string) (string, error) {
|
// Auth is not supported by the Stash driver.
|
||||||
log.Info("Staring to auth for bitbucketServer. %s", token)
|
func (*client) Auth(token, secret string) (string, error) {
|
||||||
if len(token) == 0 {
|
return "", fmt.Errorf("Not Implemented")
|
||||||
return "", fmt.Errorf("Hasn't logged in yet")
|
|
||||||
}
|
|
||||||
return token, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *BitbucketServer) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
// Teams is not supported by the Stash driver.
|
||||||
log.Info("Staring repo for bitbucketServer with user " + u.Login + " " + owner + " " + name)
|
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)
|
response, err := client.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
@ -145,40 +154,36 @@ func (bs *BitbucketServer) Repo(u *model.User, owner, name string) (*model.Repo,
|
||||||
bsRepo := BSRepo{}
|
bsRepo := BSRepo{}
|
||||||
json.Unmarshal(contents, &bsRepo)
|
json.Unmarshal(contents, &bsRepo)
|
||||||
|
|
||||||
cloneLink := ""
|
repo := &model.Repo{
|
||||||
repoLink := ""
|
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 {
|
for _, item := range bsRepo.Links.Clone {
|
||||||
if item.Name == "http" {
|
if item.Name == "http" {
|
||||||
cloneLink = item.Href
|
repo.Clone = item.Href
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, item := range bsRepo.Links.Self {
|
for _, item := range bsRepo.Links.Self {
|
||||||
if item.Href != "" {
|
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
|
return repo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *BitbucketServer) Repos(u *model.User) ([]*model.RepoLite, error) {
|
func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) {
|
||||||
log.Info("Staring repos for bitbucketServer " + u.Login)
|
|
||||||
var repos = []*model.RepoLite{}
|
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 {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -198,10 +203,8 @@ func (bs *BitbucketServer) Repos(u *model.User) ([]*model.RepoLite, error) {
|
||||||
return repos, nil
|
return repos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *BitbucketServer) Perm(u *model.User, owner, repo string) (*model.Perm, error) {
|
func (c *client) Perm(u *model.User, owner, repo string) (*model.Perm, error) {
|
||||||
|
// TODO need to fetch real permissions here
|
||||||
//TODO: find the real permissions
|
|
||||||
log.Info("Staring perm for bitbucketServer")
|
|
||||||
perms := new(model.Perm)
|
perms := new(model.Perm)
|
||||||
perms.Pull = true
|
perms.Pull = true
|
||||||
perms.Admin = true
|
perms.Admin = true
|
||||||
|
@ -209,11 +212,11 @@ func (bs *BitbucketServer) Perm(u *model.User, owner, repo string) (*model.Perm,
|
||||||
return perms, nil
|
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))
|
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)
|
client := NewClientWithToken(&c.Consumer, u.Token)
|
||||||
fileURL := fmt.Sprintf("%s/projects/%s/repos/%s/browse/%s?raw", bs.URL, r.Owner, r.Name, f)
|
fileURL := fmt.Sprintf("%s/projects/%s/repos/%s/browse/%s?raw", c.URL, r.Owner, r.Name, f)
|
||||||
log.Info(fileURL)
|
log.Info(fileURL)
|
||||||
response, err := client.Get(fileURL)
|
response, err := client.Get(fileURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -231,28 +234,26 @@ func (bs *BitbucketServer) File(u *model.User, r *model.Repo, b *model.Build, f
|
||||||
return responseBytes, nil
|
return responseBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *BitbucketServer) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
// Status is not supported by the Gogs driver.
|
||||||
log.Info("Staring status for bitbucketServer")
|
func (*client) Status(*model.User, *model.Repo, *model.Build, string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *BitbucketServer) Netrc(user *model.User, r *model.Repo) (*model.Netrc, error) {
|
func (c *client) Netrc(user *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||||
log.Info("Starting the Netrc lookup")
|
u, err := url.Parse(c.URL) // TODO strip port from url
|
||||||
u, err := url.Parse(bs.URL)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &model.Netrc{
|
return &model.Netrc{
|
||||||
Machine: u.Host,
|
Machine: u.Host,
|
||||||
Login: bs.GitUserName,
|
Login: c.GitUserName,
|
||||||
Password: bs.GitPassword,
|
Password: c.GitPassword,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *BitbucketServer) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
|
func (c *client) Activate(u *model.User, r *model.Repo, 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(&c.Consumer, u.Token)
|
||||||
client := NewClientWithToken(&bs.Consumer, u.Token)
|
hook, err := c.CreateHook(client, r.Owner, r.Name, "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook", link)
|
||||||
hook, err := bs.CreateHook(client, r.Owner, r.Name, "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook", link)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -260,68 +261,56 @@ func (bs *BitbucketServer) Activate(u *model.User, r *model.Repo, k *model.Key,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *BitbucketServer) Deactivate(u *model.User, r *model.Repo, link string) error {
|
func (c *client) 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(&c.Consumer, u.Token)
|
||||||
client := NewClientWithToken(&bs.Consumer, u.Token)
|
err := c.DeleteHook(client, r.Owner, r.Name, "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook", link)
|
||||||
err := bs.DeleteHook(client, r.Owner, r.Name, "com.atlassian.stash.plugin.stash-web-post-receive-hooks-plugin:postReceiveHook", link)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bs *BitbucketServer) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
log.Info("Staring hook for bitbucketServer")
|
hook := new(postHook)
|
||||||
defer r.Body.Close()
|
if err := json.NewDecoder(r.Body).Decode(hook); err != nil {
|
||||||
contents, err := ioutil.ReadAll(r.Body)
|
return nil, nil, err
|
||||||
if err != nil {
|
|
||||||
log.Info(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var hookPost postHook
|
build := &model.Build{
|
||||||
json.Unmarshal(contents, &hookPost)
|
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{}
|
repo := &model.Repo{
|
||||||
buildModel.Event = model.EventPush
|
Name: hook.Repository.Slug,
|
||||||
buildModel.Ref = hookPost.RefChanges[0].RefID
|
Owner: hook.Repository.Project.Key,
|
||||||
buildModel.Author = hookPost.Changesets.Values[0].ToCommit.Author.EmailAddress
|
FullName: fmt.Sprintf("%s/%s", hook.Repository.Project.Key, hook.Repository.Slug),
|
||||||
buildModel.Commit = hookPost.RefChanges[0].ToHash
|
Branch: "master",
|
||||||
buildModel.Avatar = avatarLink(hookPost.Changesets.Values[0].ToCommit.Author.EmailAddress)
|
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
|
return repo, build, nil
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type HookDetail struct {
|
type HookDetail struct {
|
||||||
Key string `"json:key"`
|
Key string `json:"key"`
|
||||||
Name string `"json:name"`
|
Name string `json:"name"`
|
||||||
Type string `"json:type"`
|
Type string `json:"type"`
|
||||||
Description string `"json:description"`
|
Description string `json:"description"`
|
||||||
Version string `"json:version"`
|
Version string `json:"version"`
|
||||||
ConfigFormKey string `"json:configFormKey"`
|
ConfigFormKey string `json:"configFormKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Hook struct {
|
type Hook struct {
|
||||||
Enabled bool `"json:enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Details *HookDetail `"json:details"`
|
Details *HookDetail `json:"details"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable hook for named repository
|
// Enable hook for named repository
|
||||||
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
|
// Set hook
|
||||||
hookBytes := []byte(fmt.Sprintf(`{"hook-url-0":"%s"}`, link))
|
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
|
// 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",
|
enablePath := fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s/settings/hooks/%s/enabled",
|
||||||
project, slug, hook_key)
|
project, slug, hook_key)
|
||||||
doDelete(client, bs.URL+enablePath)
|
doDelete(client, bs.URL+enablePath)
|
||||||
|
|
|
@ -11,22 +11,18 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
"github.com/drone/drone/shared/oauth2"
|
"github.com/drone/drone/shared/oauth2"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/google/go-github/github"
|
"github.com/google/go-github/github"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultURL = "https://github.com"
|
DefaultURL = "https://github.com" // Default GitHub URL
|
||||||
DefaultAPI = "https://api.github.com"
|
DefaultAPI = "https://api.github.com" // Default GitHub API URL
|
||||||
DefaultScope = "repo,repo:status,user:email"
|
|
||||||
DefaultMergeRef = "merge"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var githubDeployRegex = regexp.MustCompile(".+/deployments/(\\d+)")
|
|
||||||
|
|
||||||
type Github struct {
|
type Github struct {
|
||||||
URL string
|
URL string
|
||||||
API string
|
API string
|
||||||
|
@ -34,58 +30,35 @@ type Github struct {
|
||||||
Secret string
|
Secret string
|
||||||
Scope string
|
Scope string
|
||||||
MergeRef string
|
MergeRef string
|
||||||
Orgs []string
|
|
||||||
Open bool
|
|
||||||
PrivateMode bool
|
PrivateMode bool
|
||||||
SkipVerify bool
|
SkipVerify bool
|
||||||
GitSSH bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load(config string) *Github {
|
func New(url, client, secret string, scope []string, private, skipverify, mergeref bool) (remote.Remote, error) {
|
||||||
|
remote := &Github{
|
||||||
// parse the remote DSN configuration string
|
URL: strings.TrimSuffix(url, "/"),
|
||||||
url_, err := url.Parse(config)
|
Client: client,
|
||||||
if err != nil {
|
Secret: secret,
|
||||||
log.Fatalln("unable to parse remote dsn. %s", err)
|
Scope: strings.Join(scope, ","),
|
||||||
|
PrivateMode: private,
|
||||||
|
SkipVerify: skipverify,
|
||||||
|
MergeRef: "head",
|
||||||
}
|
}
|
||||||
params := url_.Query()
|
|
||||||
url_.Path = ""
|
|
||||||
url_.RawQuery = ""
|
|
||||||
|
|
||||||
// create the Githbub remote using parameters from
|
if remote.URL == DefaultURL {
|
||||||
// the parsed DSN configuration string.
|
remote.API = DefaultAPI
|
||||||
github := Github{}
|
|
||||||
github.URL = url_.String()
|
|
||||||
github.Client = params.Get("client_id")
|
|
||||||
github.Secret = params.Get("client_secret")
|
|
||||||
github.Scope = params.Get("scope")
|
|
||||||
github.Orgs = params["orgs"]
|
|
||||||
github.PrivateMode, _ = strconv.ParseBool(params.Get("private_mode"))
|
|
||||||
github.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify"))
|
|
||||||
github.Open, _ = strconv.ParseBool(params.Get("open"))
|
|
||||||
github.GitSSH, _ = strconv.ParseBool(params.Get("ssh"))
|
|
||||||
github.MergeRef = params.Get("merge_ref")
|
|
||||||
|
|
||||||
if github.URL == DefaultURL {
|
|
||||||
github.API = DefaultAPI
|
|
||||||
} else {
|
} else {
|
||||||
github.API = github.URL + "/api/v3/"
|
remote.API = remote.URL + "/api/v3/"
|
||||||
|
}
|
||||||
|
if mergeref {
|
||||||
|
remote.MergeRef = "merge"
|
||||||
}
|
}
|
||||||
|
|
||||||
if github.Scope == "" {
|
return remote, nil
|
||||||
github.Scope = DefaultScope
|
|
||||||
}
|
|
||||||
|
|
||||||
if github.MergeRef == "" {
|
|
||||||
github.MergeRef = DefaultMergeRef
|
|
||||||
}
|
|
||||||
|
|
||||||
return &github
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login authenticates the session and returns the
|
// Login authenticates the session and returns the remote user details.
|
||||||
// remote user details.
|
func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
|
||||||
func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
|
|
||||||
|
|
||||||
var config = &oauth2.Config{
|
var config = &oauth2.Config{
|
||||||
ClientId: g.Client,
|
ClientId: g.Client,
|
||||||
|
@ -101,7 +74,7 @@ func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User,
|
||||||
if len(code) == 0 {
|
if len(code) == 0 {
|
||||||
var random = GetRandom()
|
var random = GetRandom()
|
||||||
http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther)
|
http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther)
|
||||||
return nil, false, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var trans = &oauth2.Transport{
|
var trans = &oauth2.Transport{
|
||||||
|
@ -117,23 +90,13 @@ func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User,
|
||||||
}
|
}
|
||||||
var token, err = trans.Exchange(code)
|
var token, err = trans.Exchange(code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, fmt.Errorf("Error exchanging token. %s", err)
|
return nil, fmt.Errorf("Error exchanging token. %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var client = NewClient(g.API, token.AccessToken, g.SkipVerify)
|
var client = NewClient(g.API, token.AccessToken, g.SkipVerify)
|
||||||
var useremail, errr = GetUserEmail(client)
|
var useremail, errr = GetUserEmail(client)
|
||||||
if errr != nil {
|
if errr != nil {
|
||||||
return nil, false, fmt.Errorf("Error retrieving user or verified email. %s", errr)
|
return nil, fmt.Errorf("Error retrieving user or verified email. %s", errr)
|
||||||
}
|
|
||||||
|
|
||||||
if len(g.Orgs) > 0 {
|
|
||||||
allowedOrg, err := UserBelongsToOrg(client, g.Orgs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, fmt.Errorf("Could not check org membership. %s", err)
|
|
||||||
}
|
|
||||||
if !allowedOrg {
|
|
||||||
return nil, false, fmt.Errorf("User does not belong to correct org. Must belong to %v", g.Orgs)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
user := model.User{}
|
user := model.User{}
|
||||||
|
@ -141,7 +104,7 @@ func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User,
|
||||||
user.Email = *useremail.Email
|
user.Email = *useremail.Email
|
||||||
user.Token = token.AccessToken
|
user.Token = token.AccessToken
|
||||||
user.Avatar = *useremail.AvatarURL
|
user.Avatar = *useremail.AvatarURL
|
||||||
return &user, g.Open, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth authenticates the session and returns the remote user
|
// Auth authenticates the session and returns the remote user
|
||||||
|
@ -155,37 +118,52 @@ func (g *Github) Auth(token, secret string) (string, error) {
|
||||||
return *user.Login, nil
|
return *user.Login, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repo fetches the named repository from the remote system.
|
func (g *Github) Teams(u *model.User) ([]*model.Team, error) {
|
||||||
func (g *Github) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||||
repo_, err := GetRepo(client, owner, name)
|
orgs, err := GetOrgs(client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
repo := &model.Repo{}
|
var teams []*model.Team
|
||||||
repo.Owner = owner
|
for _, org := range orgs {
|
||||||
repo.Name = name
|
teams = append(teams, &model.Team{
|
||||||
repo.FullName = *repo_.FullName
|
Login: *org.Login,
|
||||||
repo.Link = *repo_.HTMLURL
|
Avatar: *org.AvatarURL,
|
||||||
repo.IsPrivate = *repo_.Private
|
})
|
||||||
repo.Clone = *repo_.CloneURL
|
}
|
||||||
repo.Branch = "master"
|
return teams, nil
|
||||||
repo.Avatar = *repo_.Owner.AvatarURL
|
}
|
||||||
repo.Kind = model.RepoGit
|
|
||||||
|
|
||||||
if repo_.DefaultBranch != nil {
|
// Repo fetches the named repository from the remote system.
|
||||||
repo.Branch = *repo_.DefaultBranch
|
func (g *Github) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
||||||
|
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||||
|
r, err := GetRepo(client, owner, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repo := &model.Repo{
|
||||||
|
Owner: owner,
|
||||||
|
Name: name,
|
||||||
|
FullName: *r.FullName,
|
||||||
|
Link: *r.HTMLURL,
|
||||||
|
IsPrivate: *r.Private,
|
||||||
|
Clone: *r.CloneURL,
|
||||||
|
Avatar: *r.Owner.AvatarURL,
|
||||||
|
Kind: model.RepoGit,
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.DefaultBranch != nil {
|
||||||
|
repo.Branch = *r.DefaultBranch
|
||||||
|
} else {
|
||||||
|
repo.Branch = "master"
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.PrivateMode {
|
if g.PrivateMode {
|
||||||
repo.IsPrivate = true
|
repo.IsPrivate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.GitSSH && repo.IsPrivate {
|
|
||||||
repo.Clone = *repo_.SSHURL
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo, err
|
return repo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,8 +235,10 @@ func repoStatus(client *github.Client, r *model.Repo, b *model.Build, link strin
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var reDeploy = regexp.MustCompile(".+/deployments/(\\d+)")
|
||||||
|
|
||||||
func deploymentStatus(client *github.Client, r *model.Repo, b *model.Build, link string) error {
|
func deploymentStatus(client *github.Client, r *model.Repo, b *model.Build, link string) error {
|
||||||
matches := githubDeployRegex.FindStringSubmatch(b.Link)
|
matches := reDeploy.FindStringSubmatch(b.Link)
|
||||||
// if the deployment was not triggered from github, don't send a deployment status
|
// if the deployment was not triggered from github, don't send a deployment status
|
||||||
if len(matches) != 2 {
|
if len(matches) != 2 {
|
||||||
return nil
|
return nil
|
||||||
|
@ -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
|
// Activate activates a repository by creating the post-commit hook and
|
||||||
// adding the SSH deploy key, if applicable.
|
// adding the SSH deploy key, if applicable.
|
||||||
func (g *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)
|
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||||
title, err := GetKeyTitle(link)
|
_, err := CreateUpdateHook(client, r.Owner, r.Name, link)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the CloneURL is using the SSHURL then we know that
|
|
||||||
// we need to add an SSH key to GitHub.
|
|
||||||
if r.IsPrivate || g.PrivateMode {
|
|
||||||
_, err = CreateUpdateKey(client, r.Owner, r.Name, title, k.Public)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = CreateUpdateHook(client, r.Owner, r.Name, link)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,18 +282,6 @@ func (g *Github) Activate(u *model.User, r *model.Repo, k *model.Key, link strin
|
||||||
// which are equal to link and removing the SSH deploy key.
|
// which are equal to link and removing the SSH deploy key.
|
||||||
func (g *Github) Deactivate(u *model.User, r *model.Repo, link string) error {
|
func (g *Github) Deactivate(u *model.User, r *model.Repo, link string) error {
|
||||||
client := NewClient(g.API, u.Token, g.SkipVerify)
|
client := NewClient(g.API, u.Token, g.SkipVerify)
|
||||||
title, err := GetKeyTitle(link)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the deploy-key if it is installed remote.
|
|
||||||
if r.IsPrivate || g.PrivateMode {
|
|
||||||
if err := DeleteKey(client, r.Owner, r.Name, title); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return DeleteHook(client, r.Owner, r.Name, link)
|
return DeleteHook(client, r.Owner, r.Name, link)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,31 +46,32 @@ func TestHook(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoad(t *testing.T) {
|
//
|
||||||
conf := "https://github.com?client_id=client&client_secret=secret&scope=scope1,scope2"
|
// func TestLoad(t *testing.T) {
|
||||||
|
// conf := "https://github.com?client_id=client&client_secret=secret&scope=scope1,scope2"
|
||||||
g := Load(conf)
|
//
|
||||||
if g.URL != "https://github.com" {
|
// g := Load(conf)
|
||||||
t.Errorf("g.URL = %q; want https://github.com", g.URL)
|
// if g.URL != "https://github.com" {
|
||||||
}
|
// t.Errorf("g.URL = %q; want https://github.com", g.URL)
|
||||||
if g.Client != "client" {
|
// }
|
||||||
t.Errorf("g.Client = %q; want client", g.Client)
|
// if g.Client != "client" {
|
||||||
}
|
// t.Errorf("g.Client = %q; want client", g.Client)
|
||||||
if g.Secret != "secret" {
|
// }
|
||||||
t.Errorf("g.Secret = %q; want secret", g.Secret)
|
// if g.Secret != "secret" {
|
||||||
}
|
// t.Errorf("g.Secret = %q; want secret", g.Secret)
|
||||||
if g.Scope != "scope1,scope2" {
|
// }
|
||||||
t.Errorf("g.Scope = %q; want scope1,scope2", g.Scope)
|
// if g.Scope != "scope1,scope2" {
|
||||||
}
|
// t.Errorf("g.Scope = %q; want scope1,scope2", g.Scope)
|
||||||
if g.API != DefaultAPI {
|
// }
|
||||||
t.Errorf("g.API = %q; want %q", g.API, DefaultAPI)
|
// if g.API != DefaultAPI {
|
||||||
}
|
// t.Errorf("g.API = %q; want %q", g.API, DefaultAPI)
|
||||||
if g.MergeRef != DefaultMergeRef {
|
// }
|
||||||
t.Errorf("g.MergeRef = %q; want %q", g.MergeRef, DefaultMergeRef)
|
// if g.MergeRef != DefaultMergeRef {
|
||||||
}
|
// t.Errorf("g.MergeRef = %q; want %q", g.MergeRef, DefaultMergeRef)
|
||||||
|
// }
|
||||||
g = Load("")
|
//
|
||||||
if g.Scope != DefaultScope {
|
// g = Load("")
|
||||||
t.Errorf("g.Scope = %q; want %q", g.Scope, DefaultScope)
|
// if g.Scope != DefaultScope {
|
||||||
}
|
// t.Errorf("g.Scope = %q; want %q", g.Scope, DefaultScope)
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
|
@ -72,53 +72,6 @@ func GetRepo(client *github.Client, owner, repo string) (*github.Repository, err
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllRepos is a helper function that returns an aggregated list
|
|
||||||
// of all user and organization repositories.
|
|
||||||
func GetAllRepos(client *github.Client) ([]github.Repository, error) {
|
|
||||||
orgs, err := GetOrgs(client)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
repos, err := GetUserRepos(client)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, org := range orgs {
|
|
||||||
list, err := GetOrgRepos(client, *org.Login)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
repos = append(repos, list...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return repos, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSubscriptions is a helper function that returns an aggregated list
|
|
||||||
// of all user and organization repositories.
|
|
||||||
// func GetSubscriptions(client *github.Client) ([]github.Repository, error) {
|
|
||||||
// var repos []github.Repository
|
|
||||||
// var opts = github.ListOptions{}
|
|
||||||
// opts.PerPage = 100
|
|
||||||
// opts.Page = 1
|
|
||||||
|
|
||||||
// // loop through user repository list
|
|
||||||
// for opts.Page > 0 {
|
|
||||||
// list, resp, err := client.Activity.ListWatched(""), &opts)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// repos = append(repos, list...)
|
|
||||||
|
|
||||||
// // increment the next page to retrieve
|
|
||||||
// opts.Page = resp.NextPage
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return repos, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// GetUserRepos is a helper function that returns a list of
|
// GetUserRepos is a helper function that returns a list of
|
||||||
// all user repositories. Paginated results are aggregated into
|
// all user repositories. Paginated results are aggregated into
|
||||||
// a single list.
|
// a single list.
|
||||||
|
@ -143,30 +96,6 @@ func GetUserRepos(client *github.Client) ([]github.Repository, error) {
|
||||||
return repos, nil
|
return repos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrgRepos is a helper function that returns a list of
|
|
||||||
// all org repositories. Paginated results are aggregated into
|
|
||||||
// a single list.
|
|
||||||
func GetOrgRepos(client *github.Client, org string) ([]github.Repository, error) {
|
|
||||||
var repos []github.Repository
|
|
||||||
var opts = github.RepositoryListByOrgOptions{}
|
|
||||||
opts.PerPage = 100
|
|
||||||
opts.Page = 1
|
|
||||||
|
|
||||||
// loop through user repository list
|
|
||||||
for opts.Page > 0 {
|
|
||||||
list, resp, err := client.Repositories.ListByOrg(org, &opts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
repos = append(repos, list...)
|
|
||||||
|
|
||||||
// increment the next page to retrieve
|
|
||||||
opts.Page = resp.NextPage
|
|
||||||
}
|
|
||||||
|
|
||||||
return repos, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOrgs is a helper function that returns a list of
|
// GetOrgs is a helper function that returns a list of
|
||||||
// all orgs that a user belongs to.
|
// all orgs that a user belongs to.
|
||||||
func GetOrgs(client *github.Client) ([]github.Organization, error) {
|
func GetOrgs(client *github.Client) ([]github.Organization, error) {
|
||||||
|
@ -250,70 +179,6 @@ func CreateUpdateHook(client *github.Client, owner, name, url string) (*github.H
|
||||||
return CreateHook(client, owner, name, url)
|
return CreateHook(client, owner, name, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKey is a heper function that retrieves a public Key by
|
|
||||||
// title. To do this, it will retrieve a list of all keys
|
|
||||||
// and iterate through the list.
|
|
||||||
func GetKey(client *github.Client, owner, name, title string) (*github.Key, error) {
|
|
||||||
keys, _, err := client.Repositories.ListKeys(owner, name, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, key := range keys {
|
|
||||||
if *key.Title == title {
|
|
||||||
return &key, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKeyTitle is a helper function that generates a title for the
|
|
||||||
// RSA public key based on the username and domain name.
|
|
||||||
func GetKeyTitle(rawurl string) (string, error) {
|
|
||||||
var uri, err = url.Parse(rawurl)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("drone@%s", uri.Host), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteKey is a helper function that deletes a deploy key
|
|
||||||
// for the specified repository.
|
|
||||||
func DeleteKey(client *github.Client, owner, name, title string) error {
|
|
||||||
var k, err = GetKey(client, owner, name, title)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if k == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err = client.Repositories.DeleteKey(owner, name, *k.ID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateKey is a helper function that creates a deploy key
|
|
||||||
// for the specified repository.
|
|
||||||
func CreateKey(client *github.Client, owner, name, title, key string) (*github.Key, error) {
|
|
||||||
var k = new(github.Key)
|
|
||||||
k.Title = github.String(title)
|
|
||||||
k.Key = github.String(key)
|
|
||||||
created, _, err := client.Repositories.CreateKey(owner, name, k)
|
|
||||||
return created, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateUpdateKey is a helper function that creates a deployment key
|
|
||||||
// for the specified repository if it does not already exist, otherwise
|
|
||||||
// it updates the existing key
|
|
||||||
func CreateUpdateKey(client *github.Client, owner, name, title, key string) (*github.Key, error) {
|
|
||||||
var k, _ = GetKey(client, owner, name, title)
|
|
||||||
if k != nil {
|
|
||||||
k.Title = github.String(title)
|
|
||||||
k.Key = github.String(key)
|
|
||||||
client.Repositories.DeleteKey(owner, name, *k.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateKey(client, owner, name, title, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFile is a heper function that retrieves a file from
|
// GetFile is a heper function that retrieves a file from
|
||||||
// GitHub and returns its contents in byte array format.
|
// GitHub and returns its contents in byte array format.
|
||||||
func GetFile(client *github.Client, owner, name, path, ref string) ([]byte, error) {
|
func GetFile(client *github.Client, owner, name, path, ref string) ([]byte, error) {
|
||||||
|
@ -344,25 +209,3 @@ func GetPayload(req *http.Request) []byte {
|
||||||
}
|
}
|
||||||
return []byte(payload)
|
return []byte(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserBelongsToOrg returns true if the currently authenticated user is a
|
|
||||||
// member of any of the organizations provided.
|
|
||||||
func UserBelongsToOrg(client *github.Client, permittedOrgs []string) (bool, error) {
|
|
||||||
userOrgs, err := GetOrgs(client)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
userOrgSet := make(map[string]struct{}, len(userOrgs))
|
|
||||||
for _, org := range userOrgs {
|
|
||||||
userOrgSet[*org.Login] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, org := range permittedOrgs {
|
|
||||||
if _, ok := userOrgSet[org]; ok {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,30 +4,63 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
"github.com/drone/drone/shared/oauth2"
|
"github.com/drone/drone/shared/oauth2"
|
||||||
"github.com/drone/drone/shared/token"
|
|
||||||
|
|
||||||
"github.com/drone/drone/remote/gitlab/client"
|
"github.com/drone/drone/remote/gitlab/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const DefaultScope = "api"
|
||||||
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 {
|
type Gitlab struct {
|
||||||
URL string
|
URL string
|
||||||
Client string
|
Client string
|
||||||
Secret string
|
Secret string
|
||||||
AllowedOrgs []string
|
Machine string
|
||||||
CloneMode string
|
Username string
|
||||||
Open bool
|
Password string
|
||||||
PrivateMode bool
|
PrivateMode bool
|
||||||
SkipVerify bool
|
SkipVerify bool
|
||||||
HideArchives bool
|
HideArchives bool
|
||||||
|
@ -46,17 +79,17 @@ func Load(config string) *Gitlab {
|
||||||
gitlab.URL = url_.String()
|
gitlab.URL = url_.String()
|
||||||
gitlab.Client = params.Get("client_id")
|
gitlab.Client = params.Get("client_id")
|
||||||
gitlab.Secret = params.Get("client_secret")
|
gitlab.Secret = params.Get("client_secret")
|
||||||
gitlab.AllowedOrgs = params["orgs"]
|
// gitlab.AllowedOrgs = params["orgs"]
|
||||||
gitlab.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify"))
|
gitlab.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify"))
|
||||||
gitlab.HideArchives, _ = strconv.ParseBool(params.Get("hide_archives"))
|
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") {
|
// switch params.Get("clone_mode") {
|
||||||
case "oauth":
|
// case "oauth":
|
||||||
gitlab.CloneMode = "oauth"
|
// gitlab.CloneMode = "oauth"
|
||||||
default:
|
// default:
|
||||||
gitlab.CloneMode = "token"
|
// gitlab.CloneMode = "token"
|
||||||
}
|
// }
|
||||||
|
|
||||||
// this is a temp workaround
|
// this is a temp workaround
|
||||||
gitlab.Search, _ = strconv.ParseBool(params.Get("search"))
|
gitlab.Search, _ = strconv.ParseBool(params.Get("search"))
|
||||||
|
@ -66,7 +99,7 @@ func Load(config string) *Gitlab {
|
||||||
|
|
||||||
// Login authenticates the session and returns the
|
// Login authenticates the session and returns the
|
||||||
// remote user details.
|
// 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{
|
var config = &oauth2.Config{
|
||||||
ClientId: g.Client,
|
ClientId: g.Client,
|
||||||
|
@ -86,41 +119,41 @@ func (g *Gitlab) Login(res http.ResponseWriter, req *http.Request) (*model.User,
|
||||||
var code = req.FormValue("code")
|
var code = req.FormValue("code")
|
||||||
if len(code) == 0 {
|
if len(code) == 0 {
|
||||||
http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther)
|
http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther)
|
||||||
return nil, false, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var trans = &oauth2.Transport{Config: config, Transport: trans_}
|
var trans = &oauth2.Transport{Config: config, Transport: trans_}
|
||||||
var token_, err = trans.Exchange(code)
|
var token_, err = trans.Exchange(code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, fmt.Errorf("Error exchanging token. %s", err)
|
return nil, fmt.Errorf("Error exchanging token. %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := NewClient(g.URL, token_.AccessToken, g.SkipVerify)
|
client := NewClient(g.URL, token_.AccessToken, g.SkipVerify)
|
||||||
login, err := client.CurrentUser()
|
login, err := client.CurrentUser()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(g.AllowedOrgs) != 0 {
|
// if len(g.AllowedOrgs) != 0 {
|
||||||
groups, err := client.AllGroups()
|
// groups, err := client.AllGroups()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, false, fmt.Errorf("Could not check org membership. %s", err)
|
// return nil, fmt.Errorf("Could not check org membership. %s", err)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
var member bool
|
// var member bool
|
||||||
for _, group := range groups {
|
// for _, group := range groups {
|
||||||
for _, allowedOrg := range g.AllowedOrgs {
|
// for _, allowedOrg := range g.AllowedOrgs {
|
||||||
if group.Path == allowedOrg {
|
// if group.Path == allowedOrg {
|
||||||
member = true
|
// member = true
|
||||||
break
|
// break
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
if !member {
|
// if !member {
|
||||||
return nil, false, fmt.Errorf("User does not belong to correct group. Must belong to %v", g.AllowedOrgs)
|
// return nil, false, fmt.Errorf("User does not belong to correct group. Must belong to %v", g.AllowedOrgs)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
user := &model.User{}
|
user := &model.User{}
|
||||||
user.Login = login.Username
|
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
|
user.Avatar = g.URL + "/" + login.AvatarUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
return user, g.Open, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Gitlab) Auth(token, secret string) (string, error) {
|
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
|
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.
|
// Repo fetches the named repository from the remote system.
|
||||||
func (g *Gitlab) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
func (g *Gitlab) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
||||||
client := NewClient(g.URL, u.Token, g.SkipVerify)
|
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
|
// Netrc returns a .netrc file that can be used to clone
|
||||||
// private repositories from a remote system.
|
// private repositories from a remote system.
|
||||||
func (g *Gitlab) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
// func (g *Gitlab) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||||
url_, err := url.Parse(g.URL)
|
// url_, err := url.Parse(g.URL)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
netrc := &model.Netrc{}
|
// netrc := &model.Netrc{}
|
||||||
netrc.Machine = url_.Host
|
// 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 {
|
// Netrc returns a netrc file capable of authenticating Gitlab requests and
|
||||||
case "oauth":
|
// cloning Gitlab repositories. The netrc will use the global machine account
|
||||||
netrc.Login = "oauth2"
|
// when configured.
|
||||||
netrc.Password = u.Token
|
func (g *Gitlab) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||||
case "token":
|
if g.Password != "" {
|
||||||
t := token.New(token.HookToken, r.FullName)
|
return &model.Netrc{
|
||||||
netrc.Login = "drone-ci-token"
|
Login: g.Username,
|
||||||
netrc.Password, err = t.Sign(r.Hash)
|
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
|
// Activate activates a repository by adding a Post-commit hook and
|
||||||
// a Public Deploy key, if applicable.
|
// 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)
|
var client = NewClient(g.URL, user.Token, g.SkipVerify)
|
||||||
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
|
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -94,13 +94,13 @@ func Test_Gitlab(t *testing.T) {
|
||||||
// Test activate method
|
// Test activate method
|
||||||
g.Describe("Activate", func() {
|
g.Describe("Activate", func() {
|
||||||
g.It("Should be success", 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.Assert(err == nil).IsTrue()
|
||||||
})
|
})
|
||||||
|
|
||||||
g.It("Should be failed, when token not given", func() {
|
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()
|
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",
|
"ref": "refs/heads/master",
|
||||||
"before": "4b2626259b5a97b6b4eab5e6cca66adb986b672b",
|
"before": "4b2626259b5a97b6b4eab5e6cca66adb986b672b",
|
|
@ -6,189 +6,187 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/remote"
|
||||||
"github.com/gogits/go-gogs-client"
|
"github.com/gogits/go-gogs-client"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Gogs struct {
|
// 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
|
URL string
|
||||||
Open bool
|
Machine string
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
PrivateMode bool
|
PrivateMode bool
|
||||||
SkipVerify bool
|
SkipVerify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load(config string) *Gogs {
|
// New returns a Remote implementation that integrates with Gogs, an open
|
||||||
// parse the remote DSN configuration string
|
// source Git service written in Go. See https://gogs.io/
|
||||||
url_, err := url.Parse(config)
|
func New(opts Opts) (remote.Remote, error) {
|
||||||
|
url, err := url.Parse(opts.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("unable to parse remote dsn. %s", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
params := url_.Query()
|
host, _, err := net.SplitHostPort(url.Host)
|
||||||
url_.RawQuery = ""
|
if err == nil {
|
||||||
|
url.Host = host
|
||||||
// create the Githbub remote using parameters from
|
}
|
||||||
// the parsed DSN configuration string.
|
return &client{
|
||||||
gogs := Gogs{}
|
URL: opts.URL,
|
||||||
gogs.URL = url_.String()
|
Machine: url.Host,
|
||||||
gogs.PrivateMode, _ = strconv.ParseBool(params.Get("private_mode"))
|
Username: opts.Username,
|
||||||
gogs.SkipVerify, _ = strconv.ParseBool(params.Get("skip_verify"))
|
Password: opts.Password,
|
||||||
gogs.Open, _ = strconv.ParseBool(params.Get("open"))
|
PrivateMode: opts.PrivateMode,
|
||||||
|
SkipVerify: opts.SkipVerify,
|
||||||
return &gogs
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login authenticates the session and returns the
|
// Login authenticates an account with Gogs using basic authenticaiton. The
|
||||||
// remote user details.
|
// Gogs account details are returned when the user is successfully authenticated.
|
||||||
func (g *Gogs) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
|
func (c *client) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) {
|
||||||
var (
|
var (
|
||||||
username = req.FormValue("username")
|
username = req.FormValue("username")
|
||||||
password = req.FormValue("password")
|
password = req.FormValue("password")
|
||||||
)
|
)
|
||||||
|
|
||||||
// if the username or password doesn't exist we re-direct
|
// if the username or password is empty we re-direct to the login screen.
|
||||||
// the user to the login screen.
|
|
||||||
if len(username) == 0 || len(password) == 0 {
|
if len(username) == 0 || len(password) == 0 {
|
||||||
http.Redirect(res, req, "/login/form", http.StatusSeeOther)
|
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
|
// try to fetch drone token if it exists
|
||||||
var accessToken string
|
var accessToken string
|
||||||
tokens, err := client.ListAccessTokens(username, password)
|
tokens, err := client.ListAccessTokens(username, password)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
return nil, false, err
|
for _, token := range tokens {
|
||||||
}
|
if token.Name == "drone" {
|
||||||
for _, token := range tokens {
|
accessToken = token.Sha1
|
||||||
if token.Name == "drone" {
|
break
|
||||||
accessToken = token.Sha1
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if drone token not found, create it
|
// if drone token not found, create it
|
||||||
if accessToken == "" {
|
if accessToken == "" {
|
||||||
token, err := client.CreateAccessToken(username, password, gogs.CreateAccessTokenOption{Name: "drone"})
|
token, terr := client.CreateAccessToken(
|
||||||
if err != nil {
|
username,
|
||||||
return nil, false, err
|
password,
|
||||||
|
gogs.CreateAccessTokenOption{Name: "drone"},
|
||||||
|
)
|
||||||
|
if terr != nil {
|
||||||
|
return nil, terr
|
||||||
}
|
}
|
||||||
accessToken = token.Sha1
|
accessToken = token.Sha1
|
||||||
}
|
}
|
||||||
|
|
||||||
client = NewGogsClient(g.URL, accessToken, g.SkipVerify)
|
client = c.newClientToken(accessToken)
|
||||||
userInfo, err := client.GetUserInfo(username)
|
account, 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()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fullName := owner + "/" + name
|
return &model.User{
|
||||||
for _, repo := range repos_ {
|
Token: accessToken,
|
||||||
if repo.FullName == fullName {
|
Login: account.UserName,
|
||||||
return toRepo(repo), nil
|
Email: account.Email,
|
||||||
}
|
Avatar: expandAvatar(c.URL, account.AvatarUrl),
|
||||||
}
|
}, nil
|
||||||
|
|
||||||
return nil, fmt.Errorf("Not Found")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repos fetches a list of repos from the remote system.
|
// Auth is not supported by the Gogs driver.
|
||||||
func (g *Gogs) Repos(u *model.User) ([]*model.RepoLite, error) {
|
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{}
|
repos := []*model.RepoLite{}
|
||||||
|
|
||||||
client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
|
client := c.newClientToken(u.Token)
|
||||||
repos_, err := client.ListMyRepos()
|
all, err := client.ListMyRepos()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return repos, err
|
return repos, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, repo := range repos_ {
|
for _, repo := range all {
|
||||||
repos = append(repos, toRepoLite(repo))
|
repos = append(repos, toRepoLite(repo))
|
||||||
}
|
}
|
||||||
|
|
||||||
return repos, err
|
return repos, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perm fetches the named repository permissions from
|
// Perm returns the user permissions for the named Gogs repository.
|
||||||
// the remote system for the specified user.
|
func (c *client) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
||||||
func (g *Gogs) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
client := c.newClientToken(u.Token)
|
||||||
client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
|
repo, err := client.GetRepo(owner, name)
|
||||||
repos_, err := client.ListMyRepos()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return toPerm(repo.Permissions), nil
|
||||||
fullName := owner + "/" + name
|
|
||||||
for _, repo := range repos_ {
|
|
||||||
if repo.FullName == fullName {
|
|
||||||
return toPerm(repo.Permissions), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("Not Found")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// File fetches a file from the remote repository and returns in string format.
|
// File fetches the file from the Gogs repository and returns its contents.
|
||||||
func (g *Gogs) 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) {
|
||||||
client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
|
client := c.newClientToken(u.Token)
|
||||||
cfg, err := client.GetFile(r.Owner, r.Name, b.Commit, f)
|
cfg, err := client.GetFile(r.Owner, r.Name, b.Commit, f)
|
||||||
return cfg, err
|
return cfg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status sends the commit status to the remote system.
|
// Status is not supported by the Gogs driver.
|
||||||
// An example would be the GitHub pull request status.
|
func (c *client) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||||
func (g *Gogs) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
return nil
|
||||||
return fmt.Errorf("Not Implemented")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Netrc returns a .netrc file that can be used to clone
|
// Netrc returns a netrc file capable of authenticating Gogs requests and
|
||||||
// private repositories from a remote system.
|
// cloning Gogs repositories. The netrc will use the global machine account
|
||||||
func (g *Gogs) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
// when configured.
|
||||||
url_, err := url.Parse(g.URL)
|
func (c *client) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||||
if err != nil {
|
if c.Password != "" {
|
||||||
return nil, err
|
return &model.Netrc{
|
||||||
}
|
Login: c.Username,
|
||||||
host, _, err := net.SplitHostPort(url_.Host)
|
Password: c.Password,
|
||||||
if err == nil {
|
Machine: c.Machine,
|
||||||
url_.Host = host
|
}, nil
|
||||||
}
|
}
|
||||||
return &model.Netrc{
|
return &model.Netrc{
|
||||||
Login: u.Token,
|
Login: u.Token,
|
||||||
Password: "x-oauth-basic",
|
Password: "x-oauth-basic",
|
||||||
Machine: url_.Host,
|
Machine: c.Machine,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activate activates a repository by creating the post-commit hook and
|
// Activate activates the repository by registering post-commit hooks with
|
||||||
// adding the SSH deploy key, if applicable.
|
// the Gogs repository.
|
||||||
func (g *Gogs) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
|
func (c *client) Activate(u *model.User, r *model.Repo, link string) error {
|
||||||
config := map[string]string{
|
config := map[string]string{
|
||||||
"url": link,
|
"url": link,
|
||||||
"secret": r.Hash,
|
"secret": r.Hash,
|
||||||
|
@ -200,20 +198,19 @@ func (g *Gogs) Activate(u *model.User, r *model.Repo, k *model.Key, link string)
|
||||||
Active: true,
|
Active: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
client := NewGogsClient(g.URL, u.Token, g.SkipVerify)
|
client := c.newClientToken(u.Token)
|
||||||
_, err := client.CreateRepoHook(r.Owner, r.Name, hook)
|
_, err := client.CreateRepoHook(r.Owner, r.Name, hook)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deactivate removes a repository by removing all the post-commit hooks
|
// Deactivate is not supported by the Gogs driver.
|
||||||
// which are equal to link and removing the SSH deploy key.
|
func (c *client) Deactivate(u *model.User, r *model.Repo, link string) error {
|
||||||
func (g *Gogs) Deactivate(u *model.User, r *model.Repo, link string) error {
|
return nil
|
||||||
return fmt.Errorf("Not Implemented")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook parses the post-commit hook from the Request body
|
// Hook parses the incoming Gogs hook and returns the Repository and Build
|
||||||
// and returns the required data in a standard format.
|
// details. If the hook is unsupported nil values are returned.
|
||||||
func (g *Gogs) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
func (c *client) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
repo *model.Repo
|
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") {
|
switch r.Header.Get("X-Gogs-Event") {
|
||||||
case "push":
|
case "push":
|
||||||
var push *PushHook
|
var push *pushHook
|
||||||
push, err = parsePush(r.Body)
|
push, err = parsePush(r.Body)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
repo = repoFromPush(push)
|
repo = repoFromPush(push)
|
||||||
|
@ -232,20 +229,20 @@ func (g *Gogs) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
return repo, build, err
|
return repo, build, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient initializes and returns a API client.
|
// helper function to return the Gogs client
|
||||||
func NewGogsClient(url, token string, skipVerify bool) *gogs.Client {
|
func (c *client) newClient() *gogs.Client {
|
||||||
sslClient := &http.Client{}
|
return c.newClientToken("")
|
||||||
c := gogs.NewClient(url, token)
|
}
|
||||||
|
|
||||||
if skipVerify {
|
// helper function to return the Gogs client
|
||||||
sslClient.Transport = &http.Transport{
|
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},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
}
|
}
|
||||||
c.SetHTTPClient(sslClient)
|
client.SetHTTPClient(httpClient)
|
||||||
}
|
}
|
||||||
return c
|
return client
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Gogs) String() string {
|
|
||||||
return "gogs"
|
|
||||||
}
|
}
|
||||||
|
|
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"
|
"github.com/gogits/go-gogs-client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// helper function that converts a Gogs repository
|
// helper function that converts a Gogs repository to a Drone repository.
|
||||||
// to a Drone repository.
|
|
||||||
func toRepoLite(from *gogs.Repository) *model.RepoLite {
|
func toRepoLite(from *gogs.Repository) *model.RepoLite {
|
||||||
name := strings.Split(from.FullName, "/")[1]
|
name := strings.Split(from.FullName, "/")[1]
|
||||||
avatar := expandAvatar(
|
avatar := expandAvatar(
|
||||||
|
@ -28,8 +27,7 @@ func toRepoLite(from *gogs.Repository) *model.RepoLite {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function that converts a Gogs repository
|
// helper function that converts a Gogs repository to a Drone repository.
|
||||||
// to a Drone repository.
|
|
||||||
func toRepo(from *gogs.Repository) *model.Repo {
|
func toRepo(from *gogs.Repository) *model.Repo {
|
||||||
name := strings.Split(from.FullName, "/")[1]
|
name := strings.Split(from.FullName, "/")[1]
|
||||||
avatar := expandAvatar(
|
avatar := expandAvatar(
|
||||||
|
@ -49,8 +47,7 @@ func toRepo(from *gogs.Repository) *model.Repo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function that converts a Gogs permission
|
// helper function that converts a Gogs permission to a Drone permission.
|
||||||
// to a Drone permission.
|
|
||||||
func toPerm(from gogs.Permission) *model.Perm {
|
func toPerm(from gogs.Permission) *model.Perm {
|
||||||
return &model.Perm{
|
return &model.Perm{
|
||||||
Pull: from.Pull,
|
Pull: from.Pull,
|
||||||
|
@ -59,11 +56,10 @@ func toPerm(from gogs.Permission) *model.Perm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function that extracts the Build data
|
// helper function that extracts the Build data from a Gogs push hook
|
||||||
// from a Gogs push hook
|
func buildFromPush(hook *pushHook) *model.Build {
|
||||||
func buildFromPush(hook *PushHook) *model.Build {
|
|
||||||
avatar := expandAvatar(
|
avatar := expandAvatar(
|
||||||
hook.Repo.Url,
|
hook.Repo.URL,
|
||||||
fixMalformedAvatar(hook.Sender.Avatar),
|
fixMalformedAvatar(hook.Sender.Avatar),
|
||||||
)
|
)
|
||||||
return &model.Build{
|
return &model.Build{
|
||||||
|
@ -79,9 +75,8 @@ func buildFromPush(hook *PushHook) *model.Build {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function that extracts the Repository data
|
// helper function that extracts the Repository data from a Gogs push hook
|
||||||
// from a Gogs push hook
|
func repoFromPush(hook *pushHook) *model.Repo {
|
||||||
func repoFromPush(hook *PushHook) *model.Repo {
|
|
||||||
fullName := fmt.Sprintf(
|
fullName := fmt.Sprintf(
|
||||||
"%s/%s",
|
"%s/%s",
|
||||||
hook.Repo.Owner.Username,
|
hook.Repo.Owner.Username,
|
||||||
|
@ -91,20 +86,19 @@ func repoFromPush(hook *PushHook) *model.Repo {
|
||||||
Name: hook.Repo.Name,
|
Name: hook.Repo.Name,
|
||||||
Owner: hook.Repo.Owner.Username,
|
Owner: hook.Repo.Owner.Username,
|
||||||
FullName: fullName,
|
FullName: fullName,
|
||||||
Link: hook.Repo.Url,
|
Link: hook.Repo.URL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function that parses a push hook from
|
// helper function that parses a push hook from a read closer.
|
||||||
// a read closer.
|
func parsePush(r io.Reader) (*pushHook, error) {
|
||||||
func parsePush(r io.Reader) (*PushHook, error) {
|
push := new(pushHook)
|
||||||
push := new(PushHook)
|
|
||||||
err := json.NewDecoder(r).Decode(push)
|
err := json.NewDecoder(r).Decode(push)
|
||||||
return push, err
|
return push, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixMalformedAvatar is a helper function that fixes
|
// fixMalformedAvatar is a helper function that fixes an avatar url if malformed
|
||||||
// an avatar url if malformed (known bug with gogs)
|
// (currently a known bug with gogs)
|
||||||
func fixMalformedAvatar(url string) string {
|
func fixMalformedAvatar(url string) string {
|
||||||
index := strings.Index(url, "///")
|
index := strings.Index(url, "///")
|
||||||
if index != -1 {
|
if index != -1 {
|
||||||
|
@ -117,16 +111,16 @@ func fixMalformedAvatar(url string) string {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
// expandAvatar is a helper function that converts
|
// expandAvatar is a helper function that converts a relative avatar URL to the
|
||||||
// a relative avatar URL to the abosolute url.
|
// abosolute url.
|
||||||
func expandAvatar(repo, rawurl string) string {
|
func expandAvatar(repo, rawurl string) string {
|
||||||
if !strings.HasPrefix(rawurl, "/avatars/") {
|
if !strings.HasPrefix(rawurl, "/avatars/") {
|
||||||
return rawurl
|
return rawurl
|
||||||
}
|
}
|
||||||
url_, err := url.Parse(repo)
|
url, err := url.Parse(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rawurl
|
return rawurl
|
||||||
}
|
}
|
||||||
url_.Path = rawurl
|
url.Path = rawurl
|
||||||
return url_.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/drone/drone/model"
|
"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/franela/goblin"
|
||||||
"github.com/gogits/go-gogs-client"
|
"github.com/gogits/go-gogs-client"
|
||||||
|
@ -17,7 +17,7 @@ func Test_parse(t *testing.T) {
|
||||||
g.Describe("Gogs", func() {
|
g.Describe("Gogs", func() {
|
||||||
|
|
||||||
g.It("Should parse push hook payload", func() {
|
g.It("Should parse push hook payload", func() {
|
||||||
buf := bytes.NewBufferString(testdata.PushHook)
|
buf := bytes.NewBufferString(fixtures.HookPush)
|
||||||
hook, err := parsePush(buf)
|
hook, err := parsePush(buf)
|
||||||
g.Assert(err == nil).IsTrue()
|
g.Assert(err == nil).IsTrue()
|
||||||
g.Assert(hook.Ref).Equal("refs/heads/master")
|
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.Before).Equal("4b2626259b5a97b6b4eab5e6cca66adb986b672b")
|
||||||
g.Assert(hook.Compare).Equal("http://gogs.golang.org/gordon/hello-world/compare/4b2626259b5a97b6b4eab5e6cca66adb986b672b...ef98532add3b2feb7a137426bba1248724367df5")
|
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.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.Name).Equal("gordon")
|
||||||
g.Assert(hook.Repo.Owner.Email).Equal("gordon@golang.org")
|
g.Assert(hook.Repo.Owner.Email).Equal("gordon@golang.org")
|
||||||
g.Assert(hook.Repo.Owner.Username).Equal("gordon")
|
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() {
|
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)
|
hook, _ := parsePush(buf)
|
||||||
build := buildFromPush(hook)
|
build := buildFromPush(hook)
|
||||||
g.Assert(build.Event).Equal(model.EventPush)
|
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() {
|
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)
|
hook, _ := parsePush(buf)
|
||||||
repo := repoFromPush(hook)
|
repo := repoFromPush(hook)
|
||||||
g.Assert(repo.Name).Equal(hook.Repo.Name)
|
g.Assert(repo.Name).Equal(hook.Repo.Name)
|
||||||
g.Assert(repo.Owner).Equal(hook.Repo.Owner.Username)
|
g.Assert(repo.Owner).Equal(hook.Repo.Owner.Username)
|
||||||
g.Assert(repo.FullName).Equal("gordon/hello-world")
|
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() {
|
g.It("Should return a Perm struct from a Gogs Perm", func() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package gogs
|
package gogs
|
||||||
|
|
||||||
type PushHook struct {
|
type pushHook struct {
|
||||||
Ref string `json:"ref"`
|
Ref string `json:"ref"`
|
||||||
Before string `json:"before"`
|
Before string `json:"before"`
|
||||||
After string `json:"after"`
|
After string `json:"after"`
|
||||||
|
@ -15,7 +15,7 @@ type PushHook struct {
|
||||||
Repo struct {
|
Repo struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Url string `json:"url"`
|
URL string `json:"url"`
|
||||||
Private bool `json:"private"`
|
Private bool `json:"private"`
|
||||||
Owner struct {
|
Owner struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
@ -27,7 +27,7 @@ type PushHook struct {
|
||||||
Commits []struct {
|
Commits []struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Url string `json:"url"`
|
URL string `json:"url"`
|
||||||
} `json:"commits"`
|
} `json:"commits"`
|
||||||
|
|
||||||
Sender struct {
|
Sender struct {
|
||||||
|
|
|
@ -1,42 +1,32 @@
|
||||||
package mock
|
package mock
|
||||||
|
|
||||||
import "github.com/stretchr/testify/mock"
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
import "net/http"
|
"github.com/drone/drone/model"
|
||||||
import "github.com/drone/drone/model"
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is an autogenerated mock type for the Remote type
|
||||||
type Remote struct {
|
type Remote struct {
|
||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *Remote) Login(w http.ResponseWriter, r *http.Request) (*model.User, bool, error) {
|
// Activate provides a mock function with given fields: u, r, link
|
||||||
ret := _m.Called(w, r)
|
func (_m *Remote) Activate(u *model.User, r *model.Repo, link string) error {
|
||||||
|
ret := _m.Called(u, r, link)
|
||||||
|
|
||||||
var r0 *model.User
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) *model.User); ok {
|
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, string) error); ok {
|
||||||
r0 = rf(w, r)
|
r0 = rf(u, r, link)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
r0 = ret.Error(0)
|
||||||
r0 = ret.Get(0).(*model.User)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var r1 bool
|
return r0
|
||||||
if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) bool); ok {
|
|
||||||
r1 = rf(w, r)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Get(1).(bool)
|
|
||||||
}
|
|
||||||
|
|
||||||
var r2 error
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auth provides a mock function with given fields: token, secret
|
||||||
func (_m *Remote) Auth(token string, secret string) (string, error) {
|
func (_m *Remote) Auth(token string, secret string) (string, error) {
|
||||||
ret := _m.Called(token, secret)
|
ret := _m.Called(token, secret)
|
||||||
|
|
||||||
|
@ -56,69 +46,22 @@ func (_m *Remote) Auth(token string, secret string) (string, error) {
|
||||||
|
|
||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
func (_m *Remote) Repo(u *model.User, owner string, repo string) (*model.Repo, error) {
|
|
||||||
ret := _m.Called(u, owner, repo)
|
|
||||||
|
|
||||||
var r0 *model.Repo
|
// Deactivate provides a mock function with given fields: u, r, link
|
||||||
if rf, ok := ret.Get(0).(func(*model.User, string, string) *model.Repo); ok {
|
func (_m *Remote) Deactivate(u *model.User, r *model.Repo, link string) error {
|
||||||
r0 = rf(u, owner, repo)
|
ret := _m.Called(u, r, link)
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, string) error); ok {
|
||||||
|
r0 = rf(u, r, link)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
r0 = ret.Error(0)
|
||||||
r0 = ret.Get(0).(*model.Repo)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var r1 error
|
return r0
|
||||||
if rf, ok := ret.Get(1).(func(*model.User, string, string) error); ok {
|
|
||||||
r1 = rf(u, owner, repo)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
}
|
||||||
func (_m *Remote) Repos(u *model.User) ([]*model.RepoLite, error) {
|
|
||||||
ret := _m.Called(u)
|
|
||||||
|
|
||||||
var r0 []*model.RepoLite
|
// File provides a mock function with given fields: u, r, b, f
|
||||||
if rf, ok := ret.Get(0).(func(*model.User) []*model.RepoLite); ok {
|
|
||||||
r0 = rf(u)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).([]*model.RepoLite)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(1).(func(*model.User) error); ok {
|
|
||||||
r1 = rf(u)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
func (_m *Remote) Perm(u *model.User, owner string, repo string) (*model.Perm, error) {
|
|
||||||
ret := _m.Called(u, owner, repo)
|
|
||||||
|
|
||||||
var r0 *model.Perm
|
|
||||||
if rf, ok := ret.Get(0).(func(*model.User, string, string) *model.Perm); ok {
|
|
||||||
r0 = rf(u, owner, repo)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).(*model.Perm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(1).(func(*model.User, string, string) error); ok {
|
|
||||||
r1 = rf(u, owner, repo)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
func (_m *Remote) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
|
func (_m *Remote) File(u *model.User, r *model.Repo, b *model.Build, f string) ([]byte, error) {
|
||||||
ret := _m.Called(u, r, b, f)
|
ret := _m.Called(u, r, b, f)
|
||||||
|
|
||||||
|
@ -140,63 +83,8 @@ func (_m *Remote) File(u *model.User, r *model.Repo, b *model.Build, f string) (
|
||||||
|
|
||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
func (_m *Remote) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
|
||||||
ret := _m.Called(u, r, b, link)
|
|
||||||
|
|
||||||
var r0 error
|
// Hook provides a mock function with given fields: r
|
||||||
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, *model.Build, string) error); ok {
|
|
||||||
r0 = rf(u, r, b, link)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
func (_m *Remote) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
|
||||||
ret := _m.Called(u, r)
|
|
||||||
|
|
||||||
var r0 *model.Netrc
|
|
||||||
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo) *model.Netrc); ok {
|
|
||||||
r0 = rf(u, r)
|
|
||||||
} else {
|
|
||||||
if ret.Get(0) != nil {
|
|
||||||
r0 = ret.Get(0).(*model.Netrc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var r1 error
|
|
||||||
if rf, ok := ret.Get(1).(func(*model.User, *model.Repo) error); ok {
|
|
||||||
r1 = rf(u, r)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
|
||||||
func (_m *Remote) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
|
|
||||||
ret := _m.Called(u, r, k, link)
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, *model.Key, string) error); ok {
|
|
||||||
r0 = rf(u, r, k, link)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
func (_m *Remote) Deactivate(u *model.User, r *model.Repo, link string) error {
|
|
||||||
ret := _m.Called(u, r, link)
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, string) error); ok {
|
|
||||||
r0 = rf(u, r, link)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
func (_m *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
func (_m *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
ret := _m.Called(r)
|
ret := _m.Called(r)
|
||||||
|
|
||||||
|
@ -227,3 +115,155 @@ func (_m *Remote) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
|
|
||||||
return r0, r1, r2
|
return r0, r1, r2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Login provides a mock function with given fields: w, r
|
||||||
|
func (_m *Remote) Login(w http.ResponseWriter, r *http.Request) (*model.User, error) {
|
||||||
|
ret := _m.Called(w, r)
|
||||||
|
|
||||||
|
var r0 *model.User
|
||||||
|
if rf, ok := ret.Get(0).(func(http.ResponseWriter, *http.Request) *model.User); ok {
|
||||||
|
r0 = rf(w, r)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*model.User)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(http.ResponseWriter, *http.Request) error); ok {
|
||||||
|
r1 = rf(w, r)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Netrc provides a mock function with given fields: u, r
|
||||||
|
func (_m *Remote) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||||
|
ret := _m.Called(u, r)
|
||||||
|
|
||||||
|
var r0 *model.Netrc
|
||||||
|
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo) *model.Netrc); ok {
|
||||||
|
r0 = rf(u, r)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*model.Netrc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(*model.User, *model.Repo) error); ok {
|
||||||
|
r1 = rf(u, r)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perm provides a mock function with given fields: u, owner, repo
|
||||||
|
func (_m *Remote) Perm(u *model.User, owner string, repo string) (*model.Perm, error) {
|
||||||
|
ret := _m.Called(u, owner, repo)
|
||||||
|
|
||||||
|
var r0 *model.Perm
|
||||||
|
if rf, ok := ret.Get(0).(func(*model.User, string, string) *model.Perm); ok {
|
||||||
|
r0 = rf(u, owner, repo)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*model.Perm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(*model.User, string, string) error); ok {
|
||||||
|
r1 = rf(u, owner, repo)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repo provides a mock function with given fields: u, owner, repo
|
||||||
|
func (_m *Remote) Repo(u *model.User, owner string, repo string) (*model.Repo, error) {
|
||||||
|
ret := _m.Called(u, owner, repo)
|
||||||
|
|
||||||
|
var r0 *model.Repo
|
||||||
|
if rf, ok := ret.Get(0).(func(*model.User, string, string) *model.Repo); ok {
|
||||||
|
r0 = rf(u, owner, repo)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*model.Repo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(*model.User, string, string) error); ok {
|
||||||
|
r1 = rf(u, owner, repo)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repos provides a mock function with given fields: u
|
||||||
|
func (_m *Remote) Repos(u *model.User) ([]*model.RepoLite, error) {
|
||||||
|
ret := _m.Called(u)
|
||||||
|
|
||||||
|
var r0 []*model.RepoLite
|
||||||
|
if rf, ok := ret.Get(0).(func(*model.User) []*model.RepoLite); ok {
|
||||||
|
r0 = rf(u)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]*model.RepoLite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(*model.User) error); ok {
|
||||||
|
r1 = rf(u)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status provides a mock function with given fields: u, r, b, link
|
||||||
|
func (_m *Remote) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||||
|
ret := _m.Called(u, r, b, link)
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(*model.User, *model.Repo, *model.Build, string) error); ok {
|
||||||
|
r0 = rf(u, r, b, link)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Teams provides a mock function with given fields: u
|
||||||
|
func (_m *Remote) Teams(u *model.User) ([]*model.Team, error) {
|
||||||
|
ret := _m.Called(u)
|
||||||
|
|
||||||
|
var r0 []*model.Team
|
||||||
|
if rf, ok := ret.Get(0).(func(*model.User) []*model.Team); ok {
|
||||||
|
r0 = rf(u)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]*model.Team)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(*model.User) error); ok {
|
||||||
|
r1 = rf(u)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
|
@ -13,12 +13,15 @@ import (
|
||||||
type Remote interface {
|
type Remote interface {
|
||||||
// Login authenticates the session and returns the
|
// Login authenticates the session and returns the
|
||||||
// remote user details.
|
// remote user details.
|
||||||
Login(w http.ResponseWriter, r *http.Request) (*model.User, bool, error)
|
Login(w http.ResponseWriter, r *http.Request) (*model.User, error)
|
||||||
|
|
||||||
// Auth authenticates the session and returns the remote user
|
// Auth authenticates the session and returns the remote user
|
||||||
// login for the given token and secret
|
// login for the given token and secret
|
||||||
Auth(token, secret string) (string, error)
|
Auth(token, secret string) (string, error)
|
||||||
|
|
||||||
|
// Teams fetches a list of team memberships from the remote system.
|
||||||
|
Teams(u *model.User) ([]*model.Team, error)
|
||||||
|
|
||||||
// Repo fetches the named repository from the remote system.
|
// Repo fetches the named repository from the remote system.
|
||||||
Repo(u *model.User, owner, repo string) (*model.Repo, error)
|
Repo(u *model.User, owner, repo string) (*model.Repo, error)
|
||||||
|
|
||||||
|
@ -41,29 +44,28 @@ type Remote interface {
|
||||||
// private repositories from a remote system.
|
// private repositories from a remote system.
|
||||||
Netrc(u *model.User, r *model.Repo) (*model.Netrc, error)
|
Netrc(u *model.User, r *model.Repo) (*model.Netrc, error)
|
||||||
|
|
||||||
// Activate activates a repository by creating the post-commit hook and
|
// Activate activates a repository by creating the post-commit hook.
|
||||||
// adding the SSH deploy key, if applicable.
|
Activate(u *model.User, r *model.Repo, link string) error
|
||||||
Activate(u *model.User, r *model.Repo, k *model.Key, link string) error
|
|
||||||
|
|
||||||
// Deactivate removes a repository by removing all the post-commit hooks
|
// Deactivate deactivates a repository by removing all previously created
|
||||||
// which are equal to link and removing the SSH deploy key.
|
// post-commit hooks matching the given link.
|
||||||
Deactivate(u *model.User, r *model.Repo, link string) error
|
Deactivate(u *model.User, r *model.Repo, link string) error
|
||||||
|
|
||||||
// Hook parses the post-commit hook from the Request body
|
// Hook parses the post-commit hook from the Request body and returns the
|
||||||
// and returns the required data in a standard format.
|
// required data in a standard format.
|
||||||
Hook(r *http.Request) (*model.Repo, *model.Build, error)
|
Hook(r *http.Request) (*model.Repo, *model.Build, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresher refreshes an oauth token and expiration for the given user. It
|
||||||
|
// returns true if the token was refreshed, false if the token was not refreshed,
|
||||||
|
// and error if it failed to refersh.
|
||||||
type Refresher interface {
|
type Refresher interface {
|
||||||
// Refresh refreshes an oauth token and expiration for the given
|
|
||||||
// user. It returns true if the token was refreshed, false if the
|
|
||||||
// token was not refreshed, and error if it failed to refersh.
|
|
||||||
Refresh(*model.User) (bool, error)
|
Refresh(*model.User) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login authenticates the session and returns the
|
// Login authenticates the session and returns the
|
||||||
// remote user details.
|
// remote user details.
|
||||||
func Login(c context.Context, w http.ResponseWriter, r *http.Request) (*model.User, bool, error) {
|
func Login(c context.Context, w http.ResponseWriter, r *http.Request) (*model.User, error) {
|
||||||
return FromContext(c).Login(w, r)
|
return FromContext(c).Login(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +75,11 @@ func Auth(c context.Context, token, secret string) (string, error) {
|
||||||
return FromContext(c).Auth(token, secret)
|
return FromContext(c).Auth(token, secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Teams fetches a list of team memberships from the remote system.
|
||||||
|
func Teams(c context.Context, u *model.User) ([]*model.Team, error) {
|
||||||
|
return FromContext(c).Teams(u)
|
||||||
|
}
|
||||||
|
|
||||||
// Repo fetches the named repository from the remote system.
|
// Repo fetches the named repository from the remote system.
|
||||||
func Repo(c context.Context, u *model.User, owner, repo string) (*model.Repo, error) {
|
func Repo(c context.Context, u *model.User, owner, repo string) (*model.Repo, error) {
|
||||||
return FromContext(c).Repo(u, owner, repo)
|
return FromContext(c).Repo(u, owner, repo)
|
||||||
|
@ -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
|
// Activate activates a repository by creating the post-commit hook and
|
||||||
// adding the SSH deploy key, if applicable.
|
// adding the SSH deploy key, if applicable.
|
||||||
func Activate(c context.Context, u *model.User, r *model.Repo, k *model.Key, link string) error {
|
func Activate(c context.Context, u *model.User, r *model.Repo, link string) error {
|
||||||
return FromContext(c).Activate(u, r, k, link)
|
return FromContext(c).Activate(u, r, link)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deactivate removes a repository by removing all the post-commit hooks
|
// Deactivate removes a repository by removing all the post-commit hooks
|
||||||
|
|
|
@ -1,45 +1,33 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
"github.com/drone/drone/shared/token"
|
"github.com/drone/drone/shared/token"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/ianschenck/envflag"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const agentKey = "agent"
|
||||||
secret = envflag.String("AGENT_SECRET", "", "")
|
|
||||||
noauth = envflag.Bool("AGENT_NO_AUTH", false, "")
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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.
|
// for agents to connect to the queue.
|
||||||
func AgentMust() gin.HandlerFunc {
|
func Agents(cli *cli.Context) gin.HandlerFunc {
|
||||||
|
secret := cli.String("agent-secret")
|
||||||
if *secret == "" {
|
if secret == "" {
|
||||||
logrus.Fatalf("please provide the agent secret to authenticate agent requests")
|
logrus.Fatalf("failed to generate token from DRONE_AGENT_SECRET")
|
||||||
}
|
}
|
||||||
|
|
||||||
t := token.New(token.AgentToken, "")
|
t := token.New(secret, "")
|
||||||
s, err := t.Sign(*secret)
|
s, err := t.Sign(secret)
|
||||||
if err != nil {
|
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)
|
logrus.Warnf("agents can connect with token %s", s)
|
||||||
|
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
c.Set(agentKey, secret)
|
||||||
return *secret, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithError(403, err)
|
|
||||||
} else if parsed.Kind != token.AgentToken {
|
|
||||||
c.AbortWithStatus(403)
|
|
||||||
} else {
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,16 @@ package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/drone/drone/bus"
|
"github.com/drone/drone/bus"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Bus() gin.HandlerFunc {
|
// Bus is a middleware function that initializes the Event Bus and attaches to
|
||||||
bus_ := bus.New()
|
// the context of every http.Request.
|
||||||
|
func Bus(cli *cli.Context) gin.HandlerFunc {
|
||||||
|
v := bus.New()
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
bus.ToContext(c, bus_)
|
bus.ToContext(c, v)
|
||||||
c.Next()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/drone/drone/cache"
|
"github.com/drone/drone/cache"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
"github.com/gin-gonic/gin"
|
"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
|
// Cache is a middleware function that initializes the Cache and attaches to
|
||||||
// the context of every http.Request.
|
// the context of every http.Request.
|
||||||
func Cache() gin.HandlerFunc {
|
func Cache(cli *cli.Context) gin.HandlerFunc {
|
||||||
cc := cache.NewTTL(*ttl)
|
v := setupCache(cli)
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
cache.ToContext(c, cc)
|
cache.ToContext(c, v)
|
||||||
c.Next()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 (
|
import (
|
||||||
"github.com/drone/drone/queue"
|
"github.com/drone/drone/queue"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Queue() gin.HandlerFunc {
|
// Queue is a middleware function that initializes the Queue and attaches to
|
||||||
queue_ := queue.New()
|
// the context of every http.Request.
|
||||||
|
func Queue(cli *cli.Context) gin.HandlerFunc {
|
||||||
|
v := queue.New()
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
queue.ToContext(c, queue_)
|
queue.ToContext(c, v)
|
||||||
c.Next()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +1,102 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
"github.com/drone/drone/remote"
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/remote/bitbucket"
|
"github.com/drone/drone/remote/bitbucket"
|
||||||
|
"github.com/drone/drone/remote/bitbucketserver"
|
||||||
"github.com/drone/drone/remote/github"
|
"github.com/drone/drone/remote/github"
|
||||||
"github.com/drone/drone/remote/gitlab"
|
"github.com/drone/drone/remote/gitlab"
|
||||||
"github.com/drone/drone/remote/gogs"
|
"github.com/drone/drone/remote/gogs"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gin-gonic/gin"
|
"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
|
// Remote is a middleware function that initializes the Remote and attaches to
|
||||||
// the context of every http.Request.
|
// the context of every http.Request.
|
||||||
func Remote() gin.HandlerFunc {
|
func Remote(c *cli.Context) gin.HandlerFunc {
|
||||||
|
v, err := setupRemote(c)
|
||||||
logrus.Infof("using remote driver %s", *driver)
|
if err != nil {
|
||||||
logrus.Infof("using remote config %s", *config)
|
logrus.Fatalln(err)
|
||||||
|
|
||||||
var remote_ remote.Remote
|
|
||||||
switch *driver {
|
|
||||||
case "github":
|
|
||||||
remote_ = github.Load(*config)
|
|
||||||
case "bitbucket":
|
|
||||||
remote_ = bitbucket.Load(*config)
|
|
||||||
case "gogs":
|
|
||||||
remote_ = gogs.Load(*config)
|
|
||||||
case "gitlab":
|
|
||||||
remote_ = gitlab.Load(*config)
|
|
||||||
case "bitbucketserver":
|
|
||||||
remote_ = bitbucketserver.Load(*config)
|
|
||||||
default:
|
|
||||||
logrus.Fatalln("remote configuration not found")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
remote.ToContext(c, remote_)
|
remote.ToContext(c, v)
|
||||||
c.Next()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
return user.Hash, err
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
confv := c.MustGet("config")
|
||||||
|
if conf, ok := confv.(*model.Config); ok {
|
||||||
|
user.Admin = conf.IsAdmin(user)
|
||||||
|
}
|
||||||
c.Set("user", user)
|
c.Set("user", user)
|
||||||
|
|
||||||
// if this is a session token (ie not the API token)
|
// if this is a session token (ie not the API token)
|
||||||
|
|
|
@ -1,29 +1,27 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
"github.com/drone/drone/store"
|
"github.com/drone/drone/store"
|
||||||
"github.com/drone/drone/store/datastore"
|
"github.com/drone/drone/store/datastore"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gin-gonic/gin"
|
"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
|
// Store is a middleware function that initializes the Datastore and attaches to
|
||||||
// the context of every http.Request.
|
// the context of every http.Request.
|
||||||
func Store() gin.HandlerFunc {
|
func Store(cli *cli.Context) gin.HandlerFunc {
|
||||||
db := datastore.New(*database, *datasource)
|
v := setupStore(cli)
|
||||||
|
|
||||||
logrus.Infof("using database driver %s", *database)
|
|
||||||
logrus.Infof("using database config %s", *datasource)
|
|
||||||
|
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
store.ToContext(c, db)
|
store.ToContext(c, v)
|
||||||
c.Next()
|
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 (
|
import (
|
||||||
"github.com/drone/drone/stream"
|
"github.com/drone/drone/stream"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Stream() gin.HandlerFunc {
|
// Stream is a middleware function that initializes the Stream and attaches to
|
||||||
stream_ := stream.New()
|
// the context of every http.Request.
|
||||||
|
func Stream(cli *cli.Context) gin.HandlerFunc {
|
||||||
|
v := stream.New()
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
stream.ToContext(c, stream_)
|
stream.ToContext(c, v)
|
||||||
c.Next()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,8 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version is a middleware function that appends the Drone
|
// Version is a middleware function that appends the Drone version information
|
||||||
// version information to the HTTP response. This is intended
|
// to the HTTP response. This is intended for debugging and troubleshooting.
|
||||||
// for debugging and troubleshooting.
|
|
||||||
func Version(c *gin.Context) {
|
func Version(c *gin.Context) {
|
||||||
c.Header("X-DRONE-VERSION", version.Version)
|
c.Header("X-DRONE-VERSION", version.Version)
|
||||||
c.Next()
|
|
||||||
}
|
}
|
||||||
|
|
173
router/router.go
173
router/router.go
|
@ -2,22 +2,20 @@ package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/drone/drone/api"
|
|
||||||
"github.com/drone/drone/router/middleware"
|
|
||||||
"github.com/drone/drone/router/middleware/header"
|
"github.com/drone/drone/router/middleware/header"
|
||||||
"github.com/drone/drone/router/middleware/session"
|
"github.com/drone/drone/router/middleware/session"
|
||||||
"github.com/drone/drone/router/middleware/token"
|
"github.com/drone/drone/router/middleware/token"
|
||||||
|
"github.com/drone/drone/server"
|
||||||
"github.com/drone/drone/static"
|
"github.com/drone/drone/static"
|
||||||
"github.com/drone/drone/template"
|
"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 := gin.New()
|
||||||
e.Use(gin.Recovery())
|
e.Use(gin.Recovery())
|
||||||
|
|
||||||
|
@ -27,22 +25,21 @@ func Load(middlewares ...gin.HandlerFunc) http.Handler {
|
||||||
e.Use(header.NoCache)
|
e.Use(header.NoCache)
|
||||||
e.Use(header.Options)
|
e.Use(header.Options)
|
||||||
e.Use(header.Secure)
|
e.Use(header.Secure)
|
||||||
e.Use(middlewares...)
|
e.Use(middleware...)
|
||||||
e.Use(session.SetUser())
|
e.Use(session.SetUser())
|
||||||
e.Use(token.Refresh)
|
e.Use(token.Refresh)
|
||||||
|
|
||||||
e.GET("/", web.ShowIndex)
|
e.GET("/", server.ShowIndex)
|
||||||
e.GET("/repos", web.ShowAllRepos)
|
e.GET("/repos", server.ShowAllRepos)
|
||||||
e.GET("/login", web.ShowLogin)
|
e.GET("/login", server.ShowLogin)
|
||||||
e.GET("/login/form", web.ShowLoginForm)
|
e.GET("/login/form", server.ShowLoginForm)
|
||||||
e.GET("/logout", web.GetLogout)
|
e.GET("/logout", server.GetLogout)
|
||||||
|
|
||||||
|
// TODO below will Go away with React UI
|
||||||
settings := e.Group("/settings")
|
settings := e.Group("/settings")
|
||||||
{
|
{
|
||||||
settings.Use(session.MustUser())
|
settings.Use(session.MustUser())
|
||||||
settings.GET("/profile", web.ShowUser)
|
settings.GET("/profile", server.ShowUser)
|
||||||
settings.GET("/people", session.MustAdmin(), web.ShowUsers)
|
|
||||||
settings.GET("/nodes", session.MustAdmin(), web.ShowNodes)
|
|
||||||
}
|
}
|
||||||
repo := e.Group("/repos/:owner/:name")
|
repo := e.Group("/repos/:owner/:name")
|
||||||
{
|
{
|
||||||
|
@ -50,50 +47,43 @@ func Load(middlewares ...gin.HandlerFunc) http.Handler {
|
||||||
repo.Use(session.SetPerm())
|
repo.Use(session.SetPerm())
|
||||||
repo.Use(session.MustPull)
|
repo.Use(session.MustPull)
|
||||||
|
|
||||||
repo.GET("", web.ShowRepo)
|
repo.GET("", server.ShowRepo)
|
||||||
repo.GET("/builds/:number", web.ShowBuild)
|
repo.GET("/builds/:number", server.ShowBuild)
|
||||||
repo.GET("/builds/:number/:job", web.ShowBuild)
|
repo.GET("/builds/:number/:job", server.ShowBuild)
|
||||||
|
|
||||||
repo_settings := repo.Group("/settings")
|
repo_settings := repo.Group("/settings")
|
||||||
{
|
{
|
||||||
repo_settings.GET("", session.MustPush, web.ShowRepoConf)
|
repo_settings.GET("", session.MustPush, server.ShowRepoConf)
|
||||||
repo_settings.GET("/encrypt", session.MustPush, web.ShowRepoEncrypt)
|
repo_settings.GET("/encrypt", session.MustPush, server.ShowRepoEncrypt)
|
||||||
repo_settings.GET("/badges", web.ShowRepoBadges)
|
repo_settings.GET("/badges", server.ShowRepoBadges)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO above will Go away with React UI
|
||||||
|
|
||||||
user := e.Group("/api/user")
|
user := e.Group("/api/user")
|
||||||
{
|
{
|
||||||
user.Use(session.MustUser())
|
user.Use(session.MustUser())
|
||||||
user.GET("", api.GetSelf)
|
user.GET("", server.GetSelf)
|
||||||
user.GET("/feed", api.GetFeed)
|
user.GET("/feed", server.GetFeed)
|
||||||
user.GET("/repos", api.GetRepos)
|
user.GET("/repos", server.GetRepos)
|
||||||
user.GET("/repos/remote", api.GetRemoteRepos)
|
user.GET("/repos/remote", server.GetRemoteRepos)
|
||||||
user.POST("/token", api.PostToken)
|
user.POST("/token", server.PostToken)
|
||||||
user.DELETE("/token", api.DeleteToken)
|
user.DELETE("/token", server.DeleteToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
users := e.Group("/api/users")
|
users := e.Group("/api/users")
|
||||||
{
|
{
|
||||||
users.Use(session.MustAdmin())
|
users.Use(session.MustAdmin())
|
||||||
users.GET("", api.GetUsers)
|
users.GET("", server.GetUsers)
|
||||||
users.POST("", api.PostUser)
|
users.POST("", server.PostUser)
|
||||||
users.GET("/:login", api.GetUser)
|
users.GET("/:login", server.GetUser)
|
||||||
users.PATCH("/:login", api.PatchUser)
|
users.PATCH("/:login", server.PatchUser)
|
||||||
users.DELETE("/:login", api.DeleteUser)
|
users.DELETE("/:login", server.DeleteUser)
|
||||||
}
|
|
||||||
|
|
||||||
nodes := e.Group("/api/nodes")
|
|
||||||
{
|
|
||||||
nodes.Use(session.MustAdmin())
|
|
||||||
nodes.GET("", api.GetNodes)
|
|
||||||
nodes.POST("", api.PostNode)
|
|
||||||
nodes.DELETE("/:node", api.DeleteNode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repos := e.Group("/api/repos/:owner/:name")
|
repos := e.Group("/api/repos/:owner/:name")
|
||||||
{
|
{
|
||||||
repos.POST("", api.PostRepo)
|
repos.POST("", server.PostRepo)
|
||||||
|
|
||||||
repo := repos.Group("")
|
repo := repos.Group("")
|
||||||
{
|
{
|
||||||
|
@ -101,37 +91,32 @@ func Load(middlewares ...gin.HandlerFunc) http.Handler {
|
||||||
repo.Use(session.SetPerm())
|
repo.Use(session.SetPerm())
|
||||||
repo.Use(session.MustPull)
|
repo.Use(session.MustPull)
|
||||||
|
|
||||||
repo.GET("", api.GetRepo)
|
repo.GET("", server.GetRepo)
|
||||||
repo.GET("/key", api.GetRepoKey)
|
repo.GET("/builds", server.GetBuilds)
|
||||||
repo.POST("/key", api.PostRepoKey)
|
repo.GET("/builds/:number", server.GetBuild)
|
||||||
repo.GET("/builds", api.GetBuilds)
|
repo.GET("/logs/:number/:job", server.GetBuildLogs)
|
||||||
repo.GET("/builds/:number", api.GetBuild)
|
repo.POST("/sign", session.MustPush, server.Sign)
|
||||||
repo.GET("/logs/:number/:job", api.GetBuildLogs)
|
|
||||||
repo.POST("/sign", session.MustPush, api.Sign)
|
|
||||||
|
|
||||||
repo.POST("/secrets", session.MustPush, api.PostSecret)
|
repo.POST("/secrets", session.MustPush, server.PostSecret)
|
||||||
repo.DELETE("/secrets/:secret", session.MustPush, api.DeleteSecret)
|
repo.DELETE("/secrets/:secret", session.MustPush, server.DeleteSecret)
|
||||||
|
|
||||||
// requires authenticated user
|
|
||||||
repo.POST("/encrypt", session.MustUser(), api.PostSecure)
|
|
||||||
|
|
||||||
// requires push permissions
|
// requires push permissions
|
||||||
repo.PATCH("", session.MustPush, api.PatchRepo)
|
repo.PATCH("", session.MustPush, server.PatchRepo)
|
||||||
repo.DELETE("", session.MustPush, api.DeleteRepo)
|
repo.DELETE("", session.MustPush, server.DeleteRepo)
|
||||||
|
|
||||||
repo.POST("/builds/:number", session.MustPush, api.PostBuild)
|
repo.POST("/builds/:number", session.MustPush, server.PostBuild)
|
||||||
repo.DELETE("/builds/:number/:job", session.MustPush, api.DeleteBuild)
|
repo.DELETE("/builds/:number/:job", session.MustPush, server.DeleteBuild)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
badges := e.Group("/api/badges/:owner/:name")
|
badges := e.Group("/api/badges/:owner/:name")
|
||||||
{
|
{
|
||||||
badges.GET("/status.svg", web.GetBadge)
|
badges.GET("/status.svg", server.GetBadge)
|
||||||
badges.GET("/cc.xml", web.GetCC)
|
badges.GET("/cc.xml", server.GetCC)
|
||||||
}
|
}
|
||||||
|
|
||||||
e.POST("/hook", web.PostHook)
|
e.POST("/hook", server.PostHook)
|
||||||
e.POST("/api/hook", web.PostHook)
|
e.POST("/api/hook", server.PostHook)
|
||||||
|
|
||||||
stream := e.Group("/api/stream")
|
stream := e.Group("/api/stream")
|
||||||
{
|
{
|
||||||
|
@ -139,57 +124,53 @@ func Load(middlewares ...gin.HandlerFunc) http.Handler {
|
||||||
stream.Use(session.SetPerm())
|
stream.Use(session.SetPerm())
|
||||||
stream.Use(session.MustPull)
|
stream.Use(session.MustPull)
|
||||||
|
|
||||||
if os.Getenv("CANARY") == "true" {
|
stream.GET("/:owner/:name", server.GetRepoEvents)
|
||||||
stream.GET("/:owner/:name", web.GetRepoEvents2)
|
stream.GET("/:owner/:name/:build/:number", server.GetStream)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auth := e.Group("/authorize")
|
auth := e.Group("/authorize")
|
||||||
{
|
{
|
||||||
auth.GET("", web.GetLogin)
|
auth.GET("", server.GetLogin)
|
||||||
auth.POST("", web.GetLogin)
|
auth.POST("", server.GetLogin)
|
||||||
auth.POST("/token", web.GetLoginToken)
|
auth.POST("/token", server.GetLoginToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
queue := e.Group("/api/queue")
|
queue := e.Group("/api/queue")
|
||||||
{
|
{
|
||||||
if os.Getenv("CANARY") == "true" {
|
queue.Use(session.AuthorizeAgent)
|
||||||
queue.Use(middleware.AgentMust())
|
queue.POST("/pull", server.Pull)
|
||||||
queue.POST("/pull", api.Pull)
|
queue.POST("/pull/:os/:arch", server.Pull)
|
||||||
queue.POST("/pull/:os/:arch", api.Pull)
|
queue.POST("/wait/:id", server.Wait)
|
||||||
queue.POST("/wait/:id", api.Wait)
|
queue.POST("/stream/:id", server.Stream)
|
||||||
queue.POST("/stream/:id", api.Stream)
|
queue.POST("/status/:id", server.Update)
|
||||||
queue.POST("/status/:id", api.Update)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gitlab := e.Group("/gitlab/:owner/:name")
|
// DELETE THESE
|
||||||
{
|
// gitlab := e.Group("/gitlab/:owner/:name")
|
||||||
gitlab.Use(session.SetRepo())
|
// {
|
||||||
gitlab.GET("/commits/:sha", web.GetCommit)
|
// gitlab.Use(session.SetRepo())
|
||||||
gitlab.GET("/pulls/:number", web.GetPullRequest)
|
// gitlab.GET("/commits/:sha", GetCommit)
|
||||||
|
// gitlab.GET("/pulls/:number", GetPullRequest)
|
||||||
|
//
|
||||||
|
// redirects := gitlab.Group("/redirect")
|
||||||
|
// {
|
||||||
|
// redirects.GET("/commits/:sha", RedirectSha)
|
||||||
|
// redirects.GET("/pulls/:number", RedirectPullRequest)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
redirects := gitlab.Group("/redirect")
|
// bots := e.Group("/bots")
|
||||||
{
|
// {
|
||||||
redirects.GET("/commits/:sha", web.RedirectSha)
|
// bots.Use(session.MustUser())
|
||||||
redirects.GET("/pulls/:number", web.RedirectPullRequest)
|
// bots.POST("/slack", Slack)
|
||||||
}
|
// bots.POST("/slack/:command", Slack)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return normalize(e)
|
return normalize(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// THIS HACK JOB IS GOING AWAY SOON.
|
||||||
|
//
|
||||||
// normalize is a helper function to work around the following
|
// normalize is a helper function to work around the following
|
||||||
// issue with gin. https://github.com/gin-gonic/gin/issues/388
|
// issue with gin. https://github.com/gin-gonic/gin/issues/388
|
||||||
func normalize(h http.Handler) http.Handler {
|
func normalize(h http.Handler) http.Handler {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package web
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -1,18 +1,13 @@
|
||||||
package api
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/drone/drone/bus"
|
"github.com/drone/drone/bus"
|
||||||
"github.com/drone/drone/engine"
|
|
||||||
"github.com/drone/drone/queue"
|
"github.com/drone/drone/queue"
|
||||||
"github.com/drone/drone/remote"
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
|
@ -24,21 +19,6 @@ import (
|
||||||
"github.com/drone/drone/router/middleware/session"
|
"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) {
|
func GetBuilds(c *gin.Context) {
|
||||||
repo := session.Repo(c)
|
repo := session.Repo(c)
|
||||||
builds, err := store.GetBuildList(c, repo)
|
builds, err := store.GetBuildList(c, repo)
|
||||||
|
@ -135,7 +115,6 @@ func GetBuildLogs(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteBuild(c *gin.Context) {
|
func DeleteBuild(c *gin.Context) {
|
||||||
engine_ := engine.FromContext(c)
|
|
||||||
repo := session.Repo(c)
|
repo := session.Repo(c)
|
||||||
|
|
||||||
// parse the build number and job sequence number from
|
// parse the build number and job sequence number from
|
||||||
|
@ -155,17 +134,8 @@ func DeleteBuild(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getenv("CANARY") == "true" {
|
bus.Publish(c, bus.NewEvent(bus.Cancelled, repo, build, job))
|
||||||
bus.Publish(c, bus.NewEvent(bus.Cancelled, repo, build, job))
|
c.String(204, "")
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
node, err := store.GetNode(c, job.NodeID)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithError(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
engine_.Cancel(build.ID, job.ID, node)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func PostBuild(c *gin.Context) {
|
func PostBuild(c *gin.Context) {
|
||||||
|
@ -205,7 +175,8 @@ func PostBuild(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch the .drone.yml file from the database
|
// 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 {
|
if err != nil {
|
||||||
log.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
log.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
||||||
c.AbortWithError(404, 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
|
// 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 {
|
if err != nil {
|
||||||
log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err)
|
log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
key, _ := store.GetKey(c, repo)
|
|
||||||
netrc, err := remote_.Netrc(user, repo)
|
netrc, err := remote_.Netrc(user, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
|
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)
|
log.Errorf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IMPORTANT. PLEASE READ
|
var signed bool
|
||||||
//
|
var verified bool
|
||||||
// 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
|
|
||||||
|
|
||||||
if os.Getenv("CANARY") == "true" {
|
signature, err := jose.ParseSigned(string(sec))
|
||||||
|
if err != nil {
|
||||||
var signed bool
|
log.Debugf("cannot parse .drone.yml.sig file. %s", err)
|
||||||
var verified bool
|
} else if len(sec) == 0 {
|
||||||
|
log.Debugf("cannot parse .drone.yml.sig file. empty file")
|
||||||
signature, err := jose.ParseSigned(string(sec))
|
} else {
|
||||||
|
signed = true
|
||||||
|
output, err := signature.Verify([]byte(repo.Hash))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("cannot parse .drone.yml.sig file. %s", err)
|
log.Debugf("cannot verify .drone.yml.sig file. %s", err)
|
||||||
} else if len(sec) == 0 {
|
} else if string(output) != string(raw) {
|
||||||
log.Debugf("cannot parse .drone.yml.sig file. empty file")
|
log.Debugf("cannot verify .drone.yml.sig file. no match. %q <> %q", string(output), string(raw))
|
||||||
} else {
|
} else {
|
||||||
signed = true
|
verified = 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
log.Debugf(".drone.yml is signed=%v and verified=%v", signed, verified)
|
||||||
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"), " "),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -1,18 +1,14 @@
|
||||||
package web
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/square/go-jose"
|
"github.com/square/go-jose"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/drone/drone/bus"
|
"github.com/drone/drone/bus"
|
||||||
"github.com/drone/drone/engine"
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/queue"
|
"github.com/drone/drone/queue"
|
||||||
"github.com/drone/drone/remote"
|
"github.com/drone/drone/remote"
|
||||||
|
@ -22,21 +18,6 @@ import (
|
||||||
"github.com/drone/drone/yaml"
|
"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)\]`)
|
var skipRe = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`)
|
||||||
|
|
||||||
func PostHook(c *gin.Context) {
|
func PostHook(c *gin.Context) {
|
||||||
|
@ -141,13 +122,14 @@ func PostHook(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch the build file from the database
|
// 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 {
|
if err != nil {
|
||||||
log.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
log.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
||||||
c.AbortWithError(404, err)
|
c.AbortWithError(404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sec, err := remote_.File(user, repo, build, droneSec)
|
sec, err := remote_.File(user, repo, build, config.Shasum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err)
|
log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err)
|
||||||
// NOTE we don't exit on failure. The sec file is optional
|
// NOTE we don't exit on failure. The sec file is optional
|
||||||
|
@ -168,8 +150,6 @@ func PostHook(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key, _ := store.GetKey(c, repo)
|
|
||||||
|
|
||||||
// verify the branches can be built vs skipped
|
// verify the branches can be built vs skipped
|
||||||
branches := yaml.ParseBranch(raw)
|
branches := yaml.ParseBranch(raw)
|
||||||
if !branches.Matches(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
|
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)
|
log.Errorf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IMPORTANT. PLEASE READ
|
var signed bool
|
||||||
//
|
var verified bool
|
||||||
// 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
|
|
||||||
|
|
||||||
if os.Getenv("CANARY") == "true" {
|
signature, err := jose.ParseSigned(string(sec))
|
||||||
|
if err != nil {
|
||||||
var signed bool
|
log.Debugf("cannot parse .drone.yml.sig file. %s", err)
|
||||||
var verified bool
|
} else if len(sec) == 0 {
|
||||||
|
log.Debugf("cannot parse .drone.yml.sig file. empty file")
|
||||||
signature, err := jose.ParseSigned(string(sec))
|
} else {
|
||||||
|
signed = true
|
||||||
|
output, err := signature.Verify([]byte(repo.Hash))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("cannot parse .drone.yml.sig file. %s", err)
|
log.Debugf("cannot verify .drone.yml.sig file. %s", err)
|
||||||
} else if len(sec) == 0 {
|
} else if string(output) != string(raw) {
|
||||||
log.Debugf("cannot parse .drone.yml.sig file. empty file")
|
log.Debugf("cannot verify .drone.yml.sig file. no match")
|
||||||
} else {
|
} else {
|
||||||
signed = true
|
verified = 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
log.Debugf(".drone.yml is signed=%v and verified=%v", signed, verified)
|
||||||
go engine_.Schedule(c.Copy(), &engine.Task{
|
|
||||||
User: user,
|
bus.Publish(c, bus.NewBuildEvent(bus.Enqueued, repo, build))
|
||||||
Repo: repo,
|
for _, job := range jobs {
|
||||||
Build: build,
|
queue.Publish(c, &queue.Work{
|
||||||
BuildPrev: last,
|
Signed: signed,
|
||||||
Jobs: jobs,
|
Verified: verified,
|
||||||
Keys: key,
|
User: user,
|
||||||
Netrc: netrc,
|
Repo: repo,
|
||||||
Config: string(raw),
|
Build: build,
|
||||||
Secret: string(sec),
|
BuildLast: last,
|
||||||
System: &model.System{
|
Job: job,
|
||||||
Link: httputil.GetURL(c.Request),
|
Netrc: netrc,
|
||||||
Plugins: strings.Split(os.Getenv("PLUGIN_FILTER"), " "),
|
Yaml: string(raw),
|
||||||
Globals: strings.Split(os.Getenv("PLUGIN_PARAMS"), " "),
|
Secrets: secs,
|
||||||
Escalates: strings.Split(os.Getenv("ESCALATE_FILTER"), " "),
|
System: &model.System{Link: httputil.GetURL(c.Request)},
|
||||||
},
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,100 +1,106 @@
|
||||||
package web
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/remote"
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/shared/crypto"
|
"github.com/drone/drone/shared/crypto"
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
"github.com/drone/drone/shared/token"
|
"github.com/drone/drone/shared/token"
|
||||||
"github.com/drone/drone/store"
|
"github.com/drone/drone/store"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetLogin(c *gin.Context) {
|
func GetLogin(c *gin.Context) {
|
||||||
remote := remote.FromContext(c)
|
|
||||||
|
|
||||||
// when dealing with redirects we may need
|
// when dealing with redirects we may need to adjust the content type. I
|
||||||
// to adjust the content type. I cannot, however,
|
// cannot, however, remember why, so need to revisit this line.
|
||||||
// remember why, so need to revisit this line.
|
|
||||||
c.Writer.Header().Del("Content-Type")
|
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 {
|
if err != nil {
|
||||||
log.Errorf("cannot authenticate user. %s", err)
|
logrus.Errorf("cannot authenticate user. %s", err)
|
||||||
c.Redirect(303, "/login?error=oauth_error")
|
c.Redirect(303, "/login?error=oauth_error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// this will happen when the user is redirected by
|
// this will happen when the user is redirected by the remote provider as
|
||||||
// the remote provide as part of the oauth dance.
|
// part of the authorization workflow.
|
||||||
if tmpuser == nil {
|
if tmpuser == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
config := ToConfig(c)
|
||||||
|
|
||||||
// get the user from the database
|
// get the user from the database
|
||||||
u, err := store.GetUserLogin(c, tmpuser.Login)
|
u, err := store.GetUserLogin(c, tmpuser.Login)
|
||||||
if err != nil {
|
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
|
// if self-registration is disabled we should return a not authorized error
|
||||||
// return a notAuthorized error. the only exception
|
if !config.Open {
|
||||||
// is if no users exist yet in the system we'll proceed.
|
logrus.Errorf("cannot register %s. registration closed", tmpuser.Login)
|
||||||
if !open && count != 0 {
|
|
||||||
log.Errorf("cannot register %s. registration closed", tmpuser.Login)
|
|
||||||
c.Redirect(303, "/login?error=access_denied")
|
c.Redirect(303, "/login?error=access_denied")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the user account
|
// create the user account
|
||||||
u = &model.User{}
|
u = &model.User{
|
||||||
u.Login = tmpuser.Login
|
Login: tmpuser.Login,
|
||||||
u.Token = tmpuser.Token
|
Token: tmpuser.Token,
|
||||||
u.Secret = tmpuser.Secret
|
Secret: tmpuser.Secret,
|
||||||
u.Email = tmpuser.Email
|
Email: tmpuser.Email,
|
||||||
u.Avatar = tmpuser.Avatar
|
Avatar: tmpuser.Avatar,
|
||||||
u.Hash = crypto.Rand()
|
Hash: crypto.Rand(),
|
||||||
|
}
|
||||||
|
|
||||||
// insert the user into the database
|
// insert the user into the database
|
||||||
if err := store.CreateUser(c, u); err != nil {
|
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")
|
c.Redirect(303, "/login?error=internal_error")
|
||||||
return
|
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
|
// update the user meta data and authorization data.
|
||||||
// data and cache in the datastore.
|
|
||||||
u.Token = tmpuser.Token
|
u.Token = tmpuser.Token
|
||||||
u.Secret = tmpuser.Secret
|
u.Secret = tmpuser.Secret
|
||||||
u.Email = tmpuser.Email
|
u.Email = tmpuser.Email
|
||||||
u.Avatar = tmpuser.Avatar
|
u.Avatar = tmpuser.Avatar
|
||||||
|
|
||||||
if err := store.UpdateUser(c, u); err != nil {
|
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")
|
c.Redirect(303, "/login?error=internal_error")
|
||||||
return
|
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()
|
exp := time.Now().Add(time.Hour * 72).Unix()
|
||||||
token := token.New(token.SessToken, u.Login)
|
token := token.New(token.SessToken, u.Login)
|
||||||
tokenstr, err := token.SignExpires(u.Hash, exp)
|
tokenstr, err := token.SignExpires(u.Hash, exp)
|
||||||
if err != nil {
|
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")
|
c.Redirect(303, "/login?error=internal_error")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -109,15 +115,12 @@ func GetLogin(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetLogout(c *gin.Context) {
|
func GetLogout(c *gin.Context) {
|
||||||
|
|
||||||
httputil.DelCookie(c.Writer, c.Request, "user_sess")
|
httputil.DelCookie(c.Writer, c.Request, "user_sess")
|
||||||
httputil.DelCookie(c.Writer, c.Request, "user_last")
|
httputil.DelCookie(c.Writer, c.Request, "user_last")
|
||||||
c.Redirect(303, "/login")
|
c.Redirect(303, "/login")
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetLoginToken(c *gin.Context) {
|
func GetLoginToken(c *gin.Context) {
|
||||||
remote := remote.FromContext(c)
|
|
||||||
|
|
||||||
in := &tokenPayload{}
|
in := &tokenPayload{}
|
||||||
err := c.Bind(in)
|
err := c.Bind(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -125,7 +128,7 @@ func GetLoginToken(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
login, err := remote.Auth(in.Access, in.Refresh)
|
login, err := remote.Auth(c, in.Access, in.Refresh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithError(http.StatusUnauthorized, err)
|
c.AbortWithError(http.StatusUnauthorized, err)
|
||||||
return
|
return
|
||||||
|
@ -156,3 +159,9 @@ type tokenPayload struct {
|
||||||
Refresh string `json:"refresh_token,omitempty"`
|
Refresh string `json:"refresh_token,omitempty"`
|
||||||
Expires int64 `json:"expires_in,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 (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -30,7 +30,7 @@ func ShowIndex(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter to only show the currently active ones
|
// filter to only show the currently active ones
|
||||||
activeRepos, err := store.GetRepoListOf(c,repos)
|
activeRepos, err := store.GetRepoListOf(c, repos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.String(400, err.Error())
|
c.String(400, err.Error())
|
||||||
return
|
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) {
|
func ShowRepo(c *gin.Context) {
|
||||||
user := session.User(c)
|
user := session.User(c)
|
||||||
repo := session.Repo(c)
|
repo := session.Repo(c)
|
||||||
|
@ -136,7 +116,6 @@ func ShowRepoConf(c *gin.Context) {
|
||||||
|
|
||||||
user := session.User(c)
|
user := session.User(c)
|
||||||
repo := session.Repo(c)
|
repo := session.Repo(c)
|
||||||
key, _ := store.GetKey(c, repo)
|
|
||||||
|
|
||||||
token, _ := token.New(
|
token, _ := token.New(
|
||||||
token.CsrfToken,
|
token.CsrfToken,
|
||||||
|
@ -146,7 +125,6 @@ func ShowRepoConf(c *gin.Context) {
|
||||||
c.HTML(200, "repo_config.html", gin.H{
|
c.HTML(200, "repo_config.html", gin.H{
|
||||||
"User": user,
|
"User": user,
|
||||||
"Repo": repo,
|
"Repo": repo,
|
||||||
"Key": key,
|
|
||||||
"Csrf": token,
|
"Csrf": token,
|
||||||
"Link": httputil.GetURL(c.Request),
|
"Link": httputil.GetURL(c.Request),
|
||||||
})
|
})
|
||||||
|
@ -227,10 +205,3 @@ func ShowBuild(c *gin.Context) {
|
||||||
"Csrf": csrf,
|
"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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
|
@ -1,16 +1,12 @@
|
||||||
package api
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
|
|
||||||
"github.com/drone/drone/cache"
|
"github.com/drone/drone/cache"
|
||||||
"github.com/drone/drone/model"
|
|
||||||
"github.com/drone/drone/remote"
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/router/middleware/session"
|
"github.com/drone/drone/router/middleware/session"
|
||||||
"github.com/drone/drone/shared/crypto"
|
"github.com/drone/drone/shared/crypto"
|
||||||
|
@ -74,19 +70,9 @@ func PostRepo(c *gin.Context) {
|
||||||
sig,
|
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
|
// activate the repository before we make any
|
||||||
// local changes to the database.
|
// local changes to the database.
|
||||||
err = remote.Activate(user, r, keys, link)
|
err = remote.Activate(user, r, link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.String(500, err.Error())
|
c.String(500, err.Error())
|
||||||
return
|
return
|
||||||
|
@ -98,12 +84,6 @@ func PostRepo(c *gin.Context) {
|
||||||
c.String(500, err.Error())
|
c.String(500, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
keys.RepoID = r.ID
|
|
||||||
err = store.CreateKey(c, keys)
|
|
||||||
if err != nil {
|
|
||||||
c.String(500, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(200, r)
|
c.JSON(200, r)
|
||||||
}
|
}
|
||||||
|
@ -157,45 +137,6 @@ func GetRepo(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, session.Repo(c))
|
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) {
|
func DeleteRepo(c *gin.Context) {
|
||||||
remote := remote.FromContext(c)
|
remote := remote.FromContext(c)
|
||||||
repo := session.Repo(c)
|
repo := session.Repo(c)
|
||||||
|
@ -210,44 +151,3 @@ func DeleteRepo(c *gin.Context) {
|
||||||
remote.Deactivate(user, repo, httputil.GetURL(c.Request))
|
remote.Deactivate(user, repo, httputil.GetURL(c.Request))
|
||||||
c.Writer.WriteHeader(http.StatusOK)
|
c.Writer.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PostSecure(c *gin.Context) {
|
|
||||||
repo := session.Repo(c)
|
|
||||||
|
|
||||||
in, err := ioutil.ReadAll(c.Request.Body)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithError(http.StatusBadRequest, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// we found some strange characters included in
|
|
||||||
// the yaml file when entered into a browser textarea.
|
|
||||||
// these need to be removed
|
|
||||||
in = bytes.Replace(in, []byte{'\xA0'}, []byte{' '}, -1)
|
|
||||||
|
|
||||||
// make sure the Yaml is valid format to prevent
|
|
||||||
// a malformed value from being used in the build
|
|
||||||
err = yaml.Unmarshal(in, &yaml.MapSlice{})
|
|
||||||
if err != nil {
|
|
||||||
c.String(http.StatusBadRequest, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := store.GetKey(c, repo)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// encrypts using go-jose
|
|
||||||
out, err := crypto.Encrypt(string(in), key.Private)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.String(http.StatusOK, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostReactivate(c *gin.Context) {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package api
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
|
@ -1,4 +1,4 @@
|
||||||
package api
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
|
@ -1,4 +1,4 @@
|
||||||
package web
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
|
@ -1,4 +1,4 @@
|
||||||
package web
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
@ -19,14 +19,9 @@ import (
|
||||||
"github.com/manucorporat/sse"
|
"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
|
// GetRepoEvents will upgrade the connection to a Websocket and will stream
|
||||||
// event updates to the browser.
|
// event updates to the browser.
|
||||||
func GetRepoEvents2(c *gin.Context) {
|
func GetRepoEvents(c *gin.Context) {
|
||||||
repo := session.Repo(c)
|
repo := session.Repo(c)
|
||||||
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
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)
|
repo := session.Repo(c)
|
||||||
buildn, _ := strconv.Atoi(c.Param("build"))
|
buildn, _ := strconv.Atoi(c.Param("build"))
|
|
@ -11,6 +11,7 @@
|
||||||
// - application/json
|
// - application/json
|
||||||
//
|
//
|
||||||
// swagger:meta
|
// 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 (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -6,31 +6,16 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/drone/drone/cache"
|
"github.com/drone/drone/cache"
|
||||||
"github.com/drone/drone/model"
|
|
||||||
"github.com/drone/drone/router/middleware/session"
|
"github.com/drone/drone/router/middleware/session"
|
||||||
"github.com/drone/drone/shared/crypto"
|
"github.com/drone/drone/shared/crypto"
|
||||||
"github.com/drone/drone/shared/token"
|
"github.com/drone/drone/shared/token"
|
||||||
"github.com/drone/drone/store"
|
"github.com/drone/drone/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// swagger:route GET /user user getUser
|
|
||||||
//
|
|
||||||
// Get the currently authenticated user.
|
|
||||||
//
|
|
||||||
// Responses:
|
|
||||||
// 200: user
|
|
||||||
//
|
|
||||||
func GetSelf(c *gin.Context) {
|
func GetSelf(c *gin.Context) {
|
||||||
c.JSON(200, session.User(c))
|
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) {
|
func GetFeed(c *gin.Context) {
|
||||||
repos, err := cache.GetRepos(c, session.User(c))
|
repos, err := cache.GetRepos(c, session.User(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -46,13 +31,6 @@ func GetFeed(c *gin.Context) {
|
||||||
c.JSON(200, feed)
|
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) {
|
func GetRepos(c *gin.Context) {
|
||||||
repos, err := cache.GetRepos(c, session.User(c))
|
repos, err := cache.GetRepos(c, session.User(c))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -105,27 +83,3 @@ func DeleteToken(c *gin.Context) {
|
||||||
}
|
}
|
||||||
c.String(http.StatusOK, tokenstr)
|
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 (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -10,13 +10,6 @@ import (
|
||||||
"github.com/drone/drone/store"
|
"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) {
|
func GetUsers(c *gin.Context) {
|
||||||
users, err := store.GetUserList(c)
|
users, err := store.GetUserList(c)
|
||||||
if err != nil {
|
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) {
|
func GetUser(c *gin.Context) {
|
||||||
user, err := store.GetUserLogin(c, c.Param("login"))
|
user, err := store.GetUserLogin(c, c.Param("login"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.String(404, "Cannot find user. %s", err)
|
c.String(404, "Cannot find user. %s", err)
|
||||||
} else {
|
return
|
||||||
c.JSON(200, user)
|
|
||||||
}
|
}
|
||||||
|
c.JSON(200, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PatchUser(c *gin.Context) {
|
func PatchUser(c *gin.Context) {
|
||||||
|
@ -74,31 +60,20 @@ func PostUser(c *gin.Context) {
|
||||||
c.String(http.StatusBadRequest, err.Error())
|
c.String(http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
user := &model.User{
|
||||||
user := &model.User{}
|
Active: true,
|
||||||
user.Login = in.Login
|
Login: in.Login,
|
||||||
user.Email = in.Email
|
Email: in.Email,
|
||||||
user.Admin = in.Admin
|
Avatar: in.Avatar,
|
||||||
user.Avatar = in.Avatar
|
Hash: crypto.Rand(),
|
||||||
user.Active = true
|
}
|
||||||
user.Hash = crypto.Rand()
|
if err = store.CreateUser(c, user); err != nil {
|
||||||
|
|
||||||
err = store.CreateUser(c, user)
|
|
||||||
if err != nil {
|
|
||||||
c.String(http.StatusInternalServerError, err.Error())
|
c.String(http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, user)
|
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) {
|
func DeleteUser(c *gin.Context) {
|
||||||
user, err := store.GetUserLogin(c, c.Param("login"))
|
user, err := store.GetUserLogin(c, c.Param("login"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -107,7 +82,7 @@ func DeleteUser(c *gin.Context) {
|
||||||
}
|
}
|
||||||
if err = store.DeleteUser(c, user); err != nil {
|
if err = store.DeleteUser(c, user); err != nil {
|
||||||
c.String(500, "Error deleting user. %s", err)
|
c.String(500, "Error deleting user. %s", err)
|
||||||
} else {
|
return
|
||||||
c.String(200, "")
|
|
||||||
}
|
}
|
||||||
|
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",
|
Email: "foo@bar.com",
|
||||||
Avatar: "b9015b0857e16ac4d94a0ffd9a0b79c8",
|
Avatar: "b9015b0857e16ac4d94a0ffd9a0b79c8",
|
||||||
Active: true,
|
Active: true,
|
||||||
Admin: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.CreateUser(&user)
|
s.CreateUser(&user)
|
||||||
|
@ -71,7 +70,6 @@ func TestUsers(t *testing.T) {
|
||||||
g.Assert(user.Email).Equal(getuser.Email)
|
g.Assert(user.Email).Equal(getuser.Email)
|
||||||
g.Assert(user.Avatar).Equal(getuser.Avatar)
|
g.Assert(user.Avatar).Equal(getuser.Avatar)
|
||||||
g.Assert(user.Active).Equal(getuser.Active)
|
g.Assert(user.Active).Equal(getuser.Active)
|
||||||
g.Assert(user.Admin).Equal(getuser.Admin)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
g.It("Should Get a User By Login", func() {
|
g.It("Should Get a User By Login", func() {
|
||||||
|
|
|
@ -54,18 +54,6 @@ type Store interface {
|
||||||
// DeleteRepo deletes a user repository.
|
// DeleteRepo deletes a user repository.
|
||||||
DeleteRepo(*model.Repo) error
|
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 gets a list of repository secrets
|
||||||
GetSecretList(*model.Repo) ([]*model.Secret, error)
|
GetSecretList(*model.Repo) ([]*model.Secret, error)
|
||||||
|
|
||||||
|
@ -125,21 +113,6 @@ type Store interface {
|
||||||
|
|
||||||
// WriteLog writes the job logs to the datastore.
|
// WriteLog writes the job logs to the datastore.
|
||||||
WriteLog(*model.Job, io.Reader) error
|
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.
|
// 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)
|
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) {
|
func GetSecretList(c context.Context, r *model.Repo) ([]*model.Secret, error) {
|
||||||
return FromContext(c).GetSecretList(r)
|
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 {
|
func WriteLog(c context.Context, job *model.Job, r io.Reader) error {
|
||||||
return FromContext(c).WriteLog(job, r)
|
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
|
i.material-icons expand_more
|
||||||
div.dropdown-menu.dropdown-menu-right
|
div.dropdown-menu.dropdown-menu-right
|
||||||
a.dropdown-item[href="/settings/profile"] Profile
|
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
|
a.dropdown-item[href="/logout"] Logout
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -65,10 +65,6 @@ block content
|
||||||
else
|
else
|
||||||
input#trusted[type="checkbox"][hidden="hidden"]
|
input#trusted[type="checkbox"][hidden="hidden"]
|
||||||
label.switch[for="trusted"]
|
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.row
|
||||||
div.col-md-12
|
div.col-md-12
|
||||||
div.alert.alert-danger
|
div.alert.alert-danger
|
||||||
|
|
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