@@ -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
147191func 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