@@ -3,6 +3,7 @@ package devcontainers
33import (
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
96133func 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