Various fixes and improvements (#1643)

- allow repo names to be case-insensitive
- improve backend error handling on DB get errors (record not found ->
404, else -> 500)
- replace magic numbers of http response codes
- unify the look and feel of cancel / save buttons on forms and view
them in one line

---------

Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
qwerty287 2023-03-19 13:52:58 +01:00 committed by GitHub
parent 42a115e19e
commit ade8e6d010
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 230 additions and 179 deletions

View file

@ -21,7 +21,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/gorilla/securecookie"
"github.com/woodpecker-ci/woodpecker/server/model"
"github.com/woodpecker-ci/woodpecker/server/router/middleware/session"
"github.com/woodpecker-ci/woodpecker/server/store"
@ -45,7 +44,7 @@ func GetAgent(c *gin.Context) {
agent, err := store.FromContext(c).AgentFind(agentID)
if err != nil {
c.String(http.StatusNotFound, "Cannot find agent. %s", err)
handleDbGetError(c, err)
return
}
c.JSON(http.StatusOK, agent)
@ -69,7 +68,7 @@ func PatchAgent(c *gin.Context) {
agent, err := _store.AgentFind(agentID)
if err != nil {
c.AbortWithStatus(http.StatusNotFound)
handleDbGetError(c, err)
return
}
agent.Name = in.Name
@ -121,12 +120,12 @@ func DeleteAgent(c *gin.Context) {
agent, err := _store.AgentFind(agentID)
if err != nil {
c.String(http.StatusNotFound, "Cannot find user. %s", err)
handleDbGetError(c, err)
return
}
if err = _store.AgentDelete(agent); err != nil {
c.String(http.StatusInternalServerError, "Error deleting user. %s", err)
return
}
c.String(http.StatusOK, "")
c.String(http.StatusNoContent, "")
}

View file

@ -19,10 +19,12 @@
package api
import (
"errors"
"fmt"
"net/http"
"github.com/rs/zerolog/log"
"github.com/woodpecker-ci/woodpecker/server/store/types"
"github.com/gin-gonic/gin"
@ -36,7 +38,11 @@ func GetBadge(c *gin.Context) {
_store := store.FromContext(c)
repo, err := _store.GetRepoName(c.Param("owner") + "/" + c.Param("name"))
if err != nil || !repo.IsActive {
c.AbortWithStatus(404)
if err == nil || errors.Is(err, types.RecordNotExist) {
c.AbortWithStatus(http.StatusNotFound)
return
}
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
@ -63,17 +69,17 @@ func GetCC(c *gin.Context) {
_store := store.FromContext(c)
repo, err := _store.GetRepoName(c.Param("owner") + "/" + c.Param("name"))
if err != nil {
c.AbortWithStatus(404)
handleDbGetError(c, err)
return
}
pipelines, err := _store.GetPipelineList(repo, 1)
if err != nil || len(pipelines) == 0 {
c.AbortWithStatus(404)
c.AbortWithStatus(http.StatusNotFound)
return
}
url := fmt.Sprintf("%s/%s/%d", server.Config.Server.Host, repo.FullName, pipelines[0].Number)
cc := ccmenu.New(repo, pipelines[0], url)
c.XML(200, cc)
c.XML(http.StatusOK, cc)
}

View file

@ -34,16 +34,16 @@ func GetCron(c *gin.Context) {
repo := session.Repo(c)
id, err := strconv.ParseInt(c.Param("cron"), 10, 64)
if err != nil {
c.String(400, "Error parsing cron id. %s", err)
c.String(http.StatusBadRequest, "Error parsing cron id. %s", err)
return
}
cron, err := store.FromContext(c).CronFind(repo, id)
if err != nil {
c.String(404, "Error getting cron %q. %s", id, err)
handleDbGetError(c, err)
return
}
c.JSON(200, cron)
c.JSON(http.StatusOK, cron)
}
// RunCron starts a cron job now.
@ -52,13 +52,13 @@ func RunCron(c *gin.Context) {
_store := store.FromContext(c)
id, err := strconv.ParseInt(c.Param("cron"), 10, 64)
if err != nil {
c.String(400, "Error parsing cron id. %s", err)
c.String(http.StatusBadRequest, "Error parsing cron id. %s", err)
return
}
cron, err := _store.CronFind(repo, id)
if err != nil {
c.String(http.StatusNotFound, "Error getting cron %q. %s", id, err)
handleDbGetError(c, err)
return
}
@ -74,14 +74,14 @@ func RunCron(c *gin.Context) {
return
}
c.JSON(200, pl)
c.JSON(http.StatusOK, pl)
}
// PostCron persists the cron job to the database.
func PostCron(c *gin.Context) {
repo := session.Repo(c)
user := session.User(c)
store := store.FromContext(c)
_store := store.FromContext(c)
forge := server.Config.Services.Forge
in := new(model.Cron)
@ -97,13 +97,13 @@ func PostCron(c *gin.Context) {
Branch: in.Branch,
}
if err := cron.Validate(); err != nil {
c.String(400, "Error inserting cron. validate failed: %s", err)
c.String(http.StatusUnprocessableEntity, "Error inserting cron. validate failed: %s", err)
return
}
nextExec, err := cronScheduler.CalcNewNext(in.Schedule, time.Now())
if err != nil {
c.String(400, "Error inserting cron. schedule could not parsed: %s", err)
c.String(http.StatusBadRequest, "Error inserting cron. schedule could not parsed: %s", err)
return
}
cron.NextExec = nextExec.Unix()
@ -112,28 +112,28 @@ func PostCron(c *gin.Context) {
// check if branch exists on forge
_, err := forge.BranchHead(c, user, repo, in.Branch)
if err != nil {
c.String(400, "Error inserting cron. branch not resolved: %s", err)
c.String(http.StatusBadRequest, "Error inserting cron. branch not resolved: %s", err)
return
}
}
if err := store.CronCreate(cron); err != nil {
c.String(500, "Error inserting cron %q. %s", in.Name, err)
if err := _store.CronCreate(cron); err != nil {
c.String(http.StatusInternalServerError, "Error inserting cron %q. %s", in.Name, err)
return
}
c.JSON(200, cron)
c.JSON(http.StatusOK, cron)
}
// PatchCron updates the cron job in the database.
func PatchCron(c *gin.Context) {
repo := session.Repo(c)
user := session.User(c)
store := store.FromContext(c)
_store := store.FromContext(c)
forge := server.Config.Services.Forge
id, err := strconv.ParseInt(c.Param("cron"), 10, 64)
if err != nil {
c.String(400, "Error parsing cron id. %s", err)
c.String(http.StatusBadRequest, "Error parsing cron id. %s", err)
return
}
@ -144,16 +144,16 @@ func PatchCron(c *gin.Context) {
return
}
cron, err := store.CronFind(repo, id)
cron, err := _store.CronFind(repo, id)
if err != nil {
c.String(404, "Error getting cron %d. %s", id, err)
handleDbGetError(c, err)
return
}
if in.Branch != "" {
// check if branch exists on forge
_, err := forge.BranchHead(c, user, repo, in.Branch)
if err != nil {
c.String(400, "Error inserting cron. branch not resolved: %s", err)
c.String(http.StatusBadRequest, "Error inserting cron. branch not resolved: %s", err)
return
}
cron.Branch = in.Branch
@ -162,7 +162,7 @@ func PatchCron(c *gin.Context) {
cron.Schedule = in.Schedule
nextExec, err := cronScheduler.CalcNewNext(in.Schedule, time.Now())
if err != nil {
c.String(400, "Error inserting cron. schedule could not parsed: %s", err)
c.String(http.StatusBadRequest, "Error inserting cron. schedule could not parsed: %s", err)
return
}
cron.NextExec = nextExec.Unix()
@ -173,14 +173,14 @@ func PatchCron(c *gin.Context) {
cron.CreatorID = user.ID
if err := cron.Validate(); err != nil {
c.String(400, "Error inserting cron. validate failed: %s", err)
c.String(http.StatusUnprocessableEntity, "Error inserting cron. validate failed: %s", err)
return
}
if err := store.CronUpdate(repo, cron); err != nil {
c.String(500, "Error updating cron %q. %s", in.Name, err)
if err := _store.CronUpdate(repo, cron); err != nil {
c.String(http.StatusInternalServerError, "Error updating cron %q. %s", in.Name, err)
return
}
c.JSON(200, cron)
c.JSON(http.StatusOK, cron)
}
// GetCronList gets the cron job list from the database and writes
@ -189,10 +189,10 @@ func GetCronList(c *gin.Context) {
repo := session.Repo(c)
list, err := store.FromContext(c).CronList(repo)
if err != nil {
c.String(500, "Error getting cron list. %s", err)
c.String(http.StatusInternalServerError, "Error getting cron list. %s", err)
return
}
c.JSON(200, list)
c.JSON(http.StatusOK, list)
}
// DeleteCron deletes the named cron job from the database.
@ -200,12 +200,12 @@ func DeleteCron(c *gin.Context) {
repo := session.Repo(c)
id, err := strconv.ParseInt(c.Param("cron"), 10, 64)
if err != nil {
c.String(400, "Error parsing cron id. %s", err)
c.String(http.StatusBadRequest, "Error parsing cron id. %s", err)
return
}
if err := store.FromContext(c).CronDelete(repo, id); err != nil {
c.String(500, "Error deleting cron %d. %s", id, err)
c.String(http.StatusInternalServerError, "Error deleting cron %d. %s", id, err)
return
}
c.String(204, "")
c.String(http.StatusNoContent, "")
}

View file

@ -23,7 +23,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"github.com/woodpecker-ci/woodpecker/server/router/middleware/session"
"github.com/woodpecker-ci/woodpecker/server/store"
)
@ -40,7 +39,7 @@ func FileList(c *gin.Context) {
repo := session.Repo(c)
pipeline, err := _store.GetPipelineNumber(repo, num)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
handleDbGetError(c, err)
return
}
@ -79,7 +78,7 @@ func FileGet(c *gin.Context) {
pipeline, err := _store.GetPipelineNumber(repo, num)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
handleDbGetError(c, err)
return
}
@ -91,18 +90,18 @@ func FileGet(c *gin.Context) {
file, err := _store.FileFind(step, name)
if err != nil {
c.String(404, "Error getting file %q. %s", name, err)
c.String(http.StatusNotFound, "Error getting file %q. %s", name, err)
return
}
if !raw {
c.JSON(200, file)
c.JSON(http.StatusOK, file)
return
}
rc, err := _store.FileRead(step, file.Name)
if err != nil {
c.String(404, "Error getting file stream %q. %s", name, err)
c.String(http.StatusNotFound, "Error getting file stream %q. %s", name, err)
return
}
defer rc.Close()

View file

@ -17,10 +17,9 @@ package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/woodpecker-ci/woodpecker/server"
"github.com/woodpecker-ci/woodpecker/server/model"
"github.com/gin-gonic/gin"
)
// GetGlobalSecretList gets the global secret list from
@ -45,10 +44,10 @@ func GetGlobalSecret(c *gin.Context) {
name := c.Param("secret")
secret, err := server.Config.Services.Secrets.GlobalSecretFind(name)
if err != nil {
c.String(404, "Error getting global secret %q. %s", name, err)
handleDbGetError(c, err)
return
}
c.JSON(200, secret.Copy())
c.JSON(http.StatusOK, secret.Copy())
}
// PostGlobalSecret persists a global secret to the database.
@ -66,14 +65,14 @@ func PostGlobalSecret(c *gin.Context) {
PluginsOnly: in.PluginsOnly,
}
if err := secret.Validate(); err != nil {
c.String(400, "Error inserting global secret. %s", err)
c.String(http.StatusBadRequest, "Error inserting global secret. %s", err)
return
}
if err := server.Config.Services.Secrets.GlobalSecretCreate(secret); err != nil {
c.String(500, "Error inserting global secret %q. %s", in.Name, err)
c.String(http.StatusInternalServerError, "Error inserting global secret %q. %s", in.Name, err)
return
}
c.JSON(200, secret.Copy())
c.JSON(http.StatusOK, secret.Copy())
}
// PatchGlobalSecret updates a global secret in the database.
@ -89,7 +88,7 @@ func PatchGlobalSecret(c *gin.Context) {
secret, err := server.Config.Services.Secrets.GlobalSecretFind(name)
if err != nil {
c.String(404, "Error getting global secret %q. %s", name, err)
handleDbGetError(c, err)
return
}
if in.Value != "" {
@ -104,22 +103,22 @@ func PatchGlobalSecret(c *gin.Context) {
secret.PluginsOnly = in.PluginsOnly
if err := secret.Validate(); err != nil {
c.String(400, "Error updating global secret. %s", err)
c.String(http.StatusBadRequest, "Error updating global secret. %s", err)
return
}
if err := server.Config.Services.Secrets.GlobalSecretUpdate(secret); err != nil {
c.String(500, "Error updating global secret %q. %s", in.Name, err)
c.String(http.StatusInternalServerError, "Error updating global secret %q. %s", in.Name, err)
return
}
c.JSON(200, secret.Copy())
c.JSON(http.StatusOK, secret.Copy())
}
// DeleteGlobalSecret deletes the named global secret from the database.
func DeleteGlobalSecret(c *gin.Context) {
name := c.Param("secret")
if err := server.Config.Services.Secrets.GlobalSecretDelete(name); err != nil {
c.String(500, "Error deleting global secret %q. %s", name, err)
c.String(http.StatusInternalServerError, "Error deleting global secret %q. %s", name, err)
return
}
c.String(204, "")
c.String(http.StatusNoContent, "")
}

View file

@ -20,6 +20,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"github.com/woodpecker-ci/woodpecker/server/store/types"
"github.com/woodpecker-ci/woodpecker/server"
"github.com/woodpecker-ci/woodpecker/server/forge"
@ -40,6 +41,14 @@ func handlePipelineErr(c *gin.Context, err error) {
}
}
func handleDbGetError(c *gin.Context, err error) {
if errors.Is(err, types.RecordNotExist) {
c.AbortWithStatus(http.StatusNotFound)
return
}
_ = c.AbortWithError(http.StatusInternalServerError, err)
}
// if the forge has a refresh token, the current access token may be stale.
// Therefore, we should refresh prior to dispatching the job.
func refreshUserToken(c *gin.Context, user *model.User) {

View file

@ -42,7 +42,7 @@ func init() {
}
func GetQueueInfo(c *gin.Context) {
c.IndentedJSON(200,
c.IndentedJSON(http.StatusOK,
server.Config.Services.Queue.Info(c),
)
}
@ -179,6 +179,6 @@ func PostHook(c *gin.Context) {
if err != nil {
handlePipelineErr(c, err)
} else {
c.JSON(200, pl)
c.JSON(http.StatusOK, pl)
}
}

View file

@ -16,12 +16,14 @@ package api
import (
"encoding/base32"
"errors"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/gorilla/securecookie"
"github.com/rs/zerolog/log"
"github.com/woodpecker-ci/woodpecker/server/store/types"
"github.com/woodpecker-ci/woodpecker/server"
"github.com/woodpecker-ci/woodpecker/server/model"
@ -52,7 +54,7 @@ func HandleAuth(c *gin.Context) {
tmpuser, err := server.Config.Services.Forge.Login(c, c.Writer, c.Request)
if err != nil {
log.Error().Msgf("cannot authenticate user. %s", err)
c.Redirect(303, "/login?error=oauth_error")
c.Redirect(http.StatusSeeOther, "/login?error=oauth_error")
return
}
// this will happen when the user is redirected by the forge as
@ -65,10 +67,15 @@ func HandleAuth(c *gin.Context) {
// get the user from the database
u, err := _store.GetUserLogin(tmpuser.Login)
if err != nil {
if !errors.Is(err, types.RecordNotExist) {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
// if self-registration is disabled we should return a not authorized error
if !config.Open && !config.IsAdmin(tmpuser) {
log.Error().Msgf("cannot register %s. registration closed", tmpuser.Login)
c.Redirect(303, "/login?error=access_denied")
c.Redirect(http.StatusSeeOther, "/login?error=access_denied")
return
}
@ -98,7 +105,7 @@ func HandleAuth(c *gin.Context) {
// insert the user into the database
if err := _store.CreateUser(u); err != nil {
log.Error().Msgf("cannot insert %s. %s", u.Login, err)
c.Redirect(303, "/login?error=internal_error")
c.Redirect(http.StatusSeeOther, "/login?error=internal_error")
return
}
}
@ -116,14 +123,14 @@ func HandleAuth(c *gin.Context) {
teams, terr := server.Config.Services.Forge.Teams(c, u)
if terr != nil || !config.IsMember(teams) {
log.Error().Msgf("cannot verify team membership for %s.", u.Login)
c.Redirect(303, "/login?error=access_denied")
c.Redirect(http.StatusSeeOther, "/login?error=access_denied")
return
}
}
if err := _store.UpdateUser(u); err != nil {
log.Error().Msgf("cannot update %s. %s", u.Login, err)
c.Redirect(303, "/login?error=internal_error")
c.Redirect(http.StatusSeeOther, "/login?error=internal_error")
return
}
@ -131,19 +138,19 @@ func HandleAuth(c *gin.Context) {
tokenString, err := token.New(token.SessToken, u.Login).SignExpires(u.Hash, exp)
if err != nil {
log.Error().Msgf("cannot create token for %s. %s", u.Login, err)
c.Redirect(303, "/login?error=internal_error")
c.Redirect(http.StatusSeeOther, "/login?error=internal_error")
return
}
httputil.SetCookie(c.Writer, c.Request, "user_sess", tokenString)
c.Redirect(303, "/")
c.Redirect(http.StatusSeeOther, "/")
}
func GetLogout(c *gin.Context) {
httputil.DelCookie(c.Writer, c.Request, "user_sess")
httputil.DelCookie(c.Writer, c.Request, "user_last")
c.Redirect(303, "/")
c.Redirect(http.StatusSeeOther, "/")
}
func GetLoginToken(c *gin.Context) {

View file

@ -17,10 +17,9 @@ package api
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/woodpecker-ci/woodpecker/server"
"github.com/woodpecker-ci/woodpecker/server/model"
"github.com/gin-gonic/gin"
)
// GetOrgSecret gets the named organization secret from the database
@ -32,10 +31,10 @@ func GetOrgSecret(c *gin.Context) {
)
secret, err := server.Config.Services.Secrets.OrgSecretFind(owner, name)
if err != nil {
c.String(404, "Error getting org %q secret %q. %s", owner, name, err)
handleDbGetError(c, err)
return
}
c.JSON(200, secret.Copy())
c.JSON(http.StatusOK, secret.Copy())
}
// GetOrgSecretList gest the organization secret list from
@ -73,14 +72,14 @@ func PostOrgSecret(c *gin.Context) {
PluginsOnly: in.PluginsOnly,
}
if err := secret.Validate(); err != nil {
c.String(400, "Error inserting org %q secret. %s", owner, err)
c.String(http.StatusUnprocessableEntity, "Error inserting org %q secret. %s", owner, err)
return
}
if err := server.Config.Services.Secrets.OrgSecretCreate(owner, secret); err != nil {
c.String(500, "Error inserting org %q secret %q. %s", owner, in.Name, err)
c.String(http.StatusInternalServerError, "Error inserting org %q secret %q. %s", owner, in.Name, err)
return
}
c.JSON(200, secret.Copy())
c.JSON(http.StatusOK, secret.Copy())
}
// PatchOrgSecret updates an organization secret in the database.
@ -99,7 +98,7 @@ func PatchOrgSecret(c *gin.Context) {
secret, err := server.Config.Services.Secrets.OrgSecretFind(owner, name)
if err != nil {
c.String(404, "Error getting org %q secret %q. %s", owner, name, err)
handleDbGetError(c, err)
return
}
if in.Value != "" {
@ -114,14 +113,14 @@ func PatchOrgSecret(c *gin.Context) {
secret.PluginsOnly = in.PluginsOnly
if err := secret.Validate(); err != nil {
c.String(400, "Error updating org %q secret. %s", owner, err)
c.String(http.StatusUnprocessableEntity, "Error updating org %q secret. %s", owner, err)
return
}
if err := server.Config.Services.Secrets.OrgSecretUpdate(owner, secret); err != nil {
c.String(500, "Error updating org %q secret %q. %s", owner, in.Name, err)
c.String(http.StatusInternalServerError, "Error updating org %q secret %q. %s", owner, in.Name, err)
return
}
c.JSON(200, secret.Copy())
c.JSON(http.StatusOK, secret.Copy())
}
// DeleteOrgSecret deletes the named organization secret from the database.
@ -131,8 +130,8 @@ func DeleteOrgSecret(c *gin.Context) {
name = c.Param("secret")
)
if err := server.Config.Services.Secrets.OrgSecretDelete(owner, name); err != nil {
c.String(500, "Error deleting org %q secret %q. %s", owner, name, err)
c.String(http.StatusInternalServerError, "Error deleting org %q secret %q. %s", owner, name, err)
return
}
c.String(204, "")
c.String(http.StatusNoContent, "")
}

View file

@ -148,7 +148,7 @@ func GetPipelineLast(c *gin.Context) {
pl, err := _store.GetPipelineLast(repo, branch)
if err != nil {
c.String(http.StatusInternalServerError, err.Error())
handleDbGetError(c, err)
return
}
@ -176,19 +176,19 @@ func GetPipelineLogs(c *gin.Context) {
pl, err := _store.GetPipelineNumber(repo, num)
if err != nil {
_ = c.AbortWithError(404, err)
handleDbGetError(c, err)
return
}
step, err := _store.StepChild(pl, ppid, name)
if err != nil {
_ = c.AbortWithError(404, err)
handleDbGetError(c, err)
return
}
rc, err := _store.LogFind(step)
if err != nil {
_ = c.AbortWithError(404, err)
handleDbGetError(c, err)
return
}
@ -211,19 +211,19 @@ func GetStepLogs(c *gin.Context) {
pl, err := _store.GetPipelineNumber(repo, num)
if err != nil {
_ = c.AbortWithError(http.StatusNotFound, err)
handleDbGetError(c, err)
return
}
step, err := _store.StepFind(pl, pid)
if err != nil {
_ = c.AbortWithError(http.StatusNotFound, err)
handleDbGetError(c, err)
return
}
rc, err := _store.LogFind(step)
if err != nil {
_ = c.AbortWithError(http.StatusNotFound, err)
handleDbGetError(c, err)
return
}
@ -246,7 +246,7 @@ func GetPipelineConfig(c *gin.Context) {
pl, err := _store.GetPipelineNumber(repo, num)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
handleDbGetError(c, err)
return
}
@ -267,7 +267,7 @@ func CancelPipeline(c *gin.Context) {
pl, err := _store.GetPipelineNumber(repo, num)
if err != nil {
_ = c.AbortWithError(http.StatusNotFound, err)
handleDbGetError(c, err)
return
}
@ -289,7 +289,7 @@ func PostApproval(c *gin.Context) {
pl, err := _store.GetPipelineNumber(repo, num)
if err != nil {
_ = c.AbortWithError(404, err)
handleDbGetError(c, err)
return
}
@ -297,7 +297,7 @@ func PostApproval(c *gin.Context) {
if err != nil {
handlePipelineErr(c, err)
} else {
c.JSON(200, newpipeline)
c.JSON(http.StatusOK, newpipeline)
}
}
@ -320,17 +320,17 @@ func PostDecline(c *gin.Context) {
if err != nil {
handlePipelineErr(c, err)
} else {
c.JSON(200, pl)
c.JSON(http.StatusOK, pl)
}
}
func GetPipelineQueue(c *gin.Context) {
out, err := store.FromContext(c).GetPipelineQueue()
if err != nil {
c.String(500, "Error getting pipeline queue. %s", err)
c.String(http.StatusInternalServerError, "Error getting pipeline queue. %s", err)
return
}
c.JSON(200, out)
c.JSON(http.StatusOK, out)
}
// PostPipeline restarts a pipeline optional with altered event, deploy or environment
@ -346,15 +346,21 @@ func PostPipeline(c *gin.Context) {
user, err := _store.GetUser(repo.UserID)
if err != nil {
log.Error().Msgf("failure to find repo owner %s. %s", repo.FullName, err)
_ = c.AbortWithError(500, err)
if errors.Is(err, types.RecordNotExist) {
c.AbortWithStatus(http.StatusNotFound)
return
}
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
pl, err := _store.GetPipelineNumber(repo, num)
if err != nil {
log.Error().Msgf("failure to get pipeline %d. %s", num, err)
_ = c.AbortWithError(404, err)
if errors.Is(err, types.RecordNotExist) {
c.AbortWithStatus(http.StatusNotFound)
return
}
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
@ -394,7 +400,7 @@ func PostPipeline(c *gin.Context) {
if err != nil {
handlePipelineErr(c, err)
} else {
c.JSON(200, newpipeline)
c.JSON(http.StatusOK, newpipeline)
}
}
@ -407,19 +413,19 @@ func DeletePipelineLogs(c *gin.Context) {
pl, err := _store.GetPipelineNumber(repo, num)
if err != nil {
_ = c.AbortWithError(404, err)
handleDbGetError(c, err)
return
}
steps, err := _store.StepList(pl)
if err != nil {
_ = c.AbortWithError(404, err)
_ = c.AbortWithError(http.StatusNotFound, err)
return
}
switch pl.Status {
case model.StatusRunning, model.StatusPending:
c.String(400, "Cannot delete logs for a pending or running pipeline")
c.String(http.StatusUnprocessableEntity, "Cannot delete logs for a pending or running pipeline")
return
}
@ -432,11 +438,11 @@ func DeletePipelineLogs(c *gin.Context) {
}
}
if err != nil {
c.String(400, "There was a problem deleting your logs. %s", err)
c.String(http.StatusInternalServerError, "There was a problem deleting your logs. %s", err)
return
}
c.String(204, "")
c.String(http.StatusNoContent, "")
}
var deleteStr = `[

View file

@ -15,7 +15,6 @@
package api
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
@ -23,7 +22,6 @@ import (
"github.com/woodpecker-ci/woodpecker/server"
"github.com/woodpecker-ci/woodpecker/server/model"
"github.com/woodpecker-ci/woodpecker/server/router/middleware/session"
"github.com/woodpecker-ci/woodpecker/server/store/types"
)
// GetRegistry gets the name registry from the database and writes
@ -35,7 +33,7 @@ func GetRegistry(c *gin.Context) {
)
registry, err := server.Config.Services.Registries.RegistryFind(repo, name)
if err != nil {
c.String(404, "Error getting registry %q. %s", name, err)
handleDbGetError(c, err)
return
}
c.JSON(200, registry.Copy())
@ -59,14 +57,14 @@ func PostRegistry(c *gin.Context) {
Email: in.Email,
}
if err := registry.Validate(); err != nil {
c.String(400, "Error inserting registry. %s", err)
c.String(http.StatusBadRequest, "Error inserting registry. %s", err)
return
}
if err := server.Config.Services.Registries.RegistryCreate(repo, registry); err != nil {
c.String(500, "Error inserting registry %q. %s", in.Address, err)
c.String(http.StatusInternalServerError, "Error inserting registry %q. %s", in.Address, err)
return
}
c.JSON(200, in.Copy())
c.JSON(http.StatusOK, in.Copy())
}
// PatchRegistry updates the registry in the database.
@ -85,7 +83,7 @@ func PatchRegistry(c *gin.Context) {
registry, err := server.Config.Services.Registries.RegistryFind(repo, name)
if err != nil {
c.String(404, "Error getting registry %q. %s", name, err)
handleDbGetError(c, err)
return
}
if in.Username != "" {
@ -102,14 +100,14 @@ func PatchRegistry(c *gin.Context) {
}
if err := registry.Validate(); err != nil {
c.String(400, "Error updating registry. %s", err)
c.String(http.StatusUnprocessableEntity, "Error updating registry. %s", err)
return
}
if err := server.Config.Services.Registries.RegistryUpdate(repo, registry); err != nil {
c.String(500, "Error updating registry %q. %s", in.Address, err)
c.String(http.StatusInternalServerError, "Error updating registry %q. %s", in.Address, err)
return
}
c.JSON(200, in.Copy())
c.JSON(http.StatusOK, in.Copy())
}
// GetRegistryList gets the registry list from the database and writes
@ -118,7 +116,7 @@ func GetRegistryList(c *gin.Context) {
repo := session.Repo(c)
list, err := server.Config.Services.Registries.RegistryList(repo)
if err != nil {
c.String(500, "Error getting registry list. %s", err)
c.String(http.StatusInternalServerError, "Error getting registry list. %s", err)
return
}
// copy the registry detail to remove the sensitive
@ -126,7 +124,7 @@ func GetRegistryList(c *gin.Context) {
for i, registry := range list {
list[i] = registry.Copy()
}
c.JSON(200, list)
c.JSON(http.StatusOK, list)
}
// DeleteRegistry deletes the named registry from the database.
@ -137,12 +135,8 @@ func DeleteRegistry(c *gin.Context) {
)
err := server.Config.Services.Registries.RegistryDelete(repo, name)
if err != nil {
if errors.Is(err, types.RecordNotExist) {
c.String(404, "no records found, cannot delete registry")
return
}
c.String(500, "Error deleting registry %q. %s", name, err)
c.String(http.StatusInternalServerError, "Error deleting registry %q. %s", name, err)
return
}
c.String(204, "")
c.String(http.StatusNoContent, "")
}

View file

@ -263,7 +263,7 @@ func RepairRepo(c *gin.Context) {
t := token.New(token.HookToken, repo.FullName)
sig, err := t.Sign(repo.Hash)
if err != nil {
c.String(500, err.Error())
c.String(http.StatusInternalServerError, err.Error())
return
}
@ -301,7 +301,7 @@ func RepairRepo(c *gin.Context) {
log.Trace().Err(err).Msgf("deactivate repo '%s' to repair failed", repo.FullName)
}
if err := forge.Activate(c, user, repo, link); err != nil {
c.String(500, err.Error())
c.String(http.StatusInternalServerError, err.Error())
return
}
@ -355,7 +355,7 @@ func MoveRepo(c *gin.Context) {
t := token.New(token.HookToken, repo.FullName)
sig, err := t.Sign(repo.Hash)
if err != nil {
c.String(500, err.Error())
c.String(http.StatusInternalServerError, err.Error())
return
}
@ -371,7 +371,7 @@ func MoveRepo(c *gin.Context) {
log.Trace().Err(err).Msgf("deactivate repo '%s' for move to activate later, got an error", repo.FullName)
}
if err := forge.Activate(c, user, repo, link); err != nil {
c.String(500, err.Error())
c.String(http.StatusInternalServerError, err.Error())
return
}
c.Writer.WriteHeader(http.StatusOK)

View file

@ -19,7 +19,6 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/woodpecker-ci/woodpecker/server"
"github.com/woodpecker-ci/woodpecker/server/model"
"github.com/woodpecker-ci/woodpecker/server/router/middleware/session"
@ -34,10 +33,10 @@ func GetSecret(c *gin.Context) {
)
secret, err := server.Config.Services.Secrets.SecretFind(repo, name)
if err != nil {
c.String(404, "Error getting secret %q. %s", name, err)
handleDbGetError(c, err)
return
}
c.JSON(200, secret.Copy())
c.JSON(http.StatusOK, secret.Copy())
}
// PostSecret persists the secret to the database.
@ -58,14 +57,14 @@ func PostSecret(c *gin.Context) {
PluginsOnly: in.PluginsOnly,
}
if err := secret.Validate(); err != nil {
c.String(400, "Error inserting secret. %s", err)
c.String(http.StatusUnprocessableEntity, "Error inserting secret. %s", err)
return
}
if err := server.Config.Services.Secrets.SecretCreate(repo, secret); err != nil {
c.String(500, "Error inserting secret %q. %s", in.Name, err)
c.String(http.StatusInternalServerError, "Error inserting secret %q. %s", in.Name, err)
return
}
c.JSON(200, secret.Copy())
c.JSON(http.StatusOK, secret.Copy())
}
// PatchSecret updates the secret in the database.
@ -84,7 +83,7 @@ func PatchSecret(c *gin.Context) {
secret, err := server.Config.Services.Secrets.SecretFind(repo, name)
if err != nil {
c.String(404, "Error getting secret %q. %s", name, err)
handleDbGetError(c, err)
return
}
if in.Value != "" {
@ -99,14 +98,14 @@ func PatchSecret(c *gin.Context) {
secret.PluginsOnly = in.PluginsOnly
if err := secret.Validate(); err != nil {
c.String(400, "Error updating secret. %s", err)
c.String(http.StatusUnprocessableEntity, "Error updating secret. %s", err)
return
}
if err := server.Config.Services.Secrets.SecretUpdate(repo, secret); err != nil {
c.String(500, "Error updating secret %q. %s", in.Name, err)
c.String(http.StatusInternalServerError, "Error updating secret %q. %s", in.Name, err)
return
}
c.JSON(200, secret.Copy())
c.JSON(http.StatusOK, secret.Copy())
}
// GetSecretList gets the secret list from the database and writes
@ -115,7 +114,7 @@ func GetSecretList(c *gin.Context) {
repo := session.Repo(c)
list, err := server.Config.Services.Secrets.SecretList(repo)
if err != nil {
c.String(500, "Error getting secret list. %s", err)
c.String(http.StatusInternalServerError, "Error getting secret list. %s", err)
return
}
// copy the secret detail to remove the sensitive
@ -123,7 +122,7 @@ func GetSecretList(c *gin.Context) {
for i, secret := range list {
list[i] = secret.Copy()
}
c.JSON(200, list)
c.JSON(http.StatusOK, list)
}
// DeleteSecret deletes the named secret from the database.
@ -133,8 +132,8 @@ func DeleteSecret(c *gin.Context) {
name = c.Param("secret")
)
if err := server.Config.Services.Secrets.SecretDelete(repo, name); err != nil {
c.String(500, "Error deleting secret %q. %s", name, err)
c.String(http.StatusInternalServerError, "Error deleting secret %q. %s", name, err)
return
}
c.String(204, "")
c.String(http.StatusNoContent, "")
}

View file

@ -37,5 +37,5 @@ func GetSignaturePublicKey(c *gin.Context) {
Bytes: b,
}
c.String(200, "%s", pem.EncodeToMemory(block))
c.String(http.StatusOK, "%s", pem.EncodeToMemory(block))
}

View file

@ -48,7 +48,7 @@ func EventStreamSSE(c *gin.Context) {
flusher, ok := rw.(http.Flusher)
if !ok {
c.String(500, "Streaming not supported")
c.String(http.StatusInternalServerError, "Streaming not supported")
return
}
@ -131,7 +131,7 @@ func LogStreamSSE(c *gin.Context) {
flusher, ok := rw.(http.Flusher)
if !ok {
c.String(500, "Streaming not supported")
c.String(http.StatusInternalServerError, "Streaming not supported")
return
}

View file

@ -33,7 +33,7 @@ import (
)
func GetSelf(c *gin.Context) {
c.JSON(200, session.User(c))
c.JSON(http.StatusOK, session.User(c))
}
func GetFeed(c *gin.Context) {
@ -70,19 +70,19 @@ func GetFeed(c *gin.Context) {
if latest {
feed, err := _store.RepoListLatest(user)
if err != nil {
c.String(500, "Error fetching feed. %s", err)
c.String(http.StatusInternalServerError, "Error fetching feed. %s", err)
} else {
c.JSON(200, feed)
c.JSON(http.StatusOK, feed)
}
return
}
feed, err := _store.UserFeed(user)
if err != nil {
c.String(500, "Error fetching user feed. %s", err)
c.String(http.StatusInternalServerError, "Error fetching user feed. %s", err)
return
}
c.JSON(200, feed)
c.JSON(http.StatusOK, feed)
}
func GetRepos(c *gin.Context) {
@ -119,7 +119,7 @@ func GetRepos(c *gin.Context) {
repos, err := _store.RepoList(user, true)
if err != nil {
c.String(500, "Error fetching repository list. %s", err)
c.String(http.StatusInternalServerError, "Error fetching repository list. %s", err)
return
}
@ -155,7 +155,7 @@ func DeleteToken(c *gin.Context) {
securecookie.GenerateRandomKey(32),
)
if err := _store.UpdateUser(user); err != nil {
c.String(500, "Error revoking tokens. %s", err)
c.String(http.StatusInternalServerError, "Error revoking tokens. %s", err)
return
}

View file

@ -20,7 +20,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/gorilla/securecookie"
"github.com/woodpecker-ci/woodpecker/server/model"
"github.com/woodpecker-ci/woodpecker/server/store"
)
@ -37,7 +36,7 @@ func GetUsers(c *gin.Context) {
func GetUser(c *gin.Context) {
user, err := store.FromContext(c).GetUserLogin(c.Param("login"))
if err != nil {
c.String(404, "Cannot find user. %s", err)
handleDbGetError(c, err)
return
}
c.JSON(200, user)
@ -55,7 +54,7 @@ func PatchUser(c *gin.Context) {
user, err := _store.GetUserLogin(c.Param("login"))
if err != nil {
c.AbortWithStatus(http.StatusNotFound)
handleDbGetError(c, err)
return
}

View file

@ -28,15 +28,15 @@ import (
// Health endpoint returns a 500 if the server state is unhealthy.
func Health(c *gin.Context) {
if err := store.FromContext(c).Ping(); err != nil {
c.String(500, err.Error())
c.String(http.StatusInternalServerError, err.Error())
return
}
c.String(200, "")
c.String(http.StatusOK, "")
}
// Version endpoint returns the server version and build information.
func Version(c *gin.Context) {
c.JSON(200, gin.H{
c.JSON(http.StatusOK, gin.H{
"source": "https://github.com/woodpecker-ci/woodpecker",
"version": version.String(),
})
@ -44,7 +44,7 @@ func Version(c *gin.Context) {
// LogLevel endpoint returns the current logging level
func LogLevel(c *gin.Context) {
c.JSON(200, gin.H{
c.JSON(http.StatusOK, gin.H{
"log-level": zerolog.GlobalLevel().String(),
})
}
@ -67,5 +67,5 @@ func SetLogLevel(c *gin.Context) {
log.Log().Msgf("log level set to %s", lvl.String())
zerolog.SetGlobalLevel(lvl)
c.JSON(200, logLevel)
c.JSON(http.StatusOK, logLevel)
}

View file

@ -15,11 +15,13 @@
package session
import (
"errors"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"github.com/woodpecker-ci/woodpecker/server/store/types"
"github.com/woodpecker-ci/woodpecker/server"
"github.com/woodpecker-ci/woodpecker/server/model"
@ -62,7 +64,11 @@ func SetRepo() gin.HandlerFunc {
)
if user != nil {
c.AbortWithStatus(http.StatusNotFound)
if errors.Is(err, types.RecordNotExist) {
c.AbortWithStatus(http.StatusNotFound)
return
}
_ = c.AbortWithError(http.StatusInternalServerError, err)
} else {
c.AbortWithStatus(http.StatusUnauthorized)
}

View file

@ -16,6 +16,7 @@ package datastore
import (
"errors"
"strings"
"github.com/rs/zerolog/log"
"xorm.io/builder"
@ -72,7 +73,7 @@ func (s storage) GetRepoName(fullName string) (*model.Repo, error) {
func (s storage) getRepoName(e *xorm.Session, fullName string) (*model.Repo, error) {
repo := new(model.Repo)
return repo, wrapGet(e.Where("repo_full_name = ?", fullName).Get(repo))
return repo, wrapGet(e.Where("LOWER(repo_full_name) = ?", strings.ToLower(fullName)).Get(repo))
}
func (s storage) GetRepoCount() (int64, error) {

View file

@ -103,6 +103,22 @@ func TestRepos(t *testing.T) {
g.Assert(repo.Name).Equal(getrepo.Name)
})
g.It("Should Get a Repo by Name (case-insensitive)", func() {
repo := model.Repo{
UserID: 1,
FullName: "bradrydzewski/TEST",
Owner: "bradrydzewski",
Name: "TEST",
}
g.Assert(store.CreateRepo(&repo)).IsNil()
getrepo, err := store.GetRepoName("Bradrydzewski/test")
g.Assert(err).IsNil()
g.Assert(repo.ID).Equal(getrepo.ID)
g.Assert(repo.UserID).Equal(getrepo.UserID)
g.Assert(repo.Owner).Equal(getrepo.Owner)
g.Assert(repo.Name).Equal(getrepo.Name)
})
g.It("Should Enforce Unique Repo Name", func() {
repo1 := model.Repo{
UserID: 1,

View file

@ -358,6 +358,7 @@
},
"capacity": {
"capacity": "Capacity",
"desc": "The max amount of parallel pipelines executed by this agent.",
"badge": "capacity"
},
"version": "Version",

View file

@ -78,7 +78,7 @@
:label="$t('admin.settings.agents.capacity.capacity')"
docs-url="docs/next/administration/agent-config#woodpecker_max_procs"
>
<span class="text-color-alt">The max amount of parallel pipelines executed by this agent.</span>
<span class="text-color-alt">{{ $t('admin.settings.agents.capacity.desc') }}</span>
<TextField :model-value="selectedAgent.capacity?.toString()" disabled />
</InputField>
@ -98,11 +98,15 @@
</InputField>
</template>
<Button
:is-loading="isSaving"
type="submit"
:text="isEditingAgent ? $t('admin.settings.agents.save') : $t('admin.settings.agents.add')"
/>
<div class="flex gap-2">
<Button type="button" color="gray" :text="$t('cancel')" @click="selectedAgent = undefined" />
<Button
:is-loading="isSaving"
type="submit"
color="green"
:text="isEditingAgent ? $t('admin.settings.agents.save') : $t('admin.settings.agents.add')"
/>
</div>
</form>
</div>
</Panel>

View file

@ -27,7 +27,8 @@
<IconButton
icon="edit"
:title="$t('admin.settings.users.edit_user')"
class="ml-2 w-8 h-8"
class="w-8 h-8"
:class="{ 'ml-auto': !user.admin, 'ml-2': user.admin }"
@click="editUser(user)"
/>
<IconButton

View file

@ -74,12 +74,15 @@
<span v-else class="text-color">{{ $t('repo.settings.crons.not_executed_yet') }}</span>
</div>
<Button
type="submit"
color="green"
:is-loading="isSaving"
:text="isEditingCron ? $t('repo.settings.crons.save') : $t('repo.settings.crons.add')"
/>
<div class="flex gap-2">
<Button type="button" color="gray" :text="$t('cancel')" @click="selectedCron = undefined" />
<Button
type="submit"
color="green"
:is-loading="isSaving"
:text="isEditingCron ? $t('repo.settings.crons.save') : $t('repo.settings.crons.add')"
/>
</div>
</form>
</div>
</Panel>

View file

@ -65,12 +65,15 @@
<TextField v-model="selectedRegistry.password" :placeholder="$t('password')" required />
</InputField>
<Button
type="submit"
color="green"
:is-loading="isSaving"
:text="isEditingRegistry ? $t('repo.settings.registries.save') : $t('repo.settings.registries.add')"
/>
<div class="flex gap-2">
<Button type="button" color="gray" :text="$t('cancel')" @click="selectedRegistry = undefined" />
<Button
type="submit"
color="green"
:is-loading="isSaving"
:text="isEditingRegistry ? $t('repo.settings.registries.save') : $t('repo.settings.registries.add')"
/>
</div>
</form>
</div>
</Panel>

View file

@ -24,7 +24,7 @@
<CheckboxesField v-model="innerValue.event" :options="secretEventsOptions" />
</InputField>
<div class="flex gap-2 justify-center">
<div class="flex gap-2">
<Button type="button" color="gray" :text="$t('cancel')" @click="$emit('cancel')" />
<Button
type="submit"