feat(cli): add remote client methods for restart and tags API
Add Restart() to trigger container restarts via admin API with optional attachment restart. Add ListTags() to list available image tags from the registry. Fix SSE stream reader to process buffered data before checking for read errors, preventing dropped log lines at stream end.
- commit
- dc89b46ba109b53f3f9be8d8eeb677c72e6fd020
- author
- brice · · 2026-02-01 21:26 UTC
- committer
- brice · · 2026-02-02 18:05 UTC
- parent
- 7fedf97
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +49 -4
M internal/adapters/in/cli/remote/client.go +49 -4
| @@ -424,6 +424,51 @@ func (c *Client) Deploy(ctx context.Context, deployDomain string) (*DeployResult | |||
| 424 | 424 | return &result, nil | |
| 425 | 425 | } | |
| 426 | 426 | ||
| 427 | + | // Restart API | |
| 428 | + | ||
| 429 | + | // RestartResult contains the result of a restart. | |
| 430 | + | type RestartResult struct { | |
| 431 | + | Status string `json:"status"` | |
| 432 | + | Domain string `json:"domain"` | |
| 433 | + | } | |
| 434 | + | ||
| 435 | + | // Restart triggers a container restart for the specified domain. | |
| 436 | + | func (c *Client) Restart(ctx context.Context, restartDomain string, withAttachments bool) (*RestartResult, error) { | |
| 437 | + | path := "/restart/" + url.PathEscape(restartDomain) | |
| 438 | + | if withAttachments { | |
| 439 | + | path += "?attachments=true" | |
| 440 | + | } | |
| 441 | + | resp, err := c.request(ctx, http.MethodPost, path, nil) | |
| 442 | + | if err != nil { | |
| 443 | + | return nil, err | |
| 444 | + | } | |
| 445 | + | ||
| 446 | + | var result RestartResult | |
| 447 | + | if err := parseResponse(resp, &result); err != nil { | |
| 448 | + | return nil, err | |
| 449 | + | } | |
| 450 | + | ||
| 451 | + | return &result, nil | |
| 452 | + | } | |
| 453 | + | ||
| 454 | + | // Tags API | |
| 455 | + | ||
| 456 | + | // ListTags returns available tags for a repository. | |
| 457 | + | func (c *Client) ListTags(ctx context.Context, repository string) ([]string, error) { | |
| 458 | + | resp, err := c.request(ctx, http.MethodGet, "/tags/"+url.PathEscape(repository), nil) | |
| 459 | + | if err != nil { | |
| 460 | + | return nil, err | |
| 461 | + | } | |
| 462 | + | var result struct { | |
| 463 | + | Repository string `json:"repository"` | |
| 464 | + | Tags []string `json:"tags"` | |
| 465 | + | } | |
| 466 | + | if err := parseResponse(resp, &result); err != nil { | |
| 467 | + | return nil, err | |
| 468 | + | } | |
| 469 | + | return result.Tags, nil | |
| 470 | + | } | |
| 471 | + | ||
| 427 | 472 | // Logs API | |
| 428 | 473 | ||
| 429 | 474 | // GetProcessLogs returns Gordon process logs. | |
| @@ -596,13 +641,13 @@ func (c *Client) streamLogs(ctx context.Context, path string) (<-chan string, er | |||
| 596 | 641 | } | |
| 597 | 642 | ||
| 598 | 643 | n, err := resp.Body.Read(buf) | |
| 644 | + | if n > 0 { | |
| 645 | + | lineBuffer.Write(buf[:n]) | |
| 646 | + | } | |
| 599 | 647 | if err != nil { | |
| 600 | - | // EOF is expected when stream ends, other errors are ignored | |
| 601 | - | return | |
| 648 | + | return // EOF or context cancellation — clean exit | |
| 602 | 649 | } | |
| 603 | 650 | ||
| 604 | - | lineBuffer.Write(buf[:n]) | |
| 605 | - | ||
| 606 | 651 | // Process complete SSE events | |
| 607 | 652 | for { | |
| 608 | 653 | data := lineBuffer.String() | |
| @@ -424,6 +424,51 @@ func (c *Client) Deploy(ctx context.Context, deployDomain string) (*DeployResult | |||||
| 424 | return &result, nil | 424 | return &result, nil | ||
| 425 | } | 425 | } | ||
| 426 | 426 | ||||
| 427 | + | // Restart API | |||
| 428 | + | ||||
| 429 | + | // RestartResult contains the result of a restart. | |||
| 430 | + | type RestartResult struct { | |||
| 431 | + | Status string `json:"status"` | |||
| 432 | + | Domain string `json:"domain"` | |||
| 433 | + | } | |||
| 434 | + | ||||
| 435 | + | // Restart triggers a container restart for the specified domain. | |||
| 436 | + | func (c *Client) Restart(ctx context.Context, restartDomain string, withAttachments bool) (*RestartResult, error) { | |||
| 437 | + | path := "/restart/" + url.PathEscape(restartDomain) | |||
| 438 | + | if withAttachments { | |||
| 439 | + | path += "?attachments=true" | |||
| 440 | + | } | |||
| 441 | + | resp, err := c.request(ctx, http.MethodPost, path, nil) | |||
| 442 | + | if err != nil { | |||
| 443 | + | return nil, err | |||
| 444 | + | } | |||
| 445 | + | ||||
| 446 | + | var result RestartResult | |||
| 447 | + | if err := parseResponse(resp, &result); err != nil { | |||
| 448 | + | return nil, err | |||
| 449 | + | } | |||
| 450 | + | ||||
| 451 | + | return &result, nil | |||
| 452 | + | } | |||
| 453 | + | ||||
| 454 | + | // Tags API | |||
| 455 | + | ||||
| 456 | + | // ListTags returns available tags for a repository. | |||
| 457 | + | func (c *Client) ListTags(ctx context.Context, repository string) ([]string, error) { | |||
| 458 | + | resp, err := c.request(ctx, http.MethodGet, "/tags/"+url.PathEscape(repository), nil) | |||
| 459 | + | if err != nil { | |||
| 460 | + | return nil, err | |||
| 461 | + | } | |||
| 462 | + | var result struct { | |||
| 463 | + | Repository string `json:"repository"` | |||
| 464 | + | Tags []string `json:"tags"` | |||
| 465 | + | } | |||
| 466 | + | if err := parseResponse(resp, &result); err != nil { | |||
| 467 | + | return nil, err | |||
| 468 | + | } | |||
| 469 | + | return result.Tags, nil | |||
| 470 | + | } | |||
| 471 | + | ||||
| 427 | // Logs API | 472 | // Logs API | ||
| 428 | 473 | ||||
| 429 | // GetProcessLogs returns Gordon process logs. | 474 | // GetProcessLogs returns Gordon process logs. | ||
| @@ -596,13 +641,13 @@ func (c *Client) streamLogs(ctx context.Context, path string) (<-chan string, er | |||||
| 596 | } | 641 | } | ||
| 597 | 642 | ||||
| 598 | n, err := resp.Body.Read(buf) | 643 | n, err := resp.Body.Read(buf) | ||
| 644 | + | if n > 0 { | |||
| 645 | + | lineBuffer.Write(buf[:n]) | |||
| 646 | + | } | |||
| 599 | if err != nil { | 647 | if err != nil { | ||
| 600 | - | // EOF is expected when stream ends, other errors are ignored | 648 | + | return // EOF or context cancellation — clean exit |
| 601 | - | return | |||
| 602 | } | 649 | } | ||
| 603 | 650 | ||||
| 604 | - | lineBuffer.Write(buf[:n]) | |||
| 605 | - | ||||
| 606 | // Process complete SSE events | 651 | // Process complete SSE events | ||
| 607 | for { | 652 | for { | ||
| 608 | data := lineBuffer.String() | 653 | data := lineBuffer.String() | ||