From df7068ef3224ecc01062093a9b6e80dac59ded34 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 12 Apr 2016 10:12:14 -0700 Subject: [PATCH 1/6] stub for slash commands --- router/router.go | 8 +++++++- web/slack.go | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 web/slack.go diff --git a/router/router.go b/router/router.go index b44ccb325..15d935364 100644 --- a/router/router.go +++ b/router/router.go @@ -142,6 +142,12 @@ func Load(middleware ...gin.HandlerFunc) http.Handler { stream.GET("/:owner/:name/:build/:number", web.GetStream) } + slash := e.Group("/slash/slack") + { + slash.Use(session.MustUser()) + slash.POST("/slash", web.Slack) + } + auth := e.Group("/authorize") { auth.GET("", web.GetLogin) @@ -172,7 +178,7 @@ func normalize(h http.Handler) http.Handler { parts := strings.Split(r.URL.Path, "/")[1:] switch parts[0] { - case "settings", "repos", "api", "login", "logout", "", "authorize", "hook", "static", "gitlab": + case "settings", "slash", "repos", "api", "login", "logout", "", "authorize", "hook", "static", "gitlab": // no-op default: diff --git a/web/slack.go b/web/slack.go new file mode 100644 index 000000000..add479e73 --- /dev/null +++ b/web/slack.go @@ -0,0 +1,9 @@ +package web + +import "github.com/gin-gonic/gin" + +// Slack is handler function that handles Slack slash commands. +func Slack(c *gin.Context) { + text := c.PostForm("text") + c.String(200, "received message %s", text) +} From d635097105f2c0d91f3dce9d3dcd9b9cf87ac4d8 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 12 Apr 2016 10:13:26 -0700 Subject: [PATCH 2/6] fixed router path --- router/router.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/router/router.go b/router/router.go index 15d935364..f021e7346 100644 --- a/router/router.go +++ b/router/router.go @@ -142,10 +142,10 @@ func Load(middleware ...gin.HandlerFunc) http.Handler { stream.GET("/:owner/:name/:build/:number", web.GetStream) } - slash := e.Group("/slash/slack") + bots := e.Group("/bots") { - slash.Use(session.MustUser()) - slash.POST("/slash", web.Slack) + bots.Use(session.MustUser()) + bots.POST("/slack", web.Slack) } auth := e.Group("/authorize") From 42d6d8d3b2f3a2d85c62c0c830a5b6d311806c59 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 12 Apr 2016 10:14:10 -0700 Subject: [PATCH 3/6] fix normalize routes --- router/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/router.go b/router/router.go index f021e7346..7d9484f44 100644 --- a/router/router.go +++ b/router/router.go @@ -178,7 +178,7 @@ func normalize(h http.Handler) http.Handler { parts := strings.Split(r.URL.Path, "/")[1:] switch parts[0] { - case "settings", "slash", "repos", "api", "login", "logout", "", "authorize", "hook", "static", "gitlab": + case "settings", "bots", "repos", "api", "login", "logout", "", "authorize", "hook", "static", "gitlab": // no-op default: From 9b306a1bc8432375f8d33abae8cd121fae4b71a0 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 12 Apr 2016 13:08:17 -0700 Subject: [PATCH 4/6] load all things via middleware --- api/build.go | 5 +- api/node.go | 6 +- drone.go | 30 +---- engine/context.go | 23 ++++ engine/engine.go | 6 +- remote/bitbucket/bitbucket.go | 4 +- remote/github/github.go | 4 +- remote/gitlab/gitlab.go | 5 +- remote/gogs/gogs.go | 5 +- remote/remote.go | 26 ----- router/middleware/cache.go | 22 ++++ router/middleware/cache/cache.go | 14 --- router/middleware/context/context.go | 37 ------- router/middleware/engine.go | 28 +++++ router/middleware/location/location.go | 95 ---------------- router/middleware/location/location_test.go | 48 -------- router/middleware/remote.go | 45 ++++++++ router/middleware/store.go | 29 +++++ router/middleware/version.go | 14 +++ router/router.go | 3 +- shared/envconfig/envconfig.go | 117 -------------------- store/datastore/store.go | 15 --- web/hook.go | 3 +- web/slack.go | 108 +++++++++++++++++- web/stream.go | 5 +- 25 files changed, 289 insertions(+), 408 deletions(-) create mode 100644 engine/context.go create mode 100644 router/middleware/cache.go delete mode 100644 router/middleware/cache/cache.go delete mode 100644 router/middleware/context/context.go create mode 100644 router/middleware/engine.go delete mode 100644 router/middleware/location/location.go delete mode 100644 router/middleware/location/location_test.go create mode 100644 router/middleware/remote.go create mode 100644 router/middleware/store.go create mode 100644 router/middleware/version.go delete mode 100644 shared/envconfig/envconfig.go diff --git a/api/build.go b/api/build.go index 45c20bce1..75ec3730b 100644 --- a/api/build.go +++ b/api/build.go @@ -18,7 +18,6 @@ import ( "github.com/gin-gonic/gin" "github.com/drone/drone/model" - "github.com/drone/drone/router/middleware/context" "github.com/drone/drone/router/middleware/session" ) @@ -130,7 +129,7 @@ func GetBuildLogs(c *gin.Context) { } func DeleteBuild(c *gin.Context) { - engine_ := context.Engine(c) + engine_ := engine.FromContext(c) repo := session.Repo(c) // parse the build number and job sequence number from @@ -281,7 +280,7 @@ func PostBuild(c *gin.Context) { // on status change notifications last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID) - engine_ := context.Engine(c) + engine_ := engine.FromContext(c) go engine_.Schedule(c.Copy(), &engine.Task{ User: user, Repo: repo, diff --git a/api/node.go b/api/node.go index 90b731795..592ec0c1b 100644 --- a/api/node.go +++ b/api/node.go @@ -6,8 +6,8 @@ import ( "github.com/gin-gonic/gin" + "github.com/drone/drone/engine" "github.com/drone/drone/model" - "github.com/drone/drone/router/middleware/context" "github.com/drone/drone/store" ) @@ -25,7 +25,7 @@ func GetNode(c *gin.Context) { } func PostNode(c *gin.Context) { - engine := context.Engine(c) + engine := engine.FromContext(c) in := struct { Addr string `json:"address"` @@ -63,7 +63,7 @@ func PostNode(c *gin.Context) { } func DeleteNode(c *gin.Context) { - engine := context.Engine(c) + engine := engine.FromContext(c) id, _ := strconv.Atoi(c.Param("node")) node, err := store.GetNode(c, int64(id)) diff --git a/drone.go b/drone.go index fe517f61f..a0cae10cc 100644 --- a/drone.go +++ b/drone.go @@ -4,14 +4,8 @@ import ( "net/http" "time" - "github.com/drone/drone/engine" - "github.com/drone/drone/remote" "github.com/drone/drone/router" - "github.com/drone/drone/router/middleware/cache" - "github.com/drone/drone/router/middleware/context" - "github.com/drone/drone/router/middleware/header" - "github.com/drone/drone/shared/envconfig" - "github.com/drone/drone/store/datastore" + "github.com/drone/drone/router/middleware" "github.com/Sirupsen/logrus" "github.com/gin-gonic/contrib/ginrus" @@ -37,26 +31,14 @@ func main() { logrus.SetLevel(logrus.WarnLevel) } - // Load the configuration from env file - env := envconfig.Load(".env") - - // Setup the database driver - store_ := datastore.Load(env) - - // setup the remote driver - remote_ := remote.Load(env) - - // setup the runner - engine_ := engine.Load(env, store_) - // setup the server and start the listener handler := router.Load( ginrus.Ginrus(logrus.StandardLogger(), time.RFC3339, true), - header.Version, - cache.Default(), - context.SetStore(store_), - context.SetRemote(remote_), - context.SetEngine(engine_), + middleware.Version, + middleware.Cache(), + middleware.Store(), + middleware.Remote(), + middleware.Engine(), ) if *cert != "" { diff --git a/engine/context.go b/engine/context.go new file mode 100644 index 000000000..2321fa071 --- /dev/null +++ b/engine/context.go @@ -0,0 +1,23 @@ +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) +} diff --git a/engine/engine.go b/engine/engine.go index bf3569c2f..32a2c6a10 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "io/ioutil" + "os" "runtime" "time" @@ -15,7 +16,6 @@ import ( "github.com/docker/docker/pkg/stdcopy" "github.com/drone/drone/model" "github.com/drone/drone/shared/docker" - "github.com/drone/drone/shared/envconfig" "github.com/drone/drone/store" "github.com/samalba/dockerclient" "golang.org/x/net/context" @@ -60,7 +60,7 @@ type engine struct { // 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(env envconfig.Env, s store.Store) Engine { +func Load(s store.Store) Engine { engine := &engine{} engine.bus = newEventbus() engine.pool = newPool() @@ -70,7 +70,7 @@ func Load(env envconfig.Env, s store.Store) Engine { // throughout the build environment. var proxyVars = []string{"HTTP_PROXY", "http_proxy", "HTTPS_PROXY", "https_proxy", "NO_PROXY", "no_proxy"} for _, proxyVar := range proxyVars { - proxyVal := env.Get(proxyVar) + proxyVal := os.Getenv(proxyVar) if len(proxyVal) != 0 { engine.envs = append(engine.envs, proxyVar+"="+proxyVal) } diff --git a/remote/bitbucket/bitbucket.go b/remote/bitbucket/bitbucket.go index 12d8a076f..94735e5ee 100644 --- a/remote/bitbucket/bitbucket.go +++ b/remote/bitbucket/bitbucket.go @@ -9,7 +9,6 @@ import ( "strconv" "github.com/drone/drone/model" - "github.com/drone/drone/shared/envconfig" "github.com/drone/drone/shared/httputil" log "github.com/Sirupsen/logrus" @@ -24,8 +23,7 @@ type Bitbucket struct { Open bool } -func Load(env envconfig.Env) *Bitbucket { - config := env.String("REMOTE_CONFIG", "") +func Load(config string) *Bitbucket { // parse the remote DSN configuration string url_, err := url.Parse(config) diff --git a/remote/github/github.go b/remote/github/github.go index 9ace24773..e935f20f7 100644 --- a/remote/github/github.go +++ b/remote/github/github.go @@ -11,7 +11,6 @@ import ( "strings" "github.com/drone/drone/model" - "github.com/drone/drone/shared/envconfig" "github.com/drone/drone/shared/httputil" "github.com/drone/drone/shared/oauth2" @@ -42,8 +41,7 @@ type Github struct { GitSSH bool } -func Load(env envconfig.Env) *Github { - config := env.String("REMOTE_CONFIG", "") +func Load(config string) *Github { // parse the remote DSN configuration string url_, err := url.Parse(config) diff --git a/remote/gitlab/gitlab.go b/remote/gitlab/gitlab.go index 1be25292d..60ffc5ed2 100644 --- a/remote/gitlab/gitlab.go +++ b/remote/gitlab/gitlab.go @@ -10,7 +10,6 @@ import ( "strings" "github.com/drone/drone/model" - "github.com/drone/drone/shared/envconfig" "github.com/drone/drone/shared/httputil" "github.com/drone/drone/shared/oauth2" "github.com/drone/drone/shared/token" @@ -35,9 +34,7 @@ type Gitlab struct { Search bool } -func Load(env envconfig.Env) *Gitlab { - config := env.String("REMOTE_CONFIG", "") - +func Load(config string) *Gitlab { url_, err := url.Parse(config) if err != nil { panic(err) diff --git a/remote/gogs/gogs.go b/remote/gogs/gogs.go index 1a3535a5d..d30a63236 100644 --- a/remote/gogs/gogs.go +++ b/remote/gogs/gogs.go @@ -9,7 +9,6 @@ import ( "strconv" "github.com/drone/drone/model" - "github.com/drone/drone/shared/envconfig" "github.com/gogits/go-gogs-client" log "github.com/Sirupsen/logrus" @@ -22,9 +21,7 @@ type Gogs struct { SkipVerify bool } -func Load(env envconfig.Env) *Gogs { - config := env.String("REMOTE_CONFIG", "") - +func Load(config string) *Gogs { // parse the remote DSN configuration string url_, err := url.Parse(config) if err != nil { diff --git a/remote/remote.go b/remote/remote.go index 192154eab..875988d20 100644 --- a/remote/remote.go +++ b/remote/remote.go @@ -6,36 +6,10 @@ import ( "net/http" "github.com/drone/drone/model" - "github.com/drone/drone/remote/bitbucket" - "github.com/drone/drone/remote/github" - "github.com/drone/drone/remote/gitlab" - "github.com/drone/drone/remote/gogs" - "github.com/drone/drone/shared/envconfig" - "github.com/Sirupsen/logrus" "golang.org/x/net/context" ) -func Load(env envconfig.Env) Remote { - driver := env.Get("REMOTE_DRIVER") - - switch driver { - case "bitbucket": - return bitbucket.Load(env) - case "github": - return github.Load(env) - case "gitlab": - return gitlab.Load(env) - case "gogs": - return gogs.Load(env) - - default: - logrus.Fatalf("unknown remote driver %s", driver) - } - - return nil -} - type Remote interface { // Login authenticates the session and returns the // remote user details. diff --git a/router/middleware/cache.go b/router/middleware/cache.go new file mode 100644 index 000000000..aa8d46b4d --- /dev/null +++ b/router/middleware/cache.go @@ -0,0 +1,22 @@ +package middleware + +import ( + "time" + + "github.com/drone/drone/cache" + + "github.com/gin-gonic/gin" + "github.com/ianschenck/envflag" +) + +var ttl = envflag.Duration("CACHE_TTL", time.Minute*15, "") + +// Cache is a middleware function that initializes the Cache and attaches to +// the context of every http.Request. +func Cache() gin.HandlerFunc { + cc := cache.NewTTL(*ttl) + return func(c *gin.Context) { + cache.ToContext(c, cc) + c.Next() + } +} diff --git a/router/middleware/cache/cache.go b/router/middleware/cache/cache.go deleted file mode 100644 index 72fa0279e..000000000 --- a/router/middleware/cache/cache.go +++ /dev/null @@ -1,14 +0,0 @@ -package cache - -import ( - "github.com/drone/drone/cache" - "github.com/gin-gonic/gin" -) - -func Default() gin.HandlerFunc { - cc := cache.Default() - return func(c *gin.Context) { - cache.ToContext(c, cc) - c.Next() - } -} diff --git a/router/middleware/context/context.go b/router/middleware/context/context.go deleted file mode 100644 index 2c81ee456..000000000 --- a/router/middleware/context/context.go +++ /dev/null @@ -1,37 +0,0 @@ -package context - -import ( - "github.com/drone/drone/engine" - "github.com/drone/drone/remote" - "github.com/drone/drone/store" - "github.com/gin-gonic/gin" -) - -func SetStore(s store.Store) gin.HandlerFunc { - return func(c *gin.Context) { - store.ToContext(c, s) - c.Next() - } -} - -func SetRemote(remote remote.Remote) gin.HandlerFunc { - return func(c *gin.Context) { - c.Set("remote", remote) - c.Next() - } -} - -func Remote(c *gin.Context) remote.Remote { - return c.MustGet("remote").(remote.Remote) -} - -func SetEngine(engine engine.Engine) gin.HandlerFunc { - return func(c *gin.Context) { - c.Set("engine", engine) - c.Next() - } -} - -func Engine(c *gin.Context) engine.Engine { - return c.MustGet("engine").(engine.Engine) -} diff --git a/router/middleware/engine.go b/router/middleware/engine.go new file mode 100644 index 000000000..01da07067 --- /dev/null +++ b/router/middleware/engine.go @@ -0,0 +1,28 @@ +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() + } +} diff --git a/router/middleware/location/location.go b/router/middleware/location/location.go deleted file mode 100644 index 9e5290113..000000000 --- a/router/middleware/location/location.go +++ /dev/null @@ -1,95 +0,0 @@ -package location - -import ( - "net/http" - "strings" - - "github.com/gin-gonic/gin" -) - -// Resolve is a middleware function that resolves the hostname -// and scheme for the http.Request and adds to the context. -func Resolve(c *gin.Context) { - c.Set("host", resolveHost(c.Request)) - c.Set("scheme", resolveScheme(c.Request)) - c.Next() -} - -// parseHeader parses non unique headers value -// from a http.Request and return a slice of the values -// queried from the header -func parseHeader(r *http.Request, header string, token string) (val []string) { - for _, v := range r.Header[header] { - options := strings.Split(v, ";") - for _, o := range options { - keyvalue := strings.Split(o, "=") - var key, value string - if len(keyvalue) > 1 { - key, value = strings.TrimSpace(keyvalue[0]), strings.TrimSpace(keyvalue[1]) - } - key = strings.ToLower(key) - if key == token { - val = append(val, value) - } - } - } - return -} - -// resolveScheme is a helper function that evaluates the http.Request -// and returns the scheme, HTTP or HTTPS. It is able to detect, -// using the X-Forwarded-Proto, if the original request was HTTPS -// and routed through a reverse proxy with SSL termination. -func resolveScheme(r *http.Request) string { - switch { - case r.URL.Scheme == "https": - return "https" - case r.TLS != nil: - return "https" - case strings.HasPrefix(r.Proto, "HTTPS"): - return "https" - case r.Header.Get("X-Forwarded-Proto") == "https": - return "https" - case len(r.Header.Get("Forwarded")) != 0 && len(parseHeader(r, "Forwarded", "proto")) != 0 && parseHeader(r, "Forwarded", "proto")[0] == "https": - return "https" - default: - return "http" - } -} - -// resolveHost is a helper function that evaluates the http.Request -// and returns the hostname. It is able to detect, using the -// X-Forarded-For header, the original hostname when routed -// through a reverse proxy. -func resolveHost(r *http.Request) string { - switch { - case len(r.Host) != 0: - return r.Host - case len(r.URL.Host) != 0: - return r.URL.Host - case len(r.Header.Get("X-Forwarded-For")) != 0: - return r.Header.Get("X-Forwarded-For") - case len(r.Header.Get("Forwarded")) != 0 && len(parseHeader(r, "Forwarded", "for")) != 0: - return parseHeader(r, "Forwarded", "for")[0] - case len(r.Header.Get("X-Host")) != 0: - return r.Header.Get("X-Host") - case len(r.Header.Get("Forwarded")) != 0 && len(parseHeader(r, "Forwarded", "host")) != 0: - return parseHeader(r, "Forwarded", "host")[0] - case len(r.Header.Get("XFF")) != 0: - return r.Header.Get("XFF") - case len(r.Header.Get("X-Real-IP")) != 0: - return r.Header.Get("X-Real-IP") - default: - return "localhost:8000" - } -} - -// Hostname returns the hostname associated with -// the current context. -func Hostname(c *gin.Context) (host string) { - v, ok := c.Get("host") - if ok { - host = v.(string) - } - return -} diff --git a/router/middleware/location/location_test.go b/router/middleware/location/location_test.go deleted file mode 100644 index 46ddca676..000000000 --- a/router/middleware/location/location_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package location - -import ( - "github.com/franela/goblin" - "net/http" - "reflect" - "testing" -) - -var mockHeader []string -var mockRequest *http.Request - -var wronglyFormedHeader []string -var wronglyFormedRequest *http.Request - -func init() { - mockHeader = []string{"For= 110.0.2.2", "for = \"[::1]\"; Host=example.com; foR=10.2.3.4; pRoto =https ; By = 127.0.0.1"} - mockRequest = &http.Request{Header: map[string][]string{"Forwarded": mockHeader}} - wronglyFormedHeader = []string{"Fro= 110.0.2.2", "for = \"[:1]\"% Host=example:.com| foR=10.278.3.4% poto =https | Bi % 127.0.0.1", ""} - wronglyFormedRequest = &http.Request{Header: map[string][]string{"Forwarded": wronglyFormedHeader}} -} - -func TestParseForwardedHeadersProto(t *testing.T) { - g := goblin.Goblin(t) - - g.Describe("Parse proto Forwarded Headers", func() { - g.It("Should parse a normal proto Forwarded header", func() { - parsedHeader := parseHeader(mockRequest, "Forwarded", "proto") - g.Assert("https" == parsedHeader[0]).IsTrue() - }) - g.It("Should parse a normal for Forwarded header", func() { - parsedHeader := parseHeader(mockRequest, "Forwarded", "for") - g.Assert(reflect.DeepEqual([]string{"110.0.2.2", "\"[::1]\"", "10.2.3.4"}, parsedHeader)).IsTrue() - }) - g.It("Should parse a normal host Forwarded header", func() { - parsedHeader := parseHeader(mockRequest, "Forwarded", "host") - g.Assert("example.com" == parsedHeader[0]).IsTrue() - }) - g.It("Should parse a normal by Forwarded header", func() { - parsedHeader := parseHeader(mockRequest, "Forwarded", "by") - g.Assert("127.0.0.1" == parsedHeader[0]).IsTrue() - }) - g.It("Should not crash if a wrongly formed Forwarder header is sent", func() { - parsedHeader := parseHeader(wronglyFormedRequest, "Forwarded", "by") - g.Assert(len(parsedHeader) == 0).IsTrue() - }) - }) -} diff --git a/router/middleware/remote.go b/router/middleware/remote.go new file mode 100644 index 000000000..0c49ae361 --- /dev/null +++ b/router/middleware/remote.go @@ -0,0 +1,45 @@ +package middleware + +import ( + "github.com/drone/drone/remote" + "github.com/drone/drone/remote/bitbucket" + "github.com/drone/drone/remote/github" + "github.com/drone/drone/remote/gitlab" + "github.com/drone/drone/remote/gogs" + + "github.com/Sirupsen/logrus" + "github.com/gin-gonic/gin" + "github.com/ianschenck/envflag" +) + +var ( + driver = envflag.String("REMOTE_DRIVER", "", "") + config = envflag.String("REMOTE_CONFIG", "", "") +) + +// Remote is a middleware function that initializes the Remote and attaches to +// the context of every http.Request. +func Remote() gin.HandlerFunc { + + logrus.Infof("using remote driver %s", *driver) + logrus.Infof("using remote config %s", *config) + + var remote_ remote.Remote + switch *driver { + case "github": + remote_ = github.Load(*config) + case "bitbucket": + remote_ = bitbucket.Load(*config) + case "gogs": + remote_ = gogs.Load(*config) + case "gitlab": + remote_ = gitlab.Load(*config) + default: + logrus.Fatalln("remote configuraiton not found") + } + + return func(c *gin.Context) { + remote.ToContext(c, remote_) + c.Next() + } +} diff --git a/router/middleware/store.go b/router/middleware/store.go new file mode 100644 index 000000000..91439d433 --- /dev/null +++ b/router/middleware/store.go @@ -0,0 +1,29 @@ +package middleware + +import ( + "github.com/drone/drone/store" + "github.com/drone/drone/store/datastore" + + "github.com/Sirupsen/logrus" + "github.com/gin-gonic/gin" + "github.com/ianschenck/envflag" +) + +var ( + database = envflag.String("DATABASE_DRIVER", "sqlite3", "") + datasource = envflag.String("DATABASE_CONFIG", "drone.sqlite", "") +) + +// Store is a middleware function that initializes the Datastore and attaches to +// the context of every http.Request. +func Store() gin.HandlerFunc { + db := datastore.New(*database, *datasource) + + logrus.Infof("using database driver %s", *database) + logrus.Infof("using database config %s", *datasource) + + return func(c *gin.Context) { + store.ToContext(c, db) + c.Next() + } +} diff --git a/router/middleware/version.go b/router/middleware/version.go new file mode 100644 index 000000000..b4de05f96 --- /dev/null +++ b/router/middleware/version.go @@ -0,0 +1,14 @@ +package middleware + +import ( + "github.com/drone/drone/version" + "github.com/gin-gonic/gin" +) + +// Version is a middleware function that appends the Drone +// version information to the HTTP response. This is intended +// for debugging and troubleshooting. +func Version(c *gin.Context) { + c.Header("X-DRONE-VERSION", version.Version) + c.Next() +} diff --git a/router/router.go b/router/router.go index 7d9484f44..84587352a 100644 --- a/router/router.go +++ b/router/router.go @@ -8,7 +8,6 @@ import ( "github.com/drone/drone/api" "github.com/drone/drone/router/middleware/header" - "github.com/drone/drone/router/middleware/location" "github.com/drone/drone/router/middleware/session" "github.com/drone/drone/router/middleware/token" "github.com/drone/drone/static" @@ -23,7 +22,6 @@ func Load(middleware ...gin.HandlerFunc) http.Handler { e.SetHTMLTemplate(template.Load()) e.StaticFS("/static", static.FileSystem()) - e.Use(location.Resolve) e.Use(header.NoCache) e.Use(header.Options) e.Use(header.Secure) @@ -146,6 +144,7 @@ func Load(middleware ...gin.HandlerFunc) http.Handler { { bots.Use(session.MustUser()) bots.POST("/slack", web.Slack) + bots.POST("/slack/:command", web.Slack) } auth := e.Group("/authorize") diff --git a/shared/envconfig/envconfig.go b/shared/envconfig/envconfig.go deleted file mode 100644 index 26059aacb..000000000 --- a/shared/envconfig/envconfig.go +++ /dev/null @@ -1,117 +0,0 @@ -package envconfig - -import ( - "bufio" - "errors" - "os" - "strconv" - "strings" -) - -type Env map[string]string - -// Get returns the value of the environment variable named by the key. -func (env Env) Get(key string) string { - return env[key] -} - -// String returns the string value of the environment variable named by the -// key. If the variable is not present, the default value is returned. -func (env Env) String(key, value string) string { - got, ok := env[key] - if ok { - value = got - } - return value -} - -// Bool returns the boolean value of the environment variable named by the key. -// If the variable is not present, the default value is returned. -func (env Env) Bool(name string, value bool) bool { - got, ok := env[name] - if ok { - value, _ = strconv.ParseBool(got) - } - return value -} - -// Int returns the integer value of the environment variable named by the key. -// If the variable is not present, the default value is returned. -func (env Env) Int(name string, value int) int { - got, ok := env[name] - if ok { - value, _ = strconv.Atoi(got) - } - return value -} - -// Load reads the environment file and reads variables in "key=value" format. -// Then it read the system environment variables. It returns the combined -// results in a key value map. -func Load(filepath string) Env { - var envs = map[string]string{} - - // load the environment file - f, err := os.Open(filepath) - if err == nil { - defer f.Close() - - r := bufio.NewReader(f) - for { - line, _, err := r.ReadLine() - if err != nil { - break - } - - key, val, err := parseln(string(line)) - if err != nil { - continue - } - - os.Setenv(key, val) - } - } - - // load the environment variables - for _, env := range os.Environ() { - key, val, err := parseln(env) - if err != nil { - continue - } - - envs[key] = val - } - - return Env(envs) -} - -// helper function to parse a "key=value" environment variable string. -func parseln(line string) (key string, val string, err error) { - line = removeComments(line) - if len(line) == 0 { - return - } - splits := strings.SplitN(line, "=", 2) - - if len(splits) < 2 { - err = errors.New("missing delimiter '='") - return - } - - key = strings.Trim(splits[0], " ") - val = strings.Trim(splits[1], ` "'`) - return -} - -// helper function to trim comments and whitespace from a string. -func removeComments(s string) (_ string) { - if len(s) == 0 || string(s[0]) == "#" { - return - } else { - index := strings.Index(s, " #") - if index > -1 { - s = strings.TrimSpace(s[0:index]) - } - } - return s -} diff --git a/store/datastore/store.go b/store/datastore/store.go index 4619c082e..18169d6a9 100644 --- a/store/datastore/store.go +++ b/store/datastore/store.go @@ -5,7 +5,6 @@ import ( "os" "time" - "github.com/drone/drone/shared/envconfig" "github.com/drone/drone/store" "github.com/drone/drone/store/datastore/ddl" _ "github.com/go-sql-driver/mysql" @@ -23,20 +22,6 @@ type datastore struct { *sql.DB } -// Load opens a new database connection with the specified driver -// and connection string specified in the environment variables. -func Load(env envconfig.Env) store.Store { - var ( - driver = env.String("DATABASE_DRIVER", "sqlite3") - config = env.String("DATABASE_CONFIG", "drone.sqlite") - ) - - logrus.Infof("using database driver %s", driver) - logrus.Infof("using database config %s", config) - - return New(driver, config) -} - // New creates a database connection for the given driver and datasource // and returns a new Store. func New(driver, config string) store.Store { diff --git a/web/hook.go b/web/hook.go index 30fec18e8..a476e5f13 100644 --- a/web/hook.go +++ b/web/hook.go @@ -14,7 +14,6 @@ import ( "github.com/drone/drone/engine/parser" "github.com/drone/drone/model" "github.com/drone/drone/remote" - "github.com/drone/drone/router/middleware/context" "github.com/drone/drone/shared/httputil" "github.com/drone/drone/shared/token" "github.com/drone/drone/store" @@ -205,7 +204,7 @@ func PostHook(c *gin.Context) { // on status change notifications last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID) - engine_ := context.Engine(c) + engine_ := engine.FromContext(c) go engine_.Schedule(c.Copy(), &engine.Task{ User: user, Repo: repo, diff --git a/web/slack.go b/web/slack.go index add479e73..bb03422ce 100644 --- a/web/slack.go +++ b/web/slack.go @@ -1,9 +1,113 @@ package web -import "github.com/gin-gonic/gin" +import ( + "strings" + + "github.com/drone/drone/store" + + "github.com/gin-gonic/gin" +) + +const ( + slashDeploy = "deploy" + slashRestart = "restart" + slashStatus = "status" +) // Slack is handler function that handles Slack slash commands. func Slack(c *gin.Context) { + command := c.Param("command") text := c.PostForm("text") - c.String(200, "received message %s", text) + args := strings.Split(text, " ") + + if command == "" { + command = args[0] + args = args[1:] + } + + switch command { + case slashStatus: + slackStatus(c, args) + + case slashRestart: + slackRestart(c, args) + + case slashDeploy: + slackDeploy(c, args) + + default: + c.String(200, "sorry, I didn't understand [%s]", text) + } +} + +func slackDeploy(c *gin.Context, args []string) { + if len(args) != 3 { + c.String(200, "Invalid command. Please provide [repo] [build number] [environment]") + return + } + var ( + repo = args[0] + num = args[1] + env = args[2] + ) + owner, name, _ := parseRepoBranch(repo) + + c.String(200, "deploying build %s/%s#%s to %s", owner, name, num, env) +} + +func slackRestart(c *gin.Context, args []string) { + var ( + repo = args[0] + num = args[1] + ) + owner, name, _ := parseRepoBranch(repo) + + c.String(200, "restarting build %s/%s#%s", owner, name, num) +} + +func slackStatus(c *gin.Context, args []string) { + var ( + owner string + name string + branch string + ) + if len(args) > 0 { + owner, name, branch = parseRepoBranch(args[0]) + } + + repo, err := store.GetRepoOwnerName(c, owner, name) + if err != nil { + c.String(200, "cannot find repository %s/%s", owner, name) + return + } + if branch == "" { + branch = repo.Branch + } + build, err := store.GetBuildLast(c, repo, branch) + if err != nil { + c.String(200, "cannot find status for %s/%s@%s", owner, name, branch) + return + } + c.String(200, "%s@%s build number %d finished with status %s", + repo.FullName, + build.Branch, + build.Number, + build.Status, + ) +} + +func parseRepoBranch(repo string) (owner, name, branch string) { + + parts := strings.Split(repo, "@") + if len(parts) == 2 { + branch = parts[1] + repo = parts[0] + } + + parts = strings.Split(repo, "/") + if len(parts) == 2 { + owner = parts[0] + name = parts[1] + } + return owner, name, branch } diff --git a/web/stream.go b/web/stream.go index e06b74caf..623beb827 100644 --- a/web/stream.go +++ b/web/stream.go @@ -8,7 +8,6 @@ import ( "github.com/docker/docker/pkg/stdcopy" "github.com/drone/drone/engine" - "github.com/drone/drone/router/middleware/context" "github.com/drone/drone/router/middleware/session" "github.com/drone/drone/store" @@ -20,7 +19,7 @@ import ( // GetRepoEvents will upgrade the connection to a Websocket and will stream // event updates to the browser. func GetRepoEvents(c *gin.Context) { - engine_ := context.Engine(c) + engine_ := engine.FromContext(c) repo := session.Repo(c) c.Writer.Header().Set("Content-Type", "text/event-stream") @@ -55,7 +54,7 @@ func GetRepoEvents(c *gin.Context) { func GetStream(c *gin.Context) { - engine_ := context.Engine(c) + engine_ := engine.FromContext(c) repo := session.Repo(c) buildn, _ := strconv.Atoi(c.Param("build")) jobn, _ := strconv.Atoi(c.Param("number")) From 84db808eca63c8b2d6b7344f61724349735831cd Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 12 Apr 2016 13:15:03 -0700 Subject: [PATCH 5/6] fixed unit test reference deleted pkg --- remote/github/github_test.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/remote/github/github_test.go b/remote/github/github_test.go index 50348e85c..85ad35724 100644 --- a/remote/github/github_test.go +++ b/remote/github/github_test.go @@ -6,7 +6,6 @@ import ( "net/http" "testing" - "github.com/drone/drone/shared/envconfig" "github.com/franela/goblin" ) @@ -48,12 +47,11 @@ func TestHook(t *testing.T) { } func TestLoad(t *testing.T) { - env := envconfig.Env{ - "REMOTE_CONFIG": "https://github.com?client_id=client&client_secret=secret&scope=scope1,scope2", - } - g := Load(env) + conf := "https://github.com?client_id=client&client_secret=secret&scope=scope1,scope2" + + g := Load(conf) if g.URL != "https://github.com" { - t.Errorf("g.URL = %q; want https://github.com") + t.Errorf("g.URL = %q; want https://github.com", g.URL) } if g.Client != "client" { t.Errorf("g.Client = %q; want client", g.Client) @@ -71,7 +69,7 @@ func TestLoad(t *testing.T) { t.Errorf("g.MergeRef = %q; want %q", g.MergeRef, DefaultMergeRef) } - g = Load(envconfig.Env{}) + g = Load("") if g.Scope != DefaultScope { t.Errorf("g.Scope = %q; want %q", g.Scope, DefaultScope) } From 857062d8955a7da229355238e25a647a749bf562 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 12 Apr 2016 13:22:23 -0700 Subject: [PATCH 6/6] fixed failing gitlab test --- remote/gitlab/gitlab_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/remote/gitlab/gitlab_test.go b/remote/gitlab/gitlab_test.go index c6faa05dd..944afbb70 100644 --- a/remote/gitlab/gitlab_test.go +++ b/remote/gitlab/gitlab_test.go @@ -15,8 +15,7 @@ func Test_Gitlab(t *testing.T) { var server = testdata.NewServer() defer server.Close() - env := map[string]string{} - env["REMOTE_CONFIG"] = server.URL + "?client_id=test&client_secret=test" + env := server.URL + "?client_id=test&client_secret=test" gitlab := Load(env)