refactor: Simplify error response handling in deploy process
- Remove `DeploymentError` and `ConflictCheckResponse` structs. - Use `DeployResponse` for all deployment-related responses. - Simplify error handling and logging in deployment functions. - Update `checkDeployConflict` to return `DeployResponse`. - Refactor `handleDeployResponse` to use `DeployResponse`. - Add `StatusCode` field to `DeployResponse` for better error reporting. - Remove redundant error handling and improve error messages.
- commit
- 3c49eb92ebdc9ec692fc9fb0661d570312e4632c
- author
- Brice · · 2024-11-24 10:29 UTC
- parent
- c20d46c
- signature
- unsigned
7 files · +106 -154
M internal/cli/cmd/deploy.go +15 -35
| @@ -6,7 +6,6 @@ import ( | |||
| 6 | 6 | "bytes" | |
| 7 | 7 | "context" | |
| 8 | 8 | "encoding/json" | |
| 9 | - | "errors" | |
| 10 | 9 | "fmt" | |
| 11 | 10 | "io" | |
| 12 | 11 | "net/http" | |
| @@ -81,27 +80,27 @@ func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain str | |||
| 81 | 80 | ||
| 82 | 81 | log.Info("Deploying image", "image", imageName) | |
| 83 | 82 | // Check for conflicts first | |
| 84 | - | conflictResp, err := checkDeployConflict(a, targetDomain, port) | |
| 83 | + | resp, err := checkDeployConflict(a, targetDomain, port) | |
| 85 | 84 | if err != nil { | |
| 86 | 85 | return fmt.Errorf("conflict check failed: %w", err) | |
| 87 | 86 | } | |
| 88 | 87 | ||
| 89 | 88 | var shortID string | |
| 90 | - | if len(conflictResp.ContainerID) >= 12 { | |
| 91 | - | shortID = conflictResp.ContainerID[:12] | |
| 89 | + | if len(resp.ContainerID) >= 12 { | |
| 90 | + | shortID = resp.ContainerID[:12] | |
| 92 | 91 | } else { | |
| 93 | - | shortID = conflictResp.ContainerID // Use full ID if less than 12 chars | |
| 92 | + | shortID = resp.ContainerID // Use full ID if less than 12 chars | |
| 94 | 93 | } | |
| 95 | 94 | ||
| 96 | 95 | // If there's a conflict, handle it before proceeding | |
| 97 | - | if !conflictResp.Success && conflictResp.ContainerID != "" { | |
| 96 | + | if !resp.Success && resp.ContainerID != "" { | |
| 98 | 97 | log.Warn("Container already exists", | |
| 99 | - | "name", conflictResp.ContainerName, | |
| 98 | + | "name", resp.ContainerName, | |
| 100 | 99 | "id", shortID, | |
| 101 | - | "state", conflictResp.State, | |
| 102 | - | "uptime", conflictResp.RunningTime) | |
| 100 | + | "state", resp.State, | |
| 101 | + | "uptime", resp.RunningTime) | |
| 103 | 102 | ||
| 104 | - | if err := handler.HandleExistingContainer(a, conflictResp); err != nil { | |
| 103 | + | if err := handler.HandleExistingContainer(a, resp); err != nil { | |
| 105 | 104 | if strings.Contains(err.Error(), "cancelled by user") { | |
| 106 | 105 | return fmt.Errorf("deployment cancelled by user") | |
| 107 | 106 | } | |
| @@ -136,35 +135,17 @@ func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain str | |||
| 136 | 135 | ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) | |
| 137 | 136 | defer cancel() | |
| 138 | 137 | ||
| 139 | - | resp, err := chunkedClient.SendFileAsChunks(ctx, "/deploy/chunked", headers, imageReader, size, imageName) | |
| 138 | + | chunkResp, err := chunkedClient.SendFileAsChunks(ctx, "/deploy/chunked", headers, imageReader, size, imageName) | |
| 140 | 139 | if err != nil { | |
| 141 | - | var deployErr *common.DeploymentError | |
| 142 | - | if errors.As(err, &deployErr) && deployErr.StatusCode == http.StatusConflict { | |
| 143 | - | var deployResponse common.DeployResponse | |
| 144 | - | if jsonErr := json.Unmarshal([]byte(deployErr.RawResponse), &deployResponse); jsonErr == nil { | |
| 145 | - | if deployResponse.ContainerID != "" { | |
| 146 | - | log.Warn("Container already exists") | |
| 147 | - | // Retry the deployment after handling the conflict | |
| 148 | - | if _, err := imageReader.Seek(0, 0); err != nil { | |
| 149 | - | return fmt.Errorf("failed to reset image reader: %w", err) | |
| 150 | - | } | |
| 151 | - | resp, err = chunkedClient.SendFileAsChunks(ctx, "/deploy", headers, imageReader, size, imageName) | |
| 152 | - | if err != nil { | |
| 153 | - | return fmt.Errorf("deployment failed after container removal: %w", err) | |
| 154 | - | } | |
| 155 | - | } | |
| 156 | - | } | |
| 157 | - | } else { | |
| 158 | - | return fmt.Errorf("deployment failed: %w", err) | |
| 159 | - | } | |
| 140 | + | return fmt.Errorf("failed to deploy image: %w", err) | |
| 160 | 141 | } | |
| 161 | 142 | ||
| 162 | - | if resp == nil { | |
| 143 | + | if chunkResp == nil { | |
| 163 | 144 | return fmt.Errorf("received nil response from server") | |
| 164 | 145 | } | |
| 165 | 146 | ||
| 166 | 147 | var deployResp common.DeployResponse | |
| 167 | - | if err := json.Unmarshal(resp.Body, &deployResp); err != nil { | |
| 148 | + | if err := json.Unmarshal(chunkResp.Body, &deployResp); err != nil { | |
| 168 | 149 | return fmt.Errorf("failed to parse deployment response: %w", err) | |
| 169 | 150 | } | |
| 170 | 151 | ||
| @@ -224,7 +205,7 @@ func validateDeployInputs(imageName, port, targetDomain string) error { | |||
| 224 | 205 | return handler.ValidateTargetDomain(targetDomain) | |
| 225 | 206 | } | |
| 226 | 207 | ||
| 227 | - | func checkDeployConflict(a *cli.App, targetDomain string, port string) (*common.ConflictCheckResponse, error) { | |
| 208 | + | func checkDeployConflict(a *cli.App, targetDomain string, port string) (*common.DeployResponse, error) { | |
| 228 | 209 | ||
| 229 | 210 | // Create request to check-conflict endpoint with both domain and port parameters | |
| 230 | 211 | reqUrl := fmt.Sprintf("%s/api/deploy/check-conflict?domain=%s&port=%s", | |
| @@ -248,8 +229,7 @@ func checkDeployConflict(a *cli.App, targetDomain string, port string) (*common. | |||
| 248 | 229 | } | |
| 249 | 230 | defer resp.Body.Close() | |
| 250 | 231 | ||
| 251 | - | // Parse response | |
| 252 | - | var conflictResp common.ConflictCheckResponse | |
| 232 | + | var conflictResp common.DeployResponse | |
| 253 | 233 | if err := json.NewDecoder(resp.Body).Decode(&conflictResp); err != nil { | |
| 254 | 234 | return nil, fmt.Errorf("failed to parse conflict check response: %w", err) | |
| 255 | 235 | } | |
| @@ -6,7 +6,6 @@ import ( | |||||
| 6 | "bytes" | 6 | "bytes" | ||
| 7 | "context" | 7 | "context" | ||
| 8 | "encoding/json" | 8 | "encoding/json" | ||
| 9 | - | "errors" | |||
| 10 | "fmt" | 9 | "fmt" | ||
| 11 | "io" | 10 | "io" | ||
| 12 | "net/http" | 11 | "net/http" | ||
| @@ -81,27 +80,27 @@ func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain str | |||||
| 81 | 80 | ||||
| 82 | log.Info("Deploying image", "image", imageName) | 81 | log.Info("Deploying image", "image", imageName) | ||
| 83 | // Check for conflicts first | 82 | // Check for conflicts first | ||
| 84 | - | conflictResp, err := checkDeployConflict(a, targetDomain, port) | 83 | + | resp, err := checkDeployConflict(a, targetDomain, port) |
| 85 | if err != nil { | 84 | if err != nil { | ||
| 86 | return fmt.Errorf("conflict check failed: %w", err) | 85 | return fmt.Errorf("conflict check failed: %w", err) | ||
| 87 | } | 86 | } | ||
| 88 | 87 | ||||
| 89 | var shortID string | 88 | var shortID string | ||
| 90 | - | if len(conflictResp.ContainerID) >= 12 { | 89 | + | if len(resp.ContainerID) >= 12 { |
| 91 | - | shortID = conflictResp.ContainerID[:12] | 90 | + | shortID = resp.ContainerID[:12] |
| 92 | } else { | 91 | } else { | ||
| 93 | - | shortID = conflictResp.ContainerID // Use full ID if less than 12 chars | 92 | + | shortID = resp.ContainerID // Use full ID if less than 12 chars |
| 94 | } | 93 | } | ||
| 95 | 94 | ||||
| 96 | // If there's a conflict, handle it before proceeding | 95 | // If there's a conflict, handle it before proceeding | ||
| 97 | - | if !conflictResp.Success && conflictResp.ContainerID != "" { | 96 | + | if !resp.Success && resp.ContainerID != "" { |
| 98 | log.Warn("Container already exists", | 97 | log.Warn("Container already exists", | ||
| 99 | - | "name", conflictResp.ContainerName, | 98 | + | "name", resp.ContainerName, |
| 100 | "id", shortID, | 99 | "id", shortID, | ||
| 101 | - | "state", conflictResp.State, | 100 | + | "state", resp.State, |
| 102 | - | "uptime", conflictResp.RunningTime) | 101 | + | "uptime", resp.RunningTime) |
| 103 | 102 | ||||
| 104 | - | if err := handler.HandleExistingContainer(a, conflictResp); err != nil { | 103 | + | if err := handler.HandleExistingContainer(a, resp); err != nil { |
| 105 | if strings.Contains(err.Error(), "cancelled by user") { | 104 | if strings.Contains(err.Error(), "cancelled by user") { | ||
| 106 | return fmt.Errorf("deployment cancelled by user") | 105 | return fmt.Errorf("deployment cancelled by user") | ||
| 107 | } | 106 | } | ||
| @@ -136,35 +135,17 @@ func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain str | |||||
| 136 | ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) | 135 | ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) | ||
| 137 | defer cancel() | 136 | defer cancel() | ||
| 138 | 137 | ||||
| 139 | - | resp, err := chunkedClient.SendFileAsChunks(ctx, "/deploy/chunked", headers, imageReader, size, imageName) | 138 | + | chunkResp, err := chunkedClient.SendFileAsChunks(ctx, "/deploy/chunked", headers, imageReader, size, imageName) |
| 140 | if err != nil { | 139 | if err != nil { | ||
| 141 | - | var deployErr *common.DeploymentError | 140 | + | return fmt.Errorf("failed to deploy image: %w", err) |
| 142 | - | if errors.As(err, &deployErr) && deployErr.StatusCode == http.StatusConflict { | |||
| 143 | - | var deployResponse common.DeployResponse | |||
| 144 | - | if jsonErr := json.Unmarshal([]byte(deployErr.RawResponse), &deployResponse); jsonErr == nil { | |||
| 145 | - | if deployResponse.ContainerID != "" { | |||
| 146 | - | log.Warn("Container already exists") | |||
| 147 | - | // Retry the deployment after handling the conflict | |||
| 148 | - | if _, err := imageReader.Seek(0, 0); err != nil { | |||
| 149 | - | return fmt.Errorf("failed to reset image reader: %w", err) | |||
| 150 | - | } | |||
| 151 | - | resp, err = chunkedClient.SendFileAsChunks(ctx, "/deploy", headers, imageReader, size, imageName) | |||
| 152 | - | if err != nil { | |||
| 153 | - | return fmt.Errorf("deployment failed after container removal: %w", err) | |||
| 154 | - | } | |||
| 155 | - | } | |||
| 156 | - | } | |||
| 157 | - | } else { | |||
| 158 | - | return fmt.Errorf("deployment failed: %w", err) | |||
| 159 | - | } | |||
| 160 | } | 141 | } | ||
| 161 | 142 | ||||
| 162 | - | if resp == nil { | 143 | + | if chunkResp == nil { |
| 163 | return fmt.Errorf("received nil response from server") | 144 | return fmt.Errorf("received nil response from server") | ||
| 164 | } | 145 | } | ||
| 165 | 146 | ||||
| 166 | var deployResp common.DeployResponse | 147 | var deployResp common.DeployResponse | ||
| 167 | - | if err := json.Unmarshal(resp.Body, &deployResp); err != nil { | 148 | + | if err := json.Unmarshal(chunkResp.Body, &deployResp); err != nil { |
| 168 | return fmt.Errorf("failed to parse deployment response: %w", err) | 149 | return fmt.Errorf("failed to parse deployment response: %w", err) | ||
| 169 | } | 150 | } | ||
| 170 | 151 | ||||
| @@ -224,7 +205,7 @@ func validateDeployInputs(imageName, port, targetDomain string) error { | |||||
| 224 | return handler.ValidateTargetDomain(targetDomain) | 205 | return handler.ValidateTargetDomain(targetDomain) | ||
| 225 | } | 206 | } | ||
| 226 | 207 | ||||
| 227 | - | func checkDeployConflict(a *cli.App, targetDomain string, port string) (*common.ConflictCheckResponse, error) { | 208 | + | func checkDeployConflict(a *cli.App, targetDomain string, port string) (*common.DeployResponse, error) { |
| 228 | 209 | ||||
| 229 | // Create request to check-conflict endpoint with both domain and port parameters | 210 | // Create request to check-conflict endpoint with both domain and port parameters | ||
| 230 | reqUrl := fmt.Sprintf("%s/api/deploy/check-conflict?domain=%s&port=%s", | 211 | reqUrl := fmt.Sprintf("%s/api/deploy/check-conflict?domain=%s&port=%s", | ||
| @@ -248,8 +229,7 @@ func checkDeployConflict(a *cli.App, targetDomain string, port string) (*common. | |||||
| 248 | } | 229 | } | ||
| 249 | defer resp.Body.Close() | 230 | defer resp.Body.Close() | ||
| 250 | 231 | ||||
| 251 | - | // Parse response | 232 | + | var conflictResp common.DeployResponse |
| 252 | - | var conflictResp common.ConflictCheckResponse | |||
| 253 | if err := json.NewDecoder(resp.Body).Decode(&conflictResp); err != nil { | 233 | if err := json.NewDecoder(resp.Body).Decode(&conflictResp); err != nil { | ||
| 254 | return nil, fmt.Errorf("failed to parse conflict check response: %w", err) | 234 | return nil, fmt.Errorf("failed to parse conflict check response: %w", err) | ||
| 255 | } | 235 | } | ||
M internal/cli/handler/chunked_transfer.go +8 -11
| @@ -29,6 +29,8 @@ import ( | |||
| 29 | 29 | "golang.org/x/exp/rand" | |
| 30 | 30 | ) | |
| 31 | 31 | ||
| 32 | + | type DeployResponse = common.DeployResponse | |
| 33 | + | ||
| 32 | 34 | // UploadProgress represents the current state of the upload | |
| 33 | 35 | type UploadProgress struct { | |
| 34 | 36 | Completed int | |
| @@ -425,8 +427,7 @@ func handleFinalResponse(resp *Response, responseType string) error { | |||
| 425 | 427 | ||
| 426 | 428 | // handleDeployResponse handles the response from a deploy operation | |
| 427 | 429 | func handleDeployResponse(resp *Response) error { | |
| 428 | - | // If not a conflict, process as normal deploy response | |
| 429 | - | var finalResp common.DeployResponse | |
| 430 | + | finalResp := DeployResponse{} | |
| 430 | 431 | if err := json.Unmarshal(resp.Body, &finalResp); err != nil { | |
| 431 | 432 | return fmt.Errorf("failed to unmarshal deploy response: %w", err) | |
| 432 | 433 | } | |
| @@ -527,15 +528,11 @@ func (c *ChunkedClient) attemptChunkSend(ctx context.Context, endpoint string, h | |||
| 527 | 528 | ||
| 528 | 529 | if resp.StatusCode != http.StatusOK { | |
| 529 | 530 | return &Response{ | |
| 530 | - | Http: resp, | |
| 531 | - | Body: body, | |
| 532 | - | StatusCode: resp.StatusCode, | |
| 533 | - | Header: resp.Header, | |
| 534 | - | }, &common.DeploymentError{ | |
| 535 | - | StatusCode: resp.StatusCode, | |
| 536 | - | Message: "Request failed", | |
| 537 | - | RawResponse: string(body), | |
| 538 | - | } | |
| 531 | + | Http: resp, | |
| 532 | + | Body: body, | |
| 533 | + | StatusCode: resp.StatusCode, | |
| 534 | + | Header: resp.Header, | |
| 535 | + | }, fmt.Errorf("unexpected status code: %d", resp.StatusCode) | |
| 539 | 536 | } | |
| 540 | 537 | ||
| 541 | 538 | return &Response{ | |
| @@ -29,6 +29,8 @@ import ( | |||||
| 29 | "golang.org/x/exp/rand" | 29 | "golang.org/x/exp/rand" | ||
| 30 | ) | 30 | ) | ||
| 31 | 31 | ||||
| 32 | + | type DeployResponse = common.DeployResponse | |||
| 33 | + | ||||
| 32 | // UploadProgress represents the current state of the upload | 34 | // UploadProgress represents the current state of the upload | ||
| 33 | type UploadProgress struct { | 35 | type UploadProgress struct { | ||
| 34 | Completed int | 36 | Completed int | ||
| @@ -425,8 +427,7 @@ func handleFinalResponse(resp *Response, responseType string) error { | |||||
| 425 | 427 | ||||
| 426 | // handleDeployResponse handles the response from a deploy operation | 428 | // handleDeployResponse handles the response from a deploy operation | ||
| 427 | func handleDeployResponse(resp *Response) error { | 429 | func handleDeployResponse(resp *Response) error { | ||
| 428 | - | // If not a conflict, process as normal deploy response | 430 | + | finalResp := DeployResponse{} |
| 429 | - | var finalResp common.DeployResponse | |||
| 430 | if err := json.Unmarshal(resp.Body, &finalResp); err != nil { | 431 | if err := json.Unmarshal(resp.Body, &finalResp); err != nil { | ||
| 431 | return fmt.Errorf("failed to unmarshal deploy response: %w", err) | 432 | return fmt.Errorf("failed to unmarshal deploy response: %w", err) | ||
| 432 | } | 433 | } | ||
| @@ -527,15 +528,11 @@ func (c *ChunkedClient) attemptChunkSend(ctx context.Context, endpoint string, h | |||||
| 527 | 528 | ||||
| 528 | if resp.StatusCode != http.StatusOK { | 529 | if resp.StatusCode != http.StatusOK { | ||
| 529 | return &Response{ | 530 | return &Response{ | ||
| 530 | - | Http: resp, | 531 | + | Http: resp, |
| 531 | - | Body: body, | 532 | + | Body: body, |
| 532 | - | StatusCode: resp.StatusCode, | 533 | + | StatusCode: resp.StatusCode, |
| 533 | - | Header: resp.Header, | 534 | + | Header: resp.Header, |
| 534 | - | }, &common.DeploymentError{ | 535 | + | }, fmt.Errorf("unexpected status code: %d", resp.StatusCode) |
| 535 | - | StatusCode: resp.StatusCode, | |||
| 536 | - | Message: "Request failed", | |||
| 537 | - | RawResponse: string(body), | |||
| 538 | - | } | |||
| 539 | } | 536 | } | ||
| 540 | 537 | ||||
| 541 | return &Response{ | 538 | return &Response{ | ||
M internal/cli/handler/deploy_survey.go +1 -1
| @@ -13,7 +13,7 @@ import ( | |||
| 13 | 13 | "github.com/charmbracelet/log" | |
| 14 | 14 | ) | |
| 15 | 15 | ||
| 16 | - | func HandleExistingContainer(app *cli.App, conflictResp *common.ConflictCheckResponse) error { | |
| 16 | + | func HandleExistingContainer(app *cli.App, conflictResp *common.DeployResponse) error { | |
| 17 | 17 | // Add a small delay to ensure terminal is clear | |
| 18 | 18 | time.Sleep(100 * time.Millisecond) | |
| 19 | 19 | ||
| @@ -13,7 +13,7 @@ import ( | |||||
| 13 | "github.com/charmbracelet/log" | 13 | "github.com/charmbracelet/log" | ||
| 14 | ) | 14 | ) | ||
| 15 | 15 | ||||
| 16 | - | func HandleExistingContainer(app *cli.App, conflictResp *common.ConflictCheckResponse) error { | 16 | + | func HandleExistingContainer(app *cli.App, conflictResp *common.DeployResponse) error { |
| 17 | // Add a small delay to ensure terminal is clear | 17 | // Add a small delay to ensure terminal is clear | ||
| 18 | time.Sleep(100 * time.Millisecond) | 18 | time.Sleep(100 * time.Millisecond) | ||
| 19 | 19 | ||||
M internal/cli/handler/http.go +2 -10
| @@ -80,17 +80,9 @@ func SendHTTPRequest(a *cli.App, rp *common.RequestPayload, method string, endpo | |||
| 80 | 80 | reauthenticated = true | |
| 81 | 81 | continue | |
| 82 | 82 | } | |
| 83 | - | return nil, &common.DeploymentError{ | |
| 84 | - | StatusCode: resp.StatusCode, | |
| 85 | - | Message: errorResp.Message, | |
| 86 | - | RawResponse: string(body), | |
| 87 | - | } | |
| 88 | - | } | |
| 89 | - | return nil, &common.DeploymentError{ | |
| 90 | - | StatusCode: resp.StatusCode, | |
| 91 | - | Message: "Unexpected error occurred", | |
| 92 | - | RawResponse: string(body), | |
| 83 | + | return nil, fmt.Errorf("failed to send request: %s", errorResp.Message) | |
| 93 | 84 | } | |
| 85 | + | return nil, fmt.Errorf("failed to send request: %s", string(body)) | |
| 94 | 86 | } | |
| 95 | 87 | ||
| 96 | 88 | return &Response{Http: resp, Body: body, StatusCode: resp.StatusCode}, nil | |
| @@ -80,17 +80,9 @@ func SendHTTPRequest(a *cli.App, rp *common.RequestPayload, method string, endpo | |||||
| 80 | reauthenticated = true | 80 | reauthenticated = true | ||
| 81 | continue | 81 | continue | ||
| 82 | } | 82 | } | ||
| 83 | - | return nil, &common.DeploymentError{ | 83 | + | return nil, fmt.Errorf("failed to send request: %s", errorResp.Message) |
| 84 | - | StatusCode: resp.StatusCode, | |||
| 85 | - | Message: errorResp.Message, | |||
| 86 | - | RawResponse: string(body), | |||
| 87 | - | } | |||
| 88 | - | } | |||
| 89 | - | return nil, &common.DeploymentError{ | |||
| 90 | - | StatusCode: resp.StatusCode, | |||
| 91 | - | Message: "Unexpected error occurred", | |||
| 92 | - | RawResponse: string(body), | |||
| 93 | } | 84 | } | ||
| 85 | + | return nil, fmt.Errorf("failed to send request: %s", string(body)) | |||
| 94 | } | 86 | } | ||
| 95 | 87 | ||||
| 96 | return &Response{Http: resp, Body: body, StatusCode: resp.StatusCode}, nil | 88 | return &Response{Http: resp, Body: body, StatusCode: resp.StatusCode}, nil | ||
M internal/common/payload.go +7 -26
| @@ -21,9 +21,13 @@ type Payload interface { | |||
| 21 | 21 | type DeployResponse struct { | |
| 22 | 22 | Success bool `json:"success"` | |
| 23 | 23 | Message string `json:"message"` | |
| 24 | - | Domain string `json:"domain"` | |
| 25 | - | ContainerID string `json:"container_id"` | |
| 26 | - | ContainerName string `json:"container_name"` | |
| 24 | + | StatusCode int `json:"status_code,omitempty"` | |
| 25 | + | Domain string `json:"domain,omitempty"` | |
| 26 | + | ContainerID string `json:"container_id,omitempty"` | |
| 27 | + | ContainerName string `json:"container_name,omitempty"` | |
| 28 | + | State string `json:"state,omitempty"` | |
| 29 | + | RunningTime string `json:"running_time,omitempty"` | |
| 30 | + | Ports string `json:"ports,omitempty"` | |
| 27 | 31 | } | |
| 28 | 32 | ||
| 29 | 33 | // PushResponse represents the response structure for push requests | |
| @@ -49,18 +53,6 @@ type StartResponse struct { | |||
| 49 | 53 | Message string `json:"message"` | |
| 50 | 54 | } | |
| 51 | 55 | ||
| 52 | - | // DeploymentError represents an error that occurred during deployment | |
| 53 | - | type DeploymentError struct { | |
| 54 | - | StatusCode int `json:"status"` | |
| 55 | - | Message string `json:"message"` | |
| 56 | - | RawResponse string `json:"raw_response"` | |
| 57 | - | } | |
| 58 | - | ||
| 59 | - | // Error implements the error interface for DeploymentError | |
| 60 | - | func (e *DeploymentError) Error() string { | |
| 61 | - | return fmt.Sprintf("Deployment failed (status %d): %s", e.StatusCode, e.Message) | |
| 62 | - | } | |
| 63 | - | ||
| 64 | 56 | // DeployPayload represents the payload for deployment requests | |
| 65 | 57 | type DeployPayload struct { | |
| 66 | 58 | Port string `json:"port"` | |
| @@ -86,17 +78,6 @@ type ChunkMetadata struct { | |||
| 86 | 78 | TransferID string `json:"transfer_id"` | |
| 87 | 79 | } | |
| 88 | 80 | ||
| 89 | - | type ConflictCheckResponse struct { | |
| 90 | - | Success bool `json:"success"` | |
| 91 | - | Message string `json:"message"` | |
| 92 | - | ContainerID string `json:"container_id"` | |
| 93 | - | ContainerName string `json:"container_name"` | |
| 94 | - | Domain string `json:"domain"` | |
| 95 | - | State string `json:"state"` | |
| 96 | - | RunningTime string `json:"running_time"` | |
| 97 | - | Ports string `json:"ports"` | |
| 98 | - | } | |
| 99 | - | ||
| 100 | 81 | type ConflictAction struct { | |
| 101 | 82 | Action string `json:"action"` // "stop", "remove", or "cancel" | |
| 102 | 83 | Force bool `json:"force"` | |
| @@ -21,9 +21,13 @@ type Payload interface { | |||||
| 21 | type DeployResponse struct { | 21 | type DeployResponse struct { | ||
| 22 | Success bool `json:"success"` | 22 | Success bool `json:"success"` | ||
| 23 | Message string `json:"message"` | 23 | Message string `json:"message"` | ||
| 24 | - | Domain string `json:"domain"` | 24 | + | StatusCode int `json:"status_code,omitempty"` |
| 25 | - | ContainerID string `json:"container_id"` | 25 | + | Domain string `json:"domain,omitempty"` |
| 26 | - | ContainerName string `json:"container_name"` | 26 | + | ContainerID string `json:"container_id,omitempty"` |
| 27 | + | ContainerName string `json:"container_name,omitempty"` | |||
| 28 | + | State string `json:"state,omitempty"` | |||
| 29 | + | RunningTime string `json:"running_time,omitempty"` | |||
| 30 | + | Ports string `json:"ports,omitempty"` | |||
| 27 | } | 31 | } | ||
| 28 | 32 | ||||
| 29 | // PushResponse represents the response structure for push requests | 33 | // PushResponse represents the response structure for push requests | ||
| @@ -49,18 +53,6 @@ type StartResponse struct { | |||||
| 49 | Message string `json:"message"` | 53 | Message string `json:"message"` | ||
| 50 | } | 54 | } | ||
| 51 | 55 | ||||
| 52 | - | // DeploymentError represents an error that occurred during deployment | |||
| 53 | - | type DeploymentError struct { | |||
| 54 | - | StatusCode int `json:"status"` | |||
| 55 | - | Message string `json:"message"` | |||
| 56 | - | RawResponse string `json:"raw_response"` | |||
| 57 | - | } | |||
| 58 | - | ||||
| 59 | - | // Error implements the error interface for DeploymentError | |||
| 60 | - | func (e *DeploymentError) Error() string { | |||
| 61 | - | return fmt.Sprintf("Deployment failed (status %d): %s", e.StatusCode, e.Message) | |||
| 62 | - | } | |||
| 63 | - | ||||
| 64 | // DeployPayload represents the payload for deployment requests | 56 | // DeployPayload represents the payload for deployment requests | ||
| 65 | type DeployPayload struct { | 57 | type DeployPayload struct { | ||
| 66 | Port string `json:"port"` | 58 | Port string `json:"port"` | ||
| @@ -86,17 +78,6 @@ type ChunkMetadata struct { | |||||
| 86 | TransferID string `json:"transfer_id"` | 78 | TransferID string `json:"transfer_id"` | ||
| 87 | } | 79 | } | ||
| 88 | 80 | ||||
| 89 | - | type ConflictCheckResponse struct { | |||
| 90 | - | Success bool `json:"success"` | |||
| 91 | - | Message string `json:"message"` | |||
| 92 | - | ContainerID string `json:"container_id"` | |||
| 93 | - | ContainerName string `json:"container_name"` | |||
| 94 | - | Domain string `json:"domain"` | |||
| 95 | - | State string `json:"state"` | |||
| 96 | - | RunningTime string `json:"running_time"` | |||
| 97 | - | Ports string `json:"ports"` | |||
| 98 | - | } | |||
| 99 | - | ||||
| 100 | type ConflictAction struct { | 81 | type ConflictAction struct { | ||
| 101 | Action string `json:"action"` // "stop", "remove", or "cancel" | 82 | Action string `json:"action"` // "stop", "remove", or "cancel" | ||
| 102 | Force bool `json:"force"` | 83 | Force bool `json:"force"` | ||
M internal/httpserve/handlers/endpoint_deploy.go +59 -58
| @@ -25,8 +25,9 @@ func PostDeploy(c echo.Context, a *server.App) error { | |||
| 25 | 25 | // Debug | |
| 26 | 26 | log.Error("Failed to validate and prepare payload", "error", err) | |
| 27 | 27 | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ | |
| 28 | - | Success: false, | |
| 29 | - | Message: fmt.Sprintf("Invalid payload: %v", err), | |
| 28 | + | Success: false, | |
| 29 | + | Message: fmt.Sprintf("Invalid payload: %v", err), | |
| 30 | + | StatusCode: http.StatusBadRequest, | |
| 30 | 31 | }) | |
| 31 | 32 | } | |
| 32 | 33 | ||
| @@ -35,8 +36,9 @@ func PostDeploy(c echo.Context, a *server.App) error { | |||
| 35 | 36 | // Debug | |
| 36 | 37 | log.Error("Failed to save and import image", "error", err) | |
| 37 | 38 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | |
| 38 | - | Success: false, | |
| 39 | - | Message: fmt.Sprintf("Failed to save or import image: %v", err), | |
| 39 | + | Success: false, | |
| 40 | + | Message: fmt.Sprintf("Failed to save or import image: %v", err), | |
| 41 | + | StatusCode: http.StatusInternalServerError, | |
| 40 | 42 | }) | |
| 41 | 43 | } | |
| 42 | 44 | ||
| @@ -52,8 +54,9 @@ func PostDeploy(c echo.Context, a *server.App) error { | |||
| 52 | 54 | if existingContainerID == "" || existingContainerName == "" { | |
| 53 | 55 | log.Error("Failed to extract container ID or Name from error message") | |
| 54 | 56 | return sendJSONResponse(c, http.StatusConflict, DeployResponse{ | |
| 55 | - | Success: false, | |
| 56 | - | Message: "A container for this deploy already exists but could not be identified.", | |
| 57 | + | Success: false, | |
| 58 | + | Message: "A container for this deploy already exists but could not be identified.", | |
| 59 | + | StatusCode: http.StatusConflict, | |
| 57 | 60 | }) | |
| 58 | 61 | } | |
| 59 | 62 | ||
| @@ -62,30 +65,25 @@ func PostDeploy(c echo.Context, a *server.App) error { | |||
| 62 | 65 | Message: "A container for this deploy already exists.", | |
| 63 | 66 | ContainerID: existingContainerID, | |
| 64 | 67 | ContainerName: existingContainerName, | |
| 68 | + | StatusCode: http.StatusConflict, | |
| 65 | 69 | }) | |
| 66 | 70 | } | |
| 67 | 71 | log.Error("Failed to create or start container", "error", err) | |
| 68 | 72 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | |
| 69 | - | Success: false, | |
| 70 | - | Message: fmt.Sprintf("Failed to create or start container: %v", err), | |
| 73 | + | Success: false, | |
| 74 | + | Message: fmt.Sprintf("Failed to create or start container: %v", err), | |
| 75 | + | StatusCode: http.StatusInternalServerError, | |
| 71 | 76 | }) | |
| 72 | 77 | } | |
| 73 | - | response := DeployResponse{ | |
| 78 | + | ||
| 79 | + | // Arrived here means deployment was successful | |
| 80 | + | return sendJSONResponse(c, http.StatusOK, DeployResponse{ | |
| 74 | 81 | Success: true, | |
| 75 | 82 | Message: "Deployment successful", | |
| 76 | 83 | Domain: payload.TargetDomain, | |
| 77 | 84 | ContainerID: containerID, | |
| 78 | 85 | ContainerName: containerName, | |
| 79 | - | } | |
| 80 | - | ||
| 81 | - | // Debug: Verify containerID in response | |
| 82 | - | if response.ContainerID != containerID { | |
| 83 | - | log.Error("Container ID mismatch", "responseID", response.ContainerID, "actualID", containerID) | |
| 84 | - | } else { | |
| 85 | - | log.Info("Container ID successfully stored in response", "containerID", containerID) | |
| 86 | - | } | |
| 87 | - | ||
| 88 | - | return sendJSONResponse(c, http.StatusOK, response) | |
| 86 | + | }) | |
| 89 | 87 | } | |
| 90 | 88 | ||
| 91 | 89 | // PostDeployChunked handles the chunked container deployment request | |
| @@ -97,8 +95,9 @@ func handleChunkedDeploy(c echo.Context, a *server.App, metadataStr string) erro | |||
| 97 | 95 | var metadata ChunkMetadata | |
| 98 | 96 | if err := json.Unmarshal([]byte(metadataStr), &metadata); err != nil { | |
| 99 | 97 | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ | |
| 100 | - | Success: false, | |
| 101 | - | Message: "Invalid chunk metadata", | |
| 98 | + | Success: false, | |
| 99 | + | Message: "Invalid chunk metadata", | |
| 100 | + | StatusCode: http.StatusBadRequest, | |
| 102 | 101 | }) | |
| 103 | 102 | } | |
| 104 | 103 | ||
| @@ -106,34 +105,42 @@ func handleChunkedDeploy(c echo.Context, a *server.App, metadataStr string) erro | |||
| 106 | 105 | if err != nil { | |
| 107 | 106 | log.Error("Failed to validate and prepare payload", "error", err) | |
| 108 | 107 | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ | |
| 109 | - | Success: false, | |
| 110 | - | Message: fmt.Sprintf("Invalid payload: %v", err), | |
| 108 | + | Success: false, | |
| 109 | + | Message: fmt.Sprintf("Invalid payload: %v", err), | |
| 110 | + | StatusCode: http.StatusBadRequest, | |
| 111 | 111 | }) | |
| 112 | 112 | } | |
| 113 | 113 | ||
| 114 | - | // Authenticate request | |
| 114 | + | // Auth verification steps | |
| 115 | + | ||
| 115 | 116 | auth := c.Request().Header.Get("Authorization") | |
| 116 | 117 | if auth == "" { | |
| 117 | 118 | return sendJSONResponse(c, http.StatusUnauthorized, DeployResponse{ | |
| 118 | - | Success: false, | |
| 119 | - | Message: "Authentication required", | |
| 119 | + | Success: false, | |
| 120 | + | Message: "Authentication required", | |
| 121 | + | StatusCode: http.StatusUnauthorized, | |
| 120 | 122 | }) | |
| 121 | 123 | } | |
| 122 | 124 | ||
| 125 | + | // Validate the token through GitHub API | |
| 123 | 126 | token := strings.TrimPrefix(auth, "Bearer ") | |
| 124 | 127 | if !authToken.IsValidGitHubToken(token) { | |
| 125 | 128 | return sendJSONResponse(c, http.StatusUnauthorized, DeployResponse{ | |
| 126 | - | Success: false, | |
| 127 | - | Message: "Invalid or expired token", | |
| 129 | + | Success: false, | |
| 130 | + | Message: "Invalid or expired token", | |
| 131 | + | StatusCode: http.StatusUnauthorized, | |
| 128 | 132 | }) | |
| 129 | 133 | } | |
| 130 | 134 | ||
| 135 | + | // TODO: Here we should probably check that the github user is the same as the one in DB | |
| 136 | + | ||
| 131 | 137 | if metadata.TotalChunks == 1 { | |
| 132 | 138 | chunkData, err := io.ReadAll(c.Request().Body) | |
| 133 | 139 | if err != nil { | |
| 134 | 140 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | |
| 135 | - | Success: false, | |
| 136 | - | Message: "Failed to read chunk data", | |
| 141 | + | Success: false, | |
| 142 | + | Message: "Failed to read chunk data", | |
| 143 | + | StatusCode: http.StatusInternalServerError, | |
| 137 | 144 | }) | |
| 138 | 145 | } | |
| 139 | 146 | ||
| @@ -159,17 +166,20 @@ func handleChunkedDeploy(c echo.Context, a *server.App, metadataStr string) erro | |||
| 159 | 166 | if err != nil { | |
| 160 | 167 | chunkStore.mu.Unlock() | |
| 161 | 168 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | |
| 162 | - | Success: false, | |
| 163 | - | Message: "Failed to read chunk data", | |
| 169 | + | Success: false, | |
| 170 | + | Message: "Failed to read chunk data", | |
| 171 | + | StatusCode: http.StatusInternalServerError, | |
| 164 | 172 | }) | |
| 165 | 173 | } | |
| 166 | 174 | ||
| 167 | - | // Add chunk validation | |
| 175 | + | // Chunks validation steps | |
| 176 | + | // | |
| 168 | 177 | if len(chunkData) == 0 { | |
| 169 | 178 | log.Error("Received empty chunk") | |
| 170 | 179 | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ | |
| 171 | - | Success: false, | |
| 172 | - | Message: "Received empty chunk", | |
| 180 | + | Success: false, | |
| 181 | + | Message: "Received empty chunk", | |
| 182 | + | StatusCode: http.StatusBadRequest, | |
| 173 | 183 | }) | |
| 174 | 184 | } | |
| 175 | 185 | ||
| @@ -182,10 +192,11 @@ func handleChunkedDeploy(c echo.Context, a *server.App, metadataStr string) erro | |||
| 182 | 192 | Success: false, | |
| 183 | 193 | Message: fmt.Sprintf("Chunk size mismatch: expected %d, got %d", | |
| 184 | 194 | metadata.ChunkSize, len(chunkData)), | |
| 195 | + | StatusCode: http.StatusBadRequest, | |
| 185 | 196 | }) | |
| 186 | 197 | } | |
| 187 | 198 | ||
| 188 | - | // Store the complete chunk | |
| 199 | + | // Store the complete chunk into our store | |
| 189 | 200 | chunkStore.chunks[metadata.TransferID][metadata.ChunkNumber] = chunkData | |
| 190 | 201 | chunkStore.mu.Unlock() | |
| 191 | 202 | ||
| @@ -196,9 +207,10 @@ func handleChunkedDeploy(c echo.Context, a *server.App, metadataStr string) erro | |||
| 196 | 207 | ||
| 197 | 208 | // Include domain information in intermediate chunk responses | |
| 198 | 209 | return sendJSONResponse(c, http.StatusOK, DeployResponse{ | |
| 199 | - | Success: true, | |
| 200 | - | Message: fmt.Sprintf("Chunk %d/%d received", metadata.ChunkNumber+1, metadata.TotalChunks), | |
| 201 | - | Domain: payload.TargetDomain, | |
| 210 | + | Success: true, | |
| 211 | + | Message: fmt.Sprintf("Chunk %d/%d received", metadata.ChunkNumber+1, metadata.TotalChunks), | |
| 212 | + | Domain: payload.TargetDomain, | |
| 213 | + | StatusCode: http.StatusOK, | |
| 202 | 214 | }) | |
| 203 | 215 | } | |
| 204 | 216 | ||
| @@ -255,12 +267,7 @@ func processCompleteChunkedDeployTansfert(c echo.Context, a *server.App, transfe | |||
| 255 | 267 | log.Warn("Container already exists", | |
| 256 | 268 | "name", containerName, | |
| 257 | 269 | "id", existingContainerID) | |
| 258 | - | return sendJSONResponse(c, http.StatusConflict, DeployResponse{ | |
| 259 | - | Success: false, | |
| 260 | - | Message: fmt.Sprintf("Container with name '%s' already exists", containerName), | |
| 261 | - | ContainerID: existingContainerID, | |
| 262 | - | ContainerName: containerName, | |
| 263 | - | }) | |
| 270 | + | return sendDeployErrorResponse(c, "Container already exists", nil) | |
| 264 | 271 | } | |
| 265 | 272 | ||
| 266 | 273 | // Import the image | |
| @@ -274,11 +281,7 @@ func processCompleteChunkedDeployTansfert(c echo.Context, a *server.App, transfe | |||
| 274 | 281 | log.Error("Platform compatibility check failed", | |
| 275 | 282 | "error", err, | |
| 276 | 283 | "imageName", imageName) | |
| 277 | - | return c.JSON(http.StatusBadRequest, &common.DeploymentError{ | |
| 278 | - | StatusCode: http.StatusBadRequest, | |
| 279 | - | Message: "Platform compatibility error", | |
| 280 | - | RawResponse: err.Error(), | |
| 281 | - | }) | |
| 284 | + | return sendDeployErrorResponse(c, "Platform compatibility check failed", err) | |
| 282 | 285 | } | |
| 283 | 286 | return sendDeployErrorResponse(c, "Failed to import image", err) | |
| 284 | 287 | } | |
| @@ -299,11 +302,7 @@ func processCompleteChunkedDeployTansfert(c echo.Context, a *server.App, transfe | |||
| 299 | 302 | existingContainerName, _ := docker.GetContainerName(existingContainerID) | |
| 300 | 303 | existingContainerName = strings.TrimPrefix(existingContainerName, "/") | |
| 301 | 304 | ||
| 302 | - | return c.JSON(http.StatusBadRequest, &common.DeploymentError{ | |
| 303 | - | StatusCode: http.StatusBadRequest, | |
| 304 | - | Message: fmt.Sprintf("Container with name '%s' already exists", existingContainerName), | |
| 305 | - | RawResponse: err.Error(), | |
| 306 | - | }) | |
| 305 | + | return sendDeployErrorResponse(c, "Container already in use", err) | |
| 307 | 306 | } | |
| 308 | 307 | ||
| 309 | 308 | return sendDeployErrorResponse(c, "Failed to create or start container", err) | |
| @@ -327,10 +326,12 @@ func processCompleteChunkedDeployTansfert(c echo.Context, a *server.App, transfe | |||
| 327 | 326 | } | |
| 328 | 327 | ||
| 329 | 328 | func sendDeployErrorResponse(c echo.Context, message string, err error) error { | |
| 330 | - | log.Error(message, "error", err) | |
| 329 | + | // The error becomes the message | |
| 330 | + | errorMsg := fmt.Sprintf("%s: %s", message, err.Error()) | |
| 331 | 331 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | |
| 332 | - | Success: false, | |
| 333 | - | Message: message, | |
| 332 | + | Success: false, | |
| 333 | + | Message: errorMsg, | |
| 334 | + | StatusCode: http.StatusInternalServerError, | |
| 334 | 335 | }) | |
| 335 | 336 | } | |
| 336 | 337 | ||
| @@ -25,8 +25,9 @@ func PostDeploy(c echo.Context, a *server.App) error { | |||||
| 25 | // Debug | 25 | // Debug | ||
| 26 | log.Error("Failed to validate and prepare payload", "error", err) | 26 | log.Error("Failed to validate and prepare payload", "error", err) | ||
| 27 | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ | 27 | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ | ||
| 28 | - | Success: false, | 28 | + | Success: false, |
| 29 | - | Message: fmt.Sprintf("Invalid payload: %v", err), | 29 | + | Message: fmt.Sprintf("Invalid payload: %v", err), |
| 30 | + | StatusCode: http.StatusBadRequest, | |||
| 30 | }) | 31 | }) | ||
| 31 | } | 32 | } | ||
| 32 | 33 | ||||
| @@ -35,8 +36,9 @@ func PostDeploy(c echo.Context, a *server.App) error { | |||||
| 35 | // Debug | 36 | // Debug | ||
| 36 | log.Error("Failed to save and import image", "error", err) | 37 | log.Error("Failed to save and import image", "error", err) | ||
| 37 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | 38 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | ||
| 38 | - | Success: false, | 39 | + | Success: false, |
| 39 | - | Message: fmt.Sprintf("Failed to save or import image: %v", err), | 40 | + | Message: fmt.Sprintf("Failed to save or import image: %v", err), |
| 41 | + | StatusCode: http.StatusInternalServerError, | |||
| 40 | }) | 42 | }) | ||
| 41 | } | 43 | } | ||
| 42 | 44 | ||||
| @@ -52,8 +54,9 @@ func PostDeploy(c echo.Context, a *server.App) error { | |||||
| 52 | if existingContainerID == "" || existingContainerName == "" { | 54 | if existingContainerID == "" || existingContainerName == "" { | ||
| 53 | log.Error("Failed to extract container ID or Name from error message") | 55 | log.Error("Failed to extract container ID or Name from error message") | ||
| 54 | return sendJSONResponse(c, http.StatusConflict, DeployResponse{ | 56 | return sendJSONResponse(c, http.StatusConflict, DeployResponse{ | ||
| 55 | - | Success: false, | 57 | + | Success: false, |
| 56 | - | Message: "A container for this deploy already exists but could not be identified.", | 58 | + | Message: "A container for this deploy already exists but could not be identified.", |
| 59 | + | StatusCode: http.StatusConflict, | |||
| 57 | }) | 60 | }) | ||
| 58 | } | 61 | } | ||
| 59 | 62 | ||||
| @@ -62,30 +65,25 @@ func PostDeploy(c echo.Context, a *server.App) error { | |||||
| 62 | Message: "A container for this deploy already exists.", | 65 | Message: "A container for this deploy already exists.", | ||
| 63 | ContainerID: existingContainerID, | 66 | ContainerID: existingContainerID, | ||
| 64 | ContainerName: existingContainerName, | 67 | ContainerName: existingContainerName, | ||
| 68 | + | StatusCode: http.StatusConflict, | |||
| 65 | }) | 69 | }) | ||
| 66 | } | 70 | } | ||
| 67 | log.Error("Failed to create or start container", "error", err) | 71 | log.Error("Failed to create or start container", "error", err) | ||
| 68 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | 72 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | ||
| 69 | - | Success: false, | 73 | + | Success: false, |
| 70 | - | Message: fmt.Sprintf("Failed to create or start container: %v", err), | 74 | + | Message: fmt.Sprintf("Failed to create or start container: %v", err), |
| 75 | + | StatusCode: http.StatusInternalServerError, | |||
| 71 | }) | 76 | }) | ||
| 72 | } | 77 | } | ||
| 73 | - | response := DeployResponse{ | 78 | + | |
| 79 | + | // Arrived here means deployment was successful | |||
| 80 | + | return sendJSONResponse(c, http.StatusOK, DeployResponse{ | |||
| 74 | Success: true, | 81 | Success: true, | ||
| 75 | Message: "Deployment successful", | 82 | Message: "Deployment successful", | ||
| 76 | Domain: payload.TargetDomain, | 83 | Domain: payload.TargetDomain, | ||
| 77 | ContainerID: containerID, | 84 | ContainerID: containerID, | ||
| 78 | ContainerName: containerName, | 85 | ContainerName: containerName, | ||
| 79 | - | } | 86 | + | }) |
| 80 | - | ||||
| 81 | - | // Debug: Verify containerID in response | |||
| 82 | - | if response.ContainerID != containerID { | |||
| 83 | - | log.Error("Container ID mismatch", "responseID", response.ContainerID, "actualID", containerID) | |||
| 84 | - | } else { | |||
| 85 | - | log.Info("Container ID successfully stored in response", "containerID", containerID) | |||
| 86 | - | } | |||
| 87 | - | ||||
| 88 | - | return sendJSONResponse(c, http.StatusOK, response) | |||
| 89 | } | 87 | } | ||
| 90 | 88 | ||||
| 91 | // PostDeployChunked handles the chunked container deployment request | 89 | // PostDeployChunked handles the chunked container deployment request | ||
| @@ -97,8 +95,9 @@ func handleChunkedDeploy(c echo.Context, a *server.App, metadataStr string) erro | |||||
| 97 | var metadata ChunkMetadata | 95 | var metadata ChunkMetadata | ||
| 98 | if err := json.Unmarshal([]byte(metadataStr), &metadata); err != nil { | 96 | if err := json.Unmarshal([]byte(metadataStr), &metadata); err != nil { | ||
| 99 | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ | 97 | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ | ||
| 100 | - | Success: false, | 98 | + | Success: false, |
| 101 | - | Message: "Invalid chunk metadata", | 99 | + | Message: "Invalid chunk metadata", |
| 100 | + | StatusCode: http.StatusBadRequest, | |||
| 102 | }) | 101 | }) | ||
| 103 | } | 102 | } | ||
| 104 | 103 | ||||
| @@ -106,34 +105,42 @@ func handleChunkedDeploy(c echo.Context, a *server.App, metadataStr string) erro | |||||
| 106 | if err != nil { | 105 | if err != nil { | ||
| 107 | log.Error("Failed to validate and prepare payload", "error", err) | 106 | log.Error("Failed to validate and prepare payload", "error", err) | ||
| 108 | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ | 107 | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ | ||
| 109 | - | Success: false, | 108 | + | Success: false, |
| 110 | - | Message: fmt.Sprintf("Invalid payload: %v", err), | 109 | + | Message: fmt.Sprintf("Invalid payload: %v", err), |
| 110 | + | StatusCode: http.StatusBadRequest, | |||
| 111 | }) | 111 | }) | ||
| 112 | } | 112 | } | ||
| 113 | 113 | ||||
| 114 | - | // Authenticate request | 114 | + | // Auth verification steps |
| 115 | + | ||||
| 115 | auth := c.Request().Header.Get("Authorization") | 116 | auth := c.Request().Header.Get("Authorization") | ||
| 116 | if auth == "" { | 117 | if auth == "" { | ||
| 117 | return sendJSONResponse(c, http.StatusUnauthorized, DeployResponse{ | 118 | return sendJSONResponse(c, http.StatusUnauthorized, DeployResponse{ | ||
| 118 | - | Success: false, | 119 | + | Success: false, |
| 119 | - | Message: "Authentication required", | 120 | + | Message: "Authentication required", |
| 121 | + | StatusCode: http.StatusUnauthorized, | |||
| 120 | }) | 122 | }) | ||
| 121 | } | 123 | } | ||
| 122 | 124 | ||||
| 125 | + | // Validate the token through GitHub API | |||
| 123 | token := strings.TrimPrefix(auth, "Bearer ") | 126 | token := strings.TrimPrefix(auth, "Bearer ") | ||
| 124 | if !authToken.IsValidGitHubToken(token) { | 127 | if !authToken.IsValidGitHubToken(token) { | ||
| 125 | return sendJSONResponse(c, http.StatusUnauthorized, DeployResponse{ | 128 | return sendJSONResponse(c, http.StatusUnauthorized, DeployResponse{ | ||
| 126 | - | Success: false, | 129 | + | Success: false, |
| 127 | - | Message: "Invalid or expired token", | 130 | + | Message: "Invalid or expired token", |
| 131 | + | StatusCode: http.StatusUnauthorized, | |||
| 128 | }) | 132 | }) | ||
| 129 | } | 133 | } | ||
| 130 | 134 | ||||
| 135 | + | // TODO: Here we should probably check that the github user is the same as the one in DB | |||
| 136 | + | ||||
| 131 | if metadata.TotalChunks == 1 { | 137 | if metadata.TotalChunks == 1 { | ||
| 132 | chunkData, err := io.ReadAll(c.Request().Body) | 138 | chunkData, err := io.ReadAll(c.Request().Body) | ||
| 133 | if err != nil { | 139 | if err != nil { | ||
| 134 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | 140 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | ||
| 135 | - | Success: false, | 141 | + | Success: false, |
| 136 | - | Message: "Failed to read chunk data", | 142 | + | Message: "Failed to read chunk data", |
| 143 | + | StatusCode: http.StatusInternalServerError, | |||
| 137 | }) | 144 | }) | ||
| 138 | } | 145 | } | ||
| 139 | 146 | ||||
| @@ -159,17 +166,20 @@ func handleChunkedDeploy(c echo.Context, a *server.App, metadataStr string) erro | |||||
| 159 | if err != nil { | 166 | if err != nil { | ||
| 160 | chunkStore.mu.Unlock() | 167 | chunkStore.mu.Unlock() | ||
| 161 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | 168 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | ||
| 162 | - | Success: false, | 169 | + | Success: false, |
| 163 | - | Message: "Failed to read chunk data", | 170 | + | Message: "Failed to read chunk data", |
| 171 | + | StatusCode: http.StatusInternalServerError, | |||
| 164 | }) | 172 | }) | ||
| 165 | } | 173 | } | ||
| 166 | 174 | ||||
| 167 | - | // Add chunk validation | 175 | + | // Chunks validation steps |
| 176 | + | // | |||
| 168 | if len(chunkData) == 0 { | 177 | if len(chunkData) == 0 { | ||
| 169 | log.Error("Received empty chunk") | 178 | log.Error("Received empty chunk") | ||
| 170 | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ | 179 | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ | ||
| 171 | - | Success: false, | 180 | + | Success: false, |
| 172 | - | Message: "Received empty chunk", | 181 | + | Message: "Received empty chunk", |
| 182 | + | StatusCode: http.StatusBadRequest, | |||
| 173 | }) | 183 | }) | ||
| 174 | } | 184 | } | ||
| 175 | 185 | ||||
| @@ -182,10 +192,11 @@ func handleChunkedDeploy(c echo.Context, a *server.App, metadataStr string) erro | |||||
| 182 | Success: false, | 192 | Success: false, | ||
| 183 | Message: fmt.Sprintf("Chunk size mismatch: expected %d, got %d", | 193 | Message: fmt.Sprintf("Chunk size mismatch: expected %d, got %d", | ||
| 184 | metadata.ChunkSize, len(chunkData)), | 194 | metadata.ChunkSize, len(chunkData)), | ||
| 195 | + | StatusCode: http.StatusBadRequest, | |||
| 185 | }) | 196 | }) | ||
| 186 | } | 197 | } | ||
| 187 | 198 | ||||
| 188 | - | // Store the complete chunk | 199 | + | // Store the complete chunk into our store |
| 189 | chunkStore.chunks[metadata.TransferID][metadata.ChunkNumber] = chunkData | 200 | chunkStore.chunks[metadata.TransferID][metadata.ChunkNumber] = chunkData | ||
| 190 | chunkStore.mu.Unlock() | 201 | chunkStore.mu.Unlock() | ||
| 191 | 202 | ||||
| @@ -196,9 +207,10 @@ func handleChunkedDeploy(c echo.Context, a *server.App, metadataStr string) erro | |||||
| 196 | 207 | ||||
| 197 | // Include domain information in intermediate chunk responses | 208 | // Include domain information in intermediate chunk responses | ||
| 198 | return sendJSONResponse(c, http.StatusOK, DeployResponse{ | 209 | return sendJSONResponse(c, http.StatusOK, DeployResponse{ | ||
| 199 | - | Success: true, | 210 | + | Success: true, |
| 200 | - | Message: fmt.Sprintf("Chunk %d/%d received", metadata.ChunkNumber+1, metadata.TotalChunks), | 211 | + | Message: fmt.Sprintf("Chunk %d/%d received", metadata.ChunkNumber+1, metadata.TotalChunks), |
| 201 | - | Domain: payload.TargetDomain, | 212 | + | Domain: payload.TargetDomain, |
| 213 | + | StatusCode: http.StatusOK, | |||
| 202 | }) | 214 | }) | ||
| 203 | } | 215 | } | ||
| 204 | 216 | ||||
| @@ -255,12 +267,7 @@ func processCompleteChunkedDeployTansfert(c echo.Context, a *server.App, transfe | |||||
| 255 | log.Warn("Container already exists", | 267 | log.Warn("Container already exists", | ||
| 256 | "name", containerName, | 268 | "name", containerName, | ||
| 257 | "id", existingContainerID) | 269 | "id", existingContainerID) | ||
| 258 | - | return sendJSONResponse(c, http.StatusConflict, DeployResponse{ | 270 | + | return sendDeployErrorResponse(c, "Container already exists", nil) |
| 259 | - | Success: false, | |||
| 260 | - | Message: fmt.Sprintf("Container with name '%s' already exists", containerName), | |||
| 261 | - | ContainerID: existingContainerID, | |||
| 262 | - | ContainerName: containerName, | |||
| 263 | - | }) | |||
| 264 | } | 271 | } | ||
| 265 | 272 | ||||
| 266 | // Import the image | 273 | // Import the image | ||
| @@ -274,11 +281,7 @@ func processCompleteChunkedDeployTansfert(c echo.Context, a *server.App, transfe | |||||
| 274 | log.Error("Platform compatibility check failed", | 281 | log.Error("Platform compatibility check failed", | ||
| 275 | "error", err, | 282 | "error", err, | ||
| 276 | "imageName", imageName) | 283 | "imageName", imageName) | ||
| 277 | - | return c.JSON(http.StatusBadRequest, &common.DeploymentError{ | 284 | + | return sendDeployErrorResponse(c, "Platform compatibility check failed", err) |
| 278 | - | StatusCode: http.StatusBadRequest, | |||
| 279 | - | Message: "Platform compatibility error", | |||
| 280 | - | RawResponse: err.Error(), | |||
| 281 | - | }) | |||
| 282 | } | 285 | } | ||
| 283 | return sendDeployErrorResponse(c, "Failed to import image", err) | 286 | return sendDeployErrorResponse(c, "Failed to import image", err) | ||
| 284 | } | 287 | } | ||
| @@ -299,11 +302,7 @@ func processCompleteChunkedDeployTansfert(c echo.Context, a *server.App, transfe | |||||
| 299 | existingContainerName, _ := docker.GetContainerName(existingContainerID) | 302 | existingContainerName, _ := docker.GetContainerName(existingContainerID) | ||
| 300 | existingContainerName = strings.TrimPrefix(existingContainerName, "/") | 303 | existingContainerName = strings.TrimPrefix(existingContainerName, "/") | ||
| 301 | 304 | ||||
| 302 | - | return c.JSON(http.StatusBadRequest, &common.DeploymentError{ | 305 | + | return sendDeployErrorResponse(c, "Container already in use", err) |
| 303 | - | StatusCode: http.StatusBadRequest, | |||
| 304 | - | Message: fmt.Sprintf("Container with name '%s' already exists", existingContainerName), | |||
| 305 | - | RawResponse: err.Error(), | |||
| 306 | - | }) | |||
| 307 | } | 306 | } | ||
| 308 | 307 | ||||
| 309 | return sendDeployErrorResponse(c, "Failed to create or start container", err) | 308 | return sendDeployErrorResponse(c, "Failed to create or start container", err) | ||
| @@ -327,10 +326,12 @@ func processCompleteChunkedDeployTansfert(c echo.Context, a *server.App, transfe | |||||
| 327 | } | 326 | } | ||
| 328 | 327 | ||||
| 329 | func sendDeployErrorResponse(c echo.Context, message string, err error) error { | 328 | func sendDeployErrorResponse(c echo.Context, message string, err error) error { | ||
| 330 | - | log.Error(message, "error", err) | 329 | + | // The error becomes the message |
| 330 | + | errorMsg := fmt.Sprintf("%s: %s", message, err.Error()) | |||
| 331 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | 331 | return sendJSONResponse(c, http.StatusInternalServerError, DeployResponse{ | ||
| 332 | - | Success: false, | 332 | + | Success: false, |
| 333 | - | Message: message, | 333 | + | Message: errorMsg, |
| 334 | + | StatusCode: http.StatusInternalServerError, | |||
| 334 | }) | 335 | }) | ||
| 335 | } | 336 | } | ||
| 336 | 337 | ||||
M internal/httpserve/handlers/endpoint_deploy_check.go +14 -13
| @@ -5,21 +5,19 @@ import ( | |||
| 5 | 5 | "net/http" | |
| 6 | 6 | "strings" | |
| 7 | 7 | ||
| 8 | - | "github.com/bnema/gordon/internal/common" | |
| 9 | 8 | "github.com/bnema/gordon/internal/server" | |
| 10 | 9 | "github.com/bnema/gordon/pkg/docker" | |
| 11 | 10 | "github.com/charmbracelet/log" | |
| 12 | 11 | "github.com/labstack/echo/v4" | |
| 13 | 12 | ) | |
| 14 | 13 | ||
| 15 | - | type ConflictCheckResponse = common.ConflictCheckResponse | |
| 16 | - | ||
| 17 | 14 | func CheckDeployConflict(c echo.Context, a *server.App) error { | |
| 18 | 15 | targetDomain := c.QueryParam("domain") | |
| 19 | 16 | if targetDomain == "" { | |
| 20 | - | return sendJSONResponse(c, http.StatusBadRequest, ConflictCheckResponse{ | |
| 21 | - | Success: false, | |
| 22 | - | Message: "Target domain is required", | |
| 17 | + | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ | |
| 18 | + | Success: false, | |
| 19 | + | Message: "Target domain is required", | |
| 20 | + | StatusCode: http.StatusBadRequest, | |
| 23 | 21 | }) | |
| 24 | 22 | } | |
| 25 | 23 | ||
| @@ -28,18 +26,20 @@ func CheckDeployConflict(c echo.Context, a *server.App) error { | |||
| 28 | 26 | containerName := strings.Split(cleanDomain, ".")[0] | |
| 29 | 27 | ||
| 30 | 28 | if containerName == "" { | |
| 31 | - | return sendJSONResponse(c, http.StatusBadRequest, ConflictCheckResponse{ | |
| 32 | - | Success: false, | |
| 33 | - | Message: "Invalid target domain format", | |
| 29 | + | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ | |
| 30 | + | Success: false, | |
| 31 | + | Message: "Invalid target domain format", | |
| 32 | + | StatusCode: http.StatusBadRequest, | |
| 34 | 33 | }) | |
| 35 | 34 | } | |
| 36 | 35 | ||
| 37 | 36 | // Check if container exists | |
| 38 | 37 | containerID := docker.GetContainerIDByName(containerName) | |
| 39 | 38 | if containerID == "" { | |
| 40 | - | return sendJSONResponse(c, http.StatusOK, ConflictCheckResponse{ | |
| 41 | - | Success: true, | |
| 42 | - | Message: "No conflicts found", | |
| 39 | + | return sendJSONResponse(c, http.StatusOK, DeployResponse{ | |
| 40 | + | Success: true, | |
| 41 | + | Message: "No conflicts found", | |
| 42 | + | StatusCode: http.StatusOK, | |
| 43 | 43 | }) | |
| 44 | 44 | } | |
| 45 | 45 | ||
| @@ -62,7 +62,7 @@ func CheckDeployConflict(c echo.Context, a *server.App) error { | |||
| 62 | 62 | uptime = "unknown" | |
| 63 | 63 | } | |
| 64 | 64 | ||
| 65 | - | return sendJSONResponse(c, http.StatusConflict, ConflictCheckResponse{ | |
| 65 | + | return sendJSONResponse(c, http.StatusConflict, DeployResponse{ | |
| 66 | 66 | Success: false, | |
| 67 | 67 | Message: fmt.Sprintf("Container with name '%s' already exists", containerName), | |
| 68 | 68 | ContainerID: containerID, | |
| @@ -70,5 +70,6 @@ func CheckDeployConflict(c echo.Context, a *server.App) error { | |||
| 70 | 70 | State: state, | |
| 71 | 71 | RunningTime: uptime, | |
| 72 | 72 | Ports: ports, | |
| 73 | + | StatusCode: http.StatusConflict, | |
| 73 | 74 | }) | |
| 74 | 75 | } | |
| @@ -5,21 +5,19 @@ import ( | |||||
| 5 | "net/http" | 5 | "net/http" | ||
| 6 | "strings" | 6 | "strings" | ||
| 7 | 7 | ||||
| 8 | - | "github.com/bnema/gordon/internal/common" | |||
| 9 | "github.com/bnema/gordon/internal/server" | 8 | "github.com/bnema/gordon/internal/server" | ||
| 10 | "github.com/bnema/gordon/pkg/docker" | 9 | "github.com/bnema/gordon/pkg/docker" | ||
| 11 | "github.com/charmbracelet/log" | 10 | "github.com/charmbracelet/log" | ||
| 12 | "github.com/labstack/echo/v4" | 11 | "github.com/labstack/echo/v4" | ||
| 13 | ) | 12 | ) | ||
| 14 | 13 | ||||
| 15 | - | type ConflictCheckResponse = common.ConflictCheckResponse | |||
| 16 | - | ||||
| 17 | func CheckDeployConflict(c echo.Context, a *server.App) error { | 14 | func CheckDeployConflict(c echo.Context, a *server.App) error { | ||
| 18 | targetDomain := c.QueryParam("domain") | 15 | targetDomain := c.QueryParam("domain") | ||
| 19 | if targetDomain == "" { | 16 | if targetDomain == "" { | ||
| 20 | - | return sendJSONResponse(c, http.StatusBadRequest, ConflictCheckResponse{ | 17 | + | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ |
| 21 | - | Success: false, | 18 | + | Success: false, |
| 22 | - | Message: "Target domain is required", | 19 | + | Message: "Target domain is required", |
| 20 | + | StatusCode: http.StatusBadRequest, | |||
| 23 | }) | 21 | }) | ||
| 24 | } | 22 | } | ||
| 25 | 23 | ||||
| @@ -28,18 +26,20 @@ func CheckDeployConflict(c echo.Context, a *server.App) error { | |||||
| 28 | containerName := strings.Split(cleanDomain, ".")[0] | 26 | containerName := strings.Split(cleanDomain, ".")[0] | ||
| 29 | 27 | ||||
| 30 | if containerName == "" { | 28 | if containerName == "" { | ||
| 31 | - | return sendJSONResponse(c, http.StatusBadRequest, ConflictCheckResponse{ | 29 | + | return sendJSONResponse(c, http.StatusBadRequest, DeployResponse{ |
| 32 | - | Success: false, | 30 | + | Success: false, |
| 33 | - | Message: "Invalid target domain format", | 31 | + | Message: "Invalid target domain format", |
| 32 | + | StatusCode: http.StatusBadRequest, | |||
| 34 | }) | 33 | }) | ||
| 35 | } | 34 | } | ||
| 36 | 35 | ||||
| 37 | // Check if container exists | 36 | // Check if container exists | ||
| 38 | containerID := docker.GetContainerIDByName(containerName) | 37 | containerID := docker.GetContainerIDByName(containerName) | ||
| 39 | if containerID == "" { | 38 | if containerID == "" { | ||
| 40 | - | return sendJSONResponse(c, http.StatusOK, ConflictCheckResponse{ | 39 | + | return sendJSONResponse(c, http.StatusOK, DeployResponse{ |
| 41 | - | Success: true, | 40 | + | Success: true, |
| 42 | - | Message: "No conflicts found", | 41 | + | Message: "No conflicts found", |
| 42 | + | StatusCode: http.StatusOK, | |||
| 43 | }) | 43 | }) | ||
| 44 | } | 44 | } | ||
| 45 | 45 | ||||
| @@ -62,7 +62,7 @@ func CheckDeployConflict(c echo.Context, a *server.App) error { | |||||
| 62 | uptime = "unknown" | 62 | uptime = "unknown" | ||
| 63 | } | 63 | } | ||
| 64 | 64 | ||||
| 65 | - | return sendJSONResponse(c, http.StatusConflict, ConflictCheckResponse{ | 65 | + | return sendJSONResponse(c, http.StatusConflict, DeployResponse{ |
| 66 | Success: false, | 66 | Success: false, | ||
| 67 | Message: fmt.Sprintf("Container with name '%s' already exists", containerName), | 67 | Message: fmt.Sprintf("Container with name '%s' already exists", containerName), | ||
| 68 | ContainerID: containerID, | 68 | ContainerID: containerID, | ||
| @@ -70,5 +70,6 @@ func CheckDeployConflict(c echo.Context, a *server.App) error { | |||||
| 70 | State: state, | 70 | State: state, | ||
| 71 | RunningTime: uptime, | 71 | RunningTime: uptime, | ||
| 72 | Ports: ports, | 72 | Ports: ports, | ||
| 73 | + | StatusCode: http.StatusConflict, | |||
| 73 | }) | 74 | }) | ||
| 74 | } | 75 | } | ||