Added regex option to forward rules

This commit is contained in:
2026-03-31 12:45:56 +02:00
parent 55348057a0
commit dcc8201099
3 changed files with 49 additions and 13 deletions
+9 -1
View File
@@ -1,6 +1,7 @@
package handlers
import (
"log"
"os"
"path/filepath"
"quay/internal/config"
@@ -18,7 +19,14 @@ func NewStaticHandler(storagePath string, siteMap map[string]config.SiteConfig)
urlPath := filepath.Clean(c.Path())
for _, rule := range site.ForwardRules {
if rule.Source == urlPath {
if rule.Regex && rule.Compiled != nil {
match := rule.Compiled.FindStringSubmatchIndex(urlPath)
if match != nil {
dest := rule.Compiled.ExpandString([]byte{}, rule.Destination, urlPath, match)
log.Printf("Forwarding %s to %s based on regex rule", urlPath, dest)
return c.Redirect().Status(rule.StatusCode).To(string(dest))
}
} else if rule.Source == urlPath {
return c.Redirect().Status(rule.StatusCode).To(rule.Destination)
}
}
+9 -2
View File
@@ -12,9 +12,16 @@ sites:
- source: /npm
destination: https://www.npmjs.com/package/my-lib
status_code: 302
- source: /github
destination: https://github.com/yourname/my-lib
- source: ^/docs/v(\d+)$
destination: https://v$1.docs.my-lib.com/
status_code: 301
regex: true
- source: ^/blog/(?P<slug>[^/]+)$
destination: /posts/${slug}
status_code: 302
regex: true
- repo: portfolio
owner: yourname
+31 -10
View File
@@ -1,7 +1,10 @@
package config
import (
"fmt"
"os"
"regexp"
"strconv"
"gopkg.in/yaml.v3"
)
@@ -10,6 +13,8 @@ type ForwardRule struct {
Source string `yaml:"source"`
Destination string `yaml:"destination"`
StatusCode int `yaml:"status_code"`
Regex bool `yaml:"regex"`
Compiled *regexp.Regexp
}
type SiteConfig struct {
@@ -40,32 +45,42 @@ func (c Error) Error() string {
func validateConfig(config *Config) error {
for i, site := range config.Sites {
if site.Name == "" {
return &Error{Field: "sites[" + string(i) + "].name", Message: "Name is required"}
return &Error{Field: "sites[" + strconv.Itoa(i) + "].name", Message: "Name is required"}
}
if site.Repo == "" {
return &Error{Field: "sites[" + string(i) + "].repo", Message: "Repo is required"}
return &Error{Field: "sites[" + strconv.Itoa(i) + "].repo", Message: "Repo is required"}
}
if site.Owner == "" {
return &Error{Field: "sites[" + string(i) + "].owner", Message: "Owner is required"}
return &Error{Field: "sites[" + strconv.Itoa(i) + "].owner", Message: "Owner is required"}
}
if site.Branch == "" {
return &Error{Field: "sites[" + string(i) + "].branch", Message: "Branch is required"}
return &Error{Field: "sites[" + strconv.Itoa(i) + "].branch", Message: "Branch is required"}
}
if site.Domain == "" {
return &Error{Field: "sites[" + string(i) + "].domain", Message: "Domain is required"}
return &Error{Field: "sites[" + strconv.Itoa(i) + "].domain", Message: "Domain is required"}
}
for j, rule := range site.ForwardRules {
if rule.Source == "" {
return &Error{Field: "sites[" + string(i) + "].forward_rules[" + string(j) + "].source", Message: "Source is required"}
return &Error{Field: "sites[" + strconv.Itoa(i) + "].forward_rules[" + strconv.Itoa(j) + "].source", Message: "Source is required"}
}
if rule.Destination == "" {
return &Error{Field: "sites[" + string(i) + "].forward_rules[" + string(j) + "].destination", Message: "Destination is required"}
return &Error{Field: "sites[" + strconv.Itoa(i) + "].forward_rules[" + strconv.Itoa(j) + "].destination", Message: "Destination is required"}
}
if rule.StatusCode == 0 {
return &Error{Field: "sites[" + string(i) + "].forward_rules[" + string(j) + "].status_code", Message: "Status code is required"}
return &Error{Field: "sites[" + strconv.Itoa(i) + "].forward_rules[" + strconv.Itoa(j) + "].status_code", Message: "Status code is required"}
}
if rule.StatusCode < 300 || rule.StatusCode >= 400 {
return &Error{Field: "sites[" + string(i) + "].forward_rules[" + string(j) + "].status_code", Message: "Status code must be a valid redirect code (300-399)"}
return &Error{Field: "sites[" + strconv.Itoa(i) + "].forward_rules[" + strconv.Itoa(j) + "].status_code", Message: "Status code must be a valid redirect code (300-399)"}
}
if rule.Regex {
re, err := regexp.Compile(rule.Source)
if err != nil {
return &Error{
Field: fmt.Sprintf("sites[%d].forward_rules[%d].source", i, j),
Message: "Invalid regex: " + err.Error(),
}
}
config.Sites[i].ForwardRules[j].Compiled = re
}
}
}
@@ -90,7 +105,13 @@ func Load(path string) (*Config, error) {
if err != nil {
return nil, err
}
expanded := os.Expand(string(f), os.Getenv)
expanded := os.Expand(string(f), func(key string) string {
val, ok := os.LookupEnv(key)
if !ok {
return "${" + key + "}" // env vars that aren't set are left as is to not cause errors with regex patterns
}
return val
})
var config Config
err = yaml.Unmarshal([]byte(expanded), &config)
if err != nil {