Skip to content

Commit 4226f20

Browse files
committed
update workdir handling for exec
1 parent e835954 commit 4226f20

2 files changed

Lines changed: 122 additions & 45 deletions

File tree

cmd/devcontainer/devcontainer.go

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,37 @@ func createExecCommand() *cobra.Command {
9898
return cmd.Usage()
9999
}
100100

101-
containerIDOrName := ""
101+
// workDir default:
102+
// - devcontainer mount path if name or prompt specified (ExecInDevContainer defaults to this if workDir is "")
103+
// - path if path set
104+
// - current directory if path == "" and neither name or prompt set
105+
workDir := argWorkDir
106+
107+
containerID := ""
102108
devcontainerList, err := devcontainers.ListDevcontainers()
103109
if err != nil {
104110
return err
105111
}
106112
if argDevcontainerName != "" {
107-
containerIDOrName = argDevcontainerName
113+
containerIDOrName := argDevcontainerName
114+
devcontainerList, err := devcontainers.ListDevcontainers()
115+
if err != nil {
116+
return err
117+
}
118+
119+
// Get container ID
120+
for _, devcontainer := range devcontainerList {
121+
if devcontainer.ContainerName == containerIDOrName ||
122+
devcontainer.DevcontainerName == containerIDOrName ||
123+
devcontainer.ContainerID == containerIDOrName {
124+
containerID = devcontainer.ContainerID
125+
break
126+
}
127+
}
128+
129+
if containerID == "" {
130+
return fmt.Errorf("Failed to find a matching (running) dev container for %q", containerIDOrName)
131+
}
108132
} else if argPromptForDevcontainer {
109133
// prompt user
110134
fmt.Println("Specify the devcontainer to use:")
@@ -116,16 +140,26 @@ func createExecCommand() *cobra.Command {
116140
if selection < 0 || selection >= len(devcontainerList) {
117141
return fmt.Errorf("Invalid option")
118142
}
119-
containerIDOrName = devcontainerList[selection].ContainerID
143+
containerID = devcontainerList[selection].ContainerID
120144
} else {
121145
devcontainerPath := argDevcontainerPath
122-
containerIDOrName, err = devcontainers.GetContainerIDForPath(devcontainerPath)
146+
// TODO - update to check for devcontainers in the path ancestry
147+
// Can't just check up the path for a .devcontainer folder as the container might
148+
// have been created via repository containers (https://github.com/microsoft/vscode-dev-containers/tree/main/repository-containers)
149+
containerID, err = devcontainers.GetContainerIDForPath(devcontainerPath)
123150
if err != nil {
124151
return err
125152
}
153+
if workDir == "" {
154+
if devcontainerPath == "" {
155+
workDir = "."
156+
} else {
157+
workDir = devcontainerPath
158+
}
159+
}
126160
}
127161

128-
return devcontainers.ExecInDevContainer(containerIDOrName, argWorkDir, args)
162+
return devcontainers.ExecInDevContainer(containerID, workDir, args)
129163
},
130164
Args: cobra.ArbitraryArgs,
131165
DisableFlagsInUseLine: true,

internal/pkg/devcontainers/dockerutils.go

Lines changed: 83 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package devcontainers
33
import (
44
"bufio"
55
"bytes"
6+
"encoding/json"
67
"fmt"
78
"os"
89
"os/exec"
@@ -92,6 +93,42 @@ func GetLocalFolderFromDevContainer(containerIDOrName string) (string, error) {
9293
return strings.TrimSpace(string(output)), nil
9394
}
9495

96+
// DockerMount represents mount info from Docker output
97+
type DockerMount struct {
98+
Source string `json:"Source"`
99+
Destination string `json:"Destination"`
100+
}
101+
102+
// GetSourceMountFolderFromDevContainer inspects the specified container and returns the DockerMount for the source mount
103+
func GetSourceMountFolderFromDevContainer(containerIDOrName string) (DockerMount, error) {
104+
localPath, err := GetLocalFolderFromDevContainer(containerIDOrName)
105+
if err != nil {
106+
return DockerMount{}, err
107+
}
108+
109+
if strings.HasPrefix(localPath, "\\\\wsl$") && wsl.IsWsl() {
110+
localPath, err = wsl.ConvertWindowsPathToWslPath(localPath)
111+
if err != nil {
112+
return DockerMount{}, fmt.Errorf("error converting path: %s", err)
113+
}
114+
}
115+
116+
cmd := exec.Command("docker", "inspect", containerIDOrName, "--format", fmt.Sprintf("{{ range .Mounts }}{{if eq .Source \"%s\"}}{{json .}}{{end}}{{end}}", localPath))
117+
118+
output, err := cmd.Output()
119+
if err != nil {
120+
return DockerMount{}, fmt.Errorf("Failed to read docker stdout: %v", err)
121+
}
122+
123+
var mount DockerMount
124+
err = json.Unmarshal(output, &mount)
125+
if err != nil {
126+
return DockerMount{}, err
127+
}
128+
129+
return mount, nil
130+
}
131+
95132
// GetContainerIDForPath returns the ID of the running container that matches the path
96133
func GetContainerIDForPath(devcontainerPath string) (string, error) {
97134
if devcontainerPath == "" {
@@ -125,54 +162,18 @@ func GetContainerIDForPath(devcontainerPath string) (string, error) {
125162
return "", fmt.Errorf("Could not find running container for path %q", devcontainerPath)
126163
}
127164

128-
func ExecInDevContainer(containerIDOrName string, workDir string, args []string) error {
165+
func ExecInDevContainer(containerID string, workDir string, args []string) error {
129166

130167
statusWriter := &terminal.UpdatingStatusWriter{}
131168

132-
containerID := ""
133-
devcontainerList, err := ListDevcontainers()
169+
sourceMount, err := GetSourceMountFolderFromDevContainer(containerID)
134170
if err != nil {
135171
return err
136172
}
137-
138-
// Get container ID
139-
for _, devcontainer := range devcontainerList {
140-
if devcontainer.ContainerName == containerIDOrName ||
141-
devcontainer.DevcontainerName == containerIDOrName ||
142-
devcontainer.ContainerID == containerIDOrName {
143-
containerID = devcontainer.ContainerID
144-
break
145-
}
146-
}
147-
148-
if containerID == "" {
149-
return fmt.Errorf("Failed to find a matching (running) dev container for %q", containerIDOrName)
150-
}
151-
152-
localPath, err := GetLocalFolderFromDevContainer(containerID)
153-
if err != nil {
154-
return err
155-
}
156-
157-
statusWriter.Printf("Getting mount path")
158-
if workDir == "" {
159-
workDir, err = GetWorkspaceMountPath(localPath)
160-
if err != nil {
161-
return err
162-
}
163-
}
164-
165-
wslPath := localPath
166-
if strings.HasPrefix(wslPath, "\\\\wsl$") && wsl.IsWsl() {
167-
statusWriter.Printf("Converting to WSL path")
168-
wslPath, err = wsl.ConvertWindowsPathToWslPath(wslPath)
169-
if err != nil {
170-
return fmt.Errorf("error converting path: %s", err)
171-
}
172-
}
173+
localPath := sourceMount.Source
173174

174175
statusWriter.Printf("Getting user name")
175-
devcontainerJSONPath := path.Join(wslPath, ".devcontainer/devcontainer.json")
176+
devcontainerJSONPath := path.Join(localPath, ".devcontainer/devcontainer.json")
176177
userName, err := GetDevContainerUserName(devcontainerJSONPath)
177178
if err != nil {
178179
return err
@@ -217,6 +218,35 @@ func ExecInDevContainer(containerIDOrName string, workDir string, args []string)
217218
fmt.Println("Continuing without setting VSCODE_IPC_HOOK_CLI...")
218219
}
219220

221+
mountPath := sourceMount.Destination
222+
if workDir == "" {
223+
workDir = mountPath
224+
} else if !filepath.IsAbs(workDir) {
225+
226+
// Convert to absolute (local) path
227+
// This takes into account current directory (potentially within the dev container path)
228+
// We'll convert local to container path below
229+
workDir, err = filepath.Abs(workDir)
230+
if err != nil {
231+
return err
232+
}
233+
}
234+
235+
statusWriter.Printf("Test container path")
236+
containerPathExists, err := testContainerPathExists(containerID, workDir)
237+
if err != nil {
238+
return fmt.Errorf("error checking container path: %s", err)
239+
}
240+
if !containerPathExists {
241+
// path not found - try converting from local path
242+
// ? Should we check here that the workDir has localPath as a prefix?
243+
devContainerRelativePath, err := filepath.Rel(localPath, workDir)
244+
if err != nil {
245+
return fmt.Errorf("error getting path relative to mount dir: %s", err)
246+
}
247+
workDir = filepath.Join(mountPath, devContainerRelativePath)
248+
}
249+
220250
statusWriter.Printf("Starting exec session\n") // newline to put container shell at start of line
221251
dockerArgs := []string{"exec", "-it", "--workdir", workDir}
222252
if userName != "" {
@@ -306,3 +336,16 @@ func getContainerEnvVar(containerID string, varName string) (string, error) {
306336

307337
return string(buf), nil
308338
}
339+
340+
func testContainerPathExists(containerID string, path string) (bool, error) {
341+
dockerArgs := []string{"exec", containerID, "bash", "-c", fmt.Sprintf("[[ -d %s ]]; echo $?", path)}
342+
dockerCmd := exec.Command("docker", dockerArgs...)
343+
buf, err := dockerCmd.CombinedOutput()
344+
if err != nil {
345+
errMessage := string(buf)
346+
return false, fmt.Errorf("Docker exec error: %s (%s)", err, strings.TrimSpace(errMessage))
347+
}
348+
349+
response := strings.TrimSpace(string(buf))
350+
return response == "0", nil
351+
}

0 commit comments

Comments
 (0)