Skip to content

Commit 048ee73

Browse files
committed
fix(run): wait for image to be ready before creating instance
The CLI was immediately attempting to create an instance after calling POST /images, which returns 202 Accepted with status 'pending'. Since image builds are asynchronous, the instance creation would fail with 'image_not_ready' error. This fix adds polling logic to wait for the image status to become 'ready' (or 'failed') before proceeding to create the instance. Status updates are shown to the user as the image progresses through: pending -> pulling -> converting -> ready Fixes race condition where first 'hypeman run' would fail but second would succeed after background build completed.
1 parent 9d7cf81 commit 048ee73

File tree

1 file changed

+74
-4
lines changed

1 file changed

+74
-4
lines changed

pkg/cmd/run.go

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"strings"
8+
"time"
89

910
"github.com/onkernel/hypeman-go"
1011
"github.com/onkernel/hypeman-go/option"
@@ -65,25 +66,29 @@ func handleRun(ctx context.Context, cmd *cli.Command) error {
6566

6667
client := hypeman.NewClient(getDefaultRequestOptions(cmd)...)
6768

68-
// Check if image exists, pull if not
69-
_, err := client.Images.Get(ctx, image)
69+
// Check if image exists and is ready
70+
imgInfo, err := client.Images.Get(ctx, image)
7071
if err != nil {
7172
// Image not found, try to pull it
7273
var apiErr *hypeman.Error
7374
if ok := isNotFoundError(err, &apiErr); ok {
7475
fmt.Fprintf(os.Stderr, "Image not found locally. Pulling %s...\n", image)
75-
_, err = client.Images.New(ctx, hypeman.ImageNewParams{
76+
imgInfo, err = client.Images.New(ctx, hypeman.ImageNewParams{
7677
Name: image,
7778
})
7879
if err != nil {
7980
return fmt.Errorf("failed to pull image: %w", err)
8081
}
81-
fmt.Fprintf(os.Stderr, "Pull complete.\n")
8282
} else {
8383
return fmt.Errorf("failed to check image: %w", err)
8484
}
8585
}
8686

87+
// Wait for image to be ready (build is asynchronous)
88+
if err := waitForImageReady(ctx, &client, image, imgInfo); err != nil {
89+
return err
90+
}
91+
8792
// Generate name if not provided
8893
name := cmd.String("name")
8994
if name == "" {
@@ -139,3 +144,68 @@ func isNotFoundError(err error, target **hypeman.Error) bool {
139144
return false
140145
}
141146

147+
// waitForImageReady polls image status until it becomes ready or failed
148+
func waitForImageReady(ctx context.Context, client *hypeman.Client, imageName string, img *hypeman.Image) error {
149+
if img.Status == hypeman.ImageStatusReady {
150+
return nil
151+
}
152+
if img.Status == hypeman.ImageStatusFailed {
153+
if img.Error != "" {
154+
return fmt.Errorf("image build failed: %s", img.Error)
155+
}
156+
return fmt.Errorf("image build failed")
157+
}
158+
159+
// Poll until ready
160+
ticker := time.NewTicker(1 * time.Second)
161+
defer ticker.Stop()
162+
163+
// Show initial status
164+
showImageStatus(img)
165+
166+
for {
167+
select {
168+
case <-ctx.Done():
169+
return ctx.Err()
170+
case <-ticker.C:
171+
updated, err := client.Images.Get(ctx, imageName)
172+
if err != nil {
173+
return fmt.Errorf("failed to check image status: %w", err)
174+
}
175+
176+
// Show status update if changed
177+
if updated.Status != img.Status {
178+
showImageStatus(updated)
179+
img = updated
180+
}
181+
182+
switch updated.Status {
183+
case hypeman.ImageStatusReady:
184+
return nil
185+
case hypeman.ImageStatusFailed:
186+
if updated.Error != "" {
187+
return fmt.Errorf("image build failed: %s", updated.Error)
188+
}
189+
return fmt.Errorf("image build failed")
190+
}
191+
}
192+
}
193+
}
194+
195+
// showImageStatus prints image build status to stderr
196+
func showImageStatus(img *hypeman.Image) {
197+
switch img.Status {
198+
case hypeman.ImageStatusPending:
199+
if img.QueuePosition > 0 {
200+
fmt.Fprintf(os.Stderr, "Queued (position %d)...\n", img.QueuePosition)
201+
} else {
202+
fmt.Fprintf(os.Stderr, "Queued...\n")
203+
}
204+
case hypeman.ImageStatusPulling:
205+
fmt.Fprintf(os.Stderr, "Pulling image...\n")
206+
case hypeman.ImageStatusConverting:
207+
fmt.Fprintf(os.Stderr, "Converting to disk image...\n")
208+
case hypeman.ImageStatusReady:
209+
fmt.Fprintf(os.Stderr, "Image ready.\n")
210+
}
211+
}

0 commit comments

Comments
 (0)