mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-20 12:42:34 +02:00
fixed healthchecker start even if disabled, simplified label parsing
This commit is contained in:
parent
2951304647
commit
c07f2ed722
11 changed files with 158 additions and 290 deletions
|
@ -1,125 +1,51 @@
|
||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
type LabelMap = map[string]any
|
||||||
Formats:
|
|
||||||
- namespace.attribute
|
|
||||||
- namespace.target.attribute
|
|
||||||
- namespace.target.attribute.namespace2.attribute
|
|
||||||
*/
|
|
||||||
type (
|
|
||||||
Label struct {
|
|
||||||
Namespace string
|
|
||||||
Target string
|
|
||||||
Attribute string
|
|
||||||
Value any
|
|
||||||
}
|
|
||||||
NestedLabelMap map[string]U.SerializedObject
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
func ParseLabels(labels map[string]string) (LabelMap, E.Error) {
|
||||||
ErrApplyToNil = E.New("label value is nil")
|
nestedMap := make(LabelMap)
|
||||||
ErrFieldNotExist = E.New("field does not exist")
|
errs := E.NewBuilder("labels error")
|
||||||
)
|
|
||||||
|
|
||||||
func (l *Label) String() string {
|
for lbl, value := range labels {
|
||||||
if l.Attribute == "" {
|
parts := strings.Split(lbl, ".")
|
||||||
return l.Namespace + "." + l.Target
|
if parts[0] != NSProxy {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
return l.Namespace + "." + l.Target + "." + l.Attribute
|
if len(parts) == 1 {
|
||||||
}
|
errs.Add(E.Errorf("invalid label %s", lbl).Subject(lbl))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts = parts[1:]
|
||||||
|
currentMap := nestedMap
|
||||||
|
|
||||||
// Apply applies the value of a Label to the corresponding field in the given object.
|
for i, k := range parts {
|
||||||
//
|
if i == len(parts)-1 {
|
||||||
// Parameters:
|
// Last element, set the value
|
||||||
// - obj: a pointer to the object to which the Label value will be applied.
|
currentMap[k] = value
|
||||||
// - l: a pointer to the Label containing the attribute and value to be applied.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// - error: an error if the field does not exist.
|
|
||||||
func ApplyLabel[T any](obj *T, l *Label) E.Error {
|
|
||||||
if obj == nil {
|
|
||||||
return ErrApplyToNil.Subject(l.String())
|
|
||||||
}
|
|
||||||
switch nestedLabel := l.Value.(type) {
|
|
||||||
case *Label:
|
|
||||||
var field reflect.Value
|
|
||||||
objType := reflect.TypeFor[T]()
|
|
||||||
for i := range reflect.TypeFor[T]().NumField() {
|
|
||||||
if objType.Field(i).Tag.Get("yaml") == l.Attribute {
|
|
||||||
field = reflect.ValueOf(obj).Elem().Field(i)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !field.IsValid() {
|
|
||||||
return ErrFieldNotExist.Subject(l.Attribute).Subject(l.String())
|
|
||||||
}
|
|
||||||
dst, ok := field.Interface().(NestedLabelMap)
|
|
||||||
if !ok {
|
|
||||||
if field.Kind() == reflect.Ptr {
|
|
||||||
if field.IsNil() {
|
|
||||||
field.Set(reflect.New(field.Type().Elem()))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
field = field.Addr()
|
// If the key doesn't exist, create a new map
|
||||||
|
if _, exists := currentMap[k]; !exists {
|
||||||
|
currentMap[k] = make(LabelMap)
|
||||||
}
|
}
|
||||||
err := U.Deserialize(U.SerializedObject{nestedLabel.Namespace: nestedLabel.Value}, field.Interface())
|
// Move deeper into the nested map
|
||||||
if err != nil {
|
m, ok := currentMap[k].(LabelMap)
|
||||||
return err.Subject(l.String())
|
if !ok && currentMap[k] != "" {
|
||||||
|
errs.Add(E.Errorf("expect mapping, got %T", currentMap[k]).Subject(lbl))
|
||||||
|
continue
|
||||||
|
} else if !ok {
|
||||||
|
m = make(LabelMap)
|
||||||
|
currentMap[k] = m
|
||||||
}
|
}
|
||||||
return nil
|
currentMap = m
|
||||||
}
|
}
|
||||||
if dst == nil {
|
|
||||||
field.Set(reflect.MakeMap(reflect.TypeFor[NestedLabelMap]()))
|
|
||||||
dst = field.Interface().(NestedLabelMap)
|
|
||||||
}
|
}
|
||||||
if dst[nestedLabel.Namespace] == nil {
|
|
||||||
dst[nestedLabel.Namespace] = make(U.SerializedObject)
|
|
||||||
}
|
|
||||||
dst[nestedLabel.Namespace][nestedLabel.Attribute] = nestedLabel.Value
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
err := U.Deserialize(U.SerializedObject{l.Attribute: l.Value}, obj)
|
|
||||||
if err != nil {
|
|
||||||
return err.Subject(l.String())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return nestedMap, errs.Error()
|
||||||
func ParseLabel(label string, value string) *Label {
|
|
||||||
parts := strings.Split(label, ".")
|
|
||||||
|
|
||||||
if len(parts) < 2 {
|
|
||||||
return &Label{
|
|
||||||
Namespace: label,
|
|
||||||
Value: value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
l := &Label{
|
|
||||||
Namespace: parts[0],
|
|
||||||
Target: parts[1],
|
|
||||||
Value: value,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch len(parts) {
|
|
||||||
case 2:
|
|
||||||
l.Attribute = l.Target
|
|
||||||
case 3:
|
|
||||||
l.Attribute = parts[2]
|
|
||||||
default:
|
|
||||||
l.Attribute = parts[2]
|
|
||||||
nestedLabel := ParseLabel(strings.Join(parts[3:], "."), value)
|
|
||||||
l.Value = nestedLabel
|
|
||||||
}
|
|
||||||
|
|
||||||
return l
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
mName = "middleware1"
|
|
||||||
mAttr = "prop1"
|
|
||||||
v = "value1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeLabel(ns, name, attr string) string {
|
|
||||||
return fmt.Sprintf("%s.%s.%s", ns, name, attr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNestedLabel(t *testing.T) {
|
|
||||||
mAttr := "prop1"
|
|
||||||
lbl := ParseLabel(makeLabel(NSProxy, "foo", makeLabel("middlewares", mName, mAttr)), v)
|
|
||||||
sGot := ExpectType[*Label](t, lbl.Value)
|
|
||||||
ExpectFalse(t, sGot == nil)
|
|
||||||
ExpectEqual(t, sGot.Namespace, mName)
|
|
||||||
ExpectEqual(t, sGot.Attribute, mAttr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApplyNestedLabel(t *testing.T) {
|
|
||||||
entry := new(struct {
|
|
||||||
Middlewares NestedLabelMap `yaml:"middlewares"`
|
|
||||||
})
|
|
||||||
lbl := ParseLabel(makeLabel(NSProxy, "foo", makeLabel("middlewares", mName, mAttr)), v)
|
|
||||||
err := ApplyLabel(entry, lbl)
|
|
||||||
ExpectNoError(t, err)
|
|
||||||
middleware1, ok := entry.Middlewares[mName]
|
|
||||||
ExpectTrue(t, ok)
|
|
||||||
got := ExpectType[string](t, middleware1[mAttr])
|
|
||||||
ExpectEqual(t, got, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApplyNestedLabelExisting(t *testing.T) {
|
|
||||||
checkAttr := "prop2"
|
|
||||||
checkV := "value2"
|
|
||||||
entry := new(struct {
|
|
||||||
Middlewares NestedLabelMap `yaml:"middlewares"`
|
|
||||||
})
|
|
||||||
entry.Middlewares = make(NestedLabelMap)
|
|
||||||
entry.Middlewares[mName] = make(U.SerializedObject)
|
|
||||||
entry.Middlewares[mName][checkAttr] = checkV
|
|
||||||
|
|
||||||
lbl := ParseLabel(makeLabel(NSProxy, "foo", makeLabel("middlewares", mName, mAttr)), v)
|
|
||||||
err := ApplyLabel(entry, lbl)
|
|
||||||
ExpectNoError(t, err)
|
|
||||||
middleware1, ok := entry.Middlewares[mName]
|
|
||||||
ExpectTrue(t, ok)
|
|
||||||
got := ExpectType[string](t, middleware1[mAttr])
|
|
||||||
ExpectEqual(t, got, v)
|
|
||||||
|
|
||||||
// check if prop2 is affected
|
|
||||||
ExpectFalse(t, middleware1[checkAttr] == nil)
|
|
||||||
got = ExpectType[string](t, middleware1[checkAttr])
|
|
||||||
ExpectEqual(t, got, checkV)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApplyNestedLabelNoAttr(t *testing.T) {
|
|
||||||
entry := new(struct {
|
|
||||||
Middlewares NestedLabelMap `yaml:"middlewares"`
|
|
||||||
})
|
|
||||||
entry.Middlewares = make(NestedLabelMap)
|
|
||||||
entry.Middlewares[mName] = make(U.SerializedObject)
|
|
||||||
|
|
||||||
lbl := ParseLabel(makeLabel(NSProxy, "foo", fmt.Sprintf("%s.%s", "middlewares", mName)), v)
|
|
||||||
err := ApplyLabel(entry, lbl)
|
|
||||||
ExpectNoError(t, err)
|
|
||||||
_, ok := entry.Middlewares[mName]
|
|
||||||
ExpectTrue(t, ok)
|
|
||||||
}
|
|
|
@ -4,7 +4,6 @@ const (
|
||||||
WildcardAlias = "*"
|
WildcardAlias = "*"
|
||||||
|
|
||||||
NSProxy = "proxy"
|
NSProxy = "proxy"
|
||||||
NSHomePage = "homepage"
|
|
||||||
|
|
||||||
LabelAliases = NSProxy + ".aliases"
|
LabelAliases = NSProxy + ".aliases"
|
||||||
LabelExclude = NSProxy + ".exclude"
|
LabelExclude = NSProxy + ".exclude"
|
||||||
|
|
|
@ -19,8 +19,6 @@ type Entry interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateEntry(m *RawEntry) (Entry, E.Error) {
|
func ValidateEntry(m *RawEntry) (Entry, E.Error) {
|
||||||
m.FillMissingFields()
|
|
||||||
|
|
||||||
scheme, err := T.NewScheme(m.Scheme)
|
scheme, err := T.NewScheme(m.Scheme)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.From(err)
|
return nil, E.From(err)
|
||||||
|
@ -36,6 +34,9 @@ func ValidateEntry(m *RawEntry) (Entry, E.Error) {
|
||||||
if errs.HasError() {
|
if errs.HasError() {
|
||||||
return nil, errs.Error()
|
return nil, errs.Error()
|
||||||
}
|
}
|
||||||
|
if !UseHealthCheck(entry) && (UseLoadBalance(entry) || UseIdleWatcher(entry)) {
|
||||||
|
return nil, E.New("healthCheck.disable cannot be true when loadbalancer or idlewatcher is enabled")
|
||||||
|
}
|
||||||
return entry, nil
|
return entry, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,11 +30,13 @@ type (
|
||||||
PathPatterns []string `json:"path_patterns,omitempty" yaml:"path_patterns"` // http(s) proxy only
|
PathPatterns []string `json:"path_patterns,omitempty" yaml:"path_patterns"` // http(s) proxy only
|
||||||
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty" yaml:"healthcheck"`
|
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty" yaml:"healthcheck"`
|
||||||
LoadBalance *loadbalancer.Config `json:"load_balance,omitempty" yaml:"load_balance"`
|
LoadBalance *loadbalancer.Config `json:"load_balance,omitempty" yaml:"load_balance"`
|
||||||
Middlewares docker.NestedLabelMap `json:"middlewares,omitempty" yaml:"middlewares"`
|
Middlewares map[string]docker.LabelMap `json:"middlewares,omitempty" yaml:"middlewares"`
|
||||||
Homepage *homepage.Item `json:"homepage,omitempty" yaml:"homepage"`
|
Homepage *homepage.Item `json:"homepage,omitempty" yaml:"homepage"`
|
||||||
|
|
||||||
/* Docker only */
|
/* Docker only */
|
||||||
Container *docker.Container `json:"container,omitempty" yaml:"-"`
|
Container *docker.Container `json:"container,omitempty" yaml:"-"`
|
||||||
|
|
||||||
|
finalized bool
|
||||||
}
|
}
|
||||||
|
|
||||||
RawEntries = F.Map[string, *RawEntry]
|
RawEntries = F.Map[string, *RawEntry]
|
||||||
|
@ -42,7 +44,11 @@ type (
|
||||||
|
|
||||||
var NewProxyEntries = F.NewMapOf[string, *RawEntry]
|
var NewProxyEntries = F.NewMapOf[string, *RawEntry]
|
||||||
|
|
||||||
func (e *RawEntry) FillMissingFields() {
|
func (e *RawEntry) Finalize() {
|
||||||
|
if e.finalized {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
isDocker := e.Container != nil
|
isDocker := e.Container != nil
|
||||||
cont := e.Container
|
cont := e.Container
|
||||||
if !isDocker {
|
if !isDocker {
|
||||||
|
@ -124,14 +130,7 @@ func (e *RawEntry) FillMissingFields() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.HealthCheck == nil {
|
if e.HealthCheck == nil {
|
||||||
e.HealthCheck = new(health.HealthCheckConfig)
|
e.HealthCheck = health.DefaultHealthCheckConfig()
|
||||||
}
|
|
||||||
|
|
||||||
if e.HealthCheck.Interval == 0 {
|
|
||||||
e.HealthCheck.Interval = common.HealthCheckIntervalDefault
|
|
||||||
}
|
|
||||||
if e.HealthCheck.Timeout == 0 {
|
|
||||||
e.HealthCheck.Timeout = common.HealthCheckTimeoutDefault
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.HealthCheck.Disable {
|
if e.HealthCheck.Disable {
|
||||||
|
@ -159,6 +158,8 @@ func (e *RawEntry) FillMissingFields() {
|
||||||
e.Port = "0"
|
e.Port = "0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.finalized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RawEntry) splitPorts() (lp string, pp string, extra string) {
|
func (e *RawEntry) splitPorts() (lp string, pp string, extra string) {
|
||||||
|
@ -168,10 +169,10 @@ func (e *RawEntry) splitPorts() (lp string, pp string, extra string) {
|
||||||
} else {
|
} else {
|
||||||
lp = portSplit[0]
|
lp = portSplit[0]
|
||||||
pp = portSplit[1]
|
pp = portSplit[1]
|
||||||
}
|
|
||||||
if len(portSplit) > 2 {
|
if len(portSplit) > 2 {
|
||||||
extra = strings.Join(portSplit[2:], ":")
|
extra = strings.Join(portSplit[2:], ":")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ type ReverseProxyEntry struct { // real model after validation
|
||||||
PathPatterns fields.PathPatterns `json:"path_patterns,omitempty"`
|
PathPatterns fields.PathPatterns `json:"path_patterns,omitempty"`
|
||||||
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
|
HealthCheck *health.HealthCheckConfig `json:"healthcheck,omitempty"`
|
||||||
LoadBalance *loadbalancer.Config `json:"load_balance,omitempty"`
|
LoadBalance *loadbalancer.Config `json:"load_balance,omitempty"`
|
||||||
Middlewares docker.NestedLabelMap `json:"middlewares,omitempty"`
|
Middlewares map[string]docker.LabelMap `json:"middlewares,omitempty"`
|
||||||
|
|
||||||
/* Docker only */
|
/* Docker only */
|
||||||
Idlewatcher *idlewatcher.Config `json:"idlewatcher,omitempty"`
|
Idlewatcher *idlewatcher.Config `json:"idlewatcher,omitempty"`
|
||||||
|
|
|
@ -32,7 +32,7 @@ func ValidatePathPattern(s string) (PathPattern, error) {
|
||||||
|
|
||||||
func ValidatePathPatterns(s []string) (PathPatterns, E.Error) {
|
func ValidatePathPatterns(s []string) (PathPatterns, E.Error) {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return []PathPattern{"/"}, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
errs := E.NewBuilder("invalid path patterns")
|
errs := E.NewBuilder("invalid path patterns")
|
||||||
pp := make(PathPatterns, len(s))
|
pp := make(PathPatterns, len(s))
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -12,6 +11,7 @@ import (
|
||||||
E "github.com/yusing/go-proxy/internal/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"github.com/yusing/go-proxy/internal/proxy/entry"
|
"github.com/yusing/go-proxy/internal/proxy/entry"
|
||||||
"github.com/yusing/go-proxy/internal/route"
|
"github.com/yusing/go-proxy/internal/route"
|
||||||
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||||
"github.com/yusing/go-proxy/internal/watcher"
|
"github.com/yusing/go-proxy/internal/watcher"
|
||||||
)
|
)
|
||||||
|
@ -22,13 +22,13 @@ type DockerProvider struct {
|
||||||
l zerolog.Logger
|
l zerolog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
const (
|
||||||
AliasRefRegex = regexp.MustCompile(`#\d+`)
|
aliasRefPrefix = '#'
|
||||||
AliasRefRegexOld = regexp.MustCompile(`\$\d+`)
|
aliasRefPrefixAlt = '$'
|
||||||
|
|
||||||
ErrAliasRefIndexOutOfRange = E.New("index out of range")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrAliasRefIndexOutOfRange = E.New("index out of range")
|
||||||
|
|
||||||
func DockerProviderImpl(name, dockerHost string, explicitOnly bool) (ProviderImpl, error) {
|
func DockerProviderImpl(name, dockerHost string, explicitOnly bool) (ProviderImpl, error) {
|
||||||
if dockerHost == common.DockerHostFromEnv {
|
if dockerHost == common.DockerHostFromEnv {
|
||||||
dockerHost = common.GetEnvString("DOCKER_HOST", client.DefaultDockerHost)
|
dockerHost = common.GetEnvString("DOCKER_HOST", client.DefaultDockerHost)
|
||||||
|
@ -114,65 +114,70 @@ func (p *DockerProvider) entriesFromContainerLabels(container *docker.Container)
|
||||||
}
|
}
|
||||||
|
|
||||||
errs := E.NewBuilder("label errors")
|
errs := E.NewBuilder("label errors")
|
||||||
for key, val := range container.Labels {
|
|
||||||
errs.Add(p.applyLabel(container, entries, key, val))
|
m, err := docker.ParseLabels(container.Labels)
|
||||||
|
errs.Add(err)
|
||||||
|
|
||||||
|
var wildcardProps docker.LabelMap
|
||||||
|
|
||||||
|
for alias, entryMapAny := range m {
|
||||||
|
if len(alias) == 0 {
|
||||||
|
errs.Add(E.New("empty alias"))
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove all entries that failed to fill in missing fields
|
var ok bool
|
||||||
entries.RangeAll(func(_ string, re *entry.RawEntry) {
|
entryMap, ok := entryMapAny.(docker.LabelMap)
|
||||||
re.FillMissingFields()
|
if !ok {
|
||||||
|
errs.Add(E.Errorf("expect mapping, got %T", entryMap).Subject(alias))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if alias == docker.WildcardAlias {
|
||||||
|
wildcardProps = entryMap
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if it is an alias reference
|
||||||
|
switch alias[0] {
|
||||||
|
case aliasRefPrefix, aliasRefPrefixAlt:
|
||||||
|
index, err := strutils.Atoi(alias[1:])
|
||||||
|
if err != nil {
|
||||||
|
errs.Add(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if index < 1 || index > len(container.Aliases) {
|
||||||
|
errs.Add(ErrAliasRefIndexOutOfRange.Subject(strconv.Itoa(index)))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
alias = container.Aliases[index-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// init entry if not exist
|
||||||
|
var en *entry.RawEntry
|
||||||
|
if en, ok = entries.Load(alias); !ok {
|
||||||
|
en = &entry.RawEntry{
|
||||||
|
Alias: alias,
|
||||||
|
Container: container,
|
||||||
|
}
|
||||||
|
entries.Store(alias, en)
|
||||||
|
}
|
||||||
|
|
||||||
|
// deserialize map into entry object
|
||||||
|
err := U.Deserialize(entryMap, en)
|
||||||
|
if err != nil {
|
||||||
|
errs.Add(err.Subject(alias))
|
||||||
|
} else {
|
||||||
|
entries.Store(alias, en)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if wildcardProps != nil {
|
||||||
|
entries.RangeAll(func(alias string, re *entry.RawEntry) {
|
||||||
|
if err := U.Deserialize(wildcardProps, re); err != nil {
|
||||||
|
errs.Add(err.Subject(alias))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return entries, errs.Error()
|
return entries, errs.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DockerProvider) applyLabel(container *docker.Container, entries entry.RawEntries, key, val string) E.Error {
|
|
||||||
lbl := docker.ParseLabel(key, val)
|
|
||||||
if lbl.Namespace != docker.NSProxy {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if lbl.Target == docker.WildcardAlias {
|
|
||||||
// apply label for all aliases
|
|
||||||
labelErrs := entries.CollectErrors(func(a string, e *entry.RawEntry) error {
|
|
||||||
return docker.ApplyLabel(e, lbl)
|
|
||||||
})
|
|
||||||
if err := E.Join(labelErrs...); err != nil {
|
|
||||||
return err.Subject(lbl.Target)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
refErrs := E.NewBuilder("alias ref errors")
|
|
||||||
replaceIndexRef := func(ref string) string {
|
|
||||||
index, err := strutils.Atoi(ref[1:])
|
|
||||||
if err != nil {
|
|
||||||
refErrs.Add(err)
|
|
||||||
return ref
|
|
||||||
}
|
|
||||||
if index < 1 || index > len(container.Aliases) {
|
|
||||||
refErrs.Add(ErrAliasRefIndexOutOfRange.Subject(strconv.Itoa(index)))
|
|
||||||
return ref
|
|
||||||
}
|
|
||||||
return container.Aliases[index-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
lbl.Target = AliasRefRegex.ReplaceAllStringFunc(lbl.Target, replaceIndexRef)
|
|
||||||
lbl.Target = AliasRefRegexOld.ReplaceAllStringFunc(lbl.Target, func(ref string) string {
|
|
||||||
p.l.Warn().Msgf("%q should now be %q, old syntax will be removed in a future version", lbl, strings.ReplaceAll(lbl.String(), "$", "#"))
|
|
||||||
return replaceIndexRef(ref)
|
|
||||||
})
|
|
||||||
if refErrs.HasError() {
|
|
||||||
return refErrs.Error().Subject(lbl.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
en, ok := entries.Load(lbl.Target)
|
|
||||||
if !ok {
|
|
||||||
en = &entry.RawEntry{
|
|
||||||
Alias: lbl.Target,
|
|
||||||
Container: container,
|
|
||||||
}
|
|
||||||
entries.Store(lbl.Target, en)
|
|
||||||
}
|
|
||||||
|
|
||||||
return docker.ApplyLabel(en, lbl)
|
|
||||||
}
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ func TestApplyLabel(t *testing.T) {
|
||||||
"POST /upload/{$}",
|
"POST /upload/{$}",
|
||||||
"GET /static",
|
"GET /static",
|
||||||
}
|
}
|
||||||
middlewaresExpect := D.NestedLabelMap{
|
middlewaresExpect := map[string]map[string]any{
|
||||||
"middleware1": {
|
"middleware1": {
|
||||||
"prop1": "value1",
|
"prop1": "value1",
|
||||||
"prop2": "value2",
|
"prop2": "value2",
|
||||||
|
@ -55,7 +55,6 @@ func TestApplyLabel(t *testing.T) {
|
||||||
"proxy.*.scheme": "https",
|
"proxy.*.scheme": "https",
|
||||||
"proxy.*.host": "app",
|
"proxy.*.host": "app",
|
||||||
"proxy.*.port": "4567",
|
"proxy.*.port": "4567",
|
||||||
"proxy.a.no_tls_verify": "true",
|
|
||||||
"proxy.a.path_patterns": pathPatterns,
|
"proxy.a.path_patterns": pathPatterns,
|
||||||
"proxy.a.middlewares.middleware1.prop1": "value1",
|
"proxy.a.middlewares.middleware1.prop1": "value1",
|
||||||
"proxy.a.middlewares.middleware1.prop2": "value2",
|
"proxy.a.middlewares.middleware1.prop2": "value2",
|
||||||
|
@ -215,6 +214,21 @@ func TestDynamicAliases(t *testing.T) {
|
||||||
ExpectEqual(t, raw.Port, "5678")
|
ExpectEqual(t, raw.Port, "5678")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDisableHealthCheck(t *testing.T) {
|
||||||
|
c := D.FromDocker(&types.Container{
|
||||||
|
Names: dummyNames,
|
||||||
|
State: "running",
|
||||||
|
Labels: map[string]string{
|
||||||
|
"proxy.a.healthcheck.disable": "true",
|
||||||
|
"proxy.a.port": "1234",
|
||||||
|
},
|
||||||
|
}, client.DefaultDockerHost)
|
||||||
|
raw, ok := E.Must(p.entriesFromContainerLabels(c)).Load("a")
|
||||||
|
ExpectTrue(t, ok)
|
||||||
|
|
||||||
|
ExpectEqual(t, raw.HealthCheck, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPublicIPLocalhost(t *testing.T) {
|
func TestPublicIPLocalhost(t *testing.T) {
|
||||||
c := D.FromDocker(&types.Container{Names: dummyNames, State: "running"}, client.DefaultDockerHost)
|
c := D.FromDocker(&types.Container{Names: dummyNames, State: "running"}, client.DefaultDockerHost)
|
||||||
raw, ok := E.Must(p.entriesFromContainerLabels(c)).Load("a")
|
raw, ok := E.Must(p.entriesFromContainerLabels(c)).Load("a")
|
||||||
|
|
|
@ -45,6 +45,7 @@ func (rt *Route) Container() *docker.Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRoute(raw *entry.RawEntry) (*Route, E.Error) {
|
func NewRoute(raw *entry.RawEntry) (*Route, E.Error) {
|
||||||
|
raw.Finalize()
|
||||||
en, err := entry.ValidateEntry(raw)
|
en, err := entry.ValidateEntry(raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -14,8 +14,8 @@ type HealthCheckConfig struct {
|
||||||
Timeout time.Duration `json:"timeout" yaml:"timeout"`
|
Timeout time.Duration `json:"timeout" yaml:"timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultHealthCheckConfig() HealthCheckConfig {
|
func DefaultHealthCheckConfig() *HealthCheckConfig {
|
||||||
return HealthCheckConfig{
|
return &HealthCheckConfig{
|
||||||
Interval: common.HealthCheckIntervalDefault,
|
Interval: common.HealthCheckIntervalDefault,
|
||||||
Timeout: common.HealthCheckTimeoutDefault,
|
Timeout: common.HealthCheckTimeoutDefault,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue