Skip to content

Commit e8b29f8

Browse files
authored
Merge pull request #22 from stuartleeks/sl/exec-working-dir
Set working folder for exec command to workspace mount folder
2 parents 08b715a + dbaf58b commit e8b29f8

File tree

4 files changed

+74
-24
lines changed

4 files changed

+74
-24
lines changed

cmd/devcontainer/devcontainer.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func createExecCommand() *cobra.Command {
7474
}
7575

7676
devcontainerName := args[0]
77-
devcontainers, err := devcontainers.ListDevcontainers()
77+
devcontainerList, err := devcontainers.ListDevcontainers()
7878
if err != nil {
7979
return err
8080
}
@@ -83,17 +83,17 @@ func createExecCommand() *cobra.Command {
8383
if devcontainerName == "?" {
8484
// prompt user
8585
fmt.Println("Specify the devcontainer to use:")
86-
for index, devcontainer := range devcontainers {
86+
for index, devcontainer := range devcontainerList {
8787
fmt.Printf("%4d: %s (%s)\n", index, devcontainer.DevcontainerName, devcontainer.ContainerName)
8888
}
8989
selection := -1
9090
_, _ = fmt.Scanf("%d", &selection)
91-
if selection < 0 || selection >= len(devcontainers) {
91+
if selection < 0 || selection >= len(devcontainerList) {
9292
return fmt.Errorf("Invalid option")
9393
}
94-
containerID = devcontainers[selection].ContainerID
94+
containerID = devcontainerList[selection].ContainerID
9595
} else {
96-
for _, devcontainer := range devcontainers {
96+
for _, devcontainer := range devcontainerList {
9797
if devcontainer.ContainerName == devcontainerName || devcontainer.DevcontainerName == devcontainerName {
9898
containerID = devcontainer.ContainerID
9999
break
@@ -104,7 +104,17 @@ func createExecCommand() *cobra.Command {
104104
}
105105
}
106106

107-
dockerArgs := []string{"exec", "-it", containerID}
107+
localPath, err := devcontainers.GetLocalFolderFromDevContainer(containerID)
108+
if err != nil {
109+
return err
110+
}
111+
112+
mountPath, err := devcontainers.GetWorkspaceMountPath(localPath)
113+
if err != nil {
114+
return err
115+
}
116+
117+
dockerArgs := []string{"exec", "-it", "--workdir", mountPath, containerID}
108118
dockerArgs = append(dockerArgs, args[1:]...)
109119

110120
dockerCmd := exec.Command("docker", dockerArgs...)

internal/pkg/devcontainers/dockerutils.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ type DevcontainerInfo struct {
2222
}
2323

2424
const (
25-
ListPartID int = 0
26-
ListPartLocalFolder int = 1
27-
ListPartComposeProject int = 2
28-
ListPartComposeService int = 3
29-
ListPartComposeContainerNumber int = 4
30-
ListPartContainerName int = 5
25+
listPartID int = 0
26+
listPartLocalFolder int = 1
27+
listPartComposeProject int = 2
28+
listPartComposeService int = 3
29+
listPartComposeContainerNumber int = 4
30+
listPartContainerName int = 5
3131
)
3232

33+
var _ = listPartComposeContainerNumber
34+
3335
// ListDevcontainers returns a list of devcontainers
3436
func ListDevcontainers() ([]DevcontainerInfo, error) {
3537
cmd := exec.Command("docker", "ps", "--format", "{{.ID}}|{{.Label \"vsch.local.folder\"}}|{{.Label \"com.docker.compose.project\"}}|{{.Label \"com.docker.compose.service\"}}|{{.Label \"com.docker.compose.container-number\"}}|{{.Names}}")
@@ -48,22 +50,36 @@ func ListDevcontainers() ([]DevcontainerInfo, error) {
4850
for scanner.Scan() {
4951
line := scanner.Text()
5052
parts := strings.Split(line, "|")
51-
name := parts[ListPartLocalFolder]
53+
name := parts[listPartLocalFolder]
5254
if name == "" {
5355
// No local folder => use dockercompose parts
54-
name = fmt.Sprintf("%s/%s", parts[ListPartComposeProject], parts[ListPartComposeService])
56+
name = fmt.Sprintf("%s/%s", parts[listPartComposeProject], parts[listPartComposeService])
5557
} else {
5658
// get the last path segment for the name
5759
if index := strings.LastIndexAny(name, "/\\"); index >= 0 {
5860
name = name[index+1:]
5961
}
6062
}
6163
devcontainer := DevcontainerInfo{
62-
ContainerID: parts[ListPartID],
63-
ContainerName: parts[ListPartContainerName],
64+
ContainerID: parts[listPartID],
65+
ContainerName: parts[listPartContainerName],
6466
DevcontainerName: name,
6567
}
6668
devcontainers = append(devcontainers, devcontainer)
6769
}
6870
return devcontainers, nil
6971
}
72+
73+
// GetLocalFolderFromDevContainer looks up the local (host) folder name from the container labels
74+
func GetLocalFolderFromDevContainer(containerIDOrName string) (string, error) {
75+
//docker inspect cool_goldberg --format '{{ index .Config.Labels "vsch.local.folder" }}'
76+
77+
cmd := exec.Command("docker", "inspect", containerIDOrName, "--format", "{{ index .Config.Labels \"vsch.local.folder\" }}")
78+
79+
output, err := cmd.Output()
80+
if err != nil {
81+
return "", fmt.Errorf("Failed to read docker stdout: %v", err)
82+
}
83+
84+
return strings.TrimSpace(string(output)), nil
85+
}

internal/pkg/devcontainers/remoteuri.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io/ioutil"
77
"path/filepath"
88
"regexp"
9+
"strings"
910

1011
"github.com/stuartleeks/devcontainer-cli/internal/pkg/wsl"
1112
)
@@ -28,7 +29,7 @@ func GetDevContainerURI(folderPath string) (string, error) {
2829
}
2930

3031
launchPathHex := convertToHexString(launchPath)
31-
workspaceMountPath, err := getWorkspaceMountPath(absPath)
32+
workspaceMountPath, err := GetWorkspaceMountPath(absPath)
3233
if err != nil {
3334
return "", err
3435
}
@@ -41,14 +42,21 @@ func convertToHexString(input string) string {
4142
return hex.EncodeToString([]byte(input))
4243
}
4344

44-
// TODO: add tests (and implementation) to handle JSON parsing with comments
45-
// Current implementation doesn't handle
46-
// - block comments
47-
// - the value split on a new line from the property name
48-
49-
func getWorkspaceMountPath(folderPath string) (string, error) {
45+
// GetWorkspaceMountPath returns the devcontainer mount path for the devcontainer in the specified folder
46+
func GetWorkspaceMountPath(folderPath string) (string, error) {
5047
// TODO - consider how to support repository-containers (https://github.com/microsoft/vscode-remote-release/issues/3218)
5148

49+
// If we're called from WSL we want a WSL Path but will also handle a Windows Path
50+
if wsl.IsWsl() {
51+
if strings.HasPrefix(folderPath, "\\\\wsl$\\") {
52+
convertedPath, err := wsl.ConvertWindowsPathToWslPath(folderPath)
53+
if err != nil {
54+
return "", err
55+
}
56+
folderPath = convertedPath
57+
}
58+
}
59+
5260
devcontainerDefinitionPath := filepath.Join(folderPath, ".devcontainer/devcontainer.json")
5361
buf, err := ioutil.ReadFile(devcontainerDefinitionPath)
5462
if err != nil {
@@ -68,6 +76,11 @@ func getWorkspaceMountPath(folderPath string) (string, error) {
6876
return fmt.Sprintf("/workspaces/%s", folderName), nil
6977
}
7078

79+
// TODO: add tests (and implementation) to handle JSON parsing with comments
80+
// Current implementation doesn't handle
81+
// - block comments
82+
// - the value split on a new line from the property name
83+
7184
func getWorkspaceMountPathFromDevcontainerDefinition(definition []byte) (string, error) {
7285
r, err := regexp.Compile("(?m)^\\s*\"workspaceFolder\"\\s*:\\s*\"(.*)\"")
7386
if err != nil {

internal/pkg/wsl/wsl.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,18 @@ func ConvertWslPathToWindowsPath(path string) (string, error) {
1919

2020
buf, err := cmd.Output()
2121
if err != nil {
22-
return "", fmt.Errorf("Error running wslpath: %s", err)
22+
return "", fmt.Errorf("Error running wslpath (for %q): %s", path, err)
23+
}
24+
return strings.TrimSpace(string(buf)), nil
25+
}
26+
27+
// ConvertWslPathToWindowsPath converts a WSL path to the corresponding \\wsl$\... path for access from Windows
28+
func ConvertWindowsPathToWslPath(path string) (string, error) {
29+
cmd := exec.Command("wslpath", "-u", path)
30+
31+
buf, err := cmd.Output()
32+
if err != nil {
33+
return "", fmt.Errorf("Error running wslpath (for %q): %s", path, err)
2334
}
2435
return strings.TrimSpace(string(buf)), nil
2536
}

0 commit comments

Comments
 (0)