Skip to content

Commit 12bc1f0

Browse files
committed
Fix platform image wait ref and binfmt EEXIST handling
1 parent 8efb016 commit 12bc1f0

4 files changed

Lines changed: 81 additions & 3 deletions

File tree

lib/instances/create.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -608,11 +608,18 @@ func resolveImageForCreate(ctx context.Context, imageManager createImageResolver
608608
if err != nil {
609609
return nil, fmt.Errorf("resolve image %s for platform %s: %w", imageName, platform, err)
610610
}
611+
resolvedName := img.Name
612+
if img.Digest != "" {
613+
resolvedName, err = imageDigestRef(img.Name, img.Digest)
614+
if err != nil {
615+
return nil, fmt.Errorf("build resolved image ref: %w", err)
616+
}
617+
}
611618
if img.Status != images.StatusReady {
612-
if err := waitForImagePull(ctx, imageManager, img.Name, log); err != nil {
619+
if err := waitForImagePull(ctx, imageManager, resolvedName, log); err != nil {
613620
return nil, err
614621
}
615-
img, err = imageManager.GetImage(ctx, img.Name)
622+
img, err = imageManager.GetImage(ctx, resolvedName)
616623
if err != nil {
617624
return nil, fmt.Errorf("get image after platform resolve: %w", err)
618625
}
@@ -648,6 +655,14 @@ func resolveImageForCreate(ctx context.Context, imageManager createImageResolver
648655
return img, nil
649656
}
650657

658+
func imageDigestRef(imageName, digest string) (string, error) {
659+
ref, err := images.ParseNormalizedRef(imageName)
660+
if err != nil {
661+
return "", fmt.Errorf("parse image ref %q: %w", imageName, err)
662+
}
663+
return ref.Repository() + "@" + digest, nil
664+
}
665+
651666
func waitForImagePull(ctx context.Context, imageManager createImageResolver, imageName string, log *slog.Logger) error {
652667
pullCtx, pullCancel := context.WithTimeout(ctx, 5*time.Second)
653668
defer pullCancel()

lib/instances/create_image_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,67 @@ func TestResolveImageForCreateRejectsResolvedPlatformMismatch(t *testing.T) {
9595
}
9696
}
9797

98+
func TestResolveImageForCreateWithPlatformWaitsOnResolvedDigest(t *testing.T) {
99+
t.Parallel()
100+
101+
const digest = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
102+
const tagRef = "docker.io/library/alpine:3.19"
103+
const digestRef = "docker.io/library/alpine@" + digest
104+
105+
waitName := ""
106+
getNames := make([]string, 0, 2)
107+
resolver := createImageResolverFake{
108+
getImage: func(_ context.Context, name string) (*images.Image, error) {
109+
getNames = append(getNames, name)
110+
switch name {
111+
case digestRef:
112+
return &images.Image{
113+
Name: tagRef,
114+
Digest: digest,
115+
Platform: "linux/amd64",
116+
Status: images.StatusReady,
117+
}, nil
118+
case tagRef:
119+
return &images.Image{
120+
Name: tagRef,
121+
Digest: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
122+
Platform: "linux/arm64",
123+
Status: images.StatusReady,
124+
}, nil
125+
default:
126+
t.Fatalf("unexpected GetImage name %q", name)
127+
return nil, nil
128+
}
129+
},
130+
createImage: func(_ context.Context, req images.CreateImageRequest) (*images.Image, error) {
131+
return &images.Image{
132+
Name: req.Name,
133+
Digest: digest,
134+
Platform: "linux/amd64",
135+
Status: images.StatusPending,
136+
}, nil
137+
},
138+
waitReady: func(_ context.Context, name string) error {
139+
waitName = name
140+
return nil
141+
},
142+
}
143+
144+
img, err := resolveImageForCreate(context.Background(), resolver, tagRef, "linux/amd64", slog.Default())
145+
if err != nil {
146+
t.Fatalf("resolve image: %v", err)
147+
}
148+
if waitName != digestRef {
149+
t.Fatalf("expected WaitForReady on %q, got %q", digestRef, waitName)
150+
}
151+
if len(getNames) != 1 || getNames[0] != digestRef {
152+
t.Fatalf("expected GetImage on %q, got %#v", digestRef, getNames)
153+
}
154+
if img.Platform != "linux/amd64" {
155+
t.Fatalf("expected amd64 image, got %s", img.Platform)
156+
}
157+
}
158+
98159
func TestResolveImageForCreateWithoutPlatformUsesExistingImage(t *testing.T) {
99160
t.Parallel()
100161

lib/system/init/rosetta.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func registerBinfmt(rule string) error {
107107
// alreadyRegistered reports whether a binfmt_misc register write failed only
108108
// because a handler of that name already exists (EEXIST).
109109
func alreadyRegistered(err error) bool {
110-
return errors.Is(err, fs.ErrExist)
110+
return errors.Is(err, fs.ErrExist) || errors.Is(err, syscall.EEXIST)
111111
}
112112

113113
// writeBinfmtdConf drops a systemd binfmt.d config that re-registers Rosetta

lib/system/init/rosetta_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"errors"
55
"io/fs"
6+
"os"
67
"strings"
78
"syscall"
89
"testing"
@@ -37,6 +38,7 @@ func TestRosettaAlreadyRegistered(t *testing.T) {
3738
// EEXIST means a :rosetta: handler is already registered; treat it as success.
3839
assert.True(t, alreadyRegistered(syscall.EEXIST))
3940
assert.True(t, alreadyRegistered(fs.ErrExist))
41+
assert.True(t, alreadyRegistered(&os.PathError{Op: "write", Path: binfmtRegister, Err: syscall.EEXIST}))
4042
// Any other error (or none) is not an "already registered" condition.
4143
assert.False(t, alreadyRegistered(nil))
4244
assert.False(t, alreadyRegistered(syscall.EACCES))

0 commit comments

Comments
 (0)