feature(api,release): prepare for next major release and switch to requiring content-type set to JSON for all incoming requests and expose more CORS environment variables
All checks were successful
/ build (push) Successful in 5m3s

- Switched to enforce JSON as Content-Type for all incoming requests
- Switched to properly respond with JSON on page not found or method not allowed
- Renamed CORS_ALLOW_ORIGIN to CORS_ALLOW_ORIGINS
- Added CORS_ALLOW_CREDENTIALS which defaults to true
- Added CORS_EXPOSE_HEADERS which defaults to *
- Overhauled package visibility for server module
This commit is contained in:
Varakh 2024-06-10 20:03:25 +02:00
parent 1d79258670
commit 1fc3818d3c
24 changed files with 151 additions and 101 deletions

View file

@ -3,9 +3,9 @@ on:
tags: tags:
- '*' - '*'
env: env:
VERSION_MAJOR: 2 VERSION_MAJOR: 3
VERSION_MINOR: 0 VERSION_MINOR: 0
VERSION_PATCH: 2 VERSION_PATCH: 0
IMAGE_TAG: varakh/upda IMAGE_TAG: varakh/upda
IMAGE_TAG_PRIVATE: git.myservermanager.com/varakh/upda IMAGE_TAG_PRIVATE: git.myservermanager.com/varakh/upda
FORGEJO_URL: https://git.myservermanager.com FORGEJO_URL: https://git.myservermanager.com

View file

@ -2,8 +2,17 @@
Changes adhere to [semantic versioning](https://semver.org). Changes adhere to [semantic versioning](https://semver.org).
## [2.0.2] - UNRELEASED ## [3.0.0] - UNRELEASED
> This is a major version upgrade. Other versions are incompatible with this release.
* Added automatic detection of `GOMAXPROCS`
* Switched to enforce JSON as `Content-Type` for all incoming requests
* Switched to properly respond with JSON on page not found or method not allowed
* Renamed `CORS_ALLOW_ORIGIN` to `CORS_ALLOW_ORIGINS`
* Added `CORS_ALLOW_CREDENTIALS` which defaults to `true`
* Added `CORS_EXPOSE_HEADERS` which defaults to `*`
* Overhauled package visibility for server module
* Updated dependencies * Updated dependencies
* Updated OCI image base to alpine `3.20` with Go `1.22` * Updated OCI image base to alpine `3.20` with Go `1.22`

View file

@ -110,6 +110,7 @@ platform, uploads produced artifacts to that release and publishes docker images
built successfully: built successfully:
* Adapt `constants_app.go` and change `Version` to the correct version number * Adapt `constants_app.go` and change `Version` to the correct version number
* Adapt `app.go` and change `version` to the correct version number (CLI)
* Adapt `CHANGELOG.md` to reflect changes and ensure a date is properly set in the header, also add a reference link * Adapt `CHANGELOG.md` to reflect changes and ensure a date is properly set in the header, also add a reference link
in footer (link to scm git tag source) in footer (link to scm git tag source)
* Adapt `api.yaml`: `version` attribute must reflect the to be released version * Adapt `api.yaml`: `version` attribute must reflect the to be released version
@ -118,6 +119,7 @@ built successfully:
After the release has been created, ensure to change the following settings for the _next development cycle_: After the release has been created, ensure to change the following settings for the _next development cycle_:
* Adapt `constants_app.go` and change `Version` to the _next_ version number * Adapt `constants_app.go` and change `Version` to the _next_ version number
* Adapt `app.go` and change `version` to the _next_ version number (CLI)
* Adapt `CHANGELOG.md` and add an _UNRELEASED_ section * Adapt `CHANGELOG.md` and add an _UNRELEASED_ section
* Adapt `api.yaml`: `version` attribute must reflect the _next_ version number * Adapt `api.yaml`: `version` attribute must reflect the _next_ version number
* Adapt `env: VERSION_*` in `.forgejo/workflows/release.yaml` to _next_ version number * Adapt `env: VERSION_*` in `.forgejo/workflows/release.yaml` to _next_ version number

View file

@ -2,7 +2,7 @@ openapi: 3.0.3
info: info:
title: upda title: upda
description: API specification description: API specification
version: 2.0.2 version: 3.0.0
externalDocs: externalDocs:
description: Find out more about the project description: Find out more about the project
url: https://git.myservermanager.com/varakh/upda url: https://git.myservermanager.com/varakh/upda

2
go.sum
View file

@ -201,8 +201,6 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/redis/go-redis/v9 v9.5.2 h1:L0L3fcSNReTRGyZ6AqAEN0K56wYeYAwapBIhkvh0f3E=
github.com/redis/go-redis/v9 v9.5.2/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU= github.com/redis/go-redis/v9 v9.5.3 h1:fOAp1/uJG+ZtcITgZOfYFmTKPE7n4Vclj1wZFgRciUU=
github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/redis/go-redis/v9 v9.5.3/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo= github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo=

View file

@ -19,7 +19,7 @@ func errAbortWithValidatorPayload(c *gin.Context, err error) {
errorMap[key] = txt errorMap[key] = txt
} }
resErr := newServiceError(IllegalArgument, fmt.Errorf("validation error: %v (%w)", util.ValuesString(errorMap), err)) resErr := newServiceError(illegalArgument, fmt.Errorf("validation error: %v (%w)", util.ValuesString(errorMap), err))
c.Header(headerContentType, headerContentTypeApplicationJson) c.Header(headerContentType, headerContentTypeApplicationJson)
_ = c.AbortWithError(http.StatusBadRequest, resErr) _ = c.AbortWithError(http.StatusBadRequest, resErr)
return return
@ -29,17 +29,19 @@ func errToHttpStatus(err error) int {
var e *serviceError var e *serviceError
switch { switch {
case errors.As(err, &e): case errors.As(err, &e):
if e.Status == IllegalArgument { if e.Status == illegalArgument {
return http.StatusBadRequest return http.StatusBadRequest
} else if e.Status == Unauthorized { } else if e.Status == unauthorized {
return http.StatusUnauthorized return http.StatusUnauthorized
} else if e.Status == Forbidden { } else if e.Status == forbidden {
return http.StatusForbidden return http.StatusForbidden
} else if e.Status == NotFound { } else if e.Status == notFound {
return http.StatusNotFound return http.StatusNotFound
} else if e.Status == Conflict { } else if e.Status == methodNotAllowed {
return http.StatusMethodNotAllowed
} else if e.Status == conflict {
return http.StatusConflict return http.StatusConflict
} else if e.Status == General { } else if e.Status == general {
return http.StatusInternalServerError return http.StatusInternalServerError
} }
default: default:
@ -57,7 +59,7 @@ func errCodeToStr(err error) string {
return string(e.Status) return string(e.Status)
} }
return string(General) return string(general)
} }
func validatorErrorToText(e *validator.FieldError) (string, string) { func validatorErrorToText(e *validator.FieldError) (string, string) {

View file

@ -16,8 +16,8 @@ func newInfoHandler(a *appConfig) *infoHandler {
func (h *infoHandler) show(c *gin.Context) { func (h *infoHandler) show(c *gin.Context) {
c.JSON(http.StatusOK, api.DataResponse{Data: gin.H{ c.JSON(http.StatusOK, api.DataResponse{Data: gin.H{
"name": Name, "name": name,
"version": Version, "version": version,
"timeZone": h.appConfig.timeZone, "timeZone": h.appConfig.timeZone,
}}) }})
} }

View file

@ -17,7 +17,7 @@ func newWebhookInvocationHandler(i *webhookInvocationService, w *webhookService)
} }
func (h *webhookInvocationHandler) execute(c *gin.Context) { func (h *webhookInvocationHandler) execute(c *gin.Context) {
tokenHeader := c.GetHeader(HeaderWebhookToken) tokenHeader := c.GetHeader(headerWebhookToken)
webhookId := c.Param("id") webhookId := c.Param("id")
var w *Webhook var w *Webhook
@ -52,7 +52,7 @@ func (h *webhookInvocationHandler) execute(c *gin.Context) {
} }
break break
default: default:
err = newServiceError(IllegalArgument, errors.New("no default handler for webhook type found")) err = newServiceError(illegalArgument, errors.New("no default handler for webhook type found"))
_ = c.AbortWithError(errToHttpStatus(err), err) _ = c.AbortWithError(errToHttpStatus(err), err)
return return
} }

View file

@ -5,18 +5,43 @@ import (
"git.myservermanager.com/varakh/upda/api" "git.myservermanager.com/varakh/upda/api"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
"strings"
) )
func middlewareAppName() gin.HandlerFunc { func middlewareAppName() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
c.Header(HeaderAppName, Name) c.Header(headerAppName, name)
c.Next()
}
}
func middlewareGlobalNotFound() gin.HandlerFunc {
return func(c *gin.Context) {
c.AbortWithStatusJSON(http.StatusNotFound, api.NewErrorResponseWithStatusAndMessage(string(notFound), "page not found"))
return
}
}
func middlewareGlobalMethodNotAllowed() gin.HandlerFunc {
return func(c *gin.Context) {
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, api.NewErrorResponseWithStatusAndMessage(string(methodNotAllowed), "method not allowed"))
return
}
}
func middlewareEnforceJsonContentType() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.Method != http.MethodOptions && !strings.HasPrefix(c.GetHeader(headerContentType), headerContentTypeApplicationJson) {
c.AbortWithStatusJSON(http.StatusBadRequest, api.NewErrorResponseWithStatusAndMessage(string(illegalArgument), "content-type must be application/json"))
return
}
c.Next() c.Next()
} }
} }
func middlewareAppVersion() gin.HandlerFunc { func middlewareAppVersion() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
c.Header(HeaderAppVersion, Version) c.Header(headerAppVersion, version)
c.Next() c.Next()
} }
} }
@ -47,7 +72,7 @@ func middlewareAppErrorRecoveryHandler() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, api.NewErrorResponseWithStatusAndMessage(string(General), fmt.Sprintf("%s", err))) c.AbortWithStatusJSON(http.StatusInternalServerError, api.NewErrorResponseWithStatusAndMessage(string(general), fmt.Sprintf("%s", err)))
} }
}() }()
c.Next() c.Next()

View file

@ -100,17 +100,21 @@ func Start() {
hh := newHealthHandler() hh := newHealthHandler()
authH := newAuthHandler() authH := newAuthHandler()
router.Use(middlewareEnforceJsonContentType())
router.Use(middlewareAppName()) router.Use(middlewareAppName())
router.Use(middlewareAppVersion()) router.Use(middlewareAppVersion())
router.Use(middlewareAppContentType()) router.Use(middlewareAppContentType())
router.Use(middlewareErrorHandler()) router.Use(middlewareErrorHandler())
router.Use(middlewareAppErrorRecoveryHandler()) router.Use(middlewareAppErrorRecoveryHandler())
router.NoRoute(middlewareGlobalNotFound())
router.NoMethod(middlewareGlobalMethodNotAllowed())
router.Use(cors.New(cors.Config{ router.Use(cors.New(cors.Config{
AllowOrigins: env.serverConfig.corsAllowOrigin, AllowOrigins: env.serverConfig.corsAllowOrigins,
AllowMethods: env.serverConfig.corsAllowMethods, AllowMethods: env.serverConfig.corsAllowMethods,
AllowHeaders: env.serverConfig.corsAllowHeaders, AllowHeaders: env.serverConfig.corsAllowHeaders,
AllowCredentials: true, AllowCredentials: env.serverConfig.corsAllowCredentials,
ExposeHeaders: env.serverConfig.corsExposeHeaders,
})) }))
apiPublicGroup := router.Group("/api/v1") apiPublicGroup := router.Group("/api/v1")

View file

@ -1,10 +1,10 @@
package server package server
const ( const (
HeaderAppName = "X-App-Name" headerAppName = "X-App-Name"
HeaderAppVersion = "X-App-Version" headerAppVersion = "X-App-Version"
HeaderWebhookToken = "X-Webhook-Token" headerWebhookToken = "X-Webhook-Token"
headerContentType = "Content-Type" headerContentType = "Content-Type"
headerContentTypeApplicationJson = "application/json" headerContentTypeApplicationJson = "application/json"

View file

@ -1,6 +1,6 @@
package server package server
const ( const (
Name = "upda" name = "upda"
Version = "2.0.2" version = "3.0.0"
) )

View file

@ -35,12 +35,16 @@ const (
serverTlsEnabledDefault = "false" serverTlsEnabledDefault = "false"
serverTimeoutDefault = "1s" serverTimeoutDefault = "1s"
envCorsAllowOrigin = "CORS_ALLOW_ORIGIN" envCorsAllowOrigins = "CORS_ALLOW_ORIGINS"
envCorsAllowMethods = "CORS_ALLOW_METHODS" envCorsAllowMethods = "CORS_ALLOW_METHODS"
envCorsAllowHeaders = "CORS_ALLOW_HEADERS" envCorsAllowHeaders = "CORS_ALLOW_HEADERS"
corsAllowOriginDefault = "*" envCorsAllowCredentials = "CORS_ALLOW_CREDENTIALS"
corsAllowMethodsDefault = "HEAD, GET, POST, PUT, PATCH, DELETE, OPTIONS" envCorsExposeHeaders = "CORS_EXPOSE_HEADERS"
corsAllowHeadersDefault = "Authorization, Content-Type" corsAllowOriginsDefault = "*"
corsAllowMethodsDefault = "HEAD, GET, POST, PUT, PATCH, DELETE, OPTIONS"
corsAllowHeadersDefault = "Authorization, Content-Type"
corsAllowCredentialsDefault = "true"
corsExposeHeadersDefault = "*"
dbTypeSqlite = "sqlite" dbTypeSqlite = "sqlite"
dbTypePostgres = "postgres" dbTypePostgres = "postgres"

View file

@ -27,15 +27,17 @@ type appConfig struct {
} }
type serverConfig struct { type serverConfig struct {
port int port int
listen string listen string
tlsEnabled bool tlsEnabled bool
tlsCertPath string tlsCertPath string
tlsKeyPath string tlsKeyPath string
timeout time.Duration timeout time.Duration
corsAllowOrigin []string corsAllowCredentials bool
corsAllowMethods []string corsAllowOrigins []string
corsAllowHeaders []string corsAllowMethods []string
corsAllowHeaders []string
corsExposeHeaders []string
} }
type authConfig struct { type authConfig struct {
@ -203,15 +205,17 @@ func bootstrapEnvironment() *Environment {
} }
sc = &serverConfig{ sc = &serverConfig{
port: serverPort, port: serverPort,
timeout: serverTimeout, timeout: serverTimeout,
listen: os.Getenv(envServerListen), listen: os.Getenv(envServerListen),
tlsEnabled: serverTlsEnabled, tlsEnabled: serverTlsEnabled,
tlsCertPath: os.Getenv(envServerTlsCertPath), tlsCertPath: os.Getenv(envServerTlsCertPath),
tlsKeyPath: os.Getenv(envServerTlsKeyPath), tlsKeyPath: os.Getenv(envServerTlsKeyPath),
corsAllowOrigin: []string{os.Getenv(envCorsAllowOrigin)}, corsAllowCredentials: os.Getenv(envCorsAllowCredentials) == "true",
corsAllowMethods: []string{os.Getenv(envCorsAllowMethods)}, corsExposeHeaders: []string{os.Getenv(envCorsExposeHeaders)},
corsAllowHeaders: []string{os.Getenv(envCorsAllowHeaders)}, corsAllowOrigins: []string{os.Getenv(envCorsAllowOrigins)},
corsAllowMethods: []string{os.Getenv(envCorsAllowMethods)},
corsAllowHeaders: []string{os.Getenv(envCorsAllowHeaders)},
} }
authMode := os.Getenv(envAuthMode) authMode := os.Getenv(envAuthMode)
@ -338,7 +342,7 @@ func bootstrapEnvironment() *Environment {
if os.Getenv(envDbType) == dbTypeSqlite { if os.Getenv(envDbType) == dbTypeSqlite {
if os.Getenv(envDbSqliteFile) == "" { if os.Getenv(envDbSqliteFile) == "" {
var defaultDbFile string var defaultDbFile string
if defaultDbFile, err = xdg.DataFile(Name + "/" + dbTypeSqliteDbNameDefault); err != nil { if defaultDbFile, err = xdg.DataFile(name + "/" + dbTypeSqliteDbNameDefault); err != nil {
zap.L().Sugar().Fatalf("Database file '%s' could not be created. Reason: %v", defaultDbFile, err) zap.L().Sugar().Fatalf("Database file '%s' could not be created. Reason: %v", defaultDbFile, err)
} }
setEnvKeyDefault(envDbSqliteFile, defaultDbFile) setEnvKeyDefault(envDbSqliteFile, defaultDbFile)
@ -464,9 +468,11 @@ func bootstrapFromEnvironmentAndValidate() {
setEnvKeyDefault(envServerPort, serverPortDefault) setEnvKeyDefault(envServerPort, serverPortDefault)
setEnvKeyDefault(envServerListen, serverListenDefault) setEnvKeyDefault(envServerListen, serverListenDefault)
setEnvKeyDefault(envServerTlsEnabled, serverTlsEnabledDefault) setEnvKeyDefault(envServerTlsEnabled, serverTlsEnabledDefault)
setEnvKeyDefault(envCorsAllowOrigin, corsAllowOriginDefault) setEnvKeyDefault(envCorsAllowOrigins, corsAllowOriginsDefault)
setEnvKeyDefault(envCorsAllowMethods, corsAllowMethodsDefault) setEnvKeyDefault(envCorsAllowMethods, corsAllowMethodsDefault)
setEnvKeyDefault(envCorsAllowHeaders, corsAllowHeadersDefault) setEnvKeyDefault(envCorsAllowHeaders, corsAllowHeadersDefault)
setEnvKeyDefault(envCorsAllowCredentials, corsAllowCredentialsDefault)
setEnvKeyDefault(envCorsExposeHeaders, corsExposeHeadersDefault)
setEnvKeyDefault(envServerTimeout, serverTimeoutDefault) setEnvKeyDefault(envServerTimeout, serverTimeoutDefault)
} }

View file

@ -6,43 +6,44 @@ import (
) )
var ( var (
errorValidationNotEmpty = newServiceError(IllegalArgument, errors.New("assert: empty values are not allowed")) errorValidationNotEmpty = newServiceError(illegalArgument, errors.New("assert: empty values are not allowed"))
errorValidationNotBlank = newServiceError(IllegalArgument, errors.New("assert: blank values are not allowed")) errorValidationNotBlank = newServiceError(illegalArgument, errors.New("assert: blank values are not allowed"))
errorValidationPageGreaterZero = newServiceError(IllegalArgument, errors.New("assert: page has to be greater 0")) errorValidationPageGreaterZero = newServiceError(illegalArgument, errors.New("assert: page has to be greater 0"))
errorValidationPageSizeGreaterZero = newServiceError(IllegalArgument, errors.New("assert: pageSize has to be greater 0")) errorValidationPageSizeGreaterZero = newServiceError(illegalArgument, errors.New("assert: pageSize has to be greater 0"))
errorValidationLimitGreaterZero = newServiceError(IllegalArgument, errors.New("assert: limit has to be greater 0")) errorValidationLimitGreaterZero = newServiceError(illegalArgument, errors.New("assert: limit has to be greater 0"))
errorValidationSizeGreaterZero = newServiceError(IllegalArgument, errors.New("assert: size has to be greater 0")) errorValidationSizeGreaterZero = newServiceError(illegalArgument, errors.New("assert: size has to be greater 0"))
errorValidationMaxRetriesGreaterZero = newServiceError(IllegalArgument, errors.New("assert: max retries has to be greater 0")) errorValidationMaxRetriesGreaterZero = newServiceError(illegalArgument, errors.New("assert: max retries has to be greater 0"))
errorResourceNotFound = newServiceError(NotFound, errors.New("resource not found")) errorResourceNotFound = newServiceError(notFound, errors.New("resource not found"))
errorResourceAccessDenied = newServiceError(Forbidden, errors.New("resource access denied")) errorResourceAccessDenied = newServiceError(forbidden, errors.New("resource access denied"))
errorDatabaseRowsExpected = newServiceDatabaseError(errors.New("action failed, expected affected rows, but got none")) errorDatabaseRowsExpected = newServiceDatabaseError(errors.New("action failed, expected affected rows, but got none"))
) )
type ErrorCode string type errorCode string
const ( const (
IllegalArgument ErrorCode = "IllegalArgument" illegalArgument errorCode = "IllegalArgument"
Unauthorized ErrorCode = "Unauthorized" unauthorized errorCode = "Unauthorized"
Forbidden ErrorCode = "Forbidden" forbidden errorCode = "Forbidden"
NotFound ErrorCode = "NotFound" notFound errorCode = "NotFound"
Conflict ErrorCode = "Conflict" methodNotAllowed errorCode = "MethodNotAllowed"
General ErrorCode = "General" conflict errorCode = "Conflict"
general errorCode = "General"
) )
// newServiceError returns an error that formats as the given text and aligns with builtin error // newServiceError returns an error that formats as the given text and aligns with builtin error
func newServiceError(status ErrorCode, err error) error { func newServiceError(status errorCode, err error) error {
return &serviceError{status, fmt.Errorf("service error (%v): %w", status, err)} return &serviceError{status, fmt.Errorf("service error (%v): %w", status, err)}
} }
// newServiceDatabaseError returns an error that formats as the given text and aligns with builtin error // newServiceDatabaseError returns an error that formats as the given text and aligns with builtin error
func newServiceDatabaseError(error error) error { func newServiceDatabaseError(error error) error {
return newServiceError(General, fmt.Errorf("database error: %w", error)) return newServiceError(general, fmt.Errorf("database error: %w", error))
} }
type serviceError struct { type serviceError struct {
Status ErrorCode Status errorCode
Cause error Cause error
} }

View file

@ -5,7 +5,7 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
type WebhookRepository interface { type webhookRepository interface {
paginate(page int, pageSize int, orderBy string, order string) ([]*Webhook, error) paginate(page int, pageSize int, orderBy string, order string) ([]*Webhook, error)
count() (int64, error) count() (int64, error)
find(id string) (*Webhook, error) find(id string) (*Webhook, error)

View file

@ -39,7 +39,7 @@ func (s *actionService) create(label string, t api.ActionType, matchEvent *strin
} }
if isValid, validationErr := s.isValidPayload(t, payload); !isValid { if isValid, validationErr := s.isValidPayload(t, payload); !isValid {
return nil, newServiceError(IllegalArgument, validationErr) return nil, newServiceError(illegalArgument, validationErr)
} }
var err error var err error
@ -197,7 +197,7 @@ func (s *actionService) updateTypeAndPayload(id string, t api.ActionType, payloa
} }
if isValid, validationErr := s.isValidPayload(t, payload); !isValid { if isValid, validationErr := s.isValidPayload(t, payload); !isValid {
return nil, newServiceError(IllegalArgument, validationErr) return nil, newServiceError(illegalArgument, validationErr)
} }
if e, err = s.repo.updateTypeAndPayload(id, t, payload); err != nil { if e, err = s.repo.updateTypeAndPayload(id, t, payload); err != nil {

View file

@ -28,7 +28,7 @@ func newActionInvocationService(r ActionInvocationRepository, a *actionService,
func (s *actionInvocationService) enqueue(batchSize int) error { func (s *actionInvocationService) enqueue(batchSize int) error {
if batchSize <= 0 { if batchSize <= 0 {
return newServiceError(General, errors.New("cannot enqueue actions from events with invalid configured batch size")) return newServiceError(general, errors.New("cannot enqueue actions from events with invalid configured batch size"))
} }
var events []*Event var events []*Event
@ -54,7 +54,7 @@ func (s *actionInvocationService) enqueue(batchSize int) error {
func (s *actionInvocationService) enqueueFromEvent(event *Event, actions []*Action) error { func (s *actionInvocationService) enqueueFromEvent(event *Event, actions []*Action) error {
if event == nil || actions == nil { if event == nil || actions == nil {
return newServiceError(IllegalArgument, errorValidationNotEmpty) return newServiceError(illegalArgument, errorValidationNotEmpty)
} }
var err error var err error
@ -99,10 +99,10 @@ func (s *actionInvocationService) enqueueFromEvent(event *Event, actions []*Acti
func (s *actionInvocationService) invoke(batchSize int, maxRetries int) error { func (s *actionInvocationService) invoke(batchSize int, maxRetries int) error {
if batchSize <= 0 { if batchSize <= 0 {
return newServiceError(General, errors.New("cannot invoke actions with invalid configured batch size")) return newServiceError(general, errors.New("cannot invoke actions with invalid configured batch size"))
} }
if maxRetries <= 0 { if maxRetries <= 0 {
return newServiceError(General, errors.New("cannot invoke actions with invalid configured max retries")) return newServiceError(general, errors.New("cannot invoke actions with invalid configured max retries"))
} }
var err error var err error
@ -198,14 +198,14 @@ func (s *actionInvocationService) execute(action *Action, eventPayloadInfo *even
var bytes []byte var bytes []byte
if bytes, err = action.Payload.MarshalJSON(); err != nil { if bytes, err = action.Payload.MarshalJSON(); err != nil {
return newServiceError(General, err) return newServiceError(general, err)
} }
switch action.Type { switch action.Type {
case api.ActionTypeShoutrrr.Value(): case api.ActionTypeShoutrrr.Value():
var payload actionPayloadShoutrrrDto var payload actionPayloadShoutrrrDto
if payload, err = util.UnmarshalGenericJSON[actionPayloadShoutrrrDto](bytes); err != nil { if payload, err = util.UnmarshalGenericJSON[actionPayloadShoutrrrDto](bytes); err != nil {
return newServiceError(General, err) return newServiceError(general, err)
} }
body := s.replaceVars(payload.Body, eventPayloadInfo) body := s.replaceVars(payload.Body, eventPayloadInfo)
@ -220,7 +220,7 @@ func (s *actionInvocationService) execute(action *Action, eventPayloadInfo *even
} }
break break
default: default:
return newServiceError(General, errors.New("no matching action type found for invocation")) return newServiceError(general, errors.New("no matching action type found for invocation"))
} }
return nil return nil

View file

@ -194,41 +194,41 @@ func (s *eventService) extractPayloadInfo(event *Event) (*eventPayloadInformatio
var bytes []byte var bytes []byte
if bytes, err = event.Payload.MarshalJSON(); err != nil { if bytes, err = event.Payload.MarshalJSON(); err != nil {
return nil, newServiceError(General, err) return nil, newServiceError(general, err)
} }
switch event.Name { switch event.Name {
case api.EventNameUpdateCreated.Value(): case api.EventNameUpdateCreated.Value():
var p api.EventPayloadUpdateCreatedDto var p api.EventPayloadUpdateCreatedDto
if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateCreatedDto](bytes); err != nil { if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateCreatedDto](bytes); err != nil {
return nil, newServiceError(General, err) return nil, newServiceError(general, err)
} }
return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version, State: p.State}, nil return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version, State: p.State}, nil
case api.EventNameUpdateDeleted.Value(): case api.EventNameUpdateDeleted.Value():
var p api.EventPayloadUpdateDeletedDto var p api.EventPayloadUpdateDeletedDto
if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateDeletedDto](bytes); err != nil { if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateDeletedDto](bytes); err != nil {
return nil, newServiceError(General, err) return nil, newServiceError(general, err)
} }
return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version, State: p.State}, nil return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version, State: p.State}, nil
case api.EventNameUpdateUpdatedState.Value(): case api.EventNameUpdateUpdatedState.Value():
var p api.EventPayloadUpdateUpdatedDto var p api.EventPayloadUpdateUpdatedDto
if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateUpdatedDto](bytes); err != nil { if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateUpdatedDto](bytes); err != nil {
return nil, newServiceError(General, err) return nil, newServiceError(general, err)
} }
return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version, State: p.State}, nil return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version, State: p.State}, nil
case api.EventNameUpdateUpdatedVersion.Value(): case api.EventNameUpdateUpdatedVersion.Value():
var p api.EventPayloadUpdateUpdatedDto var p api.EventPayloadUpdateUpdatedDto
if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateUpdatedDto](bytes); err != nil { if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateUpdatedDto](bytes); err != nil {
return nil, newServiceError(General, err) return nil, newServiceError(general, err)
} }
return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version, State: p.State}, nil return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version, State: p.State}, nil
case api.EventNameUpdateUpdated.Value(): case api.EventNameUpdateUpdated.Value():
var p api.EventPayloadUpdateUpdatedDto var p api.EventPayloadUpdateUpdatedDto
if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateUpdatedDto](bytes); err != nil { if p, err = util.UnmarshalGenericJSON[api.EventPayloadUpdateUpdatedDto](bytes); err != nil {
return nil, newServiceError(General, err) return nil, newServiceError(general, err)
} }
return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version, State: p.State}, nil return &eventPayloadInformationDto{Host: p.Host, Application: p.Application, Provider: p.Provider, Version: p.Version, State: p.State}, nil
} }
return nil, newServiceError(General, errors.New("no matching event found")) return nil, newServiceError(general, errors.New("no matching event found"))
} }

View file

@ -13,7 +13,7 @@ type lockMemService struct {
} }
var ( var (
errLockMemNotReleased = newServiceError(Conflict, errors.New("lock service: could not release lock")) errLockMemNotReleased = newServiceError(conflict, errors.New("lock service: could not release lock"))
) )
func newLockMemService() lockService { func newLockMemService() lockService {

View file

@ -15,8 +15,8 @@ type lockRedisService struct {
} }
var ( var (
errLockRedisNotObtained = newServiceError(Conflict, errors.New("lock service: could not obtain lock")) errLockRedisNotObtained = newServiceError(conflict, errors.New("lock service: could not obtain lock"))
errLockRedisNotReleased = newServiceError(Conflict, errors.New("lock service: could not release lock")) errLockRedisNotReleased = newServiceError(conflict, errors.New("lock service: could not release lock"))
) )
func newLockRedisService(lc *lockConfig) (lockService, error) { func newLockRedisService(lc *lockConfig) (lockService, error) {

View file

@ -18,7 +18,7 @@ func newPrometheusService(r *gin.Engine, c *prometheusConfig) *prometheusService
if c.secureTokenEnabled { if c.secureTokenEnabled {
p = ginprom.New( p = ginprom.New(
ginprom.Engine(r), ginprom.Engine(r),
ginprom.Namespace(Name), ginprom.Namespace(name),
ginprom.Subsystem(""), ginprom.Subsystem(""),
ginprom.Path(c.path), ginprom.Path(c.path),
ginprom.Ignore(c.path), ginprom.Ignore(c.path),
@ -27,7 +27,7 @@ func newPrometheusService(r *gin.Engine, c *prometheusConfig) *prometheusService
} else { } else {
p = ginprom.New( p = ginprom.New(
ginprom.Engine(r), ginprom.Engine(r),
ginprom.Namespace(Name), ginprom.Namespace(name),
ginprom.Subsystem(""), ginprom.Subsystem(""),
ginprom.Ignore(c.path), ginprom.Ignore(c.path),
ginprom.Path(c.path), ginprom.Path(c.path),

View file

@ -8,11 +8,11 @@ import (
) )
type webhookService struct { type webhookService struct {
repo WebhookRepository repo webhookRepository
webhookConfig *webhookConfig webhookConfig *webhookConfig
} }
func newWebhookService(r WebhookRepository, c *webhookConfig) *webhookService { func newWebhookService(r webhookRepository, c *webhookConfig) *webhookService {
return &webhookService{ return &webhookService{
repo: r, repo: r,
webhookConfig: c, webhookConfig: c,
@ -42,7 +42,7 @@ func (s *webhookService) create(label string, t api.WebhookType, ignoreHost bool
var token string var token string
if token, err = util.GenerateSecureRandomString(s.webhookConfig.tokenLength); err != nil { if token, err = util.GenerateSecureRandomString(s.webhookConfig.tokenLength); err != nil {
return nil, newServiceError(General, fmt.Errorf("token generation failed: %w", err)) return nil, newServiceError(general, fmt.Errorf("token generation failed: %w", err))
} }
var e *Webhook var e *Webhook

View file

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"git.myservermanager.com/varakh/upda/api" "git.myservermanager.com/varakh/upda/api"
"git.myservermanager.com/varakh/upda/server"
"git.myservermanager.com/varakh/upda/util" "git.myservermanager.com/varakh/upda/util"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -17,7 +16,7 @@ import (
const ( const (
name = "upda-cli" name = "upda-cli"
desc = "a commandline helper for upda" desc = "a commandline helper for upda"
version = server.Version version = "3.0.0"
envServerUrl = "UPDA_SERVER_URL" envServerUrl = "UPDA_SERVER_URL"
envUser = "UPDA_USER" envUser = "UPDA_USER"
@ -264,7 +263,7 @@ func webhookSend(cCtx *cli.Context) error {
client.SetDisableWarn(true) client.SetDisableWarn(true)
res, err := client.R(). res, err := client.R().
SetHeader("Content-Type", "application/json"). SetHeader("Content-Type", "application/json").
SetHeader(server.HeaderWebhookToken, cCtx.String(flagWebhookToken)). SetHeader("X-Webhook-Token", cCtx.String(flagWebhookToken)).
SetBody(payloadArg). SetBody(payloadArg).
SetError(&errorRes). SetError(&errorRes).
Post(url) Post(url)