Skip to content

Commit 83c5dc4

Browse files
stasadevclaude
andauthored
feat: add user flag for ssh/exec/hooks and x-ddev.ssh-shell for ssh, fixes #7339 (#7786)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 970a0c3 commit 83c5dc4

16 files changed

Lines changed: 697 additions & 319 deletions

File tree

cmd/ddev/cmd/describe.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,12 @@ func renderAppDescribe(app *ddevapp.DdevApp, desc map[string]interface{}) (strin
199199
extraInfo = append(extraInfo, "User/Pass: 'db/db'\nor 'root/root'")
200200
}
201201

202-
// Add x-ddev.describe to URL/Port column if it exists
202+
// Add x-ddev.describe-url-port to URL/Port column if it exists
203203
if desc, ok := v["describe-url-port"].(string); ok && desc != "" {
204204
urlPortParts = append(urlPortParts, desc)
205205
}
206206

207-
// Add x-ddev.info to info column if it exists
207+
// Add x-ddev.describe-info to info column if it exists
208208
if desc, ok := v["describe-info"].(string); ok && desc != "" {
209209
extraInfo = append(extraInfo, desc)
210210
}

cmd/ddev/cmd/exec.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ var DdevExecCmd = &cobra.Command{
2525
ddev exec --service db
2626
ddev exec -s db
2727
ddev exec -s solr (assuming an add-on service named 'solr')
28-
ddev exec --raw -- ls -lR`,
28+
ddev exec --raw -- ls -lR
29+
ddev exec -s db -u root ls -la /root`,
2930
Run: func(cmd *cobra.Command, args []string) {
3031
if len(args) == 0 {
3132
err := cmd.Usage()
@@ -53,6 +54,7 @@ ddev exec --raw -- ls -lR`,
5354
Dir: execDirArg,
5455
Cmd: quoteArgs(args),
5556
Tty: true,
57+
User: serviceUser,
5658
}
5759

5860
// If they've chosen raw, use the actual passed values.
@@ -62,6 +64,7 @@ ddev exec --raw -- ls -lR`,
6264
path, _, err := app.Exec(&ddevapp.ExecOpts{
6365
Service: serviceType,
6466
Cmd: "echo $PATH",
67+
User: serviceUser,
6568
})
6669
path = strings.Trim(path, "\n")
6770
if err == nil && path != "" {
@@ -118,6 +121,7 @@ func init() {
118121
DdevExecCmd.Flags().StringVarP(&execDirArg, "dir", "d", "", "Define the execution directory within the container")
119122
DdevExecCmd.Flags().Bool("raw", true, "Use raw exec (do not interpret with Bash inside container)")
120123
DdevExecCmd.Flags().BoolP("quiet", "q", false, "Suppress detailed error output")
124+
DdevExecCmd.Flags().StringVarP(&serviceUser, "user", "u", "", "Defines the user to use within the container")
121125
// This requires flags for exec to be specified prior to any arguments, allowing for
122126
// flags to be ignored by cobra for commands that are to be executed in a container.
123127
DdevExecCmd.Flags().SetInterspersed(false)

cmd/ddev/cmd/exec_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,22 @@ func TestCmdExec(t *testing.T) {
8383
assert.NoError(err)
8484
assert.Contains(out, "root")
8585

86+
out, err = exec.RunHostCommand(DdevBin, "exec", "-u", "root", "whoami")
87+
assert.NoError(err)
88+
assert.Contains(out, "root")
89+
90+
out, err = exec.RunHostCommand(DdevBin, "exec", "-u", "root", "-s", "db", "whoami")
91+
assert.NoError(err)
92+
assert.Contains(out, "root")
93+
94+
out, err = exec.RunHostCommand(DdevBin, "exec", "-u", "0", "-s", "db", "whoami")
95+
assert.NoError(err)
96+
assert.Contains(out, "root")
97+
98+
out, err = exec.RunHostCommand(DdevBin, "exec", "--raw", "-u", "root", "--", "whoami")
99+
assert.NoError(err)
100+
assert.Contains(out, "root")
101+
86102
// Test that an nonexistent working directory generates an error
87103
out, err = exec.RunHostCommand(DdevBin, "exec", "-d", "/does/not/exist", "pwd")
88104
assert.Error(err)

cmd/ddev/cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
var (
2121
updateInterval = time.Hour * 4 // Four-hour interval between updates
2222
serviceType string
23+
serviceUser string
2324
updateDocURL = "https://docs.ddev.com/en/stable/users/install/ddev-upgrade/"
2425
instrumentationApp *ddevapp.DdevApp
2526
)

cmd/ddev/cmd/ssh.go

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

77
"github.com/ddev/ddev/pkg/ddevapp"
8-
"github.com/ddev/ddev/pkg/nodeps"
98
"github.com/ddev/ddev/pkg/util"
109
"github.com/spf13/cobra"
1110
)
@@ -21,6 +20,7 @@ var DdevSSHCmd = &cobra.Command{
2120
Long: `Starts a shell session in the container for a service. Uses web service by default. To start a shell session for another service, run "ddev ssh --service <service>`,
2221
Example: `ddev ssh
2322
ddev ssh -s db
23+
ddev ssh -s db -u root
2424
ddev ssh <projectname>
2525
ddev ssh -d /var/www/html`,
2626
Args: cobra.MaximumNArgs(1),
@@ -36,14 +36,13 @@ ddev ssh -d /var/www/html`,
3636

3737
// Use Bash for our containers, sh for 3rd-party containers
3838
// that may not have Bash.
39-
shell := "bash"
40-
if !nodeps.ArrayContainsString([]string{"web", "db", "solr"}, serviceType) {
41-
shell = "sh"
42-
}
39+
shell := app.GetXDdevExtension(serviceType).SSHShell
40+
4341
err = app.ExecWithTty(&ddevapp.ExecOpts{
4442
Service: serviceType,
4543
Cmd: shell + " -l",
4644
Dir: sshDirArg,
45+
User: serviceUser,
4746
})
4847
if err != nil {
4948
if exiterr, ok := err.(*exec.ExitError); ok {
@@ -58,5 +57,6 @@ func init() {
5857
DdevSSHCmd.Flags().StringVarP(&serviceType, "service", "s", "web", "Defines the service to connect to. [e.g. web, db]")
5958
_ = DdevSSHCmd.RegisterFlagCompletionFunc("service", ddevapp.GetServiceNamesFunc(true))
6059
DdevSSHCmd.Flags().StringVarP(&sshDirArg, "dir", "d", "", "Defines the destination directory within the container")
60+
DdevSSHCmd.Flags().StringVarP(&serviceUser, "user", "u", "", "Defines the user to use within the container")
6161
RootCmd.AddCommand(DdevSSHCmd)
6262
}

docs/content/users/configuration/hooks.md

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,13 @@ DDEV currently supports these tasks:
4242

4343
### `exec`: Execute a shell command in a container (defaults to web container)
4444

45-
Value: string providing the command to run. Commands requiring user interaction are not supported. You can also add a “service” key to the command, specifying to run it on the `db` container or any other container you use.
45+
Value: string providing the command to run. Commands requiring user interaction are not supported.
46+
47+
**Optional keys:**
48+
49+
* `service`: Specify which container to run the command in (defaults to `web`)
50+
* `user`: Specify which user to run the command as (username or UID, defaults to container's default user)
51+
* `exec_raw`: Array of command arguments for direct execution without shell interpretation (alternative to string command)
4652

4753
Example: _Use Drush to rebuild all caches and get a user login link after database import_.
4854

@@ -76,15 +82,24 @@ hooks:
7682
pre-import-db:
7783
- exec: mysql -uroot -proot -e "CREATE DATABASE IF NOT EXISTS some_new_database;"
7884
service: db
85+
```
86+
87+
Example: _Execute a command as root user in the `db` container_.
7988

89+
```yaml
90+
hooks:
91+
post-start:
92+
- exec: ls -la /root
93+
service: db
94+
user: root
8095
```
8196

8297
Example: _Add the common `ll` alias into the `web` container’s `.bashrc` file_.
8398

8499
```yaml
85100
hooks:
86101
post-start:
87-
- exec: sudo echo alias ll=\"ls -lhA\" >> ~/.bashrc
102+
- exec: sudo echo alias ll=\"ls -lhA\" >> ~/.bashrc
88103
```
89104

90105
!!!tip
@@ -95,8 +110,8 @@ Advanced usages may require running commands directly with explicit arguments. T
95110
```yaml
96111
hooks:
97112
post-start:
98-
- exec:
99-
exec_raw: [ls, -lR, /var/www/html]
113+
- exec:
114+
exec_raw: [ls, -lR, /var/www/html]
100115
```
101116

102117
### `exec-host`: Execute a shell command on the host system
@@ -113,6 +128,10 @@ hooks:
113128

114129
Value: string providing the Composer command to run.
115130

131+
**Optional keys:**
132+
133+
* `exec_raw`: Array of Composer command arguments (alternative to string command)
134+
116135
Example:
117136

118137
```yaml
@@ -121,6 +140,15 @@ hooks:
121140
- composer: config discard-changes true
122141
```
123142

143+
Example with `exec_raw`:
144+
145+
```yaml
146+
hooks:
147+
post-start:
148+
- composer:
149+
exec_raw: [install, --no-dev]
150+
```
151+
124152
## WordPress Example
125153

126154
```yaml
@@ -174,6 +202,6 @@ hooks:
174202

175203
```yaml
176204
hooks:
177-
post-start:
178-
- composer: install
205+
post-start:
206+
- composer: install
179207
```

docs/content/users/extend/custom-docker-services.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,11 @@ volumes:
118118
- "../:/var/www/html:cached"
119119
```
120120

121-
### Customizing ddev describe Output
121+
### Customizing `ddev describe` Output
122122

123-
You can add custom descriptions that appear in `ddev describe` output using the `x-ddev` extension field. This is helpful for providing information about credentials, URLs, or usage instructions for your custom services.
123+
You can use the `x-ddev` extension field in your `.ddev/docker-compose.*.yaml` configuration to customize the output of [`ddev describe`](../usage/commands.md#describe).
124+
125+
This feature is useful for showing credentials, URLs, or usage notes for custom services.
124126

125127
```yaml
126128
services:
@@ -131,11 +133,12 @@ services:
131133
com.ddev.site-name: ${DDEV_SITENAME}
132134
com.ddev.approot: ${DDEV_APPROOT}
133135
restart: "no"
134-
ports:
136+
expose:
135137
- "15672"
136138
environment:
137139
- VIRTUAL_HOST=${DDEV_HOSTNAME}
138140
- HTTP_EXPOSE=15672:15672
141+
- HTTPS_EXPOSE=15673:15672
139142
- RABBITMQ_DEFAULT_USER=rabbitmq
140143
- RABBITMQ_DEFAULT_PASS=rabbitmq
141144
x-ddev:
@@ -147,7 +150,11 @@ services:
147150
describe-url-port: "extra help here"
148151
```
149152

150-
The `x-ddev.describe-url-port` value appears in the URL/Port column when running `ddev describe` and the `x-ddev-describe-info` value appears in the `info` column, making it easy for team members to see important service information without digging through documentation and configuration files.
153+
- `x-ddev.describe-url-port`: Appears in the `URL/PORT` column when running [`ddev describe`](../usage/commands.md#describe).
154+
- `x-ddev.describe-info`: Appears in the `INFO` column, making it easy for team members to view relevant service details without checking config files.
155+
156+
!!!tip
157+
See related `x-ddev.ssh-shell` configuration for [Changing `ddev ssh` Shell](../extend/in-container-configuration.md#changing-ddev-ssh-shell).
151158

152159
## Advanced Service Examples
153160

docs/content/users/extend/in-container-configuration.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Custom shell configuration (Bash or your preferred shell), your usual Git configuration, a Composer `auth.json` and more can be achieved within your containers.
44

5+
## Using `homeadditions` to Customize In-Container Home Directory
6+
57
Place all your dotfiles in your global`~/.ddev/homeadditions` or your project’s `.ddev/homeadditions` directory and DDEV will use these in your project’s `web` containers.
68

79
!!!tip "Ignore `.ddev/.homeadditions`!"
@@ -31,6 +33,36 @@ Usage examples:
3133
alias ll="ls -lhA"
3234
```
3335
36+
## Changing `ddev ssh` Shell
37+
38+
You can define a default shell for [`ddev ssh`](../usage/commands.md#ssh) using the `x-ddev` extension field in your `.ddev/docker-compose.*.yaml` configuration.
39+
40+
Use the `x-ddev.ssh-shell` key and make sure that shell (such as `zsh` or `bash`) is included in the container image so `ddev ssh` work correctly. The selected shell also appears in the [`ddev describe`](../usage/commands.md#describe) output (if it's not the default one).
41+
42+
Changing the default shell to `zsh` in the `web` and `db` containers:
43+
44+
```yaml
45+
# .ddev/config.yaml
46+
webimage_extra_packages: [zsh]
47+
dbimage_extra_packages: [zsh]
48+
```
49+
50+
```yaml
51+
# .ddev/docker-compose.ssh-shell.yaml
52+
services:
53+
web:
54+
x-ddev:
55+
ssh-shell: zsh
56+
db:
57+
x-ddev:
58+
ssh-shell: zsh
59+
```
60+
61+
To change the shell for a custom service, add the `x-ddev.ssh-shell` field to that service's configuration and ensure the desired shell is [installed in the image](./customizing-images.md).
62+
63+
!!!tip
64+
See related `x-ddev.describe-*` configuration for [Customizing `ddev describe` Output](../extend/custom-docker-services.md#customizing-ddev-describe-output).
65+
3466
## Using `NO_COLOR` Inside Containers
3567

3668
To set the `NO_COLOR` variable in all containers across all projects, define the `NO_COLOR` environment variable in your shell configuration file (e.g., `~/.bashrc` or `~/.zshrc`), outside of DDEV, for example:

docs/content/users/usage/commands.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -598,14 +598,15 @@ Flags:
598598
* `--raw`: Use raw exec (do not interpret with Bash inside container). (default `true`)
599599
* `--service`, `-s`: Define the service to connect to. (e.g. `web`, `db`) (default `"web"`)
600600
* `--quiet`, `-q`: Suppress detailed error message.
601+
* `--user`, `-u`: Defines the user to run shell as.
601602

602603
Example:
603604

604605
```shell
605-
# List the web containers docroot contents
606+
# List the web container's docroot contents
606607
ddev exec ls /var/www/html
607608
608-
# List the web containers vendor directory contents
609+
# List the web container's vendor directory contents
609610
ddev exec --dir /var/www/html/vendor ls
610611
611612
# Output a long, recursive list of the files in the web container
@@ -614,6 +615,9 @@ ddev exec --raw -- ls -lR
614615
# Suppress detailed error message:
615616
# "Failed to execute command `exit 1`: exit status 1"
616617
ddev exec -q "exit 1"
618+
619+
# List the db container's /root directory contents as root user
620+
ddev exec -s db -u root ls -la /root
617621
```
618622

619623
## `export-db`
@@ -1346,6 +1350,7 @@ Flags:
13461350
13471351
* `--dir`, `-d`: Defines the destination directory within the container.
13481352
* `--service`, `-s`: Defines the service to connect to. (default `"web"`)
1353+
* `--user`, `-u`: Defines the user to run shell as.
13491354
13501355
Example:
13511356
@@ -1356,6 +1361,9 @@ ddev ssh
13561361
# SSH into the current project’s database container
13571362
ddev ssh -s db
13581363
1364+
# SSH into the current project’s database container as root user
1365+
ddev ssh -s db -u root
1366+
13591367
# SSH into the web container for my-project
13601368
ddev ssh my-project
13611369

0 commit comments

Comments
 (0)