mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-22 16:58:54 +02:00
refactor(errs): migrate from gperr.Error to standard Go error interface
This is a large-scale refactoring across the codebase that replaces the custom `gperr.Error` type with Go's standard `error` interface. The changes include: - Replacing `gperr.Error` return types with `error` in function signatures - Using `errors.New()` and `fmt.Errorf()` instead of `gperr.New()` and `gperr.Errorf()` - Using `%w` format verb for error wrapping instead of `.With()` method - Replacing `gperr.Subject()` calls with `gperr.PrependSubject()` - Converting error logging from `gperr.Log*()` functions to zerolog's `.Err().Msg()` pattern - Update NewLogger to handle multiline error message - Updating `goutils` submodule to latest commit This refactoring aligns with Go idioms and removes the dependency on custom error handling abstractions in favor of standard library patterns.
This commit is contained in:
@@ -54,13 +54,13 @@ type Matchers []Matcher
|
||||
### Exported functions and methods
|
||||
|
||||
```go
|
||||
func (c *Config) Validate() gperr.Error
|
||||
func (c *Config) Validate() error
|
||||
```
|
||||
|
||||
Validates configuration and sets defaults. Must be called before `Start`.
|
||||
|
||||
```go
|
||||
func (c *Config) Start(parent task.Parent) gperr.Error
|
||||
func (c *Config) Start(parent task.Parent) error
|
||||
```
|
||||
|
||||
Initializes the ACL, starts the logger and notification goroutines.
|
||||
@@ -169,14 +169,14 @@ Configuration is loaded from `config/config.yml` under the `acl` key.
|
||||
|
||||
```yaml
|
||||
acl:
|
||||
default: "allow" # "allow" or "deny"
|
||||
allow_local: true # Allow private/loopback IPs
|
||||
default: "allow" # "allow" or "deny"
|
||||
allow_local: true # Allow private/loopback IPs
|
||||
log:
|
||||
log_allowed: false # Log allowed connections
|
||||
log_allowed: false # Log allowed connections
|
||||
notify:
|
||||
to: ["gotify"] # Notification providers
|
||||
interval: "1m" # Notification interval
|
||||
include_allowed: false # Include allowed in notifications
|
||||
to: ["gotify"] # Notification providers
|
||||
interval: "1m" # Notification interval
|
||||
include_allowed: false # Include allowed in notifications
|
||||
```
|
||||
|
||||
### Hot-reloading
|
||||
|
||||
@@ -87,7 +87,7 @@ const (
|
||||
ACLDeny = "deny"
|
||||
)
|
||||
|
||||
func (c *Config) Validate() gperr.Error {
|
||||
func (c *Config) Validate() error {
|
||||
switch c.Default {
|
||||
case "", ACLAllow:
|
||||
c.defaultAllow = true
|
||||
@@ -131,7 +131,7 @@ func (c *Config) Valid() bool {
|
||||
return c != nil && c.valErr == nil
|
||||
}
|
||||
|
||||
func (c *Config) Start(parent task.Parent) gperr.Error {
|
||||
func (c *Config) Start(parent task.Parent) error {
|
||||
if c.Log != nil {
|
||||
logger, err := accesslog.NewAccessLogger(parent, c.Log)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package acl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
@@ -38,9 +39,9 @@ var errMatcherFormat = gperr.Multiline().AddLines(
|
||||
)
|
||||
|
||||
var (
|
||||
errSyntax = gperr.New("syntax error")
|
||||
errInvalidIP = gperr.New("invalid IP")
|
||||
errInvalidCIDR = gperr.New("invalid CIDR")
|
||||
errSyntax = errors.New("syntax error")
|
||||
errInvalidIP = errors.New("invalid IP")
|
||||
errInvalidCIDR = errors.New("invalid CIDR")
|
||||
)
|
||||
|
||||
func (matcher *Matcher) Parse(s string) error {
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/yusing/godoxy/internal/auth"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
// @title GoDoxy API
|
||||
@@ -203,9 +202,8 @@ func ErrorHandler() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Next()
|
||||
if len(c.Errors) > 0 {
|
||||
logger := log.With().Str("uri", c.Request.RequestURI).Logger()
|
||||
for _, err := range c.Errors {
|
||||
gperr.LogError("Internal error", err.Err, &logger)
|
||||
log.Err(err.Err).Str("uri", c.Request.RequestURI).Msg("Internal error")
|
||||
}
|
||||
if !c.IsWebsocket() {
|
||||
c.JSON(http.StatusInternalServerError, apitypes.Error("Internal server error"))
|
||||
|
||||
@@ -2,6 +2,7 @@ package agentapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
config "github.com/yusing/godoxy/internal/config/types"
|
||||
"github.com/yusing/godoxy/internal/route/provider"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type VerifyNewAgentRequest struct {
|
||||
@@ -84,9 +84,9 @@ func Verify(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, apitypes.Success(fmt.Sprintf("Added %d routes", nRoutesAdded)))
|
||||
}
|
||||
|
||||
var errAgentAlreadyExists = gperr.New("agent already exists")
|
||||
var errAgentAlreadyExists = errors.New("agent already exists")
|
||||
|
||||
func verifyNewAgent(ctx context.Context, host string, ca agent.PEMPair, client agent.PEMPair, containerRuntime agent.ContainerRuntime) (int, gperr.Error) {
|
||||
func verifyNewAgent(ctx context.Context, host string, ca agent.PEMPair, client agent.PEMPair, containerRuntime agent.ContainerRuntime) (int, error) {
|
||||
var agentCfg agent.AgentConfig
|
||||
agentCfg.Addr = host
|
||||
agentCfg.Runtime = containerRuntime
|
||||
@@ -105,12 +105,12 @@ func verifyNewAgent(ctx context.Context, host string, ca agent.PEMPair, client a
|
||||
|
||||
err := agentCfg.InitWithCerts(ctx, ca.Cert, client.Cert, client.Key)
|
||||
if err != nil {
|
||||
return 0, gperr.Wrap(err, "failed to initialize agent config")
|
||||
return 0, fmt.Errorf("failed to initialize agent config: %w", err)
|
||||
}
|
||||
|
||||
provider := provider.NewAgentProvider(&agentCfg)
|
||||
if _, loaded := cfgState.LoadOrStoreProvider(provider.String(), provider); loaded {
|
||||
return 0, gperr.Errorf("provider %s already exists", provider.String())
|
||||
return 0, fmt.Errorf("provider %s already exists", provider.String())
|
||||
}
|
||||
|
||||
// agent must be added before loading routes
|
||||
@@ -122,7 +122,7 @@ func verifyNewAgent(ctx context.Context, host string, ca agent.PEMPair, client a
|
||||
if err != nil {
|
||||
cfgState.DeleteProvider(provider.String())
|
||||
agentpool.Remove(&agentCfg)
|
||||
return 0, gperr.Wrap(err, "failed to load routes")
|
||||
return 0, fmt.Errorf("failed to load routes: %w", err)
|
||||
}
|
||||
|
||||
return provider.NumRoutes(), nil
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
|
||||
_ "github.com/yusing/goutils/apitypes"
|
||||
@@ -36,18 +37,18 @@ func Containers(c *gin.Context) {
|
||||
serveHTTP[Container](c, GetContainers)
|
||||
}
|
||||
|
||||
func GetContainers(ctx context.Context, dockerClients DockerClients) ([]Container, gperr.Error) {
|
||||
func GetContainers(ctx context.Context, dockerClients DockerClients) ([]Container, error) {
|
||||
errs := gperr.NewBuilder("failed to get containers")
|
||||
containers := make([]Container, 0)
|
||||
for server, dockerClient := range dockerClients {
|
||||
for name, dockerClient := range dockerClients {
|
||||
conts, err := dockerClient.ContainerList(ctx, client.ContainerListOptions{All: true})
|
||||
if err != nil {
|
||||
errs.Add(err)
|
||||
errs.AddSubject(err, name)
|
||||
continue
|
||||
}
|
||||
for _, cont := range conts.Items {
|
||||
containers = append(containers, Container{
|
||||
Server: server,
|
||||
Server: name,
|
||||
Name: cont.Names[0],
|
||||
ID: cont.ID,
|
||||
Image: cont.Image,
|
||||
@@ -59,11 +60,10 @@ func GetContainers(ctx context.Context, dockerClients DockerClients) ([]Containe
|
||||
return containers[i].Name < containers[j].Name
|
||||
})
|
||||
if err := errs.Error(); err != nil {
|
||||
gperr.LogError("failed to get containers", err)
|
||||
if len(containers) == 0 {
|
||||
return nil, err
|
||||
if len(containers) > 0 {
|
||||
log.Err(err).Msg("failed to get containers from some servers")
|
||||
return containers, nil
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
return containers, nil
|
||||
return containers, errs.Error()
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func Info(c *gin.Context) {
|
||||
serveHTTP[dockerInfo](c, GetDockerInfo)
|
||||
}
|
||||
|
||||
func GetDockerInfo(ctx context.Context, dockerClients DockerClients) ([]dockerInfo, gperr.Error) {
|
||||
func GetDockerInfo(ctx context.Context, dockerClients DockerClients) ([]dockerInfo, error) {
|
||||
errs := gperr.NewBuilder("failed to get docker info")
|
||||
dockerInfos := make([]dockerInfo, len(dockerClients))
|
||||
|
||||
@@ -67,7 +67,7 @@ func GetDockerInfo(ctx context.Context, dockerClients DockerClients) ([]dockerIn
|
||||
for name, dockerClient := range dockerClients {
|
||||
info, err := dockerClient.Info(ctx, client.InfoOptions{})
|
||||
if err != nil {
|
||||
errs.Add(err)
|
||||
errs.AddSubject(err, name)
|
||||
continue
|
||||
}
|
||||
info.Info.Name = name
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/http/httpheaders"
|
||||
"github.com/yusing/goutils/http/websocket"
|
||||
)
|
||||
@@ -39,7 +38,7 @@ func handleResult[V any, T ResultType[V]](c *gin.Context, errs error, result T)
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func serveHTTP[V any, T ResultType[V]](c *gin.Context, getResult func(ctx context.Context, dockerClients DockerClients) (T, gperr.Error)) {
|
||||
func serveHTTP[V any, T ResultType[V]](c *gin.Context, getResult func(ctx context.Context, dockerClients DockerClients) (T, error)) {
|
||||
dockerClients := docker.Clients()
|
||||
defer closeAllClients(dockerClients)
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ func Validate(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, apitypes.Success("file validated"))
|
||||
}
|
||||
|
||||
func validateFile(fileType FileType, content []byte) gperr.Error {
|
||||
func validateFile(fileType FileType, content []byte) error {
|
||||
switch fileType {
|
||||
case FileTypeConfig:
|
||||
return config.Validate(content)
|
||||
|
||||
@@ -113,7 +113,7 @@ func AllSystemInfo(c *gin.Context) {
|
||||
data, err := systeminfo.Poller.GetRespData(req.Period, query)
|
||||
if err != nil {
|
||||
numErrs.Add(1)
|
||||
return gperr.PrependSubject("Main server", err)
|
||||
return gperr.PrependSubject(err, "Main server")
|
||||
}
|
||||
select {
|
||||
case <-manager.Done():
|
||||
@@ -133,7 +133,7 @@ func AllSystemInfo(c *gin.Context) {
|
||||
data, err := getAgentSystemInfoWithRetry(manager.Context(), a, queryEncoded)
|
||||
if err != nil {
|
||||
numErrs.Add(1)
|
||||
return gperr.PrependSubject("Agent "+a.Name, err)
|
||||
return gperr.PrependSubject(err, "Agent "+a.Name)
|
||||
}
|
||||
select {
|
||||
case <-manager.Done():
|
||||
@@ -170,7 +170,7 @@ func AllSystemInfo(c *gin.Context) {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to get all system info"))
|
||||
return
|
||||
}
|
||||
gperr.LogWarn("failed to get some system info", err)
|
||||
log.Warn().Err(err).Msg("failed to get some system info")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package routeApi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -54,16 +55,16 @@ type PlaygroundResponse struct {
|
||||
MatchedRules []string `json:"matchedRules"`
|
||||
FinalRequest FinalRequest `json:"finalRequest"`
|
||||
FinalResponse FinalResponse `json:"finalResponse"`
|
||||
ExecutionError gperr.Error `json:"executionError,omitempty"`
|
||||
ExecutionError error `json:"executionError,omitempty"` // we need the structured error, not the plain string
|
||||
UpstreamCalled bool `json:"upstreamCalled"`
|
||||
} // @name PlaygroundResponse
|
||||
|
||||
type ParsedRule struct {
|
||||
Name string `json:"name"`
|
||||
On string `json:"on"`
|
||||
Do string `json:"do"`
|
||||
ValidationError gperr.Error `json:"validationError,omitempty"`
|
||||
IsResponseRule bool `json:"isResponseRule"`
|
||||
Name string `json:"name"`
|
||||
On string `json:"on"`
|
||||
Do string `json:"do"`
|
||||
ValidationError error `json:"validationError,omitempty"` // we need the structured error, not the plain string
|
||||
IsResponseRule bool `json:"isResponseRule"`
|
||||
} // @name ParsedRule
|
||||
|
||||
type FinalRequest struct {
|
||||
@@ -138,7 +139,7 @@ func Playground(c *gin.Context) {
|
||||
// Execute rules
|
||||
matchedRules := []string{}
|
||||
upstreamCalled := false
|
||||
var executionError gperr.Error
|
||||
var executionError error
|
||||
|
||||
// Variables to capture modified request state
|
||||
var finalReqMethod, finalReqPath, finalReqHost string
|
||||
@@ -244,21 +245,23 @@ func Playground(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
func handlerWithRecover(w http.ResponseWriter, r *http.Request, h http.HandlerFunc, outErr *gperr.Error) {
|
||||
func handlerWithRecover(w http.ResponseWriter, r *http.Request, h http.HandlerFunc, outErr *error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if outErr != nil {
|
||||
*outErr = gperr.Errorf("panic during rule execution: %v", r)
|
||||
*outErr = fmt.Errorf("panic during rule execution: %v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
h(w, r)
|
||||
}
|
||||
|
||||
func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, gperr.Error) {
|
||||
func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, error) {
|
||||
var parsedRules []ParsedRule
|
||||
var rulesList rules.Rules
|
||||
|
||||
var valErrs gperr.Builder
|
||||
|
||||
// Parse each rule individually to capture per-rule errors
|
||||
for _, rawRule := range rawRules {
|
||||
var rule rules.Rule
|
||||
@@ -284,7 +287,11 @@ func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, gperr.Error) {
|
||||
|
||||
// Determine if valid
|
||||
isValid := onErr == nil && doErr == nil
|
||||
validationErr := gperr.Join(gperr.PrependSubject("on", onErr), gperr.PrependSubject("do", doErr))
|
||||
var validationErr error
|
||||
if !isValid {
|
||||
validationErr = gperr.Join(gperr.PrependSubject(onErr, "on"), gperr.PrependSubject(doErr, "do"))
|
||||
valErrs.Add(validationErr)
|
||||
}
|
||||
|
||||
parsedRules = append(parsedRules, ParsedRule{
|
||||
Name: name,
|
||||
@@ -300,7 +307,7 @@ func parseRules(rawRules []RawRule) ([]ParsedRule, rules.Rules, gperr.Error) {
|
||||
}
|
||||
}
|
||||
|
||||
return parsedRules, rulesList, nil
|
||||
return parsedRules, rulesList, valErrs.Error()
|
||||
}
|
||||
|
||||
func createMockRequest(mock MockRequest) *http.Request {
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
"github.com/yusing/godoxy/internal/utils"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
httputils "github.com/yusing/goutils/http"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/time/rate"
|
||||
@@ -76,8 +75,8 @@ const (
|
||||
var (
|
||||
errMissingIDToken = errors.New("missing id_token field from oauth token")
|
||||
|
||||
ErrMissingOAuthToken = gperr.New("missing oauth token")
|
||||
ErrInvalidOAuthToken = gperr.New("invalid oauth token")
|
||||
ErrMissingOAuthToken = errors.New("missing oauth token")
|
||||
ErrInvalidOAuthToken = errors.New("invalid oauth token")
|
||||
)
|
||||
|
||||
// generateState generates a random string for OIDC state.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -8,16 +9,12 @@ import (
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
httputils "github.com/yusing/goutils/http"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidUsername = gperr.New("invalid username")
|
||||
ErrInvalidPassword = gperr.New("invalid password")
|
||||
)
|
||||
var ErrInvalidUsername = errors.New("invalid username")
|
||||
|
||||
type (
|
||||
UserPassAuth struct {
|
||||
@@ -94,9 +91,9 @@ func (auth *UserPassAuth) CheckToken(r *http.Request) error {
|
||||
case !token.Valid:
|
||||
return ErrInvalidSessionToken
|
||||
case claims.Username != auth.username:
|
||||
return ErrUserNotAllowed.Subject(claims.Username)
|
||||
return fmt.Errorf("%w: %s", ErrUserNotAllowed, claims.Username)
|
||||
case claims.ExpiresAt.Before(time.Now()):
|
||||
return gperr.Errorf("token expired on %s", strutils.FormatTime(claims.ExpiresAt.Time))
|
||||
return fmt.Errorf("token expired on %s", strutils.FormatTime(claims.ExpiresAt.Time))
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -140,10 +137,10 @@ func (auth *UserPassAuth) LogoutHandler(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
func (auth *UserPassAuth) validatePassword(user, pass string) error {
|
||||
if user != auth.username {
|
||||
return ErrInvalidUsername.Subject(user)
|
||||
return ErrInvalidUsername
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword(auth.pwdHash, []byte(pass)); err != nil {
|
||||
return ErrInvalidPassword.With(err).Subject(pass)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestUserPassValidateCredentials(t *testing.T) {
|
||||
err := auth.validatePassword("username", "password")
|
||||
expect.NoError(t, err)
|
||||
err = auth.validatePassword("username", "wrong-password")
|
||||
expect.ErrorIs(t, ErrInvalidPassword, err)
|
||||
expect.ErrorIs(t, bcrypt.ErrMismatchedHashAndPassword, err)
|
||||
err = auth.validatePassword("wrong-username", "password")
|
||||
expect.ErrorIs(t, ErrInvalidUsername, err)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingSessionToken = gperr.New("missing session token")
|
||||
ErrInvalidSessionToken = gperr.New("invalid session token")
|
||||
ErrUserNotAllowed = gperr.New("user not allowed")
|
||||
ErrMissingSessionToken = errors.New("missing session token")
|
||||
ErrInvalidSessionToken = errors.New("invalid session token")
|
||||
ErrUserNotAllowed = errors.New("user not allowed")
|
||||
)
|
||||
|
||||
func IsFrontend(r *http.Request) bool {
|
||||
|
||||
@@ -66,13 +66,13 @@ const (
|
||||
|
||||
var domainOrWildcardRE = regexp.MustCompile(`^\*?([^.]+\.)+[^.]+$`)
|
||||
|
||||
// Validate implements the utils.CustomValidator interface.
|
||||
func (cfg *Config) Validate() gperr.Error {
|
||||
// Validate implements the serialization.CustomValidator interface.
|
||||
func (cfg *Config) Validate() error {
|
||||
seenPaths := make(map[string]int) // path -> provider idx (0 for main, 1+ for extras)
|
||||
return cfg.validate(seenPaths)
|
||||
}
|
||||
|
||||
func (cfg *ConfigExtra) Validate() gperr.Error {
|
||||
func (cfg *ConfigExtra) Validate() error {
|
||||
return nil // done by main config's validate
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func (cfg *ConfigExtra) AsConfig() *Config {
|
||||
return (*Config)(cfg)
|
||||
}
|
||||
|
||||
func (cfg *Config) validate(seenPaths map[string]int) gperr.Error {
|
||||
func (cfg *Config) validate(seenPaths map[string]int) error {
|
||||
if cfg.Provider == "" {
|
||||
cfg.Provider = ProviderLocal
|
||||
}
|
||||
@@ -157,7 +157,7 @@ func (cfg *Config) validate(seenPaths map[string]int) gperr.Error {
|
||||
cfg.Extra[i].AsConfig().idx = i + 1
|
||||
err := cfg.Extra[i].AsConfig().validate(seenPaths)
|
||||
if err != nil {
|
||||
b.Add(err.Subjectf("extra[%d]", i))
|
||||
b.AddSubjectf(err, "extra[%d]", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,10 +179,10 @@ func (cfg *Config) GetLegoConfig() (*User, *lego.Config, error) {
|
||||
log.Info().Err(err).Msg("failed to load ACME private key, generating a now one")
|
||||
privKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, gperr.New("generate ACME private key").With(err)
|
||||
return nil, nil, fmt.Errorf("generate ACME private key: %w", err)
|
||||
}
|
||||
if err = cfg.SaveACMEKey(privKey); err != nil {
|
||||
return nil, nil, gperr.New("save ACME private key").With(err)
|
||||
return nil, nil, fmt.Errorf("save ACME private key: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,7 +206,7 @@ func (cfg *Config) GetLegoConfig() (*User, *lego.Config, error) {
|
||||
if len(cfg.CACerts) > 0 {
|
||||
certPool, err := lego.CreateCertPool(cfg.CACerts, true)
|
||||
if err != nil {
|
||||
return nil, nil, gperr.New("failed to create cert pool").With(err)
|
||||
return nil, nil, fmt.Errorf("failed to create cert pool: %w", err)
|
||||
}
|
||||
legoCfg.HTTPClient.Transport.(*http.Transport).TLSClientConfig.RootCAs = certPool
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ func (p *Provider) GetName() string {
|
||||
}
|
||||
|
||||
func (p *Provider) fmtError(err error) error {
|
||||
return gperr.PrependSubject(fmt.Sprintf("provider: %s", p.GetName()), err)
|
||||
return gperr.PrependSubject(err, "provider: "+p.GetName())
|
||||
}
|
||||
|
||||
func (p *Provider) GetCertPath() string {
|
||||
@@ -216,7 +216,7 @@ func (p *Provider) ObtainCertIfNotExistsAll() error {
|
||||
for _, provider := range p.allProviders() {
|
||||
errs.Go(func() error {
|
||||
if err := provider.obtainCertIfNotExists(); err != nil {
|
||||
return fmt.Errorf("failed to obtain cert for %s: %w", provider.GetName(), err)
|
||||
return gperr.PrependSubject(err, provider.GetName())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -475,7 +475,7 @@ func (p *Provider) scheduleRenewal(parent task.Parent) {
|
||||
|
||||
renewed, err := p.renew(renewMode)
|
||||
if err != nil {
|
||||
gperr.LogWarn("autocert: cert renew failed", p.fmtError(err))
|
||||
log.Warn().Err(p.fmtError(err)).Msg("autocert: cert renew failed")
|
||||
notif.Notify(¬if.LogMessage{
|
||||
Level: zerolog.ErrorLevel,
|
||||
Title: fmt.Sprintf("SSL certificate renewal failed for %s", p.GetName()),
|
||||
@@ -494,7 +494,7 @@ func (p *Provider) scheduleRenewal(parent task.Parent) {
|
||||
|
||||
// Reset on success
|
||||
if err := p.ClearLastFailure(); err != nil {
|
||||
gperr.LogWarn("autocert: failed to clear last failure", p.fmtError(err))
|
||||
log.Warn().Err(p.fmtError(err)).Msg("autocert: failed to clear last failure")
|
||||
}
|
||||
timer.Reset(time.Until(p.ShouldRenewOn()))
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@ package autocert
|
||||
import (
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
)
|
||||
|
||||
type Generator func(map[string]strutils.Redacted) (challenge.Provider, gperr.Error)
|
||||
type Generator func(map[string]strutils.Redacted) (challenge.Provider, error)
|
||||
|
||||
var Providers = make(map[string]Generator)
|
||||
|
||||
@@ -15,7 +14,7 @@ func DNSProvider[CT any, PT challenge.Provider](
|
||||
defaultCfg func() *CT,
|
||||
newProvider func(*CT) (PT, error),
|
||||
) Generator {
|
||||
return func(opt map[string]strutils.Redacted) (challenge.Provider, gperr.Error) {
|
||||
return func(opt map[string]strutils.Redacted) (challenge.Provider, error) {
|
||||
cfg := defaultCfg()
|
||||
if len(opt) > 0 {
|
||||
err := serialization.MapUnmarshalValidate(serialization.ToSerializedObject(opt), &cfg)
|
||||
@@ -24,6 +23,6 @@ func DNSProvider[CT any, PT challenge.Provider](
|
||||
}
|
||||
}
|
||||
p, pErr := newProvider(cfg)
|
||||
return p, gperr.Wrap(pErr)
|
||||
return p, pErr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
func (p *Provider) setupExtraProviders() gperr.Error {
|
||||
func (p *Provider) setupExtraProviders() error {
|
||||
p.sniMatcher = sniMatcher{}
|
||||
if len(p.cfg.Extra) == 0 {
|
||||
return nil
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
config "github.com/yusing/godoxy/internal/config/types"
|
||||
"github.com/yusing/godoxy/internal/notif"
|
||||
@@ -32,7 +33,7 @@ You may run "ls-config" to show or dump the current config.`
|
||||
)
|
||||
|
||||
func logNotifyError(action string, err error) {
|
||||
gperr.LogError("config "+action+" error", err)
|
||||
log.Error().Err(err).Msg("config " + action + " error")
|
||||
notif.Notify(¬if.LogMessage{
|
||||
Level: zerolog.ErrorLevel,
|
||||
Title: fmt.Sprintf("Config %s error", action),
|
||||
@@ -41,7 +42,7 @@ func logNotifyError(action string, err error) {
|
||||
}
|
||||
|
||||
func logNotifyWarn(action string, err error) {
|
||||
gperr.LogWarn("config "+action+" error", err)
|
||||
log.Warn().Err(err).Msg("config " + action + " warning")
|
||||
notif.Notify(¬if.LogMessage{
|
||||
Level: zerolog.WarnLevel,
|
||||
Title: fmt.Sprintf("Config %s warning", action),
|
||||
@@ -89,7 +90,7 @@ func Load() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Reload() gperr.Error {
|
||||
func Reload() error {
|
||||
// avoid race between config change and API reload request
|
||||
reloadMu.Lock()
|
||||
defer reloadMu.Unlock()
|
||||
@@ -128,7 +129,7 @@ func WatchChanges() {
|
||||
t,
|
||||
configEventFlushInterval,
|
||||
OnConfigChange,
|
||||
func(err gperr.Error) {
|
||||
func(err error) {
|
||||
logNotifyError("reload", err)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -353,7 +353,7 @@ func (state *state) initProxmox() error {
|
||||
for _, cfg := range proxmoxCfg {
|
||||
errs.Go(func() error {
|
||||
if err := cfg.Init(state.task.Context()); err != nil {
|
||||
return err.Subject(cfg.URL)
|
||||
return gperr.PrependSubject(err, cfg.URL)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -377,7 +377,7 @@ func (state *state) loadRouteProviders() error {
|
||||
for _, a := range providers.Agents {
|
||||
agentErrs.Go(func() error {
|
||||
if err := a.Init(state.task.Context()); err != nil {
|
||||
return gperr.PrependSubject(a.String(), err)
|
||||
return gperr.PrependSubject(err, a.String())
|
||||
}
|
||||
agentpool.Add(a)
|
||||
return nil
|
||||
@@ -395,7 +395,7 @@ func (state *state) loadRouteProviders() error {
|
||||
for _, filename := range providers.Files {
|
||||
p, err := route.NewFileProvider(filename)
|
||||
if err != nil {
|
||||
errs.Add(gperr.PrependSubject(filename, err))
|
||||
errs.Add(gperr.PrependSubject(err, filename))
|
||||
return err
|
||||
}
|
||||
registerProvider(p)
|
||||
@@ -420,7 +420,7 @@ func (state *state) loadRouteProviders() error {
|
||||
for _, p := range state.providers.Range {
|
||||
loadErrs.Go(func() error {
|
||||
if err := p.LoadRoutes(); err != nil {
|
||||
return err.Subject(p.String())
|
||||
return gperr.PrependSubject(err, p.String())
|
||||
}
|
||||
resultsMu.Lock()
|
||||
results.Addf("%-"+strconv.Itoa(lenLongestName)+"s %d routes", p.String(), p.NumRoutes())
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/yusing/godoxy/internal/proxmox"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -42,7 +41,7 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func Validate(data []byte) gperr.Error {
|
||||
func Validate(data []byte) error {
|
||||
var model Config
|
||||
return serialization.UnmarshalValidate(data, &model, yaml.Unmarshal)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
)
|
||||
|
||||
var ErrInvalidLabel = gperr.New("invalid label")
|
||||
var ErrInvalidLabel = errors.New("invalid label")
|
||||
|
||||
const nsProxyDot = NSProxy + "."
|
||||
|
||||
@@ -23,7 +24,7 @@ var refPrefixes = func() []string {
|
||||
return prefixes
|
||||
}()
|
||||
|
||||
func ParseLabels(labels map[string]string, aliases ...string) (types.LabelMap, gperr.Error) {
|
||||
func ParseLabels(labels map[string]string, aliases ...string) (types.LabelMap, error) {
|
||||
nestedMap := make(types.LabelMap)
|
||||
errs := gperr.NewBuilder("labels error")
|
||||
|
||||
@@ -35,7 +36,7 @@ func ParseLabels(labels map[string]string, aliases ...string) (types.LabelMap, g
|
||||
continue
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
errs.Add(ErrInvalidLabel.Subject(lbl))
|
||||
errs.AddSubject(ErrInvalidLabel, lbl)
|
||||
continue
|
||||
}
|
||||
parts = parts[1:]
|
||||
|
||||
@@ -41,7 +41,7 @@ type HealthCheckFunc func(url *url.URL) (result types.HealthCheckResult, err err
|
||||
|
||||
```go
|
||||
type HealthMonitor interface {
|
||||
Start(parent task.Parent) gperr.Error
|
||||
Start(parent task.Parent) error
|
||||
Task() *task.Task
|
||||
Finish(reason any)
|
||||
UpdateURL(url *url.URL)
|
||||
|
||||
@@ -2,6 +2,7 @@ package monitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
config "github.com/yusing/godoxy/internal/config/types"
|
||||
"github.com/yusing/godoxy/internal/notif"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
"github.com/yusing/goutils/synk"
|
||||
"github.com/yusing/goutils/task"
|
||||
@@ -42,7 +42,7 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
var ErrNegativeInterval = gperr.New("negative interval")
|
||||
var ErrNegativeInterval = errors.New("negative interval")
|
||||
|
||||
func (mon *monitor) init(u *url.URL, cfg types.HealthCheckConfig, healthCheckFunc HealthCheckFunc) {
|
||||
if state := config.WorkingState.Load(); state != nil {
|
||||
@@ -79,7 +79,7 @@ func (mon *monitor) CheckHealth() (types.HealthCheckResult, error) {
|
||||
}
|
||||
|
||||
// Start implements task.TaskStarter.
|
||||
func (mon *monitor) Start(parent task.Parent) gperr.Error {
|
||||
func (mon *monitor) Start(parent task.Parent) error {
|
||||
if mon.config.Interval <= 0 {
|
||||
return ErrNegativeInterval
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ func (icon *Meta) Filenames(ref string) []string
|
||||
func NewURL(source Source, refOrName, format string) *URL
|
||||
|
||||
// ErrInvalidIconURL is returned when icon URL parsing fails
|
||||
var ErrInvalidIconURL = gperr.New("invalid icon url")
|
||||
var ErrInvalidIconURL = errors.New("invalid icon url")
|
||||
```
|
||||
|
||||
### Provider Interface
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package icons
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -40,7 +41,7 @@ const (
|
||||
VariantDark Variant = "dark"
|
||||
)
|
||||
|
||||
var ErrInvalidIconURL = gperr.New("invalid icon url")
|
||||
var ErrInvalidIconURL = errors.New("invalid icon url")
|
||||
|
||||
func NewURL(source Source, refOrName, format string) *URL {
|
||||
switch source {
|
||||
@@ -119,7 +120,7 @@ func (u *URL) parse(v string, checkExists bool) error {
|
||||
case "@target", "": // @target/favicon.ico, /favicon.ico
|
||||
url := v[slashIndex:]
|
||||
if url == "/" {
|
||||
return ErrInvalidIconURL.Withf("%s", "empty path")
|
||||
return fmt.Errorf("%w: empty path", ErrInvalidIconURL)
|
||||
}
|
||||
u.FullURL = &url
|
||||
u.Source = SourceRelative
|
||||
@@ -131,7 +132,7 @@ func (u *URL) parse(v string, checkExists bool) error {
|
||||
}
|
||||
parts := strings.Split(v[slashIndex+1:], ".")
|
||||
if len(parts) != 2 {
|
||||
return ErrInvalidIconURL.Withf("expect @%s/<reference>.<format>, e.g. @%s/adguard-home.webp", beforeSlash, beforeSlash)
|
||||
return fmt.Errorf("%w: expect %s/<reference>.<format>, e.g. %s/adguard-home.webp", ErrInvalidIconURL, beforeSlash, beforeSlash)
|
||||
}
|
||||
reference, format := parts[0], strings.ToLower(parts[1])
|
||||
if reference == "" || format == "" {
|
||||
@@ -140,7 +141,7 @@ func (u *URL) parse(v string, checkExists bool) error {
|
||||
switch format {
|
||||
case "svg", "png", "webp":
|
||||
default:
|
||||
return ErrInvalidIconURL.Withf("%s", "invalid image format, expect svg/png/webp")
|
||||
return fmt.Errorf("%w: invalid image format, expect svg/png/webp", ErrInvalidIconURL)
|
||||
}
|
||||
isLight, isDark := false, false
|
||||
if strings.HasSuffix(reference, "-light") {
|
||||
@@ -158,10 +159,10 @@ func (u *URL) parse(v string, checkExists bool) error {
|
||||
IsDark: isDark,
|
||||
}
|
||||
if checkExists && !u.HasIcon() {
|
||||
return ErrInvalidIconURL.Withf("no such icon %s.%s from %s", reference, format, u.Source)
|
||||
return fmt.Errorf("%w: no such icon %s.%s from %s", ErrInvalidIconURL, reference, format, u.Source)
|
||||
}
|
||||
default:
|
||||
return ErrInvalidIconURL.Subject(v)
|
||||
return gperr.PrependSubject(ErrInvalidIconURL, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -2,13 +2,13 @@ package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/yusing/godoxy/internal/homepage/widgets"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
@@ -46,7 +46,7 @@ func (c *Client) doRequest(ctx context.Context, method, endpoint string, query u
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, gperr.Errorf("%w: %d %s", widgets.ErrHTTPStatus, resp.StatusCode, resp.Status)
|
||||
return nil, fmt.Errorf("%w: %d %s", widgets.ErrHTTPStatus, resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
||||
@@ -50,8 +50,7 @@ const (
|
||||
### Errors
|
||||
|
||||
```go
|
||||
var ErrInvalidProvider = gperr.New("invalid provider")
|
||||
var ErrHTTPStatus = gperr.New("http status")
|
||||
var ErrInvalidProvider = errors.New("invalid provider")
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package widgets
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
var HTTPClient = &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
var ErrHTTPStatus = gperr.New("http status")
|
||||
var ErrHTTPStatus = errors.New("http status")
|
||||
|
||||
@@ -2,6 +2,8 @@ package widgets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
@@ -30,21 +32,21 @@ var widgetProviders = map[string]struct{}{
|
||||
WidgetProviderQbittorrent: {},
|
||||
}
|
||||
|
||||
var ErrInvalidProvider = gperr.New("invalid provider")
|
||||
var ErrInvalidProvider = errors.New("invalid provider")
|
||||
|
||||
func (cfg *Config) UnmarshalMap(m map[string]any) error {
|
||||
var ok bool
|
||||
cfg.Provider, ok = m["provider"].(string)
|
||||
if !ok {
|
||||
return ErrInvalidProvider.Withf("non string")
|
||||
return fmt.Errorf("%w: non string", ErrInvalidProvider)
|
||||
}
|
||||
if _, ok := widgetProviders[cfg.Provider]; !ok {
|
||||
return ErrInvalidProvider.Subject(cfg.Provider)
|
||||
return gperr.PrependSubject(ErrInvalidProvider, cfg.Provider)
|
||||
}
|
||||
delete(m, "provider")
|
||||
m, ok = m["config"].(map[string]any)
|
||||
if !ok {
|
||||
return gperr.New("invalid config")
|
||||
return errors.New("invalid config")
|
||||
}
|
||||
return serialization.MapUnmarshalValidate(m, &cfg.Config)
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func NewWatcher(parent task.Parent, r types.Route, cfg *types.IdlewatcherConfig)
|
||||
func (w *Watcher) Wake(ctx context.Context) error
|
||||
|
||||
// Start begins the idle watcher loop
|
||||
func (w *Watcher) Start(parent task.Parent) gperr.Error
|
||||
func (w *Watcher) Start(parent task.Parent) error
|
||||
|
||||
// ServeHTTP serves the loading page and SSE events
|
||||
func (w *Watcher) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
||||
@@ -103,7 +103,7 @@ var (
|
||||
classDiagram
|
||||
class Watcher {
|
||||
+Wake(ctx) error
|
||||
+Start(parent) gperr.Error
|
||||
+Start(parent) error
|
||||
+ServeHTTP(ResponseWriter, *Request)
|
||||
+ListenAndServe(ctx, preDial, onRead)
|
||||
+Key() string
|
||||
|
||||
@@ -20,7 +20,7 @@ func (e *watcherError) Error() string {
|
||||
}
|
||||
|
||||
func (w *Watcher) newWatcherError(err error) error {
|
||||
if errors.Is(err, causeReload) {
|
||||
if errors.Is(err, errCauseReload) {
|
||||
return nil
|
||||
}
|
||||
if wErr, ok := err.(*watcherError); ok { //nolint:errorlint
|
||||
@@ -44,7 +44,7 @@ func (e *depError) Error() string {
|
||||
}
|
||||
|
||||
func (w *Watcher) newDepError(action string, dep *dependency, err error) error {
|
||||
if errors.Is(err, causeReload) {
|
||||
if errors.Is(err, errCauseReload) {
|
||||
return nil
|
||||
}
|
||||
if dErr, ok := err.(*depError); ok { //nolint:errorlint
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/homepage/icons"
|
||||
iconfetch "github.com/yusing/godoxy/internal/homepage/icons/fetch"
|
||||
idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
httputils "github.com/yusing/goutils/http"
|
||||
|
||||
_ "unsafe"
|
||||
@@ -79,7 +79,7 @@ func (w *Watcher) handleWakeEventsSSE(rw http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
err := errors.Join(event.WriteSSE(rw), controller.Flush())
|
||||
if err != nil {
|
||||
gperr.LogError("Failed to write SSE event", err, &w.l)
|
||||
log.Err(err).Msg("Failed to write SSE event")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ func (w *Watcher) handleWakeEventsSSE(rw http.ResponseWriter, r *http.Request) {
|
||||
case event := <-eventCh:
|
||||
err := errors.Join(event.WriteSSE(rw), controller.Flush())
|
||||
if err != nil {
|
||||
gperr.LogError("Failed to write SSE event", err, &w.l)
|
||||
log.Err(err).Msg("Failed to write SSE event")
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
@@ -169,7 +169,7 @@ func (w *Watcher) wakeFromHTTP(rw http.ResponseWriter, r *http.Request) (shouldN
|
||||
|
||||
err := w.Wake(r.Context())
|
||||
if err != nil {
|
||||
gperr.LogError("Failed to wake container", err, &w.l)
|
||||
log.Err(err).Msg("Failed to wake container")
|
||||
if !acceptHTML {
|
||||
http.Error(rw, "Failed to wake container", http.StatusInternalServerError)
|
||||
return false
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package idlewatcher
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
// Start implements health.HealthMonitor.
|
||||
func (w *Watcher) Start(parent task.Parent) gperr.Error {
|
||||
func (w *Watcher) Start(parent task.Parent) error {
|
||||
w.task.OnCancel("route_cleanup", func() {
|
||||
parent.Finish(w.task.FinishCause())
|
||||
})
|
||||
@@ -113,7 +113,7 @@ func (w *Watcher) checkUpdateState() (ready bool, err error) {
|
||||
if !state.startedAt.IsZero() {
|
||||
elapsed := time.Since(state.startedAt)
|
||||
if elapsed > w.cfg.WakeTimeout {
|
||||
err := gperr.Errorf("container failed to become ready within %v (started at %v, %d health check attempts)",
|
||||
err := fmt.Errorf("container failed to become ready within %v (started at %v, %d health check attempts)",
|
||||
w.cfg.WakeTimeout, state.startedAt, state.healthTries)
|
||||
w.l.Error().
|
||||
Dur("elapsed", elapsed).
|
||||
|
||||
@@ -2,6 +2,7 @@ package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
@@ -9,7 +10,6 @@ import (
|
||||
idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
"github.com/yusing/godoxy/internal/watcher"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type DockerProvider struct {
|
||||
@@ -75,10 +75,10 @@ func (p *DockerProvider) ContainerStatus(ctx context.Context) (idlewatcher.Conta
|
||||
case container.StatePaused:
|
||||
return idlewatcher.ContainerStatusPaused, nil
|
||||
}
|
||||
return idlewatcher.ContainerStatusError, idlewatcher.ErrUnexpectedContainerStatus.Subject(string(status.Container.State.Status))
|
||||
return idlewatcher.ContainerStatusError, fmt.Errorf("%w: %s", idlewatcher.ErrUnexpectedContainerStatus, status.Container.State.Status)
|
||||
}
|
||||
|
||||
func (p *DockerProvider) Watch(ctx context.Context) (eventCh <-chan watcher.Event, errCh <-chan gperr.Error) {
|
||||
func (p *DockerProvider) Watch(ctx context.Context) (eventCh <-chan watcher.Event, errCh <-chan error) {
|
||||
return p.watcher.EventsWithOptions(ctx, watcher.DockerListOptions{
|
||||
Filters: watcher.NewDockerFilters(
|
||||
watcher.DockerFilterContainer,
|
||||
|
||||
@@ -2,6 +2,8 @@ package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -27,7 +29,7 @@ var ErrNodeNotFound = gperr.New("node not found in pool")
|
||||
|
||||
func NewProxmoxProvider(ctx context.Context, nodeName string, vmid int) (idlewatcher.Provider, error) {
|
||||
if nodeName == "" || vmid == 0 {
|
||||
return nil, gperr.New("node name and vmid are required")
|
||||
return nil, errors.New("node name and vmid are required")
|
||||
}
|
||||
|
||||
node, ok := proxmox.Nodes.Get(nodeName)
|
||||
@@ -77,12 +79,12 @@ func (p *ProxmoxProvider) ContainerStatus(ctx context.Context) (idlewatcher.Cont
|
||||
case proxmox.LXCStatusStopped:
|
||||
return idlewatcher.ContainerStatusStopped, nil
|
||||
}
|
||||
return idlewatcher.ContainerStatusError, idlewatcher.ErrUnexpectedContainerStatus.Subject(string(status))
|
||||
return idlewatcher.ContainerStatusError, fmt.Errorf("%w: %s", idlewatcher.ErrUnexpectedContainerStatus, string(status))
|
||||
}
|
||||
|
||||
func (p *ProxmoxProvider) Watch(ctx context.Context) (<-chan watcher.Event, <-chan gperr.Error) {
|
||||
func (p *ProxmoxProvider) Watch(ctx context.Context) (<-chan watcher.Event, <-chan error) {
|
||||
eventCh := make(chan watcher.Event)
|
||||
errCh := make(chan gperr.Error)
|
||||
errCh := make(chan error)
|
||||
|
||||
go func() {
|
||||
defer close(eventCh)
|
||||
@@ -91,7 +93,7 @@ func (p *ProxmoxProvider) Watch(ctx context.Context) (<-chan watcher.Event, <-ch
|
||||
var err error
|
||||
p.running, err = p.LXCIsRunning(ctx, p.vmid)
|
||||
if err != nil {
|
||||
errCh <- gperr.Wrap(err)
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
|
||||
@@ -110,7 +112,7 @@ func (p *ProxmoxProvider) Watch(ctx context.Context) (<-chan watcher.Event, <-ch
|
||||
case <-ticker.C:
|
||||
status, err := p.ContainerStatus(ctx)
|
||||
if err != nil {
|
||||
errCh <- gperr.Wrap(err)
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
running := status == idlewatcher.ContainerStatusRunning
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package idlewatcher
|
||||
|
||||
import gperr "github.com/yusing/goutils/errs"
|
||||
import "errors"
|
||||
|
||||
type ContainerStatus string
|
||||
|
||||
@@ -11,4 +11,4 @@ const (
|
||||
ContainerStatusStopped ContainerStatus = "stopped"
|
||||
)
|
||||
|
||||
var ErrUnexpectedContainerStatus = gperr.New("unexpected container status")
|
||||
var ErrUnexpectedContainerStatus = errors.New("unexpected container status")
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
"github.com/yusing/godoxy/internal/watcher/events"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type Provider interface {
|
||||
@@ -15,6 +14,6 @@ type Provider interface {
|
||||
ContainerStop(ctx context.Context, signal types.ContainerSignal, timeout int) error
|
||||
ContainerKill(ctx context.Context, signal types.ContainerSignal) error
|
||||
ContainerStatus(ctx context.Context) (ContainerStatus, error)
|
||||
Watch(ctx context.Context) (eventCh <-chan events.Event, errCh <-chan gperr.Error)
|
||||
Watch(ctx context.Context) (eventCh <-chan events.Event, errCh <-chan error)
|
||||
Close()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package idlewatcher
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -97,8 +98,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
causeReload = gperr.New("reloaded") //nolint:errname
|
||||
causeContainerDestroy = gperr.New("container destroyed") //nolint:errname
|
||||
errCauseReload = errors.New("reloaded")
|
||||
errCauseContainerDestroy = errors.New("container destroyed")
|
||||
)
|
||||
|
||||
const reqTimeout = 3 * time.Second
|
||||
@@ -286,7 +287,7 @@ func NewWatcher(parent task.Parent, r types.Route, cfg *types.IdlewatcherConfig)
|
||||
w.stream = r.Stream()
|
||||
default:
|
||||
p.Close()
|
||||
return nil, w.newWatcherError(gperr.Errorf("unexpected route type: %T", r))
|
||||
return nil, w.newWatcherError(fmt.Errorf("unexpected route type: %T", r))
|
||||
}
|
||||
w.route = r
|
||||
|
||||
@@ -320,12 +321,12 @@ func NewWatcher(parent task.Parent, r types.Route, cfg *types.IdlewatcherConfig)
|
||||
delete(watcherMap, key)
|
||||
watcherMapMu.Unlock()
|
||||
|
||||
if errors.Is(cause, causeReload) {
|
||||
if errors.Is(cause, errCauseReload) {
|
||||
// no log
|
||||
} else if errors.Is(cause, causeContainerDestroy) || errors.Is(cause, task.ErrProgramExiting) || errors.Is(cause, config.ErrConfigChanged) {
|
||||
} else if errors.Is(cause, errCauseContainerDestroy) || errors.Is(cause, task.ErrProgramExiting) || errors.Is(cause, config.ErrConfigChanged) {
|
||||
w.l.Info().Msg("idlewatcher stopped")
|
||||
} else {
|
||||
gperr.LogError("idlewatcher stopped unexpectedly", cause, &w.l)
|
||||
w.l.Err(cause).Msg("idlewatcher stopped unexpectedly")
|
||||
}
|
||||
|
||||
w.idleTicker.Stop()
|
||||
@@ -467,7 +468,7 @@ func (w *Watcher) wakeIfStopped(ctx context.Context) error {
|
||||
defer cancel()
|
||||
p := w.provider.Load()
|
||||
if p == nil {
|
||||
return gperr.Errorf("provider not set")
|
||||
return errors.New("provider not set")
|
||||
}
|
||||
switch state.status {
|
||||
case idlewatcher.ContainerStatusStopped:
|
||||
@@ -477,7 +478,7 @@ func (w *Watcher) wakeIfStopped(ctx context.Context) error {
|
||||
w.sendEvent(WakeEventStarting, w.cfg.ContainerName()+" is unpausing...", nil)
|
||||
return p.ContainerUnpause(ctx)
|
||||
default:
|
||||
return gperr.Errorf("unexpected container status: %s", state.status)
|
||||
return fmt.Errorf("unexpected container status: %s", state.status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,7 +513,7 @@ func (w *Watcher) stopByMethod() error {
|
||||
var err error
|
||||
p := w.provider.Load()
|
||||
if p == nil {
|
||||
return gperr.New("provider not set")
|
||||
return errors.New("provider not set")
|
||||
}
|
||||
switch cfg.StopMethod {
|
||||
case types.ContainerStopMethodPause:
|
||||
@@ -522,7 +523,7 @@ func (w *Watcher) stopByMethod() error {
|
||||
case types.ContainerStopMethodKill:
|
||||
err = p.ContainerKill(ctx, cfg.StopSignal)
|
||||
default:
|
||||
err = w.newWatcherError(gperr.Errorf("unexpected stop method: %q", cfg.StopMethod))
|
||||
err = w.newWatcherError(fmt.Errorf("unexpected stop method: %q", cfg.StopMethod))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -564,7 +565,7 @@ func (w *Watcher) expires() time.Time {
|
||||
func (w *Watcher) watchUntilDestroy() (returnCause error) {
|
||||
p := w.provider.Load()
|
||||
if p == nil {
|
||||
return gperr.Errorf("provider not set")
|
||||
return errors.New("provider not set")
|
||||
}
|
||||
defer p.Close()
|
||||
eventCh, errCh := p.Watch(w.Task().Context())
|
||||
@@ -572,14 +573,14 @@ func (w *Watcher) watchUntilDestroy() (returnCause error) {
|
||||
for {
|
||||
select {
|
||||
case <-w.task.Context().Done():
|
||||
return gperr.Wrap(w.task.FinishCause())
|
||||
return w.task.FinishCause()
|
||||
case err := <-errCh:
|
||||
gperr.LogError("watcher error", err, &w.l)
|
||||
w.l.Err(err).Msg("watcher error")
|
||||
case e := <-eventCh:
|
||||
w.l.Debug().Stringer("action", e.Action).Msg("state changed")
|
||||
switch e.Action {
|
||||
case events.ActionContainerDestroy:
|
||||
return causeContainerDestroy
|
||||
return errCauseContainerDestroy
|
||||
case events.ActionForceReload:
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package accesslog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -58,9 +58,9 @@ var (
|
||||
ReqLoggerFormats = []Format{FormatCommon, FormatCombined, FormatJSON}
|
||||
)
|
||||
|
||||
func (cfg *ConfigBase) Validate() gperr.Error {
|
||||
func (cfg *ConfigBase) Validate() error {
|
||||
if cfg.Path == "" && !cfg.Stdout {
|
||||
return gperr.New("path or stdout is required")
|
||||
return errors.New("path or stdout is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package accesslog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
@@ -11,7 +12,6 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
maxmind "github.com/yusing/godoxy/internal/maxmind/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
ioutils "github.com/yusing/goutils/io"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
"github.com/yusing/goutils/synk"
|
||||
@@ -161,9 +161,9 @@ func (l *fileAccessLogger) Rotate(result *RotateResult) (rotated bool, err error
|
||||
|
||||
func (l *fileAccessLogger) handleErr(err error) {
|
||||
if l.errRateLimiter.Allow() {
|
||||
gperr.LogError("failed to write access log", err, &l.logger)
|
||||
l.logger.Err(err).Msg("failed to write access log")
|
||||
} else {
|
||||
gperr.LogError("too many errors, stopping access log", err, &l.logger)
|
||||
l.logger.Err(err).Msg("too many errors, stopping access log")
|
||||
l.task.Finish(err)
|
||||
}
|
||||
}
|
||||
@@ -234,7 +234,7 @@ func (l *fileAccessLogger) write(data []byte) {
|
||||
if err != nil {
|
||||
l.handleErr(err)
|
||||
} else if n < len(data) {
|
||||
l.handleErr(gperr.Errorf("%w, writing %d bytes, only %d written", io.ErrShortWrite, len(data), n))
|
||||
l.handleErr(fmt.Errorf("%w, writing %d bytes, only %d written", io.ErrShortWrite, len(data), n))
|
||||
}
|
||||
atomic.AddInt64(&l.writeCount, int64(n))
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package accesslog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
)
|
||||
|
||||
@@ -29,7 +30,7 @@ type (
|
||||
} // @name CIDR
|
||||
)
|
||||
|
||||
var ErrInvalidHTTPHeaderFilter = gperr.New("invalid http header filter")
|
||||
var ErrInvalidHTTPHeaderFilter = errors.New("invalid http header filter")
|
||||
|
||||
func (f *LogFilter[T]) CheckKeep(req *http.Request, res *http.Response) bool {
|
||||
if len(f.Values) == 0 {
|
||||
@@ -59,7 +60,7 @@ func (k *HTTPHeader) Parse(v string) error {
|
||||
split = append(split, "")
|
||||
case 2:
|
||||
default:
|
||||
return ErrInvalidHTTPHeaderFilter.Subject(v)
|
||||
return fmt.Errorf("%w: %s", ErrInvalidHTTPHeaderFilter, v)
|
||||
}
|
||||
k.Key = split[0]
|
||||
k.Value = split[1]
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package accesslog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
)
|
||||
|
||||
@@ -15,8 +15,8 @@ type Retention struct {
|
||||
} // @name LogRetention
|
||||
|
||||
var (
|
||||
ErrInvalidSyntax = gperr.New("invalid syntax")
|
||||
ErrZeroValue = gperr.New("zero value")
|
||||
ErrInvalidSyntax = errors.New("invalid syntax")
|
||||
ErrZeroValue = errors.New("zero value")
|
||||
)
|
||||
|
||||
// see back_scanner_test.go#L210 for benchmarks
|
||||
@@ -34,7 +34,7 @@ var defaultChunkSize = 32 * kilobyte
|
||||
func (r *Retention) Parse(v string) (err error) {
|
||||
split := strutils.SplitSpace(v)
|
||||
if len(split) != 2 {
|
||||
return ErrInvalidSyntax.Subject(v)
|
||||
return fmt.Errorf("%w: %s", ErrInvalidSyntax, v)
|
||||
}
|
||||
switch split[0] {
|
||||
case "last":
|
||||
@@ -64,7 +64,7 @@ func (r *Retention) Parse(v string) (err error) {
|
||||
case "GB":
|
||||
r.KeepSize = n * gigabyte
|
||||
default:
|
||||
return ErrInvalidSyntax.Subject("unit " + split[1])
|
||||
return fmt.Errorf("%w: unit %s", ErrInvalidSyntax, split[1])
|
||||
}
|
||||
}
|
||||
if !r.IsValid() {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package accesslog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
@@ -12,7 +14,7 @@ type StatusCodeRange struct {
|
||||
End int
|
||||
} // @name StatusCodeRange
|
||||
|
||||
var ErrInvalidStatusCodeRange = gperr.New("invalid status code range")
|
||||
var ErrInvalidStatusCodeRange = errors.New("invalid status code range")
|
||||
|
||||
func (r *StatusCodeRange) Includes(code int) bool {
|
||||
return r.Start <= code && code <= r.End
|
||||
@@ -25,7 +27,7 @@ func (r *StatusCodeRange) Parse(v string) error {
|
||||
case 1:
|
||||
start, err := strconv.Atoi(split[0])
|
||||
if err != nil {
|
||||
return gperr.Wrap(err)
|
||||
return err
|
||||
}
|
||||
r.Start = start
|
||||
r.End = start
|
||||
@@ -40,7 +42,7 @@ func (r *StatusCodeRange) Parse(v string) error {
|
||||
r.End = end
|
||||
return nil
|
||||
default:
|
||||
return ErrInvalidStatusCodeRange.Subject(v)
|
||||
return fmt.Errorf("%w: %s", ErrInvalidStatusCodeRange, v)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,8 +87,28 @@ func multiWriter(out ...io.Writer) io.Writer {
|
||||
|
||||
func NewLogger(out ...io.Writer) zerolog.Logger {
|
||||
writer := zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
|
||||
w.Out = diodeMultiWriter(out...)
|
||||
if !common.IsTest {
|
||||
w.Out = diodeMultiWriter(out...)
|
||||
} else {
|
||||
w.Out = multiWriter(out...)
|
||||
}
|
||||
w.TimeFormat = timeFmt
|
||||
w.FormatPrepare = func(evt map[string]any) error {
|
||||
// move error field to join message if it's multiline
|
||||
if err, ok := evt[zerolog.ErrorFieldName].(string); ok {
|
||||
if strings.Count(err, "\n") == 0 {
|
||||
return nil
|
||||
}
|
||||
msg, ok := evt[zerolog.MessageFieldName].(string)
|
||||
if ok && msg != "" {
|
||||
evt[zerolog.MessageFieldName] = msg + "\n" + err
|
||||
} else {
|
||||
evt[zerolog.MessageFieldName] = err
|
||||
}
|
||||
delete(evt, zerolog.ErrorFieldName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
w.FormatMessage = func(msgI any) string { // pad spaces for each line
|
||||
if msgI == nil {
|
||||
return ""
|
||||
|
||||
@@ -99,7 +99,7 @@ type Location struct {
|
||||
|
||||
```go
|
||||
// LoadMaxMindDB loads or downloads the MaxMind database.
|
||||
func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) gperr.Error
|
||||
func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) error
|
||||
```
|
||||
|
||||
### Lookup
|
||||
@@ -324,8 +324,8 @@ The maxmind package integrates with:
|
||||
|
||||
```go
|
||||
var (
|
||||
ErrResponseNotOK = gperr.New("response not OK")
|
||||
ErrDownloadFailure = gperr.New("download failure")
|
||||
ErrResponseNotOK = errors.New("response not OK")
|
||||
ErrDownloadFailure = errors.New("download failure")
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/notif"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
@@ -24,7 +23,7 @@ func warnNotConfigured() {
|
||||
})
|
||||
}
|
||||
|
||||
func SetInstance(parent task.Parent, cfg *Config) gperr.Error {
|
||||
func SetInstance(parent task.Parent, cfg *Config) error {
|
||||
newInstance := &MaxMind{Config: cfg}
|
||||
if err := newInstance.LoadMaxMindDB(parent); err != nil {
|
||||
return err
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
maxmind "github.com/yusing/godoxy/internal/maxmind/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
@@ -52,8 +51,8 @@ var httpClient = &http.Client{
|
||||
}
|
||||
|
||||
var (
|
||||
ErrResponseNotOK = gperr.New("response not OK")
|
||||
ErrDownloadFailure = gperr.New("download failure")
|
||||
ErrResponseNotOK = errors.New("response not OK")
|
||||
ErrDownloadFailure = errors.New("download failure")
|
||||
)
|
||||
|
||||
func (cfg *MaxMind) dbPath() string {
|
||||
@@ -74,7 +73,7 @@ func (cfg *MaxMind) dbFilename() string {
|
||||
return "GeoIP2-Country.mmdb"
|
||||
}
|
||||
|
||||
func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) gperr.Error {
|
||||
func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) error {
|
||||
if cfg.Database == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -92,7 +91,7 @@ func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) gperr.Error {
|
||||
// ignore invalid error, just download it again
|
||||
var invalidErr maxminddb.InvalidDatabaseError
|
||||
if !errors.As(err, &invalidErr) {
|
||||
return gperr.Wrap(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
valid = false
|
||||
@@ -101,7 +100,7 @@ func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) gperr.Error {
|
||||
if !valid {
|
||||
cfg.Logger().Info().Msg("MaxMind DB not found/invalid, downloading...")
|
||||
if err = cfg.download(); err != nil {
|
||||
return ErrDownloadFailure.With(err)
|
||||
return fmt.Errorf("%w: %w", ErrDownloadFailure, err)
|
||||
}
|
||||
} else {
|
||||
cfg.Logger().Info().Msg("MaxMind DB loaded")
|
||||
@@ -236,7 +235,7 @@ func (cfg *MaxMind) download() error {
|
||||
// extract .tar.gz and to database
|
||||
err = extractFileFromTarGz(databaseGZ, cfg.dbFilename(), tmpDBPath)
|
||||
if err != nil {
|
||||
return gperr.New("failed to extract database from archive").With(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// test if the downloaded database is valid
|
||||
|
||||
@@ -3,7 +3,6 @@ package maxmind
|
||||
import (
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
)
|
||||
|
||||
@@ -21,7 +20,7 @@ const (
|
||||
MaxMindGeoIP2 DatabaseType = "geoip2"
|
||||
)
|
||||
|
||||
func (cfg *Config) Validate() gperr.Error {
|
||||
func (cfg *Config) Validate() error {
|
||||
if cfg.Database == "" {
|
||||
cfg.Database = MaxMindGeoLite
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package period
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -196,7 +195,7 @@ func (p *Poller[T, AggregateT]) Start(parent task.Parent) {
|
||||
if tickCount%gatherErrsTicks == 0 {
|
||||
errs, ok := p.gatherErrs()
|
||||
if ok {
|
||||
gperr.LogError(fmt.Sprintf("poller %s has encountered %d errors in the last %s:", p.name, len(p.errs), gatherErrsInterval), errs)
|
||||
log.Err(errs).Msgf("poller %s has encountered %d errors in the last %s:", p.name, len(p.errs), gatherErrsInterval)
|
||||
}
|
||||
p.clearErrs()
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ type LoadBalancer struct {
|
||||
func New(cfg *types.LoadBalancerConfig) *LoadBalancer
|
||||
|
||||
// Start the load balancer as a background task
|
||||
func (lb *LoadBalancer) Start(parent task.Parent) gperr.Error
|
||||
func (lb *LoadBalancer) Start(parent task.Parent) error
|
||||
|
||||
// Update configuration dynamically
|
||||
func (lb *LoadBalancer) UpdateConfigIfNeeded(cfg *types.LoadBalancerConfig)
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/bytedance/gopkg/util/xxhash3"
|
||||
"github.com/yusing/godoxy/internal/net/gphttp/middleware"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type ipHash struct {
|
||||
@@ -28,10 +27,10 @@ func (lb *LoadBalancer) newIPHash() impl {
|
||||
if len(lb.Options) == 0 {
|
||||
return impl
|
||||
}
|
||||
var err gperr.Error
|
||||
var err error
|
||||
impl.realIP, err = middleware.RealIP.New(lb.Options)
|
||||
if err != nil {
|
||||
gperr.LogError("invalid real_ip options, ignoring", err, &impl.l)
|
||||
impl.l.Err(err).Msg("invalid real_ip options, ignoring")
|
||||
}
|
||||
return impl
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func New(cfg *types.LoadBalancerConfig) *LoadBalancer {
|
||||
}
|
||||
|
||||
// Start implements task.TaskStarter.
|
||||
func (lb *LoadBalancer) Start(parent task.Parent) gperr.Error {
|
||||
func (lb *LoadBalancer) Start(parent task.Parent) error {
|
||||
lb.startTime = time.Now()
|
||||
lb.task = parent.Subtask("loadbalancer."+lb.Link, true)
|
||||
lb.task.OnCancel("cleanup", func() {
|
||||
@@ -234,7 +234,7 @@ func (lb *LoadBalancer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
if err := errs.Wait().Error(); err != nil {
|
||||
gperr.LogWarn("failed to wake some servers", err, &lb.l)
|
||||
lb.l.Warn().Err(err).Msg("failed to wake some servers")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ The package includes an embedded HTML template (`captcha.html`) that renders the
|
||||
## Error Handling
|
||||
|
||||
```go
|
||||
var ErrCaptchaVerificationFailed = gperr.New("captcha verification failed")
|
||||
var ErrCaptchaVerificationFailed = errors.New("captcha verification failed")
|
||||
|
||||
// Verification errors are logged with request details
|
||||
log.Warn().Err(err).Str("url", r.URL.String()).Str("remote_addr", r.RemoteAddr).Msg("failed to verify captcha")
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type Provider interface {
|
||||
@@ -16,4 +15,4 @@ type Provider interface {
|
||||
FormHTML() string
|
||||
}
|
||||
|
||||
var ErrCaptchaVerificationFailed = gperr.New("captcha verification failed")
|
||||
var ErrCaptchaVerificationFailed = errors.New("captcha verification failed")
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
"github.com/yusing/godoxy/internal/watcher"
|
||||
"github.com/yusing/godoxy/internal/watcher/events"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/fs"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
@@ -93,7 +92,7 @@ func watchDir() {
|
||||
loadContent()
|
||||
}
|
||||
case err := <-errCh:
|
||||
gperr.LogError("error watching error page directory", err)
|
||||
log.Err(err).Msg("error watching error page directory")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"reflect"
|
||||
@@ -10,15 +11,12 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
httputils "github.com/yusing/goutils/http"
|
||||
"github.com/yusing/goutils/http/httpheaders"
|
||||
"github.com/yusing/goutils/http/reverseproxy"
|
||||
)
|
||||
|
||||
type (
|
||||
Error = gperr.Error
|
||||
|
||||
ReverseProxy = reverseproxy.ReverseProxy
|
||||
ProxyRequest = reverseproxy.ProxyRequest
|
||||
|
||||
@@ -87,7 +85,7 @@ func (m *Middleware) setup() {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Middleware) apply(optsRaw OptionsRaw) gperr.Error {
|
||||
func (m *Middleware) apply(optsRaw OptionsRaw) error {
|
||||
if len(optsRaw) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -120,10 +118,10 @@ func (m *Middleware) finalize() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Middleware) New(optsRaw OptionsRaw) (*Middleware, gperr.Error) {
|
||||
func (m *Middleware) New(optsRaw OptionsRaw) (*Middleware, error) {
|
||||
if m.construct == nil { // likely a middleware from compose
|
||||
if len(optsRaw) != 0 {
|
||||
return nil, gperr.New("additional options not allowed for middleware").Subject(m.name)
|
||||
return nil, fmt.Errorf("additional options not allowed for middleware %s", m.name)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -133,7 +131,7 @@ func (m *Middleware) New(optsRaw OptionsRaw) (*Middleware, gperr.Error) {
|
||||
return nil, err
|
||||
}
|
||||
if err := mid.finalize(); err != nil {
|
||||
return nil, gperr.Wrap(err)
|
||||
return nil, err
|
||||
}
|
||||
mid.impl = mid.withCheckBypass()
|
||||
return mid, nil
|
||||
@@ -252,7 +250,7 @@ func (m *Middleware) LogError(req *http.Request) *zerolog.Event {
|
||||
Str("path", req.URL.Path)
|
||||
}
|
||||
|
||||
func PatchReverseProxy(rp *ReverseProxy, middlewaresMap map[string]OptionsRaw) (err gperr.Error) {
|
||||
func PatchReverseProxy(rp *ReverseProxy, middlewaresMap map[string]OptionsRaw) (err error) {
|
||||
var middlewares []*Middleware
|
||||
middlewares, err = compileMiddlewares(middlewaresMap)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
@@ -10,7 +11,7 @@ import (
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
var ErrMissingMiddlewareUse = gperr.New("missing middleware 'use' field")
|
||||
var ErrMissingMiddlewareUse = errors.New("missing middleware 'use' field")
|
||||
|
||||
func BuildMiddlewaresFromComposeFile(filePath string, eb *gperr.Builder) map[string]*Middleware {
|
||||
fileContent, err := os.ReadFile(filePath)
|
||||
@@ -32,7 +33,7 @@ func BuildMiddlewaresFromYAML(source string, data []byte, eb *gperr.Builder) map
|
||||
for name, defs := range rawMap {
|
||||
chain, err := BuildMiddlewareFromChainRaw(name, defs)
|
||||
if err != nil {
|
||||
eb.Add(err.Subject(source))
|
||||
eb.AddSubject(err, source)
|
||||
} else {
|
||||
middlewares[name+"@file"] = chain
|
||||
}
|
||||
@@ -40,7 +41,7 @@ func BuildMiddlewaresFromYAML(source string, data []byte, eb *gperr.Builder) map
|
||||
return middlewares
|
||||
}
|
||||
|
||||
func compileMiddlewares(middlewaresMap map[string]OptionsRaw) ([]*Middleware, gperr.Error) {
|
||||
func compileMiddlewares(middlewaresMap map[string]OptionsRaw) ([]*Middleware, error) {
|
||||
middlewares := make([]*Middleware, 0, len(middlewaresMap))
|
||||
|
||||
var errs gperr.Builder
|
||||
@@ -68,7 +69,7 @@ func compileMiddlewares(middlewaresMap map[string]OptionsRaw) ([]*Middleware, gp
|
||||
return middlewares, errs.Error()
|
||||
}
|
||||
|
||||
func BuildMiddlewareFromMap(name string, middlewaresMap map[string]OptionsRaw) (*Middleware, gperr.Error) {
|
||||
func BuildMiddlewareFromMap(name string, middlewaresMap map[string]OptionsRaw) (*Middleware, error) {
|
||||
compiled, err := compileMiddlewares(middlewaresMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -77,7 +78,7 @@ func BuildMiddlewareFromMap(name string, middlewaresMap map[string]OptionsRaw) (
|
||||
}
|
||||
|
||||
// TODO: check conflict or duplicates.
|
||||
func BuildMiddlewareFromChainRaw(name string, defs []map[string]any) (*Middleware, gperr.Error) {
|
||||
func BuildMiddlewareFromChainRaw(name string, defs []map[string]any) (*Middleware, error) {
|
||||
var chainErr gperr.Builder
|
||||
chain := make([]*Middleware, 0, len(defs))
|
||||
for i, def := range defs {
|
||||
|
||||
@@ -2,6 +2,7 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
@@ -47,7 +48,7 @@ func (m *middlewareChain) modifyResponse(resp *http.Response) error {
|
||||
}
|
||||
for i, mr := range m.modResps {
|
||||
if err := mr.modifyResponse(resp); err != nil {
|
||||
return gperr.Wrap(err).Subjectf("%d", i)
|
||||
return gperr.PrependSubject(err, strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -44,15 +44,14 @@ var allMiddlewares = map[string]*Middleware{
|
||||
}
|
||||
|
||||
var (
|
||||
ErrUnknownMiddleware = gperr.New("unknown middleware")
|
||||
ErrMiddlewareAlreadyExists = gperr.New("middleware with the same name already exists")
|
||||
ErrUnknownMiddleware = errors.New("unknown middleware")
|
||||
ErrMiddlewareAlreadyExists = errors.New("middleware with the same name already exists")
|
||||
)
|
||||
|
||||
func Get(name string) (*Middleware, Error) {
|
||||
func Get(name string) (*Middleware, error) {
|
||||
middleware, ok := allMiddlewares[strutils.ToLowerNoSnake(name)]
|
||||
if !ok {
|
||||
return nil, ErrUnknownMiddleware.
|
||||
Subject(name).
|
||||
return nil, gperr.PrependSubject(ErrUnknownMiddleware, name).
|
||||
With(gperr.DoYouMeanField(name, allMiddlewares))
|
||||
}
|
||||
return middleware, nil
|
||||
@@ -63,7 +62,7 @@ func All() map[string]*Middleware {
|
||||
}
|
||||
|
||||
func LoadComposeFiles() {
|
||||
errs := gperr.NewBuilder("middleware compile errors")
|
||||
var errs gperr.Builder
|
||||
middlewareDefs, err := fsutils.ListFiles(common.MiddlewareComposeBasePath, 0)
|
||||
if err != nil {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
@@ -81,7 +80,7 @@ func LoadComposeFiles() {
|
||||
for name, m := range mws {
|
||||
name = strutils.ToLowerNoSnake(name)
|
||||
if _, ok := allMiddlewares[name]; ok {
|
||||
errs.Add(ErrMiddlewareAlreadyExists.Subject(name))
|
||||
errs.AddSubject(ErrMiddlewareAlreadyExists, name)
|
||||
continue
|
||||
}
|
||||
allMiddlewares[name] = m
|
||||
@@ -111,6 +110,6 @@ func LoadComposeFiles() {
|
||||
}
|
||||
}
|
||||
if errs.HasError() {
|
||||
gperr.LogError(errs.About(), errs.Error())
|
||||
log.Err(errs.Error()).Msg("middleware compile errors")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/yusing/godoxy/internal/auth"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type oidcMiddleware struct {
|
||||
@@ -28,7 +27,7 @@ var OIDC = NewMiddleware[oidcMiddleware]()
|
||||
|
||||
func (amw *oidcMiddleware) finalize() error {
|
||||
if !auth.IsOIDCEnabled() {
|
||||
return gperr.New("OIDC not enabled but OIDC middleware is used")
|
||||
return errors.New("OIDC not enabled but OIDC middleware is used")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/http/reverseproxy"
|
||||
)
|
||||
|
||||
@@ -121,7 +120,7 @@ func (args *testArgs) bodyReader() io.Reader {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newMiddlewareTest(middleware *Middleware, args *testArgs) (*TestResult, gperr.Error) {
|
||||
func newMiddlewareTest(middleware *Middleware, args *testArgs) (*TestResult, error) {
|
||||
if args == nil {
|
||||
args = new(testArgs)
|
||||
}
|
||||
@@ -135,7 +134,7 @@ func newMiddlewareTest(middleware *Middleware, args *testArgs) (*TestResult, gpe
|
||||
return newMiddlewaresTest([]*Middleware{mid}, args)
|
||||
}
|
||||
|
||||
func newMiddlewaresTest(middlewares []*Middleware, args *testArgs) (*TestResult, gperr.Error) {
|
||||
func newMiddlewaresTest(middlewares []*Middleware, args *testArgs) (*TestResult, error) {
|
||||
if args == nil {
|
||||
args = new(testArgs)
|
||||
}
|
||||
@@ -160,7 +159,7 @@ func newMiddlewaresTest(middlewares []*Middleware, args *testArgs) (*TestResult,
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, gperr.Wrap(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TestResult{
|
||||
|
||||
@@ -2,6 +2,7 @@ package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -66,7 +67,7 @@ func (m *themed) finalize() error {
|
||||
m.m.HTML += buf.String()
|
||||
}
|
||||
if m.CSS != "" && m.Theme != "" {
|
||||
return gperr.New("css and theme are mutually exclusive")
|
||||
return errors.New("css and theme are mutually exclusive")
|
||||
}
|
||||
// credit: https://hackcss.egoist.dev
|
||||
if m.Theme != "" {
|
||||
@@ -78,7 +79,7 @@ func (m *themed) finalize() error {
|
||||
case SolarizedDarkTheme:
|
||||
m.m.HTML += wrapStyleTag(solarizedDarkModeCSS)
|
||||
default:
|
||||
return gperr.New("invalid theme").Subject(string(m.Theme))
|
||||
return gperr.PrependSubject(errors.New("invalid theme"), m.Theme)
|
||||
}
|
||||
}
|
||||
if m.CSS != "" {
|
||||
|
||||
@@ -304,9 +304,9 @@ The notif package integrates with:
|
||||
|
||||
```go
|
||||
var (
|
||||
ErrMissingNotifProvider = gperr.New("missing notification provider")
|
||||
ErrInvalidNotifProviderType = gperr.New("invalid notification provider type")
|
||||
ErrUnknownNotifProvider = gperr.New("unknown notification provider")
|
||||
ErrMissingNotifProvider = errors.New("missing notification provider")
|
||||
ErrInvalidNotifProviderType = errors.New("invalid notification provider type")
|
||||
ErrUnknownNotifProvider = errors.New("unknown notification provider")
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package notif
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -23,13 +24,13 @@ func (e rawError) Error() string {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrMissingToken = gperr.New("token is required")
|
||||
ErrURLMissingScheme = gperr.New("url missing scheme, expect 'http://' or 'https://'")
|
||||
ErrUnknownError = gperr.New("unknown error")
|
||||
ErrMissingToken = errors.New("token is required")
|
||||
ErrURLMissingScheme = errors.New("url missing scheme, expect 'http://' or 'https://'")
|
||||
ErrUnknownError = errors.New("unknown error")
|
||||
)
|
||||
|
||||
// Validate implements the utils.CustomValidator interface.
|
||||
func (base *ProviderBase) Validate() gperr.Error {
|
||||
func (base *ProviderBase) Validate() error {
|
||||
switch base.Format {
|
||||
case "":
|
||||
base.Format = LogFormatMarkdown
|
||||
@@ -48,7 +49,7 @@ func (base *ProviderBase) Validate() gperr.Error {
|
||||
}
|
||||
u, err := url.Parse(base.URL)
|
||||
if err != nil {
|
||||
return gperr.Wrap(err)
|
||||
return err
|
||||
}
|
||||
base.URL = u.String()
|
||||
return nil
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package notif
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
@@ -11,13 +14,13 @@ type NotificationConfig struct {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrMissingNotifProvider = gperr.New("missing notification provider")
|
||||
ErrInvalidNotifProviderType = gperr.New("invalid notification provider type")
|
||||
ErrUnknownNotifProvider = gperr.New("unknown notification provider")
|
||||
ErrMissingNotifProvider = errors.New("missing notification provider")
|
||||
ErrInvalidNotifProviderType = errors.New("invalid notification provider type")
|
||||
ErrUnknownNotifProvider = errors.New("unknown notification provider")
|
||||
)
|
||||
|
||||
// UnmarshalMap implements MapUnmarshaler.
|
||||
func (cfg *NotificationConfig) UnmarshalMap(m map[string]any) (err gperr.Error) {
|
||||
func (cfg *NotificationConfig) UnmarshalMap(m map[string]any) (err error) {
|
||||
// extract provider name
|
||||
providerName := m["provider"]
|
||||
switch providerName := providerName.(type) {
|
||||
@@ -41,9 +44,8 @@ func (cfg *NotificationConfig) UnmarshalMap(m map[string]any) (err gperr.Error)
|
||||
case ProviderNtfy:
|
||||
cfg.Provider = &Ntfy{}
|
||||
default:
|
||||
return ErrUnknownNotifProvider.
|
||||
Subject(cfg.ProviderName).
|
||||
Withf("expect %s or %s", ProviderWebhook, ProviderGotify)
|
||||
return gperr.PrependSubject(ErrUnknownNotifProvider, cfg.ProviderName).
|
||||
Withf("expect %s", strings.Join(AvailableProviders, ", "))
|
||||
}
|
||||
|
||||
return serialization.MapUnmarshalValidate(m, cfg.Provider)
|
||||
|
||||
@@ -19,14 +19,15 @@ type (
|
||||
|
||||
const gotifyMsgEndpoint = "/message"
|
||||
|
||||
func (client *GotifyClient) Validate() gperr.Error {
|
||||
func (client *GotifyClient) Validate() error {
|
||||
var errs gperr.Builder
|
||||
if err := client.ProviderBase.Validate(); err != nil {
|
||||
return err
|
||||
errs.Add(err)
|
||||
}
|
||||
if client.Token == "" {
|
||||
return gperr.New("token is required")
|
||||
errs.Adds("token is required")
|
||||
}
|
||||
return nil
|
||||
return errs.Error()
|
||||
}
|
||||
|
||||
func (client *GotifyClient) GetURL() string {
|
||||
|
||||
@@ -14,20 +14,21 @@ type Ntfy struct {
|
||||
}
|
||||
|
||||
// Validate implements the utils.CustomValidator interface.
|
||||
func (n *Ntfy) Validate() gperr.Error {
|
||||
func (n *Ntfy) Validate() error {
|
||||
var errs gperr.Builder
|
||||
if err := n.ProviderBase.Validate(); err != nil {
|
||||
return err
|
||||
errs.Add(err)
|
||||
}
|
||||
if n.URL == "" {
|
||||
return gperr.New("url is required")
|
||||
errs.Adds("url is required")
|
||||
}
|
||||
if n.Topic == "" {
|
||||
return gperr.New("topic is required")
|
||||
errs.Adds("topic is required")
|
||||
}
|
||||
if n.Topic[0] == '/' {
|
||||
return gperr.New("topic should not start with a slash")
|
||||
if n.Topic != "" && n.Topic[0] == '/' {
|
||||
errs.Adds("topic should not start with a slash")
|
||||
}
|
||||
return nil
|
||||
return errs.Error()
|
||||
}
|
||||
|
||||
// GetURL implements Provider.
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -27,7 +26,7 @@ type (
|
||||
|
||||
fmtError(respBody io.Reader) error
|
||||
}
|
||||
ProviderCreateFunc func(map[string]any) (Provider, gperr.Error)
|
||||
ProviderCreateFunc func(map[string]any) (Provider, error)
|
||||
ProviderConfig map[string]any
|
||||
)
|
||||
|
||||
@@ -37,6 +36,8 @@ const (
|
||||
ProviderWebhook = "webhook"
|
||||
)
|
||||
|
||||
var AvailableProviders = []string{ProviderGotify, ProviderNtfy, ProviderWebhook}
|
||||
|
||||
func (msg *LogMessage) notify(ctx context.Context, provider Provider) error {
|
||||
body, err := provider.MarshalMessage(msg)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package notif
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -26,9 +27,11 @@ var webhookTemplates = map[string]string{
|
||||
"discord": discordPayload,
|
||||
}
|
||||
|
||||
func (webhook *Webhook) Validate() gperr.Error {
|
||||
if err := webhook.ProviderBase.Validate(); err != nil && !err.Is(ErrMissingToken) {
|
||||
return err
|
||||
func (webhook *Webhook) Validate() error {
|
||||
var errs gperr.Builder
|
||||
|
||||
if err := webhook.ProviderBase.Validate(); err != nil && !errors.Is(err, ErrMissingToken) {
|
||||
errs.Add(err)
|
||||
}
|
||||
|
||||
switch webhook.MIMEType {
|
||||
@@ -36,18 +39,18 @@ func (webhook *Webhook) Validate() gperr.Error {
|
||||
webhook.MIMEType = MimeTypeJSON
|
||||
case MimeTypeJSON, MimeTypeForm, MimeTypeText:
|
||||
default:
|
||||
return gperr.Errorf("invalid mime_type, expect %s", strings.Join([]string{"empty", MimeTypeJSON, MimeTypeForm, MimeTypeText}, ", "))
|
||||
errs.Addf("invalid mime_type, expect %s", strings.Join([]string{"empty", MimeTypeJSON, MimeTypeForm, MimeTypeText}, ", "))
|
||||
}
|
||||
|
||||
switch webhook.Template {
|
||||
case "":
|
||||
if webhook.MIMEType == MimeTypeJSON {
|
||||
if !validateJSONPayload(webhook.Payload) {
|
||||
return gperr.New("invalid payload, expect valid JSON")
|
||||
errs.Adds("invalid payload, expect valid JSON")
|
||||
}
|
||||
}
|
||||
if webhook.Payload == "" {
|
||||
return gperr.New("invalid payload, expect non-empty")
|
||||
errs.Adds("invalid payload, expect non-empty")
|
||||
}
|
||||
case "discord":
|
||||
webhook.ColorMode = "dec"
|
||||
@@ -57,7 +60,7 @@ func (webhook *Webhook) Validate() gperr.Error {
|
||||
webhook.Payload = discordPayload
|
||||
}
|
||||
default:
|
||||
return gperr.New("invalid template, expect empty or 'discord'")
|
||||
errs.Adds("invalid template, expect empty or 'discord'")
|
||||
}
|
||||
|
||||
switch webhook.Method {
|
||||
@@ -65,7 +68,7 @@ func (webhook *Webhook) Validate() gperr.Error {
|
||||
webhook.Method = http.MethodPost
|
||||
case http.MethodGet, http.MethodPost, http.MethodPut:
|
||||
default:
|
||||
return gperr.New("invalid method, expect empty, 'GET', 'POST' or 'PUT'")
|
||||
errs.Adds("invalid method, expect empty, 'GET', 'POST' or 'PUT'")
|
||||
}
|
||||
|
||||
switch webhook.ColorMode {
|
||||
@@ -73,10 +76,10 @@ func (webhook *Webhook) Validate() gperr.Error {
|
||||
webhook.ColorMode = "hex"
|
||||
case "hex", "dec":
|
||||
default:
|
||||
return gperr.New("invalid color_mode, expect empty, 'hex' or 'dec'")
|
||||
errs.Adds("invalid color_mode, expect empty, 'hex' or 'dec'")
|
||||
}
|
||||
|
||||
return nil
|
||||
return errs.Error()
|
||||
}
|
||||
|
||||
// GetMethod implements Provider.
|
||||
|
||||
@@ -115,7 +115,7 @@ type NodeConfig struct {
|
||||
|
||||
```go
|
||||
// Init initializes the Proxmox client.
|
||||
func (c *Config) Init(ctx context.Context) gperr.Error
|
||||
func (c *Config) Init(ctx context.Context) error
|
||||
|
||||
// Client returns the Proxmox client.
|
||||
func (c *Config) Client() *Client
|
||||
@@ -460,7 +460,7 @@ if r.Idlewatcher != nil && r.Idlewatcher.Proxmox != nil {
|
||||
|
||||
node, ok := proxmox.Nodes.Get(node)
|
||||
if !ok {
|
||||
return gperr.Errorf("proxmox node %s not found", node)
|
||||
return fmt.Errorf("proxmox node %s not found", node)
|
||||
}
|
||||
|
||||
// Get container IPs
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
"github.com/luthermonson/go-proxmox"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/net/gphttp"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
)
|
||||
|
||||
@@ -44,7 +44,7 @@ func (c *Config) Client() *Client {
|
||||
return c.client
|
||||
}
|
||||
|
||||
func (c *Config) Init(ctx context.Context) gperr.Error {
|
||||
func (c *Config) Init(ctx context.Context) error {
|
||||
var tr *http.Transport
|
||||
if c.NoTLSVerify {
|
||||
// user specified
|
||||
@@ -87,15 +87,15 @@ func (c *Config) Init(ctx context.Context) gperr.Error {
|
||||
if useCredentials {
|
||||
err := c.client.CreateSession(initCtx)
|
||||
if err != nil {
|
||||
return gperr.New("failed to create session").With(err)
|
||||
return fmt.Errorf("failed to create session: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.client.UpdateClusterInfo(initCtx); err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
return gperr.New("timeout fetching proxmox cluster info")
|
||||
return fmt.Errorf("timeout fetching proxmox cluster info: %w", err)
|
||||
}
|
||||
return gperr.New("failed to fetch proxmox cluster info").With(err)
|
||||
return fmt.Errorf("failed to fetch proxmox cluster info: %w", err)
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ type Node struct {
|
||||
}
|
||||
|
||||
// Validate implements the serialization.CustomValidator interface.
|
||||
func (n *NodeConfig) Validate() gperr.Error {
|
||||
func (n *NodeConfig) Validate() error {
|
||||
var errs gperr.Builder
|
||||
for i, service := range n.Services {
|
||||
if err := checkValidInput(service); err != nil {
|
||||
|
||||
@@ -93,8 +93,8 @@ const (
|
||||
|
||||
```go
|
||||
// Validation and lifecycle
|
||||
func (r *Route) Validate() gperr.Error
|
||||
func (r *Route) Start(parent task.Parent) gperr.Error
|
||||
func (r *Route) Validate() error
|
||||
func (r *Route) Start(parent task.Parent) error
|
||||
func (r *Route) Finish(reason any)
|
||||
func (r *Route) Started() <-chan struct{}
|
||||
|
||||
@@ -119,8 +119,8 @@ func (r *Route) UseHealthCheck() bool
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Route {
|
||||
+Validate() gperr.Error
|
||||
+Start(parent) gperr.Error
|
||||
+Validate() error
|
||||
+Start(parent) error
|
||||
+Finish(reason)
|
||||
+Started() <-chan struct#123;#125;
|
||||
}
|
||||
|
||||
@@ -2,16 +2,16 @@ package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
// checkExists checks if the route already exists in the entrypoint.
|
||||
//
|
||||
// Context must be passed from the parent task that carries the entrypoint value.
|
||||
func checkExists(ctx context.Context, r types.Route) gperr.Error {
|
||||
func checkExists(ctx context.Context, r types.Route) error {
|
||||
if r.UseLoadBalance() { // skip checking for load balanced routes
|
||||
return nil
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func checkExists(ctx context.Context, r types.Route) gperr.Error {
|
||||
existing, ok = entrypoint.FromCtx(ctx).StreamRoutes().Get(r.Key())
|
||||
}
|
||||
if ok {
|
||||
return gperr.Errorf("route already exists: from provider %s and %s", existing.ProviderName(), r.ProviderName())
|
||||
return fmt.Errorf("route already exists: from provider %s and %s", existing.ProviderName(), r.ProviderName())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
@@ -50,12 +51,12 @@ func handler(root string, spa bool, index string) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func NewFileServer(base *Route) (*FileServer, gperr.Error) {
|
||||
func NewFileServer(base *Route) (*FileServer, error) {
|
||||
s := &FileServer{Route: base}
|
||||
|
||||
s.Root = filepath.Clean(s.Root)
|
||||
if !path.IsAbs(s.Root) {
|
||||
return nil, gperr.New("`root` must be an absolute path")
|
||||
return nil, errors.New("`root` must be an absolute path")
|
||||
}
|
||||
|
||||
if s.Index == "" {
|
||||
@@ -77,7 +78,7 @@ func NewFileServer(base *Route) (*FileServer, gperr.Error) {
|
||||
}
|
||||
|
||||
// Start implements task.TaskStarter.
|
||||
func (s *FileServer) Start(parent task.Parent) gperr.Error {
|
||||
func (s *FileServer) Start(parent task.Parent) error {
|
||||
s.task = parent.Subtask("fileserver."+s.Name(), false)
|
||||
|
||||
pathPatterns := s.PathPatterns
|
||||
@@ -109,7 +110,7 @@ func (s *FileServer) Start(parent task.Parent) gperr.Error {
|
||||
s.accessLogger, err = accesslog.NewAccessLogger(s.task, s.AccessLog)
|
||||
if err != nil {
|
||||
s.task.Finish(err)
|
||||
return gperr.Wrap(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,14 +128,14 @@ func (s *FileServer) Start(parent task.Parent) gperr.Error {
|
||||
|
||||
ep := entrypoint.FromCtx(parent.Context())
|
||||
if ep == nil {
|
||||
err := gperr.New("entrypoint not initialized")
|
||||
err := errors.New("entrypoint not initialized")
|
||||
s.task.Finish(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ep.StartAddRoute(s); err != nil {
|
||||
s.task.Finish(err)
|
||||
return gperr.Wrap(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ type ProviderImpl interface {
|
||||
fmt.Stringer
|
||||
ShortName() string
|
||||
IsExplicitOnly() bool
|
||||
loadRoutesImpl() (route.Routes, gperr.Error)
|
||||
loadRoutesImpl() (route.Routes, error)
|
||||
NewWatcher() W.Watcher
|
||||
Logger() *zerolog.Logger
|
||||
}
|
||||
@@ -62,8 +62,8 @@ func NewAgentProvider(cfg *agent.AgentConfig) *Provider
|
||||
|
||||
```go
|
||||
func (p *Provider) GetType() provider.Type
|
||||
func (p *Provider) Start(parent task.Parent) gperr.Error
|
||||
func (p *Provider) LoadRoutes() gperr.Error
|
||||
func (p *Provider) Start(parent task.Parent) error
|
||||
func (p *Provider) LoadRoutes() error
|
||||
func (p *Provider) IterRoutes(yield func(string, types.Route) bool)
|
||||
func (p *Provider) GetRoute(alias string) (types.Route, bool)
|
||||
func (p *Provider) FindService(project, service string) (types.Route, bool)
|
||||
@@ -80,8 +80,8 @@ classDiagram
|
||||
+t provider.Type
|
||||
+routes route.Routes
|
||||
+watcher W.Watcher
|
||||
+Start(parent) gperr.Error
|
||||
+LoadRoutes() gperr.Error
|
||||
+Start(parent) error
|
||||
+LoadRoutes() error
|
||||
+IterRoutes(yield)
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ classDiagram
|
||||
+String() string
|
||||
+ShortName() string
|
||||
+IsExplicitOnly() bool
|
||||
+loadRoutesImpl() (route.Routes, gperr.Error)
|
||||
+loadRoutesImpl() (route.Routes, error)
|
||||
+NewWatcher() W.Watcher
|
||||
+Logger() *zerolog.Logger
|
||||
}
|
||||
@@ -99,20 +99,20 @@ classDiagram
|
||||
+name string
|
||||
+dockerCfg types.DockerProviderConfig
|
||||
+ShortName() string
|
||||
+loadRoutesImpl() (route.Routes, gperr.Error)
|
||||
+loadRoutesImpl() (route.Routes, error)
|
||||
}
|
||||
|
||||
class FileProviderImpl {
|
||||
+filename string
|
||||
+ShortName() string
|
||||
+loadRoutesImpl() (route.Routes, gperr.Error)
|
||||
+loadRoutesImpl() (route.Routes, error)
|
||||
}
|
||||
|
||||
class AgentProviderImpl {
|
||||
+*agent.AgentConfig
|
||||
+docker DockerProviderImpl
|
||||
+ShortName() string
|
||||
+loadRoutesImpl() (route.Routes, gperr.Error)
|
||||
+loadRoutesImpl() (route.Routes, error)
|
||||
}
|
||||
|
||||
Provider --> ProviderImpl : wraps
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
"github.com/yusing/godoxy/internal/route"
|
||||
"github.com/yusing/godoxy/internal/watcher"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type AgentProvider struct {
|
||||
@@ -25,7 +24,7 @@ func (p *AgentProvider) IsExplicitOnly() bool {
|
||||
return p.docker.IsExplicitOnly()
|
||||
}
|
||||
|
||||
func (p *AgentProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
func (p *AgentProvider) loadRoutesImpl() (route.Routes, error) {
|
||||
return p.docker.loadRoutesImpl()
|
||||
}
|
||||
|
||||
|
||||
@@ -58,13 +58,13 @@ func (p *DockerProvider) NewWatcher() watcher.Watcher {
|
||||
return watcher.NewDockerWatcher(p.dockerCfg)
|
||||
}
|
||||
|
||||
func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
func (p *DockerProvider) loadRoutesImpl() (route.Routes, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
containers, err := docker.ListContainers(ctx, p.dockerCfg)
|
||||
if err != nil {
|
||||
return nil, gperr.Wrap(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
errs := gperr.NewBuilder("")
|
||||
@@ -74,21 +74,21 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
container := docker.FromDocker(&c, p.dockerCfg)
|
||||
|
||||
if container.Errors != nil {
|
||||
errs.Add(gperr.PrependSubject(container.ContainerName, container.Errors))
|
||||
errs.AddSubject(container.Errors, container.ContainerName)
|
||||
continue
|
||||
}
|
||||
|
||||
if container.IsHostNetworkMode {
|
||||
err := docker.UpdatePorts(ctx, container)
|
||||
if err != nil {
|
||||
errs.Add(gperr.PrependSubject(container.ContainerName, err))
|
||||
errs.AddSubject(err, container.ContainerName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
newEntries, err := p.routesFromContainerLabels(container)
|
||||
if err != nil {
|
||||
errs.Add(err.Subject(container.ContainerName))
|
||||
errs.AddSubject(err, container.ContainerName)
|
||||
}
|
||||
for k, v := range newEntries {
|
||||
if conflict, ok := routes[k]; ok {
|
||||
@@ -97,7 +97,7 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
Addf("container %s", container.ContainerName).
|
||||
Addf("conflicting container %s", conflict.Container.ContainerName)
|
||||
if conflict.ShouldExclude() || v.ShouldExclude() {
|
||||
gperr.LogWarn("skipping conflicting route", err)
|
||||
log.Warn().Err(err).Msg("skipping conflicting route")
|
||||
} else {
|
||||
errs.Add(err)
|
||||
}
|
||||
@@ -112,7 +112,7 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
|
||||
// Returns a list of proxy entries for a container.
|
||||
// Always non-nil.
|
||||
func (p *DockerProvider) routesFromContainerLabels(container *types.Container) (route.Routes, gperr.Error) {
|
||||
func (p *DockerProvider) routesFromContainerLabels(container *types.Container) (route.Routes, error) {
|
||||
if !container.IsExplicit && p.IsExplicitOnly() {
|
||||
return make(route.Routes, 0), nil
|
||||
}
|
||||
@@ -150,7 +150,7 @@ func (p *DockerProvider) routesFromContainerLabels(container *types.Container) (
|
||||
panic(fmt.Errorf("invalid entry map type %T", entryMapAny))
|
||||
}
|
||||
if err := yaml.Unmarshal([]byte(yamlStr), &entryMap); err != nil {
|
||||
errs.Add(gperr.Wrap(err).Subject(alias))
|
||||
errs.AddSubject(err, alias)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -185,7 +185,7 @@ func (p *DockerProvider) routesFromContainerLabels(container *types.Container) (
|
||||
// deserialize map into entry object
|
||||
err := serialization.MapUnmarshalValidate(entryMap, r)
|
||||
if err != nil {
|
||||
errs.Add(err.Subject(alias))
|
||||
errs.AddSubject(err, alias)
|
||||
} else {
|
||||
routes[alias] = r
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ func (handler *EventHandler) match(event watcher.Event, route *route.Route) bool
|
||||
func (handler *EventHandler) Add(parent task.Parent, route *route.Route) {
|
||||
err := handler.provider.startRoute(parent, route)
|
||||
if err != nil {
|
||||
handler.errs.Add(err.Subject("add"))
|
||||
handler.errs.AddSubjectf(err, "add")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ func (handler *EventHandler) Update(parent task.Parent, oldRoute *route.Route, n
|
||||
oldRoute.FinishAndWait("route update")
|
||||
err := handler.provider.startRoute(parent, newRoute)
|
||||
if err != nil {
|
||||
handler.errs.Add(err.Subject("update"))
|
||||
handler.errs.AddSubjectf(err, "update")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/yusing/godoxy/internal/route"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
W "github.com/yusing/godoxy/internal/watcher"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type FileProvider struct {
|
||||
@@ -34,7 +33,7 @@ func FileProviderImpl(filename string) (ProviderImpl, error) {
|
||||
return impl, nil
|
||||
}
|
||||
|
||||
func removeXPrefix(m map[string]any) gperr.Error {
|
||||
func removeXPrefix(m map[string]any) error {
|
||||
for alias := range m {
|
||||
if strings.HasPrefix(alias, "x-") {
|
||||
delete(m, alias)
|
||||
@@ -43,12 +42,12 @@ func removeXPrefix(m map[string]any) gperr.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validate(data []byte) (routes route.Routes, err gperr.Error) {
|
||||
func validate(data []byte) (routes route.Routes, err error) {
|
||||
err = serialization.UnmarshalValidate(data, &routes, yaml.Unmarshal, removeXPrefix)
|
||||
return routes, err
|
||||
}
|
||||
|
||||
func Validate(data []byte) (err gperr.Error) {
|
||||
func Validate(data []byte) (err error) {
|
||||
_, err = validate(data)
|
||||
return err
|
||||
}
|
||||
@@ -69,16 +68,16 @@ func (p *FileProvider) Logger() *zerolog.Logger {
|
||||
return &p.l
|
||||
}
|
||||
|
||||
func (p *FileProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
func (p *FileProvider) loadRoutesImpl() (route.Routes, error) {
|
||||
data, err := os.ReadFile(p.path)
|
||||
if err != nil {
|
||||
return nil, gperr.Wrap(err)
|
||||
return nil, err
|
||||
}
|
||||
routes, err := validate(data)
|
||||
if err != nil && len(routes) == 0 {
|
||||
return nil, gperr.Wrap(err)
|
||||
return nil, err
|
||||
}
|
||||
return routes, gperr.Wrap(err)
|
||||
return routes, err
|
||||
}
|
||||
|
||||
func (p *FileProvider) NewWatcher() W.Watcher {
|
||||
|
||||
@@ -34,7 +34,7 @@ type (
|
||||
fmt.Stringer
|
||||
ShortName() string
|
||||
IsExplicitOnly() bool
|
||||
loadRoutesImpl() (route.Routes, gperr.Error)
|
||||
loadRoutesImpl() (route.Routes, error)
|
||||
NewWatcher() W.Watcher
|
||||
Logger() *zerolog.Logger
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func (p *Provider) MarshalText() ([]byte, error) {
|
||||
}
|
||||
|
||||
// Start implements task.TaskStarter.
|
||||
func (p *Provider) Start(parent task.Parent) gperr.Error {
|
||||
func (p *Provider) Start(parent task.Parent) error {
|
||||
errs := gperr.NewGroup("routes error")
|
||||
|
||||
t := parent.Subtask("provider."+p.String(), false)
|
||||
@@ -124,8 +124,8 @@ func (p *Provider) Start(parent task.Parent) gperr.Error {
|
||||
handler.Handle(t, events)
|
||||
handler.Log()
|
||||
},
|
||||
func(err gperr.Error) {
|
||||
gperr.LogError("event error", err, p.Logger())
|
||||
func(err error) {
|
||||
p.Logger().Err(err).Msg("event error")
|
||||
},
|
||||
)
|
||||
eventQueue.Start(p.watcher.Events(t.Context()))
|
||||
@@ -136,7 +136,7 @@ func (p *Provider) Start(parent task.Parent) gperr.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) LoadRoutes() (err gperr.Error) {
|
||||
func (p *Provider) LoadRoutes() (err error) {
|
||||
p.routes, err = p.loadRoutes()
|
||||
return err
|
||||
}
|
||||
@@ -188,7 +188,7 @@ func (p *Provider) GetRoute(alias string) (types.Route, bool) {
|
||||
return r.Impl(), true
|
||||
}
|
||||
|
||||
func (p *Provider) loadRoutes() (routes route.Routes, err gperr.Error) {
|
||||
func (p *Provider) loadRoutes() (routes route.Routes, err error) {
|
||||
routes, err = p.loadRoutesImpl()
|
||||
if err != nil && len(routes) == 0 {
|
||||
return route.Routes{}, err
|
||||
@@ -201,7 +201,7 @@ func (p *Provider) loadRoutes() (routes route.Routes, err gperr.Error) {
|
||||
r.Alias = alias
|
||||
r.SetProvider(p)
|
||||
if err := r.Validate(); err != nil {
|
||||
errs.Add(err.Subject(alias))
|
||||
errs.AddSubject(err, alias)
|
||||
delete(routes, alias)
|
||||
continue
|
||||
}
|
||||
@@ -210,11 +210,11 @@ func (p *Provider) loadRoutes() (routes route.Routes, err gperr.Error) {
|
||||
return routes, errs.Error()
|
||||
}
|
||||
|
||||
func (p *Provider) startRoute(parent task.Parent, r *route.Route) gperr.Error {
|
||||
func (p *Provider) startRoute(parent task.Parent, r *route.Route) error {
|
||||
err := r.Start(parent)
|
||||
if err != nil {
|
||||
p.lockDeleteRoute(r.Alias)
|
||||
return err.Subject(r.Alias)
|
||||
return gperr.PrependSubject(err, r.Alias)
|
||||
}
|
||||
|
||||
p.lockAddRoute(r)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
"github.com/yusing/godoxy/agent/pkg/agentproxy"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
@@ -16,7 +18,6 @@ import (
|
||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||
route "github.com/yusing/godoxy/internal/route/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/http/reverseproxy"
|
||||
"github.com/yusing/goutils/task"
|
||||
"github.com/yusing/goutils/version"
|
||||
@@ -34,7 +35,7 @@ var _ types.ReverseProxyRoute = (*ReveseProxyRoute)(nil)
|
||||
|
||||
// var globalMux = http.NewServeMux() // TODO: support regex subdomain matching.
|
||||
|
||||
func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, gperr.Error) {
|
||||
func NewReverseProxyRoute(base *Route) (*ReveseProxyRoute, error) {
|
||||
httpConfig := base.HTTPConfig
|
||||
proxyURL := base.ProxyURL
|
||||
|
||||
@@ -123,7 +124,7 @@ func (r *ReveseProxyRoute) ReverseProxy() *reverseproxy.ReverseProxy {
|
||||
}
|
||||
|
||||
// Start implements task.TaskStarter.
|
||||
func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
||||
func (r *ReveseProxyRoute) Start(parent task.Parent) error {
|
||||
r.task = parent.Subtask("http."+r.Name(), false)
|
||||
|
||||
switch {
|
||||
@@ -131,7 +132,7 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
||||
waker, err := idlewatcher.NewWatcher(parent, r, r.IdlewatcherConfig())
|
||||
if err != nil {
|
||||
r.task.Finish(err)
|
||||
return gperr.Wrap(err)
|
||||
return err
|
||||
}
|
||||
r.handler = waker
|
||||
r.HealthMon = waker
|
||||
@@ -148,7 +149,7 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
||||
r.rp.AccessLogger, err = accesslog.NewAccessLogger(r.task, r.AccessLog)
|
||||
if err != nil {
|
||||
r.task.Finish(err)
|
||||
return gperr.Wrap(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,14 +159,14 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
||||
|
||||
if r.HealthMon != nil {
|
||||
if err := r.HealthMon.Start(r.task); err != nil {
|
||||
gperr.LogWarn("health monitor error", err, &r.rp.Logger)
|
||||
log.Warn().Err(err).Msg("health monitor error")
|
||||
r.HealthMon = nil
|
||||
}
|
||||
}
|
||||
|
||||
ep := entrypoint.FromCtx(parent.Context())
|
||||
if ep == nil {
|
||||
err := gperr.New("entrypoint not initialized")
|
||||
err := errors.New("entrypoint not initialized")
|
||||
r.task.Finish(err)
|
||||
return err
|
||||
}
|
||||
@@ -173,12 +174,12 @@ func (r *ReveseProxyRoute) Start(parent task.Parent) gperr.Error {
|
||||
if r.UseLoadBalance() {
|
||||
if err := r.addToLoadBalancer(parent, ep); err != nil {
|
||||
r.task.Finish(err)
|
||||
return gperr.Wrap(err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := ep.StartAddRoute(r); err != nil {
|
||||
r.task.Finish(err)
|
||||
return gperr.Wrap(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -108,17 +108,17 @@ type (
|
||||
)
|
||||
|
||||
type lockedError struct {
|
||||
err gperr.Error
|
||||
err error
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func (le *lockedError) Get() gperr.Error {
|
||||
func (le *lockedError) Get() error {
|
||||
le.lock.Lock()
|
||||
defer le.lock.Unlock()
|
||||
return le.err
|
||||
}
|
||||
|
||||
func (le *lockedError) Set(err gperr.Error) {
|
||||
func (le *lockedError) Set(err error) {
|
||||
le.lock.Lock()
|
||||
defer le.lock.Unlock()
|
||||
le.err = err
|
||||
@@ -131,7 +131,7 @@ func (r Routes) Contains(alias string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func (r *Route) Validate() gperr.Error {
|
||||
func (r *Route) Validate() error {
|
||||
// wait for alias to be set
|
||||
if r.Alias == "" {
|
||||
return nil
|
||||
@@ -150,13 +150,13 @@ func (r *Route) Validate() gperr.Error {
|
||||
return r.valErr.Get()
|
||||
}
|
||||
|
||||
func (r *Route) validate() gperr.Error {
|
||||
func (r *Route) validate() error {
|
||||
// if strings.HasPrefix(r.Alias, "godoxy") {
|
||||
// log.Debug().Any("route", r).Msg("validating route")
|
||||
// }
|
||||
if r.Agent != "" {
|
||||
if r.Container != nil {
|
||||
return gperr.Errorf("specifying agent is not allowed for docker container routes")
|
||||
return errors.New("specifying agent is not allowed for docker container routes")
|
||||
}
|
||||
var ok bool
|
||||
// by agent address
|
||||
@@ -165,7 +165,7 @@ func (r *Route) validate() gperr.Error {
|
||||
// fallback to get agent by name
|
||||
r.agent, ok = agentpool.GetAgent(r.Agent)
|
||||
if !ok {
|
||||
return gperr.Errorf("agent %s not found", r.Agent)
|
||||
return fmt.Errorf("agent %s not found", r.Agent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,7 +258,7 @@ func (r *Route) validate() gperr.Error {
|
||||
switch r.Port.Proxy {
|
||||
case common.ProxyHTTPPort, common.ProxyHTTPSPort, common.APIHTTPPort:
|
||||
if r.Scheme.IsReverseProxy() || r.Scheme == route.SchemeTCP {
|
||||
return gperr.Errorf("localhost:%d is reserved for godoxy", r.Port.Proxy)
|
||||
return fmt.Errorf("localhost:%d is reserved for godoxy", r.Port.Proxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,9 +269,6 @@ func (r *Route) validate() gperr.Error {
|
||||
errs.Add(err)
|
||||
}
|
||||
|
||||
var impl types.Route
|
||||
var err gperr.Error
|
||||
|
||||
if r.ShouldExclude() {
|
||||
r.ProxyURL = gperr.Collect(&errs, nettypes.ParseURL, fmt.Sprintf("%s://%s", r.Scheme, net.JoinHostPort(r.Host, strconv.Itoa(r.Port.Proxy))))
|
||||
} else {
|
||||
@@ -318,6 +315,8 @@ func (r *Route) validate() gperr.Error {
|
||||
return errs.Error()
|
||||
}
|
||||
|
||||
var impl types.Route
|
||||
var err error
|
||||
switch r.Scheme {
|
||||
case route.SchemeFileServer:
|
||||
impl, err = NewFileServer(r)
|
||||
@@ -479,16 +478,16 @@ func (r *Route) Task() *task.Task {
|
||||
return r.task
|
||||
}
|
||||
|
||||
func (r *Route) Start(parent task.Parent) gperr.Error {
|
||||
func (r *Route) Start(parent task.Parent) error {
|
||||
r.onceStart.Do(func() {
|
||||
r.startErr.Set(r.start(parent))
|
||||
})
|
||||
return r.startErr.Get()
|
||||
}
|
||||
|
||||
func (r *Route) start(parent task.Parent) gperr.Error {
|
||||
func (r *Route) start(parent task.Parent) error {
|
||||
if r.impl == nil { // should not happen
|
||||
return gperr.New("route not initialized")
|
||||
return errors.New("route not initialized")
|
||||
}
|
||||
defer close(r.started)
|
||||
|
||||
@@ -511,7 +510,7 @@ func (r *Route) start(parent task.Parent) gperr.Error {
|
||||
} else {
|
||||
ep := entrypoint.FromCtx(parent.Context())
|
||||
if ep == nil {
|
||||
return gperr.New("entrypoint not initialized")
|
||||
return errors.New("entrypoint not initialized")
|
||||
}
|
||||
|
||||
r.task = parent.Subtask("excluded."+r.Name(), false)
|
||||
|
||||
@@ -72,7 +72,7 @@ var commands = map[string]struct {
|
||||
description: makeLines("Require HTTP authentication for incoming requests"),
|
||||
args: map[string]string{},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
if len(args) != 0 {
|
||||
return nil, ErrExpectNoArg
|
||||
}
|
||||
@@ -103,17 +103,17 @@ var commands = map[string]struct {
|
||||
"to": "the path to rewrite to, must start with /",
|
||||
},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrExpectTwoArgs
|
||||
}
|
||||
path1, err1 := validateURLPath(args[:1])
|
||||
path2, err2 := validateURLPath(args[1:])
|
||||
if err1 != nil {
|
||||
err1 = gperr.Errorf("from: %w", err1)
|
||||
err1 = gperr.PrependSubject(err1, "from")
|
||||
}
|
||||
if err2 != nil {
|
||||
err2 = gperr.Errorf("to: %w", err2)
|
||||
err2 = gperr.PrependSubject(err2, "to")
|
||||
}
|
||||
if err1 != nil || err2 != nil {
|
||||
return nil, gperr.Join(err1, err2)
|
||||
@@ -189,7 +189,7 @@ var commands = map[string]struct {
|
||||
"route": "the route to route to",
|
||||
},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrExpectOneArg
|
||||
}
|
||||
@@ -227,7 +227,7 @@ var commands = map[string]struct {
|
||||
"text": "the error message to return",
|
||||
},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrExpectTwoArgs
|
||||
}
|
||||
@@ -267,7 +267,7 @@ var commands = map[string]struct {
|
||||
"realm": "the authentication realm",
|
||||
},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
if len(args) == 1 {
|
||||
return args[0], nil
|
||||
}
|
||||
@@ -334,7 +334,7 @@ var commands = map[string]struct {
|
||||
"value": "the value to set",
|
||||
},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
return validateModField(ModFieldSet, args)
|
||||
},
|
||||
build: func(args any) CommandHandler {
|
||||
@@ -354,7 +354,7 @@ var commands = map[string]struct {
|
||||
"value": "the value to add",
|
||||
},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
return validateModField(ModFieldAdd, args)
|
||||
},
|
||||
build: func(args any) CommandHandler {
|
||||
@@ -373,7 +373,7 @@ var commands = map[string]struct {
|
||||
"field": "the field to remove",
|
||||
},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
return validateModField(ModFieldRemove, args)
|
||||
},
|
||||
build: func(args any) CommandHandler {
|
||||
@@ -398,7 +398,7 @@ var commands = map[string]struct {
|
||||
"template": "the template to log",
|
||||
},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
if len(args) != 3 {
|
||||
return nil, ErrExpectThreeArgs
|
||||
}
|
||||
@@ -455,7 +455,7 @@ var commands = map[string]struct {
|
||||
"body": "the body of the notification",
|
||||
},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
if len(args) != 4 {
|
||||
return nil, ErrExpectFourArgs
|
||||
}
|
||||
@@ -543,7 +543,7 @@ func (cmd *Command) Parse(v string) error {
|
||||
validArgs, err := builder.validate(args)
|
||||
if err != nil {
|
||||
// Only attach help for the directive that failed, avoid bringing in unrelated KV errors
|
||||
return err.Subject(directive).With(builder.help.Error())
|
||||
return gperr.PrependSubject(err, directive).With(builder.help.Error())
|
||||
}
|
||||
|
||||
handler := builder.build(validArgs)
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
// mockUpstream creates a simple upstream handler for testing
|
||||
@@ -32,7 +31,7 @@ func mockUpstreamWithHeaders(status int, body string, headers http.Header) http.
|
||||
}
|
||||
}
|
||||
|
||||
func parseRules(data string, target *Rules) gperr.Error {
|
||||
func parseRules(data string, target *Rules) error {
|
||||
_, err := serialization.ConvertString(data, reflect.ValueOf(target))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
httputils "github.com/yusing/goutils/http"
|
||||
ioutils "github.com/yusing/goutils/io"
|
||||
)
|
||||
@@ -228,7 +227,7 @@ var modFields = map[string]struct {
|
||||
"template": "the body template",
|
||||
},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrExpectOneArg
|
||||
}
|
||||
@@ -273,7 +272,7 @@ var modFields = map[string]struct {
|
||||
"template": "the response body template",
|
||||
},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrExpectOneArg
|
||||
}
|
||||
@@ -301,7 +300,7 @@ var modFields = map[string]struct {
|
||||
"code": "the status code",
|
||||
},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrExpectOneArg
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package rules
|
||||
import (
|
||||
"testing"
|
||||
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func TestErrorFormat(t *testing.T) {
|
||||
@@ -19,5 +19,5 @@ func TestErrorFormat(t *testing.T) {
|
||||
do: set invalid_command
|
||||
- do: set resp_body "{{ .Request.Method {{ .Request.URL.Path }}"
|
||||
`, &rules)
|
||||
gperr.LogError("error", err)
|
||||
log.Err(err).Msg("error")
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ Generate help string as error, e.g.
|
||||
from: the path to rewrite, must start with /
|
||||
to: the path to rewrite to, must start with /
|
||||
*/
|
||||
func (h *Help) Error() gperr.Error {
|
||||
func (h *Help) Error() error {
|
||||
var lines gperr.MultilineError
|
||||
|
||||
lines.Adds(ansi.WithANSI(h.command, ansi.HighlightGreen))
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/yusing/godoxy/internal/route"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
. "github.com/yusing/godoxy/internal/route/rules"
|
||||
@@ -44,7 +43,7 @@ func mockRoute(alias string) *route.FileServer {
|
||||
return &route.FileServer{Route: &route.Route{Alias: alias}}
|
||||
}
|
||||
|
||||
func parseRules(data string, target *Rules) gperr.Error {
|
||||
func parseRules(data string, target *Rules) error {
|
||||
_, err := serialization.ConvertString(strings.TrimSpace(data), reflect.ValueOf(target))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
"github.com/yusing/godoxy/internal/logging/accesslog"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type noopWriteCloser struct {
|
||||
@@ -31,7 +30,7 @@ var (
|
||||
testFilesLock sync.Mutex
|
||||
)
|
||||
|
||||
func openFile(path string) (io.WriteCloser, gperr.Error) {
|
||||
func openFile(path string) (io.WriteCloser, error) {
|
||||
switch path {
|
||||
case "/dev/stdout":
|
||||
return stdout, nil
|
||||
|
||||
@@ -59,7 +59,7 @@ var checkers = map[string]struct {
|
||||
),
|
||||
args: map[string]string{},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
if len(args) != 0 {
|
||||
return nil, ErrExpectNoArg
|
||||
}
|
||||
@@ -251,7 +251,7 @@ var checkers = map[string]struct {
|
||||
"proto": "the http protocol (http, https, h3)",
|
||||
},
|
||||
},
|
||||
validate: func(args []string) (any, gperr.Error) {
|
||||
validate: func(args []string) (any, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrExpectOneArg
|
||||
}
|
||||
@@ -581,7 +581,7 @@ func (on *RuleOn) Parse(v string) error {
|
||||
}
|
||||
parsed, isResp, err := parseOn(rule)
|
||||
if err != nil {
|
||||
errs.Add(err.Subjectf("line %d", i+1))
|
||||
errs.AddSubjectf(err, "line %d", i+1)
|
||||
continue
|
||||
}
|
||||
if isResp {
|
||||
@@ -603,7 +603,7 @@ func (on *RuleOn) MarshalText() ([]byte, error) {
|
||||
return []byte(on.String()), nil
|
||||
}
|
||||
|
||||
func parseOn(line string) (Checker, bool, gperr.Error) {
|
||||
func parseOn(line string) (Checker, bool, error) {
|
||||
ors := splitPipe(line)
|
||||
|
||||
if len(ors) > 1 {
|
||||
@@ -645,7 +645,7 @@ func parseOn(line string) (Checker, bool, gperr.Error) {
|
||||
|
||||
validArgs, err := checker.validate(args)
|
||||
if err != nil {
|
||||
return nil, false, err.With(checker.help.Error())
|
||||
return nil, false, gperr.Wrap(err).With(checker.help.Error())
|
||||
}
|
||||
|
||||
checkFunc := checker.builder(validArgs)
|
||||
|
||||
@@ -31,7 +31,7 @@ var quoteChars = [256]bool{
|
||||
// error 403 "Forbidden 'foo' 'bar'"
|
||||
// error 403 Forbidden\ \"foo\"\ \"bar\".
|
||||
// error 403 "Message: ${CLOUDFLARE_API_KEY}"
|
||||
func parse(v string) (subject string, args []string, err gperr.Error) {
|
||||
func parse(v string) (subject string, args []string, err error) {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, len(v)))
|
||||
|
||||
escaped := false
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/route/rules"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
//go:embed *.yml
|
||||
@@ -35,12 +34,12 @@ func initPresets() {
|
||||
var rules rules.Rules
|
||||
content, err := fs.ReadFile(file.Name())
|
||||
if err != nil {
|
||||
gperr.LogError("failed to read rule preset", err)
|
||||
log.Err(err).Msg("failed to read rule preset")
|
||||
continue
|
||||
}
|
||||
_, err = serialization.ConvertString(string(content), reflect.ValueOf(&rules))
|
||||
if err != nil {
|
||||
gperr.LogError("failed to unmarshal rule preset", err)
|
||||
log.Err(err).Msg("failed to unmarshal rule preset")
|
||||
continue
|
||||
}
|
||||
rulePresets[file.Name()] = rules
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/rs/zerolog/log"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
httputils "github.com/yusing/goutils/http"
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
@@ -58,7 +57,7 @@ func (rule *Rule) IsResponseRule() bool {
|
||||
return rule.On.IsResponseChecker() || rule.Do.IsResponseHandler()
|
||||
}
|
||||
|
||||
func (rules Rules) Validate() gperr.Error {
|
||||
func (rules Rules) Validate() error {
|
||||
var defaultRulesFound []int
|
||||
for i, rule := range rules {
|
||||
if rule.Name == "default" || rule.On.raw == OnDefault {
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
ValidateFunc func(args []string) (any, gperr.Error)
|
||||
ValidateFunc func(args []string) (any, error)
|
||||
Tuple[T1, T2 any] struct {
|
||||
First T1
|
||||
Second T2
|
||||
@@ -62,7 +62,7 @@ func (t *Tuple4[T1, T2, T3, T4]) String() string {
|
||||
}
|
||||
|
||||
// validateSingleMatcher returns Matcher with the matcher validated.
|
||||
func validateSingleMatcher(args []string) (any, gperr.Error) {
|
||||
func validateSingleMatcher(args []string) (any, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrExpectOneArg
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func validateSingleMatcher(args []string) (any, gperr.Error) {
|
||||
}
|
||||
|
||||
// toKVOptionalVMatcher returns *MapValueMatcher that value is optional.
|
||||
func toKVOptionalVMatcher(args []string) (any, gperr.Error) {
|
||||
func toKVOptionalVMatcher(args []string) (any, error) {
|
||||
switch len(args) {
|
||||
case 1:
|
||||
return &MapValueMatcher{args[0], nil}, nil
|
||||
@@ -85,7 +85,7 @@ func toKVOptionalVMatcher(args []string) (any, gperr.Error) {
|
||||
}
|
||||
}
|
||||
|
||||
func toKeyValueTemplate(args []string) (any, gperr.Error) {
|
||||
func toKeyValueTemplate(args []string) (any, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrExpectTwoArgs
|
||||
}
|
||||
@@ -98,7 +98,7 @@ func toKeyValueTemplate(args []string) (any, gperr.Error) {
|
||||
}
|
||||
|
||||
// validateURL returns types.URL with the URL validated.
|
||||
func validateURL(args []string) (any, gperr.Error) {
|
||||
func validateURL(args []string) (any, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrExpectOneArg
|
||||
}
|
||||
@@ -116,7 +116,7 @@ func validateURL(args []string) (any, gperr.Error) {
|
||||
}
|
||||
|
||||
// validateCIDR returns types.CIDR with the CIDR validated.
|
||||
func validateCIDR(args []string) (any, gperr.Error) {
|
||||
func validateCIDR(args []string) (any, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrExpectOneArg
|
||||
}
|
||||
@@ -131,7 +131,7 @@ func validateCIDR(args []string) (any, gperr.Error) {
|
||||
}
|
||||
|
||||
// validateURLPath returns string with the path validated.
|
||||
func validateURLPath(args []string) (any, gperr.Error) {
|
||||
func validateURLPath(args []string) (any, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrExpectOneArg
|
||||
}
|
||||
@@ -148,7 +148,7 @@ func validateURLPath(args []string) (any, gperr.Error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func validateURLPathMatcher(args []string) (any, gperr.Error) {
|
||||
func validateURLPathMatcher(args []string) (any, error) {
|
||||
path, err := validateURLPath(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -157,7 +157,7 @@ func validateURLPathMatcher(args []string) (any, gperr.Error) {
|
||||
}
|
||||
|
||||
// validateFSPath returns string with the path validated.
|
||||
func validateFSPath(args []string) (any, gperr.Error) {
|
||||
func validateFSPath(args []string) (any, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrExpectOneArg
|
||||
}
|
||||
@@ -169,7 +169,7 @@ func validateFSPath(args []string) (any, gperr.Error) {
|
||||
}
|
||||
|
||||
// validateMethod returns string with the method validated.
|
||||
func validateMethod(args []string) (any, gperr.Error) {
|
||||
func validateMethod(args []string) (any, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrExpectOneArg
|
||||
}
|
||||
@@ -200,7 +200,7 @@ func validateStatusCode(status string) (int, error) {
|
||||
// - 3xx
|
||||
// - 4xx
|
||||
// - 5xx
|
||||
func validateStatusRange(args []string) (any, gperr.Error) {
|
||||
func validateStatusRange(args []string) (any, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, ErrExpectOneArg
|
||||
}
|
||||
@@ -232,7 +232,7 @@ func validateStatusRange(args []string) (any, gperr.Error) {
|
||||
}
|
||||
|
||||
// validateUserBCryptPassword returns *HashedCrendential with the password validated.
|
||||
func validateUserBCryptPassword(args []string) (any, gperr.Error) {
|
||||
func validateUserBCryptPassword(args []string) (any, error) {
|
||||
if len(args) != 2 {
|
||||
return nil, ErrExpectTwoArgs
|
||||
}
|
||||
@@ -240,7 +240,7 @@ func validateUserBCryptPassword(args []string) (any, gperr.Error) {
|
||||
}
|
||||
|
||||
// validateModField returns CommandHandler with the field validated.
|
||||
func validateModField(mod FieldModifier, args []string) (CommandHandler, gperr.Error) {
|
||||
func validateModField(mod FieldModifier, args []string) (CommandHandler, error) {
|
||||
if len(args) == 0 {
|
||||
return nil, ErrExpectTwoOrThreeArgs
|
||||
}
|
||||
@@ -257,7 +257,7 @@ func validateModField(mod FieldModifier, args []string) (CommandHandler, gperr.E
|
||||
}
|
||||
validArgs, err := setField.validate(args[1:])
|
||||
if err != nil {
|
||||
return nil, err.With(setField.help.Error())
|
||||
return nil, gperr.Wrap(err).With(setField.help.Error())
|
||||
}
|
||||
modder := setField.builder(validArgs)
|
||||
switch mod {
|
||||
@@ -281,7 +281,7 @@ func validateModField(mod FieldModifier, args []string) (CommandHandler, gperr.E
|
||||
return set, nil
|
||||
}
|
||||
|
||||
func validateTemplate(tmplStr string, newline bool) (templateString, gperr.Error) {
|
||||
func validateTemplate(tmplStr string, newline bool) (templateString, error) {
|
||||
if newline && !strings.HasSuffix(tmplStr, "\n") {
|
||||
tmplStr += "\n"
|
||||
}
|
||||
@@ -292,22 +292,15 @@ func validateTemplate(tmplStr string, newline bool) (templateString, gperr.Error
|
||||
|
||||
err := ValidateVars(tmplStr)
|
||||
if err != nil {
|
||||
return templateString{}, gperr.Wrap(err)
|
||||
return templateString{}, err
|
||||
}
|
||||
return templateString{tmplStr, true}, nil
|
||||
}
|
||||
|
||||
func validateLevel(level string) (zerolog.Level, gperr.Error) {
|
||||
func validateLevel(level string) (zerolog.Level, error) {
|
||||
l, err := zerolog.ParseLevel(level)
|
||||
if err != nil {
|
||||
return zerolog.NoLevel, ErrInvalidArguments.With(err)
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// func validateNotifProvider(provider string) gperr.Error {
|
||||
// if !notif.HasProvider(provider) {
|
||||
// return ErrInvalidArguments.Subject(provider)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
@@ -2,17 +2,18 @@ package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
|
||||
"github.com/yusing/godoxy/internal/health/monitor"
|
||||
"github.com/yusing/godoxy/internal/idlewatcher"
|
||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||
"github.com/yusing/godoxy/internal/route/stream"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
@@ -24,7 +25,7 @@ type StreamRoute struct {
|
||||
|
||||
var _ types.StreamRoute = (*StreamRoute)(nil)
|
||||
|
||||
func NewStreamRoute(base *Route) (types.Route, gperr.Error) {
|
||||
func NewStreamRoute(base *Route) (types.Route, error) {
|
||||
// TODO: support non-coherent scheme
|
||||
return &StreamRoute{Route: base}, nil
|
||||
}
|
||||
@@ -34,14 +35,14 @@ func (r *StreamRoute) Stream() nettypes.Stream {
|
||||
}
|
||||
|
||||
// Start implements task.TaskStarter.
|
||||
func (r *StreamRoute) Start(parent task.Parent) gperr.Error {
|
||||
func (r *StreamRoute) Start(parent task.Parent) error {
|
||||
if r.LisURL == nil {
|
||||
return gperr.Errorf("listen URL is not set")
|
||||
return errors.New("listen URL is not set")
|
||||
}
|
||||
|
||||
stream, err := r.initStream()
|
||||
if err != nil {
|
||||
return gperr.Wrap(err)
|
||||
return err
|
||||
}
|
||||
r.stream = stream
|
||||
|
||||
@@ -52,7 +53,7 @@ func (r *StreamRoute) Start(parent task.Parent) gperr.Error {
|
||||
waker, err := idlewatcher.NewWatcher(parent, r, r.IdlewatcherConfig())
|
||||
if err != nil {
|
||||
r.task.Finish(err)
|
||||
return gperr.Wrap(err, "idlewatcher error")
|
||||
return fmt.Errorf("idlewatcher error: %w", err)
|
||||
}
|
||||
r.stream = waker
|
||||
r.HealthMon = waker
|
||||
@@ -62,20 +63,20 @@ func (r *StreamRoute) Start(parent task.Parent) gperr.Error {
|
||||
|
||||
if r.HealthMon != nil {
|
||||
if err := r.HealthMon.Start(r.task); err != nil {
|
||||
gperr.LogWarn("health monitor error", err)
|
||||
log.Warn().Err(err).Msg("health monitor error")
|
||||
r.HealthMon = nil
|
||||
}
|
||||
}
|
||||
|
||||
ep := entrypoint.FromCtx(parent.Context())
|
||||
if ep == nil {
|
||||
err := gperr.New("entrypoint not initialized")
|
||||
err := errors.New("entrypoint not initialized")
|
||||
r.task.Finish(err)
|
||||
return err
|
||||
}
|
||||
if err := ep.StartAddRoute(r); err != nil {
|
||||
r.task.Finish(err)
|
||||
return gperr.Wrap(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package route
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -25,7 +26,7 @@ type HTTPConfig struct {
|
||||
}
|
||||
|
||||
// BuildTLSConfig creates a TLS configuration based on the HTTP config options.
|
||||
func (cfg *HTTPConfig) BuildTLSConfig(targetURL *url.URL) (*tls.Config, gperr.Error) {
|
||||
func (cfg *HTTPConfig) BuildTLSConfig(targetURL *url.URL) (*tls.Config, error) {
|
||||
tlsConfig := &tls.Config{}
|
||||
|
||||
// Handle InsecureSkipVerify (legacy NoTLSVerify option)
|
||||
@@ -54,15 +55,12 @@ func (cfg *HTTPConfig) BuildTLSConfig(targetURL *url.URL) (*tls.Config, gperr.Er
|
||||
if cfg.SSLTrustedCertificate != "" {
|
||||
caCertData, err := os.ReadFile(cfg.SSLTrustedCertificate)
|
||||
if err != nil {
|
||||
return nil, gperr.New("failed to read trusted certificate file").
|
||||
Subject(cfg.SSLTrustedCertificate).
|
||||
With(err)
|
||||
return nil, gperr.PrependSubject(err, cfg.SSLTrustedCertificate)
|
||||
}
|
||||
|
||||
caCertPool := x509.NewCertPool()
|
||||
if !caCertPool.AppendCertsFromPEM(caCertData) {
|
||||
return nil, gperr.New("failed to parse trusted certificates").
|
||||
Subject(cfg.SSLTrustedCertificate)
|
||||
return nil, gperr.PrependSubject(errors.New("failed to parse trusted certificates"), cfg.SSLTrustedCertificate)
|
||||
}
|
||||
tlsConfig.RootCAs = caCertPool
|
||||
}
|
||||
@@ -70,16 +68,16 @@ func (cfg *HTTPConfig) BuildTLSConfig(targetURL *url.URL) (*tls.Config, gperr.Er
|
||||
// Handle ssl_certificate and ssl_certificate_key (client certificates)
|
||||
if cfg.SSLCertificate != "" {
|
||||
if cfg.SSLCertificateKey == "" {
|
||||
return nil, gperr.New("ssl_certificate_key is required when ssl_certificate is specified")
|
||||
return nil, errors.New("ssl_certificate_key is required when ssl_certificate is specified")
|
||||
}
|
||||
|
||||
clientCert, err := tls.LoadX509KeyPair(cfg.SSLCertificate, cfg.SSLCertificateKey)
|
||||
if err != nil {
|
||||
return nil, gperr.New("failed to load client certificate").
|
||||
Subject(cfg.SSLCertificate).
|
||||
With(err)
|
||||
return nil, gperr.PrependSubject(err, cfg.SSLCertificate)
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{clientCert}
|
||||
} else if cfg.SSLCertificateKey != "" {
|
||||
return nil, errors.New("ssl_certificate is required when ssl_certificate_key is specified")
|
||||
}
|
||||
|
||||
// Handle ssl_protocols (TLS versions)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
@@ -13,8 +14,8 @@ type Port struct {
|
||||
} // @name Port
|
||||
|
||||
var (
|
||||
ErrInvalidPortSyntax = gperr.New("invalid port syntax, expect [listening_port:]target_port")
|
||||
ErrPortOutOfRange = gperr.New("port out of range")
|
||||
ErrInvalidPortSyntax = errors.New("invalid port syntax, expect [listening_port:]target_port")
|
||||
ErrPortOutOfRange = errors.New("port out of range")
|
||||
)
|
||||
|
||||
// Parse implements strutils.Parser.
|
||||
@@ -30,7 +31,7 @@ func (p *Port) Parse(v string) (err error) {
|
||||
p.Proxy, err2 = strconv.Atoi(parts[1])
|
||||
err = gperr.Join(err, err2)
|
||||
default:
|
||||
return ErrInvalidPortSyntax.Subject(v)
|
||||
return gperr.PrependSubject(ErrInvalidPortSyntax, v)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -38,11 +39,11 @@ func (p *Port) Parse(v string) (err error) {
|
||||
}
|
||||
|
||||
if p.Listening < MinPort || p.Listening > MaxPort {
|
||||
return ErrPortOutOfRange.Subjectf("%d", p.Listening)
|
||||
return gperr.PrependSubject(ErrPortOutOfRange, strconv.Itoa(p.Listening))
|
||||
}
|
||||
|
||||
if p.Proxy < MinPort || p.Proxy > MaxPort {
|
||||
return ErrPortOutOfRange.Subjectf("%d", p.Proxy)
|
||||
return gperr.PrependSubject(ErrPortOutOfRange, strconv.Itoa(p.Proxy))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user