| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- package ionet
- import (
- "encoding/json"
- "fmt"
- "strings"
- "time"
- "github.com/samber/lo"
- )
- // ListContainers retrieves all containers for a specific deployment
- func (c *Client) ListContainers(deploymentID string) (*ContainerList, error) {
- if deploymentID == "" {
- return nil, fmt.Errorf("deployment ID cannot be empty")
- }
- endpoint := fmt.Sprintf("/deployment/%s/containers", deploymentID)
- resp, err := c.makeRequest("GET", endpoint, nil)
- if err != nil {
- return nil, fmt.Errorf("failed to list containers: %w", err)
- }
- var containerList ContainerList
- if err := decodeDataWithFlexibleTimes(resp.Body, &containerList); err != nil {
- return nil, fmt.Errorf("failed to parse containers list: %w", err)
- }
- return &containerList, nil
- }
- // GetContainerDetails retrieves detailed information about a specific container
- func (c *Client) GetContainerDetails(deploymentID, containerID string) (*Container, error) {
- if deploymentID == "" {
- return nil, fmt.Errorf("deployment ID cannot be empty")
- }
- if containerID == "" {
- return nil, fmt.Errorf("container ID cannot be empty")
- }
- endpoint := fmt.Sprintf("/deployment/%s/container/%s", deploymentID, containerID)
- resp, err := c.makeRequest("GET", endpoint, nil)
- if err != nil {
- return nil, fmt.Errorf("failed to get container details: %w", err)
- }
- // API response format not documented, assuming direct format
- var container Container
- if err := decodeWithFlexibleTimes(resp.Body, &container); err != nil {
- return nil, fmt.Errorf("failed to parse container details: %w", err)
- }
- return &container, nil
- }
- // GetContainerJobs retrieves containers jobs for a specific container (similar to containers endpoint)
- func (c *Client) GetContainerJobs(deploymentID, containerID string) (*ContainerList, error) {
- if deploymentID == "" {
- return nil, fmt.Errorf("deployment ID cannot be empty")
- }
- if containerID == "" {
- return nil, fmt.Errorf("container ID cannot be empty")
- }
- endpoint := fmt.Sprintf("/deployment/%s/containers-jobs/%s", deploymentID, containerID)
- resp, err := c.makeRequest("GET", endpoint, nil)
- if err != nil {
- return nil, fmt.Errorf("failed to get container jobs: %w", err)
- }
- var containerList ContainerList
- if err := decodeDataWithFlexibleTimes(resp.Body, &containerList); err != nil {
- return nil, fmt.Errorf("failed to parse container jobs: %w", err)
- }
- return &containerList, nil
- }
- // buildLogEndpoint constructs the request path for fetching logs
- func buildLogEndpoint(deploymentID, containerID string, opts *GetLogsOptions) (string, error) {
- if deploymentID == "" {
- return "", fmt.Errorf("deployment ID cannot be empty")
- }
- if containerID == "" {
- return "", fmt.Errorf("container ID cannot be empty")
- }
- params := make(map[string]interface{})
- if opts != nil {
- if opts.Level != "" {
- params["level"] = opts.Level
- }
- if opts.Stream != "" {
- params["stream"] = opts.Stream
- }
- if opts.Limit > 0 {
- params["limit"] = opts.Limit
- }
- if opts.Cursor != "" {
- params["cursor"] = opts.Cursor
- }
- if opts.Follow {
- params["follow"] = true
- }
- if opts.StartTime != nil {
- params["start_time"] = opts.StartTime
- }
- if opts.EndTime != nil {
- params["end_time"] = opts.EndTime
- }
- }
- endpoint := fmt.Sprintf("/deployment/%s/log/%s", deploymentID, containerID)
- endpoint += buildQueryParams(params)
- return endpoint, nil
- }
- // GetContainerLogs retrieves logs for containers in a deployment and normalizes them
- func (c *Client) GetContainerLogs(deploymentID, containerID string, opts *GetLogsOptions) (*ContainerLogs, error) {
- raw, err := c.GetContainerLogsRaw(deploymentID, containerID, opts)
- if err != nil {
- return nil, err
- }
- logs := &ContainerLogs{
- ContainerID: containerID,
- }
- if raw == "" {
- return logs, nil
- }
- normalized := strings.ReplaceAll(raw, "\r\n", "\n")
- lines := strings.Split(normalized, "\n")
- logs.Logs = lo.FilterMap(lines, func(line string, _ int) (LogEntry, bool) {
- if strings.TrimSpace(line) == "" {
- return LogEntry{}, false
- }
- return LogEntry{Message: line}, true
- })
- return logs, nil
- }
- // GetContainerLogsRaw retrieves the raw text logs for a specific container
- func (c *Client) GetContainerLogsRaw(deploymentID, containerID string, opts *GetLogsOptions) (string, error) {
- endpoint, err := buildLogEndpoint(deploymentID, containerID, opts)
- if err != nil {
- return "", err
- }
- resp, err := c.makeRequest("GET", endpoint, nil)
- if err != nil {
- return "", fmt.Errorf("failed to get container logs: %w", err)
- }
- return string(resp.Body), nil
- }
- // StreamContainerLogs streams real-time logs for a specific container
- // This method uses a callback function to handle incoming log entries
- func (c *Client) StreamContainerLogs(deploymentID, containerID string, opts *GetLogsOptions, callback func(*LogEntry) error) error {
- if deploymentID == "" {
- return fmt.Errorf("deployment ID cannot be empty")
- }
- if containerID == "" {
- return fmt.Errorf("container ID cannot be empty")
- }
- if callback == nil {
- return fmt.Errorf("callback function cannot be nil")
- }
- // Set follow to true for streaming
- if opts == nil {
- opts = &GetLogsOptions{}
- }
- opts.Follow = true
- endpoint, err := buildLogEndpoint(deploymentID, containerID, opts)
- if err != nil {
- return err
- }
- // Note: This is a simplified implementation. In a real scenario, you might want to use
- // Server-Sent Events (SSE) or WebSocket for streaming logs
- for {
- resp, err := c.makeRequest("GET", endpoint, nil)
- if err != nil {
- return fmt.Errorf("failed to stream container logs: %w", err)
- }
- var logs ContainerLogs
- if err := decodeWithFlexibleTimes(resp.Body, &logs); err != nil {
- return fmt.Errorf("failed to parse container logs: %w", err)
- }
- // Call the callback for each log entry
- for _, logEntry := range logs.Logs {
- if err := callback(&logEntry); err != nil {
- return fmt.Errorf("callback error: %w", err)
- }
- }
- // If there are no more logs or we have a cursor, continue polling
- if !logs.HasMore && logs.NextCursor == "" {
- break
- }
- // Update cursor for next request
- if logs.NextCursor != "" {
- opts.Cursor = logs.NextCursor
- endpoint, err = buildLogEndpoint(deploymentID, containerID, opts)
- if err != nil {
- return err
- }
- }
- // Wait a bit before next poll to avoid overwhelming the API
- time.Sleep(2 * time.Second)
- }
- return nil
- }
- // RestartContainer restarts a specific container (if supported by the API)
- func (c *Client) RestartContainer(deploymentID, containerID string) error {
- if deploymentID == "" {
- return fmt.Errorf("deployment ID cannot be empty")
- }
- if containerID == "" {
- return fmt.Errorf("container ID cannot be empty")
- }
- endpoint := fmt.Sprintf("/deployment/%s/container/%s/restart", deploymentID, containerID)
- _, err := c.makeRequest("POST", endpoint, nil)
- if err != nil {
- return fmt.Errorf("failed to restart container: %w", err)
- }
- return nil
- }
- // StopContainer stops a specific container (if supported by the API)
- func (c *Client) StopContainer(deploymentID, containerID string) error {
- if deploymentID == "" {
- return fmt.Errorf("deployment ID cannot be empty")
- }
- if containerID == "" {
- return fmt.Errorf("container ID cannot be empty")
- }
- endpoint := fmt.Sprintf("/deployment/%s/container/%s/stop", deploymentID, containerID)
- _, err := c.makeRequest("POST", endpoint, nil)
- if err != nil {
- return fmt.Errorf("failed to stop container: %w", err)
- }
- return nil
- }
- // ExecuteInContainer executes a command in a specific container (if supported by the API)
- func (c *Client) ExecuteInContainer(deploymentID, containerID string, command []string) (string, error) {
- if deploymentID == "" {
- return "", fmt.Errorf("deployment ID cannot be empty")
- }
- if containerID == "" {
- return "", fmt.Errorf("container ID cannot be empty")
- }
- if len(command) == 0 {
- return "", fmt.Errorf("command cannot be empty")
- }
- reqBody := map[string]interface{}{
- "command": command,
- }
- endpoint := fmt.Sprintf("/deployment/%s/container/%s/exec", deploymentID, containerID)
- resp, err := c.makeRequest("POST", endpoint, reqBody)
- if err != nil {
- return "", fmt.Errorf("failed to execute command in container: %w", err)
- }
- var result map[string]interface{}
- if err := json.Unmarshal(resp.Body, &result); err != nil {
- return "", fmt.Errorf("failed to parse execution result: %w", err)
- }
- if output, ok := result["output"].(string); ok {
- return output, nil
- }
- return string(resp.Body), nil
- }
|