Skip to content

Commit 15ad21e

Browse files
authored
Merge pull request #32 from stuartleeks/sl/exec-redesign
Redesign exec command
2 parents da4fbad + 6a9ea10 commit 15ad21e

File tree

5 files changed

+104
-31
lines changed

5 files changed

+104
-31
lines changed

Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ help: ## show this help
55
| column -t -s '|'
66

77

8-
build: ## Build devcontainer cli
8+
build: fmt ## Build devcontainer cli
99
go build ./cmd/devcontainer
1010

1111
lint: build ## Build and lint
@@ -31,4 +31,8 @@ endif
3131

3232

3333
test:
34-
go test -v ./...
34+
go test -v ./...
35+
36+
37+
fmt:
38+
find . -name '*.go' | grep -v vendor | xargs gofmt -s -w

README.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,25 @@ For example:
5151

5252
```bash
5353
# Run an interactive bash shell in the vscode-remote-test-dockerfile devcontainer
54-
devcontainer exec vscode-remote-test-dockerfile bash
54+
devcontainer exec --name vscode-remote-test-dockerfile bash
5555

5656
# Run a command with args in the vscode-remote-test-dockercompose_devcontainer/mongo devcontainer
57-
devcontainer exec vscode-remote-test-dockercompose_devcontainer/mongo ls -a /workspaces/vscode-remote-test-dockerfile
57+
devcontainer exec --name vscode-remote-test-dockercompose_devcontainer/mongo ls -a /workspaces/vscode-remote-test-dockerfile
58+
59+
# Run `bash` in the dev container for the project at `~/ source/my-proj`
60+
devcontainer exec --path ~/source/my-proj bash
61+
62+
# If none of --name/--path/--prompt are specified then `--path .` is assumed (i.e. use the dev container for the current directory)
63+
devcontainer exec bash
64+
65+
# If command/args not set, `bash` is assumed
66+
devcontainer exec --name vscode-remote-test-dockerfile
67+
68+
# Combining these to launch bash in the dev container for the project in the current directory:
69+
devcontainer exec
5870
```
5971

60-
You can pass `?` as the devcontainer name and the CLI will prompt you to pick a devcontainer to run the `exec` command against, e.g.:
72+
You can use `--prompt` instead of `--name` or `--path` and the CLI will prompt you to pick a devcontainer to run the `exec` command against, e.g.:
6173

6274
```bash
6375
$ ./devcontainer exec ? bash
@@ -74,10 +86,12 @@ You can use this with Windows Terminal profiles:
7486
"guid": "{4b304185-99d2-493c-940c-ae74e0f14bba}",
7587
"hidden": false,
7688
"name": "devcontainer exec",
77-
"commandline": "wsl bash -c \"path/to/devcontainer exec ? bash\"",
89+
"commandline": "wsl bash -c \"path/to/devcontainer exec --prompt bash\"",
7890
},
7991
```
8092

93+
By default, `devcontainer exec` will set the working directory to be the mount path for the dev container. This can be overridden using `--work-dir`.
94+
8195
### Working with devcontainer templates
8296

8397
To work with devcontainer templates `devcontainer` needs to know where you have the templates stored.

cmd/devcontainer/devcontainer.go

Lines changed: 77 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66
"os/exec"
77
"path"
8+
"path/filepath"
89
"sort"
910
"strings"
1011
"text/tabwriter"
@@ -65,30 +66,57 @@ func createListCommand() *cobra.Command {
6566
return cmdList
6667
}
6768

69+
func countBooleans(values ...bool) int {
70+
count := 0
71+
for _, v := range values {
72+
if v {
73+
count++
74+
}
75+
}
76+
return count
77+
}
78+
6879
func createExecCommand() *cobra.Command {
80+
var argDevcontainerName string
81+
var argDevcontainerPath string
82+
var argPromptForDevcontainer bool
83+
var argWorkDir string
84+
6985
cmd := &cobra.Command{
70-
Use: "exec DEVCONTAINER_NAME COMMAND [args...] (args will default to /bin/bash if none provided)",
86+
Use: "exec [--name <name>| --path <path> | --prompt ] [--work-dir <work-dir>] [<command> [<args...>]] (command will default to /bin/bash if none provided)",
7187
Short: "Execute a command in a devcontainer",
72-
Long: "Execute a command in a devcontainer, similar to `docker exec`. Pass `?` as DEVCONTAINER_NAME to be prompted.",
88+
Long: "Execute a command in a devcontainer, similar to `docker exec`",
7389
RunE: func(cmd *cobra.Command, args []string) error {
7490

75-
if len(args) < 1 {
76-
return cmd.Usage()
91+
// Default to executing /bin/bash
92+
if len(args) == 0 {
93+
args = []string{"/bin/bash"}
7794
}
7895

79-
// Default to executing /bin/bash
80-
if (len(args) == 1) {
81-
args = append(args, "/bin/bash")
96+
sourceCount := countBooleans(
97+
argDevcontainerName != "",
98+
argDevcontainerPath != "",
99+
argPromptForDevcontainer,
100+
)
101+
if sourceCount > 0 {
102+
fmt.Println("Can specify at most one of --name/--path/--prompt")
103+
return cmd.Usage()
82104
}
83105

84-
devcontainerName := args[0]
106+
containerID := ""
85107
devcontainerList, err := devcontainers.ListDevcontainers()
86108
if err != nil {
87109
return err
88110
}
89-
90-
containerID := ""
91-
if devcontainerName == "?" {
111+
if argDevcontainerName != "" {
112+
devcontainerName := argDevcontainerName
113+
for _, devcontainer := range devcontainerList {
114+
if devcontainer.ContainerName == devcontainerName || devcontainer.DevcontainerName == devcontainerName {
115+
containerID = devcontainer.ContainerID
116+
break
117+
}
118+
}
119+
} else if argPromptForDevcontainer {
92120
// prompt user
93121
fmt.Println("Specify the devcontainer to use:")
94122
for index, devcontainer := range devcontainerList {
@@ -101,25 +129,47 @@ func createExecCommand() *cobra.Command {
101129
}
102130
containerID = devcontainerList[selection].ContainerID
103131
} else {
132+
devcontainerPath := argDevcontainerPath
133+
if devcontainerPath == "" {
134+
devcontainerPath = "."
135+
}
136+
absPath, err := filepath.Abs(devcontainerPath)
137+
if err != nil {
138+
return fmt.Errorf("Error handling path %q: %s", devcontainerPath, err)
139+
}
140+
141+
windowsPath := absPath
142+
if wsl.IsWsl() {
143+
var err error
144+
windowsPath, err = wsl.ConvertWslPathToWindowsPath(windowsPath)
145+
if err != nil {
146+
return err
147+
}
148+
}
104149
for _, devcontainer := range devcontainerList {
105-
if devcontainer.ContainerName == devcontainerName || devcontainer.DevcontainerName == devcontainerName {
150+
if devcontainer.LocalFolderPath == windowsPath {
106151
containerID = devcontainer.ContainerID
107152
break
108153
}
109154
}
110-
if containerID == "" {
111-
return cmd.Usage()
112-
}
155+
}
156+
157+
if containerID == "" {
158+
fmt.Println("Failed to find dev container")
159+
return cmd.Usage()
113160
}
114161

115162
localPath, err := devcontainers.GetLocalFolderFromDevContainer(containerID)
116163
if err != nil {
117164
return err
118165
}
119166

120-
mountPath, err := devcontainers.GetWorkspaceMountPath(localPath)
121-
if err != nil {
122-
return err
167+
mountPath := argWorkDir
168+
if mountPath == "" {
169+
mountPath, err = devcontainers.GetWorkspaceMountPath(localPath)
170+
if err != nil {
171+
return err
172+
}
123173
}
124174

125175
wslPath := localPath
@@ -130,8 +180,8 @@ func createExecCommand() *cobra.Command {
130180
}
131181
}
132182

133-
devcontainerJsonPath := path.Join(wslPath, ".devcontainer/devcontainer.json")
134-
userName, err := devcontainers.GetDevContainerUserName(devcontainerJsonPath)
183+
devcontainerJSONPath := path.Join(wslPath, ".devcontainer/devcontainer.json")
184+
userName, err := devcontainers.GetDevContainerUserName(devcontainerJSONPath)
135185
if err != nil {
136186
return err
137187
}
@@ -141,24 +191,23 @@ func createExecCommand() *cobra.Command {
141191
dockerArgs = append(dockerArgs, "--user", userName)
142192
}
143193
dockerArgs = append(dockerArgs, containerID)
144-
dockerArgs = append(dockerArgs, args[1:]...)
194+
dockerArgs = append(dockerArgs, args...)
145195

146196
dockerCmd := exec.Command("docker", dockerArgs...)
147197
dockerCmd.Stdin = os.Stdin
148198
dockerCmd.Stdout = os.Stdout
149199

150200
err = dockerCmd.Start()
151201
if err != nil {
152-
return fmt.Errorf("Exec: start error: %s\n", err)
202+
return fmt.Errorf("Exec: start error: %s", err)
153203
}
154204
err = dockerCmd.Wait()
155205
if err != nil {
156-
return fmt.Errorf("Exec: wait error: %s\n", err)
206+
return fmt.Errorf("Exec: wait error: %s", err)
157207
}
158208
return nil
159209
},
160210
Args: cobra.ArbitraryArgs,
161-
DisableFlagParsing: true,
162211
DisableFlagsInUseLine: true,
163212
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
164213
// only completing the first arg (devcontainer name)
@@ -177,5 +226,9 @@ func createExecCommand() *cobra.Command {
177226
return names, cobra.ShellCompDirectiveNoFileComp
178227
},
179228
}
229+
cmd.Flags().StringVarP(&argDevcontainerName, "name", "n", "", "name of dev container to exec into")
230+
cmd.Flags().StringVarP(&argDevcontainerPath, "path", "", "", "path containing the dev container to exec into")
231+
cmd.Flags().BoolVarP(&argPromptForDevcontainer, "prompt", "", false, "prompt for the dev container to exec into")
232+
cmd.Flags().StringVarP(&argWorkDir, "work-dir", "", "", "working directory to use in the dev container")
180233
return cmd
181234
}

internal/pkg/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,6 @@ func SaveConfig() error {
6868
if err := os.MkdirAll(configPath, 0755); err != nil {
6969
return err
7070
}
71-
configFilePath:=filepath.Join(configPath, "devcontainer-cli.json")
71+
configFilePath := filepath.Join(configPath, "devcontainer-cli.json")
7272
return viper.WriteConfigAs(configFilePath)
7373
}

internal/pkg/devcontainers/dockerutils.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type DevcontainerInfo struct {
1919
ContainerID string
2020
ContainerName string
2121
DevcontainerName string
22+
LocalFolderPath string
2223
}
2324

2425
const (
@@ -63,6 +64,7 @@ func ListDevcontainers() ([]DevcontainerInfo, error) {
6364
devcontainer := DevcontainerInfo{
6465
ContainerID: parts[listPartID],
6566
ContainerName: parts[listPartContainerName],
67+
LocalFolderPath: parts[listPartLocalFolder],
6668
DevcontainerName: name,
6769
}
6870
devcontainers = append(devcontainers, devcontainer)

0 commit comments

Comments
 (0)