mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-27 11:17:29 +02:00
Sort proxy.* keys by dot depth, then name, before building the tree so broader paths apply before deeper ones. When a new value would sit on a node that is already a map, parse it as a YAML object (tabs normalized to two spaces), deep-merge, and treat an empty string as an empty object. Return clear errors when a scalar and a nested map disagree. Drop the preallocated refPrefixes table in favor of refPrefix(n). Add internal tests for parseLabelObject, mergeLabelMaps, key order, and flatten; extend export tests for mixed OIDC-style labels and conflicts. * refactor(docker): extract label parse and flatten helpers Refactor ParseLabels by moving proxy label application into applyLabel, descendLabelMap, and setLabelValue so traversal and leaf merge share one path without labelLoop continues. Add splitAliasLabel for ExpandWildcard so proxy.* prefix handling stays in one place and uses CutPrefix/Cut consistently. Deduplicate flattenMap and flattenMapAny value handling with flattenValue plus joinLabelKey and stringifyLabelKey for flattened key construction. * refactor(docker): structured errors for label type clashes Replace ad hoc fmt.Errorf messages in descendLabelMap, setLabelValue, and mergeLabelMaps with UnexpectedTypeError so wording is consistent and mapping vs scalar conflicts stay explicit. Hoist requireMap in label tests to a shared helper. Normalize tabs to two spaces in expandYamlWildcard so wildcard YAML matches the indentation used in the object-merge path. * refactor(docker): optional UnexpectedTypeError message for merge conflicts Extend UnexpectedTypeError with an optional Message field; when set, Error() returns it instead of the default expect-versus-actual formatting. mergeLabelMaps sets that message when a mapping would merge into an existing scalar, so the error states the situation instead of only "expect scalar". Update TestMergeLabelMaps to assert the new wording.
301 lines
8.4 KiB
Go
301 lines
8.4 KiB
Go
package docker_test
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/yusing/godoxy/internal/docker"
|
|
)
|
|
|
|
func TestExpandWildcard(t *testing.T) {
|
|
t.Run("basic", func(t *testing.T) {
|
|
labels := map[string]string{
|
|
"proxy.a.host": "localhost",
|
|
"proxy.b.port": "4444",
|
|
"proxy.b.scheme": "http",
|
|
"proxy.*.port": "5555",
|
|
"proxy.*.healthcheck.disable": "true",
|
|
}
|
|
docker.ExpandWildcard(labels, "a", "b")
|
|
require.Equal(t, map[string]string{
|
|
"proxy.#1.host": "localhost",
|
|
"proxy.#1.port": "5555",
|
|
"proxy.#1.healthcheck.disable": "true",
|
|
"proxy.#2.port": "5555",
|
|
"proxy.#2.scheme": "http",
|
|
"proxy.#2.healthcheck.disable": "true",
|
|
}, labels)
|
|
})
|
|
|
|
t.Run("no wildcards", func(t *testing.T) {
|
|
labels := map[string]string{
|
|
"proxy.a.host": "localhost",
|
|
"proxy.b.port": "4444",
|
|
}
|
|
docker.ExpandWildcard(labels, "a", "b")
|
|
require.Equal(t, map[string]string{
|
|
"proxy.a.host": "localhost",
|
|
"proxy.b.port": "4444",
|
|
}, labels)
|
|
})
|
|
|
|
t.Run("no aliases", func(t *testing.T) {
|
|
labels := map[string]string{
|
|
"proxy.*.port": "5555",
|
|
}
|
|
docker.ExpandWildcard(labels)
|
|
require.Equal(t, map[string]string{}, labels)
|
|
})
|
|
|
|
t.Run("empty labels", func(t *testing.T) {
|
|
labels := map[string]string{}
|
|
docker.ExpandWildcard(labels, "a", "b")
|
|
require.Equal(t, map[string]string{}, labels)
|
|
})
|
|
|
|
t.Run("only wildcards no explicit labels", func(t *testing.T) {
|
|
labels := map[string]string{
|
|
"proxy.*.port": "5555",
|
|
"proxy.*.scheme": "https",
|
|
}
|
|
docker.ExpandWildcard(labels, "a", "b")
|
|
require.Equal(t, map[string]string{
|
|
"proxy.#1.port": "5555",
|
|
"proxy.#1.scheme": "https",
|
|
"proxy.#2.port": "5555",
|
|
"proxy.#2.scheme": "https",
|
|
}, labels)
|
|
})
|
|
|
|
t.Run("non-proxy labels unchanged", func(t *testing.T) {
|
|
labels := map[string]string{
|
|
"other.label": "value",
|
|
"proxy.*.port": "5555",
|
|
"proxy.a.scheme": "http",
|
|
}
|
|
docker.ExpandWildcard(labels, "a")
|
|
require.Equal(t, map[string]string{
|
|
"other.label": "value",
|
|
"proxy.#1.port": "5555",
|
|
"proxy.#1.scheme": "http",
|
|
}, labels)
|
|
})
|
|
|
|
t.Run("single alias multiple labels", func(t *testing.T) {
|
|
labels := map[string]string{
|
|
"proxy.a.host": "localhost",
|
|
"proxy.a.port": "8080",
|
|
"proxy.a.scheme": "https",
|
|
"proxy.*.port": "5555",
|
|
}
|
|
docker.ExpandWildcard(labels, "a")
|
|
require.Equal(t, map[string]string{
|
|
"proxy.#1.host": "localhost",
|
|
"proxy.#1.port": "5555",
|
|
"proxy.#1.scheme": "https",
|
|
}, labels)
|
|
})
|
|
|
|
t.Run("wildcard partial override", func(t *testing.T) {
|
|
labels := map[string]string{
|
|
"proxy.a.host": "localhost",
|
|
"proxy.a.port": "8080",
|
|
"proxy.a.healthcheck.path": "/health",
|
|
"proxy.*.port": "5555",
|
|
}
|
|
docker.ExpandWildcard(labels, "a")
|
|
require.Equal(t, map[string]string{
|
|
"proxy.#1.host": "localhost",
|
|
"proxy.#1.port": "5555",
|
|
"proxy.#1.healthcheck.path": "/health",
|
|
}, labels)
|
|
})
|
|
|
|
t.Run("nested suffix distinction", func(t *testing.T) {
|
|
labels := map[string]string{
|
|
"proxy.a.healthcheck.path": "/health",
|
|
"proxy.a.healthcheck.interval": "10s",
|
|
"proxy.*.healthcheck.disable": "true",
|
|
}
|
|
docker.ExpandWildcard(labels, "a")
|
|
require.Equal(t, map[string]string{
|
|
"proxy.#1.healthcheck.path": "/health",
|
|
"proxy.#1.healthcheck.interval": "10s",
|
|
"proxy.#1.healthcheck.disable": "true",
|
|
}, labels)
|
|
})
|
|
|
|
t.Run("discovered alias from explicit label", func(t *testing.T) {
|
|
labels := map[string]string{
|
|
"proxy.c.host": "localhost",
|
|
"proxy.*.port": "5555",
|
|
}
|
|
docker.ExpandWildcard(labels, "a", "b")
|
|
require.Equal(t, map[string]string{
|
|
"proxy.#1.port": "5555",
|
|
"proxy.#2.port": "5555",
|
|
"proxy.#3.host": "localhost",
|
|
"proxy.#3.port": "5555",
|
|
}, labels)
|
|
})
|
|
|
|
t.Run("ref alias not converted", func(t *testing.T) {
|
|
labels := map[string]string{
|
|
"proxy.#1.host": "localhost",
|
|
"proxy.#2.port": "8080",
|
|
"proxy.*.scheme": "https",
|
|
}
|
|
docker.ExpandWildcard(labels, "a", "b")
|
|
require.Equal(t, map[string]string{
|
|
"proxy.#1.host": "localhost",
|
|
"proxy.#1.scheme": "https",
|
|
"proxy.#2.port": "8080",
|
|
"proxy.#2.scheme": "https",
|
|
}, labels)
|
|
})
|
|
|
|
t.Run("mixed ref and named aliases", func(t *testing.T) {
|
|
labels := map[string]string{
|
|
"proxy.#1.host": "host1",
|
|
"proxy.a.host": "host2",
|
|
"proxy.*.port": "5555",
|
|
}
|
|
docker.ExpandWildcard(labels, "a", "b")
|
|
require.Equal(t, map[string]string{
|
|
"proxy.#1.host": "host2",
|
|
"proxy.#1.port": "5555",
|
|
"proxy.#2.port": "5555",
|
|
}, labels)
|
|
})
|
|
}
|
|
|
|
func TestExpandWildcardYAML(t *testing.T) {
|
|
t.Run("basic yaml wildcard", func(t *testing.T) {
|
|
yaml := `
|
|
host: localhost
|
|
port: 5555
|
|
healthcheck:
|
|
disable: true`[1:]
|
|
labels := map[string]string{
|
|
"proxy.*": yaml,
|
|
"proxy.a.port": "4444",
|
|
"proxy.a.healthcheck.disable": "false",
|
|
"proxy.a.healthcheck.path": "/health",
|
|
"proxy.b.port": "6666",
|
|
}
|
|
docker.ExpandWildcard(labels, "a", "b")
|
|
require.Equal(t, map[string]string{
|
|
"proxy.#1.host": "localhost",
|
|
"proxy.#1.port": "5555",
|
|
"proxy.#1.healthcheck.disable": "true",
|
|
"proxy.#1.healthcheck.path": "/health",
|
|
"proxy.#2.host": "localhost",
|
|
"proxy.#2.port": "5555",
|
|
"proxy.#2.healthcheck.disable": "true",
|
|
}, labels)
|
|
})
|
|
|
|
t.Run("yaml with nested maps", func(t *testing.T) {
|
|
yaml := `
|
|
middlewares:
|
|
request:
|
|
hide_headers: X-Secret
|
|
add_headers:
|
|
X-Custom: value`[1:]
|
|
labels := map[string]string{
|
|
"proxy.*": yaml,
|
|
"proxy.a.middlewares.request.set_headers": "X-Override: yes",
|
|
}
|
|
docker.ExpandWildcard(labels, "a")
|
|
require.Equal(t, map[string]string{
|
|
"proxy.#1.middlewares.request.hide_headers": "X-Secret",
|
|
"proxy.#1.middlewares.request.add_headers.X-Custom": "value",
|
|
"proxy.#1.middlewares.request.set_headers": "X-Override: yes",
|
|
}, labels)
|
|
})
|
|
|
|
t.Run("yaml only no explicit labels", func(t *testing.T) {
|
|
yaml := `
|
|
host: localhost
|
|
port: 8080`[1:]
|
|
labels := map[string]string{
|
|
"proxy.*": yaml,
|
|
}
|
|
docker.ExpandWildcard(labels, "a", "b")
|
|
require.Equal(t, map[string]string{
|
|
"proxy.#1.host": "localhost",
|
|
"proxy.#1.port": "8080",
|
|
"proxy.#2.host": "localhost",
|
|
"proxy.#2.port": "8080",
|
|
}, labels)
|
|
})
|
|
|
|
t.Run("invalid yaml ignored", func(t *testing.T) {
|
|
labels := map[string]string{
|
|
"proxy.*": "invalid: yaml: content:\n\t\tbad",
|
|
"proxy.a.port": "8080",
|
|
}
|
|
docker.ExpandWildcard(labels, "a")
|
|
require.Equal(t, map[string]string{
|
|
"proxy.a.port": "8080",
|
|
}, labels)
|
|
})
|
|
}
|
|
|
|
func requireMap(t *testing.T, value any) map[string]any {
|
|
t.Helper()
|
|
|
|
m, ok := value.(map[string]any)
|
|
require.True(t, ok, "expected map[string]any, got %T", value)
|
|
return m
|
|
}
|
|
|
|
func BenchmarkParseLabels(b *testing.B) {
|
|
m := map[string]string{
|
|
"proxy.a.host": "localhost",
|
|
"proxy.b.port": "4444",
|
|
"proxy.*.scheme": "http",
|
|
"proxy.*.middlewares.request.hide_headers": "X-Header1,X-Header2",
|
|
}
|
|
for b.Loop() {
|
|
_, _ = docker.ParseLabels(m, "a", "b")
|
|
}
|
|
}
|
|
|
|
func TestParseLabelsMixedObjectAndFlatFields(t *testing.T) {
|
|
for i := range 100 {
|
|
labels := map[string]string{
|
|
"proxy.universal.middlewares.oidc": "allowed_groups: [everyone]",
|
|
"proxy.universal.middlewares.oidc.bypass": "- path glob(/geheimenvan/*)",
|
|
}
|
|
|
|
parsed, err := docker.ParseLabels(labels)
|
|
require.NoError(t, err, fmt.Sprintf("iteration %d", i))
|
|
|
|
universal := requireMap(t, parsed["universal"])
|
|
middlewares := requireMap(t, universal["middlewares"])
|
|
oidc := requireMap(t, middlewares["oidc"])
|
|
|
|
require.Equal(t, []any{"everyone"}, oidc["allowed_groups"])
|
|
require.Equal(t, "- path glob(/geheimenvan/*)", oidc["bypass"])
|
|
}
|
|
}
|
|
|
|
func TestParseLabelsRejectsScalarAndNestedObjectConflict(t *testing.T) {
|
|
for i := range 100 {
|
|
parsed, err := docker.ParseLabels(map[string]string{
|
|
"proxy.universal.middlewares.oidc": "bypass: skip",
|
|
"proxy.universal.middlewares.oidc.bypass.path": "/geheimenvan",
|
|
})
|
|
|
|
require.ErrorContains(t, err, "proxy.universal.middlewares.oidc.bypass.path")
|
|
require.ErrorContains(t, err, "expect mapping, got string")
|
|
|
|
universal := requireMap(t, parsed["universal"])
|
|
middlewares := requireMap(t, universal["middlewares"])
|
|
oidc := requireMap(t, middlewares["oidc"])
|
|
require.Equal(t, "skip", oidc["bypass"], "iteration %d", i)
|
|
}
|
|
}
|