detached 0053bf8

Backend: Improve deployment process and error handling

- Enhance error handling in deployImage function
- Add container ID to deployment failure logs
- Refactor waitForDeployment function to include container ID
- Improve logging throughout the deployment process
- Add function to extract container ID from error messages
- Remove redundant debug logs and clean up code structure
commit
0053bf8b0c1e1870794c7409046c458dfe991d17
author
bamen · · 2024-09-27 11:57 UTC
parent
fb60b50
signature
unsigned
4 files · +77 -42
M internal/cli/cmd/deploy.go +34 -25
@@ -4,6 +4,7 @@ package cmd
44
55 import (
66 "encoding/json"
7+ "errors"
78 "fmt"
89 "io"
910 "net/http"
@@ -59,18 +60,8 @@ func NewDeployCommand(a *cli.App) *cobra.Command {
5960 log.Info("Image exported successfully", "image", imageName, "size", fmt.Sprintf("%.2fMB", sizeInMB))
6061
6162 if err := deployImage(a, reader, imageName, port, targetDomain); err != nil {
62- if deployErr, ok := err.(*common.DeploymentError); ok {
63- log.Error("Deployment failed",
64- "status_code", deployErr.StatusCode,
65- "message", deployErr.Message,
66- )
67- } else {
68- log.Error("Deployment failed", "error", err)
69- }
70- return
63+ os.Exit(1)
7164 }
72-
73- log.Info("Deployment successful!")
7465 },
7566 }
7667
@@ -124,6 +115,9 @@ func exportDockerImage(imageName string) (io.ReadCloser, int64, error) {
124115 }
125116
126117 func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain string) error {
118+
119+ log.Info("Attempting to deploy...", "image", imageName, "port", port, "target_domain", targetDomain)
120+
127121 reqPayload := common.RequestPayload{
128122 Type: "deploy",
129123 Payload: common.DeployPayload{
@@ -136,33 +130,45 @@ func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain str
136130
137131 resp, err := handler.SendHTTPRequest(a, &reqPayload, "POST", "/deploy")
138132 if err != nil {
139- if deployErr, ok := err.(*common.DeploymentError); ok {
140- log.Error("Deployment failed",
141- "status_code", deployErr.StatusCode,
142- "message", deployErr.Message,
143- )
144- } else {
145- log.Error("Error sending HTTP request", "error", err)
133+ var deployErr *common.DeploymentError
134+ if errors.As(err, &deployErr) {
135+ // Try to parse the raw response to get the container ID
136+ var deployResponse common.DeployResponse
137+ if jsonErr := json.Unmarshal([]byte(deployErr.RawResponse), &deployResponse); jsonErr == nil {
138+ log.Error("Deployment failed",
139+ "error", deployErr.Message,
140+ "status_code", deployErr.StatusCode,
141+ "container_id", deployResponse.ContainerID,
142+ )
143+ } else {
144+ // If parsing fails, just log the error without the container ID
145+ log.Error("Deployment failed", "error", deployErr.Message, "status_code", deployErr.StatusCode)
146+ }
147+ // Return nil to prevent further error logging
148+ return nil
146149 }
147- return nil // Return nil to prevent double-logging
150+ log.Error("Error sending HTTP request", "error", err)
151+ return nil
148152 }
149153
150154 var deployResponse common.DeployResponse
151155 if err := json.Unmarshal(resp.Body, &deployResponse); err != nil {
152- log.Error("Error parsing response", "error", err)
156+ log.Error("Error parsing response", "error", err, "response", string(resp.Body))
153157 return nil
154158 }
155159
156160 if !deployResponse.Success {
157- log.Error("Deployment failed", "message", deployResponse.Message)
161+ log.Error("Deployment failed",
162+ "message", deployResponse.Message,
163+ "container_id", deployResponse.ContainerID,
164+ )
158165 return nil
159166 }
160167
161- log.Info("Application deployed", "domain", deployResponse.Domain)
162- return waitForDeployment(deployResponse.Domain)
168+ return waitForDeployment(deployResponse.Domain, deployResponse.ContainerID)
163169 }
164170
165-func waitForDeployment(domain string) error {
171+func waitForDeployment(domain string, containerID string) error {
166172 client := &http.Client{Timeout: 10 * time.Second}
167173 maxRetries := 20
168174 retryInterval := time.Second
@@ -173,6 +179,9 @@ func waitForDeployment(domain string) error {
173179 if err == nil {
174180 defer resp.Body.Close()
175181 if resp.StatusCode == http.StatusOK {
182+ log.Info("Deployment successful",
183+ "domain", domain,
184+ "container_id", containerID)
176185 return nil
177186 }
178187 // Check for error messages in the response body
@@ -181,7 +190,7 @@ func waitForDeployment(domain string) error {
181190 return fmt.Errorf("deployment failed: %s", string(body))
182191 }
183192 }
184- log.Warn("Deployment not ready yet, retrying", "attempt", fmt.Sprintf("%d/20", i+1))
193+ log.Warn("Deployment not ready yet, retrying", "attempt", fmt.Sprintf("%d/%d", i+1, maxRetries))
185194 time.Sleep(retryInterval)
186195 }
187196
@@ -4,6 +4,7 @@ package cmd
4 4
5 import (5 import (
6 "encoding/json"6 "encoding/json"
7+ "errors"
7 "fmt"8 "fmt"
8 "io"9 "io"
9 "net/http"10 "net/http"
@@ -59,18 +60,8 @@ func NewDeployCommand(a *cli.App) *cobra.Command {
59 log.Info("Image exported successfully", "image", imageName, "size", fmt.Sprintf("%.2fMB", sizeInMB))60 log.Info("Image exported successfully", "image", imageName, "size", fmt.Sprintf("%.2fMB", sizeInMB))
60 61
61 if err := deployImage(a, reader, imageName, port, targetDomain); err != nil {62 if err := deployImage(a, reader, imageName, port, targetDomain); err != nil {
62- if deployErr, ok := err.(*common.DeploymentError); ok {63+ os.Exit(1)
63- log.Error("Deployment failed",
64- "status_code", deployErr.StatusCode,
65- "message", deployErr.Message,
66- )
67- } else {
68- log.Error("Deployment failed", "error", err)
69- }
70- return
71 }64 }
72-
73- log.Info("Deployment successful!")
74 },65 },
75 }66 }
76 67
@@ -124,6 +115,9 @@ func exportDockerImage(imageName string) (io.ReadCloser, int64, error) {
124 }115 }
125 116
126 func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain string) error {117 func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain string) error {
118+
119+ log.Info("Attempting to deploy...", "image", imageName, "port", port, "target_domain", targetDomain)
120+
127 reqPayload := common.RequestPayload{121 reqPayload := common.RequestPayload{
128 Type: "deploy",122 Type: "deploy",
129 Payload: common.DeployPayload{123 Payload: common.DeployPayload{
@@ -136,33 +130,45 @@ func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain str
136 130
137 resp, err := handler.SendHTTPRequest(a, &reqPayload, "POST", "/deploy")131 resp, err := handler.SendHTTPRequest(a, &reqPayload, "POST", "/deploy")
138 if err != nil {132 if err != nil {
139- if deployErr, ok := err.(*common.DeploymentError); ok {133+ var deployErr *common.DeploymentError
140- log.Error("Deployment failed",134+ if errors.As(err, &deployErr) {
141- "status_code", deployErr.StatusCode,135+ // Try to parse the raw response to get the container ID
142- "message", deployErr.Message,136+ var deployResponse common.DeployResponse
143- )137+ if jsonErr := json.Unmarshal([]byte(deployErr.RawResponse), &deployResponse); jsonErr == nil {
144- } else {138+ log.Error("Deployment failed",
145- log.Error("Error sending HTTP request", "error", err)139+ "error", deployErr.Message,
140+ "status_code", deployErr.StatusCode,
141+ "container_id", deployResponse.ContainerID,
142+ )
143+ } else {
144+ // If parsing fails, just log the error without the container ID
145+ log.Error("Deployment failed", "error", deployErr.Message, "status_code", deployErr.StatusCode)
146+ }
147+ // Return nil to prevent further error logging
148+ return nil
146 }149 }
147- return nil // Return nil to prevent double-logging150+ log.Error("Error sending HTTP request", "error", err)
151+ return nil
148 }152 }
149 153
150 var deployResponse common.DeployResponse154 var deployResponse common.DeployResponse
151 if err := json.Unmarshal(resp.Body, &deployResponse); err != nil {155 if err := json.Unmarshal(resp.Body, &deployResponse); err != nil {
152- log.Error("Error parsing response", "error", err)156+ log.Error("Error parsing response", "error", err, "response", string(resp.Body))
153 return nil157 return nil
154 }158 }
155 159
156 if !deployResponse.Success {160 if !deployResponse.Success {
157- log.Error("Deployment failed", "message", deployResponse.Message)161+ log.Error("Deployment failed",
162+ "message", deployResponse.Message,
163+ "container_id", deployResponse.ContainerID,
164+ )
158 return nil165 return nil
159 }166 }
160 167
161- log.Info("Application deployed", "domain", deployResponse.Domain)168+ return waitForDeployment(deployResponse.Domain, deployResponse.ContainerID)
162- return waitForDeployment(deployResponse.Domain)
163 }169 }
164 170
165-func waitForDeployment(domain string) error {171+func waitForDeployment(domain string, containerID string) error {
166 client := &http.Client{Timeout: 10 * time.Second}172 client := &http.Client{Timeout: 10 * time.Second}
167 maxRetries := 20173 maxRetries := 20
168 retryInterval := time.Second174 retryInterval := time.Second
@@ -173,6 +179,9 @@ func waitForDeployment(domain string) error {
173 if err == nil {179 if err == nil {
174 defer resp.Body.Close()180 defer resp.Body.Close()
175 if resp.StatusCode == http.StatusOK {181 if resp.StatusCode == http.StatusOK {
182+ log.Info("Deployment successful",
183+ "domain", domain,
184+ "container_id", containerID)
176 return nil185 return nil
177 }186 }
178 // Check for error messages in the response body187 // Check for error messages in the response body
@@ -181,7 +190,7 @@ func waitForDeployment(domain string) error {
181 return fmt.Errorf("deployment failed: %s", string(body))190 return fmt.Errorf("deployment failed: %s", string(body))
182 }191 }
183 }192 }
184- log.Warn("Deployment not ready yet, retrying", "attempt", fmt.Sprintf("%d/20", i+1))193+ log.Warn("Deployment not ready yet, retrying", "attempt", fmt.Sprintf("%d/%d", i+1, maxRetries))
185 time.Sleep(retryInterval)194 time.Sleep(retryInterval)
186 }195 }
187 196
M internal/httpserve/handler/cli-endpoints.go +31 -15
@@ -170,9 +170,6 @@ func generateCreateContainerURL(a *server.App, shortID string) string {
170170
171171 // PostDeploy handles the container deployment request
172172 func PostDeploy(c echo.Context, a *server.App) error {
173- // Debug
174- log.Info("Starting deployment process", "handler", "PostDeploy", "requestID", c.Response().Header().Get(echo.HeaderXRequestID))
175-
176173 payload, err := validateAndPrepareDeployPayload(c)
177174 if err != nil {
178175 // Debug
@@ -182,10 +179,8 @@ func PostDeploy(c echo.Context, a *server.App) error {
182179 Message: fmt.Sprintf("Invalid payload: %v", err),
183180 })
184181 }
185- // Debug
186- log.Info("Payload validated and prepared", "payload", payload)
187182
188- imagePath, err := saveAndImportDeployImage(c, a, payload)
183+ _, err = saveAndImportDeployImage(c, a, payload)
189184 if err != nil {
190185 // Debug
191186 log.Error("Failed to save and import image", "error", err)
@@ -194,16 +189,26 @@ func PostDeploy(c echo.Context, a *server.App) error {
194189 Message: fmt.Sprintf("Failed to save or import image: %v", err),
195190 })
196191 }
197- // Debug
198- log.Info("Image saved and imported successfully", "imagePath", imagePath)
199192
200193 containerID, err := createAndStartContainer(a, payload)
201194 if err != nil {
202195 if strings.Contains(err.Error(), "is already in use") {
203196 log.Warn("Container already exists", "error", err)
197+ extractedID := extractContainerID(err.Error())
198+ log.Info("Extracted container ID", "containerID", extractedID)
199+
200+ if extractedID == "" {
201+ log.Error("Failed to extract container ID from error message")
202+ return sendJSONResponse(c, http.StatusConflict, DeployResponse{
203+ Success: false,
204+ Message: "A container with this name already exists, but we couldn't extract its ID.",
205+ })
206+ }
207+
204208 return sendJSONResponse(c, http.StatusConflict, DeployResponse{
205- Success: false,
206- Message: fmt.Sprintf("Container with name '%s' already exists. Please remove it first or use a different name.", payload.ImageName),
209+ Success: false,
210+ Message: fmt.Sprintf("A container for this deploy already exists."),
211+ ContainerID: extractedID,
207212 })
208213 }
209214 log.Error("Failed to create or start container", "error", err)
@@ -212,9 +217,6 @@ func PostDeploy(c echo.Context, a *server.App) error {
212217 Message: fmt.Sprintf("Failed to create or start container: %v", err),
213218 })
214219 }
215- // Debug
216- log.Info("Container created and started successfully", "containerID", containerID)
217-
218220 response := DeployResponse{
219221 Success: true,
220222 Message: "Deployment successful",
@@ -222,12 +224,26 @@ func PostDeploy(c echo.Context, a *server.App) error {
222224 ContainerID: containerID,
223225 }
224226
225- // Debug
226- log.Info("Deployment completed successfully", "response", response)
227+ // Debug: Verify containerID in response
228+ if response.ContainerID != containerID {
229+ log.Error("Container ID mismatch", "responseID", response.ContainerID, "actualID", containerID)
230+ } else {
231+ log.Info("Container ID successfully stored in response", "containerID", containerID)
232+ }
227233
228234 return sendJSONResponse(c, http.StatusOK, response)
229235 }
230236
237+// is already in use by 08433f639cb1c01254d43f234360bfa793aa8c33b29e8de8ce42b0706c0090ba. example
238+func extractContainerID(errorMessage string) string {
239+ re := regexp.MustCompile(`by\s+([0-9a-f]+)\.`)
240+ match := re.FindStringSubmatch(errorMessage)
241+ if len(match) > 1 {
242+ return match[1]
243+ }
244+ return ""
245+}
246+
231247 // validateAndPrepareDeployPayload prepares the deployment payload
232248 func validateAndPrepareDeployPayload(c echo.Context) (*common.DeployPayload, error) {
233249 payload := &common.DeployPayload{
@@ -170,9 +170,6 @@ func generateCreateContainerURL(a *server.App, shortID string) string {
170 170
171 // PostDeploy handles the container deployment request171 // PostDeploy handles the container deployment request
172 func PostDeploy(c echo.Context, a *server.App) error {172 func PostDeploy(c echo.Context, a *server.App) error {
173- // Debug
174- log.Info("Starting deployment process", "handler", "PostDeploy", "requestID", c.Response().Header().Get(echo.HeaderXRequestID))
175-
176 payload, err := validateAndPrepareDeployPayload(c)173 payload, err := validateAndPrepareDeployPayload(c)
177 if err != nil {174 if err != nil {
178 // Debug175 // Debug
@@ -182,10 +179,8 @@ func PostDeploy(c echo.Context, a *server.App) error {
182 Message: fmt.Sprintf("Invalid payload: %v", err),179 Message: fmt.Sprintf("Invalid payload: %v", err),
183 })180 })
184 }181 }
185- // Debug
186- log.Info("Payload validated and prepared", "payload", payload)
187 182
188- imagePath, err := saveAndImportDeployImage(c, a, payload)183+ _, err = saveAndImportDeployImage(c, a, payload)
189 if err != nil {184 if err != nil {
190 // Debug185 // Debug
191 log.Error("Failed to save and import image", "error", err)186 log.Error("Failed to save and import image", "error", err)
@@ -194,16 +189,26 @@ func PostDeploy(c echo.Context, a *server.App) error {
194 Message: fmt.Sprintf("Failed to save or import image: %v", err),189 Message: fmt.Sprintf("Failed to save or import image: %v", err),
195 })190 })
196 }191 }
197- // Debug
198- log.Info("Image saved and imported successfully", "imagePath", imagePath)
199 192
200 containerID, err := createAndStartContainer(a, payload)193 containerID, err := createAndStartContainer(a, payload)
201 if err != nil {194 if err != nil {
202 if strings.Contains(err.Error(), "is already in use") {195 if strings.Contains(err.Error(), "is already in use") {
203 log.Warn("Container already exists", "error", err)196 log.Warn("Container already exists", "error", err)
197+ extractedID := extractContainerID(err.Error())
198+ log.Info("Extracted container ID", "containerID", extractedID)
199+
200+ if extractedID == "" {
201+ log.Error("Failed to extract container ID from error message")
202+ return sendJSONResponse(c, http.StatusConflict, DeployResponse{
203+ Success: false,
204+ Message: "A container with this name already exists, but we couldn't extract its ID.",
205+ })
206+ }
207+
204 return sendJSONResponse(c, http.StatusConflict, DeployResponse{208 return sendJSONResponse(c, http.StatusConflict, DeployResponse{
205- Success: false,209+ Success: false,
206- Message: fmt.Sprintf("Container with name '%s' already exists. Please remove it first or use a different name.", payload.ImageName),210+ Message: fmt.Sprintf("A container for this deploy already exists."),
211+ ContainerID: extractedID,
207 })212 })
208 }213 }
209 log.Error("Failed to create or start container", "error", err)214 log.Error("Failed to create or start container", "error", err)
@@ -212,9 +217,6 @@ func PostDeploy(c echo.Context, a *server.App) error {
212 Message: fmt.Sprintf("Failed to create or start container: %v", err),217 Message: fmt.Sprintf("Failed to create or start container: %v", err),
213 })218 })
214 }219 }
215- // Debug
216- log.Info("Container created and started successfully", "containerID", containerID)
217-
218 response := DeployResponse{220 response := DeployResponse{
219 Success: true,221 Success: true,
220 Message: "Deployment successful",222 Message: "Deployment successful",
@@ -222,12 +224,26 @@ func PostDeploy(c echo.Context, a *server.App) error {
222 ContainerID: containerID,224 ContainerID: containerID,
223 }225 }
224 226
225- // Debug227+ // Debug: Verify containerID in response
226- log.Info("Deployment completed successfully", "response", response)228+ if response.ContainerID != containerID {
229+ log.Error("Container ID mismatch", "responseID", response.ContainerID, "actualID", containerID)
230+ } else {
231+ log.Info("Container ID successfully stored in response", "containerID", containerID)
232+ }
227 233
228 return sendJSONResponse(c, http.StatusOK, response)234 return sendJSONResponse(c, http.StatusOK, response)
229 }235 }
230 236
237+// is already in use by 08433f639cb1c01254d43f234360bfa793aa8c33b29e8de8ce42b0706c0090ba. example
238+func extractContainerID(errorMessage string) string {
239+ re := regexp.MustCompile(`by\s+([0-9a-f]+)\.`)
240+ match := re.FindStringSubmatch(errorMessage)
241+ if len(match) > 1 {
242+ return match[1]
243+ }
244+ return ""
245+}
246+
231 // validateAndPrepareDeployPayload prepares the deployment payload247 // validateAndPrepareDeployPayload prepares the deployment payload
232 func validateAndPrepareDeployPayload(c echo.Context) (*common.DeployPayload, error) {248 func validateAndPrepareDeployPayload(c echo.Context) (*common.DeployPayload, error) {
233 payload := &common.DeployPayload{249 payload := &common.DeployPayload{
M internal/httpserve/handler/containers.go +0 -2
@@ -2,7 +2,6 @@ package handler
22
33 import (
44 "fmt"
5- "log"
65 "net/http"
76 "strings"
87 "sync"
@@ -41,7 +40,6 @@ func safelyInteractWithIDMap(op MapOperation, key string, value ...string) (stri
4140 case Update:
4241 if len(value) > 0 {
4342 idMap[key] = value[0]
44- log.Printf("Updated mapping: %s -> %s", key, value[0])
4543 }
4644 return "", true
4745 case Delete:
@@ -2,7 +2,6 @@ package handler
2 2
3 import (3 import (
4 "fmt"4 "fmt"
5- "log"
6 "net/http"5 "net/http"
7 "strings"6 "strings"
8 "sync"7 "sync"
@@ -41,7 +40,6 @@ func safelyInteractWithIDMap(op MapOperation, key string, value ...string) (stri
41 case Update:40 case Update:
42 if len(value) > 0 {41 if len(value) > 0 {
43 idMap[key] = value[0]42 idMap[key] = value[0]
44- log.Printf("Updated mapping: %s -> %s", key, value[0])
45 }43 }
46 return "", true44 return "", true
47 case Delete:45 case Delete:
M pkg/docker/containers.go +12 -0
@@ -316,6 +316,18 @@ func GetContainerInfo(containerID string) (types.ContainerJSON, error) {
316316 return containerInfo, nil
317317 }
318318
319+// GetContainerName returns the name of a container
320+func GetContainerName(containerID string) (string, error) {
321+ containerInfo, err := GetContainerInfo(containerID)
322+ if err != nil {
323+ return "", err
324+ }
325+
326+ // Debug
327+ fmt.Printf("Container name: %s\n", containerInfo.Name)
328+ return containerInfo.Name, nil
329+}
330+
319331 // UpdateContainerConfig updates the configuration of an existing container.
320332 func UpdateContainerConfig(containerID string, newConfig *container.Config, newHostConfig *container.HostConfig, newNetworkingConfig *network.NetworkingConfig) error {
321333 ctx := context.Background()
@@ -316,6 +316,18 @@ func GetContainerInfo(containerID string) (types.ContainerJSON, error) {
316 return containerInfo, nil316 return containerInfo, nil
317 }317 }
318 318
319+// GetContainerName returns the name of a container
320+func GetContainerName(containerID string) (string, error) {
321+ containerInfo, err := GetContainerInfo(containerID)
322+ if err != nil {
323+ return "", err
324+ }
325+
326+ // Debug
327+ fmt.Printf("Container name: %s\n", containerInfo.Name)
328+ return containerInfo.Name, nil
329+}
330+
319 // UpdateContainerConfig updates the configuration of an existing container.331 // UpdateContainerConfig updates the configuration of an existing container.
320 func UpdateContainerConfig(containerID string, newConfig *container.Config, newHostConfig *container.HostConfig, newNetworkingConfig *network.NetworkingConfig) error {332 func UpdateContainerConfig(containerID string, newConfig *container.Config, newHostConfig *container.HostConfig, newNetworkingConfig *network.NetworkingConfig) error {
321 ctx := context.Background()333 ctx := context.Background()

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