// GetCC accepts a request to retrieve the latest build // status for the given repository from the datastore and // in CCTray XML format. // // GET /api/badge/:host/:owner/:name/cc.xml // func GetCC(c web.C, w http.ResponseWriter, r *http.Request) { var ctx = context.FromC(c) var ( host = c.URLParams["host"] owner = c.URLParams["owner"] name = c.URLParams["name"] ) w.Header().Set("Content-Type", "application/xml") repo, err := datastore.GetRepoName(ctx, host, owner, name) if err != nil { w.WriteHeader(http.StatusNotFound) return } commits, err := datastore.GetCommitList(ctx, repo, 1, 0) if err != nil || len(commits) == 0 { w.WriteHeader(http.StatusNotFound) return } var link = httputil.GetURL(r) + "/" + repo.Host + "/" + repo.Owner + "/" + repo.Name var cc = model.NewCC(repo, commits[0], link) xml.NewEncoder(w).Encode(cc) }
// GenerateToken generates a JWT token for the user session // that can be appended to the #access_token segment to // facilitate client-based OAuth2. func GenerateToken(c context.Context, r *http.Request, user *model.User) (string, error) { token := jwt.New(jwt.GetSigningMethod("HS256")) token.Claims["user_id"] = user.ID token.Claims["audience"] = httputil.GetURL(r) token.Claims["expires"] = time.Now().UTC().Add(time.Hour * 72).Unix() return token.SignedString([]byte(*secret)) }
// Authorize handles GitHub API Authorization. func (r *GitHub) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) { var config = &oauth.Config{ ClientId: r.Client, ClientSecret: r.Secret, Scope: DefaultScope, AuthURL: fmt.Sprintf("%s/login/oauth/authorize", r.URL), TokenURL: fmt.Sprintf("%s/login/oauth/access_token", r.URL), RedirectURL: fmt.Sprintf("%s/api/auth/%s", httputil.GetURL(req), r.GetKind()), } // get the OAuth code var code = req.FormValue("code") var state = req.FormValue("state") if len(code) == 0 { var random = GetRandom() httputil.SetCookie(res, req, "github_state", random) http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther) return nil, nil } cookieState := httputil.GetCookie(req, "github_state") httputil.DelCookie(res, req, "github_state") if cookieState != state { return nil, fmt.Errorf("Error matching state in OAuth2 redirect") } var trans = &oauth.Transport{Config: config} var token, err = trans.Exchange(code) if err != nil { return nil, fmt.Errorf("Error exchanging token. %s", err) } var client = NewClient(r.API, token.AccessToken, r.SkipVerify) var useremail, errr = GetUserEmail(client) if errr != nil { return nil, fmt.Errorf("Error retrieving user or verified email. %s", errr) } if len(r.Orgs) > 0 { allowedOrg, err := UserBelongsToOrg(client, r.Orgs) if err != nil { return nil, fmt.Errorf("Could not check org membership. %s", err) } if !allowedOrg { return nil, fmt.Errorf("User does not belong to correct org. Must belong to %v", r.Orgs) } } var login = new(model.Login) login.ID = int64(*useremail.ID) login.Access = token.AccessToken login.Login = *useremail.Login login.Email = *useremail.Email if useremail.Name != nil { login.Name = *useremail.Name } return login, nil }
// PostHook accepts a post-commit hook and parses the payload // in order to trigger a build. The payload is specified to the // remote system (ie GitHub) and will therefore get parsed by // the appropriate remote plugin. // // POST /api/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit} // func PostCommit(c web.C, w http.ResponseWriter, r *http.Request) { var ctx = context.FromC(c) var ( branch = c.URLParams["branch"] hash = c.URLParams["commit"] host = c.URLParams["host"] repo = ToRepo(c) remote = remote.Lookup(host) ) commit, err := datastore.GetCommitSha(ctx, repo, branch, hash) if err != nil { w.WriteHeader(http.StatusNotFound) return } if commit.Status == model.StatusStarted || commit.Status == model.StatusEnqueue { w.WriteHeader(http.StatusConflict) return } commit.Status = model.StatusEnqueue commit.Started = 0 commit.Finished = 0 commit.Duration = 0 if err := datastore.PutCommit(ctx, commit); err != nil { w.WriteHeader(http.StatusInternalServerError) return } owner, err := datastore.GetUser(ctx, repo.UserID) if err != nil { w.WriteHeader(http.StatusBadRequest) return } // Request a new token and update user_token, err := remote.GetToken(owner) if user_token != nil { owner.Access = user_token.AccessToken owner.Secret = user_token.RefreshToken owner.TokenExpiry = user_token.Expiry datastore.PutUser(ctx, owner) } else if err != nil { w.WriteHeader(http.StatusBadRequest) return } // drop the items on the queue go worker.Do(ctx, &worker.Work{ User: owner, Repo: repo, Commit: commit, Host: httputil.GetURL(r), }) w.WriteHeader(http.StatusOK) }
// Login authenticates the session and returns the // remote user details. func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) { var config = &oauth2.Config{ ClientId: g.Client, ClientSecret: g.Secret, Scope: DefaultScope, AuthURL: fmt.Sprintf("%s/login/oauth/authorize", g.URL), TokenURL: fmt.Sprintf("%s/login/oauth/access_token", g.URL), RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)), } // get the OAuth code var code = req.FormValue("code") if len(code) == 0 { var random = GetRandom() http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther) return nil, false, nil } var trans = &oauth2.Transport{ Config: config, } if g.SkipVerify { trans.Transport = &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, } } var token, err = trans.Exchange(code) if err != nil { return nil, false, fmt.Errorf("Error exchanging token. %s", err) } var client = NewClient(g.API, token.AccessToken, g.SkipVerify) var useremail, errr = GetUserEmail(client) if errr != nil { return nil, false, fmt.Errorf("Error retrieving user or verified email. %s", errr) } if len(g.Orgs) > 0 { allowedOrg, err := UserBelongsToOrg(client, g.Orgs) if err != nil { return nil, false, fmt.Errorf("Could not check org membership. %s", err) } if !allowedOrg { return nil, false, fmt.Errorf("User does not belong to correct org. Must belong to %v", g.Orgs) } } user := model.User{} user.Login = *useremail.Login user.Email = *useremail.Email user.Token = token.AccessToken user.Avatar = *useremail.AvatarURL return &user, g.Open, nil }
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 DeleteRepo(c *gin.Context) { remote := remote.FromContext(c) repo := session.Repo(c) user := session.User(c) err := store.DeleteRepo(c, repo) if err != nil { c.AbortWithError(http.StatusInternalServerError, err) return } remote.Deactivate(user, repo, httputil.GetURL(c.Request)) c.Writer.WriteHeader(http.StatusOK) }
// PostRepo accapets a request to activate the named repository // in the datastore. It returns a 201 status created if successful // // POST /api/repos/:host/:owner/:name // func PostRepo(c web.C, w http.ResponseWriter, r *http.Request) { var ctx = context.FromC(c) var repo = ToRepo(c) var user = ToUser(c) // update the repo active flag and fields repo.Active = true repo.PullRequest = true repo.PostCommit = true repo.UserID = user.ID repo.Timeout = 3600 // default to 1 hour // generate a secret key for post-commit hooks if len(repo.Token) == 0 { repo.Token = model.GenerateToken() } // generates the rsa key if len(repo.PublicKey) == 0 || len(repo.PrivateKey) == 0 { key, err := sshutil.GeneratePrivateKey() if err != nil { w.WriteHeader(http.StatusInternalServerError) return } repo.PublicKey = sshutil.MarshalPublicKey(&key.PublicKey) repo.PrivateKey = sshutil.MarshalPrivateKey(key) } var remote = remote.Lookup(repo.Host) if remote == nil { w.WriteHeader(http.StatusNotFound) return } // setup the post-commit hook with the remote system and // if necessary, register the public key var hook = fmt.Sprintf("%s/api/hook/%s/%s", httputil.GetURL(r), repo.Remote, repo.Token) if err := remote.Activate(user, repo, hook); err != nil { w.WriteHeader(http.StatusInternalServerError) return } if err := datastore.PutRepo(ctx, repo); err != nil { w.WriteHeader(http.StatusInternalServerError) return } w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(repo) }
// Login authenticates the session and returns the // remote user details. func (g *Gitlab) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) { var config = &oauth2.Config{ ClientId: g.Client, ClientSecret: g.Secret, Scope: DefaultScope, AuthURL: fmt.Sprintf("%s/oauth/authorize", g.URL), TokenURL: fmt.Sprintf("%s/oauth/token", g.URL), RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)), } trans_ := &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: g.SkipVerify}, } // get the OAuth code var code = req.FormValue("code") if len(code) == 0 { http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther) return nil, false, nil } var trans = &oauth2.Transport{Config: config, Transport: trans_} var token_, err = trans.Exchange(code) if err != nil { return nil, false, fmt.Errorf("Error exchanging token. %s", err) } client := NewClient(g.URL, token_.AccessToken, g.SkipVerify) login, err := client.CurrentUser() if err != nil { return nil, false, err } user := &model.User{} user.Login = login.Username user.Email = login.Email user.Token = token_.AccessToken user.Secret = token_.RefreshToken if strings.HasPrefix(login.AvatarUrl, "http") { user.Avatar = login.AvatarUrl } else { user.Avatar = g.URL + "/" + login.AvatarUrl } return user, true, nil }
// Authorize handles authentication with thrid party remote systems, // such as github or bitbucket, and returns user data. func (r *Gitlab) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) { host := httputil.GetURL(req) config := NewOauthConfig(r, host) var code = req.FormValue("code") var state = req.FormValue("state") if len(code) == 0 { var random = GetRandom() httputil.SetCookie(res, req, "gitlab_state", random) http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther) return nil, nil } cookieState := httputil.GetCookie(req, "gitlab_state") httputil.DelCookie(res, req, "gitlab_state") if cookieState != state { return nil, fmt.Errorf("Error matching state in OAuth2 redirect") } var trans = &oauth.Transport{ Config: config, Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: r.SkipVerify}, }, } var token, err = trans.Exchange(code) if err != nil { return nil, fmt.Errorf("Error exchanging token. %s", err) } var client = NewClient(r.url, token.AccessToken, r.SkipVerify) var user, errr = client.CurrentUser() if errr != nil { return nil, fmt.Errorf("Error retrieving current user. %s", errr) } var login = new(model.Login) login.ID = int64(user.Id) login.Access = token.AccessToken login.Secret = token.RefreshToken login.Login = user.Username login.Email = user.Email return login, nil }
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 (g *Gitlab) Oauth2Transport(r *http.Request) *oauth2.Transport { return &oauth2.Transport{ Config: &oauth2.Config{ ClientId: g.Client, ClientSecret: g.Secret, Scope: DefaultScope, AuthURL: fmt.Sprintf("%s/oauth/authorize", g.URL), TokenURL: fmt.Sprintf("%s/oauth/token", g.URL), RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(r)), //settings.Server.Scheme, settings.Server.Hostname), }, Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: g.SkipVerify}, }, } }
// PostHook accepts a post-commit hook and parses the payload // in order to trigger a build. The payload is specified to the // remote system (ie GitHub) and will therefore get parsed by // the appropriate remote plugin. // // POST /api/repos/{host}/{owner}/{name}/branches/{branch}/commits/{commit} // func PostCommit(c web.C, w http.ResponseWriter, r *http.Request) { var ctx = context.FromC(c) var ( branch = c.URLParams["branch"] hash = c.URLParams["commit"] repo = ToRepo(c) ) commit, err := datastore.GetCommitSha(ctx, repo, branch, hash) if err != nil { w.WriteHeader(http.StatusNotFound) return } if commit.Status == model.StatusStarted || commit.Status == model.StatusEnqueue { w.WriteHeader(http.StatusConflict) return } commit.Status = model.StatusEnqueue commit.Started = 0 commit.Finished = 0 commit.Duration = 0 if err := datastore.PutCommit(ctx, commit); err != nil { w.WriteHeader(http.StatusInternalServerError) return } owner, err := datastore.GetUser(ctx, repo.UserID) if err != nil { w.WriteHeader(http.StatusBadRequest) return } // drop the items on the queue go worker.Do(ctx, &worker.Work{ User: owner, Repo: repo, Commit: commit, Host: httputil.GetURL(r), }) w.WriteHeader(http.StatusOK) }
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) }
// Login authenticates the session and returns the remote user details. func (c *client) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) { config := c.newConfig(httputil.GetURL(req)) code := req.FormValue("code") if len(code) == 0 { // TODO(bradrydzewski) we really should be using a random value here and // storing in a cookie for verification in the next stage of the workflow. http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther) return nil, nil } token, err := config.Exchange(c.newContext(), code) if err != nil { return nil, err } client := c.newClientToken(token.AccessToken) user, _, err := client.Users.Get("") if err != nil { return nil, err } emails, _, err := client.Users.ListEmails(nil) if err != nil { return nil, err } email := matchingEmail(emails, c.API) if email == nil { return nil, fmt.Errorf("No verified Email address for GitHub account") } return &model.User{ Login: *user.Login, Email: *email.Email, Token: token.AccessToken, Avatar: *user.AvatarURL, }, nil }
// DelRepo accepts a request to delete the named // repository. // // DEL /api/repos/:host/:owner/:name // func DelRepo(c web.C, w http.ResponseWriter, r *http.Request) { var ctx = context.FromC(c) var repo = ToRepo(c) // completely remove the repository from the database var user = ToUser(c) var remote = remote.Lookup(repo.Host) if remote == nil { log.Printf("[ERROR] no remote for host '%s' found", repo.Host) } else { // Request a new token and update user_token, err := remote.GetToken(user) if err != nil { log.Printf("[ERROR] no token for user '%s' on remote '%s' ", user.Email, repo.Host) } else { if user_token != nil { user.Access = user_token.AccessToken user.Secret = user_token.RefreshToken user.TokenExpiry = user_token.Expiry datastore.PutUser(ctx, user) } // setup the post-commit hook with the remote system and // and deactiveate this hook/user on the remote system var hook = fmt.Sprintf("%s/api/hook/%s/%s", httputil.GetURL(r), repo.Remote, repo.Token) if err := remote.Deactivate(user, repo, hook); err != nil { log.Printf("[ERROR] deactivate on remote '%s' failed: %s", repo.Host, err) } } } // fail through: if any of the actions on the remote failed // we try to delete the repo in our datastore anyway if err := datastore.DelRepo(ctx, repo); err != nil { w.WriteHeader(http.StatusInternalServerError) } else { w.WriteHeader(http.StatusNoContent) } }
// Login authenticates the session and returns the remoter user details. func (g *Github) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) { var config = &oauth2.Config{ ClientID: g.Client, ClientSecret: g.Secret, Scopes: defaultScope, Endpoint: oauth2.Endpoint{ AuthURL: fmt.Sprintf("%s/login/oauth/authorize", g.URL), TokenURL: fmt.Sprintf("%s/login/oauth/access_token", g.URL), }, RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)), } // get the OAuth code var code = req.FormValue("code") if len(code) == 0 { var random = getRandom() http.Redirect(res, req, config.AuthCodeURL(random), http.StatusSeeOther) return nil, nil } tok, err := config.Exchange(oauth2.NoContext, code) if err != nil { return nil, err } client := newClient(g.API, tok.AccessToken) userInfo, _, err := client.Users.Get("") if err != nil { return nil, err } user := model.User{} user.Login = *userInfo.Login user.Token = tok.AccessToken return &user, nil }
func PostBuild(c *gin.Context) { remote_ := remote.FromContext(c) repo := session.Repo(c) fork := c.DefaultQuery("fork", "false") num, err := strconv.Atoi(c.Param("number")) if err != nil { c.AbortWithError(http.StatusBadRequest, err) 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 } build, err := store.GetBuildNumber(c, repo, num) if err != nil { log.Errorf("failure to get build %d. %s", num, err) c.AbortWithError(404, err) return } // 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 .drone.yml file from the database config := ToConfig(c) raw, err := remote_.File(user, repo, build, config.Yaml) if err != nil { log.Errorf("failure to get build config for %s. %s", repo.FullName, err) c.AbortWithError(404, err) return } // Fetch secrets file but don't exit on error as it's optional sec, err := remote_.File(user, repo, build, config.Shasum) if err != nil { log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err) } netrc, err := remote_.Netrc(user, repo) if err != nil { log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err) c.AbortWithError(500, err) return } jobs, err := store.GetJobList(c, build) if err != nil { log.Errorf("failure to get build %d jobs. %s", build.Number, err) c.AbortWithError(404, err) return } // must not restart a running build if build.Status == model.StatusPending || build.Status == model.StatusRunning { c.String(409, "Cannot re-start a started build") return } // forking the build creates a duplicate of the build // and then executes. This retains prior build history. if forkit, _ := strconv.ParseBool(fork); forkit { build.ID = 0 build.Number = 0 for _, job := range jobs { job.ID = 0 job.NodeID = 0 } err := store.CreateBuild(c, build, jobs...) if err != nil { c.String(500, err.Error()) return } event := c.DefaultQuery("event", build.Event) if event == model.EventPush || event == model.EventPull || event == model.EventTag || event == model.EventDeploy { build.Event = event } build.Deploy = c.DefaultQuery("deploy_to", build.Deploy) } // todo move this to database tier // and wrap inside a transaction build.Status = model.StatusPending build.Started = 0 build.Finished = 0 build.Enqueued = time.Now().UTC().Unix() for _, job := range jobs { job.Status = model.StatusPending job.Started = 0 job.Finished = 0 job.ExitCode = 0 job.NodeID = 0 job.Enqueued = build.Enqueued store.UpdateJob(c, job) } err = store.UpdateBuild(c, build) if err != nil { c.AbortWithStatus(500) return } c.JSON(202, build) // 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. %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)}, }) } }
func PostBuild(c *gin.Context) { remote_ := remote.FromContext(c) repo := session.Repo(c) num, err := strconv.Atoi(c.Param("number")) if err != nil { c.AbortWithError(http.StatusBadRequest, err) 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 } build, err := store.GetBuildNumber(c, repo, num) if err != nil { log.Errorf("failure to get build %d. %s", num, err) c.AbortWithError(404, err) return } // 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 .drone.yml file from the database raw, sec, err := remote_.Script(user, repo, build) if err != nil { log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err) c.AbortWithError(404, err) return } key, _ := store.GetKey(c, repo) netrc, err := remote_.Netrc(user, repo) if err != nil { log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err) c.AbortWithError(500, err) return } jobs, err := store.GetJobList(c, build) if err != nil { log.Errorf("failure to get build %d jobs. %s", build.Number, err) c.AbortWithError(404, err) return } // must not restart a running build if build.Status == model.StatusPending || build.Status == model.StatusRunning { c.AbortWithStatus(409) return } // todo move this to database tier // and wrap inside a transaction build.Status = model.StatusPending build.Started = 0 build.Finished = 0 build.Enqueued = time.Now().UTC().Unix() for _, job := range jobs { job.Status = model.StatusPending job.Started = 0 job.Finished = 0 job.ExitCode = 0 job.Enqueued = build.Enqueued store.UpdateJob(c, job) } err = store.UpdateBuild(c, build) if err != nil { c.AbortWithStatus(500) return } c.JSON(202, build) // get the previous build so taht we can send // on status change notifications last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID) engine_ := context.Engine(c) go engine_.Schedule(c.Copy(), &engine.Task{ User: user, Repo: repo, Build: build, BuildPrev: last, Jobs: jobs, Keys: key, Netrc: netrc, Config: string(raw), Secret: string(sec), System: &model.System{ Link: httputil.GetURL(c.Request), Plugins: strings.Split(os.Getenv("PLUGIN_FILTER"), " "), Globals: strings.Split(os.Getenv("PLUGIN_PARAMS"), " "), }, }) }
func PostRepo(c *gin.Context) { remote := remote.FromContext(c) user := session.User(c) owner := c.Param("owner") name := c.Param("name") if user == nil { c.AbortWithStatus(403) return } r, err := remote.Repo(user, owner, name) if err != nil { c.String(404, err.Error()) return } m, err := cache.GetPerms(c, user, owner, name) if err != nil { c.String(404, err.Error()) return } if !m.Admin { c.String(403, "Administrative access is required.") return } // error if the repository already exists _, err = store.GetRepoOwnerName(c, owner, name) if err == nil { c.String(409, "Repository already exists.") return } // set the repository owner to the // currently authenticated user. r.UserID = user.ID r.AllowPush = true r.AllowPull = true r.Timeout = 60 // 1 hour default build time r.Hash = base32.StdEncoding.EncodeToString( securecookie.GenerateRandomKey(32), ) // crates the jwt token used to verify the repository t := token.New(token.HookToken, r.FullName) sig, err := t.Sign(r.Hash) if err != nil { c.String(500, err.Error()) return } link := fmt.Sprintf( "%s/hook?access_token=%s", httputil.GetURL(c.Request), sig, ) // activate the repository before we make any // local changes to the database. err = remote.Activate(user, r, link) if err != nil { c.String(500, err.Error()) return } // persist the repository err = store.CreateRepo(c, r) if err != nil { c.String(500, err.Error()) return } c.JSON(200, r) }
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 config := ToConfig(c) raw, err := remote_.File(user, repo, build, config.Yaml) if err != nil { log.Errorf("failure to get build config for %s. %s", repo.FullName, err) c.AbortWithError(404, err) return } sec, err := remote_.File(user, repo, build, config.Shasum) if err != nil { log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err) // NOTE we don't exit on failure. The sec file is optional } 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.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy { c.String(200, "Branch does not match restrictions defined in yaml") return } 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 { build.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 { build.Verified = true } } // 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) } bus.Publish(c, bus.NewBuildEvent(bus.Enqueued, repo, build)) for _, job := range jobs { queue.Publish(c, &queue.Work{ Signed: build.Signed, Verified: build.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)}, }) } }
func PostRepo(c *gin.Context) { remote := remote.FromContext(c) user := session.User(c) owner := c.Param("owner") name := c.Param("name") if user == nil { c.AbortWithStatus(403) return } r, err := remote.Repo(user, owner, name) if err != nil { c.String(404, err.Error()) return } m, err := remote.Perm(user, owner, name) if err != nil { c.String(404, err.Error()) return } if !m.Admin { c.String(403, "Administrative access is required.") return } // error if the repository already exists _, err = store.GetRepoOwnerName(c, owner, name) if err == nil { c.String(409, "Repository already exists.") return } // set the repository owner to the // currently authenticated user. r.UserID = user.ID r.AllowPush = true r.AllowPull = true r.Timeout = 60 // 1 hour default build time r.Hash = crypto.Rand() // crates the jwt token used to verify the repository t := token.New(token.HookToken, r.FullName) sig, err := t.Sign(r.Hash) if err != nil { c.String(500, err.Error()) return } link := fmt.Sprintf( "%s/hook?access_token=%s", httputil.GetURL(c.Request), sig, ) // generate an RSA key and add to the repo key, err := crypto.GeneratePrivateKey() if err != nil { c.String(500, err.Error()) return } keys := new(model.Key) keys.Public = string(crypto.MarshalPublicKey(&key.PublicKey)) keys.Private = string(crypto.MarshalPrivateKey(key)) // activate the repository before we make any // local changes to the database. err = remote.Activate(user, r, keys, link) if err != nil { c.String(500, err.Error()) return } // persist the repository err = store.CreateRepo(c, r) if err != nil { c.String(500, err.Error()) return } keys.RepoID = r.ID err = store.CreateKey(c, keys) if err != nil { c.String(500, err.Error()) return } c.JSON(200, r) }
// PostHook accepts a post-commit hook and parses the payload // in order to trigger a build. The payload is specified to the // remote system (ie GitHub) and will therefore get parsed by // the appropriate remote plugin. // // GET /api/hook/:host // func PostHook(c web.C, w http.ResponseWriter, r *http.Request) { var ctx = context.FromC(c) var host = c.URLParams["host"] var remote = remote.Lookup(host) if remote == nil { w.WriteHeader(http.StatusNotFound) return } // parse the hook payload hook, err := remote.ParseHook(r) if err != nil { w.WriteHeader(http.StatusBadRequest) return } // in some cases we have neither a hook nor error. An example // would be GitHub sending a ping request to the URL, in which // case we'll just exit quiely with an 'OK' if hook == nil || strings.Contains(hook.Message, "[CI SKIP]") { w.WriteHeader(http.StatusOK) return } // fetch the repository from the database repo, err := datastore.GetRepoName(ctx, remote.GetHost(), hook.Owner, hook.Repo) if err != nil { w.WriteHeader(http.StatusNotFound) return } if repo.Active == false || (repo.PostCommit == false && len(hook.PullRequest) == 0) || (repo.PullRequest == false && len(hook.PullRequest) != 0) { w.WriteHeader(http.StatusNotFound) return } // fetch the user from the database that owns this repo user, err := datastore.GetUser(ctx, repo.UserID) if err != nil { w.WriteHeader(http.StatusNotFound) return } // featch the .drone.yml file from the database yml, err := remote.GetScript(user, repo, hook) if err != nil { w.WriteHeader(http.StatusBadRequest) return } // verify the commit hooks branch matches the list of approved // branches (unless it is a pull request). Note that we don't really // care if parsing the yaml fails here. s, _ := script.ParseBuild(string(yml)) if len(hook.PullRequest) == 0 && !s.MatchBranch(hook.Branch) { w.WriteHeader(http.StatusOK) return } commit := model.Commit{ RepoID: repo.ID, Status: model.StatusEnqueue, Sha: hook.Sha, Branch: hook.Branch, PullRequest: hook.PullRequest, Timestamp: hook.Timestamp, Message: hook.Message, Config: string(yml), } commit.SetAuthor(hook.Author) // inserts the commit into the database if err := datastore.PostCommit(ctx, &commit); err != nil { w.WriteHeader(http.StatusBadRequest) return } owner, err := datastore.GetUser(ctx, repo.UserID) if err != nil { w.WriteHeader(http.StatusBadRequest) return } // drop the items on the queue go worker.Do(ctx, &worker.Work{ User: owner, Repo: repo, Commit: &commit, Host: httputil.GetURL(r), }) w.WriteHeader(http.StatusOK) }
// Login authenticates the session and returns the // remote user details. func (bb *Bitbucket) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) { config := &oauth2.Config{ ClientID: bb.Client, ClientSecret: bb.Secret, Endpoint: bitbucket.Endpoint, RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)), } // get the OAuth code var code = req.FormValue("code") if len(code) == 0 { http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther) return nil, false, nil } var token, err = config.Exchange(oauth2.NoContext, code) if err != nil { return nil, false, fmt.Errorf("Error exchanging token. %s", err) } client := NewClient(config.Client(oauth2.NoContext, token)) curr, err := client.FindCurrent() if err != nil { return nil, false, err } // convers the current bitbucket user to the // common drone user structure. user := model.User{} user.Login = curr.Login user.Token = token.AccessToken user.Secret = token.RefreshToken user.Expiry = token.Expiry.UTC().Unix() user.Avatar = curr.Links.Avatar.Href // gets the primary, confirmed email from bitbucket emails, err := client.ListEmail() if err != nil { return nil, false, err } for _, email := range emails.Values { if email.IsPrimary && email.IsConfirmed { user.Email = email.Email break } } // if the installation is restricted to a subset // of organizations, get the orgs and verify the // user is a member. if len(bb.Orgs) != 0 { resp, err := client.ListTeams(&ListTeamOpts{Page: 1, PageLen: 100, Role: "member"}) if err != nil { return nil, false, err } var member bool for _, team := range resp.Values { for _, team_ := range bb.Orgs { if team.Login == team_ { member = true break } } } if !member { return nil, false, fmt.Errorf("User does not belong to correct org. Must belong to %v", bb.Orgs) } } return &user, bb.Open, nil }
// Login authenticates the session and returns the // remote user details. func (g *Gitlab) Login(res http.ResponseWriter, req *http.Request) (*model.User, error) { var config = &oauth2.Config{ ClientId: g.Client, ClientSecret: g.Secret, Scope: DefaultScope, AuthURL: fmt.Sprintf("%s/oauth/authorize", g.URL), TokenURL: fmt.Sprintf("%s/oauth/token", g.URL), RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)), } trans_ := &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: g.SkipVerify}, } // get the OAuth code var code = req.FormValue("code") if len(code) == 0 { http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther) return nil, nil } var trans = &oauth2.Transport{Config: config, Transport: trans_} var token_, err = trans.Exchange(code) if err != nil { return nil, fmt.Errorf("Error exchanging token. %s", err) } client := NewClient(g.URL, token_.AccessToken, g.SkipVerify) login, err := client.CurrentUser() if err != nil { return nil, err } // if len(g.AllowedOrgs) != 0 { // groups, err := client.AllGroups() // if err != nil { // return nil, fmt.Errorf("Could not check org membership. %s", err) // } // // var member bool // for _, group := range groups { // for _, allowedOrg := range g.AllowedOrgs { // if group.Path == allowedOrg { // member = true // break // } // } // } // // if !member { // return nil, false, fmt.Errorf("User does not belong to correct group. Must belong to %v", g.AllowedOrgs) // } // } user := &model.User{} user.Login = login.Username user.Email = login.Email user.Token = token_.AccessToken user.Secret = token_.RefreshToken if strings.HasPrefix(login.AvatarUrl, "http") { user.Avatar = login.AvatarUrl } else { user.Avatar = g.URL + "/" + login.AvatarUrl } return user, nil }
// PostHook accepts a post-commit hook and parses the payload // in order to trigger a build. The payload is specified to the // remote system (ie GitHub) and will therefore get parsed by // the appropriate remote plugin. // // GET /api/hook/:host // func PostHook(c web.C, w http.ResponseWriter, r *http.Request) { var ctx = context.FromC(c) var host = c.URLParams["host"] var token = c.URLParams["token"] var remote = remote.Lookup(host) if remote == nil { w.WriteHeader(http.StatusNotFound) return } // parse the hook payload hook, err := remote.ParseHook(r) if err != nil { log.Printf("Unable to parse hook. %s\n", err) w.WriteHeader(http.StatusBadRequest) return } // in some cases we have neither a hook nor error. An example // would be GitHub sending a ping request to the URL, in which // case we'll just exit quiely with an 'OK' if hook == nil || strings.Contains(hook.Message, "[CI SKIP]") { w.WriteHeader(http.StatusOK) return } // fetch the repository from the database repo, err := datastore.GetRepoName(ctx, remote.GetHost(), hook.Owner, hook.Repo) if err != nil { w.WriteHeader(http.StatusNotFound) return } // each hook contains a token to verify the sender. If the token // is not provided or does not match, exit if len(repo.Token) == 0 || repo.Token != token { log.Printf("Rejected post commit hook for %s. Token mismatch\n", repo.Name) w.WriteHeader(http.StatusUnauthorized) return } if repo.Active == false || (repo.PostCommit == false && len(hook.PullRequest) == 0) || (repo.PullRequest == false && len(hook.PullRequest) != 0) { w.WriteHeader(http.StatusNotFound) return } // fetch the user from the database that owns this repo user, err := datastore.GetUser(ctx, repo.UserID) if err != nil { w.WriteHeader(http.StatusNotFound) return } // Request a new token and update user_token, err := remote.GetToken(user) if user_token != nil { user.Access = user_token.AccessToken user.Secret = user_token.RefreshToken user.TokenExpiry = user_token.Expiry datastore.PutUser(ctx, user) } else if err != nil { log.Printf("Unable to refresh token. %s\n", err) w.WriteHeader(http.StatusBadRequest) return } // featch the .drone.yml file from the database yml, err := remote.GetScript(user, repo, hook) if err != nil { log.Printf("Unable to fetch .drone.yml file. %s\n", err) w.WriteHeader(http.StatusBadRequest) return } // verify the commit hooks branch matches the list of approved // branches (unless it is a pull request). Note that we don't really // care if parsing the yaml fails here. s, _ := script.ParseBuild(string(yml)) if len(hook.PullRequest) == 0 && !s.MatchBranch(hook.Branch) { w.WriteHeader(http.StatusOK) return } commit := model.Commit{ RepoID: repo.ID, Status: model.StatusEnqueue, Sha: hook.Sha, Branch: hook.Branch, PullRequest: hook.PullRequest, Timestamp: hook.Timestamp, Message: hook.Message, Config: string(yml), } commit.SetAuthor(hook.Author) // inserts the commit into the database if err := datastore.PostCommit(ctx, &commit); err != nil { log.Printf("Unable to persist commit %s@%s. %s\n", commit.Sha, commit.Branch, err) w.WriteHeader(http.StatusBadRequest) return } owner, err := datastore.GetUser(ctx, repo.UserID) if err != nil { log.Printf("Unable to retrieve repository owner. %s.\n", err) w.WriteHeader(http.StatusBadRequest) return } // drop the items on the queue go worker.Do(ctx, &worker.Work{ User: owner, Repo: repo, Commit: &commit, Host: httputil.GetURL(r), }) w.WriteHeader(http.StatusOK) }
func PostBuild(c *gin.Context) { remote_ := remote.FromContext(c) repo := session.Repo(c) fork := c.DefaultQuery("fork", "false") num, err := strconv.Atoi(c.Param("number")) if err != nil { c.AbortWithError(http.StatusBadRequest, err) 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 } build, err := store.GetBuildNumber(c, repo, num) if err != nil { log.Errorf("failure to get build %d. %s", num, err) c.AbortWithError(404, err) return } // 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 .drone.yml file from the database config := ToConfig(c) raw, err := remote_.File(user, repo, build, config.Yaml) if err != nil { log.Errorf("failure to get build config for %s. %s", repo.FullName, err) c.AbortWithError(404, err) return } // Fetch secrets file but don't exit on error as it's optional sec, err := remote_.File(user, repo, build, config.Shasum) if err != nil { log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err) } netrc, err := remote_.Netrc(user, repo) if err != nil { log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err) c.AbortWithError(500, err) return } jobs, err := store.GetJobList(c, build) if err != nil { log.Errorf("failure to get build %d jobs. %s", build.Number, err) c.AbortWithError(404, err) return } // must not restart a running build if build.Status == model.StatusPending || build.Status == model.StatusRunning { c.String(409, "Cannot re-start a started build") return } // forking the build creates a duplicate of the build // and then executes. This retains prior build history. if forkit, _ := strconv.ParseBool(fork); forkit { build.ID = 0 build.Number = 0 build.Parent = num for _, job := range jobs { job.ID = 0 job.NodeID = 0 } err := store.CreateBuild(c, build, jobs...) if err != nil { c.String(500, err.Error()) return } event := c.DefaultQuery("event", build.Event) if event == model.EventPush || event == model.EventPull || event == model.EventTag || event == model.EventDeploy { build.Event = event } build.Deploy = c.DefaultQuery("deploy_to", build.Deploy) } // Read query string parameters into buildParams, exclude reserved params var buildParams = map[string]string{} for key, val := range c.Request.URL.Query() { switch key { case "fork", "event", "deploy_to": default: // We only accept string literals, because build parameters will be // injected as environment variables buildParams[key] = val[0] } } // todo move this to database tier // and wrap inside a transaction build.Status = model.StatusPending build.Started = 0 build.Finished = 0 build.Enqueued = time.Now().UTC().Unix() for _, job := range jobs { for k, v := range buildParams { job.Environment[k] = v } job.Error = "" job.Status = model.StatusPending job.Started = 0 job.Finished = 0 job.ExitCode = 0 job.NodeID = 0 job.Enqueued = build.Enqueued store.UpdateJob(c, job) } err = store.UpdateBuild(c, build) if err != nil { c.AbortWithStatus(500) return } c.JSON(202, build) // 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.GetMergedSecretList(c, repo) if err != nil { log.Debugf("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. %q <> %q", string(output), string(raw)) } else { verified = true } } log.Debugf(".drone.yml is signed=%v and verified=%v", signed, verified) client := stomp.MustFromContext(c) client.SendJSON("/topic/events", model.Event{ Type: model.Enqueued, Repo: *repo, Build: *build, }, stomp.WithHeader("repo", repo.FullName), stomp.WithHeader("private", strconv.FormatBool(repo.IsPrivate)), ) for _, job := range jobs { broker, _ := stomp.FromContext(c) broker.SendJSON("/queue/pending", &model.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)}, }, stomp.WithHeader( "platform", yaml.ParsePlatformDefault(raw, "linux/amd64"), ), stomp.WithHeaders( yaml.ParseLabel(raw), ), ) } }
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 .drone.yml file from the database raw, sec, err := remote_.Script(user, repo, build) if err != nil { log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err) c.AbortWithError(404, err) return } axes, err := matrix.Parse(string(raw)) if err != nil { log.Errorf("failure to calculate matrix for %s. %s", repo.FullName, err) c.AbortWithError(400, err) return } if len(axes) == 0 { axes = append(axes, matrix.Axis{}) } netrc, err := remote_.Netrc(user, repo) if err != nil { log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err) c.AbortWithError(500, err) return } key, _ := store.GetKey(c, repo) // verify the branches can be built vs skipped yconfig, _ := yaml.Parse(string(raw)) var match = false for _, branch := range yconfig.Branches { if branch == build.Branch { match = true break } match, _ = filepath.Match(branch, build.Branch) if match { break } } if !match && len(yconfig.Branches) != 0 { log.Infof("ignoring hook. yaml file excludes repo and branch %s %s", repo.FullName, build.Branch) c.AbortWithStatus(200) 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 taht we can send // on status change notifications last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID) engine_ := context.Engine(c) go engine_.Schedule(c.Copy(), &engine.Task{ User: user, Repo: repo, Build: build, BuildPrev: last, Jobs: jobs, Keys: key, Netrc: netrc, Config: string(raw), Secret: string(sec), System: &model.System{ Link: httputil.GetURL(c.Request), Plugins: strings.Split(os.Getenv("PLUGIN_FILTER"), " "), Globals: strings.Split(os.Getenv("PLUGIN_PARAMS"), " "), }, }) }