Skip to content

Commit dbe2e91

Browse files
committed
feat: add arm64 support for devspacehelper
1 parent 67dddbd commit dbe2e91

8 files changed

Lines changed: 73 additions & 24 deletions

File tree

cmd/restart.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ func restartContainer(client kubectl.Client, options targetselector.Options, log
157157
return errors.Errorf("Error selecting pod: %v", err)
158158
}
159159

160-
err = services.InjectDevSpaceHelper(client, container.Pod, container.Container.Name, log)
160+
err = services.InjectDevSpaceHelper(client, container.Pod, container.Container.Name, "", log)
161161
if err != nil {
162162
return errors.Wrap(err, "inject devspace helper")
163163
}

hack/build-all.bash

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ echo "Building devspace helper"
6060
GOARCH=amd64 GOOS=linux go build -ldflags "-s -w -X github.com/loft-sh/devspace/helper/cmd.version=${VERSION}" -o "${DEVSPACE_ROOT}/release/devspacehelper" helper/main.go
6161
shasum -a 256 "${DEVSPACE_ROOT}/release/devspacehelper" > "${DEVSPACE_ROOT}/release/devspacehelper".sha256
6262

63+
GOARCH=arm64 GOOS=linux go build -ldflags "-s -w -X github.com/loft-sh/devspace/helper/cmd.version=${VERSION}" -o "${DEVSPACE_ROOT}/release/devspacehelper-arm64" helper/main.go
64+
shasum -a 256 "${DEVSPACE_ROOT}/release/devspacehelper-arm64" > "${DEVSPACE_ROOT}/release/devspacehelper-arm64".sha256
65+
6366
# build bin data
6467
$GOPATH/bin/go-bindata -o assets/assets.go -pkg assets release/devspacehelper release/ui.tar.gz component-chart-$COMPONENT_CHART_VERSION.tgz
6568

@@ -77,7 +80,7 @@ for OS in ${DEVSPACE_BUILD_PLATFORMS[@]}; do
7780
fi
7881

7982
# arm64 build is only supported for darwin
80-
if [[ "${ARCH}" == "arm64" && "${OS}" != "darwin" ]]; then
83+
if [[ "${ARCH}" == "arm64" && "${OS}" == "windows" ]]; then
8184
echo "Building for ${OS}/${ARCH} not supported."
8285
continue
8386
fi

pkg/devspace/config/loader/validate.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ func ValidInitialSyncStrategy(strategy latest.InitialSyncStrategy) bool {
2323
strategy == latest.InitialSyncStrategyPreferNewest
2424
}
2525

26+
// ValidContainerArch checks if the target container arch is valid
27+
func ValidContainerArch(arch latest.ContainerArchitecture) bool {
28+
return arch == "" ||
29+
arch == latest.ContainerArchitectureAmd64 ||
30+
arch == latest.ContainerArchitectureArm64
31+
}
32+
2633
func validate(config *latest.Config, log log.Logger) error {
2734
err := validateImages(config)
2835
if err != nil {
@@ -232,6 +239,9 @@ func validateDev(config *latest.Config) error {
232239
if len(port.PortMappings) == 0 && len(port.PortMappingsReverse) == 0 {
233240
return errors.Errorf("Error in config: portMappings is empty in port config at index %d", index)
234241
}
242+
if ValidContainerArch(port.Arch) == false {
243+
return errors.Errorf("Error in config: ports.arch is not valid '%s' at index %d", port.Arch, index)
244+
}
235245
}
236246
}
237247

@@ -248,6 +258,9 @@ func validateDev(config *latest.Config) error {
248258
if ValidInitialSyncStrategy(sync.InitialSync) == false {
249259
return errors.Errorf("Error in config: sync.initialSync is not valid '%s' at index %d", sync.InitialSync, index)
250260
}
261+
if ValidContainerArch(sync.Arch) == false {
262+
return errors.Errorf("Error in config: sync.arch is not valid '%s' at index %d", sync.Arch, index)
263+
}
251264
}
252265
}
253266

pkg/devspace/config/versions/latest/schema.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -644,12 +644,16 @@ type DevConfig struct {
644644

645645
// PortForwardingConfig defines the ports for a port forwarding to a DevSpace
646646
type PortForwardingConfig struct {
647-
ImageName string `yaml:"imageName,omitempty" json:"imageName,omitempty"`
648-
LabelSelector map[string]string `yaml:"labelSelector,omitempty" json:"labelSelector,omitempty"`
649-
ContainerName string `yaml:"containerName,omitempty" json:"containerName,omitempty"`
650-
Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"`
651-
PortMappings []*PortMapping `yaml:"forward,omitempty" json:"forward,omitempty"`
652-
PortMappingsReverse []*PortMapping `yaml:"reverseForward,omitempty" json:"reverseForward,omitempty"`
647+
ImageName string `yaml:"imageName,omitempty" json:"imageName,omitempty"`
648+
LabelSelector map[string]string `yaml:"labelSelector,omitempty" json:"labelSelector,omitempty"`
649+
ContainerName string `yaml:"containerName,omitempty" json:"containerName,omitempty"`
650+
Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"`
651+
652+
// Target Container architecture to use for the devspacehelper (currently amd64 or arm64). Defaults to amd64
653+
Arch ContainerArchitecture `yaml:"arch,omitempty" json:"arch,omitempty"`
654+
655+
PortMappings []*PortMapping `yaml:"forward,omitempty" json:"forward,omitempty"`
656+
PortMappingsReverse []*PortMapping `yaml:"reverseForward,omitempty" json:"reverseForward,omitempty"`
653657
}
654658

655659
// PortMapping defines the ports for a PortMapping
@@ -684,13 +688,23 @@ type SyncConfig struct {
684688
WaitInitialSync *bool `yaml:"waitInitialSync,omitempty" json:"waitInitialSync,omitempty"`
685689
BandwidthLimits *BandwidthLimits `yaml:"bandwidthLimits,omitempty" json:"bandwidthLimits,omitempty"`
686690

691+
// Target Container architecture to use for the devspacehelper (currently amd64 or arm64). Defaults to amd64
692+
Arch ContainerArchitecture `yaml:"arch,omitempty" json:"arch,omitempty"`
693+
687694
// If greater zero, describes the amount of milliseconds to wait after each checked 100 files
688695
ThrottleChangeDetection *int64 `yaml:"throttleChangeDetection,omitempty" json:"throttleChangeDetection,omitempty"`
689696

690697
OnUpload *SyncOnUpload `yaml:"onUpload,omitempty" json:"onUpload,omitempty"`
691698
OnDownload *SyncOnDownload `yaml:"onDownload,omitempty" json:"onDownload,omitempty"`
692699
}
693700

701+
type ContainerArchitecture string
702+
703+
const (
704+
ContainerArchitectureAmd64 ContainerArchitecture = "amd64"
705+
ContainerArchitectureArm64 ContainerArchitecture = "arm64"
706+
)
707+
694708
// SyncOnUpload defines the struct for the command that should be executed when files / folders are uploaded
695709
type SyncOnUpload struct {
696710
// If true restart container will try to restart the container after a change has been made. Make sure that

pkg/devspace/helm/generic/generic.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type Client interface {
4040

4141
func NewGenericClient(versionedClient VersionedClient, log log.Logger) Client {
4242
c := &client{
43+
log: log,
4344
exec: command.NewStreamCommand,
4445
versionedClient: versionedClient,
4546
extract: extract.NewExtractor(),
@@ -51,6 +52,7 @@ func NewGenericClient(versionedClient VersionedClient, log log.Logger) Client {
5152

5253
type client struct {
5354
exec command.Exec
55+
log log.Logger
5456
versionedClient VersionedClient
5557
extract extract.Extract
5658
downloader downloader.Downloader
@@ -86,6 +88,8 @@ func (c *client) Exec(args []string, helmConfig *latest.HelmConfig) ([]byte, err
8688
if c.versionedClient.IsInCluster() == false {
8789
args = append(args, "--kube-context", c.versionedClient.KubeContext())
8890
}
91+
92+
c.log.Infof("Execute '%s %s'", c.helmPath, strings.Join(args, " "))
8993
result, err := c.exec(c.helmPath, args).Output()
9094
if err != nil {
9195
if exitError, ok := err.(*exec.ExitError); ok {

pkg/devspace/helm/v3/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
)
1818

1919
var (
20-
helmVersion = "v3.4.2"
20+
helmVersion = "v3.5.3"
2121
helmDownload = "https://get.helm.sh/helm-" + helmVersion + "-" + runtime.GOOS + "-amd64"
2222
)
2323

pkg/devspace/services/reverse_port_forwarding.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func (serviceClient *client) startReversePortForwarding(cache *generated.CacheCo
5757

5858
// make sure the devspace helper binary is injected
5959
log.StartWait("Reverse-Port-Forwarding: Upload devspace helper...")
60-
err = InjectDevSpaceHelper(serviceClient.client, container.Pod, container.Container.Name, serviceClient.log)
60+
err = InjectDevSpaceHelper(serviceClient.client, container.Pod, container.Container.Name, string(portForwarding.Arch), serviceClient.log)
6161
log.StopWait()
6262
if err != nil {
6363
return err

pkg/devspace/services/sync.go

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ const DevSpaceHelperBaseURL = "https://github.com/loft-sh/devspace/releases"
3838
// DevSpaceHelperTempFolder is the local folder where we store the sync helper
3939
const DevSpaceHelperTempFolder = "devspacehelper"
4040

41-
// HelperBinaryRegEx is the regexp that finds the correct download link for the sync helper binary
42-
var HelperBinaryRegEx = regexp.MustCompile(`href="(\/loft-sh\/devspace\/releases\/download\/[^\/]*\/devspacehelper)"`)
41+
// helperBinaryRegEx is the regexp that finds the correct download link for the sync helper binary
42+
var helperBinaryRegEx = `href="(\/loft-sh\/devspace\/releases\/download\/[^\/]*\/%s)"`
4343

4444
// DevSpaceHelperContainerPath is the path of the devspace helper in the container
4545
const DevSpaceHelperContainerPath = "/tmp/devspacehelper"
@@ -247,7 +247,7 @@ func (serviceClient *client) isFatalSyncError(err error) bool {
247247
}
248248

249249
func (serviceClient *client) startSync(pod *v1.Pod, container string, syncConfig *latest.SyncConfig, verbose bool, syncDone chan bool, customLog logpkg.Logger) (*sync.Sync, error) {
250-
err := InjectDevSpaceHelper(serviceClient.client, pod, container, serviceClient.log)
250+
err := InjectDevSpaceHelper(serviceClient.client, pod, container, string(syncConfig.Arch), serviceClient.log)
251251
if err != nil {
252252
return nil, err
253253
}
@@ -441,18 +441,26 @@ func (serviceClient *client) startStream(pod *v1.Pod, container string, command
441441
return nil
442442
}
443443

444-
func InjectDevSpaceHelper(client kubectl.Client, pod *v1.Pod, container string, log logpkg.Logger) error {
444+
func InjectDevSpaceHelper(client kubectl.Client, pod *v1.Pod, container string, arch string, log logpkg.Logger) error {
445445
// Compare sync versions
446446
version := upgrade.GetRawVersion()
447447
if version == "" {
448448
version = "latest"
449449
}
450+
if arch != "" {
451+
if latest.ContainerArchitecture(arch) == latest.ContainerArchitectureAmd64 {
452+
arch = ""
453+
} else {
454+
arch = "-" + arch
455+
}
456+
}
450457

451458
// Check if sync is already in pod
459+
localHelperName := "devspacehelper" + arch
452460
stdout, _, err := client.ExecBuffered(pod, container, []string{DevSpaceHelperContainerPath, "version"}, nil)
453461
if err != nil || version != string(stdout) {
454462
// check if we can find it in the assets
455-
helperBytes, err := assets.Asset("release/" + DevSpaceHelperTempFolder)
463+
helperBytes, err := assets.Asset("release/" + localHelperName)
456464
if err == nil {
457465
return injectSyncHelperFromBytes(client, pod, container, helperBytes)
458466
}
@@ -463,15 +471,15 @@ func InjectDevSpaceHelper(client kubectl.Client, pod *v1.Pod, container string,
463471
}
464472

465473
syncBinaryFolder := filepath.Join(homedir, constants.DefaultHomeDevSpaceFolder, DevSpaceHelperTempFolder, version)
466-
filepath := filepath.Join(syncBinaryFolder, "devspacehelper")
467474

468475
// Download sync helper if necessary
469-
err = downloadSyncHelper(filepath, syncBinaryFolder, version, log)
476+
err = downloadSyncHelper(localHelperName, syncBinaryFolder, version, log)
470477
if err != nil {
471478
return errors.Wrap(err, "download devspace helper")
472479
}
473480

474481
// Inject sync helper
482+
filepath := filepath.Join(syncBinaryFolder, localHelperName)
475483
err = injectSyncHelper(client, pod, container, filepath)
476484
if err != nil {
477485
return errors.Wrap(err, "inject devspace helper")
@@ -481,7 +489,9 @@ func InjectDevSpaceHelper(client kubectl.Client, pod *v1.Pod, container string,
481489
return nil
482490
}
483491

484-
func downloadSyncHelper(filepath, syncBinaryFolder, version string, log logpkg.Logger) error {
492+
func downloadSyncHelper(helperName, syncBinaryFolder, version string, log logpkg.Logger) error {
493+
filepath := filepath.Join(syncBinaryFolder, helperName)
494+
485495
// Check if file exists
486496
_, err := os.Stat(filepath)
487497
if err == nil {
@@ -491,7 +501,7 @@ func downloadSyncHelper(filepath, syncBinaryFolder, version string, log logpkg.L
491501
}
492502

493503
// download sha256 html
494-
url := fmt.Sprintf("https://github.com/loft-sh/devspace/releases/download/%s/devspacehelper.sha256", version)
504+
url := fmt.Sprintf("https://github.com/loft-sh/devspace/releases/download/%s/%s.sha256", version, helperName)
495505
resp, err := http.Get(url)
496506
if err != nil {
497507
log.Warnf("Couldn't retrieve helper sha256: %v", err)
@@ -524,16 +534,16 @@ func downloadSyncHelper(filepath, syncBinaryFolder, version string, log logpkg.L
524534
}
525535

526536
// Make sync binary
527-
log.Info("Couldn't find devspacehelper, will try to download it now")
537+
log.Infof("Couldn't find %s, will try to download it now", helperName)
528538
err = os.MkdirAll(syncBinaryFolder, 0755)
529539
if err != nil {
530540
return errors.Wrap(err, "mkdir helper binary folder")
531541
}
532542

533-
return downloadFile(version, filepath)
543+
return downloadFile(version, filepath, helperName)
534544
}
535545

536-
func downloadFile(version string, filepath string) error {
546+
func downloadFile(version string, filepath string, filename string) error {
537547
// Create download url
538548
url := ""
539549
if version == "latest" {
@@ -553,9 +563,14 @@ func downloadFile(version string, filepath string) error {
553563
return errors.Wrap(err, "read body")
554564
}
555565

556-
matches := HelperBinaryRegEx.FindStringSubmatch(string(body))
566+
regEx, err := regexp.Compile(fmt.Sprintf(helperBinaryRegEx, filename))
567+
if err != nil {
568+
return err
569+
}
570+
571+
matches := regEx.FindStringSubmatch(string(body))
557572
if len(matches) != 2 {
558-
return errors.Errorf("Couldn't find devspace helper in github release %s at url %s", version, url)
573+
return errors.Errorf("couldn't find %s in github release %s at url %s", filename, version, url)
559574
}
560575

561576
out, err := os.Create(filepath)

0 commit comments

Comments
 (0)