Skip to content

Commit 0ce46b5

Browse files
committed
Handle short/long name resolution
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
1 parent 622058c commit 0ce46b5

3 files changed

Lines changed: 139 additions & 12 deletions

File tree

pkg/registry/provider_base.go

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,62 @@ func NewBaseProvider(getRegistry func() (*types.Registry, error)) *BaseProvider
2424
}
2525
}
2626

27-
// GetServer returns a specific server by name (container or remote)
27+
// GetServer returns a specific server by name (container or remote).
28+
// Supports both full reverse-DNS names (io.github.stacklok/osv) and
29+
// short names (osv) for backward compatibility.
2830
func (p *BaseProvider) GetServer(name string) (types.ServerMetadata, error) {
2931
reg, err := p.GetRegistryFunc()
3032
if err != nil {
3133
return nil, err
3234
}
3335

34-
// Use the registry's helper method
36+
// Try exact match first
3537
server, found := reg.GetServerByName(name)
36-
if !found {
37-
return nil, fmt.Errorf("server not found: %s", name)
38+
if found {
39+
return server, nil
3840
}
3941

40-
return server, nil
42+
// Fall back to short-name matching: check if name matches the last
43+
// path component of any server's full reverse-DNS name.
44+
// e.g. "osv" matches "io.github.stacklok/osv"
45+
if !strings.Contains(name, "/") {
46+
matches := findServersByShortName(reg, name)
47+
if len(matches) == 1 {
48+
return matches[0].server, nil
49+
}
50+
if len(matches) > 1 {
51+
names := make([]string, len(matches))
52+
for i, m := range matches {
53+
names[i] = m.fullName
54+
}
55+
return nil, fmt.Errorf("multiple servers match '%s': %s — use the full name",
56+
name, strings.Join(names, ", "))
57+
}
58+
}
59+
60+
return nil, fmt.Errorf("server not found: %s", name)
61+
}
62+
63+
type shortNameMatch struct {
64+
fullName string
65+
server types.ServerMetadata
66+
}
67+
68+
// findServersByShortName returns all servers whose name ends with "/<shortName>".
69+
func findServersByShortName(reg *types.Registry, shortName string) []shortNameMatch {
70+
suffix := "/" + shortName
71+
var matches []shortNameMatch
72+
for fullName, server := range reg.Servers {
73+
if strings.HasSuffix(fullName, suffix) {
74+
matches = append(matches, shortNameMatch{fullName, server})
75+
}
76+
}
77+
for fullName, server := range reg.RemoteServers {
78+
if strings.HasSuffix(fullName, suffix) {
79+
matches = append(matches, shortNameMatch{fullName, server})
80+
}
81+
}
82+
return matches
4183
}
4284

4385
// SearchServers searches for servers matching the query (both container and remote)

pkg/registry/provider_test.go

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,91 @@ func TestRemoteRegistryProvider_UpstreamFormat(t *testing.T) {
460460
assert.NotEmpty(t, registry.Servers, "Should have at least one container server")
461461
}
462462

463+
func TestGetServer_ShortNameResolution(t *testing.T) {
464+
t.Parallel()
465+
466+
// Build a controlled registry with known names
467+
reg := &types.Registry{
468+
Version: "1.0.0",
469+
LastUpdated: "2025-01-01T00:00:00Z",
470+
Servers: map[string]*types.ImageMetadata{
471+
"io.github.stacklok/osv": {BaseServerMetadata: types.BaseServerMetadata{Name: "io.github.stacklok/osv"}, Image: "ghcr.io/osv:latest"},
472+
"io.github.stacklok/github": {BaseServerMetadata: types.BaseServerMetadata{Name: "io.github.stacklok/github"}, Image: "ghcr.io/github:latest"},
473+
"io.github.acme/github": {BaseServerMetadata: types.BaseServerMetadata{Name: "io.github.acme/github"}, Image: "ghcr.io/acme-github:latest"},
474+
},
475+
RemoteServers: map[string]*types.RemoteServerMetadata{
476+
"io.github.stacklok/slack-remote": {BaseServerMetadata: types.BaseServerMetadata{Name: "io.github.stacklok/slack-remote"}, URL: "https://slack.example.com"},
477+
},
478+
}
479+
480+
provider := &LocalRegistryProvider{}
481+
provider.BaseProvider = NewBaseProvider(func() (*types.Registry, error) {
482+
return reg, nil
483+
})
484+
485+
tests := []struct {
486+
name string
487+
query string
488+
expectName string
489+
expectError string
490+
}{
491+
{
492+
name: "exact full name match",
493+
query: "io.github.stacklok/osv",
494+
expectName: "io.github.stacklok/osv",
495+
},
496+
{
497+
name: "unique short name match",
498+
query: "osv",
499+
expectName: "io.github.stacklok/osv",
500+
},
501+
{
502+
name: "ambiguous short name errors with full names",
503+
query: "github",
504+
expectError: "multiple servers match 'github'",
505+
},
506+
{
507+
name: "ambiguous error lists both full names",
508+
query: "github",
509+
expectError: "io.github.stacklok/github",
510+
},
511+
{
512+
name: "ambiguous error lists both full names (second)",
513+
query: "github",
514+
expectError: "io.github.acme/github",
515+
},
516+
{
517+
name: "short name for remote server",
518+
query: "slack-remote",
519+
expectName: "io.github.stacklok/slack-remote",
520+
},
521+
{
522+
name: "no match returns not found",
523+
query: "nonexistent",
524+
expectError: "server not found: nonexistent",
525+
},
526+
{
527+
name: "partial name does not match (github-remote suffix check)",
528+
query: "remote",
529+
expectError: "server not found: remote",
530+
},
531+
}
532+
533+
for _, tt := range tests {
534+
t.Run(tt.name, func(t *testing.T) {
535+
t.Parallel()
536+
server, err := provider.GetServer(tt.query)
537+
if tt.expectError != "" {
538+
require.Error(t, err)
539+
assert.Contains(t, err.Error(), tt.expectError)
540+
return
541+
}
542+
require.NoError(t, err)
543+
assert.Equal(t, tt.expectName, server.GetName())
544+
})
545+
}
546+
}
547+
463548
// getTypeName returns the type name of an interface value
464549
func getTypeName(v interface{}) string {
465550
switch v.(type) {
@@ -538,8 +623,8 @@ func TestGetServer(t *testing.T) {
538623
provider, err := NewRegistryProvider(cfg)
539624
require.NoError(t, err)
540625

541-
// Test getting an existing server (using upstream reverse-DNS name)
542-
server, err := provider.GetServer("io.github.stacklok/osv")
626+
// Test getting an existing server (short name resolves via suffix match)
627+
server, err := provider.GetServer("osv")
543628
if err != nil {
544629
t.Fatalf("Failed to get server: %v", err)
545630
}

pkg/runner/retriever/retriever_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,11 @@ func TestGetMCPServer_WithoutGroup(t *testing.T) {
128128
// Test that passing empty group name still works (normal behavior)
129129
imageURL, serverMetadata, err := GetMCPServer(
130130
ctx,
131-
"io.github.stacklok/osv", // Use a known server from the registry (upstream reverse-DNS name)
132-
"", // rawCACertPath
133-
VerifyImageDisabled, // verificationType
134-
"", // empty groupName should use normal registry lookup
135-
nil, // no runtime override
131+
"osv", // Use a known server from the registry
132+
"", // rawCACertPath
133+
VerifyImageDisabled, // verificationType
134+
"", // empty groupName should use normal registry lookup
135+
nil, // no runtime override
136136
)
137137

138138
// This should work as it's the normal flow

0 commit comments

Comments
 (0)