diff --git a/api/build/approve.go b/api/build/approve.go new file mode 100644 index 000000000..84aedb418 --- /dev/null +++ b/api/build/approve.go @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: Apache-2.0 + +package build + +import ( + "fmt" + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-vela/server/database" + "github.com/go-vela/server/queue" + "github.com/go-vela/server/router/middleware/build" + "github.com/go-vela/server/router/middleware/org" + "github.com/go-vela/server/router/middleware/repo" + "github.com/go-vela/server/router/middleware/user" + "github.com/go-vela/server/util" + "github.com/go-vela/types/constants" + "github.com/sirupsen/logrus" +) + +// swagger:operation POST /api/v1/repos/{org}/{repo}/builds/{build}/approve builds ApproveBuild +// +// Sign off on a build to run from an outside contributor +// +// --- +// produces: +// - application/json +// parameters: +// - in: path +// name: org +// description: Name of the org +// required: true +// type: string +// - in: path +// name: repo +// description: Name of the repo +// required: true +// type: string +// - in: path +// name: build +// description: Build number to retrieve +// required: true +// type: integer +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Request processed but build was skipped +// schema: +// type: string +// '201': +// description: Successfully created the build +// type: json +// schema: +// "$ref": "#/definitions/Build" +// '400': +// description: Unable to create the build +// schema: +// "$ref": "#/definitions/Error" +// '404': +// description: Unable to create the build +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unable to create the build +// schema: +// "$ref": "#/definitions/Error" + +// CreateBuild represents the API handler to approve a build to run in the configured backend. +func ApproveBuild(c *gin.Context) { + // capture middleware values + b := build.Retrieve(c) + o := org.Retrieve(c) + r := repo.Retrieve(c) + u := user.Retrieve(c) + ctx := c.Request.Context() + + // update engine logger with API metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields + logger := logrus.WithFields(logrus.Fields{ + "org": o, + "repo": r.GetName(), + "user": u.GetName(), + }) + + if !strings.EqualFold(b.GetStatus(), constants.StatusPendingApproval) { + retErr := fmt.Errorf("unable to approve build %s/%d: build not in pending approval state", r.GetFullName(), b.GetNumber()) + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + logger.Debugf("user %s approved build %s/%d for execution", u.GetName(), r.GetFullName(), b.GetNumber()) + + // send API call to capture the repo owner + u, err := database.FromContext(c).GetUser(ctx, r.GetUserID()) + if err != nil { + retErr := fmt.Errorf("unable to get owner for %s: %w", r.GetFullName(), err) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + b.SetStatus(constants.StatusPending) + b.SetApprovedAt(time.Now().Unix()) + b.SetApprovedBy(u.GetName()) + + // update the build in the db + _, err = database.FromContext(c).UpdateBuild(ctx, b) + if err != nil { + logrus.Errorf("Failed to update build %d during publish to queue for %s: %v", b.GetNumber(), r.GetFullName(), err) + } + + // publish the build to the queue + go PublishToQueue( + ctx, + queue.FromGinContext(c), + database.FromContext(c), + b, + r, + u, + b.GetHost(), + ) + + c.JSON(http.StatusOK, fmt.Sprintf("Successfully approved build %s/%d", r.GetFullName(), b.GetNumber())) +} diff --git a/api/build/cancel.go b/api/build/cancel.go index e8cd19264..7e72541db 100644 --- a/api/build/cancel.go +++ b/api/build/cancel.go @@ -184,7 +184,7 @@ func CancelBuild(c *gin.Context) { return } } - case constants.StatusPending: + case constants.StatusPending, constants.StatusPendingApproval: break default: @@ -198,6 +198,7 @@ func CancelBuild(c *gin.Context) { // build has been abandoned // update the status in the build table b.SetStatus(constants.StatusCanceled) + b.SetError(fmt.Sprintf("build was canceled by %s", user.GetName())) b, err := database.FromContext(c).UpdateBuild(ctx, b) if err != nil { diff --git a/api/build/create.go b/api/build/create.go index 272880432..7fb97912f 100644 --- a/api/build/create.go +++ b/api/build/create.go @@ -348,15 +348,37 @@ func CreateBuild(c *gin.Context) { logger.Errorf("unable to set commit status for build %s/%d: %v", r.GetFullName(), input.GetNumber(), err) } + // determine queue route + route, err := queue.FromGinContext(c).Route(&p.Worker) + if err != nil { + logrus.Errorf("unable to set route for build %d for %s: %v", input.GetNumber(), r.GetFullName(), err) + + // error out the build + CleanBuild(ctx, database.FromContext(c), input, nil, nil, err) + + return + } + + // temporarily set host to the route before it gets picked up by a worker + input.SetHost(route) + + err = PublishBuildExecutable(ctx, database.FromContext(c), p, input) + if err != nil { + retErr := fmt.Errorf("unable to publish build executable for %s/%d: %w", r.GetFullName(), input.GetNumber(), err) + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + // publish the build to the queue go PublishToQueue( ctx, queue.FromGinContext(c), database.FromContext(c), - p, input, r, u, + route, ) } diff --git a/api/build/executable.go b/api/build/executable.go index e7cd2c291..351314043 100644 --- a/api/build/executable.go +++ b/api/build/executable.go @@ -3,6 +3,8 @@ package build import ( + "context" + "encoding/json" "fmt" "net/http" @@ -13,6 +15,8 @@ import ( "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/repo" "github.com/go-vela/server/util" + "github.com/go-vela/types/library" + "github.com/go-vela/types/pipeline" "github.com/sirupsen/logrus" ) @@ -91,3 +95,36 @@ func GetBuildExecutable(c *gin.Context) { c.JSON(http.StatusOK, bExecutable) } + +// PublishBuildExecutable marshals a pipeline.Build into bytes and pushes that data to the build_executables table to be +// requested by a worker whenever the build has been picked up. +func PublishBuildExecutable(ctx context.Context, db database.Interface, p *pipeline.Build, b *library.Build) error { + // marshal pipeline build into byte data to add to the build executable object + byteExecutable, err := json.Marshal(p) + if err != nil { + logrus.Errorf("Failed to marshal build executable: %v", err) + + // error out the build + CleanBuild(ctx, db, b, nil, nil, err) + + return err + } + + // create build executable to push to database + bExecutable := new(library.BuildExecutable) + bExecutable.SetBuildID(b.GetID()) + bExecutable.SetData(byteExecutable) + + // send database call to create a build executable + err = db.CreateBuildExecutable(ctx, bExecutable) + if err != nil { + logrus.Errorf("Failed to publish build executable to database: %v", err) + + // error out the build + CleanBuild(ctx, db, b, nil, nil, err) + + return err + } + + return nil +} diff --git a/api/build/get_id.go b/api/build/get_id.go index 90237c420..44cfbdbea 100644 --- a/api/build/get_id.go +++ b/api/build/get_id.go @@ -99,7 +99,7 @@ func GetBuildByID(c *gin.Context) { // Capture user access from SCM. We do this in order to ensure user has access and is not // just retrieving any build using a random id number. - perm, err := scm.FromContext(c).RepoAccess(ctx, u, u.GetToken(), r.GetOrg(), r.GetName()) + perm, err := scm.FromContext(c).RepoAccess(ctx, u.GetName(), u.GetToken(), r.GetOrg(), r.GetName()) if err != nil { logrus.Errorf("unable to get user %s access level for repo %s", u.GetName(), r.GetFullName()) } diff --git a/api/build/publish.go b/api/build/publish.go index 1499fe540..f843b1461 100644 --- a/api/build/publish.go +++ b/api/build/publish.go @@ -11,40 +11,11 @@ import ( "github.com/go-vela/server/queue" "github.com/go-vela/types" "github.com/go-vela/types/library" - "github.com/go-vela/types/pipeline" "github.com/sirupsen/logrus" ) -// PublishToQueue is a helper function that pushes the build executable to the database -// and publishes a queue item (build, repo, user) to the queue. -func PublishToQueue(ctx context.Context, queue queue.Service, db database.Interface, p *pipeline.Build, b *library.Build, r *library.Repo, u *library.User) { - // marshal pipeline build into byte data to add to the build executable object - byteExecutable, err := json.Marshal(p) - if err != nil { - logrus.Errorf("Failed to marshal build executable %d for %s: %v", b.GetNumber(), r.GetFullName(), err) - - // error out the build - CleanBuild(ctx, db, b, nil, nil, err) - - return - } - - // create build executable to push to database - bExecutable := new(library.BuildExecutable) - bExecutable.SetBuildID(b.GetID()) - bExecutable.SetData(byteExecutable) - - // send database call to create a build executable - err = db.CreateBuildExecutable(ctx, bExecutable) - if err != nil { - logrus.Errorf("Failed to publish build executable to database %d for %s: %v", b.GetNumber(), r.GetFullName(), err) - - // error out the build - CleanBuild(ctx, db, b, nil, nil, err) - - return - } - +// PublishToQueue is a helper function that publishes a queue item (build, repo, user) to the queue. +func PublishToQueue(ctx context.Context, queue queue.Service, db database.Interface, b *library.Build, r *library.Repo, u *library.User, route string) { // convert build, repo, and user into queue item item := types.ToItem(b, r, u) @@ -62,17 +33,6 @@ func PublishToQueue(ctx context.Context, queue queue.Service, db database.Interf logrus.Infof("Establishing route for build %d for %s", b.GetNumber(), r.GetFullName()) - // determine the route on which to publish the queue item - route, err := queue.Route(&p.Worker) - if err != nil { - logrus.Errorf("unable to set route for build %d for %s: %v", b.GetNumber(), r.GetFullName(), err) - - // error out the build - CleanBuild(ctx, db, b, nil, nil, err) - - return - } - logrus.Infof("Publishing item for build %d for %s to queue %s", b.GetNumber(), r.GetFullName(), route) // push item on to the queue diff --git a/api/build/restart.go b/api/build/restart.go index 9867a626f..4b3487c66 100644 --- a/api/build/restart.go +++ b/api/build/restart.go @@ -98,6 +98,14 @@ func RestartBuild(c *gin.Context) { "user": u.GetName(), }) + if strings.EqualFold(b.GetStatus(), constants.StatusPendingApproval) { + retErr := fmt.Errorf("unable to restart build %s/%d: cannot restart a build pending approval", r.GetFullName(), b.GetNumber()) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + logger.Infof("restarting build %s", entry) // send API call to capture the repo owner @@ -339,14 +347,36 @@ func RestartBuild(c *gin.Context) { logger.Errorf("unable to set commit status for build %s: %v", entry, err) } + // determine queue route + route, err := queue.FromContext(c).Route(&p.Worker) + if err != nil { + logrus.Errorf("unable to set route for build %d for %s: %v", b.GetNumber(), r.GetFullName(), err) + + // error out the build + CleanBuild(ctx, database.FromContext(c), b, nil, nil, err) + + return + } + + // temporarily set host to the route before it gets picked up by a worker + b.SetHost(route) + + err = PublishBuildExecutable(ctx, database.FromContext(c), p, b) + if err != nil { + retErr := fmt.Errorf("unable to publish build executable for %s/%d: %w", r.GetFullName(), b.GetNumber(), err) + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + // publish the build to the queue go PublishToQueue( ctx, queue.FromGinContext(c), database.FromContext(c), - p, b, r, u, + route, ) } diff --git a/api/repo/create.go b/api/repo/create.go index cf4bcfe7a..fdd5f2c08 100644 --- a/api/repo/create.go +++ b/api/repo/create.go @@ -144,6 +144,13 @@ func CreateRepo(c *gin.Context) { r.SetVisibility(input.GetVisibility()) } + // set the fork policy field based off the input provided + if len(input.GetApproveBuild()) > 0 { + r.SetApproveBuild(input.GetApproveBuild()) + } else { + r.SetApproveBuild(constants.ApproveForkAlways) + } + // fields restricted to platform admins if u.GetAdmin() { // trusted default is false diff --git a/api/repo/update.go b/api/repo/update.go index b863b08c5..5e1c995ce 100644 --- a/api/repo/update.go +++ b/api/repo/update.go @@ -155,6 +155,11 @@ func UpdateRepo(c *gin.Context) { r.SetVisibility(input.GetVisibility()) } + if len(input.GetApproveBuild()) > 0 { + // update fork policy if set + r.SetApproveBuild(input.GetApproveBuild()) + } + if input.Private != nil { // update private if set r.SetPrivate(input.GetPrivate()) diff --git a/api/scm/sync.go b/api/scm/sync.go index 23723152f..2033c9a49 100644 --- a/api/scm/sync.go +++ b/api/scm/sync.go @@ -114,7 +114,7 @@ func SyncRepo(c *gin.Context) { // verify the user is an admin of the repo // we cannot use our normal permissions check due to the possibility the repo was deleted - perm, err := scm.FromContext(c).RepoAccess(ctx, u, u.GetToken(), o, r.GetName()) + perm, err := scm.FromContext(c).RepoAccess(ctx, u.GetName(), u.GetToken(), o, r.GetName()) if err != nil { logger.Errorf("unable to get user %s access level for org %s", u.GetName(), o) } diff --git a/api/webhook/post.go b/api/webhook/post.go index d816431e0..5fbf677ba 100644 --- a/api/webhook/post.go +++ b/api/webhook/post.go @@ -296,7 +296,7 @@ func PostWebhook(c *gin.Context) { } // confirm current repo owner has at least write access to repo (needed for status update later) - _, err = scm.FromContext(c).RepoAccess(ctx, u, u.GetToken(), r.GetOrg(), r.GetName()) + _, err = scm.FromContext(c).RepoAccess(ctx, u.GetName(), u.GetToken(), r.GetOrg(), r.GetName()) if err != nil { retErr := fmt.Errorf("unable to publish build to queue: repository owner %s no longer has write access to repository %s", u.GetName(), r.GetFullName()) util.HandleError(c, http.StatusUnauthorized, retErr) @@ -665,6 +665,59 @@ func PostWebhook(c *gin.Context) { c.JSON(http.StatusOK, b) + // determine queue route + route, err := queue.FromContext(c).Route(&p.Worker) + if err != nil { + logrus.Errorf("unable to set route for build %d for %s: %v", b.GetNumber(), r.GetFullName(), err) + + // error out the build + build.CleanBuild(ctx, database.FromContext(c), b, nil, nil, err) + + return + } + + // temporarily set host to the route before it gets picked up by a worker + b.SetHost(route) + + // publish the pipeline.Build to the build_executables table to be requested by a worker + err = build.PublishBuildExecutable(ctx, database.FromContext(c), p, b) + if err != nil { + retErr := fmt.Errorf("unable to publish build executable for %s/%d: %w", repo.GetFullName(), b.GetNumber(), err) + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + // if the webhook was from a Pull event from a forked repository, verify it is allowed to run + if webhook.PullRequest.IsFromFork { + switch repo.GetApproveBuild() { + case constants.ApproveForkAlways: + err = gatekeepBuild(c, b, repo, u) + if err != nil { + util.HandleError(c, http.StatusInternalServerError, err) + } + + return + case constants.ApproveForkNoWrite: + // determine if build sender has write access to parent repo. If not, this call will result in an error + _, err = scm.FromContext(c).RepoAccess(ctx, b.GetSender(), u.GetToken(), r.GetOrg(), r.GetName()) + if err != nil { + err = gatekeepBuild(c, b, repo, u) + if err != nil { + util.HandleError(c, http.StatusInternalServerError, err) + } + + return + } + + fallthrough + case constants.ApproveNever: + fallthrough + default: + logrus.Debugf("fork PR build %s/%d automatically running without approval", repo.GetFullName(), b.GetNumber()) + } + } + // send API call to set the status on the commit err = scm.FromContext(c).Status(ctx, u, b, repo.GetOrg(), repo.GetName()) if err != nil { @@ -676,10 +729,10 @@ func PostWebhook(c *gin.Context) { ctx, queue.FromGinContext(c), database.FromContext(c), - p, b, repo, u, + route, ) if build.ShouldAutoCancel(p.Metadata.AutoCancel, b, repo.GetBranch()) { @@ -920,3 +973,23 @@ func renameRepository(ctx context.Context, h *library.Hook, r *library.Repo, c * return dbR, nil } + +// gatekeepBuild is a helper function that will set the status of a build to 'pending approval' and +// send a status update to the SCM. +func gatekeepBuild(c *gin.Context, b *library.Build, r *library.Repo, u *library.User) error { + logrus.Debugf("fork PR build %s/%d waiting for approval", r.GetFullName(), b.GetNumber()) + b.SetStatus(constants.StatusPendingApproval) + + _, err := database.FromContext(c).UpdateBuild(c, b) + if err != nil { + return fmt.Errorf("unable to update build for %s/%d: %w", r.GetFullName(), b.GetNumber(), err) + } + + // send API call to set the status on the commit + err = scm.FromContext(c).Status(c, u, b, r.GetOrg(), r.GetName()) + if err != nil { + logrus.Errorf("unable to set commit status for %s/%d: %v", r.GetFullName(), b.GetNumber(), err) + } + + return nil +} diff --git a/cmd/vela-server/schedule.go b/cmd/vela-server/schedule.go index cceb70de8..bd7ce792b 100644 --- a/cmd/vela-server/schedule.go +++ b/cmd/vela-server/schedule.go @@ -166,7 +166,7 @@ func processSchedule(ctx context.Context, s *library.Schedule, compiler compiler } // send API call to confirm repo owner has at least write access to repo - _, err = scm.RepoAccess(ctx, u, u.GetToken(), r.GetOrg(), r.GetName()) + _, err = scm.RepoAccess(ctx, u.GetName(), u.GetToken(), r.GetOrg(), r.GetName()) if err != nil { return fmt.Errorf("%s does not have at least write access for repo %s", u.GetName(), r.GetFullName()) } @@ -389,15 +389,36 @@ func processSchedule(ctx context.Context, s *library.Schedule, compiler compiler return fmt.Errorf("unable to get new build %s/%d: %w", r.GetFullName(), b.GetNumber(), err) } + // determine queue route + route, err := queue.Route(&p.Worker) + if err != nil { + logrus.Errorf("unable to set route for build %d for %s: %v", b.GetNumber(), r.GetFullName(), err) + + // error out the build + build.CleanBuild(ctx, database, b, nil, nil, err) + + return err + } + + // temporarily set host to the route before it gets picked up by a worker + b.SetHost(route) + + err = build.PublishBuildExecutable(ctx, database, p, b) + if err != nil { + retErr := fmt.Errorf("unable to publish build executable for %s/%d: %w", r.GetFullName(), b.GetNumber(), err) + + return retErr + } + // publish the build to the queue go build.PublishToQueue( ctx, queue, database, - p, b, r, u, + route, ) return nil diff --git a/router/build.go b/router/build.go index d912b6788..2464dba84 100644 --- a/router/build.go +++ b/router/build.go @@ -21,6 +21,7 @@ import ( // GET /api/v1/repos/:org/:repo/builds/:build // PUT /api/v1/repos/:org/:repo/builds/:build // DELETE /api/v1/repos/:org/:repo/builds/:build +// POST /api/v1/repos/:org/:repo/builds/:build/approve // DELETE /api/v1/repos/:org/:repo/builds/:build/cancel // GET /api/v1/repos/:org/:repo/builds/:build/logs // GET /api/v1/repos/:org/:repo/builds/:build/token @@ -57,6 +58,7 @@ func BuildHandlers(base *gin.RouterGroup) { b.GET("", perm.MustRead(), build.GetBuild) b.PUT("", perm.MustBuildAccess(), middleware.Payload(), build.UpdateBuild) b.DELETE("", perm.MustPlatformAdmin(), build.DeleteBuild) + b.POST("/approve", perm.MustAdmin(), build.ApproveBuild) b.DELETE("/cancel", executors.Establish(), perm.MustWrite(), build.CancelBuild) b.GET("/logs", perm.MustRead(), log.ListLogsForBuild) b.GET("/token", perm.MustWorkerAuthToken(), build.GetBuildToken) diff --git a/router/middleware/executors/executors.go b/router/middleware/executors/executors.go index 705bac0d4..688434f07 100644 --- a/router/middleware/executors/executors.go +++ b/router/middleware/executors/executors.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net/http" + "strings" "time" "github.com/gin-gonic/gin" @@ -31,8 +32,10 @@ func Establish() gin.HandlerFunc { b := build.Retrieve(c) ctx := c.Request.Context() - // if build has no host, we cannot establish executors - if len(b.GetHost()) == 0 { + // if build is pending or pending approval, there is no host to establish executors + if strings.EqualFold(b.GetStatus(), constants.StatusPending) || + strings.EqualFold(b.GetStatus(), constants.StatusPendingApproval) || + len(b.GetHost()) == 0 { ToContext(c, *e) c.Next() diff --git a/router/middleware/perm/perm.go b/router/middleware/perm/perm.go index 5bc9291e3..a8656a261 100644 --- a/router/middleware/perm/perm.go +++ b/router/middleware/perm/perm.go @@ -276,7 +276,7 @@ func MustSecretAdmin() gin.HandlerFunc { case constants.SecretRepo: logger.Debugf("verifying user %s has 'admin' permissions for repo %s/%s", u.GetName(), o, n) - perm, err := scm.FromContext(c).RepoAccess(ctx, u, u.GetToken(), o, n) + perm, err := scm.FromContext(c).RepoAccess(ctx, u.GetName(), u.GetToken(), o, n) if err != nil { logger.Errorf("unable to get user %s access level for repo %s/%s: %v", u.GetName(), o, n, err) } @@ -365,7 +365,7 @@ func MustAdmin() gin.HandlerFunc { } // query source to determine requesters permissions for the repo using the requester's token - perm, err := scm.FromContext(c).RepoAccess(ctx, u, u.GetToken(), r.GetOrg(), r.GetName()) + perm, err := scm.FromContext(c).RepoAccess(ctx, u.GetName(), u.GetToken(), r.GetOrg(), r.GetName()) if err != nil { // requester may not have permissions to use the Github API endpoint (requires read access) // try again using the repo owner token @@ -380,7 +380,7 @@ func MustAdmin() gin.HandlerFunc { return } - perm, err = scm.FromContext(c).RepoAccess(ctx, u, ro.GetToken(), r.GetOrg(), r.GetName()) + perm, err = scm.FromContext(c).RepoAccess(ctx, u.GetName(), ro.GetToken(), r.GetOrg(), r.GetName()) if err != nil { logger.Errorf("unable to get user %s access level for repo %s", u.GetName(), r.GetFullName()) } @@ -424,7 +424,7 @@ func MustWrite() gin.HandlerFunc { } // query source to determine requesters permissions for the repo using the requester's token - perm, err := scm.FromContext(c).RepoAccess(ctx, u, u.GetToken(), r.GetOrg(), r.GetName()) + perm, err := scm.FromContext(c).RepoAccess(ctx, u.GetName(), u.GetToken(), r.GetOrg(), r.GetName()) if err != nil { // requester may not have permissions to use the Github API endpoint (requires read access) // try again using the repo owner token @@ -439,7 +439,7 @@ func MustWrite() gin.HandlerFunc { return } - perm, err = scm.FromContext(c).RepoAccess(ctx, u, ro.GetToken(), r.GetOrg(), r.GetName()) + perm, err = scm.FromContext(c).RepoAccess(ctx, u.GetName(), ro.GetToken(), r.GetOrg(), r.GetName()) if err != nil { logger.Errorf("unable to get user %s access level for repo %s", u.GetName(), r.GetFullName()) } @@ -507,7 +507,7 @@ func MustRead() gin.HandlerFunc { } // query source to determine requesters permissions for the repo using the requester's token - perm, err := scm.FromContext(c).RepoAccess(ctx, u, u.GetToken(), r.GetOrg(), r.GetName()) + perm, err := scm.FromContext(c).RepoAccess(ctx, u.GetName(), u.GetToken(), r.GetOrg(), r.GetName()) if err != nil { // requester may not have permissions to use the Github API endpoint (requires read access) // try again using the repo owner token @@ -522,7 +522,7 @@ func MustRead() gin.HandlerFunc { return } - perm, err = scm.FromContext(c).RepoAccess(ctx, u, ro.GetToken(), r.GetOrg(), r.GetName()) + perm, err = scm.FromContext(c).RepoAccess(ctx, u.GetName(), ro.GetToken(), r.GetOrg(), r.GetName()) if err != nil { logger.Errorf("unable to get user %s access level for repo %s", u.GetName(), r.GetFullName()) } diff --git a/scm/github/access.go b/scm/github/access.go index 99e582cd0..96fc1aedd 100644 --- a/scm/github/access.go +++ b/scm/github/access.go @@ -48,29 +48,31 @@ func (c *client) OrgAccess(ctx context.Context, u *library.User, org string) (st } // RepoAccess captures the user's access level for a repo. -func (c *client) RepoAccess(ctx context.Context, u *library.User, token, org, repo string) (string, error) { +func (c *client) RepoAccess(ctx context.Context, name, token, org, repo string) (string, error) { c.Logger.WithFields(logrus.Fields{ "org": org, "repo": repo, - "user": u.GetName(), - }).Tracef("capturing %s access level to repo %s/%s", u.GetName(), org, repo) + "user": name, + }).Tracef("capturing %s access level to repo %s/%s", name, org, repo) // check if user is accessing repo in personal org - if strings.EqualFold(org, u.GetName()) { + if strings.EqualFold(org, name) { c.Logger.WithFields(logrus.Fields{ "org": org, "repo": repo, - "user": u.GetName(), - }).Debugf("skipping access level check for user %s with repo %s/%s", u.GetName(), org, repo) + "user": name, + }).Debugf("skipping access level check for user %s with repo %s/%s", name, org, repo) return "admin", nil } // create github oauth client with the given token + // + //nolint:contextcheck // ignore context passing client := c.newClientToken(token) // send API call to capture repo access level for user - perm, _, err := client.Repositories.GetPermissionLevel(ctx, org, repo, u.GetName()) + perm, _, err := client.Repositories.GetPermissionLevel(ctx, org, repo, name) if err != nil { return "", err } diff --git a/scm/github/access_test.go b/scm/github/access_test.go index 804715790..d4d0044d3 100644 --- a/scm/github/access_test.go +++ b/scm/github/access_test.go @@ -219,7 +219,7 @@ func TestGithub_RepoAccess_Admin(t *testing.T) { client, _ := NewTest(s.URL) // run test - got, err := client.RepoAccess(context.TODO(), u, u.GetToken(), "github", "octocat") + got, err := client.RepoAccess(context.TODO(), "foo", u.GetToken(), "github", "octocat") if resp.Code != http.StatusOK { t.Errorf("RepoAccess returned %v, want %v", resp.Code, http.StatusOK) @@ -249,7 +249,7 @@ func TestGithub_RepoAccess_NotFound(t *testing.T) { client, _ := NewTest(s.URL) // run test - got, err := client.RepoAccess(context.TODO(), u, u.GetToken(), "github", "octocat") + got, err := client.RepoAccess(context.TODO(), "foo", u.GetToken(), "github", "octocat") if err == nil { t.Errorf("RepoAccess should have returned err") diff --git a/scm/github/repo.go b/scm/github/repo.go index 24ada78e4..d0cb67a41 100644 --- a/scm/github/repo.go +++ b/scm/github/repo.go @@ -290,6 +290,9 @@ func (c *client) Status(ctx context.Context, u *library.User, b *library.Build, case constants.StatusRunning, constants.StatusPending: state = "pending" description = fmt.Sprintf("the build is %s", b.GetStatus()) + case constants.StatusPendingApproval: + state = "pending" + description = "build needs approval from repo admin to run" case constants.StatusSuccess: state = "success" description = "the build was successful" diff --git a/scm/github/webhook.go b/scm/github/webhook.go index 2c8f9c155..ac7e1aae1 100644 --- a/scm/github/webhook.go +++ b/scm/github/webhook.go @@ -282,7 +282,8 @@ func (c *client) processPREvent(h *library.Hook, payload *github.PullRequestEven return &types.Webhook{ PullRequest: types.PullRequest{ - Number: payload.GetNumber(), + Number: payload.GetNumber(), + IsFromFork: payload.GetPullRequest().GetHead().GetRepo().GetFork(), }, Hook: h, Repo: r, diff --git a/scm/service.go b/scm/service.go index d93c633a5..c7b459e64 100644 --- a/scm/service.go +++ b/scm/service.go @@ -47,7 +47,7 @@ type Service interface { OrgAccess(context.Context, *library.User, string) (string, error) // RepoAccess defines a function that captures // the user's access level for a repo. - RepoAccess(context.Context, *library.User, string, string, string) (string, error) + RepoAccess(context.Context, string, string, string, string) (string, error) // TeamAccess defines a function that captures // the user's access level for a team. TeamAccess(context.Context, *library.User, string, string) (string, error)