feat: add gordon routes show command
- commit
- d6ce1c7e35dcfadea7ec51176b83c8be60dbc06a
- author
- brice · · 2026-03-14 03:21 UTC
- parent
- b7cdfee
- signature
- unsigned
2 files · +140 -0
M docs/cli/routes.md +56 -0
| @@ -12,6 +12,7 @@ Use `--remote` and `--token` to override. See [CLI Overview](./index.md). | |||
| 12 | 12 | | Subcommand | Description | | |
| 13 | 13 | |------------|-------------| | |
| 14 | 14 | | `list` | List all routes | | |
| 15 | + | | `show` | Show details for a single route | | |
| 15 | 16 | | `add` | Create or update a route | | |
| 16 | 17 | | `remove` | Remove a route | | |
| 17 | 18 | | `deploy` | Deploy a specific route | | |
| @@ -116,6 +117,61 @@ gordon routes add myapp.example.com myapp:latest --remote https://gordon.mydomai | |||
| 116 | 117 | ||
| 117 | 118 | --- | |
| 118 | 119 | ||
| 120 | + | ## gordon routes show | |
| 121 | + | ||
| 122 | + | Show detailed information about a single route. | |
| 123 | + | ||
| 124 | + | ```bash | |
| 125 | + | gordon routes show <domain> | |
| 126 | + | gordon routes show myapp.example.com | |
| 127 | + | gordon routes show myapp.example.com --json | |
| 128 | + | ``` | |
| 129 | + | ||
| 130 | + | ### Arguments | |
| 131 | + | ||
| 132 | + | | Argument | Description | | |
| 133 | + | |----------|-------------| | |
| 134 | + | | `<domain>` | The domain name of the route to inspect | | |
| 135 | + | ||
| 136 | + | ### Options | |
| 137 | + | ||
| 138 | + | | Option | Description | | |
| 139 | + | |--------|-------------| | |
| 140 | + | | `--json` | Output route details as JSON | | |
| 141 | + | | `--remote` | Remote Gordon URL | | |
| 142 | + | | `--token` | Authentication token for remote | | |
| 143 | + | ||
| 144 | + | ### Description | |
| 145 | + | ||
| 146 | + | Displays the configured image for the route plus any available container and HTTP health information. | |
| 147 | + | In local-only mode, health data may be unavailable. | |
| 148 | + | ||
| 149 | + | ### Examples | |
| 150 | + | ||
| 151 | + | ```bash | |
| 152 | + | # Local | |
| 153 | + | gordon routes show myapp.example.com | |
| 154 | + | ||
| 155 | + | # JSON | |
| 156 | + | gordon routes show myapp.example.com --json | |
| 157 | + | ||
| 158 | + | # Remote (override) | |
| 159 | + | gordon routes show myapp.example.com --remote https://gordon.mydomain.com --token $TOKEN | |
| 160 | + | ``` | |
| 161 | + | ||
| 162 | + | ### JSON Output | |
| 163 | + | ||
| 164 | + | ```json | |
| 165 | + | { | |
| 166 | + | "domain": "myapp.example.com", | |
| 167 | + | "image": "myapp:latest", | |
| 168 | + | "container_status": "running", | |
| 169 | + | "http_status": 200 | |
| 170 | + | } | |
| 171 | + | ``` | |
| 172 | + | ||
| 173 | + | --- | |
| 174 | + | ||
| 119 | 175 | ## gordon routes remove | |
| 120 | 176 | ||
| 121 | 177 | Remove a route. | |
| @@ -12,6 +12,7 @@ Use `--remote` and `--token` to override. See [CLI Overview](./index.md). | |||||
| 12 | | Subcommand | Description | | 12 | | Subcommand | Description | | ||
| 13 | |------------|-------------| | 13 | |------------|-------------| | ||
| 14 | | `list` | List all routes | | 14 | | `list` | List all routes | | ||
| 15 | + | | `show` | Show details for a single route | | |||
| 15 | | `add` | Create or update a route | | 16 | | `add` | Create or update a route | | ||
| 16 | | `remove` | Remove a route | | 17 | | `remove` | Remove a route | | ||
| 17 | | `deploy` | Deploy a specific route | | 18 | | `deploy` | Deploy a specific route | | ||
| @@ -116,6 +117,61 @@ gordon routes add myapp.example.com myapp:latest --remote https://gordon.mydomai | |||||
| 116 | 117 | ||||
| 117 | --- | 118 | --- | ||
| 118 | 119 | ||||
| 120 | + | ## gordon routes show | |||
| 121 | + | ||||
| 122 | + | Show detailed information about a single route. | |||
| 123 | + | ||||
| 124 | + | ```bash | |||
| 125 | + | gordon routes show <domain> | |||
| 126 | + | gordon routes show myapp.example.com | |||
| 127 | + | gordon routes show myapp.example.com --json | |||
| 128 | + | ``` | |||
| 129 | + | ||||
| 130 | + | ### Arguments | |||
| 131 | + | ||||
| 132 | + | | Argument | Description | | |||
| 133 | + | |----------|-------------| | |||
| 134 | + | | `<domain>` | The domain name of the route to inspect | | |||
| 135 | + | ||||
| 136 | + | ### Options | |||
| 137 | + | ||||
| 138 | + | | Option | Description | | |||
| 139 | + | |--------|-------------| | |||
| 140 | + | | `--json` | Output route details as JSON | | |||
| 141 | + | | `--remote` | Remote Gordon URL | | |||
| 142 | + | | `--token` | Authentication token for remote | | |||
| 143 | + | ||||
| 144 | + | ### Description | |||
| 145 | + | ||||
| 146 | + | Displays the configured image for the route plus any available container and HTTP health information. | |||
| 147 | + | In local-only mode, health data may be unavailable. | |||
| 148 | + | ||||
| 149 | + | ### Examples | |||
| 150 | + | ||||
| 151 | + | ```bash | |||
| 152 | + | # Local | |||
| 153 | + | gordon routes show myapp.example.com | |||
| 154 | + | ||||
| 155 | + | # JSON | |||
| 156 | + | gordon routes show myapp.example.com --json | |||
| 157 | + | ||||
| 158 | + | # Remote (override) | |||
| 159 | + | gordon routes show myapp.example.com --remote https://gordon.mydomain.com --token $TOKEN | |||
| 160 | + | ``` | |||
| 161 | + | ||||
| 162 | + | ### JSON Output | |||
| 163 | + | ||||
| 164 | + | ```json | |||
| 165 | + | { | |||
| 166 | + | "domain": "myapp.example.com", | |||
| 167 | + | "image": "myapp:latest", | |||
| 168 | + | "container_status": "running", | |||
| 169 | + | "http_status": 200 | |||
| 170 | + | } | |||
| 171 | + | ``` | |||
| 172 | + | ||||
| 173 | + | --- | |||
| 174 | + | ||||
| 119 | ## gordon routes remove | 175 | ## gordon routes remove | ||
| 120 | 176 | ||||
| 121 | Remove a route. | 177 | Remove a route. | ||
M internal/adapters/in/cli/routes.go +84 -0
| @@ -83,6 +83,7 @@ the local Gordon configuration.`, | |||
| 83 | 83 | } | |
| 84 | 84 | ||
| 85 | 85 | cmd.AddCommand(newRoutesListCmd()) | |
| 86 | + | cmd.AddCommand(newRoutesShowCmd()) | |
| 86 | 87 | cmd.AddCommand(newRoutesAddCmd()) | |
| 87 | 88 | cmd.AddCommand(newRoutesRemoveCmd()) | |
| 88 | 89 | ||
| @@ -133,6 +134,89 @@ func newRoutesListCmd() *cobra.Command { | |||
| 133 | 134 | return cmd | |
| 134 | 135 | } | |
| 135 | 136 | ||
| 137 | + | func newRoutesShowCmd() *cobra.Command { | |
| 138 | + | var jsonOut bool | |
| 139 | + | ||
| 140 | + | cmd := &cobra.Command{ | |
| 141 | + | Use: "show <domain>", | |
| 142 | + | Short: "Show details for a single route", | |
| 143 | + | Long: `Display detailed information about a specific route including its image, | |
| 144 | + | container status, and health. | |
| 145 | + | ||
| 146 | + | Examples: | |
| 147 | + | gordon routes show app.mydomain.com | |
| 148 | + | gordon routes show app.mydomain.com --json | |
| 149 | + | gordon routes show app.mydomain.com --remote https://gordon.mydomain.com`, | |
| 150 | + | Args: cobra.ExactArgs(1), | |
| 151 | + | RunE: func(cmd *cobra.Command, args []string) error { | |
| 152 | + | ctx := context.Background() | |
| 153 | + | handle, err := resolveControlPlane(configPath) | |
| 154 | + | if err != nil { | |
| 155 | + | return err | |
| 156 | + | } | |
| 157 | + | defer handle.close() | |
| 158 | + | return runRoutesShow(ctx, handle.plane, cmd.OutOrStdout(), args[0], jsonOut) | |
| 159 | + | }, | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | cmd.Flags().BoolVar(&jsonOut, "json", false, "Output as JSON") | |
| 163 | + | ||
| 164 | + | return cmd | |
| 165 | + | } | |
| 166 | + | ||
| 167 | + | func runRoutesShow(ctx context.Context, cp ControlPlane, out io.Writer, routeDomain string, jsonOut bool) error { | |
| 168 | + | route, err := cp.GetRoute(ctx, routeDomain) | |
| 169 | + | if err != nil { | |
| 170 | + | return fmt.Errorf("route not found: %w", err) | |
| 171 | + | } | |
| 172 | + | ||
| 173 | + | health, _ := cp.GetHealth(ctx) | |
| 174 | + | var routeHealth *remote.RouteHealth | |
| 175 | + | if health != nil { | |
| 176 | + | routeHealth = health[routeDomain] | |
| 177 | + | } | |
| 178 | + | ||
| 179 | + | containerStatus := "unknown" | |
| 180 | + | httpStatus := 0 | |
| 181 | + | if routeHealth != nil { | |
| 182 | + | containerStatus = routeHealth.ContainerStatus | |
| 183 | + | httpStatus = routeHealth.HTTPStatus | |
| 184 | + | } | |
| 185 | + | ||
| 186 | + | if jsonOut { | |
| 187 | + | payload := map[string]any{ | |
| 188 | + | "domain": route.Domain, | |
| 189 | + | "image": route.Image, | |
| 190 | + | "container_status": containerStatus, | |
| 191 | + | "http_status": httpStatus, | |
| 192 | + | } | |
| 193 | + | return writeJSON(out, payload) | |
| 194 | + | } | |
| 195 | + | ||
| 196 | + | if err := cliWriteLine(out, cliRenderTitle("Route: "+route.Domain)); err != nil { | |
| 197 | + | return err | |
| 198 | + | } | |
| 199 | + | if err := cliWriteLine(out, ""); err != nil { | |
| 200 | + | return err | |
| 201 | + | } | |
| 202 | + | if err := cliWriteLine(out, cliRenderMeta("Domain:", route.Domain)); err != nil { | |
| 203 | + | return err | |
| 204 | + | } | |
| 205 | + | if err := cliWriteLine(out, cliRenderMeta("Image:", route.Image)); err != nil { | |
| 206 | + | return err | |
| 207 | + | } | |
| 208 | + | if err := cliWriteLine(out, cliRenderMeta("Container:", containerStatus)); err != nil { | |
| 209 | + | return err | |
| 210 | + | } | |
| 211 | + | if httpStatus > 0 { | |
| 212 | + | if err := cliWriteLine(out, cliRenderMeta("HTTP Status:", fmt.Sprintf("%d", httpStatus))); err != nil { | |
| 213 | + | return err | |
| 214 | + | } | |
| 215 | + | } | |
| 216 | + | ||
| 217 | + | return nil | |
| 218 | + | } | |
| 219 | + | ||
| 136 | 220 | // runRoutesListRemote lists routes from a remote Gordon instance. | |
| 137 | 221 | func runRoutesListRemote(ctx context.Context, client *remote.Client, jsonOut bool, out io.Writer) error { | |
| 138 | 222 | routes, err := client.ListRoutesWithDetails(ctx) | |
| @@ -83,6 +83,7 @@ the local Gordon configuration.`, | |||||
| 83 | } | 83 | } | ||
| 84 | 84 | ||||
| 85 | cmd.AddCommand(newRoutesListCmd()) | 85 | cmd.AddCommand(newRoutesListCmd()) | ||
| 86 | + | cmd.AddCommand(newRoutesShowCmd()) | |||
| 86 | cmd.AddCommand(newRoutesAddCmd()) | 87 | cmd.AddCommand(newRoutesAddCmd()) | ||
| 87 | cmd.AddCommand(newRoutesRemoveCmd()) | 88 | cmd.AddCommand(newRoutesRemoveCmd()) | ||
| 88 | 89 | ||||
| @@ -133,6 +134,89 @@ func newRoutesListCmd() *cobra.Command { | |||||
| 133 | return cmd | 134 | return cmd | ||
| 134 | } | 135 | } | ||
| 135 | 136 | ||||
| 137 | + | func newRoutesShowCmd() *cobra.Command { | |||
| 138 | + | var jsonOut bool | |||
| 139 | + | ||||
| 140 | + | cmd := &cobra.Command{ | |||
| 141 | + | Use: "show <domain>", | |||
| 142 | + | Short: "Show details for a single route", | |||
| 143 | + | Long: `Display detailed information about a specific route including its image, | |||
| 144 | + | container status, and health. | |||
| 145 | + | ||||
| 146 | + | Examples: | |||
| 147 | + | gordon routes show app.mydomain.com | |||
| 148 | + | gordon routes show app.mydomain.com --json | |||
| 149 | + | gordon routes show app.mydomain.com --remote https://gordon.mydomain.com`, | |||
| 150 | + | Args: cobra.ExactArgs(1), | |||
| 151 | + | RunE: func(cmd *cobra.Command, args []string) error { | |||
| 152 | + | ctx := context.Background() | |||
| 153 | + | handle, err := resolveControlPlane(configPath) | |||
| 154 | + | if err != nil { | |||
| 155 | + | return err | |||
| 156 | + | } | |||
| 157 | + | defer handle.close() | |||
| 158 | + | return runRoutesShow(ctx, handle.plane, cmd.OutOrStdout(), args[0], jsonOut) | |||
| 159 | + | }, | |||
| 160 | + | } | |||
| 161 | + | ||||
| 162 | + | cmd.Flags().BoolVar(&jsonOut, "json", false, "Output as JSON") | |||
| 163 | + | ||||
| 164 | + | return cmd | |||
| 165 | + | } | |||
| 166 | + | ||||
| 167 | + | func runRoutesShow(ctx context.Context, cp ControlPlane, out io.Writer, routeDomain string, jsonOut bool) error { | |||
| 168 | + | route, err := cp.GetRoute(ctx, routeDomain) | |||
| 169 | + | if err != nil { | |||
| 170 | + | return fmt.Errorf("route not found: %w", err) | |||
| 171 | + | } | |||
| 172 | + | ||||
| 173 | + | health, _ := cp.GetHealth(ctx) | |||
| 174 | + | var routeHealth *remote.RouteHealth | |||
| 175 | + | if health != nil { | |||
| 176 | + | routeHealth = health[routeDomain] | |||
| 177 | + | } | |||
| 178 | + | ||||
| 179 | + | containerStatus := "unknown" | |||
| 180 | + | httpStatus := 0 | |||
| 181 | + | if routeHealth != nil { | |||
| 182 | + | containerStatus = routeHealth.ContainerStatus | |||
| 183 | + | httpStatus = routeHealth.HTTPStatus | |||
| 184 | + | } | |||
| 185 | + | ||||
| 186 | + | if jsonOut { | |||
| 187 | + | payload := map[string]any{ | |||
| 188 | + | "domain": route.Domain, | |||
| 189 | + | "image": route.Image, | |||
| 190 | + | "container_status": containerStatus, | |||
| 191 | + | "http_status": httpStatus, | |||
| 192 | + | } | |||
| 193 | + | return writeJSON(out, payload) | |||
| 194 | + | } | |||
| 195 | + | ||||
| 196 | + | if err := cliWriteLine(out, cliRenderTitle("Route: "+route.Domain)); err != nil { | |||
| 197 | + | return err | |||
| 198 | + | } | |||
| 199 | + | if err := cliWriteLine(out, ""); err != nil { | |||
| 200 | + | return err | |||
| 201 | + | } | |||
| 202 | + | if err := cliWriteLine(out, cliRenderMeta("Domain:", route.Domain)); err != nil { | |||
| 203 | + | return err | |||
| 204 | + | } | |||
| 205 | + | if err := cliWriteLine(out, cliRenderMeta("Image:", route.Image)); err != nil { | |||
| 206 | + | return err | |||
| 207 | + | } | |||
| 208 | + | if err := cliWriteLine(out, cliRenderMeta("Container:", containerStatus)); err != nil { | |||
| 209 | + | return err | |||
| 210 | + | } | |||
| 211 | + | if httpStatus > 0 { | |||
| 212 | + | if err := cliWriteLine(out, cliRenderMeta("HTTP Status:", fmt.Sprintf("%d", httpStatus))); err != nil { | |||
| 213 | + | return err | |||
| 214 | + | } | |||
| 215 | + | } | |||
| 216 | + | ||||
| 217 | + | return nil | |||
| 218 | + | } | |||
| 219 | + | ||||
| 136 | // runRoutesListRemote lists routes from a remote Gordon instance. | 220 | // runRoutesListRemote lists routes from a remote Gordon instance. | ||
| 137 | func runRoutesListRemote(ctx context.Context, client *remote.Client, jsonOut bool, out io.Writer) error { | 221 | func runRoutesListRemote(ctx context.Context, client *remote.Client, jsonOut bool, out io.Writer) error { | ||
| 138 | routes, err := client.ListRoutesWithDetails(ctx) | 222 | routes, err := client.ListRoutesWithDetails(ctx) | ||