Skip to content

Commit 46aa42d

Browse files
committed
Add a command to find services by name
1 parent 2c5aa77 commit 46aa42d

File tree

4 files changed

+560
-31
lines changed

4 files changed

+560
-31
lines changed

internal/tiger/cmd/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const (
1212
ExitPermissionDenied = 5 // Permission denied
1313
ExitServiceNotFound = 6 // Service not found
1414
ExitUpdateAvailable = 7 // Update available
15+
ExitMultipleMatches = 8 // Multiple resources match
1516
)
1617

1718
// exitCodeError creates an error that will cause the program to exit with the specified code

internal/tiger/cmd/service.go

Lines changed: 121 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func buildServiceCmd() *cobra.Command {
3434
// Add all subcommands
3535
cmd.AddCommand(buildServiceGetCmd())
3636
cmd.AddCommand(buildServiceListCmd())
37+
cmd.AddCommand(buildServiceFindCmd())
3738
cmd.AddCommand(buildServiceCreateCmd())
3839
cmd.AddCommand(buildServiceDeleteCmd())
3940
cmd.AddCommand(buildServiceUpdatePasswordCmd())
@@ -143,6 +144,49 @@ Examples:
143144
return cmd
144145
}
145146

147+
// fetchAllServices fetches all services for a project, handling authentication and common errors
148+
func fetchAllServices(cmd *cobra.Command, cfg *config.Config) ([]api.Service, error) {
149+
projectID := cfg.ProjectID
150+
if projectID == "" {
151+
return nil, fmt.Errorf("project ID is required. Set it using login with --project-id")
152+
}
153+
154+
// Get API key for authentication
155+
apiKey, err := getAPIKeyForService()
156+
if err != nil {
157+
return nil, exitWithCode(ExitAuthenticationError, fmt.Errorf("authentication required: %w", err))
158+
}
159+
160+
// Create API client
161+
client, err := api.NewTigerClient(apiKey)
162+
if err != nil {
163+
return nil, fmt.Errorf("failed to create API client: %w", err)
164+
}
165+
166+
// Make API call to list services
167+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
168+
defer cancel()
169+
170+
resp, err := client.GetProjectsProjectIdServicesWithResponse(ctx, projectID)
171+
if err != nil {
172+
return nil, fmt.Errorf("failed to list services: %w", err)
173+
}
174+
175+
// Handle API response
176+
if resp.StatusCode() != 200 {
177+
return nil, exitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX)
178+
}
179+
180+
if resp.JSON200 == nil || len(*resp.JSON200) == 0 {
181+
statusOutput := cmd.ErrOrStderr()
182+
fmt.Fprintln(statusOutput, "🏜️ No services found! Your project is looking a bit empty.")
183+
fmt.Fprintln(statusOutput, "🚀 Ready to get started? Create your first service with: tiger service create")
184+
return []api.Service{}, nil
185+
}
186+
187+
return *resp.JSON200, nil
188+
}
189+
146190
// serviceListCmd represents the list command under service
147191
func buildServiceListCmd() *cobra.Command {
148192
var output string
@@ -163,60 +207,106 @@ func buildServiceListCmd() *cobra.Command {
163207
cfg.Output = output
164208
}
165209

166-
projectID := cfg.ProjectID
167-
if projectID == "" {
168-
return fmt.Errorf("project ID is required. Set it using login with --project-id")
169-
}
170-
171210
cmd.SilenceUsage = true
172211

173-
// Get API key for authentication
174-
apiKey, err := getAPIKeyForService()
212+
// Fetch all services using shared function
213+
services, err := fetchAllServices(cmd, cfg)
175214
if err != nil {
176-
return exitWithCode(ExitAuthenticationError, fmt.Errorf("authentication required: %w", err))
215+
return err
177216
}
178217

179-
// Create API client
180-
client, err := api.NewTigerClient(apiKey)
181-
if err != nil {
182-
return fmt.Errorf("failed to create API client: %w", err)
218+
// If no services, fetchAllServices already printed the message
219+
if len(services) == 0 {
220+
return nil
183221
}
184222

185-
// Make API call to list services
186-
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
187-
defer cancel()
223+
// Output services in requested format
224+
return outputServices(cmd, services, cfg.Output)
225+
},
226+
}
227+
228+
cmd.Flags().VarP((*outputFlag)(&output), "output", "o", "output format (json, yaml, table)")
229+
230+
return cmd
231+
}
232+
233+
// buildServiceFindCmd represents the find command under service
234+
func buildServiceFindCmd() *cobra.Command {
235+
var output string
236+
var withPassword bool
237+
238+
cmd := &cobra.Command{
239+
Use: "find <name>",
240+
Short: "Find services by name",
241+
Long: `Find database services in the current project by name.
242+
243+
Searches for services with an exact name match. If exactly one service matches,
244+
displays detailed information like 'tiger service get'. If multiple services match,
245+
displays a list like 'tiger service list'.
246+
247+
Examples:
248+
# Find a service by name
249+
tiger service find my-production-db
188250
189-
resp, err := client.GetProjectsProjectIdServicesWithResponse(ctx, projectID)
251+
# Find with JSON output
252+
tiger service find my-db --output json`,
253+
Args: cobra.ExactArgs(1),
254+
RunE: func(cmd *cobra.Command, args []string) error {
255+
searchName := args[0]
256+
257+
// Get config
258+
cfg, err := config.Load()
190259
if err != nil {
191-
return fmt.Errorf("failed to list services: %w", err)
260+
return fmt.Errorf("failed to load config: %w", err)
192261
}
193262

194-
statusOutput := cmd.ErrOrStderr()
263+
// Use flag value if provided, otherwise use config value
264+
if cmd.Flags().Changed("output") {
265+
cfg.Output = output
266+
}
195267

196-
// Handle API response
197-
if resp.StatusCode() != 200 {
198-
return exitWithErrorFromStatusCode(resp.StatusCode(), resp.JSON4XX)
268+
cmd.SilenceUsage = true
269+
270+
// Fetch all services using shared function
271+
services, err := fetchAllServices(cmd, cfg)
272+
if err != nil {
273+
return err
199274
}
200275

201-
services := *resp.JSON200
276+
// If no services, fetchAllServices already printed the message
202277
if len(services) == 0 {
203-
fmt.Fprintln(statusOutput, "🏜️ No services found! Your project is looking a bit empty.")
204-
fmt.Fprintln(statusOutput, "🚀 Ready to get started? Create your first service with: tiger service create")
205-
return nil
278+
return exitWithCode(ExitGeneralError, fmt.Errorf("you have no services"))
206279
}
207280

208-
if resp.JSON200 == nil {
209-
fmt.Fprintln(statusOutput, "🏜️ No services found! Your project is looking a bit empty.")
210-
fmt.Fprintln(statusOutput, "🚀 Ready to get started? Create your first service with: tiger service create")
211-
return nil
281+
// Filter services by exact name match
282+
var matches []api.Service
283+
for _, service := range services {
284+
if service.Name != nil && *service.Name == searchName {
285+
matches = append(matches, service)
286+
}
212287
}
213288

214-
// Output services in requested format
215-
return outputServices(cmd, services, cfg.Output)
289+
// Handle no matches
290+
if len(matches) == 0 {
291+
return exitWithCode(ExitGeneralError, fmt.Errorf("no services found with name '%s'", searchName))
292+
}
293+
294+
if len(matches) > 1 {
295+
// Multiple matches - output like 'service list'
296+
if err := outputServices(cmd, matches, cfg.Output); err != nil {
297+
return err
298+
}
299+
cmd.SilenceErrors = true
300+
return exitWithCode(ExitMultipleMatches, nil)
301+
}
302+
303+
// Single match - output like 'service get'
304+
return outputService(cmd, matches[0], cfg.Output, withPassword, true)
216305
},
217306
}
218307

219308
cmd.Flags().VarP((*outputFlag)(&output), "output", "o", "output format (json, yaml, table)")
309+
cmd.Flags().BoolVar(&withPassword, "with-password", false, "Include password in output")
220310

221311
return cmd
222312
}

0 commit comments

Comments
 (0)