diff --git a/api/repo.go b/api/repo.go deleted file mode 100644 index 375bb11fa..000000000 --- a/api/repo.go +++ /dev/null @@ -1,1099 +0,0 @@ -// Copyright (c) 2022 Target Brands, Inc. All rights reserved. -// -// Use of this source code is governed by the LICENSE file in this repository. - -package api - -import ( - "encoding/base64" - "fmt" - "net/http" - "strconv" - "strings" - - "github.com/gin-gonic/gin" - "github.com/go-vela/server/database" - "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/scm" - "github.com/go-vela/server/util" - "github.com/go-vela/types/constants" - "github.com/go-vela/types/library" - "github.com/google/uuid" - "github.com/sirupsen/logrus" -) - -// swagger:operation POST /api/v1/repos repos CreateRepo -// -// Create a repo in the configured backend -// -// --- -// produces: -// - application/json -// parameters: -// - in: body -// name: body -// description: Payload containing the repo to create -// required: true -// schema: -// "$ref": "#/definitions/Repo" -// security: -// - ApiKeyAuth: [] -// responses: -// '201': -// description: Successfully created the repo -// schema: -// "$ref": "#/definitions/Repo" -// '400': -// description: Unable to create the repo -// schema: -// "$ref": "#/definitions/Error" -// '403': -// description: Unable to create the repo -// schema: -// "$ref": "#/definitions/Error" -// '409': -// description: Unable to create the repo -// schema: -// "$ref": "#/definitions/Error" -// '500': -// description: Unable to create the repo -// schema: -// "$ref": "#/definitions/Error" -// '503': -// description: Unable to create the repo -// schema: -// "$ref": "#/definitions/Error" - -// CreateRepo represents the API handler to -// create a repo in the configured backend. -// -//nolint:funlen,gocyclo // ignore function length and cyclomatic complexity -func CreateRepo(c *gin.Context) { - // capture middleware values - u := user.Retrieve(c) - allowlist := c.Value("allowlist").([]string) - defaultBuildLimit := c.Value("defaultBuildLimit").(int64) - defaultTimeout := c.Value("defaultTimeout").(int64) - maxBuildLimit := c.Value("maxBuildLimit").(int64) - defaultRepoEvents := c.Value("defaultRepoEvents").([]string) - - // capture body from API request - input := new(library.Repo) - - err := c.Bind(input) - if err != nil { - retErr := fmt.Errorf("unable to decode JSON for new repo: %w", err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": input.GetOrg(), - "repo": input.GetName(), - "user": u.GetName(), - }).Infof("creating new repo %s", input.GetFullName()) - - // get repo information from the source - r, err := scm.FromContext(c).GetRepo(u, input) - if err != nil { - retErr := fmt.Errorf("unable to retrieve repo info for %s from source: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // update fields in repo object - r.SetUserID(u.GetID()) - - // set the active field based off the input provided - if input.Active == nil { - // default active field to true - r.SetActive(true) - } else { - r.SetActive(input.GetActive()) - } - - // set the build limit field based off the input provided - if input.GetBuildLimit() == 0 { - // default build limit to value configured by server - r.SetBuildLimit(defaultBuildLimit) - } else if input.GetBuildLimit() > maxBuildLimit { - // set build limit to value configured by server to prevent limit from exceeding max - r.SetBuildLimit(maxBuildLimit) - } else { - r.SetBuildLimit(input.GetBuildLimit()) - } - - // set the timeout field based off the input provided - if input.GetTimeout() == 0 && defaultTimeout == 0 { - // default build timeout to 30m - r.SetTimeout(constants.BuildTimeoutDefault) - } else if input.GetTimeout() == 0 { - r.SetTimeout(defaultTimeout) - } else { - r.SetTimeout(input.GetTimeout()) - } - - // set the visibility field based off the input provided - if len(input.GetVisibility()) == 0 { - // default visibility field to public - r.SetVisibility(constants.VisibilityPublic) - } else { - r.SetVisibility(input.GetVisibility()) - } - - // fields restricted to platform admins - if u.GetAdmin() { - // trusted default is false - if input.GetTrusted() != r.GetTrusted() { - r.SetTrusted(input.GetTrusted()) - } - } - - // set default events if no events are passed in - if !input.GetAllowPull() && !input.GetAllowPush() && - !input.GetAllowDeploy() && !input.GetAllowTag() && - !input.GetAllowComment() { - for _, event := range defaultRepoEvents { - switch event { - case constants.EventPull: - r.SetAllowPull(true) - case constants.EventPush: - r.SetAllowPush(true) - case constants.EventDeploy: - r.SetAllowDeploy(true) - case constants.EventTag: - r.SetAllowTag(true) - case constants.EventComment: - r.SetAllowComment(true) - } - } - } else { - r.SetAllowComment(input.GetAllowComment()) - r.SetAllowDeploy(input.GetAllowDeploy()) - r.SetAllowPull(input.GetAllowPull()) - r.SetAllowPush(input.GetAllowPush()) - r.SetAllowTag(input.GetAllowTag()) - } - - if len(input.GetPipelineType()) == 0 { - r.SetPipelineType(constants.PipelineTypeYAML) - } else { - // ensure the pipeline type matches one of the expected values - if input.GetPipelineType() != constants.PipelineTypeYAML && - input.GetPipelineType() != constants.PipelineTypeGo && - input.GetPipelineType() != constants.PipelineTypeStarlark { - retErr := fmt.Errorf("unable to create new repo %s: invalid pipeline_type provided %s", r.GetFullName(), input.GetPipelineType()) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - r.SetPipelineType(input.GetPipelineType()) - } - - // create unique id for the repo - uid, err := uuid.NewRandom() - if err != nil { - retErr := fmt.Errorf("unable to create UID for repo %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusServiceUnavailable, retErr) - - return - } - - r.SetHash( - base64.StdEncoding.EncodeToString( - []byte(strings.TrimSpace(uid.String())), - ), - ) - - // ensure repo is allowed to be activated - if !checkAllowlist(r, allowlist) { - retErr := fmt.Errorf("unable to activate repo: %s is not on allowlist", r.GetFullName()) - - util.HandleError(c, http.StatusForbidden, retErr) - - return - } - - // send API call to capture the repo from the database - dbRepo, err := database.FromContext(c).GetRepoForOrg(r.GetOrg(), r.GetName()) - if err == nil && dbRepo.GetActive() { - retErr := fmt.Errorf("unable to activate repo: %s is already active", r.GetFullName()) - - util.HandleError(c, http.StatusConflict, retErr) - - return - } - - // check if the repo already has a hash created - if len(dbRepo.GetHash()) > 0 { - // overwrite the new repo hash with the existing repo hash - r.SetHash(dbRepo.GetHash()) - } - - // send API call to create the webhook - if c.Value("webhookvalidation").(bool) { - _, err = scm.FromContext(c).Enable(u, r.GetOrg(), r.GetName(), r.GetHash()) - if err != nil { - retErr := fmt.Errorf("unable to create webhook for %s: %w", r.GetFullName(), err) - - switch err.Error() { - case "repo already enabled": - util.HandleError(c, http.StatusConflict, retErr) - return - case "repo not found": - util.HandleError(c, http.StatusNotFound, retErr) - return - } - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - } - - // if the repo exists but is inactive - if len(dbRepo.GetOrg()) > 0 && !dbRepo.GetActive() { - // update the repo owner - dbRepo.SetUserID(u.GetID()) - // update the default branch - dbRepo.SetBranch(r.GetBranch()) - // activate the repo - dbRepo.SetActive(true) - - // send API call to update the repo - err = database.FromContext(c).UpdateRepo(dbRepo) - if err != nil { - retErr := fmt.Errorf("unable to set repo %s to active: %w", dbRepo.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - - // send API call to capture the updated repo - r, _ = database.FromContext(c).GetRepoForOrg(dbRepo.GetOrg(), dbRepo.GetName()) - } else { - // send API call to create the repo - err = database.FromContext(c).CreateRepo(r) - if err != nil { - retErr := fmt.Errorf("unable to create new repo %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - - // send API call to capture the created repo - r, _ = database.FromContext(c).GetRepoForOrg(r.GetOrg(), r.GetName()) - } - - c.JSON(http.StatusCreated, r) -} - -// swagger:operation GET /api/v1/repos repos GetRepos -// -// Get all repos in the configured backend -// -// --- -// produces: -// - application/json -// security: -// - ApiKeyAuth: [] -// parameters: -// - in: query -// name: page -// description: The page of results to retrieve -// type: integer -// default: 1 -// - in: query -// name: per_page -// description: How many results per page to return -// type: integer -// maximum: 100 -// default: 10 -// responses: -// '200': -// description: Successfully retrieved the repo -// schema: -// type: array -// items: -// "$ref": "#/definitions/Repo" -// headers: -// X-Total-Count: -// description: Total number of results -// type: integer -// Link: -// description: see https://tools.ietf.org/html/rfc5988 -// type: string -// '400': -// description: Unable to retrieve the repo -// schema: -// "$ref": "#/definitions/Error" -// '500': -// description: Unable to retrieve the repo -// schema: -// "$ref": "#/definitions/Error" - -// GetRepos represents the API handler to capture a list -// of repos for a user from the configured backend. -func GetRepos(c *gin.Context) { - // capture middleware values - u := user.Retrieve(c) - - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "user": u.GetName(), - }).Infof("reading repos for user %s", u.GetName()) - - // capture page query parameter if present - page, err := strconv.Atoi(c.DefaultQuery("page", "1")) - if err != nil { - retErr := fmt.Errorf("unable to convert page query parameter for user %s: %w", u.GetName(), err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // capture per_page query parameter if present - perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10")) - if err != nil { - retErr := fmt.Errorf("unable to convert per_page query parameter for user %s: %w", u.GetName(), err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // ensure per_page isn't above or below allowed values - perPage = util.MaxInt(1, util.MinInt(100, perPage)) - - // capture the sort_by query parameter if present - sortBy := util.QueryParameter(c, "sort_by", "name") - - // capture the query parameters if present: - // - // * active - filters := map[string]interface{}{ - "active": util.QueryParameter(c, "active", "true"), - } - - // send API call to capture the list of repos for the user - r, t, err := database.FromContext(c).ListReposForUser(u, sortBy, filters, page, perPage) - if err != nil { - retErr := fmt.Errorf("unable to get repos for user %s: %w", u.GetName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - - // create pagination object - pagination := Pagination{ - Page: page, - PerPage: perPage, - Total: t, - } - // set pagination headers - pagination.SetHeaderLink(c) - - c.JSON(http.StatusOK, r) -} - -// swagger:operation GET /api/v1/repos/{org} repos GetOrgRepos -// -// Get all repos for the provided org in the configured backend -// -// --- -// produces: -// - application/json -// security: -// - ApiKeyAuth: [] -// parameters: -// - in: path -// name: org -// description: Name of the org -// required: true -// type: string -// - in: query -// name: active -// description: Filter active repos -// type: boolean -// default: true -// - in: query -// name: page -// description: The page of results to retrieve -// type: integer -// default: 1 -// - in: query -// name: per_page -// description: How many results per page to return -// type: integer -// maximum: 100 -// default: 10 -// - in: query -// name: sort_by -// description: How to sort the results -// type: string -// enum: -// - name -// - latest -// default: name -// responses: -// '200': -// description: Successfully retrieved the repo -// schema: -// type: array -// items: -// "$ref": "#/definitions/Repo" -// headers: -// X-Total-Count: -// description: Total number of results -// type: integer -// Link: -// description: see https://tools.ietf.org/html/rfc5988 -// type: string -// '400': -// description: Unable to retrieve the org -// schema: -// "$ref": "#/definitions/Error" -// '500': -// description: Unable to retrieve the org -// schema: -// "$ref": "#/definitions/Error" - -// GetOrgRepos represents the API handler to capture a list -// of repos for an org from the configured backend. -func GetOrgRepos(c *gin.Context) { - // capture middleware values - o := org.Retrieve(c) - u := user.Retrieve(c) - - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "user": u.GetName(), - }).Infof("reading repos for org %s", o) - - // capture page query parameter if present - page, err := strconv.Atoi(c.DefaultQuery("page", "1")) - if err != nil { - retErr := fmt.Errorf("unable to convert page query parameter for user %s: %w", u.GetName(), err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // capture per_page query parameter if present - perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10")) - if err != nil { - retErr := fmt.Errorf("unable to convert per_page query parameter for user %s: %w", u.GetName(), err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // ensure per_page isn't above or below allowed values - perPage = util.MaxInt(1, util.MinInt(100, perPage)) - - // capture the sort_by query parameter if present - sortBy := util.QueryParameter(c, "sort_by", "name") - - // capture the query parameters if present: - // - // * active - filters := map[string]interface{}{ - "active": util.QueryParameter(c, "active", "true"), - } - - // See if the user is an org admin to bypass individual permission checks - perm, err := scm.FromContext(c).OrgAccess(u, o) - if err != nil { - logrus.Errorf("unable to get user %s access level for org %s", u.GetName(), o) - } - // Only show public repos to non-admins - if perm != "admin" { - filters["visibility"] = constants.VisibilityPublic - } - - // send API call to capture the list of repos for the org - r, t, err := database.FromContext(c).ListReposForOrg(o, sortBy, filters, page, perPage) - if err != nil { - retErr := fmt.Errorf("unable to get repos for org %s: %w", o, err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - - // create pagination object - pagination := Pagination{ - Page: page, - PerPage: perPage, - Total: t, - } - // set pagination headers - pagination.SetHeaderLink(c) - - c.JSON(http.StatusOK, r) -} - -// swagger:operation GET /api/v1/repos/{org}/{repo} repos GetRepo -// -// Get a repo in the configured backend -// -// --- -// 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 -// security: -// - ApiKeyAuth: [] -// responses: -// '200': -// description: Successfully retrieved the repo -// schema: -// "$ref": "#/definitions/Repo" - -// GetRepo represents the API handler to -// capture a repo from the configured backend. -func GetRepo(c *gin.Context) { - // capture middleware values - o := org.Retrieve(c) - r := repo.Retrieve(c) - u := user.Retrieve(c) - - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("reading repo %s", r.GetFullName()) - - c.JSON(http.StatusOK, r) -} - -// swagger:operation PUT /api/v1/repos/{org}/{repo} repos UpdateRepo -// -// Update a repo in the configured backend -// -// --- -// 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: body -// name: body -// description: Payload containing the repo to update -// required: true -// schema: -// "$ref": "#/definitions/Repo" -// security: -// - ApiKeyAuth: [] -// responses: -// '200': -// description: Successfully updated the repo -// schema: -// "$ref": "#/definitions/Repo" -// '400': -// description: Unable to update the repo -// schema: -// "$ref": "#/definitions/Error" -// '500': -// description: Unable to update the repo -// schema: -// "$ref": "#/definitions/Error" -// '503': -// description: Unable to update the repo -// schema: -// "$ref": "#/definitions/Error" - -// UpdateRepo represents the API handler to update -// a repo in the configured backend. -// -//nolint:funlen // ignore line length -func UpdateRepo(c *gin.Context) { - // capture middleware values - o := org.Retrieve(c) - r := repo.Retrieve(c) - u := user.Retrieve(c) - maxBuildLimit := c.Value("maxBuildLimit").(int64) - - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("updating repo %s", r.GetFullName()) - - // capture body from API request - input := new(library.Repo) - - err := c.Bind(input) - if err != nil { - retErr := fmt.Errorf("unable to decode JSON for repo %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - // update repo fields if provided - if len(input.GetBranch()) > 0 { - // update branch if set - r.SetBranch(input.GetBranch()) - } - - // update build limit if set - if input.GetBuildLimit() > 0 { - // allow build limit between 1 - value configured by server - r.SetBuildLimit( - int64( - util.MaxInt( - constants.BuildLimitMin, - util.MinInt( - int(input.GetBuildLimit()), - int(maxBuildLimit), - ), // clamp max - ), // clamp min - ), - ) - } - - if input.GetTimeout() > 0 { - // update build timeout if set - r.SetTimeout( - int64( - util.MaxInt( - constants.BuildTimeoutMin, - util.MinInt( - int(input.GetTimeout()), - constants.BuildTimeoutMax, - ), // clamp max - ), // clamp min - ), - ) - } - - if input.GetCounter() > 0 { - if input.GetCounter() <= r.GetCounter() { - retErr := fmt.Errorf("unable to set counter for repo %s: must be greater than current %d", - r.GetFullName(), r.GetCounter()) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - r.SetCounter(input.GetCounter()) - } - - if len(input.GetVisibility()) > 0 { - // update visibility if set - r.SetVisibility(input.GetVisibility()) - } - - if input.Private != nil { - // update private if set - r.SetPrivate(input.GetPrivate()) - } - - if input.Active != nil { - // update active if set - r.SetActive(input.GetActive()) - } - - if input.AllowPull != nil { - // update allow_pull if set - r.SetAllowPull(input.GetAllowPull()) - } - - if input.AllowPush != nil { - // update allow_push if set - r.SetAllowPush(input.GetAllowPush()) - } - - if input.AllowDeploy != nil { - // update allow_deploy if set - r.SetAllowDeploy(input.GetAllowDeploy()) - } - - if input.AllowTag != nil { - // update allow_tag if set - r.SetAllowTag(input.GetAllowTag()) - } - - if input.AllowComment != nil { - // update allow_comment if set - r.SetAllowComment(input.GetAllowComment()) - } - - // set default events if no events are enabled - if !r.GetAllowPull() && !r.GetAllowPush() && - !r.GetAllowDeploy() && !r.GetAllowTag() && - !r.GetAllowComment() { - r.SetAllowPull(true) - r.SetAllowPush(true) - } - - if len(input.GetPipelineType()) != 0 { - // ensure the pipeline type matches one of the expected values - if input.GetPipelineType() != constants.PipelineTypeYAML && - input.GetPipelineType() != constants.PipelineTypeGo && - input.GetPipelineType() != constants.PipelineTypeStarlark { - retErr := fmt.Errorf("pipeline_type of %s is invalid", input.GetPipelineType()) - - util.HandleError(c, http.StatusBadRequest, retErr) - - return - } - - r.SetPipelineType(input.GetPipelineType()) - } - - // set hash for repo if no hash is already set - if len(r.GetHash()) == 0 { - // create unique id for the repo - uid, err := uuid.NewRandom() - if err != nil { - retErr := fmt.Errorf("unable to create UID for repo %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusServiceUnavailable, retErr) - - return - } - - r.SetHash( - base64.StdEncoding.EncodeToString( - []byte(strings.TrimSpace(uid.String())), - ), - ) - } - - // fields restricted to platform admins - if u.GetAdmin() { - // trusted - if input.GetTrusted() != r.GetTrusted() { - r.SetTrusted(input.GetTrusted()) - } - } - - // send API call to update the repo - err = database.FromContext(c).UpdateRepo(r) - if err != nil { - retErr := fmt.Errorf("unable to update repo %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - - // send API call to capture the updated repo - r, _ = database.FromContext(c).GetRepoForOrg(r.GetOrg(), r.GetName()) - - c.JSON(http.StatusOK, r) -} - -// swagger:operation DELETE /api/v1/repos/{org}/{repo} repos DeleteRepo -// -// Delete a repo in the configured backend -// -// --- -// 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 -// security: -// - ApiKeyAuth: [] -// responses: -// '200': -// description: Successfully deleted the repo -// schema: -// type: string -// '500': -// description: Unable to deleted the repo -// schema: -// "$ref": "#/definitions/Error" -// '510': -// description: Unable to deleted the repo -// schema: -// "$ref": "#/definitions/Error" - -// DeleteRepo represents the API handler to remove -// a repo from the configured backend. -func DeleteRepo(c *gin.Context) { - // capture middleware values - o := org.Retrieve(c) - r := repo.Retrieve(c) - u := user.Retrieve(c) - - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("deleting repo %s", r.GetFullName()) - - // send API call to remove the webhook - err := scm.FromContext(c).Disable(u, r.GetOrg(), r.GetName()) - if err != nil { - retErr := fmt.Errorf("unable to delete webhook for %s: %w", r.GetFullName(), err) - - if err.Error() == "Repo not found" { - util.HandleError(c, http.StatusNotExtended, retErr) - - return - } - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - - // Mark the the repo as inactive - r.SetActive(false) - - err = database.FromContext(c).UpdateRepo(r) - if err != nil { - retErr := fmt.Errorf("unable to set repo %s to inactive: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - - // Comment out actual delete until delete mechanism is fleshed out - // err = database.FromContext(c).DeleteRepo(r.ID) - // if err != nil { - // retErr := fmt.Errorf("Error while deleting repo %s: %w", r.FullName, err) - // util.HandleError(c, http.StatusInternalServerError, retErr) - // return - // } - - c.JSON(http.StatusOK, fmt.Sprintf("repo %s deleted", r.GetFullName())) -} - -// swagger:operation PATCH /api/v1/repos/{org}/{repo}/repair repos RepairRepo -// -// Remove and recreate the webhook for a repo -// -// --- -// 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 -// security: -// - ApiKeyAuth: [] -// responses: -// '200': -// description: Successfully repaired the repo -// schema: -// type: string -// '500': -// description: Unable to repair the repo -// schema: -// "$ref": "#/definitions/Error" - -// RepairRepo represents the API handler to remove -// and then create a webhook for a repo. -func RepairRepo(c *gin.Context) { - // capture middleware values - o := org.Retrieve(c) - r := repo.Retrieve(c) - u := user.Retrieve(c) - - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("repairing repo %s", r.GetFullName()) - - // send API call to remove the webhook - err := scm.FromContext(c).Disable(u, r.GetOrg(), r.GetName()) - if err != nil { - retErr := fmt.Errorf("unable to delete webhook for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - - // send API call to create the webhook - _, err = scm.FromContext(c).Enable(u, r.GetOrg(), r.GetName(), r.GetHash()) - if err != nil { - retErr := fmt.Errorf("unable to create webhook for %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - - // if the repo was previously inactive, mark it as active - if !r.GetActive() { - r.SetActive(true) - - // send API call to update the repo - err = database.FromContext(c).UpdateRepo(r) - if err != nil { - retErr := fmt.Errorf("unable to set repo %s to active: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - } - - c.JSON(http.StatusOK, fmt.Sprintf("repo %s repaired", r.GetFullName())) -} - -// swagger:operation PATCH /api/v1/repos/{org}/{repo}/chown repos ChownRepo -// -// Change the owner of the webhook for a repo -// -// --- -// 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 -// security: -// - ApiKeyAuth: [] -// responses: -// '200': -// description: Successfully changed the owner for the repo -// schema: -// type: string -// '500': -// description: Unable to change the owner for the repo -// schema: -// "$ref": "#/definitions/Error" - -// ChownRepo represents the API handler to change -// the owner of a repo in the configured backend. -func ChownRepo(c *gin.Context) { - // capture middleware values - o := org.Retrieve(c) - r := repo.Retrieve(c) - u := user.Retrieve(c) - - // update engine logger with API metadata - // - // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields - logrus.WithFields(logrus.Fields{ - "org": o, - "repo": r.GetName(), - "user": u.GetName(), - }).Infof("changing owner of repo %s to %s", r.GetFullName(), u.GetName()) - - // update repo owner - r.SetUserID(u.GetID()) - - // send API call to updated the repo - err := database.FromContext(c).UpdateRepo(r) - if err != nil { - retErr := fmt.Errorf("unable to change owner of repo %s: %w", r.GetFullName(), err) - - util.HandleError(c, http.StatusInternalServerError, retErr) - - return - } - - c.JSON(http.StatusOK, fmt.Sprintf("repo %s changed owner", r.GetFullName())) -} - -// checkAllowlist is a helper function to ensure only repos in the -// allowlist are allowed to enable repos. -// -// a single entry of '*' allows any repo to be enabled. -func checkAllowlist(r *library.Repo, allowlist []string) bool { - // check if all repos are allowed to be enabled - if len(allowlist) == 1 && allowlist[0] == "*" { - return true - } - - for _, repo := range allowlist { - // allow all repos in org - if strings.Contains(repo, "/*") { - if strings.HasPrefix(repo, r.GetOrg()) { - return true - } - } - - // allow specific repo within org - if repo == r.GetFullName() { - return true - } - } - - return false -} diff --git a/api/repo/chown.go b/api/repo/chown.go new file mode 100644 index 000000000..00e728128 --- /dev/null +++ b/api/repo/chown.go @@ -0,0 +1,81 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-vela/server/database" + "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/sirupsen/logrus" +) + +// swagger:operation PATCH /api/v1/repos/{org}/{repo}/chown repos ChownRepo +// +// Change the owner of the webhook for a repo +// +// --- +// 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 +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully changed the owner for the repo +// schema: +// type: string +// '500': +// description: Unable to change the owner for the repo +// schema: +// "$ref": "#/definitions/Error" + +// ChownRepo represents the API handler to change +// the owner of a repo in the configured backend. +func ChownRepo(c *gin.Context) { + // capture middleware values + o := org.Retrieve(c) + r := repo.Retrieve(c) + u := user.Retrieve(c) + + // update engine logger with API metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields + logrus.WithFields(logrus.Fields{ + "org": o, + "repo": r.GetName(), + "user": u.GetName(), + }).Infof("changing owner of repo %s to %s", r.GetFullName(), u.GetName()) + + // update repo owner + r.SetUserID(u.GetID()) + + // send API call to update the repo + err := database.FromContext(c).UpdateRepo(r) + if err != nil { + retErr := fmt.Errorf("unable to change owner of repo %s to %s: %w", r.GetFullName(), u.GetName(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + c.JSON(http.StatusOK, fmt.Sprintf("repo %s changed owner to %s", r.GetFullName(), u.GetName())) +} diff --git a/api/repo/create.go b/api/repo/create.go new file mode 100644 index 000000000..d163d6320 --- /dev/null +++ b/api/repo/create.go @@ -0,0 +1,327 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "encoding/base64" + "fmt" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "github.com/go-vela/server/database" + "github.com/go-vela/server/router/middleware/user" + "github.com/go-vela/server/scm" + "github.com/go-vela/server/util" + "github.com/go-vela/types/constants" + "github.com/go-vela/types/library" + "github.com/google/uuid" + "github.com/sirupsen/logrus" +) + +// swagger:operation POST /api/v1/repos repos CreateRepo +// +// Create a repo in the configured backend +// +// --- +// produces: +// - application/json +// parameters: +// - in: body +// name: body +// description: Payload containing the repo to create +// required: true +// schema: +// "$ref": "#/definitions/Repo" +// security: +// - ApiKeyAuth: [] +// responses: +// '201': +// description: Successfully created the repo +// schema: +// "$ref": "#/definitions/Repo" +// '400': +// description: Unable to create the repo +// schema: +// "$ref": "#/definitions/Error" +// '403': +// description: Unable to create the repo +// schema: +// "$ref": "#/definitions/Error" +// '409': +// description: Unable to create the repo +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unable to create the repo +// schema: +// "$ref": "#/definitions/Error" +// '503': +// description: Unable to create the repo +// schema: +// "$ref": "#/definitions/Error" + +// CreateRepo represents the API handler to +// create a repo in the configured backend. +// +//nolint:funlen,gocyclo // ignore function length and cyclomatic complexity +func CreateRepo(c *gin.Context) { + // capture middleware values + u := user.Retrieve(c) + allowlist := c.Value("allowlist").([]string) + defaultBuildLimit := c.Value("defaultBuildLimit").(int64) + defaultTimeout := c.Value("defaultTimeout").(int64) + maxBuildLimit := c.Value("maxBuildLimit").(int64) + defaultRepoEvents := c.Value("defaultRepoEvents").([]string) + + // capture body from API request + input := new(library.Repo) + + err := c.Bind(input) + if err != nil { + retErr := fmt.Errorf("unable to decode JSON for new repo: %w", err) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + // update engine logger with API metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields + logrus.WithFields(logrus.Fields{ + "org": input.GetOrg(), + "repo": input.GetName(), + "user": u.GetName(), + }).Infof("creating new repo %s", input.GetFullName()) + + // get repo information from the source + r, err := scm.FromContext(c).GetRepo(u, input) + if err != nil { + retErr := fmt.Errorf("unable to retrieve repo info for %s from source: %w", r.GetFullName(), err) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + // update fields in repo object + r.SetUserID(u.GetID()) + + // set the active field based off the input provided + if input.Active == nil { + // default active field to true + r.SetActive(true) + } else { + r.SetActive(input.GetActive()) + } + + // set the build limit field based off the input provided + if input.GetBuildLimit() == 0 { + // default build limit to value configured by server + r.SetBuildLimit(defaultBuildLimit) + } else if input.GetBuildLimit() > maxBuildLimit { + // set build limit to value configured by server to prevent limit from exceeding max + r.SetBuildLimit(maxBuildLimit) + } else { + r.SetBuildLimit(input.GetBuildLimit()) + } + + // set the timeout field based off the input provided + if input.GetTimeout() == 0 && defaultTimeout == 0 { + // default build timeout to 30m + r.SetTimeout(constants.BuildTimeoutDefault) + } else if input.GetTimeout() == 0 { + r.SetTimeout(defaultTimeout) + } else { + r.SetTimeout(input.GetTimeout()) + } + + // set the visibility field based off the input provided + if len(input.GetVisibility()) == 0 { + // default visibility field to public + r.SetVisibility(constants.VisibilityPublic) + } else { + r.SetVisibility(input.GetVisibility()) + } + + // fields restricted to platform admins + if u.GetAdmin() { + // trusted default is false + if input.GetTrusted() != r.GetTrusted() { + r.SetTrusted(input.GetTrusted()) + } + } + + // set default events if no events are passed in + if !input.GetAllowPull() && !input.GetAllowPush() && + !input.GetAllowDeploy() && !input.GetAllowTag() && + !input.GetAllowComment() { + for _, event := range defaultRepoEvents { + switch event { + case constants.EventPull: + r.SetAllowPull(true) + case constants.EventPush: + r.SetAllowPush(true) + case constants.EventDeploy: + r.SetAllowDeploy(true) + case constants.EventTag: + r.SetAllowTag(true) + case constants.EventComment: + r.SetAllowComment(true) + } + } + } else { + r.SetAllowComment(input.GetAllowComment()) + r.SetAllowDeploy(input.GetAllowDeploy()) + r.SetAllowPull(input.GetAllowPull()) + r.SetAllowPush(input.GetAllowPush()) + r.SetAllowTag(input.GetAllowTag()) + } + + if len(input.GetPipelineType()) == 0 { + r.SetPipelineType(constants.PipelineTypeYAML) + } else { + // ensure the pipeline type matches one of the expected values + if input.GetPipelineType() != constants.PipelineTypeYAML && + input.GetPipelineType() != constants.PipelineTypeGo && + input.GetPipelineType() != constants.PipelineTypeStarlark { + retErr := fmt.Errorf("unable to create new repo %s: invalid pipeline_type provided %s", r.GetFullName(), input.GetPipelineType()) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + r.SetPipelineType(input.GetPipelineType()) + } + + // create unique id for the repo + uid, err := uuid.NewRandom() + if err != nil { + retErr := fmt.Errorf("unable to create UID for repo %s: %w", r.GetFullName(), err) + + util.HandleError(c, http.StatusServiceUnavailable, retErr) + + return + } + + r.SetHash( + base64.StdEncoding.EncodeToString( + []byte(strings.TrimSpace(uid.String())), + ), + ) + + // ensure repo is allowed to be activated + if !checkAllowlist(r, allowlist) { + retErr := fmt.Errorf("unable to activate repo: %s is not on allowlist", r.GetFullName()) + + util.HandleError(c, http.StatusForbidden, retErr) + + return + } + + // send API call to capture the repo from the database + dbRepo, err := database.FromContext(c).GetRepoForOrg(r.GetOrg(), r.GetName()) + if err == nil && dbRepo.GetActive() { + retErr := fmt.Errorf("unable to activate repo: %s is already active", r.GetFullName()) + + util.HandleError(c, http.StatusConflict, retErr) + + return + } + + // check if the repo already has a hash created + if len(dbRepo.GetHash()) > 0 { + // overwrite the new repo hash with the existing repo hash + r.SetHash(dbRepo.GetHash()) + } + + // check if we should create the webhook + if c.Value("webhookvalidation").(bool) { + // send API call to create the webhook + _, err = scm.FromContext(c).Enable(u, r.GetOrg(), r.GetName(), r.GetHash()) + if err != nil { + retErr := fmt.Errorf("unable to create webhook for %s: %w", r.GetFullName(), err) + + switch err.Error() { + case "repo already enabled": + util.HandleError(c, http.StatusConflict, retErr) + return + case "repo not found": + util.HandleError(c, http.StatusNotFound, retErr) + return + } + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + } + + // if the repo exists but is inactive + if len(dbRepo.GetOrg()) > 0 && !dbRepo.GetActive() { + // update the repo owner + dbRepo.SetUserID(u.GetID()) + // update the default branch + dbRepo.SetBranch(r.GetBranch()) + // activate the repo + dbRepo.SetActive(true) + + // send API call to update the repo + err = database.FromContext(c).UpdateRepo(dbRepo) + if err != nil { + retErr := fmt.Errorf("unable to set repo %s to active: %w", dbRepo.GetFullName(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + // send API call to capture the updated repo + r, _ = database.FromContext(c).GetRepoForOrg(dbRepo.GetOrg(), dbRepo.GetName()) + } else { + // send API call to create the repo + err = database.FromContext(c).CreateRepo(r) + if err != nil { + retErr := fmt.Errorf("unable to create new repo %s: %w", r.GetFullName(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + // send API call to capture the created repo + r, _ = database.FromContext(c).GetRepoForOrg(r.GetOrg(), r.GetName()) + } + + c.JSON(http.StatusCreated, r) +} + +// checkAllowlist is a helper function to ensure only repos in the +// allowlist are allowed to enable repos. +// +// a single entry of '*' allows any repo to be enabled. +func checkAllowlist(r *library.Repo, allowlist []string) bool { + // check if all repos are allowed to be enabled + if len(allowlist) == 1 && allowlist[0] == "*" { + return true + } + + for _, repo := range allowlist { + // allow all repos in org + if strings.Contains(repo, "/*") { + if strings.HasPrefix(repo, r.GetOrg()) { + return true + } + } + + // allow specific repo within org + if repo == r.GetFullName() { + return true + } + } + + return false +} diff --git a/api/repo/delete.go b/api/repo/delete.go new file mode 100644 index 000000000..63649f2eb --- /dev/null +++ b/api/repo/delete.go @@ -0,0 +1,109 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-vela/server/database" + "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/scm" + "github.com/go-vela/server/util" + "github.com/sirupsen/logrus" +) + +// swagger:operation DELETE /api/v1/repos/{org}/{repo} repos DeleteRepo +// +// Delete a repo in the configured backend +// +// --- +// 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 +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully deleted the repo +// schema: +// type: string +// '500': +// description: Unable to deleted the repo +// schema: +// "$ref": "#/definitions/Error" +// '510': +// description: Unable to deleted the repo +// schema: +// "$ref": "#/definitions/Error" + +// DeleteRepo represents the API handler to remove +// a repo from the configured backend. +func DeleteRepo(c *gin.Context) { + // capture middleware values + o := org.Retrieve(c) + r := repo.Retrieve(c) + u := user.Retrieve(c) + + // update engine logger with API metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields + logrus.WithFields(logrus.Fields{ + "org": o, + "repo": r.GetName(), + "user": u.GetName(), + }).Infof("deleting repo %s", r.GetFullName()) + + // send API call to remove the webhook + err := scm.FromContext(c).Disable(u, r.GetOrg(), r.GetName()) + if err != nil { + retErr := fmt.Errorf("unable to delete webhook for %s: %w", r.GetFullName(), err) + + if err.Error() == "Repo not found" { + util.HandleError(c, http.StatusNotFound, retErr) + + return + } + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + // Mark the repo as inactive + r.SetActive(false) + + err = database.FromContext(c).UpdateRepo(r) + if err != nil { + retErr := fmt.Errorf("unable to set repo %s to inactive: %w", r.GetFullName(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + // Comment out actual delete until delete mechanism is fleshed out + // err = database.FromContext(c).DeleteRepo(r.ID) + // if err != nil { + // retErr := fmt.Errorf("Error while deleting repo %s: %w", r.FullName, err) + // util.HandleError(c, http.StatusInternalServerError, retErr) + // return + // } + + c.JSON(http.StatusOK, fmt.Sprintf("repo %s set to inactive", r.GetFullName())) +} diff --git a/api/repo/get.go b/api/repo/get.go new file mode 100644 index 000000000..65cbede1b --- /dev/null +++ b/api/repo/get.go @@ -0,0 +1,61 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "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/sirupsen/logrus" +) + +// swagger:operation GET /api/v1/repos/{org}/{repo} repos GetRepo +// +// Get a repo in the configured backend +// +// --- +// 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 +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully retrieved the repo +// schema: +// "$ref": "#/definitions/Repo" + +// GetRepo represents the API handler to +// capture a repo from the configured backend. +func GetRepo(c *gin.Context) { + // capture middleware values + o := org.Retrieve(c) + r := repo.Retrieve(c) + u := user.Retrieve(c) + + // update engine logger with API metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields + logrus.WithFields(logrus.Fields{ + "org": o, + "repo": r.GetName(), + "user": u.GetName(), + }).Infof("reading repo %s", r.GetFullName()) + + c.JSON(http.StatusOK, r) +} diff --git a/api/repo/list.go b/api/repo/list.go new file mode 100644 index 000000000..c4ea06c54 --- /dev/null +++ b/api/repo/list.go @@ -0,0 +1,130 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "github.com/go-vela/server/api" + "github.com/go-vela/server/database" + "github.com/go-vela/server/router/middleware/user" + "github.com/go-vela/server/util" + "github.com/sirupsen/logrus" +) + +// swagger:operation GET /api/v1/repos repos ListRepos +// +// Get all repos in the configured backend +// +// --- +// produces: +// - application/json +// security: +// - ApiKeyAuth: [] +// parameters: +// - in: query +// name: page +// description: The page of results to retrieve +// type: integer +// default: 1 +// - in: query +// name: per_page +// description: How many results per page to return +// type: integer +// maximum: 100 +// default: 10 +// responses: +// '200': +// description: Successfully retrieved the repo +// schema: +// type: array +// items: +// "$ref": "#/definitions/Repo" +// headers: +// X-Total-Count: +// description: Total number of results +// type: integer +// Link: +// description: see https://tools.ietf.org/html/rfc5988 +// type: string +// '400': +// description: Unable to retrieve the repo +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unable to retrieve the repo +// schema: +// "$ref": "#/definitions/Error" + +// ListRepos represents the API handler to capture a list +// of repos for a user from the configured backend. +func ListRepos(c *gin.Context) { + // capture middleware values + u := user.Retrieve(c) + + // update engine logger with API metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields + logrus.WithFields(logrus.Fields{ + "user": u.GetName(), + }).Infof("listing repos for user %s", u.GetName()) + + // capture page query parameter if present + page, err := strconv.Atoi(c.DefaultQuery("page", "1")) + if err != nil { + retErr := fmt.Errorf("unable to convert page query parameter for user %s: %w", u.GetName(), err) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + // capture per_page query parameter if present + perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10")) + if err != nil { + retErr := fmt.Errorf("unable to convert per_page query parameter for user %s: %w", u.GetName(), err) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + // ensure per_page isn't above or below allowed values + perPage = util.MaxInt(1, util.MinInt(100, perPage)) + + // capture the sort_by query parameter if present + sortBy := util.QueryParameter(c, "sort_by", "name") + + // capture the query parameters if present: + // + // * active + filters := map[string]interface{}{ + "active": util.QueryParameter(c, "active", "true"), + } + + // send API call to capture the list of repos for the user + r, t, err := database.FromContext(c).ListReposForUser(u, sortBy, filters, page, perPage) + if err != nil { + retErr := fmt.Errorf("unable to get repos for user %s: %w", u.GetName(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + // create pagination object + pagination := api.Pagination{ + Page: page, + PerPage: perPage, + Total: t, + } + // set pagination headers + pagination.SetHeaderLink(c) + + c.JSON(http.StatusOK, r) +} diff --git a/api/repo/list_org.go b/api/repo/list_org.go new file mode 100644 index 000000000..2a8353d03 --- /dev/null +++ b/api/repo/list_org.go @@ -0,0 +1,163 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "github.com/go-vela/server/api" + "github.com/go-vela/server/database" + "github.com/go-vela/server/router/middleware/org" + "github.com/go-vela/server/router/middleware/user" + "github.com/go-vela/server/scm" + "github.com/go-vela/server/util" + "github.com/go-vela/types/constants" + "github.com/sirupsen/logrus" +) + +// swagger:operation GET /api/v1/repos/{org} repos ListReposForOrg +// +// Get all repos for the provided org in the configured backend +// +// --- +// produces: +// - application/json +// security: +// - ApiKeyAuth: [] +// parameters: +// - in: path +// name: org +// description: Name of the org +// required: true +// type: string +// - in: query +// name: active +// description: Filter active repos +// type: boolean +// default: true +// - in: query +// name: page +// description: The page of results to retrieve +// type: integer +// default: 1 +// - in: query +// name: per_page +// description: How many results per page to return +// type: integer +// maximum: 100 +// default: 10 +// - in: query +// name: sort_by +// description: How to sort the results +// type: string +// enum: +// - name +// - latest +// default: name +// responses: +// '200': +// description: Successfully retrieved the repo +// schema: +// type: array +// items: +// "$ref": "#/definitions/Repo" +// headers: +// X-Total-Count: +// description: Total number of results +// type: integer +// Link: +// description: see https://tools.ietf.org/html/rfc5988 +// type: string +// '400': +// description: Unable to retrieve the org +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unable to retrieve the org +// schema: +// "$ref": "#/definitions/Error" + +// ListReposForOrg represents the API handler to capture a list +// of repos for an org from the configured backend. +func ListReposForOrg(c *gin.Context) { + // capture middleware values + o := org.Retrieve(c) + u := user.Retrieve(c) + + // update engine logger with API metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields + logrus.WithFields(logrus.Fields{ + "org": o, + "user": u.GetName(), + }).Infof("listing repos for org %s", o) + + // capture page query parameter if present + page, err := strconv.Atoi(c.DefaultQuery("page", "1")) + if err != nil { + retErr := fmt.Errorf("unable to convert page query parameter for user %s: %w", u.GetName(), err) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + // capture per_page query parameter if present + perPage, err := strconv.Atoi(c.DefaultQuery("per_page", "10")) + if err != nil { + retErr := fmt.Errorf("unable to convert per_page query parameter for user %s: %w", u.GetName(), err) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + // ensure per_page isn't above or below allowed values + perPage = util.MaxInt(1, util.MinInt(100, perPage)) + + // capture the sort_by query parameter if present + sortBy := util.QueryParameter(c, "sort_by", "name") + + // capture the query parameters if present: + // + // * active + filters := map[string]interface{}{ + "active": util.QueryParameter(c, "active", "true"), + } + + // See if the user is an org admin to bypass individual permission checks + perm, err := scm.FromContext(c).OrgAccess(u, o) + if err != nil { + logrus.Errorf("unable to get user %s access level for org %s", u.GetName(), o) + } + // Only show public repos to non-admins + if perm != "admin" { + filters["visibility"] = constants.VisibilityPublic + } + + // send API call to capture the list of repos for the org + r, t, err := database.FromContext(c).ListReposForOrg(o, sortBy, filters, page, perPage) + if err != nil { + retErr := fmt.Errorf("unable to get repos for org %s: %w", o, err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + // create pagination object + pagination := api.Pagination{ + Page: page, + PerPage: perPage, + Total: t, + } + // set pagination headers + pagination.SetHeaderLink(c) + + c.JSON(http.StatusOK, r) +} diff --git a/api/repo/repair.go b/api/repo/repair.go new file mode 100644 index 000000000..cbe5295e1 --- /dev/null +++ b/api/repo/repair.go @@ -0,0 +1,116 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-vela/server/database" + "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/scm" + "github.com/go-vela/server/util" + "github.com/sirupsen/logrus" +) + +// swagger:operation PATCH /api/v1/repos/{org}/{repo}/repair repos RepairRepo +// +// Remove and recreate the webhook for a repo +// +// --- +// 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 +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully repaired the repo +// schema: +// type: string +// '500': +// description: Unable to repair the repo +// schema: +// "$ref": "#/definitions/Error" + +// RepairRepo represents the API handler to remove +// and then create a webhook for a repo. +func RepairRepo(c *gin.Context) { + // capture middleware values + o := org.Retrieve(c) + r := repo.Retrieve(c) + u := user.Retrieve(c) + + // update engine logger with API metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields + logrus.WithFields(logrus.Fields{ + "org": o, + "repo": r.GetName(), + "user": u.GetName(), + }).Infof("repairing repo %s", r.GetFullName()) + + // check if we should create the webhook + if c.Value("webhookvalidation").(bool) { + // send API call to remove the webhook + err := scm.FromContext(c).Disable(u, r.GetOrg(), r.GetName()) + if err != nil { + retErr := fmt.Errorf("unable to delete webhook for %s: %w", r.GetFullName(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + // send API call to create the webhook + _, err = scm.FromContext(c).Enable(u, r.GetOrg(), r.GetName(), r.GetHash()) + if err != nil { + retErr := fmt.Errorf("unable to create webhook for %s: %w", r.GetFullName(), err) + + switch err.Error() { + case "repo already enabled": + util.HandleError(c, http.StatusConflict, retErr) + return + case "repo not found": + util.HandleError(c, http.StatusNotFound, retErr) + return + } + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + } + + // if the repo was previously inactive, mark it as active + if !r.GetActive() { + r.SetActive(true) + + // send API call to update the repo + err := database.FromContext(c).UpdateRepo(r) + if err != nil { + retErr := fmt.Errorf("unable to set repo %s to active: %w", r.GetFullName(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + } + + c.JSON(http.StatusOK, fmt.Sprintf("repo %s repaired", r.GetFullName())) +} diff --git a/api/repo/update.go b/api/repo/update.go new file mode 100644 index 000000000..9ca114270 --- /dev/null +++ b/api/repo/update.go @@ -0,0 +1,255 @@ +// Copyright (c) 2022 Target Brands, Inc. All rights reserved. +// +// Use of this source code is governed by the LICENSE file in this repository. + +package repo + +import ( + "encoding/base64" + "fmt" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "github.com/go-vela/server/database" + "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/go-vela/types/library" + "github.com/google/uuid" + "github.com/sirupsen/logrus" +) + +// swagger:operation PUT /api/v1/repos/{org}/{repo} repos UpdateRepo +// +// Update a repo in the configured backend +// +// --- +// 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: body +// name: body +// description: Payload containing the repo to update +// required: true +// schema: +// "$ref": "#/definitions/Repo" +// security: +// - ApiKeyAuth: [] +// responses: +// '200': +// description: Successfully updated the repo +// schema: +// "$ref": "#/definitions/Repo" +// '400': +// description: Unable to update the repo +// schema: +// "$ref": "#/definitions/Error" +// '500': +// description: Unable to update the repo +// schema: +// "$ref": "#/definitions/Error" +// '503': +// description: Unable to update the repo +// schema: +// "$ref": "#/definitions/Error" + +// UpdateRepo represents the API handler to update +// a repo in the configured backend. +// +//nolint:funlen // ignore function length +func UpdateRepo(c *gin.Context) { + // capture middleware values + o := org.Retrieve(c) + r := repo.Retrieve(c) + u := user.Retrieve(c) + maxBuildLimit := c.Value("maxBuildLimit").(int64) + + // update engine logger with API metadata + // + // https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields + logrus.WithFields(logrus.Fields{ + "org": o, + "repo": r.GetName(), + "user": u.GetName(), + }).Infof("updating repo %s", r.GetFullName()) + + // capture body from API request + input := new(library.Repo) + + err := c.Bind(input) + if err != nil { + retErr := fmt.Errorf("unable to decode JSON for repo %s: %w", r.GetFullName(), err) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + // update repo fields if provided + if len(input.GetBranch()) > 0 { + // update branch if set + r.SetBranch(input.GetBranch()) + } + + // update build limit if set + if input.GetBuildLimit() > 0 { + // allow build limit between 1 - value configured by server + r.SetBuildLimit( + int64( + util.MaxInt( + constants.BuildLimitMin, + util.MinInt( + int(input.GetBuildLimit()), + int(maxBuildLimit), + ), // clamp max + ), // clamp min + ), + ) + } + + if input.GetTimeout() > 0 { + // update build timeout if set + r.SetTimeout( + int64( + util.MaxInt( + constants.BuildTimeoutMin, + util.MinInt( + int(input.GetTimeout()), + constants.BuildTimeoutMax, + ), // clamp max + ), // clamp min + ), + ) + } + + if input.GetCounter() > 0 { + if input.GetCounter() <= r.GetCounter() { + retErr := fmt.Errorf("unable to set counter for repo %s: must be greater than current %d", + r.GetFullName(), r.GetCounter()) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + r.SetCounter(input.GetCounter()) + } + + if len(input.GetVisibility()) > 0 { + // update visibility if set + r.SetVisibility(input.GetVisibility()) + } + + if input.Private != nil { + // update private if set + r.SetPrivate(input.GetPrivate()) + } + + if input.Active != nil { + // update active if set + r.SetActive(input.GetActive()) + } + + if input.AllowPull != nil { + // update allow_pull if set + r.SetAllowPull(input.GetAllowPull()) + } + + if input.AllowPush != nil { + // update allow_push if set + r.SetAllowPush(input.GetAllowPush()) + } + + if input.AllowDeploy != nil { + // update allow_deploy if set + r.SetAllowDeploy(input.GetAllowDeploy()) + } + + if input.AllowTag != nil { + // update allow_tag if set + r.SetAllowTag(input.GetAllowTag()) + } + + if input.AllowComment != nil { + // update allow_comment if set + r.SetAllowComment(input.GetAllowComment()) + } + + // set default events if no events are enabled + if !r.GetAllowPull() && !r.GetAllowPush() && + !r.GetAllowDeploy() && !r.GetAllowTag() && + !r.GetAllowComment() { + r.SetAllowPull(true) + r.SetAllowPush(true) + } + + if len(input.GetPipelineType()) != 0 { + // ensure the pipeline type matches one of the expected values + if input.GetPipelineType() != constants.PipelineTypeYAML && + input.GetPipelineType() != constants.PipelineTypeGo && + input.GetPipelineType() != constants.PipelineTypeStarlark { + retErr := fmt.Errorf("pipeline_type of %s is invalid", input.GetPipelineType()) + + util.HandleError(c, http.StatusBadRequest, retErr) + + return + } + + r.SetPipelineType(input.GetPipelineType()) + } + + // set hash for repo if no hash is already set + if len(r.GetHash()) == 0 { + // create unique id for the repo + uid, err := uuid.NewRandom() + if err != nil { + retErr := fmt.Errorf("unable to create UID for repo %s: %w", r.GetFullName(), err) + + util.HandleError(c, http.StatusServiceUnavailable, retErr) + + return + } + + r.SetHash( + base64.StdEncoding.EncodeToString( + []byte(strings.TrimSpace(uid.String())), + ), + ) + } + + // fields restricted to platform admins + if u.GetAdmin() { + // trusted + if input.GetTrusted() != r.GetTrusted() { + r.SetTrusted(input.GetTrusted()) + } + } + + // send API call to update the repo + err = database.FromContext(c).UpdateRepo(r) + if err != nil { + retErr := fmt.Errorf("unable to update repo %s: %w", r.GetFullName(), err) + + util.HandleError(c, http.StatusInternalServerError, retErr) + + return + } + + // send API call to capture the updated repo + r, _ = database.FromContext(c).GetRepoForOrg(r.GetOrg(), r.GetName()) + + c.JSON(http.StatusOK, r) +} diff --git a/router/middleware/perm/perm.go b/router/middleware/perm/perm.go index fe7cea501..1cc6e397d 100644 --- a/router/middleware/perm/perm.go +++ b/router/middleware/perm/perm.go @@ -78,7 +78,7 @@ func MustWorker() gin.HandlerFunc { // validate claims as worker switch { - case (strings.EqualFold(cl.Subject, "vela-worker") && strings.EqualFold(cl.TokenType, constants.ServerWorkerTokenType)): + case strings.EqualFold(cl.Subject, "vela-worker") && strings.EqualFold(cl.TokenType, constants.ServerWorkerTokenType): return default: @@ -136,6 +136,8 @@ func MustBuildAccess() gin.HandlerFunc { } // MustSecretAdmin ensures the user has admin access to the org, repo or team. +// +//nolint:funlen // ignore function length func MustSecretAdmin() gin.HandlerFunc { return func(c *gin.Context) { cl := claims.Retrieve(c) @@ -256,6 +258,16 @@ func MustSecretAdmin() gin.HandlerFunc { } case constants.SecretShared: if n == "*" && m == "GET" { + // check if user is accessing shared secrets in personal org + if strings.EqualFold(o, u.GetName()) { + logger.WithFields(logrus.Fields{ + "org": o, + "user": u.GetName(), + }).Debugf("skipping gathering teams for user %s with org %s", u.GetName(), o) + + return + } + logger.Debugf("gathering teams user %s is a member of in the org %s", u.GetName(), o) teams, err := scm.FromContext(c).ListUsersTeamsForOrg(u, o) diff --git a/router/middleware/perm/perm_test.go b/router/middleware/perm/perm_test.go index 98b00aefd..93f4ddff9 100644 --- a/router/middleware/perm/perm_test.go +++ b/router/middleware/perm/perm_test.go @@ -41,7 +41,7 @@ func TestPerm_MustPlatformAdmin(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(true) @@ -121,7 +121,7 @@ func TestPerm_MustPlatformAdmin_NotAdmin(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(false) @@ -877,7 +877,7 @@ func TestPerm_MustAdmin(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(false) @@ -973,7 +973,7 @@ func TestPerm_MustAdmin_PlatAdmin(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(true) @@ -1069,7 +1069,7 @@ func TestPerm_MustAdmin_NotAdmin(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(false) @@ -1165,7 +1165,7 @@ func TestPerm_MustWrite(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(false) @@ -1261,7 +1261,7 @@ func TestPerm_MustWrite_PlatAdmin(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(true) @@ -1357,7 +1357,7 @@ func TestPerm_MustWrite_RepoAdmin(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(false) @@ -1453,7 +1453,7 @@ func TestPerm_MustWrite_NotWrite(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(false) @@ -1549,7 +1549,7 @@ func TestPerm_MustRead(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(false) @@ -1645,7 +1645,7 @@ func TestPerm_MustRead_PlatAdmin(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(true) @@ -1823,7 +1823,7 @@ func TestPerm_MustRead_RepoAdmin(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(false) @@ -1919,7 +1919,7 @@ func TestPerm_MustRead_RepoWrite(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(false) @@ -2015,7 +2015,7 @@ func TestPerm_MustRead_RepoPublic(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(false) @@ -2111,7 +2111,7 @@ func TestPerm_MustRead_NotRead(t *testing.T) { u := new(library.User) u.SetID(1) - u.SetName("foo") + u.SetName("foob") u.SetToken("bar") u.SetHash("baz") u.SetAdmin(false) @@ -2291,7 +2291,7 @@ const permNonePayload = ` const userPayload = ` { - "login": "foo", + "login": "foob", "id": 1, "node_id": "MDQ6VXNlcjE=", "avatar_url": "https://github.com/images/error/octocat_happy.gif", diff --git a/router/repo.go b/router/repo.go index 0ebbb3522..1383d71c2 100644 --- a/router/repo.go +++ b/router/repo.go @@ -7,10 +7,11 @@ package router import ( "github.com/gin-gonic/gin" "github.com/go-vela/server/api" + "github.com/go-vela/server/api/repo" "github.com/go-vela/server/router/middleware" "github.com/go-vela/server/router/middleware/org" "github.com/go-vela/server/router/middleware/perm" - "github.com/go-vela/server/router/middleware/repo" + rmiddleware "github.com/go-vela/server/router/middleware/repo" ) // RepoHandlers is a function that extends the provided base router group @@ -52,32 +53,32 @@ import ( // DELETE /api/v1/repos/:org/:repo/builds/:build/steps/:step/logs . func RepoHandlers(base *gin.RouterGroup) { // Repos endpoints - repos := base.Group("/repos") + _repos := base.Group("/repos") { - repos.POST("", middleware.Payload(), api.CreateRepo) - repos.GET("", api.GetRepos) + _repos.POST("", middleware.Payload(), repo.CreateRepo) + _repos.GET("", repo.ListRepos) // Org endpoints - org := repos.Group("/:org", org.Establish()) + org := _repos.Group("/:org", org.Establish()) { - org.GET("", api.GetOrgRepos) + org.GET("", repo.ListReposForOrg) org.GET("/builds", api.GetOrgBuilds) // Repo endpoints - repo := org.Group("/:repo", repo.Establish()) + _repo := org.Group("/:repo", rmiddleware.Establish()) { - repo.GET("", perm.MustRead(), api.GetRepo) - repo.PUT("", perm.MustAdmin(), middleware.Payload(), api.UpdateRepo) - repo.DELETE("", perm.MustAdmin(), api.DeleteRepo) - repo.PATCH("/repair", perm.MustAdmin(), api.RepairRepo) - repo.PATCH("/chown", perm.MustAdmin(), api.ChownRepo) + _repo.GET("", perm.MustRead(), repo.GetRepo) + _repo.PUT("", perm.MustAdmin(), middleware.Payload(), repo.UpdateRepo) + _repo.DELETE("", perm.MustAdmin(), repo.DeleteRepo) + _repo.PATCH("/repair", perm.MustAdmin(), repo.RepairRepo) + _repo.PATCH("/chown", perm.MustAdmin(), repo.ChownRepo) // Build endpoints // * Service endpoints // * Log endpoints // * Step endpoints // * Log endpoints - BuildHandlers(repo) + BuildHandlers(_repo) } // end of repo endpoints } // end of org endpoints } // end of repos endpoints diff --git a/scm/github/access.go b/scm/github/access.go index cf292e5a7..e9a276662 100644 --- a/scm/github/access.go +++ b/scm/github/access.go @@ -20,8 +20,13 @@ func (c *client) OrgAccess(u *library.User, org string) (string, error) { "user": u.GetName(), }).Tracef("capturing %s access level to org %s", u.GetName(), org) - // if user is accessing personal org - if strings.EqualFold(org, *u.Name) { + // check if user is accessing personal org + if strings.EqualFold(org, u.GetName()) { + c.Logger.WithFields(logrus.Fields{ + "org": org, + "user": u.GetName(), + }).Debugf("skipping access level check for user %s with org %s", u.GetName(), org) + //nolint:goconst // ignore making constant return "admin", nil } @@ -51,6 +56,17 @@ func (c *client) RepoAccess(u *library.User, token, org, repo string) (string, e "user": u.GetName(), }).Tracef("capturing %s access level to repo %s/%s", u.GetName(), org, repo) + // check if user is accessing repo in personal org + if strings.EqualFold(org, u.GetName()) { + 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) + + return "admin", nil + } + // create github oauth client with the given token client := c.newClientToken(token) @@ -71,6 +87,17 @@ func (c *client) TeamAccess(u *library.User, org, team string) (string, error) { "user": u.GetName(), }).Tracef("capturing %s access level to team %s/%s", u.GetName(), org, team) + // check if user is accessing team in personal org + if strings.EqualFold(org, u.GetName()) { + c.Logger.WithFields(logrus.Fields{ + "org": org, + "team": team, + "user": u.GetName(), + }).Debugf("skipping access level check for user %s with team %s/%s", u.GetName(), org, team) + + return "admin", nil + } + // create GitHub OAuth client with user's token client := c.newClientToken(u.GetToken()) teams := []*github.Team{} diff --git a/scm/github/repo.go b/scm/github/repo.go index a3fc708fc..924d36000 100644 --- a/scm/github/repo.go +++ b/scm/github/repo.go @@ -98,7 +98,7 @@ func (c *client) Disable(u *library.User, org, name string) error { "org": org, "repo": name, "user": u.GetName(), - }).Tracef("deleting repository webhook for %s/%s", org, name) + }).Tracef("deleting repository webhooks for %s/%s", org, name) // create GitHub OAuth client with user's token client := c.newClientToken(*u.Token) @@ -132,6 +132,12 @@ func (c *client) Disable(u *library.User, org, name string) error { // skip if we have no hook IDs if len(ids) == 0 { + c.Logger.WithFields(logrus.Fields{ + "org": org, + "repo": name, + "user": u.GetName(), + }).Warnf("no repository webhooks matching %s/webhook found for %s/%s", c.config.ServerWebhookAddress, org, name) + return nil }