detached 979c2f9

refactor(auth): use remote.Resolve(), drop resolveRemoteEntry

All auth commands now use the same resolution path as every other
command. Local -r flags removed; inherited from root persistent flag.
commit
979c2f96e0731fe15d2c05ab8e05db6c91684834
author
brice · · 2026-04-04 11:27 UTC
parent
688083d
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +79 -106
M internal/adapters/in/cli/auth.go +71 -104
@@ -203,10 +203,7 @@ available while Gordon is running.`,
203203
204204 // newAuthLoginCmd creates the login command for remote authentication.
205205 func newAuthLoginCmd() *cobra.Command {
206- var (
207- remoteName string
208- token string
209- )
206+ var token string
210207
211208 cmd := &cobra.Command{
212209 Use: "login",
@@ -224,31 +221,30 @@ Examples:
224221 gordon auth login Verify existing token`,
225222 RunE: func(cmd *cobra.Command, args []string) error {
226223 if token != "" {
227- return runAuthLoginWithToken(cmd.Context(), remoteName, token, cmd.OutOrStdout())
224+ return runAuthLoginWithToken(cmd.Context(), token, cmd.OutOrStdout())
228225 }
229- return runAuthLoginVerify(cmd.Context(), remoteName, cmd.OutOrStdout())
226+ return runAuthLoginVerify(cmd.Context(), cmd.OutOrStdout())
230227 },
231228 }
232229
233- cmd.Flags().StringVarP(&remoteName, "remote", "r", "", "Remote to authenticate with (defaults to active remote)")
234230 cmd.Flags().StringVarP(&token, "token", "t", "", "Authentication token to store for the remote")
235231
236232 return cmd
237233 }
238234
239-func runAuthLoginWithToken(ctx context.Context, remoteName, token string, out io.Writer) error {
235+func runAuthLoginWithToken(ctx context.Context, token string, out io.Writer) error {
240236 token = strings.TrimSpace(token)
241237 if token == "" {
242238 return fmt.Errorf("token cannot be empty")
243239 }
244240
245- resolvedName, remoteConfig, err := resolveRemoteEntry(remoteName)
246- if err != nil {
247- return err
241+ // Resolve remote — but use the new token (not the stored one) for verification.
242+ resolved, isRemote := remote.Resolve(remoteFlag, "", insecureTLSFlag)
243+ if !isRemote {
244+ return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'")
248245 }
249246
250- insecureTLS := remote.ResolveInsecureTLSForRemote(insecureTLSFlag, resolvedName)
251- client := remote.NewClient(remoteConfig.URL, remoteClientOptions(token, insecureTLS)...)
247+ client := remote.NewClient(resolved.URL, remoteClientOptions(token, resolved.InsecureTLS)...)
252248 verifyCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
253249 defer cancel()
254250 verified := true
@@ -257,47 +253,59 @@ func runAuthLoginWithToken(ctx context.Context, remoteName, token string, out io
257253 verified = false
258254 }
259255
260- if err := remote.UpdateRemoteToken(resolvedName, token); err != nil {
256+ // Store token — requires a named remote.
257+ if resolved.Name == "" {
258+ _ = cliWriteLine(out, styles.RenderWarning("Ad-hoc URL: token verified but cannot be stored. Use 'gordon remotes add' to save this remote."))
259+ return nil
260+ }
261+ if err := remote.UpdateRemoteToken(resolved.Name, token); err != nil {
261262 return fmt.Errorf("failed to save token: %w", err)
262263 }
263264
264265 _ = cliWriteLine(out, "")
265- _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Token stored for remote '%s'", resolvedName)))
266+ _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Token stored for remote '%s'", resolved.Name)))
266267 if verified {
267268 _ = cliWriteLine(out, styles.RenderSuccess("Token verified with remote status endpoint"))
268269 }
269270 return nil
270271 }
271272
272-func runAuthLoginVerify(ctx context.Context, remoteName string, out io.Writer) error {
273- resolvedName, entry, err := resolveRemoteEntry(remoteName)
274- if err != nil {
275- return err
273+func runAuthLoginVerify(ctx context.Context, out io.Writer) error {
274+ resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag)
275+ if !isRemote {
276+ return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'")
276277 }
277-
278- token := remote.ResolveTokenForRemote(resolvedName, entry)
279- if token == "" {
280- return fmt.Errorf("no token stored for remote '%s'; use: gordon auth login --token <token>", resolvedName)
278+ if resolved.Token == "" {
279+ name := resolved.Name
280+ if name == "" {
281+ name = resolved.URL
282+ }
283+ return fmt.Errorf("no token stored for remote '%s'; use: gordon auth login --token <token>", name)
281284 }
282285
283- insecureTLS := remote.ResolveInsecureTLSForRemote(insecureTLSFlag, resolvedName)
284- client := remote.NewClient(entry.URL, remoteClientOptions(token, insecureTLS)...)
286+ client := remote.NewClient(resolved.URL, remoteClientOptions(resolved.Token, resolved.InsecureTLS)...)
285287 verifyCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
286288 defer cancel()
287289
288290 if _, err := client.GetStatus(verifyCtx); err != nil {
289- _ = cliWriteLine(out, styles.RenderWarning(fmt.Sprintf("Token verification failed for '%s': %v", resolvedName, err)))
291+ name := resolved.Name
292+ if name == "" {
293+ name = resolved.URL
294+ }
295+ _ = cliWriteLine(out, styles.RenderWarning(fmt.Sprintf("Token verification failed for '%s': %v", name, err)))
290296 return fmt.Errorf("authentication failed")
291297 }
292298
293- _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Authenticated to '%s'", resolvedName)))
299+ name := resolved.Name
300+ if name == "" {
301+ name = resolved.URL
302+ }
303+ _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Authenticated to '%s'", name)))
294304 return nil
295305 }
296306
297307 // newAuthShowTokenCmd creates the show-token command.
298308 func newAuthShowTokenCmd() *cobra.Command {
299- var remoteName string
300-
301309 cmd := &cobra.Command{
302310 Use: "show-token",
303311 Short: "Print the stored token for a remote",
@@ -313,38 +321,36 @@ Examples:
313321 gordon auth show-token --remote prod Show token for specific remote
314322 gordon auth show-token | pbcopy Copy token to clipboard`,
315323 RunE: func(cmd *cobra.Command, args []string) error {
316- return runAuthShowToken(remoteName, cmd.OutOrStdout())
324+ return runAuthShowToken(cmd.OutOrStdout())
317325 },
318326 }
319327
320- cmd.Flags().StringVarP(&remoteName, "remote", "r", "", "Remote to show token for (defaults to active remote)")
321-
322328 return cmd
323329 }
324330
325-func runAuthShowToken(remoteName string, out io.Writer) error {
326- resolvedName, entry, err := resolveRemoteEntry(remoteName)
327- if err != nil {
328- return err
331+func runAuthShowToken(out io.Writer) error {
332+ resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag)
333+ if !isRemote {
334+ return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'")
329335 }
330-
331- token := remote.ResolveTokenForRemote(resolvedName, entry)
332- if token == "" {
333- return fmt.Errorf("no token found for remote '%s'", resolvedName)
336+ if resolved.Token == "" {
337+ name := resolved.Name
338+ if name == "" {
339+ name = resolved.URL
340+ }
341+ return fmt.Errorf("no token found for remote '%s'", name)
334342 }
335343
336344 if f, ok := out.(*os.File); ok && isatty.IsTerminal(f.Fd()) {
337345 _ = cliWriteLine(os.Stderr, styles.RenderWarning("Warning: token will be visible in terminal scrollback"))
338346 }
339347
340- _ = cliWriteLine(out, token)
348+ _ = cliWriteLine(out, resolved.Token)
341349 return nil
342350 }
343351
344352 // newAuthLogoutCmd creates the logout command.
345353 func newAuthLogoutCmd() *cobra.Command {
346- var remoteName string
347-
348354 cmd := &cobra.Command{
349355 Use: "logout",
350356 Short: "Remove stored authentication for a remote",
@@ -357,49 +363,31 @@ Examples:
357363 gordon auth logout Logout from active remote
358364 gordon auth logout --remote prod Logout from specific remote`,
359365 RunE: func(cmd *cobra.Command, args []string) error {
360- return runAuthLogout(remoteName, cmd.OutOrStdout())
366+ return runAuthLogout(cmd.OutOrStdout())
361367 },
362368 }
363369
364- cmd.Flags().StringVarP(&remoteName, "remote", "r", "", "Remote to logout from (defaults to active remote)")
365-
366370 return cmd
367371 }
368372
369-func runAuthLogout(remoteName string, out io.Writer) error {
370- resolvedName, _, err := resolveRemoteEntry(remoteName)
371- if err != nil {
372- return err
373+func runAuthLogout(out io.Writer) error {
374+ resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag)
375+ if !isRemote {
376+ return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'")
377+ }
378+ if resolved.Name == "" {
379+ return fmt.Errorf("cannot logout from ad-hoc URL. Use 'gordon remotes add' to save this remote first")
373380 }
374381
375- if err := remote.ClearRemoteToken(resolvedName); err != nil {
382+ if err := remote.ClearRemoteToken(resolved.Name); err != nil {
376383 return fmt.Errorf("failed to clear token: %w", err)
377384 }
378385
379- _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Logged out from remote '%s'", resolvedName)))
386+ _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Logged out from remote '%s'", resolved.Name)))
380387 return nil
381388 }
382389
383-func resolveRemoteEntry(remoteName string) (string, remote.RemoteEntry, error) {
384- config, err := remote.LoadRemotes("")
385- if err != nil {
386- return "", remote.RemoteEntry{}, fmt.Errorf("failed to load remotes: %w", err)
387- }
388390
389- if remoteName == "" {
390- remoteName = config.Active
391- }
392- if remoteName == "" {
393- return "", remote.RemoteEntry{}, fmt.Errorf("no remote specified and no active remote set. Use --remote or 'gordon remote use <name>'")
394- }
395-
396- remoteConfig, ok := config.Remotes[remoteName]
397- if !ok {
398- return "", remote.RemoteEntry{}, fmt.Errorf("remote '%s' not found", remoteName)
399- }
400-
401- return remoteName, remoteConfig, nil
402-}
403391
404392 // runShowInternalAuth displays the internal registry credentials.
405393 func runShowInternalAuth() error {
@@ -711,7 +699,6 @@ func createAuthServiceForCLI(cfg *cliConfig, log zerowrap.Logger) (*auth.Service
711699
712700 // newAuthStatusCmd creates the auth status command.
713701 func newAuthStatusCmd() *cobra.Command {
714- var remoteNameFlag string
715702 cmd := &cobra.Command{
716703 Use: "status",
717704 Short: "Check authentication session status",
@@ -723,54 +710,34 @@ Examples:
723710 gordon auth status Check status of active remote
724711 gordon auth status --remote prod Check specific remote`,
725712 RunE: func(cmd *cobra.Command, args []string) error {
726- return runAuthStatus(remoteNameFlag)
713+ return runAuthStatus()
727714 },
728715 }
729- cmd.Flags().StringVarP(&remoteNameFlag, "remote", "r", "", "Check specific remote")
730716 return cmd
731717 }
732718
733719 // runAuthStatus checks authentication status for the active remote.
734-func runAuthStatus(remoteNameArg string) error {
735- // Load remotes config
736- config, err := remote.LoadRemotes("")
737- if err != nil {
738- return fmt.Errorf("failed to load remotes: %w", err)
720+func runAuthStatus() error {
721+ resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag)
722+ if !isRemote {
723+ return fmt.Errorf("no active remote configured. Use 'gordon remotes use <name>' or --remote flag")
739724 }
740725
741- // Determine remote name
742- remoteName := remoteNameArg
743- if remoteName == "" {
744- remoteName = config.Active
745- }
746- if remoteName == "" {
747- return fmt.Errorf("no active remote configured. Use 'gordon remote use <name>' or --remote flag")
748- }
726+ client := remote.NewClient(resolved.URL, remoteClientOptions(resolved.Token, resolved.InsecureTLS)...)
749727
750- remoteConfig, ok := config.Remotes[remoteName]
751- if !ok {
752- if remoteNameArg != "" {
753- return fmt.Errorf("remote '%s' not found", remoteName)
754- }
755- return fmt.Errorf("active remote '%s' not found", remoteName)
728+ name := resolved.Name
729+ if name == "" {
730+ name = resolved.URL
756731 }
732+ fmt.Printf("Checking authentication status for '%s'...\n", name)
757733
758- // Create client
759- token := remote.ResolveTokenForRemote(remoteName, remoteConfig)
760- insecureTLS := remote.ResolveInsecureTLSForRemote(insecureTLSFlag, remoteName)
761- client := remote.NewClient(remoteConfig.URL, remoteClientOptions(token, insecureTLS)...)
762-
763- // Verify auth
764734 ctx := context.Background()
765- fmt.Printf("Checking authentication status for '%s'...\n", remoteName)
766-
767735 status, err := client.VerifyAuth(ctx)
768736 if err != nil {
769- return handleAuthVerifyError(err, remoteName, remoteConfig.URL)
737+ return handleAuthVerifyError(err, name, resolved.URL)
770738 }
771739
772- // Display status
773- displayAuthStatus(remoteName, remoteConfig.URL, status)
740+ displayAuthStatus(name, resolved.URL, status)
774741 return nil
775742 }
776743
@@ -203,10 +203,7 @@ available while Gordon is running.`,
203 203
204 // newAuthLoginCmd creates the login command for remote authentication.204 // newAuthLoginCmd creates the login command for remote authentication.
205 func newAuthLoginCmd() *cobra.Command {205 func newAuthLoginCmd() *cobra.Command {
206- var (206+ var token string
207- remoteName string
208- token string
209- )
210 207
211 cmd := &cobra.Command{208 cmd := &cobra.Command{
212 Use: "login",209 Use: "login",
@@ -224,31 +221,30 @@ Examples:
224 gordon auth login Verify existing token`,221 gordon auth login Verify existing token`,
225 RunE: func(cmd *cobra.Command, args []string) error {222 RunE: func(cmd *cobra.Command, args []string) error {
226 if token != "" {223 if token != "" {
227- return runAuthLoginWithToken(cmd.Context(), remoteName, token, cmd.OutOrStdout())224+ return runAuthLoginWithToken(cmd.Context(), token, cmd.OutOrStdout())
228 }225 }
229- return runAuthLoginVerify(cmd.Context(), remoteName, cmd.OutOrStdout())226+ return runAuthLoginVerify(cmd.Context(), cmd.OutOrStdout())
230 },227 },
231 }228 }
232 229
233- cmd.Flags().StringVarP(&remoteName, "remote", "r", "", "Remote to authenticate with (defaults to active remote)")
234 cmd.Flags().StringVarP(&token, "token", "t", "", "Authentication token to store for the remote")230 cmd.Flags().StringVarP(&token, "token", "t", "", "Authentication token to store for the remote")
235 231
236 return cmd232 return cmd
237 }233 }
238 234
239-func runAuthLoginWithToken(ctx context.Context, remoteName, token string, out io.Writer) error {235+func runAuthLoginWithToken(ctx context.Context, token string, out io.Writer) error {
240 token = strings.TrimSpace(token)236 token = strings.TrimSpace(token)
241 if token == "" {237 if token == "" {
242 return fmt.Errorf("token cannot be empty")238 return fmt.Errorf("token cannot be empty")
243 }239 }
244 240
245- resolvedName, remoteConfig, err := resolveRemoteEntry(remoteName)241+ // Resolve remote — but use the new token (not the stored one) for verification.
246- if err != nil {242+ resolved, isRemote := remote.Resolve(remoteFlag, "", insecureTLSFlag)
247- return err243+ if !isRemote {
244+ return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'")
248 }245 }
249 246
250- insecureTLS := remote.ResolveInsecureTLSForRemote(insecureTLSFlag, resolvedName)247+ client := remote.NewClient(resolved.URL, remoteClientOptions(token, resolved.InsecureTLS)...)
251- client := remote.NewClient(remoteConfig.URL, remoteClientOptions(token, insecureTLS)...)
252 verifyCtx, cancel := context.WithTimeout(ctx, 10*time.Second)248 verifyCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
253 defer cancel()249 defer cancel()
254 verified := true250 verified := true
@@ -257,47 +253,59 @@ func runAuthLoginWithToken(ctx context.Context, remoteName, token string, out io
257 verified = false253 verified = false
258 }254 }
259 255
260- if err := remote.UpdateRemoteToken(resolvedName, token); err != nil {256+ // Store token — requires a named remote.
257+ if resolved.Name == "" {
258+ _ = cliWriteLine(out, styles.RenderWarning("Ad-hoc URL: token verified but cannot be stored. Use 'gordon remotes add' to save this remote."))
259+ return nil
260+ }
261+ if err := remote.UpdateRemoteToken(resolved.Name, token); err != nil {
261 return fmt.Errorf("failed to save token: %w", err)262 return fmt.Errorf("failed to save token: %w", err)
262 }263 }
263 264
264 _ = cliWriteLine(out, "")265 _ = cliWriteLine(out, "")
265- _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Token stored for remote '%s'", resolvedName)))266+ _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Token stored for remote '%s'", resolved.Name)))
266 if verified {267 if verified {
267 _ = cliWriteLine(out, styles.RenderSuccess("Token verified with remote status endpoint"))268 _ = cliWriteLine(out, styles.RenderSuccess("Token verified with remote status endpoint"))
268 }269 }
269 return nil270 return nil
270 }271 }
271 272
272-func runAuthLoginVerify(ctx context.Context, remoteName string, out io.Writer) error {273+func runAuthLoginVerify(ctx context.Context, out io.Writer) error {
273- resolvedName, entry, err := resolveRemoteEntry(remoteName)274+ resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag)
274- if err != nil {275+ if !isRemote {
275- return err276+ return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'")
276 }277 }
277-278+ if resolved.Token == "" {
278- token := remote.ResolveTokenForRemote(resolvedName, entry)279+ name := resolved.Name
279- if token == "" {280+ if name == "" {
280- return fmt.Errorf("no token stored for remote '%s'; use: gordon auth login --token <token>", resolvedName)281+ name = resolved.URL
282+ }
283+ return fmt.Errorf("no token stored for remote '%s'; use: gordon auth login --token <token>", name)
281 }284 }
282 285
283- insecureTLS := remote.ResolveInsecureTLSForRemote(insecureTLSFlag, resolvedName)286+ client := remote.NewClient(resolved.URL, remoteClientOptions(resolved.Token, resolved.InsecureTLS)...)
284- client := remote.NewClient(entry.URL, remoteClientOptions(token, insecureTLS)...)
285 verifyCtx, cancel := context.WithTimeout(ctx, 10*time.Second)287 verifyCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
286 defer cancel()288 defer cancel()
287 289
288 if _, err := client.GetStatus(verifyCtx); err != nil {290 if _, err := client.GetStatus(verifyCtx); err != nil {
289- _ = cliWriteLine(out, styles.RenderWarning(fmt.Sprintf("Token verification failed for '%s': %v", resolvedName, err)))291+ name := resolved.Name
292+ if name == "" {
293+ name = resolved.URL
294+ }
295+ _ = cliWriteLine(out, styles.RenderWarning(fmt.Sprintf("Token verification failed for '%s': %v", name, err)))
290 return fmt.Errorf("authentication failed")296 return fmt.Errorf("authentication failed")
291 }297 }
292 298
293- _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Authenticated to '%s'", resolvedName)))299+ name := resolved.Name
300+ if name == "" {
301+ name = resolved.URL
302+ }
303+ _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Authenticated to '%s'", name)))
294 return nil304 return nil
295 }305 }
296 306
297 // newAuthShowTokenCmd creates the show-token command.307 // newAuthShowTokenCmd creates the show-token command.
298 func newAuthShowTokenCmd() *cobra.Command {308 func newAuthShowTokenCmd() *cobra.Command {
299- var remoteName string
300-
301 cmd := &cobra.Command{309 cmd := &cobra.Command{
302 Use: "show-token",310 Use: "show-token",
303 Short: "Print the stored token for a remote",311 Short: "Print the stored token for a remote",
@@ -313,38 +321,36 @@ Examples:
313 gordon auth show-token --remote prod Show token for specific remote321 gordon auth show-token --remote prod Show token for specific remote
314 gordon auth show-token | pbcopy Copy token to clipboard`,322 gordon auth show-token | pbcopy Copy token to clipboard`,
315 RunE: func(cmd *cobra.Command, args []string) error {323 RunE: func(cmd *cobra.Command, args []string) error {
316- return runAuthShowToken(remoteName, cmd.OutOrStdout())324+ return runAuthShowToken(cmd.OutOrStdout())
317 },325 },
318 }326 }
319 327
320- cmd.Flags().StringVarP(&remoteName, "remote", "r", "", "Remote to show token for (defaults to active remote)")
321-
322 return cmd328 return cmd
323 }329 }
324 330
325-func runAuthShowToken(remoteName string, out io.Writer) error {331+func runAuthShowToken(out io.Writer) error {
326- resolvedName, entry, err := resolveRemoteEntry(remoteName)332+ resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag)
327- if err != nil {333+ if !isRemote {
328- return err334+ return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'")
329 }335 }
330-336+ if resolved.Token == "" {
331- token := remote.ResolveTokenForRemote(resolvedName, entry)337+ name := resolved.Name
332- if token == "" {338+ if name == "" {
333- return fmt.Errorf("no token found for remote '%s'", resolvedName)339+ name = resolved.URL
340+ }
341+ return fmt.Errorf("no token found for remote '%s'", name)
334 }342 }
335 343
336 if f, ok := out.(*os.File); ok && isatty.IsTerminal(f.Fd()) {344 if f, ok := out.(*os.File); ok && isatty.IsTerminal(f.Fd()) {
337 _ = cliWriteLine(os.Stderr, styles.RenderWarning("Warning: token will be visible in terminal scrollback"))345 _ = cliWriteLine(os.Stderr, styles.RenderWarning("Warning: token will be visible in terminal scrollback"))
338 }346 }
339 347
340- _ = cliWriteLine(out, token)348+ _ = cliWriteLine(out, resolved.Token)
341 return nil349 return nil
342 }350 }
343 351
344 // newAuthLogoutCmd creates the logout command.352 // newAuthLogoutCmd creates the logout command.
345 func newAuthLogoutCmd() *cobra.Command {353 func newAuthLogoutCmd() *cobra.Command {
346- var remoteName string
347-
348 cmd := &cobra.Command{354 cmd := &cobra.Command{
349 Use: "logout",355 Use: "logout",
350 Short: "Remove stored authentication for a remote",356 Short: "Remove stored authentication for a remote",
@@ -357,49 +363,31 @@ Examples:
357 gordon auth logout Logout from active remote363 gordon auth logout Logout from active remote
358 gordon auth logout --remote prod Logout from specific remote`,364 gordon auth logout --remote prod Logout from specific remote`,
359 RunE: func(cmd *cobra.Command, args []string) error {365 RunE: func(cmd *cobra.Command, args []string) error {
360- return runAuthLogout(remoteName, cmd.OutOrStdout())366+ return runAuthLogout(cmd.OutOrStdout())
361 },367 },
362 }368 }
363 369
364- cmd.Flags().StringVarP(&remoteName, "remote", "r", "", "Remote to logout from (defaults to active remote)")
365-
366 return cmd370 return cmd
367 }371 }
368 372
369-func runAuthLogout(remoteName string, out io.Writer) error {373+func runAuthLogout(out io.Writer) error {
370- resolvedName, _, err := resolveRemoteEntry(remoteName)374+ resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag)
371- if err != nil {375+ if !isRemote {
372- return err376+ return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'")
377+ }
378+ if resolved.Name == "" {
379+ return fmt.Errorf("cannot logout from ad-hoc URL. Use 'gordon remotes add' to save this remote first")
373 }380 }
374 381
375- if err := remote.ClearRemoteToken(resolvedName); err != nil {382+ if err := remote.ClearRemoteToken(resolved.Name); err != nil {
376 return fmt.Errorf("failed to clear token: %w", err)383 return fmt.Errorf("failed to clear token: %w", err)
377 }384 }
378 385
379- _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Logged out from remote '%s'", resolvedName)))386+ _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Logged out from remote '%s'", resolved.Name)))
380 return nil387 return nil
381 }388 }
382 389
383-func resolveRemoteEntry(remoteName string) (string, remote.RemoteEntry, error) {
384- config, err := remote.LoadRemotes("")
385- if err != nil {
386- return "", remote.RemoteEntry{}, fmt.Errorf("failed to load remotes: %w", err)
387- }
388 390
389- if remoteName == "" {
390- remoteName = config.Active
391- }
392- if remoteName == "" {
393- return "", remote.RemoteEntry{}, fmt.Errorf("no remote specified and no active remote set. Use --remote or 'gordon remote use <name>'")
394- }
395-
396- remoteConfig, ok := config.Remotes[remoteName]
397- if !ok {
398- return "", remote.RemoteEntry{}, fmt.Errorf("remote '%s' not found", remoteName)
399- }
400-
401- return remoteName, remoteConfig, nil
402-}
403 391
404 // runShowInternalAuth displays the internal registry credentials.392 // runShowInternalAuth displays the internal registry credentials.
405 func runShowInternalAuth() error {393 func runShowInternalAuth() error {
@@ -711,7 +699,6 @@ func createAuthServiceForCLI(cfg *cliConfig, log zerowrap.Logger) (*auth.Service
711 699
712 // newAuthStatusCmd creates the auth status command.700 // newAuthStatusCmd creates the auth status command.
713 func newAuthStatusCmd() *cobra.Command {701 func newAuthStatusCmd() *cobra.Command {
714- var remoteNameFlag string
715 cmd := &cobra.Command{702 cmd := &cobra.Command{
716 Use: "status",703 Use: "status",
717 Short: "Check authentication session status",704 Short: "Check authentication session status",
@@ -723,54 +710,34 @@ Examples:
723 gordon auth status Check status of active remote710 gordon auth status Check status of active remote
724 gordon auth status --remote prod Check specific remote`,711 gordon auth status --remote prod Check specific remote`,
725 RunE: func(cmd *cobra.Command, args []string) error {712 RunE: func(cmd *cobra.Command, args []string) error {
726- return runAuthStatus(remoteNameFlag)713+ return runAuthStatus()
727 },714 },
728 }715 }
729- cmd.Flags().StringVarP(&remoteNameFlag, "remote", "r", "", "Check specific remote")
730 return cmd716 return cmd
731 }717 }
732 718
733 // runAuthStatus checks authentication status for the active remote.719 // runAuthStatus checks authentication status for the active remote.
734-func runAuthStatus(remoteNameArg string) error {720+func runAuthStatus() error {
735- // Load remotes config721+ resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag)
736- config, err := remote.LoadRemotes("")722+ if !isRemote {
737- if err != nil {723+ return fmt.Errorf("no active remote configured. Use 'gordon remotes use <name>' or --remote flag")
738- return fmt.Errorf("failed to load remotes: %w", err)
739 }724 }
740 725
741- // Determine remote name726+ client := remote.NewClient(resolved.URL, remoteClientOptions(resolved.Token, resolved.InsecureTLS)...)
742- remoteName := remoteNameArg
743- if remoteName == "" {
744- remoteName = config.Active
745- }
746- if remoteName == "" {
747- return fmt.Errorf("no active remote configured. Use 'gordon remote use <name>' or --remote flag")
748- }
749 727
750- remoteConfig, ok := config.Remotes[remoteName]728+ name := resolved.Name
751- if !ok {729+ if name == "" {
752- if remoteNameArg != "" {730+ name = resolved.URL
753- return fmt.Errorf("remote '%s' not found", remoteName)
754- }
755- return fmt.Errorf("active remote '%s' not found", remoteName)
756 }731 }
732+ fmt.Printf("Checking authentication status for '%s'...\n", name)
757 733
758- // Create client
759- token := remote.ResolveTokenForRemote(remoteName, remoteConfig)
760- insecureTLS := remote.ResolveInsecureTLSForRemote(insecureTLSFlag, remoteName)
761- client := remote.NewClient(remoteConfig.URL, remoteClientOptions(token, insecureTLS)...)
762-
763- // Verify auth
764 ctx := context.Background()734 ctx := context.Background()
765- fmt.Printf("Checking authentication status for '%s'...\n", remoteName)
766-
767 status, err := client.VerifyAuth(ctx)735 status, err := client.VerifyAuth(ctx)
768 if err != nil {736 if err != nil {
769- return handleAuthVerifyError(err, remoteName, remoteConfig.URL)737+ return handleAuthVerifyError(err, name, resolved.URL)
770 }738 }
771 739
772- // Display status740+ displayAuthStatus(name, resolved.URL, status)
773- displayAuthStatus(remoteName, remoteConfig.URL, status)
774 return nil741 return nil
775 }742 }
776 743
M internal/adapters/in/cli/auth_test.go +8 -2
@@ -16,8 +16,9 @@ import (
1616 func TestRunAuthLoginWithToken_StoresToken(t *testing.T) {
1717 t.Setenv("XDG_CONFIG_HOME", t.TempDir())
1818 t.Setenv("PATH", filepath.Join("/nonexistent", "pass-disabled"))
19+ t.Setenv("GORDON_REMOTE", "")
20+ t.Setenv("GORDON_TOKEN", "")
1921
20- // Mock server — verification may fail (token exchange not mocked), but token should still be stored.
2122 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2223 w.WriteHeader(http.StatusUnauthorized)
2324 _, _ = w.Write([]byte(`{"error":"unauthorized"}`))
@@ -32,8 +33,13 @@ func TestRunAuthLoginWithToken_StoresToken(t *testing.T) {
3233 }
3334 assert.NoError(t, remote.SaveRemotes(remote.DefaultRemotesPath(), config))
3435
36+ // Set global flag to target "prod" remote
37+ origRemote := remoteFlag
38+ remoteFlag = "prod"
39+ t.Cleanup(func() { remoteFlag = origRemote })
40+
3541 var out bytes.Buffer
36- err := runAuthLoginWithToken(context.Background(), "", "token123", &out)
42+ err := runAuthLoginWithToken(context.Background(), "token123", &out)
3743 assert.NoError(t, err)
3844
3945 loaded, err := remote.LoadRemotes("")
@@ -16,8 +16,9 @@ import (
16 func TestRunAuthLoginWithToken_StoresToken(t *testing.T) {16 func TestRunAuthLoginWithToken_StoresToken(t *testing.T) {
17 t.Setenv("XDG_CONFIG_HOME", t.TempDir())17 t.Setenv("XDG_CONFIG_HOME", t.TempDir())
18 t.Setenv("PATH", filepath.Join("/nonexistent", "pass-disabled"))18 t.Setenv("PATH", filepath.Join("/nonexistent", "pass-disabled"))
19+ t.Setenv("GORDON_REMOTE", "")
20+ t.Setenv("GORDON_TOKEN", "")
19 21
20- // Mock server — verification may fail (token exchange not mocked), but token should still be stored.
21 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {22 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22 w.WriteHeader(http.StatusUnauthorized)23 w.WriteHeader(http.StatusUnauthorized)
23 _, _ = w.Write([]byte(`{"error":"unauthorized"}`))24 _, _ = w.Write([]byte(`{"error":"unauthorized"}`))
@@ -32,8 +33,13 @@ func TestRunAuthLoginWithToken_StoresToken(t *testing.T) {
32 }33 }
33 assert.NoError(t, remote.SaveRemotes(remote.DefaultRemotesPath(), config))34 assert.NoError(t, remote.SaveRemotes(remote.DefaultRemotesPath(), config))
34 35
36+ // Set global flag to target "prod" remote
37+ origRemote := remoteFlag
38+ remoteFlag = "prod"
39+ t.Cleanup(func() { remoteFlag = origRemote })
40+
35 var out bytes.Buffer41 var out bytes.Buffer
36- err := runAuthLoginWithToken(context.Background(), "", "token123", &out)42+ err := runAuthLoginWithToken(context.Background(), "token123", &out)
37 assert.NoError(t, err)43 assert.NoError(t, err)
38 44
39 loaded, err := remote.LoadRemotes("")45 loaded, err := remote.LoadRemotes("")

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close