detached aead545

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 {
7272 return deployCmd
7373 }
7474
75-// Client side - deploy.go
76-
7775 func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain string) error {
78- // Create a buffer to store the entire image data
7976 var buf bytes.Buffer
8077 size, err := io.Copy(&buf, reader)
8178 if err != nil {
8279 return fmt.Errorf("failed to read image data: %w", err)
8380 }
8481
85- // Create a new reader from the buffer
8682 imageReader := bytes.NewReader(buf.Bytes())
87-
8883 sizeInMB := float64(size) / 1024 / 1024
8984 log.Info("Attempting to deploy...",
9085 "image", imageName,
@@ -100,51 +95,54 @@ func deployImage(a *cli.App, reader io.Reader, imageName, port, targetDomain str
10095 "Content-Type": {"application/octet-stream"},
10196 }
10297
98+ // Send a small chunk first to detect any conflicts
10399 chunkedClient := handler.NewChunkedClient(a)
104- var finalResponse *common.DeployResponse
100+ ctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute)
101+ defer cancel()
105102
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)
109106
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")
122117 }
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)
126125 }
127126 }
128127 }
128+ } else {
129129 return fmt.Errorf("deployment failed: %w", err)
130130 }
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)
139137 }
140- break // Successful deployment
141138 }
142139
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)
146143 }
147- return fmt.Errorf("no response received from deployment")
144+
145+ return waitForDeployment(deployResp.Domain, deployResp.ContainerID)
148146 }
149147
150148 func waitForDeployment(domain string, containerID string) error {
@@ -72,19 +72,14 @@ func NewDeployCommand(a *cli.App) *cobra.Command {
72 return deployCmd72 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.Buffer76 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 / 102483 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.DeployResponse100+ 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.DeploymentError109+ 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.DeployResponse111+ 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 retry118+ return fmt.Errorf("failed to handle existing container: %w", err)
124- imageReader.Seek(0, 0)119+ }
125- continue // Retry deployment120+ // 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 response132+ // 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.DeployResponse134+ 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 waiting140+ 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 {
7777 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
7878 defer cancel()
7979
80- resp, err := chunkedClient.SendFile(ctx, "/push", headers, reader, actualSize, imageName)
80+ _, err = chunkedClient.SendFile(ctx, "/push", headers, reader, actualSize, imageName)
8181 if err != nil {
8282 var pushErr *common.DeploymentError
8383 if errors.As(err, &pushErr) {
@@ -86,9 +86,5 @@ func pushImage(a *cli.App, imageName string) error {
8686 return fmt.Errorf("chunked transfer failed: %w", err)
8787 }
8888
89- fmt.Println("Push response:", resp)
90-
91- log.Info("Image pushed and imported successfully", "image", imageName)
92-
9389 return nil
9490 }
@@ -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.DeploymentError82 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 nil89 return nil
94 }90 }
M internal/cli/handler/chunked_transfer.go +24 -23
@@ -132,12 +132,6 @@ func handleChunkResponse(resp *Response, responseType string) error {
132132 if err := json.Unmarshal(resp.Body, &deployResp); err != nil {
133133 return fmt.Errorf("failed to unmarshal deploy response: %w", err)
134134 }
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)
141135 return nil
142136
143137 case "push":
@@ -145,11 +139,7 @@ func handleChunkResponse(resp *Response, responseType string) error {
145139 if err := json.Unmarshal(resp.Body, &pushResp); err != nil {
146140 return fmt.Errorf("failed to unmarshal push response: %w", err)
147141 }
148- log.Info("Push chunk response",
149- "success", pushResp.Success,
150- "message", pushResp.Message,
151- "createContainerURL", pushResp.CreateContainerURL,
152- "imageID", pushResp.ImageID)
142+
153143 return nil
154144
155145 default:
@@ -164,9 +154,11 @@ func handleFinalResponse(resp *Response, responseType string) error {
164154 if err := json.Unmarshal(resp.Body, &finalResp); err != nil {
165155 return fmt.Errorf("failed to unmarshal final deploy response: %w", err)
166156 }
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,
170162 "domain", finalResp.Domain,
171163 "containerID", finalResp.ContainerID,
172164 "containerName", finalResp.ContainerName)
@@ -177,9 +169,8 @@ func handleFinalResponse(resp *Response, responseType string) error {
177169 if err := json.Unmarshal(resp.Body, &finalResp); err != nil {
178170 return fmt.Errorf("failed to unmarshal final push response: %w", err)
179171 }
180- log.Info("Final push response",
181- "success", finalResp.Success,
182- "message", finalResp.Message,
172+ log.Info(
173+ finalResp.Message,
183174 "createContainerURL", finalResp.CreateContainerURL,
184175 "imageID", finalResp.ImageID)
185176 return nil
@@ -190,18 +181,28 @@ func handleFinalResponse(resp *Response, responseType string) error {
190181 }
191182
192183 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+
193190 var lastErr error
194- for attempt := 0; attempt < 3; attempt++ {
191+ for attempt := 0; attempt < maxRetries; attempt++ {
195192 resp, err := c.sendChunk(ctx, endpoint, headers, chunk, metadata)
196193 if err == nil {
197194 return resp, nil
198195 }
199196 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+ }
205206 }
206207 return nil, fmt.Errorf("max retries exceeded: %w", lastErr)
207208 }
@@ -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 nil135 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 nil143 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 nil176 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 error190 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, nil194 return resp, nil
198 }195 }
199 lastErr = err196 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) {
5656 }
5757
5858 normalizedName, tag := normalizeImageName(imageName)
59- fmt.Printf("Searching for image: %s:%s\n", normalizedName, tag)
6059
6160 tagPattern := createTagPattern(tag)
6261
6362 for _, img := range images {
6463 for _, repoTag := range img.RepoTags {
6564 normalizedRepoTag, repoTagValue := normalizeImageName(repoTag)
66- fmt.Printf("Comparing with: %s:%s\n", normalizedRepoTag, repoTagValue)
6765
6866 // Check for exact match (including with docker.io/ prefix)
6967 if repoTag == imageName || repoTag == "docker.io/"+imageName {
@@ -134,8 +132,6 @@ func ImportImageToEngine(imageFilePath string) (string, error) {
134132 }
135133 defer imageFile.Close()
136134
137- log.Printf("Image file opened successfully")
138-
139135 resp, err := dockerCli.ImageLoad(context.Background(), imageFile, true)
140136 if err != nil {
141137 log.Printf("Failed to load image: %v", err)
@@ -143,13 +139,10 @@ func ImportImageToEngine(imageFilePath string) (string, error) {
143139 }
144140 defer resp.Body.Close()
145141
146- log.Printf("Image load initiated, parsing response")
147-
148142 var imageName string
149143 scanner := bufio.NewScanner(resp.Body)
150144 for scanner.Scan() {
151145 line := scanner.Text()
152- log.Printf("Response line: %s", line)
153146
154147 var message struct {
155148 Stream string `json:"stream"`
@@ -159,11 +152,8 @@ func ImportImageToEngine(imageFilePath string) (string, error) {
159152 continue // Skip lines that aren't JSON
160153 }
161154
162- log.Printf("Parsed message stream: %s", message.Stream)
163-
164155 if strings.HasPrefix(message.Stream, "Loaded image: ") {
165156 imageName = strings.TrimSpace(strings.TrimPrefix(message.Stream, "Loaded image: "))
166- log.Printf("Found image name: %s", imageName)
167157 break
168158 }
169159 }
@@ -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 string142 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 JSON152 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 break157 break
168 }158 }
169 }159 }

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 8

Tags 106

↑↓ moveenter openesc close