refactor: rework pre-upload conflict detection and optimize deployment process
- commit
- aead5450755e4139b478636cc1be009f21632632
- author
- Brice · · 2024-11-15 13:48 UTC
- parent
- 6eee3b5
- signature
- unsigned
4 files · +60 -75
M internal/cli/cmd/deploy.go +35 -37
| @@ -72,19 +72,14 @@ func NewDeployCommand(a *cli.App) *cobra.Command { | |||
| 72 | 72 | return deployCmd | |
| 73 | 73 | } | |
| 74 | 74 | ||
| 75 | - | // Client side - deploy.go | |
| 76 | - | ||
| 77 | 75 | func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain string) error { | |
| 78 | - | // Create a buffer to store the entire image data | |
| 79 | 76 | var buf bytes.Buffer | |
| 80 | 77 | size, err := io.Copy(&buf, reader) | |
| 81 | 78 | if err != nil { | |
| 82 | 79 | return fmt.Errorf("failed to read image data: %w", err) | |
| 83 | 80 | } | |
| 84 | 81 | ||
| 85 | - | // Create a new reader from the buffer | |
| 86 | 82 | imageReader := bytes.NewReader(buf.Bytes()) | |
| 87 | - | ||
| 88 | 83 | sizeInMB := float64(size) / 1024 / 1024 | |
| 89 | 84 | log.Info("Attempting to deploy...", | |
| 90 | 85 | "image", imageName, | |
| @@ -100,51 +95,54 @@ func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain str | |||
| 100 | 95 | "Content-Type": {"application/octet-stream"}, | |
| 101 | 96 | } | |
| 102 | 97 | ||
| 98 | + | // Send a small chunk first to detect any conflicts | |
| 103 | 99 | chunkedClient := handler.NewChunkedClient(a) | |
| 104 | - | var finalResponse *common.DeployResponse | |
| 100 | + | ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) | |
| 101 | + | defer cancel() | |
| 105 | 102 | ||
| 106 | - | for { | |
| 107 | - | ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) | |
| 108 | - | defer cancel() | |
| 103 | + | // Create a small test chunk | |
| 104 | + | testChunk := buf.Bytes()[:1024] // Just send first 1KB to check for conflicts | |
| 105 | + | testReader := bytes.NewReader(testChunk) | |
| 109 | 106 | ||
| 110 | - | resp, err := chunkedClient.SendFile(ctx, "/deploy", headers, imageReader, size, imageName) | |
| 111 | - | if err != nil { | |
| 112 | - | var deployErr *common.DeploymentError | |
| 113 | - | if errors.As(err, &deployErr) && deployErr.StatusCode == http.StatusConflict { | |
| 114 | - | var deployResponse common.DeployResponse | |
| 115 | - | if jsonErr := json.Unmarshal([]byte(deployErr.RawResponse), &deployResponse); jsonErr == nil { | |
| 116 | - | if deployResponse.ContainerID != "" { | |
| 117 | - | if err := handler.HandleExistingContainer(a, &deployResponse); err != nil { | |
| 118 | - | if strings.Contains(err.Error(), "cancelled by user") { | |
| 119 | - | return fmt.Errorf("deployment cancelled by user") | |
| 120 | - | } | |
| 121 | - | return fmt.Errorf("failed to handle existing container: %w", err) | |
| 107 | + | resp, err := chunkedClient.SendFile(ctx, "/deploy", headers, testReader, int64(len(testChunk)), imageName) | |
| 108 | + | if err != nil { | |
| 109 | + | var deployErr *common.DeploymentError | |
| 110 | + | if errors.As(err, &deployErr) && deployErr.StatusCode == http.StatusConflict { | |
| 111 | + | var deployResponse common.DeployResponse | |
| 112 | + | if jsonErr := json.Unmarshal([]byte(deployErr.RawResponse), &deployResponse); jsonErr == nil { | |
| 113 | + | if deployResponse.ContainerID != "" { | |
| 114 | + | if err := handler.HandleExistingContainer(a, &deployResponse); err != nil { | |
| 115 | + | if strings.Contains(err.Error(), "cancelled by user") { | |
| 116 | + | return fmt.Errorf("deployment cancelled by user") | |
| 122 | 117 | } | |
| 123 | - | // Reset the reader for retry | |
| 124 | - | imageReader.Seek(0, 0) | |
| 125 | - | continue // Retry deployment | |
| 118 | + | return fmt.Errorf("failed to handle existing container: %w", err) | |
| 119 | + | } | |
| 120 | + | // Now proceed with the full upload | |
| 121 | + | imageReader.Seek(0, 0) // Reset reader to beginning | |
| 122 | + | resp, err = chunkedClient.SendFile(ctx, "/deploy", headers, imageReader, size, imageName) | |
| 123 | + | if err != nil { | |
| 124 | + | return fmt.Errorf("deployment failed after container removal: %w", err) | |
| 126 | 125 | } | |
| 127 | 126 | } | |
| 128 | 127 | } | |
| 128 | + | } else { | |
| 129 | 129 | return fmt.Errorf("deployment failed: %w", err) | |
| 130 | 130 | } | |
| 131 | - | ||
| 132 | - | // Parse the final response | |
| 133 | - | if resp != nil { | |
| 134 | - | var deployResp common.DeployResponse | |
| 135 | - | if err := json.Unmarshal(resp.Body, &deployResp); err != nil { | |
| 136 | - | return fmt.Errorf("failed to parse deployment response: %w", err) | |
| 137 | - | } | |
| 138 | - | finalResponse = &deployResp | |
| 131 | + | } else { | |
| 132 | + | // No conflict detected, proceed with the rest of the upload | |
| 133 | + | imageReader.Seek(1024, 0) // Skip the first 1KB we already sent | |
| 134 | + | resp, err = chunkedClient.SendFile(ctx, "/deploy", headers, imageReader, size-1024, imageName) | |
| 135 | + | if err != nil { | |
| 136 | + | return fmt.Errorf("deployment failed during main upload: %w", err) | |
| 139 | 137 | } | |
| 140 | - | break // Successful deployment | |
| 141 | 138 | } | |
| 142 | 139 | ||
| 143 | - | // Use the response data for waiting | |
| 144 | - | if finalResponse != nil { | |
| 145 | - | return waitForDeployment(finalResponse.Domain, finalResponse.ContainerID) | |
| 140 | + | var deployResp common.DeployResponse | |
| 141 | + | if err := json.Unmarshal(resp.Body, &deployResp); err != nil { | |
| 142 | + | return fmt.Errorf("failed to parse deployment response: %w", err) | |
| 146 | 143 | } | |
| 147 | - | return fmt.Errorf("no response received from deployment") | |
| 144 | + | ||
| 145 | + | return waitForDeployment(deployResp.Domain, deployResp.ContainerID) | |
| 148 | 146 | } | |
| 149 | 147 | ||
| 150 | 148 | func waitForDeployment(domain string, containerID string) error { | |
| @@ -72,19 +72,14 @@ func NewDeployCommand(a *cli.App) *cobra.Command { | |||||
| 72 | return deployCmd | 72 | return deployCmd | ||
| 73 | } | 73 | } | ||
| 74 | 74 | ||||
| 75 | - | // Client side - deploy.go | |||
| 76 | - | ||||
| 77 | func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain string) error { | 75 | func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain string) error { | ||
| 78 | - | // Create a buffer to store the entire image data | |||
| 79 | var buf bytes.Buffer | 76 | var buf bytes.Buffer | ||
| 80 | size, err := io.Copy(&buf, reader) | 77 | size, err := io.Copy(&buf, reader) | ||
| 81 | if err != nil { | 78 | if err != nil { | ||
| 82 | return fmt.Errorf("failed to read image data: %w", err) | 79 | return fmt.Errorf("failed to read image data: %w", err) | ||
| 83 | } | 80 | } | ||
| 84 | 81 | ||||
| 85 | - | // Create a new reader from the buffer | |||
| 86 | imageReader := bytes.NewReader(buf.Bytes()) | 82 | imageReader := bytes.NewReader(buf.Bytes()) | ||
| 87 | - | ||||
| 88 | sizeInMB := float64(size) / 1024 / 1024 | 83 | sizeInMB := float64(size) / 1024 / 1024 | ||
| 89 | log.Info("Attempting to deploy...", | 84 | log.Info("Attempting to deploy...", | ||
| 90 | "image", imageName, | 85 | "image", imageName, | ||
| @@ -100,51 +95,54 @@ func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain str | |||||
| 100 | "Content-Type": {"application/octet-stream"}, | 95 | "Content-Type": {"application/octet-stream"}, | ||
| 101 | } | 96 | } | ||
| 102 | 97 | ||||
| 98 | + | // Send a small chunk first to detect any conflicts | |||
| 103 | chunkedClient := handler.NewChunkedClient(a) | 99 | chunkedClient := handler.NewChunkedClient(a) | ||
| 104 | - | var finalResponse *common.DeployResponse | 100 | + | ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) |
| 101 | + | defer cancel() | |||
| 105 | 102 | ||||
| 106 | - | for { | 103 | + | // Create a small test chunk |
| 107 | - | ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute) | 104 | + | testChunk := buf.Bytes()[:1024] // Just send first 1KB to check for conflicts |
| 108 | - | defer cancel() | 105 | + | testReader := bytes.NewReader(testChunk) |
| 109 | 106 | ||||
| 110 | - | resp, err := chunkedClient.SendFile(ctx, "/deploy", headers, imageReader, size, imageName) | 107 | + | resp, err := chunkedClient.SendFile(ctx, "/deploy", headers, testReader, int64(len(testChunk)), imageName) |
| 111 | - | if err != nil { | 108 | + | if err != nil { |
| 112 | - | var deployErr *common.DeploymentError | 109 | + | var deployErr *common.DeploymentError |
| 113 | - | if errors.As(err, &deployErr) && deployErr.StatusCode == http.StatusConflict { | 110 | + | if errors.As(err, &deployErr) && deployErr.StatusCode == http.StatusConflict { |
| 114 | - | var deployResponse common.DeployResponse | 111 | + | var deployResponse common.DeployResponse |
| 115 | - | if jsonErr := json.Unmarshal([]byte(deployErr.RawResponse), &deployResponse); jsonErr == nil { | 112 | + | if jsonErr := json.Unmarshal([]byte(deployErr.RawResponse), &deployResponse); jsonErr == nil { |
| 116 | - | if deployResponse.ContainerID != "" { | 113 | + | if deployResponse.ContainerID != "" { |
| 117 | - | if err := handler.HandleExistingContainer(a, &deployResponse); err != nil { | 114 | + | if err := handler.HandleExistingContainer(a, &deployResponse); err != nil { |
| 118 | - | if strings.Contains(err.Error(), "cancelled by user") { | 115 | + | if strings.Contains(err.Error(), "cancelled by user") { |
| 119 | - | return fmt.Errorf("deployment cancelled by user") | 116 | + | return fmt.Errorf("deployment cancelled by user") |
| 120 | - | } | |||
| 121 | - | return fmt.Errorf("failed to handle existing container: %w", err) | |||
| 122 | } | 117 | } | ||
| 123 | - | // Reset the reader for retry | 118 | + | return fmt.Errorf("failed to handle existing container: %w", err) |
| 124 | - | imageReader.Seek(0, 0) | 119 | + | } |
| 125 | - | continue // Retry deployment | 120 | + | // Now proceed with the full upload |
| 121 | + | imageReader.Seek(0, 0) // Reset reader to beginning | |||
| 122 | + | resp, err = chunkedClient.SendFile(ctx, "/deploy", headers, imageReader, size, imageName) | |||
| 123 | + | if err != nil { | |||
| 124 | + | return fmt.Errorf("deployment failed after container removal: %w", err) | |||
| 126 | } | 125 | } | ||
| 127 | } | 126 | } | ||
| 128 | } | 127 | } | ||
| 128 | + | } else { | |||
| 129 | return fmt.Errorf("deployment failed: %w", err) | 129 | return fmt.Errorf("deployment failed: %w", err) | ||
| 130 | } | 130 | } | ||
| 131 | - | 131 | + | } else { | |
| 132 | - | // Parse the final response | 132 | + | // No conflict detected, proceed with the rest of the upload |
| 133 | - | if resp != nil { | 133 | + | imageReader.Seek(1024, 0) // Skip the first 1KB we already sent |
| 134 | - | var deployResp common.DeployResponse | 134 | + | resp, err = chunkedClient.SendFile(ctx, "/deploy", headers, imageReader, size-1024, imageName) |
| 135 | - | if err := json.Unmarshal(resp.Body, &deployResp); err != nil { | 135 | + | if err != nil { |
| 136 | - | return fmt.Errorf("failed to parse deployment response: %w", err) | 136 | + | return fmt.Errorf("deployment failed during main upload: %w", err) |
| 137 | - | } | |||
| 138 | - | finalResponse = &deployResp | |||
| 139 | } | 137 | } | ||
| 140 | - | break // Successful deployment | |||
| 141 | } | 138 | } | ||
| 142 | 139 | ||||
| 143 | - | // Use the response data for waiting | 140 | + | var deployResp common.DeployResponse |
| 144 | - | if finalResponse != nil { | 141 | + | if err := json.Unmarshal(resp.Body, &deployResp); err != nil { |
| 145 | - | return waitForDeployment(finalResponse.Domain, finalResponse.ContainerID) | 142 | + | return fmt.Errorf("failed to parse deployment response: %w", err) |
| 146 | } | 143 | } | ||
| 147 | - | return fmt.Errorf("no response received from deployment") | 144 | + | |
| 145 | + | return waitForDeployment(deployResp.Domain, deployResp.ContainerID) | |||
| 148 | } | 146 | } | ||
| 149 | 147 | ||||
| 150 | func waitForDeployment(domain string, containerID string) error { | 148 | func waitForDeployment(domain string, containerID string) error { | ||
M internal/cli/cmd/push.go +1 -5
| @@ -77,7 +77,7 @@ func pushImage(a *cli.App, imageName string) error { | |||
| 77 | 77 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) | |
| 78 | 78 | defer cancel() | |
| 79 | 79 | ||
| 80 | - | resp, err := chunkedClient.SendFile(ctx, "/push", headers, reader, actualSize, imageName) | |
| 80 | + | _, err = chunkedClient.SendFile(ctx, "/push", headers, reader, actualSize, imageName) | |
| 81 | 81 | if err != nil { | |
| 82 | 82 | var pushErr *common.DeploymentError | |
| 83 | 83 | if errors.As(err, &pushErr) { | |
| @@ -86,9 +86,5 @@ func pushImage(a *cli.App, imageName string) error { | |||
| 86 | 86 | return fmt.Errorf("chunked transfer failed: %w", err) | |
| 87 | 87 | } | |
| 88 | 88 | ||
| 89 | - | fmt.Println("Push response:", resp) | |
| 90 | - | ||
| 91 | - | log.Info("Image pushed and imported successfully", "image", imageName) | |
| 92 | - | ||
| 93 | 89 | return nil | |
| 94 | 90 | } | |
| @@ -77,7 +77,7 @@ func pushImage(a *cli.App, imageName string) error { | |||||
| 77 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) | 77 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) | ||
| 78 | defer cancel() | 78 | defer cancel() | ||
| 79 | 79 | ||||
| 80 | - | resp, err := chunkedClient.SendFile(ctx, "/push", headers, reader, actualSize, imageName) | 80 | + | _, err = chunkedClient.SendFile(ctx, "/push", headers, reader, actualSize, imageName) |
| 81 | if err != nil { | 81 | if err != nil { | ||
| 82 | var pushErr *common.DeploymentError | 82 | var pushErr *common.DeploymentError | ||
| 83 | if errors.As(err, &pushErr) { | 83 | if errors.As(err, &pushErr) { | ||
| @@ -86,9 +86,5 @@ func pushImage(a *cli.App, imageName string) error { | |||||
| 86 | return fmt.Errorf("chunked transfer failed: %w", err) | 86 | return fmt.Errorf("chunked transfer failed: %w", err) | ||
| 87 | } | 87 | } | ||
| 88 | 88 | ||||
| 89 | - | fmt.Println("Push response:", resp) | |||
| 90 | - | ||||
| 91 | - | log.Info("Image pushed and imported successfully", "image", imageName) | |||
| 92 | - | ||||
| 93 | return nil | 89 | return nil | ||
| 94 | } | 90 | } | ||
M internal/cli/handler/chunked_transfer.go +24 -23
| @@ -132,12 +132,6 @@ func handleChunkResponse(resp *Response, responseType string) error { | |||
| 132 | 132 | if err := json.Unmarshal(resp.Body, &deployResp); err != nil { | |
| 133 | 133 | return fmt.Errorf("failed to unmarshal deploy response: %w", err) | |
| 134 | 134 | } | |
| 135 | - | log.Info("Deploy chunk response", | |
| 136 | - | "success", deployResp.Success, | |
| 137 | - | "message", deployResp.Message, | |
| 138 | - | "domain", deployResp.Domain, | |
| 139 | - | "containerID", deployResp.ContainerID, | |
| 140 | - | "containerName", deployResp.ContainerName) | |
| 141 | 135 | return nil | |
| 142 | 136 | ||
| 143 | 137 | case "push": | |
| @@ -145,11 +139,7 @@ func handleChunkResponse(resp *Response, responseType string) error { | |||
| 145 | 139 | if err := json.Unmarshal(resp.Body, &pushResp); err != nil { | |
| 146 | 140 | return fmt.Errorf("failed to unmarshal push response: %w", err) | |
| 147 | 141 | } | |
| 148 | - | log.Info("Push chunk response", | |
| 149 | - | "success", pushResp.Success, | |
| 150 | - | "message", pushResp.Message, | |
| 151 | - | "createContainerURL", pushResp.CreateContainerURL, | |
| 152 | - | "imageID", pushResp.ImageID) | |
| 142 | + | ||
| 153 | 143 | return nil | |
| 154 | 144 | ||
| 155 | 145 | default: | |
| @@ -164,9 +154,11 @@ func handleFinalResponse(resp *Response, responseType string) error { | |||
| 164 | 154 | if err := json.Unmarshal(resp.Body, &finalResp); err != nil { | |
| 165 | 155 | return fmt.Errorf("failed to unmarshal final deploy response: %w", err) | |
| 166 | 156 | } | |
| 167 | - | log.Info("Final deploy response", | |
| 168 | - | "success", finalResp.Success, | |
| 169 | - | "message", finalResp.Message, | |
| 157 | + | // only keep the 8 first characters of the container ID | |
| 158 | + | finalResp.ContainerID = finalResp.ContainerID[:8] | |
| 159 | + | ||
| 160 | + | log.Info( | |
| 161 | + | finalResp.Message, | |
| 170 | 162 | "domain", finalResp.Domain, | |
| 171 | 163 | "containerID", finalResp.ContainerID, | |
| 172 | 164 | "containerName", finalResp.ContainerName) | |
| @@ -177,9 +169,8 @@ func handleFinalResponse(resp *Response, responseType string) error { | |||
| 177 | 169 | if err := json.Unmarshal(resp.Body, &finalResp); err != nil { | |
| 178 | 170 | return fmt.Errorf("failed to unmarshal final push response: %w", err) | |
| 179 | 171 | } | |
| 180 | - | log.Info("Final push response", | |
| 181 | - | "success", finalResp.Success, | |
| 182 | - | "message", finalResp.Message, | |
| 172 | + | log.Info( | |
| 173 | + | finalResp.Message, | |
| 183 | 174 | "createContainerURL", finalResp.CreateContainerURL, | |
| 184 | 175 | "imageID", finalResp.ImageID) | |
| 185 | 176 | return nil | |
| @@ -190,18 +181,28 @@ func handleFinalResponse(resp *Response, responseType string) error { | |||
| 190 | 181 | } | |
| 191 | 182 | ||
| 192 | 183 | func (c *ChunkedClient) sendChunkWithRetry(ctx context.Context, endpoint string, headers http.Header, chunk []byte, metadata common.ChunkMetadata) (*Response, error) { | |
| 184 | + | // For the first chunk (conflict check), only try once | |
| 185 | + | maxRetries := 3 | |
| 186 | + | if metadata.ChunkNumber == 0 && len(chunk) == 1024 { | |
| 187 | + | maxRetries = 1 | |
| 188 | + | } | |
| 189 | + | ||
| 193 | 190 | var lastErr error | |
| 194 | - | for attempt := 0; attempt < 3; attempt++ { | |
| 191 | + | for attempt := 0; attempt < maxRetries; attempt++ { | |
| 195 | 192 | resp, err := c.sendChunk(ctx, endpoint, headers, chunk, metadata) | |
| 196 | 193 | if err == nil { | |
| 197 | 194 | return resp, nil | |
| 198 | 195 | } | |
| 199 | 196 | lastErr = err | |
| 200 | - | log.Warn("Chunk upload failed, retrying", | |
| 201 | - | "chunk", metadata.ChunkNumber, | |
| 202 | - | "attempt", attempt+1, | |
| 203 | - | "error", err) | |
| 204 | - | time.Sleep(time.Second * time.Duration(attempt+1)) | |
| 197 | + | ||
| 198 | + | // Don't log retry messages for the conflict check | |
| 199 | + | if maxRetries > 1 { | |
| 200 | + | log.Warn("Chunk upload failed, retrying", | |
| 201 | + | "chunk", metadata.ChunkNumber, | |
| 202 | + | "attempt", attempt+1, | |
| 203 | + | "error", err) | |
| 204 | + | time.Sleep(time.Second * time.Duration(attempt+1)) | |
| 205 | + | } | |
| 205 | 206 | } | |
| 206 | 207 | return nil, fmt.Errorf("max retries exceeded: %w", lastErr) | |
| 207 | 208 | } | |
| @@ -132,12 +132,6 @@ func handleChunkResponse(resp *Response, responseType string) error { | |||||
| 132 | if err := json.Unmarshal(resp.Body, &deployResp); err != nil { | 132 | if err := json.Unmarshal(resp.Body, &deployResp); err != nil { | ||
| 133 | return fmt.Errorf("failed to unmarshal deploy response: %w", err) | 133 | return fmt.Errorf("failed to unmarshal deploy response: %w", err) | ||
| 134 | } | 134 | } | ||
| 135 | - | log.Info("Deploy chunk response", | |||
| 136 | - | "success", deployResp.Success, | |||
| 137 | - | "message", deployResp.Message, | |||
| 138 | - | "domain", deployResp.Domain, | |||
| 139 | - | "containerID", deployResp.ContainerID, | |||
| 140 | - | "containerName", deployResp.ContainerName) | |||
| 141 | return nil | 135 | return nil | ||
| 142 | 136 | ||||
| 143 | case "push": | 137 | case "push": | ||
| @@ -145,11 +139,7 @@ func handleChunkResponse(resp *Response, responseType string) error { | |||||
| 145 | if err := json.Unmarshal(resp.Body, &pushResp); err != nil { | 139 | if err := json.Unmarshal(resp.Body, &pushResp); err != nil { | ||
| 146 | return fmt.Errorf("failed to unmarshal push response: %w", err) | 140 | return fmt.Errorf("failed to unmarshal push response: %w", err) | ||
| 147 | } | 141 | } | ||
| 148 | - | log.Info("Push chunk response", | 142 | + | |
| 149 | - | "success", pushResp.Success, | |||
| 150 | - | "message", pushResp.Message, | |||
| 151 | - | "createContainerURL", pushResp.CreateContainerURL, | |||
| 152 | - | "imageID", pushResp.ImageID) | |||
| 153 | return nil | 143 | return nil | ||
| 154 | 144 | ||||
| 155 | default: | 145 | default: | ||
| @@ -164,9 +154,11 @@ func handleFinalResponse(resp *Response, responseType string) error { | |||||
| 164 | if err := json.Unmarshal(resp.Body, &finalResp); err != nil { | 154 | if err := json.Unmarshal(resp.Body, &finalResp); err != nil { | ||
| 165 | return fmt.Errorf("failed to unmarshal final deploy response: %w", err) | 155 | return fmt.Errorf("failed to unmarshal final deploy response: %w", err) | ||
| 166 | } | 156 | } | ||
| 167 | - | log.Info("Final deploy response", | 157 | + | // only keep the 8 first characters of the container ID |
| 168 | - | "success", finalResp.Success, | 158 | + | finalResp.ContainerID = finalResp.ContainerID[:8] |
| 169 | - | "message", finalResp.Message, | 159 | + | |
| 160 | + | log.Info( | |||
| 161 | + | finalResp.Message, | |||
| 170 | "domain", finalResp.Domain, | 162 | "domain", finalResp.Domain, | ||
| 171 | "containerID", finalResp.ContainerID, | 163 | "containerID", finalResp.ContainerID, | ||
| 172 | "containerName", finalResp.ContainerName) | 164 | "containerName", finalResp.ContainerName) | ||
| @@ -177,9 +169,8 @@ func handleFinalResponse(resp *Response, responseType string) error { | |||||
| 177 | if err := json.Unmarshal(resp.Body, &finalResp); err != nil { | 169 | if err := json.Unmarshal(resp.Body, &finalResp); err != nil { | ||
| 178 | return fmt.Errorf("failed to unmarshal final push response: %w", err) | 170 | return fmt.Errorf("failed to unmarshal final push response: %w", err) | ||
| 179 | } | 171 | } | ||
| 180 | - | log.Info("Final push response", | 172 | + | log.Info( |
| 181 | - | "success", finalResp.Success, | 173 | + | finalResp.Message, |
| 182 | - | "message", finalResp.Message, | |||
| 183 | "createContainerURL", finalResp.CreateContainerURL, | 174 | "createContainerURL", finalResp.CreateContainerURL, | ||
| 184 | "imageID", finalResp.ImageID) | 175 | "imageID", finalResp.ImageID) | ||
| 185 | return nil | 176 | return nil | ||
| @@ -190,18 +181,28 @@ func handleFinalResponse(resp *Response, responseType string) error { | |||||
| 190 | } | 181 | } | ||
| 191 | 182 | ||||
| 192 | func (c *ChunkedClient) sendChunkWithRetry(ctx context.Context, endpoint string, headers http.Header, chunk []byte, metadata common.ChunkMetadata) (*Response, error) { | 183 | func (c *ChunkedClient) sendChunkWithRetry(ctx context.Context, endpoint string, headers http.Header, chunk []byte, metadata common.ChunkMetadata) (*Response, error) { | ||
| 184 | + | // For the first chunk (conflict check), only try once | |||
| 185 | + | maxRetries := 3 | |||
| 186 | + | if metadata.ChunkNumber == 0 && len(chunk) == 1024 { | |||
| 187 | + | maxRetries = 1 | |||
| 188 | + | } | |||
| 189 | + | ||||
| 193 | var lastErr error | 190 | var lastErr error | ||
| 194 | - | for attempt := 0; attempt < 3; attempt++ { | 191 | + | for attempt := 0; attempt < maxRetries; attempt++ { |
| 195 | resp, err := c.sendChunk(ctx, endpoint, headers, chunk, metadata) | 192 | resp, err := c.sendChunk(ctx, endpoint, headers, chunk, metadata) | ||
| 196 | if err == nil { | 193 | if err == nil { | ||
| 197 | return resp, nil | 194 | return resp, nil | ||
| 198 | } | 195 | } | ||
| 199 | lastErr = err | 196 | lastErr = err | ||
| 200 | - | log.Warn("Chunk upload failed, retrying", | 197 | + | |
| 201 | - | "chunk", metadata.ChunkNumber, | 198 | + | // Don't log retry messages for the conflict check |
| 202 | - | "attempt", attempt+1, | 199 | + | if maxRetries > 1 { |
| 203 | - | "error", err) | 200 | + | log.Warn("Chunk upload failed, retrying", |
| 204 | - | time.Sleep(time.Second * time.Duration(attempt+1)) | 201 | + | "chunk", metadata.ChunkNumber, |
| 202 | + | "attempt", attempt+1, | |||
| 203 | + | "error", err) | |||
| 204 | + | time.Sleep(time.Second * time.Duration(attempt+1)) | |||
| 205 | + | } | |||
| 205 | } | 206 | } | ||
| 206 | return nil, fmt.Errorf("max retries exceeded: %w", lastErr) | 207 | return nil, fmt.Errorf("max retries exceeded: %w", lastErr) | ||
| 207 | } | 208 | } | ||
M pkg/docker/images.go +0 -10
| @@ -56,14 +56,12 @@ func GetImageIDByName(imageName string) (string, error) { | |||
| 56 | 56 | } | |
| 57 | 57 | ||
| 58 | 58 | normalizedName, tag := normalizeImageName(imageName) | |
| 59 | - | fmt.Printf("Searching for image: %s:%s\n", normalizedName, tag) | |
| 60 | 59 | ||
| 61 | 60 | tagPattern := createTagPattern(tag) | |
| 62 | 61 | ||
| 63 | 62 | for _, img := range images { | |
| 64 | 63 | for _, repoTag := range img.RepoTags { | |
| 65 | 64 | normalizedRepoTag, repoTagValue := normalizeImageName(repoTag) | |
| 66 | - | fmt.Printf("Comparing with: %s:%s\n", normalizedRepoTag, repoTagValue) | |
| 67 | 65 | ||
| 68 | 66 | // Check for exact match (including with docker.io/ prefix) | |
| 69 | 67 | if repoTag == imageName || repoTag == "docker.io/"+imageName { | |
| @@ -134,8 +132,6 @@ func ImportImageToEngine(imageFilePath string) (string, error) { | |||
| 134 | 132 | } | |
| 135 | 133 | defer imageFile.Close() | |
| 136 | 134 | ||
| 137 | - | log.Printf("Image file opened successfully") | |
| 138 | - | ||
| 139 | 135 | resp, err := dockerCli.ImageLoad(context.Background(), imageFile, true) | |
| 140 | 136 | if err != nil { | |
| 141 | 137 | log.Printf("Failed to load image: %v", err) | |
| @@ -143,13 +139,10 @@ func ImportImageToEngine(imageFilePath string) (string, error) { | |||
| 143 | 139 | } | |
| 144 | 140 | defer resp.Body.Close() | |
| 145 | 141 | ||
| 146 | - | log.Printf("Image load initiated, parsing response") | |
| 147 | - | ||
| 148 | 142 | var imageName string | |
| 149 | 143 | scanner := bufio.NewScanner(resp.Body) | |
| 150 | 144 | for scanner.Scan() { | |
| 151 | 145 | line := scanner.Text() | |
| 152 | - | log.Printf("Response line: %s", line) | |
| 153 | 146 | ||
| 154 | 147 | var message struct { | |
| 155 | 148 | Stream string `json:"stream"` | |
| @@ -159,11 +152,8 @@ func ImportImageToEngine(imageFilePath string) (string, error) { | |||
| 159 | 152 | continue // Skip lines that aren't JSON | |
| 160 | 153 | } | |
| 161 | 154 | ||
| 162 | - | log.Printf("Parsed message stream: %s", message.Stream) | |
| 163 | - | ||
| 164 | 155 | if strings.HasPrefix(message.Stream, "Loaded image: ") { | |
| 165 | 156 | imageName = strings.TrimSpace(strings.TrimPrefix(message.Stream, "Loaded image: ")) | |
| 166 | - | log.Printf("Found image name: %s", imageName) | |
| 167 | 157 | break | |
| 168 | 158 | } | |
| 169 | 159 | } | |
| @@ -56,14 +56,12 @@ func GetImageIDByName(imageName string) (string, error) { | |||||
| 56 | } | 56 | } | ||
| 57 | 57 | ||||
| 58 | normalizedName, tag := normalizeImageName(imageName) | 58 | normalizedName, tag := normalizeImageName(imageName) | ||
| 59 | - | fmt.Printf("Searching for image: %s:%s\n", normalizedName, tag) | |||
| 60 | 59 | ||||
| 61 | tagPattern := createTagPattern(tag) | 60 | tagPattern := createTagPattern(tag) | ||
| 62 | 61 | ||||
| 63 | for _, img := range images { | 62 | for _, img := range images { | ||
| 64 | for _, repoTag := range img.RepoTags { | 63 | for _, repoTag := range img.RepoTags { | ||
| 65 | normalizedRepoTag, repoTagValue := normalizeImageName(repoTag) | 64 | normalizedRepoTag, repoTagValue := normalizeImageName(repoTag) | ||
| 66 | - | fmt.Printf("Comparing with: %s:%s\n", normalizedRepoTag, repoTagValue) | |||
| 67 | 65 | ||||
| 68 | // Check for exact match (including with docker.io/ prefix) | 66 | // Check for exact match (including with docker.io/ prefix) | ||
| 69 | if repoTag == imageName || repoTag == "docker.io/"+imageName { | 67 | if repoTag == imageName || repoTag == "docker.io/"+imageName { | ||
| @@ -134,8 +132,6 @@ func ImportImageToEngine(imageFilePath string) (string, error) { | |||||
| 134 | } | 132 | } | ||
| 135 | defer imageFile.Close() | 133 | defer imageFile.Close() | ||
| 136 | 134 | ||||
| 137 | - | log.Printf("Image file opened successfully") | |||
| 138 | - | ||||
| 139 | resp, err := dockerCli.ImageLoad(context.Background(), imageFile, true) | 135 | resp, err := dockerCli.ImageLoad(context.Background(), imageFile, true) | ||
| 140 | if err != nil { | 136 | if err != nil { | ||
| 141 | log.Printf("Failed to load image: %v", err) | 137 | log.Printf("Failed to load image: %v", err) | ||
| @@ -143,13 +139,10 @@ func ImportImageToEngine(imageFilePath string) (string, error) { | |||||
| 143 | } | 139 | } | ||
| 144 | defer resp.Body.Close() | 140 | defer resp.Body.Close() | ||
| 145 | 141 | ||||
| 146 | - | log.Printf("Image load initiated, parsing response") | |||
| 147 | - | ||||
| 148 | var imageName string | 142 | var imageName string | ||
| 149 | scanner := bufio.NewScanner(resp.Body) | 143 | scanner := bufio.NewScanner(resp.Body) | ||
| 150 | for scanner.Scan() { | 144 | for scanner.Scan() { | ||
| 151 | line := scanner.Text() | 145 | line := scanner.Text() | ||
| 152 | - | log.Printf("Response line: %s", line) | |||
| 153 | 146 | ||||
| 154 | var message struct { | 147 | var message struct { | ||
| 155 | Stream string `json:"stream"` | 148 | Stream string `json:"stream"` | ||
| @@ -159,11 +152,8 @@ func ImportImageToEngine(imageFilePath string) (string, error) { | |||||
| 159 | continue // Skip lines that aren't JSON | 152 | continue // Skip lines that aren't JSON | ||
| 160 | } | 153 | } | ||
| 161 | 154 | ||||
| 162 | - | log.Printf("Parsed message stream: %s", message.Stream) | |||
| 163 | - | ||||
| 164 | if strings.HasPrefix(message.Stream, "Loaded image: ") { | 155 | if strings.HasPrefix(message.Stream, "Loaded image: ") { | ||
| 165 | imageName = strings.TrimSpace(strings.TrimPrefix(message.Stream, "Loaded image: ")) | 156 | imageName = strings.TrimSpace(strings.TrimPrefix(message.Stream, "Loaded image: ")) | ||
| 166 | - | log.Printf("Found image name: %s", imageName) | |||
| 167 | break | 157 | break | ||
| 168 | } | 158 | } | ||
| 169 | } | 159 | } | ||