bump to 0.5 in master

This commit is contained in:
Brad Rydzewski
2016-05-02 12:21:25 -07:00
parent 53eac09f34
commit 0fb4aeda3f
43 changed files with 802 additions and 1347 deletions

View File

@@ -1,82 +0,0 @@
package web
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/drone/drone/model"
"github.com/drone/drone/shared/httputil"
"github.com/drone/drone/store"
)
var (
badgeSuccess = `<svg xmlns="http://www.w3.org/2000/svg" width="91" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="91" height="20" fill="#555"/><rect rx="3" x="37" width="54" height="20" fill="#4c1"/><path fill="#4c1" d="M37 0h4v20h-4z"/><rect rx="3" width="91" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="63" y="15" fill="#010101" fill-opacity=".3">success</text><text x="63" y="14">success</text></g></svg>`
badgeFailure = `<svg xmlns="http://www.w3.org/2000/svg" width="83" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="83" height="20" fill="#555"/><rect rx="3" x="37" width="46" height="20" fill="#e05d44"/><path fill="#e05d44" d="M37 0h4v20h-4z"/><rect rx="3" width="83" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="59" y="15" fill="#010101" fill-opacity=".3">failure</text><text x="59" y="14">failure</text></g></svg>`
badgeStarted = `<svg xmlns="http://www.w3.org/2000/svg" width="87" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="87" height="20" fill="#555"/><rect rx="3" x="37" width="50" height="20" fill="#dfb317"/><path fill="#dfb317" d="M37 0h4v20h-4z"/><rect rx="3" width="87" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="61" y="15" fill="#010101" fill-opacity=".3">started</text><text x="61" y="14">started</text></g></svg>`
badgeError = `<svg xmlns="http://www.w3.org/2000/svg" width="76" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="76" height="20" fill="#555"/><rect rx="3" x="37" width="39" height="20" fill="#9f9f9f"/><path fill="#9f9f9f" d="M37 0h4v20h-4z"/><rect rx="3" width="76" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="55.5" y="15" fill="#010101" fill-opacity=".3">error</text><text x="55.5" y="14">error</text></g></svg>`
badgeNone = `<svg xmlns="http://www.w3.org/2000/svg" width="75" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="75" height="20" fill="#555"/><rect rx="3" x="37" width="38" height="20" fill="#9f9f9f"/><path fill="#9f9f9f" d="M37 0h4v20h-4z"/><rect rx="3" width="75" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="19.5" y="15" fill="#010101" fill-opacity=".3">build</text><text x="19.5" y="14">build</text><text x="55" y="15" fill="#010101" fill-opacity=".3">none</text><text x="55" y="14">none</text></g></svg>`
)
func GetBadge(c *gin.Context) {
repo, err := store.GetRepoOwnerName(c,
c.Param("owner"),
c.Param("name"),
)
if err != nil {
c.AbortWithStatus(404)
return
}
// an SVG response is always served, even when error, so
// we can go ahead and set the content type appropriately.
c.Writer.Header().Set("Content-Type", "image/svg+xml")
// if no commit was found then display
// the 'none' badge, instead of throwing
// an error response
branch := c.Query("branch")
if len(branch) == 0 {
branch = repo.Branch
}
build, err := store.GetBuildLast(c, repo, branch)
if err != nil {
c.String(404, badgeNone)
return
}
switch build.Status {
case model.StatusSuccess:
c.String(200, badgeSuccess)
case model.StatusFailure:
c.String(200, badgeFailure)
case model.StatusError, model.StatusKilled:
c.String(200, badgeError)
case model.StatusPending, model.StatusRunning:
c.String(200, badgeStarted)
default:
c.String(404, badgeNone)
}
}
func GetCC(c *gin.Context) {
repo, err := store.GetRepoOwnerName(c,
c.Param("owner"),
c.Param("name"),
)
if err != nil {
c.AbortWithStatus(404)
return
}
builds, err := store.GetBuildList(c, repo)
if err != nil || len(builds) == 0 {
c.AbortWithStatus(404)
return
}
url := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, builds[0].Number)
cc := model.NewCC(repo, builds[0], url)
c.XML(200, cc)
}

View File

@@ -1,100 +0,0 @@
package web
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/shared/token"
"github.com/drone/drone/store"
)
func GetCommit(c *gin.Context) {
repo := session.Repo(c)
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
return repo.Hash, nil
})
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
if parsed.Text != repo.FullName {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
commit := c.Param("sha")
branch := c.Query("branch")
if len(branch) == 0 {
branch = repo.Branch
}
build, err := store.GetBuildCommit(c, repo, commit, branch)
if err != nil {
c.AbortWithError(http.StatusNotFound, err)
return
}
c.JSON(http.StatusOK, build)
}
func GetPullRequest(c *gin.Context) {
repo := session.Repo(c)
refs := fmt.Sprintf("refs/pull/%s/head", c.Param("number"))
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
return repo.Hash, nil
})
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
if parsed.Text != repo.FullName {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
build, err := store.GetBuildRef(c, repo, refs)
if err != nil {
c.AbortWithError(http.StatusNotFound, err)
return
}
c.JSON(http.StatusOK, build)
}
func RedirectSha(c *gin.Context) {
repo := session.Repo(c)
commit := c.Param("sha")
branch := c.Query("branch")
if len(branch) == 0 {
branch = repo.Branch
}
build, err := store.GetBuildCommit(c, repo, commit, branch)
if err != nil {
c.AbortWithError(http.StatusNotFound, err)
return
}
path := fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.Number)
c.Redirect(http.StatusSeeOther, path)
}
func RedirectPullRequest(c *gin.Context) {
repo := session.Repo(c)
refs := fmt.Sprintf("refs/pull/%s/head", c.Param("number"))
build, err := store.GetBuildRef(c, repo, refs)
if err != nil {
c.AbortWithError(http.StatusNotFound, err)
return
}
path := fmt.Sprintf("/%s/%s/%d", repo.Owner, repo.Name, build.Number)
c.Redirect(http.StatusSeeOther, path)
}

View File

@@ -1,248 +0,0 @@
package web
import (
"fmt"
"os"
"regexp"
"github.com/gin-gonic/gin"
"github.com/square/go-jose"
log "github.com/Sirupsen/logrus"
"github.com/drone/drone/bus"
"github.com/drone/drone/model"
"github.com/drone/drone/queue"
"github.com/drone/drone/remote"
"github.com/drone/drone/shared/httputil"
"github.com/drone/drone/shared/token"
"github.com/drone/drone/store"
"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.sig", droneYml)
}
var skipRe = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`)
func PostHook(c *gin.Context) {
remote_ := remote.FromContext(c)
tmprepo, build, err := remote_.Hook(c.Request)
if err != nil {
log.Errorf("failure to parse hook. %s", err)
c.AbortWithError(400, err)
return
}
if build == nil {
c.Writer.WriteHeader(200)
return
}
if tmprepo == nil {
log.Errorf("failure to ascertain repo from hook.")
c.Writer.WriteHeader(400)
return
}
// skip the build if any case-insensitive combination of the words "skip" and "ci"
// wrapped in square brackets appear in the commit message
skipMatch := skipRe.FindString(build.Message)
if len(skipMatch) > 0 {
log.Infof("ignoring hook. %s found in %s", skipMatch, build.Commit)
c.Writer.WriteHeader(204)
return
}
repo, err := store.GetRepoOwnerName(c, tmprepo.Owner, tmprepo.Name)
if err != nil {
log.Errorf("failure to find repo %s/%s from hook. %s", tmprepo.Owner, tmprepo.Name, err)
c.AbortWithError(404, err)
return
}
// get the token and verify the hook is authorized
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
return repo.Hash, nil
})
if err != nil {
log.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
c.AbortWithError(400, err)
return
}
if parsed.Text != repo.FullName {
log.Errorf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text)
c.AbortWithStatus(403)
return
}
if repo.UserID == 0 {
log.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
c.Writer.WriteHeader(204)
return
}
var skipped = true
if (build.Event == model.EventPush && repo.AllowPush) ||
(build.Event == model.EventPull && repo.AllowPull) ||
(build.Event == model.EventDeploy && repo.AllowDeploy) ||
(build.Event == model.EventTag && repo.AllowTag) {
skipped = false
}
if skipped {
log.Infof("ignoring hook. repo %s is disabled for %s events.", repo.FullName, build.Event)
c.Writer.WriteHeader(204)
return
}
user, err := store.GetUser(c, repo.UserID)
if err != nil {
log.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
c.AbortWithError(500, err)
return
}
// if there is no email address associated with the pull request,
// we lookup the email address based on the authors github login.
//
// my initial hesitation with this code is that it has the ability
// to expose your email address. At the same time, your email address
// is already exposed in the public .git log. So while some people will
// a small number of people will probably be upset by this, I'm not sure
// it is actually that big of a deal.
if len(build.Email) == 0 {
author, err := store.GetUserLogin(c, build.Author)
if err == nil {
build.Email = author.Email
}
}
// if the remote has a refresh token, the current access token
// may be stale. Therefore, we should refresh prior to dispatching
// the job.
if refresher, ok := remote_.(remote.Refresher); ok {
ok, _ := refresher.Refresh(user)
if ok {
store.UpdateUser(c, user)
}
}
// fetch the build file from the database
raw, err := remote_.File(user, repo, build, droneYml)
if err != nil {
log.Errorf("failure to get build config for %s. %s", repo.FullName, err)
c.AbortWithError(404, err)
return
}
sec, err := remote_.File(user, repo, build, droneSec)
if err != nil {
log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err)
// NOTE we don't exit on failure. The sec file is optional
}
axes, err := yaml.ParseMatrix(raw)
if err != nil {
c.String(500, "Failed to parse yaml file or calculate matrix. %s", err)
return
}
if len(axes) == 0 {
axes = append(axes, yaml.Axis{})
}
netrc, err := remote_.Netrc(user, repo)
if err != nil {
c.String(500, "Failed to generate netrc file. %s", err)
return
}
// verify the branches can be built vs skipped
branches := yaml.ParseBranch(raw)
if !branches.Matches(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
c.String(200, "Branch does not match restrictions defined in yaml")
return
}
// update some build fields
build.Status = model.StatusPending
build.RepoID = repo.ID
// and use a transaction
var jobs []*model.Job
for num, axis := range axes {
jobs = append(jobs, &model.Job{
BuildID: build.ID,
Number: num + 1,
Status: model.StatusPending,
Environment: axis,
})
}
err = store.CreateBuild(c, build, jobs...)
if err != nil {
log.Errorf("failure to save commit for %s. %s", repo.FullName, err)
c.AbortWithError(500, err)
return
}
c.JSON(200, build)
url := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
err = remote_.Status(user, repo, build, url)
if err != nil {
log.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
}
// get the previous build so that we can send
// on status change notifications
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
secs, err := store.GetSecretList(c, repo)
if err != nil {
log.Errorf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
}
var signed bool
var verified bool
signature, err := jose.ParseSigned(string(sec))
if err != nil {
log.Debugf("cannot parse .drone.yml.sig file. %s", err)
} else if len(sec) == 0 {
log.Debugf("cannot parse .drone.yml.sig file. empty file")
} else {
signed = true
output, err := signature.Verify([]byte(repo.Hash))
if err != nil {
log.Debugf("cannot 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)},
})
}
}

View File

@@ -1,149 +0,0 @@
package web
//
// import (
// "net/http"
// "time"
//
// "github.com/drone/drone/model"
// "github.com/drone/drone/remote"
// "github.com/drone/drone/shared/crypto"
// "github.com/drone/drone/shared/httputil"
// "github.com/drone/drone/shared/token"
// "github.com/drone/drone/store"
//
// "github.com/Sirupsen/logrus"
// "github.com/gin-gonic/gin"
// )
//
// func GetLogin(c *gin.Context) {
// remote := remote.FromContext(c)
//
// // when dealing with redirects we may need
// // to adjust the content type. I cannot, however,
// // remember why, so need to revisit this line.
// c.Writer.Header().Del("Content-Type")
//
// tmpuser, err := remote.Login(c.Writer, c.Request)
// if err != nil {
// logrus.Errorf("cannot authenticate user. %s", err)
// c.Redirect(303, "/login?error=oauth_error")
// return
// }
// // this will happen when the user is redirected by
// // the remote provide as part of the oauth dance.
// if tmpuser == nil {
// return
// }
//
// var open = false // TODO get this from context
//
// // get the user from the database
// u, err := store.GetUserLogin(c, tmpuser.Login)
// if err != nil {
//
// // if self-registration is disabled we should
// // return a notAuthorized error. the only exception
// // is if no users exist yet in the system we'll proceed.
// if !open {
// logrus.Errorf("cannot register %s. registration closed", tmpuser.Login)
// c.Redirect(303, "/login?error=access_denied")
// return
// }
//
// // create the user account
// u = &model.User{}
// u.Login = tmpuser.Login
// u.Token = tmpuser.Token
// u.Secret = tmpuser.Secret
// u.Email = tmpuser.Email
// u.Avatar = tmpuser.Avatar
// u.Hash = crypto.Rand()
//
// // insert the user into the database
// if err := store.CreateUser(c, u); err != nil {
// logrus.Errorf("cannot insert %s. %s", u.Login, err)
// c.Redirect(303, "/login?error=internal_error")
// return
// }
// }
//
// // update the user meta data and authorization
// // data and cache in the datastore.
// u.Token = tmpuser.Token
// u.Secret = tmpuser.Secret
// u.Email = tmpuser.Email
// u.Avatar = tmpuser.Avatar
//
// if err := store.UpdateUser(c, u); err != nil {
// logrus.Errorf("cannot update %s. %s", u.Login, err)
// c.Redirect(303, "/login?error=internal_error")
// return
// }
//
// exp := time.Now().Add(time.Hour * 72).Unix()
// token := token.New(token.SessToken, u.Login)
// tokenstr, err := token.SignExpires(u.Hash, exp)
// if err != nil {
// logrus.Errorf("cannot create token for %s. %s", u.Login, err)
// c.Redirect(303, "/login?error=internal_error")
// return
// }
//
// httputil.SetCookie(c.Writer, c.Request, "user_sess", tokenstr)
// redirect := httputil.GetCookie(c.Request, "user_last")
// if len(redirect) == 0 {
// redirect = "/"
// }
// c.Redirect(303, redirect)
//
// }
//
// func GetLogout(c *gin.Context) {
//
// httputil.DelCookie(c.Writer, c.Request, "user_sess")
// httputil.DelCookie(c.Writer, c.Request, "user_last")
// c.Redirect(303, "/login")
// }
//
// func GetLoginToken(c *gin.Context) {
// remote := remote.FromContext(c)
//
// in := &tokenPayload{}
// err := c.Bind(in)
// if err != nil {
// c.AbortWithError(http.StatusBadRequest, err)
// return
// }
//
// login, err := remote.Auth(in.Access, in.Refresh)
// if err != nil {
// c.AbortWithError(http.StatusUnauthorized, err)
// return
// }
//
// user, err := store.GetUserLogin(c, login)
// if err != nil {
// c.AbortWithError(http.StatusNotFound, err)
// return
// }
//
// exp := time.Now().Add(time.Hour * 72).Unix()
// token := token.New(token.SessToken, user.Login)
// tokenstr, err := token.SignExpires(user.Hash, exp)
// if err != nil {
// c.AbortWithError(http.StatusInternalServerError, err)
// return
// }
//
// c.IndentedJSON(http.StatusOK, &tokenPayload{
// Access: tokenstr,
// Expires: exp - time.Now().Unix(),
// })
// }
//
// type tokenPayload struct {
// Access string `json:"access_token,omitempty"`
// Refresh string `json:"refresh_token,omitempty"`
// Expires int64 `json:"expires_in,omitempty"`
// }

View File

@@ -1,207 +0,0 @@
package web
import (
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/drone/drone/cache"
"github.com/drone/drone/model"
"github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/shared/httputil"
"github.com/drone/drone/shared/token"
"github.com/drone/drone/store"
)
func ShowIndex(c *gin.Context) {
user := session.User(c)
if user == nil {
c.Redirect(http.StatusSeeOther, "/login")
return
}
// get the repository list from the cache
repos, err := cache.GetRepos(c, user)
if err != nil {
c.String(400, err.Error())
return
}
// filter to only show the currently active ones
activeRepos, err := store.GetRepoListOf(c, repos)
if err != nil {
c.String(400, err.Error())
return
}
c.HTML(200, "index.html", gin.H{
"User": user,
"Repos": activeRepos,
})
}
func ShowAllRepos(c *gin.Context) {
user := session.User(c)
if user == nil {
c.Redirect(http.StatusSeeOther, "/login")
return
}
// get the repository list from the cache
repos, err := cache.GetRepos(c, user)
if err != nil {
c.String(400, err.Error())
return
}
c.HTML(200, "repos.html", gin.H{
"User": user,
"Repos": repos,
})
}
func ShowLogin(c *gin.Context) {
c.HTML(200, "login.html", gin.H{"Error": c.Query("error")})
}
func ShowLoginForm(c *gin.Context) {
c.HTML(200, "login_form.html", gin.H{})
}
func ShowUser(c *gin.Context) {
user := session.User(c)
token, _ := token.New(
token.CsrfToken,
user.Login,
).Sign(user.Hash)
c.HTML(200, "user.html", gin.H{
"User": user,
"Csrf": token,
})
}
func ShowRepo(c *gin.Context) {
user := session.User(c)
repo := session.Repo(c)
builds, _ := store.GetBuildList(c, repo)
groups := []*model.BuildGroup{}
var curr *model.BuildGroup
for _, build := range builds {
date := time.Unix(build.Created, 0).Format("Jan 2 2006")
if curr == nil || curr.Date != date {
curr = &model.BuildGroup{}
curr.Date = date
groups = append(groups, curr)
}
curr.Builds = append(curr.Builds, build)
}
httputil.SetCookie(c.Writer, c.Request, "user_last", repo.FullName)
c.HTML(200, "repo.html", gin.H{
"User": user,
"Repo": repo,
"Builds": builds,
"Groups": groups,
})
}
func ShowRepoConf(c *gin.Context) {
user := session.User(c)
repo := session.Repo(c)
token, _ := token.New(
token.CsrfToken,
user.Login,
).Sign(user.Hash)
c.HTML(200, "repo_config.html", gin.H{
"User": user,
"Repo": repo,
"Csrf": token,
"Link": httputil.GetURL(c.Request),
})
}
func ShowRepoEncrypt(c *gin.Context) {
user := session.User(c)
repo := session.Repo(c)
token, _ := token.New(
token.CsrfToken,
user.Login,
).Sign(user.Hash)
c.HTML(200, "repo_secret.html", gin.H{
"User": user,
"Repo": repo,
"Csrf": token,
})
}
func ShowRepoBadges(c *gin.Context) {
user := session.User(c)
repo := session.Repo(c)
c.HTML(200, "repo_badge.html", gin.H{
"User": user,
"Repo": repo,
"Link": httputil.GetURL(c.Request),
})
}
func ShowBuild(c *gin.Context) {
user := session.User(c)
repo := session.Repo(c)
num, _ := strconv.Atoi(c.Param("number"))
seq, _ := strconv.Atoi(c.Param("job"))
if seq == 0 {
seq = 1
}
build, err := store.GetBuildNumber(c, repo, num)
if err != nil {
c.AbortWithError(404, err)
return
}
jobs, err := store.GetJobList(c, build)
if err != nil {
c.AbortWithError(404, err)
return
}
var job *model.Job
for _, j := range jobs {
if j.Number == seq {
job = j
break
}
}
httputil.SetCookie(c.Writer, c.Request, "user_last", repo.FullName)
var csrf string
if user != nil {
csrf, _ = token.New(
token.CsrfToken,
user.Login,
).Sign(user.Hash)
}
c.HTML(200, "build.html", gin.H{
"User": user,
"Repo": repo,
"Build": build,
"Jobs": jobs,
"Job": job,
"Csrf": csrf,
})
}

View File

@@ -1,113 +0,0 @@
package web
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")
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
}

View File

@@ -1,116 +0,0 @@
package web
import (
"bufio"
"encoding/json"
"io"
"strconv"
"github.com/gin-gonic/gin"
"github.com/drone/drone/bus"
"github.com/drone/drone/model"
"github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/store"
"github.com/drone/drone/stream"
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) {
repo := session.Repo(c)
c.Writer.Header().Set("Content-Type", "text/event-stream")
eventc := make(chan *bus.Event, 1)
bus.Subscribe(c, eventc)
defer func() {
bus.Unsubscribe(c, 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
}
// TODO(bradrydzewski) This is a super hacky workaround until we improve
// the actual bus. Having a per-call database event is just plain stupid.
if event.Repo.FullName == repo.FullName {
var payload = struct {
model.Build
Jobs []*model.Job `json:"jobs"`
}{}
payload.Build = event.Build
payload.Jobs, _ = store.GetJobList(c, &event.Build)
data, _ := json.Marshal(&payload)
sse.Encode(w, sse.Event{
Event: "message",
Data: string(data),
})
}
case <-c.Writer.CloseNotify():
return false
}
return true
})
}
func GetStream(c *gin.Context) {
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
}
rc, err := stream.Reader(c, stream.ToKey(job.ID))
if err != nil {
c.AbortWithError(404, err)
return
}
go func() {
<-c.Writer.CloseNotify()
rc.Close()
}()
var line int
var scanner = bufio.NewScanner(rc)
for scanner.Scan() {
line++
var err = sse.Encode(c.Writer, sse.Event{
Id: strconv.Itoa(line),
Event: "message",
Data: scanner.Text(),
})
if err != nil {
break
}
c.Writer.Flush()
}
log.Debugf("Closed stream %s#%d", repo.FullName, build.Number)
}