Skip to content

Commit 1e47f88

Browse files
committed
fix: manually handle lock
Signed-off-by: Samuel K <skevetter@pm.me>
1 parent 3f6bd78 commit 1e47f88

File tree

11 files changed

+365
-161
lines changed

11 files changed

+365
-161
lines changed

.github/workflows/pr-ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,15 +366,15 @@ jobs:
366366
KUBECONFIG="${KUBECONFIG:-$HOME/.kube/config}" \
367367
PATH="${PATH}" \
368368
GOROOT="${GOROOT}" \
369-
go test -v -ginkgo.v -timeout 3600s --ginkgo.label-filter="${{ matrix.label }}"
369+
go test -v -ginkgo.v -timeout 900s --ginkgo.label-filter="${{ matrix.label }}"
370370
else
371371
GH_USERNAME="${GH_USERNAME}" \
372372
GH_ACCESS_TOKEN="${GH_ACCESS_TOKEN}" \
373373
KUBECONFIG="${KUBECONFIG:-$HOME/.kube/config}" \
374374
PATH="${PATH}" \
375375
GOROOT="${GOROOT}" \
376376
DOCKER_HOST="npipe:////./pipe/podman-machine-default" \
377-
go test -v -ginkgo.v -timeout 3600s --ginkgo.label-filter="${{ matrix.label }}"
377+
go test -v -ginkgo.v -timeout 900s --ginkgo.label-filter="${{ matrix.label }}"
378378
fi
379379
380380
- name: verify docker is installed

cmd/agent/workspace/build.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"os"
77

8-
"github.com/sirupsen/logrus"
98
"github.com/skevetter/devpod/cmd/flags"
109
"github.com/skevetter/devpod/pkg/agent"
1110
provider2 "github.com/skevetter/devpod/pkg/provider"
@@ -98,15 +97,9 @@ func (cmd *BuildCmd) Run(ctx context.Context) error {
9897
}
9998

10099
if workspaceInfo.CLIOptions.SkipPush {
101-
logger.WithFields(logrus.Fields{
102-
"imageName": imageName,
103-
})
104-
logger.Donef("done building image")
100+
logger.Donef("done building image %s", imageName)
105101
} else {
106-
logger.WithFields(logrus.Fields{
107-
"imageName": imageName,
108-
})
109-
logger.Donef("done building and pushing image")
102+
logger.Donef("done building and pushing image %s", imageName)
110103
}
111104
}
112105

cmd/agent/workspace/up.go

Lines changed: 75 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ func contentFolderExists(path string, log log.Logger) (bool, error) {
229229

230230
func createContentFolder(path string, log log.Logger) error {
231231
log.WithFields(logrus.Fields{"path": path}).Debug("create content folder")
232-
if err := os.MkdirAll(path, 0o777); err != nil {
232+
if err := os.MkdirAll(path, 0o750); err != nil {
233233
return fmt.Errorf("make workspace folder: %w", err)
234234
}
235235
return nil
@@ -307,7 +307,7 @@ func (w *workspaceInitializer) initialize() error {
307307
return err
308308
}
309309

310-
w.configureDockerDaemon()
310+
w.tryConfigureDockerDaemon()
311311
return nil
312312
}
313313

@@ -319,7 +319,7 @@ func (w *workspaceInitializer) setupDaemonIfNeeded() {
319319
}
320320
}
321321

322-
func (w *workspaceInitializer) configureDockerDaemon() {
322+
func (w *workspaceInitializer) tryConfigureDockerDaemon() {
323323
if !w.shouldConfigureDockerDaemon() {
324324
w.logger.Debug("skipping configuring docker daemon")
325325
return
@@ -392,8 +392,11 @@ func (w *workspaceInitializer) ensureDockerInstalled() (string, error) {
392392
}
393393

394394
if dockerCmd != "docker" {
395-
_, err := exec.LookPath(dockerCmd)
396-
return "", err
395+
path, err := exec.LookPath(dockerCmd)
396+
if err != nil {
397+
return "", fmt.Errorf("custom docker path %q not found: %w", dockerCmd, err)
398+
}
399+
return path, nil
397400
}
398401

399402
if w.isDockerInstallDisabled() {
@@ -429,6 +432,8 @@ func (w *workspaceInitializer) prepareWorkspaceContent() error {
429432
})
430433
}
431434

435+
// waitForDocker waits for the Docker installation to complete.
436+
// Note: This function modifies workspaceInfo.Agent.Docker.Path if Docker was installed.
432437
func (w *workspaceInitializer) waitForDocker(resultChan <-chan dockerInstallResult) error {
433438
result := <-resultChan
434439

@@ -465,6 +470,8 @@ type prepareWorkspaceParams struct {
465470
log log.Logger
466471
}
467472

473+
// prepareWorkspace initializes the workspace content folder and downloads/prepares the workspace source.
474+
// Note: This function modifies params.workspaceInfo.ContentFolder when platform is enabled with a local folder.
468475
func prepareWorkspace(params prepareWorkspaceParams) error {
469476
if params.workspaceInfo.CLIOptions.Platform.Enabled && params.workspaceInfo.Workspace.Source.LocalFolder != "" {
470477
params.workspaceInfo.ContentFolder = agent.GetAgentWorkspaceContentDir(params.workspaceInfo.Origin)
@@ -653,52 +660,40 @@ func prepareImage(workspaceDir, image string) error {
653660
return os.WriteFile(filepath.Join(workspaceDir, ".devcontainer.json"), devcontainerConfig, 0o600)
654661
}
655662

663+
// installDocker installs Docker and returns the path to the docker binary.
664+
// This function assumes docker does not already exist - the caller should check first.
656665
func installDocker(log log.Logger) (dockerPath string, err error) {
657-
if !command.Exists("docker") {
658-
writer := log.Writer(logrus.InfoLevel, false)
659-
defer func() { _ = writer.Close() }()
660-
661-
log.Debug("Installing Docker")
662-
663-
dockerPath, err = dockerinstall.Install(writer, writer)
664-
} else {
665-
dockerPath = "docker"
666-
}
667-
return dockerPath, err
666+
writer := log.Writer(logrus.InfoLevel, false)
667+
defer func() { _ = writer.Close() }()
668+
log.Debug("installing Docker")
669+
return dockerinstall.Install(writer, writer)
668670
}
669671

670672
func configureDockerDaemon(ctx context.Context, log log.Logger) error {
671673
log.Info("configuring docker daemon")
672674

673-
daemonConfig := []byte(`{
674-
"features": {
675-
"containerd-snapshotter": true
676-
}
677-
}`)
678-
679-
if err := writeDockerDaemonConfig(daemonConfig); err != nil {
675+
if err := mergeDockerDaemonConfig(); err != nil {
680676
return err
681677
}
682678

683679
return reloadDockerDaemon(ctx)
684680
}
685681

686-
func writeDockerDaemonConfig(config []byte) error {
687-
rootlessErr := tryWriteRootlessDockerConfig(config)
682+
func mergeDockerDaemonConfig() error {
683+
rootlessErr := tryMergeRootlessDockerConfig()
688684
if rootlessErr == nil {
689685
return nil
690686
}
691687

692-
// #nosec G306 -- daemon.json needs to be readable by docker daemon
693-
rootErr := os.WriteFile("/etc/docker/daemon.json", config, 0644)
688+
rootErr := tryMergeRootDockerConfig()
694689
if rootErr == nil {
695690
return nil
696691
}
697692

698693
return fmt.Errorf("failed to write docker daemon config (rootless: %v, root: %v)", rootlessErr, rootErr)
699694
}
700695

701-
func tryWriteRootlessDockerConfig(config []byte) error {
696+
func tryMergeRootlessDockerConfig() error {
702697
homeDir, err := util.UserHomeDir()
703698
if err != nil {
704699
return err
@@ -709,10 +704,60 @@ func tryWriteRootlessDockerConfig(config []byte) error {
709704
return err
710705
}
711706

707+
configPath := filepath.Join(dockerConfigDir, "daemon.json")
708+
return mergeContainerdSnapshotterConfig(configPath)
709+
}
710+
711+
func tryMergeRootDockerConfig() error {
712+
return mergeContainerdSnapshotterConfig("/etc/docker/daemon.json")
713+
}
714+
715+
func mergeContainerdSnapshotterConfig(configPath string) error {
716+
existingConfig := make(map[string]any)
717+
data, err := os.ReadFile(configPath)
718+
if err != nil && !errors.Is(err, os.ErrNotExist) {
719+
return fmt.Errorf("read existing config: %w", err)
720+
}
721+
722+
if len(data) > 0 {
723+
if err := json.Unmarshal(data, &existingConfig); err != nil {
724+
return fmt.Errorf("parse existing config: %w", err)
725+
}
726+
}
727+
728+
features, ok := existingConfig["features"].(map[string]any)
729+
if !ok {
730+
features = make(map[string]any)
731+
existingConfig["features"] = features
732+
}
733+
features["containerd-snapshotter"] = true
734+
735+
mergedData, err := json.MarshalIndent(existingConfig, "", " ")
736+
if err != nil {
737+
return fmt.Errorf("marshal config: %w", err)
738+
}
739+
740+
if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
741+
return fmt.Errorf("create config directory: %w", err)
742+
}
743+
712744
// #nosec G306 -- daemon.json needs to be readable by docker daemon
713-
return os.WriteFile(filepath.Join(dockerConfigDir, "daemon.json"), config, 0644)
745+
if err := os.WriteFile(configPath, mergedData, 0644); err != nil {
746+
return fmt.Errorf("write config: %w", err)
747+
}
748+
749+
return nil
714750
}
715751

716752
func reloadDockerDaemon(ctx context.Context) error {
717-
return exec.CommandContext(ctx, "pkill", "-HUP", "dockerd").Run()
753+
err := exec.CommandContext(ctx, "pkill", "-HUP", "dockerd").Run()
754+
if err != nil {
755+
// pkill returns exit code 1 if no processes matched
756+
var exitErr *exec.ExitError
757+
if errors.As(err, &exitErr) && exitErr.ExitCode() == 1 {
758+
return nil // No dockerd process found, nothing to reload
759+
}
760+
return err
761+
}
762+
return nil
718763
}

e2e/tests/integration/integration.go

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,20 @@ import (
77
"os/exec"
88

99
"github.com/onsi/ginkgo/v2"
10-
"github.com/onsi/gomega"
1110
"github.com/skevetter/devpod/e2e/framework"
1211
)
1312

1413
var _ = ginkgo.Describe("[integration]: devpod provider ssh test suite", ginkgo.Ordered, func() {
1514
ginkgo.Context("testing provider integration", ginkgo.Label("integration"), ginkgo.Ordered, func() {
1615
var initialDir string
17-
ctx := context.Background()
1816

1917
ginkgo.BeforeEach(func() {
2018
var err error
2119
initialDir, err = os.Getwd()
2220
framework.ExpectNoError(err)
2321
})
2422

25-
ginkgo.It("should generate ssh keypairs", func() {
23+
ginkgo.It("should generate ssh keypairs", func(ctx context.Context) {
2624
sshDir := os.Getenv("HOME") + "/.ssh"
2725
if _, err := os.Stat(sshDir); os.IsNotExist(err) {
2826
err = os.MkdirAll(sshDir, 0700)
@@ -32,19 +30,19 @@ var _ = ginkgo.Describe("[integration]: devpod provider ssh test suite", ginkgo.
3230
_, err := os.Stat(os.Getenv("HOME") + "/.ssh/id_rsa")
3331
if err != nil {
3432
fmt.Println("generating ssh keys")
35-
cmd := exec.Command("ssh-keygen", "-q", "-t", "rsa", "-N", "", "-f", os.Getenv("HOME")+"/.ssh/id_rsa")
33+
cmd := exec.CommandContext(ctx, "ssh-keygen", "-q", "-t", "rsa", "-N", "", "-f", os.Getenv("HOME")+"/.ssh/id_rsa")
3634
err = cmd.Run()
3735
framework.ExpectNoError(err)
3836

39-
cmd = exec.Command("ssh-keygen", "-y", "-f", os.Getenv("HOME")+"/.ssh/id_rsa")
37+
cmd = exec.CommandContext(ctx, "ssh-keygen", "-y", "-f", os.Getenv("HOME")+"/.ssh/id_rsa")
4038
output, err := cmd.Output()
4139
framework.ExpectNoError(err)
4240

4341
err = os.WriteFile(os.Getenv("HOME")+"/.ssh/id_rsa.pub", output, 0600)
4442
framework.ExpectNoError(err)
4543
}
4644

47-
cmd := exec.Command("ssh-keygen", "-y", "-f", os.Getenv("HOME")+"/.ssh/id_rsa")
45+
cmd := exec.CommandContext(ctx, "ssh-keygen", "-y", "-f", os.Getenv("HOME")+"/.ssh/id_rsa")
4846
publicKey, err := cmd.Output()
4947
framework.ExpectNoError(err)
5048

@@ -63,7 +61,7 @@ var _ = ginkgo.Describe("[integration]: devpod provider ssh test suite", ginkgo.
6361
}
6462
})
6563

66-
ginkgo.It("should add provider to devpod", func() {
64+
ginkgo.It("should add provider to devpod", func(ctx context.Context) {
6765
f := framework.NewDefaultFramework(initialDir + "/bin")
6866
// ensure we don't have the ssh provider present
6967
err := f.DevPodProviderDelete(ctx, "ssh")
@@ -81,12 +79,11 @@ var _ = ginkgo.Describe("[integration]: devpod provider ssh test suite", ginkgo.
8179
framework.ExpectNoError(err)
8280
})
8381

84-
ginkgo.It("should run commands to workspace via ssh", func() {
85-
cmd := exec.Command("ssh", "testdata.devpod", "echo", "test")
86-
output, err := cmd.Output()
82+
ginkgo.It("should run commands to workspace via ssh", func(ctx context.Context) {
83+
f := framework.NewDefaultFramework(initialDir + "/bin")
84+
out, err := f.DevPodSSH(ctx, "testdata", "echo test")
8785
framework.ExpectNoError(err)
88-
89-
gomega.Expect(output).To(gomega.Equal([]byte("test\n")))
86+
framework.ExpectEqual(out, "test\n")
9087
})
9188

9289
ginkgo.It("should cleanup devpod workspace", func(ctx context.Context) {

pkg/client/clientimplementation/daemonclient/client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,8 @@ func (c *client) Ping(ctx context.Context, writer io.Writer) error {
257257

258258
for range 10 {
259259
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
260-
defer cancel()
261260
result, err := c.tsClient.Ping(timeoutCtx, *ip, tailcfg.PingDisco)
261+
cancel()
262262
if err != nil {
263263
return err
264264
}

pkg/client/clientimplementation/workspace_client.go

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -505,20 +505,7 @@ func (s *workspaceClient) Stop(ctx context.Context, opt client.StopOptions) erro
505505
}
506506

507507
func (s *workspaceClient) Command(ctx context.Context, commandOptions client.CommandOptions) (err error) {
508-
s.m.Lock()
509-
defer s.m.Unlock()
510-
511-
environ, err := provider.ToEnvironmentWithBinaries(provider.EnvironmentOptions{
512-
Context: s.workspace.Context,
513-
Workspace: s.workspace,
514-
Machine: s.machine,
515-
Options: s.devPodConfig.ProviderOptions(s.config.Name),
516-
Config: s.config,
517-
ExtraEnv: map[string]string{
518-
provider.CommandEnv: commandOptions.Command,
519-
},
520-
Log: s.log,
521-
})
508+
environ, err := s.buildEnvironment(commandOptions.Command)
522509
if err != nil {
523510
return err
524511
}
@@ -534,6 +521,23 @@ func (s *workspaceClient) Command(ctx context.Context, commandOptions client.Com
534521
})
535522
}
536523

524+
func (s *workspaceClient) buildEnvironment(command string) ([]string, error) {
525+
s.m.Lock()
526+
defer s.m.Unlock()
527+
528+
return provider.ToEnvironmentWithBinaries(provider.EnvironmentOptions{
529+
Context: s.workspace.Context,
530+
Workspace: s.workspace,
531+
Machine: s.machine,
532+
Options: s.devPodConfig.ProviderOptions(s.config.Name),
533+
Config: s.config,
534+
ExtraEnv: map[string]string{
535+
provider.CommandEnv: command,
536+
},
537+
Log: s.log,
538+
})
539+
}
540+
537541
func (s *workspaceClient) Status(ctx context.Context, options client.StatusOptions) (client.Status, error) {
538542
s.m.Lock()
539543
defer s.m.Unlock()

0 commit comments

Comments
 (0)