Skip to content

Commit 023dfc0

Browse files
authored
Add remote server support to registry system (#1363)
1 parent 756187f commit 023dfc0

File tree

14 files changed

+697
-271
lines changed

14 files changed

+697
-271
lines changed

cmd/thv/app/registry.go

Lines changed: 143 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import (
44
"encoding/json"
55
"fmt"
66
"os"
7-
"sort"
87
"strings"
98
"text/tabwriter"
109

1110
"github.com/spf13/cobra"
1211

1312
"github.com/stacklok/toolhive/pkg/registry"
13+
transtypes "github.com/stacklok/toolhive/pkg/transport/types"
1414
)
1515

1616
var registryCmd = &cobra.Command{
@@ -63,10 +63,8 @@ func registryListCmdFunc(_ *cobra.Command, _ []string) error {
6363
return fmt.Errorf("failed to list servers: %v", err)
6464
}
6565

66-
// Sort servers by name
67-
sort.Slice(servers, func(i, j int) bool {
68-
return servers[i].Name < servers[j].Name
69-
})
66+
// Sort servers by name using the utility function
67+
registry.SortServersByName(servers)
7068

7169
// Output based on format
7270
switch registryFormat {
@@ -101,7 +99,7 @@ func registryInfoCmdFunc(_ *cobra.Command, args []string) error {
10199
}
102100

103101
// printJSONServers prints servers in JSON format
104-
func printJSONServers(servers []*registry.ImageMetadata) error {
102+
func printJSONServers(servers []registry.ServerMetadata) error {
105103
// Marshal to JSON
106104
jsonData, err := json.MarshalIndent(servers, "", " ")
107105
if err != nil {
@@ -114,8 +112,7 @@ func printJSONServers(servers []*registry.ImageMetadata) error {
114112
}
115113

116114
// printJSONServer prints a single server in JSON format
117-
func printJSONServer(server *registry.ImageMetadata) error {
118-
// Marshal to JSON
115+
func printJSONServer(server registry.ServerMetadata) error {
119116
jsonData, err := json.MarshalIndent(server, "", " ")
120117
if err != nil {
121118
return fmt.Errorf("failed to marshal JSON: %v", err)
@@ -127,29 +124,30 @@ func printJSONServer(server *registry.ImageMetadata) error {
127124
}
128125

129126
// printTextServers prints servers in text format
130-
func printTextServers(servers []*registry.ImageMetadata) {
127+
func printTextServers(servers []registry.ServerMetadata) {
131128
// Create a tabwriter for pretty output
132129
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
133-
fmt.Fprintln(w, "NAME\tDESCRIPTION\tTIER\tSTARS\tPULLS")
130+
fmt.Fprintln(w, "NAME\tTYPE\tDESCRIPTION\tTIER\tSTARS\tPULLS")
134131

135132
// Print server information
136133
for _, server := range servers {
137134
stars := 0
138135
pulls := 0
139-
if server.Metadata != nil {
140-
stars = server.Metadata.Stars
141-
pulls = server.Metadata.Pulls
136+
if metadata := server.GetMetadata(); metadata != nil {
137+
stars = metadata.Stars
138+
pulls = metadata.Pulls
142139
}
143140

144-
desc := server.Description
145-
if server.Status == "Deprecated" {
141+
desc := server.GetDescription()
142+
if server.GetStatus() == "Deprecated" {
146143
desc = "**DEPRECATED** " + desc
147144
}
148145

149-
fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%d\n",
150-
server.Name,
151-
truncateString(desc, 60),
152-
server.Tier,
146+
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\t%d\n",
147+
server.GetName(),
148+
getServerType(server),
149+
truncateString(desc, 50),
150+
server.GetTier(),
153151
stars,
154152
pulls,
155153
)
@@ -161,41 +159,139 @@ func printTextServers(servers []*registry.ImageMetadata) {
161159
}
162160
}
163161

162+
// getServerType returns the type of server (container or remote)
163+
func getServerType(server registry.ServerMetadata) string {
164+
if server.IsRemote() {
165+
return "remote"
166+
}
167+
return "container"
168+
}
169+
164170
// printTextServerInfo prints detailed information about a server in text format
165171
// nolint:gocyclo
166-
func printTextServerInfo(name string, server *registry.ImageMetadata) {
167-
fmt.Printf("Name: %s\n", server.Name)
168-
fmt.Printf("Image: %s\n", server.Image)
169-
fmt.Printf("Description: %s\n", server.Description)
170-
fmt.Printf("Tier: %s\n", server.Tier)
171-
fmt.Printf("Status: %s\n", server.Status)
172-
fmt.Printf("Transport: %s\n", server.Transport)
173-
if (server.Transport == "sse" || server.Transport == "streamable-http") && server.TargetPort > 0 {
174-
fmt.Printf("Target Port: %d\n", server.TargetPort)
172+
func printTextServerInfo(name string, server registry.ServerMetadata) {
173+
fmt.Printf("Name: %s\n", server.GetName())
174+
fmt.Printf("Type: %s\n", getServerType(server))
175+
fmt.Printf("Description: %s\n", server.GetDescription())
176+
fmt.Printf("Tier: %s\n", server.GetTier())
177+
fmt.Printf("Status: %s\n", server.GetStatus())
178+
fmt.Printf("Transport: %s\n", server.GetTransport())
179+
180+
// Type-specific information
181+
if !server.IsRemote() {
182+
// Container server
183+
if img, ok := server.(*registry.ImageMetadata); ok {
184+
fmt.Printf("Image: %s\n", img.Image)
185+
isHTTPTransport := img.Transport == transtypes.TransportTypeSSE.String() ||
186+
img.Transport == transtypes.TransportTypeStreamableHTTP.String()
187+
if isHTTPTransport && img.TargetPort > 0 {
188+
fmt.Printf("Target Port: %d\n", img.TargetPort)
189+
}
190+
fmt.Printf("Has Provenance: %s\n", map[bool]string{true: "Yes", false: "No"}[img.Provenance != nil])
191+
192+
// Print permissions
193+
if img.Permissions != nil {
194+
fmt.Println("\nPermissions:")
195+
196+
// Print read permissions
197+
if len(img.Permissions.Read) > 0 {
198+
fmt.Println(" Read:")
199+
for _, path := range img.Permissions.Read {
200+
fmt.Printf(" - %s\n", path)
201+
}
202+
}
203+
204+
// Print write permissions
205+
if len(img.Permissions.Write) > 0 {
206+
fmt.Println(" Write:")
207+
for _, path := range img.Permissions.Write {
208+
fmt.Printf(" - %s\n", path)
209+
}
210+
}
211+
212+
// Print network permissions
213+
if img.Permissions.Network != nil && img.Permissions.Network.Outbound != nil {
214+
fmt.Println(" Network:")
215+
outbound := img.Permissions.Network.Outbound
216+
217+
if outbound.InsecureAllowAll {
218+
fmt.Println(" Insecure Allow All: true")
219+
}
220+
221+
if len(outbound.AllowHost) > 0 {
222+
fmt.Printf(" Allow Host: %s\n", strings.Join(outbound.AllowHost, ", "))
223+
}
224+
225+
if len(outbound.AllowPort) > 0 {
226+
ports := make([]string, len(outbound.AllowPort))
227+
for i, port := range outbound.AllowPort {
228+
ports[i] = fmt.Sprintf("%d", port)
229+
}
230+
fmt.Printf(" Allow Port: %s\n", strings.Join(ports, ", "))
231+
}
232+
}
233+
}
234+
}
235+
} else {
236+
// Remote server
237+
if remote, ok := server.(*registry.RemoteServerMetadata); ok {
238+
fmt.Printf("URL: %s\n", remote.URL)
239+
240+
// Print headers
241+
if len(remote.Headers) > 0 {
242+
fmt.Println("\nHeaders:")
243+
for _, header := range remote.Headers {
244+
required := ""
245+
if header.Required {
246+
required = " (required)"
247+
}
248+
defaultValue := ""
249+
if header.Default != "" {
250+
defaultValue = fmt.Sprintf(" [default: %s]", header.Default)
251+
}
252+
fmt.Printf(" - %s%s%s: %s\n", header.Name, required, defaultValue, header.Description)
253+
}
254+
}
255+
256+
// Print OAuth config
257+
if remote.OAuthConfig != nil {
258+
fmt.Println("\nOAuth Configuration:")
259+
if remote.OAuthConfig.Issuer != "" {
260+
fmt.Printf(" Issuer: %s\n", remote.OAuthConfig.Issuer)
261+
}
262+
if remote.OAuthConfig.ClientID != "" {
263+
fmt.Printf(" Client ID: %s\n", remote.OAuthConfig.ClientID)
264+
}
265+
if len(remote.OAuthConfig.Scopes) > 0 {
266+
fmt.Printf(" Scopes: %s\n", strings.Join(remote.OAuthConfig.Scopes, ", "))
267+
}
268+
}
269+
}
175270
}
176-
fmt.Printf("Repository URL: %s\n", server.RepositoryURL)
177-
fmt.Printf("Has Provenance: %s\n", map[bool]string{true: "Yes", false: "No"}[server.Provenance != nil])
178271

179-
if server.Metadata != nil {
180-
fmt.Printf("Popularity: %d stars, %d pulls\n", server.Metadata.Stars, server.Metadata.Pulls)
181-
fmt.Printf("Last Updated: %s\n", server.Metadata.LastUpdated)
272+
fmt.Printf("Repository URL: %s\n", server.GetRepositoryURL())
273+
274+
// Print metadata
275+
if metadata := server.GetMetadata(); metadata != nil {
276+
fmt.Printf("Popularity: %d stars, %d pulls\n", metadata.Stars, metadata.Pulls)
277+
fmt.Printf("Last Updated: %s\n", metadata.LastUpdated)
182278
} else {
183279
fmt.Printf("Popularity: 0 stars, 0 pulls\n")
184280
fmt.Printf("Last Updated: N/A\n")
185281
}
186282

187283
// Print tools
188-
if len(server.Tools) > 0 {
189-
fmt.Println("Tools:")
190-
for _, tool := range server.Tools {
284+
if tools := server.GetTools(); len(tools) > 0 {
285+
fmt.Println("\nTools:")
286+
for _, tool := range tools {
191287
fmt.Printf(" - %s\n", tool)
192288
}
193289
}
194290

195291
// Print environment variables
196-
if len(server.EnvVars) > 0 {
292+
if envVars := server.GetEnvVars(); len(envVars) > 0 {
197293
fmt.Println("\nEnvironment Variables:")
198-
for _, envVar := range server.EnvVars {
294+
for _, envVar := range envVars {
199295
required := ""
200296
if envVar.Required {
201297
required = " (required)"
@@ -209,57 +305,18 @@ func printTextServerInfo(name string, server *registry.ImageMetadata) {
209305
}
210306

211307
// Print tags
212-
if len(server.Tags) > 0 {
213-
fmt.Println("Tags:")
214-
fmt.Printf(" %s\n", strings.Join(server.Tags, ", "))
215-
}
216-
217-
// Print permissions
218-
if server.Permissions != nil {
219-
fmt.Println("Permissions:")
220-
221-
// Print read permissions
222-
if len(server.Permissions.Read) > 0 {
223-
fmt.Println(" Read:")
224-
for _, path := range server.Permissions.Read {
225-
fmt.Printf(" - %s\n", path)
226-
}
227-
}
228-
229-
// Print write permissions
230-
if len(server.Permissions.Write) > 0 {
231-
fmt.Println(" Write:")
232-
for _, path := range server.Permissions.Write {
233-
fmt.Printf(" - %s\n", path)
234-
}
235-
}
236-
237-
// Print network permissions
238-
if server.Permissions.Network != nil && server.Permissions.Network.Outbound != nil {
239-
fmt.Println(" Network:")
240-
outbound := server.Permissions.Network.Outbound
241-
242-
if outbound.InsecureAllowAll {
243-
fmt.Println(" Insecure Allow All: true")
244-
}
245-
246-
if len(outbound.AllowHost) > 0 {
247-
fmt.Printf(" Allow Host: %s\n", strings.Join(outbound.AllowHost, ", "))
248-
}
249-
250-
if len(outbound.AllowPort) > 0 {
251-
ports := make([]string, len(outbound.AllowPort))
252-
for i, port := range outbound.AllowPort {
253-
ports[i] = fmt.Sprintf("%d", port)
254-
}
255-
fmt.Printf(" Allow Port: %s\n", strings.Join(ports, ", "))
256-
}
257-
}
308+
if tags := server.GetTags(); len(tags) > 0 {
309+
fmt.Println("\nTags:")
310+
fmt.Printf(" %s\n", strings.Join(tags, ", "))
258311
}
259312

260313
// Print example command
261-
fmt.Println("Example Command:")
262-
fmt.Printf(" thv run %s\n", name)
314+
fmt.Println("\nExample Command:")
315+
if server.IsRemote() {
316+
fmt.Printf(" thv proxy %s\n", name)
317+
} else {
318+
fmt.Printf(" thv run %s\n", name)
319+
}
263320
}
264321

265322
// truncateString truncates a string to the specified length and adds "..." if truncated

cmd/thv/app/search.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"encoding/json"
55
"fmt"
66
"os"
7-
"sort"
87
"text/tabwriter"
98

109
"github.com/spf13/cobra"
@@ -49,10 +48,8 @@ func searchCmdFunc(_ *cobra.Command, args []string) error {
4948
return nil
5049
}
5150

52-
// Sort servers by name
53-
sort.Slice(servers, func(i, j int) bool {
54-
return servers[i].Name < servers[j].Name
55-
})
51+
// Sort servers by name using the utility function
52+
registry.SortServersByName(servers)
5653

5754
// Output based on format
5855
switch searchFormat {
@@ -66,7 +63,7 @@ func searchCmdFunc(_ *cobra.Command, args []string) error {
6663
}
6764

6865
// printJSONSearchResults prints servers in JSON format
69-
func printJSONSearchResults(servers []*registry.ImageMetadata) error {
66+
func printJSONSearchResults(servers []registry.ServerMetadata) error {
7067
// Marshal to JSON
7168
jsonData, err := json.MarshalIndent(servers, "", " ")
7269
if err != nil {
@@ -79,20 +76,33 @@ func printJSONSearchResults(servers []*registry.ImageMetadata) error {
7976
}
8077

8178
// printTextSearchResults prints servers in text format
82-
func printTextSearchResults(servers []*registry.ImageMetadata) {
79+
func printTextSearchResults(servers []registry.ServerMetadata) {
8380
// Create a tabwriter for pretty output
8481
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
85-
fmt.Fprintln(w, "NAME\tDESCRIPTION\tTRANSPORT\tSTARS\tPULLS")
82+
fmt.Fprintln(w, "NAME\tTYPE\tDESCRIPTION\tTRANSPORT\tSTARS\tPULLS")
8683

8784
// Print server information
8885
for _, server := range servers {
86+
stars := 0
87+
pulls := 0
88+
if metadata := server.GetMetadata(); metadata != nil {
89+
stars = metadata.Stars
90+
pulls = metadata.Pulls
91+
}
92+
93+
serverType := "container"
94+
if server.IsRemote() {
95+
serverType = "remote"
96+
}
97+
8998
// Print server information
90-
fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%d\n",
91-
server.Name,
92-
truncateSearchString(server.Description, 60),
93-
server.Transport,
94-
server.Metadata.Stars,
95-
server.Metadata.Pulls,
99+
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\t%d\n",
100+
server.GetName(),
101+
serverType,
102+
truncateSearchString(server.GetDescription(), 50),
103+
server.GetTransport(),
104+
stars,
105+
pulls,
96106
)
97107
}
98108

docs/server/docs.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/server/swagger.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)