feat: support newline and comma-whitespace external host lists for auto-discovery (#468)
This commit is contained in:
+1
-1
@@ -199,7 +199,7 @@ func (w *dockerWatcherImpl) parseContainerData(container *dockertypes.Container)
|
||||
Warnf("ignoring container with duplicate %s label", DockerRouterLabelHost)
|
||||
return
|
||||
}
|
||||
data.hosts = strings.Split(value, ",")
|
||||
data.hosts = SplitExternalHosts(value)
|
||||
}
|
||||
|
||||
if key == DockerRouterLabelPort {
|
||||
|
||||
@@ -247,7 +247,7 @@ func (w *dockerSwarmWatcherImpl) parseServiceData(service *swarm.Service, networ
|
||||
Warnf("ignoring service with duplicate %s", DockerRouterLabelHost)
|
||||
return
|
||||
}
|
||||
data.hosts = strings.Split(value, ",")
|
||||
data.hosts = SplitExternalHosts(value)
|
||||
}
|
||||
if key == DockerRouterLabelPort {
|
||||
if data.port != 0 {
|
||||
|
||||
+5
-5
@@ -3,6 +3,10 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
apps "k8s.io/api/apps/v1"
|
||||
@@ -14,10 +18,6 @@ import (
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -238,7 +238,7 @@ func (w *K8sWatcher) extractRoutableServices(obj interface{}) []*routableService
|
||||
|
||||
routableServices := make([]*routableService, 0)
|
||||
if externalServiceName, exists := service.Annotations[AnnotationExternalServerName]; exists {
|
||||
serviceNames := strings.Split(externalServiceName, ",")
|
||||
serviceNames := SplitExternalHosts(externalServiceName)
|
||||
for _, serviceName := range serviceNames {
|
||||
routableServices = append(routableServices, w.buildDetails(service, serviceName))
|
||||
}
|
||||
|
||||
@@ -117,6 +117,59 @@ func TestK8sWatcherImpl_handleAddThenUpdate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "comma with spaces",
|
||||
initial: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "a.com, b.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{server: "a.com", backend: "1.1.1.1:25565"},
|
||||
{server: "b.com", backend: "1.1.1.1:25565"},
|
||||
},
|
||||
},
|
||||
update: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "b.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{server: "a.com", backend: ""},
|
||||
{server: "b.com", backend: "1.1.1.1:25565"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "newline separated",
|
||||
initial: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "a.com\nb.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{server: "a.com", backend: "1.1.1.1:25565"},
|
||||
{server: "b.com", backend: "1.1.1.1:25565"},
|
||||
},
|
||||
},
|
||||
update: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "b.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{server: "a.com", backend: ""},
|
||||
{server: "b.com", backend: "1.1.1.1:25565"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mixed comma and newline with spaces",
|
||||
initial: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "a.com, \nb.com, c.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{server: "a.com", backend: "1.1.1.1:25565"},
|
||||
{server: "b.com", backend: "1.1.1.1:25565"},
|
||||
{server: "c.com", backend: "1.1.1.1:25565"},
|
||||
},
|
||||
},
|
||||
update: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "b.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{server: "a.com", backend: ""},
|
||||
{server: "b.com", backend: "1.1.1.1:25565"},
|
||||
{server: "c.com", backend: ""},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// splitPattern is the regex pattern used to split external host definitions.
|
||||
// kept as a const so the literal is easy to see and reuse in tests or docs.
|
||||
const splitPattern = ",|\n"
|
||||
|
||||
// splitRe is the compiled regexp for splitPattern. Compiling once at package
|
||||
// initialization avoids repeated compilation on every call to
|
||||
// SplitExternalHosts and is slightly more efficient.
|
||||
var splitRe = regexp.MustCompile(splitPattern)
|
||||
|
||||
// SplitExternalHosts splits a string containing external hostnames by comma and/or newline delimiters.
|
||||
// It trims whitespace around each hostname and filters out empty strings.
|
||||
// Examples:
|
||||
// - "host1.com,host2.com" -> ["host1.com", "host2.com"]
|
||||
// - "host1.com, host2.com" -> ["host1.com", "host2.com"]
|
||||
// - "host1.com\nhost2.com" -> ["host1.com", "host2.com"]
|
||||
// - "host1.com,\nhost2.com" -> ["host1.com", "host2.com"]
|
||||
func SplitExternalHosts(s string) []string {
|
||||
// Use regexp to split on either comma or newline
|
||||
parts := splitRe.Split(s, -1)
|
||||
|
||||
// Trim whitespace and filter out empty strings
|
||||
result := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
trimmed := strings.TrimSpace(part)
|
||||
if trimmed != "" {
|
||||
result = append(result, trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user