Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions provider/awssd/aws_sd.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@
}

for _, ch := range changeList {
_, srvName := p.parseHostname(ch.DNSName)
_, srvName := p.parseHostname(ch.DNSName, namespaces)

srv := services[srvName]
if srv == nil {
Expand Down Expand Up @@ -350,7 +350,7 @@

for _, ch := range changeList {
hostname := ch.DNSName
_, srvName := p.parseHostname(hostname)
_, srvName := p.parseHostname(hostname, namespaces)

srv := services[srvName]
if srv == nil {
Expand Down Expand Up @@ -605,7 +605,7 @@
for _, c := range changes {
// trim the trailing dot from hostname if any
hostname := strings.TrimSuffix(c.DNSName, ".")
nsName, _ := p.parseHostname(hostname)
nsName, _ := p.parseHostname(hostname, namespaces)

matchingNamespaces := matchingNamespaces(nsName, namespaces)
if len(matchingNamespaces) == 0 {
Expand Down Expand Up @@ -640,8 +640,26 @@
return matchingNamespaces
}

// parseHostname parse hostname to namespace (domain) and service
func (p *AWSSDProvider) parseHostname(hostname string) (string, string) {
// parseHostname parses hostname into namespace (domain) and service name.
// When known namespaces are provided, it uses longest-suffix matching to
// correctly handle service names that contain dots (e.g. "foo.bar.dev.local"
// with namespace "dev.local" yields service "foo.bar"). Falls back to the
// original first-dot split when no namespace suffix matches.
func (p *AWSSDProvider) parseHostname(hostname string, namespaces []*sdtypes.NamespaceSummary) (string, string) {
var bestNS, bestSrv string
for _, ns := range namespaces {
suffix := "." + aws.ToString(ns.Name)
if strings.HasSuffix(hostname, suffix) {

Check failure on line 652 in provider/awssd/aws_sd.go

View workflow job for this annotation

GitHub Actions / Markdown and Go

stringscutprefix: HasSuffix + TrimSuffix can be simplified to CutSuffix (modernize)
candidate := strings.TrimSuffix(hostname, suffix)
if bestNS == "" || len(aws.ToString(ns.Name)) > len(bestNS) {
bestNS = aws.ToString(ns.Name)
bestSrv = candidate
}
}
}
if bestNS != "" {
return bestNS, bestSrv
}
parts := strings.Split(hostname, ".")
return strings.Join(parts[1:], "."), parts[0]
Comment thread
am-ltk marked this conversation as resolved.
Outdated
}
Expand Down
131 changes: 131 additions & 0 deletions provider/awssd/aws_sd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,56 @@ func TestAWSSDProvider_ApplyChanges_Update(t *testing.T) {
assert.Equal(t, "1.2.3.5", api.deregistered[0], "wrong target de-registered")
}

func TestAWSSDProvider_ApplyChanges_DottedServiceName(t *testing.T) {
namespaces := map[string]*sdtypes.Namespace{
"dev-local": {
Id: aws.String("dev-local"),
Name: aws.String("dev.local"),
Type: sdtypes.NamespaceTypeDnsPrivate,
},
}

api := &AWSSDClientStub{
namespaces: namespaces,
services: make(map[string]map[string]*sdtypes.Service),
instances: make(map[string]map[string]*sdtypes.Instance),
}

createEndpoints := []*endpoint.Endpoint{
{DNSName: "my-app.elb.dev.local", Targets: endpoint.Targets{"1.2.3.4"}, RecordType: endpoint.RecordTypeA, RecordTTL: 60},
}

provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{"dev.local"}), "", "")

ctx := t.Context()

err := provider.ApplyChanges(ctx, &plan.Changes{
Create: createEndpoints,
})
require.NoError(t, err)

// service must be created with the dotted name "my-app.elb"
assert.Len(t, api.services["dev-local"], 1)
existingServices, err := provider.ListServicesByNamespaceID(ctx, namespaces["dev-local"].Id)
require.NoError(t, err)
assert.NotNil(t, existingServices["my-app.elb"], "service should be named 'my-app.elb'")

// verify the record round-trips through Records()
endpoints, err := provider.Records(ctx)
require.NoError(t, err)
assert.True(t, testutils.SameEndpoints(createEndpoints, endpoints),
"expected and actual endpoints don't match, expected=%v, actual=%v", createEndpoints, endpoints)

// apply deletes
err = provider.ApplyChanges(ctx, &plan.Changes{
Delete: createEndpoints,
})
require.NoError(t, err)

endpoints, _ = provider.Records(ctx)
assert.Empty(t, endpoints)
}

func TestAWSSDProvider_ListNamespaces(t *testing.T) {
namespaces := map[string]*sdtypes.Namespace{
"private": {
Expand Down Expand Up @@ -1042,3 +1092,84 @@ func TestAWSSDProvider_awsTags(t *testing.T) {
require.ElementsMatch(t, test.Expectation, awsTags(test.Input))
}
}

func TestAWSSDProvider_parseHostname(t *testing.T) {
tests := []struct {
name string
hostname string
namespaces []*sdtypes.NamespaceSummary
wantNS string
wantSrv string
}{
{
name: "simple service name",
hostname: "foo.dev.local",
namespaces: []*sdtypes.NamespaceSummary{
{Name: aws.String("dev.local")},
},
wantNS: "dev.local",
wantSrv: "foo",
},
{
name: "dotted service name",
hostname: "foo.bar.dev.local",
namespaces: []*sdtypes.NamespaceSummary{
{Name: aws.String("dev.local")},
},
wantNS: "dev.local",
wantSrv: "foo.bar",
},
{
name: "SRV-style hostname",
hostname: "_tcp.backend.mynet.internal",
namespaces: []*sdtypes.NamespaceSummary{
{Name: aws.String("mynet.internal")},
},
wantNS: "mynet.internal",
wantSrv: "_tcp.backend",
},
{
name: "longest namespace match wins",
hostname: "foo.a.b.c",
namespaces: []*sdtypes.NamespaceSummary{
{Name: aws.String("b.c")},
{Name: aws.String("a.b.c")},
},
wantNS: "a.b.c",
wantSrv: "foo",
},
{
name: "no matching namespace falls back to first-dot split",
hostname: "foo.unknown.tld",
namespaces: []*sdtypes.NamespaceSummary{
{Name: aws.String("dev.local")},
},
wantNS: "unknown.tld",
wantSrv: "foo",
},
{
name: "empty namespaces falls back to first-dot split",
hostname: "foo.bar.baz",
namespaces: []*sdtypes.NamespaceSummary{},
wantNS: "bar.baz",
wantSrv: "foo",
},
{
name: "nil namespaces falls back to first-dot split",
hostname: "foo.bar.baz",
namespaces: nil,
wantNS: "bar.baz",
wantSrv: "foo",
},
Comment thread
am-ltk marked this conversation as resolved.
}

provider := &AWSSDProvider{}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
gotNS, gotSrv := provider.parseHostname(tc.hostname, tc.namespaces)
assert.Equal(t, tc.wantNS, gotNS)
assert.Equal(t, tc.wantSrv, gotSrv)
})
}
}
Loading