Skip to content

Commit 8328365

Browse files
rgarciacursoragent
andauthored
feat: add arm64/aarch64 multi-arch support (#44)
* feat: add arm64/aarch64 multi-arch support Add support for arm64 architecture: - Map arm64 to aarch64 in GetArch() for kernel/binary paths - Map arm64 to aarch64 in cloud-hypervisor binary extraction - Add platform-specific OCI image pulling with remote.WithPlatform() to correctly pull the right architecture for multi-arch images - Add golang/protobuf dependency for grpc compatibility * Refactor inspectManifest to use remote.Image for platform-specific digests (#46) Co-authored-by: Cursor Agent <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]>
1 parent 8d70731 commit 8328365

File tree

4 files changed

+35
-7
lines changed

4 files changed

+35
-7
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/ghodss/yaml v1.0.0
1212
github.com/go-chi/chi/v5 v5.2.3
1313
github.com/golang-jwt/jwt/v5 v5.3.0
14+
github.com/golang/protobuf v1.5.4
1415
github.com/google/go-containerregistry v0.20.6
1516
github.com/google/wire v0.7.0
1617
github.com/gorilla/websocket v1.5.3
@@ -42,7 +43,6 @@ require (
4243
golang.org/x/sync v0.17.0
4344
golang.org/x/sys v0.38.0
4445
google.golang.org/grpc v1.77.0
45-
google.golang.org/protobuf v1.36.10
4646
gvisor.dev/gvisor v0.0.0-20251125014920-fc40e232ff54
4747
)
4848

@@ -112,6 +112,7 @@ require (
112112
golang.org/x/tools v0.37.0 // indirect
113113
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect
114114
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
115+
google.golang.org/protobuf v1.36.10 // indirect
115116
gopkg.in/yaml.v2 v2.4.0 // indirect
116117
gopkg.in/yaml.v3 v3.0.1 // indirect
117118
gotest.tools/v3 v3.5.2 // indirect

lib/images/oci.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import (
44
"context"
55
"fmt"
66
"os"
7+
"runtime"
78
"strings"
89

910
"github.com/google/go-containerregistry/pkg/authn"
1011
"github.com/google/go-containerregistry/pkg/name"
12+
gcr "github.com/google/go-containerregistry/pkg/v1"
1113
"github.com/google/go-containerregistry/pkg/v1/empty"
1214
"github.com/google/go-containerregistry/pkg/v1/layout"
1315
"github.com/google/go-containerregistry/pkg/v1/remote"
@@ -60,24 +62,42 @@ func newOCIClient(cacheDir string) (*ociClient, error) {
6062
return &ociClient{cacheDir: cacheDir}, nil
6163
}
6264

65+
// currentPlatform returns the platform for the current host
66+
func currentPlatform() gcr.Platform {
67+
return gcr.Platform{
68+
Architecture: runtime.GOARCH,
69+
OS: runtime.GOOS,
70+
}
71+
}
72+
6373
// inspectManifest synchronously inspects a remote image to get its digest
6474
// without pulling the image. This is used for upfront digest discovery.
75+
// For multi-arch images, it returns the platform-specific manifest digest
76+
// (matching the current host platform) rather than the manifest index digest.
6577
func (c *ociClient) inspectManifest(ctx context.Context, imageRef string) (string, error) {
6678
ref, err := name.ParseReference(imageRef)
6779
if err != nil {
6880
return "", fmt.Errorf("parse image reference: %w", err)
6981
}
7082

71-
// Use system authentication (reads from ~/.docker/config.json, etc.)
72-
// Default retry: only on network errors, max ~1.3s total
73-
descriptor, err := remote.Head(ref,
83+
// Use remote.Image with platform filtering to get the platform-specific digest.
84+
// For multi-arch images, this resolves the manifest index to the correct platform.
85+
// This matches what pullToOCILayout does to ensure cache key consistency.
86+
// Note: remote.Image is lazy - it only fetches the manifest, not layer blobs.
87+
img, err := remote.Image(ref,
7488
remote.WithContext(ctx),
75-
remote.WithAuthFromKeychain(authn.DefaultKeychain))
89+
remote.WithAuthFromKeychain(authn.DefaultKeychain),
90+
remote.WithPlatform(currentPlatform()))
7691
if err != nil {
7792
return "", fmt.Errorf("fetch manifest: %w", wrapRegistryError(err))
7893
}
7994

80-
return descriptor.Digest.String(), nil
95+
digest, err := img.Digest()
96+
if err != nil {
97+
return "", fmt.Errorf("get image digest: %w", err)
98+
}
99+
100+
return digest.String(), nil
81101
}
82102

83103
// pullResult contains the metadata and digest from pulling an image
@@ -126,9 +146,11 @@ func (c *ociClient) pullToOCILayout(ctx context.Context, imageRef, layoutTag str
126146

127147
// Use system authentication (reads from ~/.docker/config.json, etc.)
128148
// Default retry: only on network errors, max ~1.3s total
149+
// WithPlatform ensures we pull the correct architecture for multi-arch images
129150
img, err := remote.Image(ref,
130151
remote.WithContext(ctx),
131-
remote.WithAuthFromKeychain(authn.DefaultKeychain))
152+
remote.WithAuthFromKeychain(authn.DefaultKeychain),
153+
remote.WithPlatform(currentPlatform()))
132154
if err != nil {
133155
// Rate limits fail here immediately (429 is not retried by default)
134156
return fmt.Errorf("fetch image manifest: %w", wrapRegistryError(err))

lib/system/versions.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,8 @@ func GetArch() string {
7474
if arch == "amd64" {
7575
return "x86_64"
7676
}
77+
if arch == "arm64" {
78+
return "aarch64"
79+
}
7780
return arch
7881
}

lib/vmm/binaries.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ func ExtractBinary(p *paths.Paths, version CHVersion) (string, error) {
3030
arch := runtime.GOARCH
3131
if arch == "amd64" {
3232
arch = "x86_64"
33+
} else if arch == "arm64" {
34+
arch = "aarch64"
3335
}
3436

3537
embeddedPath := fmt.Sprintf("binaries/cloud-hypervisor/%s/%s/cloud-hypervisor", version, arch)

0 commit comments

Comments
 (0)