Use remote user from devcontainer CLI and respect shell in passwd (#48230)
Closes #46252 Uses the `remoteUser` property returned from the devcontainer CLI so that settings are respected. Additionally, checks for a default shell in `passwd` if `$SHELL` is not set. Release Notes: - Fixed remote_user and shell inconsistencies from within dev containers
This commit is contained in:
1 parent
d0f53a8cde
commit
6b4b4a0e85
5 files changed
+114
-52
No files matched your search
@@ -19,7 +19,7 @@ use crate::{DevContainerFeature, DevContainerSettings, DevContainerTemplate};
|
||||
struct DevContainerUp {
|
||||
_outcome: String,
|
||||
container_id: String,
|
||||
_remote_user: String,
|
||||
remote_user: String,
|
||||
remote_workspace_folder: String,
|
||||
}
|
||||
|
||||
@@ -156,6 +156,7 @@ pub async fn start_dev_container(
|
||||
Ok(DevContainerUp {
|
||||
container_id,
|
||||
remote_workspace_folder,
|
||||
remote_user,
|
||||
..
|
||||
}) => {
|
||||
let project_name = match devcontainer_read_configuration(
|
||||
@@ -180,6 +181,7 @@ pub async fn start_dev_container(
|
||||
name: project_name,
|
||||
container_id: container_id,
|
||||
use_podman,
|
||||
remote_user,
|
||||
};
|
||||
|
||||
Ok((connection, remote_workspace_folder))
|
||||
@@ -563,7 +565,7 @@ mod tests {
|
||||
up.container_id,
|
||||
"826abcac45afd412abff083ab30793daff2f3c8ce2c831df728baf39933cb37a"
|
||||
);
|
||||
assert_eq!(up._remote_user, "vscode");
|
||||
assert_eq!(up.remote_user, "vscode");
|
||||
assert_eq!(up.remote_workspace_folder, "/workspaces/zed");
|
||||
|
||||
let json_in_plaintext = r#"[2026-01-22T16:19:08.802Z] @devcontainers/cli 0.80.1. Node.js v22.21.1. darwin 24.6.0 arm64.
|
||||
@@ -574,7 +576,7 @@ mod tests {
|
||||
up.container_id,
|
||||
"826abcac45afd412abff083ab30793daff2f3c8ce2c831df728baf39933cb37a"
|
||||
);
|
||||
assert_eq!(up._remote_user, "vscode");
|
||||
assert_eq!(up.remote_user, "vscode");
|
||||
assert_eq!(up.remote_workspace_folder, "/workspaces/zed");
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,7 @@ impl From<Connection> for RemoteConnectionOptions {
|
||||
Connection::DevContainer(conn) => {
|
||||
RemoteConnectionOptions::Docker(DockerConnectionOptions {
|
||||
name: conn.name,
|
||||
remote_user: conn.remote_user,
|
||||
container_id: conn.container_id,
|
||||
upload_binary_over_docker_exec: false,
|
||||
use_podman: conn.use_podman,
|
||||
|
||||
@@ -33,6 +33,7 @@ use crate::{
|
||||
pub struct DockerConnectionOptions {
|
||||
pub name: String,
|
||||
pub container_id: String,
|
||||
pub remote_user: String,
|
||||
pub upload_binary_over_docker_exec: bool,
|
||||
pub use_podman: bool,
|
||||
}
|
||||
@@ -115,16 +116,39 @@ impl DockerExecConnection {
|
||||
{
|
||||
Ok(shell) => match shell.trim() {
|
||||
"" => {
|
||||
log::error!("$SHELL is not set, falling back to {default_shell}");
|
||||
default_shell.to_owned()
|
||||
log::info!("$SHELL is not set, checking passwd for user");
|
||||
}
|
||||
shell => {
|
||||
return shell.to_owned();
|
||||
}
|
||||
shell => shell.to_owned(),
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to get shell: {e}");
|
||||
default_shell.to_owned()
|
||||
log::error!("Failed to get $SHELL: {e}. Checking passwd for user");
|
||||
}
|
||||
}
|
||||
|
||||
match self
|
||||
.run_docker_exec(
|
||||
"sh",
|
||||
None,
|
||||
&Default::default(),
|
||||
&["-c", "getent passwd \"$(id -un)\" | cut -d: -f7"],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(shell) => match shell.trim() {
|
||||
"" => {
|
||||
log::info!("No shell found in passwd, falling back to {default_shell}");
|
||||
}
|
||||
shell => {
|
||||
return shell.to_owned();
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::info!("Error getting shell from passwd: {e}. Falling back to {default_shell}");
|
||||
}
|
||||
}
|
||||
default_shell.to_owned()
|
||||
}
|
||||
|
||||
async fn check_remote_platform(&self) -> Result<RemotePlatform> {
|
||||
@@ -370,6 +394,55 @@ impl DockerExecConnection {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn upload_and_chown(
|
||||
docker_cli: String,
|
||||
connection_options: DockerConnectionOptions,
|
||||
src_path: String,
|
||||
dst_path: String,
|
||||
) -> Result<()> {
|
||||
let mut command = util::command::new_smol_command(&docker_cli);
|
||||
command.arg("cp");
|
||||
command.arg("-a");
|
||||
command.arg(&src_path);
|
||||
command.arg(format!("{}:{}", connection_options.container_id, dst_path));
|
||||
|
||||
let output = command.output().await?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
log::debug!("failed to upload via docker cp {src_path} -> {dst_path}: {stderr}",);
|
||||
anyhow::bail!(
|
||||
"failed to upload via docker cp {} -> {}: {}",
|
||||
src_path,
|
||||
dst_path,
|
||||
stderr,
|
||||
);
|
||||
}
|
||||
|
||||
let mut chown_command = util::command::new_smol_command(&docker_cli);
|
||||
chown_command.arg("exec");
|
||||
chown_command.arg(connection_options.container_id);
|
||||
chown_command.arg("chown");
|
||||
chown_command.arg(format!(
|
||||
"{}:{}",
|
||||
connection_options.remote_user, connection_options.remote_user,
|
||||
));
|
||||
chown_command.arg(&dst_path);
|
||||
|
||||
let output = chown_command.output().await?;
|
||||
|
||||
if output.status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
log::debug!("failed to change ownership for via chown: {stderr}",);
|
||||
anyhow::bail!(
|
||||
"failed to change ownership for zed_remote_server via chown: {}",
|
||||
stderr,
|
||||
);
|
||||
}
|
||||
|
||||
async fn upload_file(
|
||||
&self,
|
||||
src_path: &Path,
|
||||
@@ -380,32 +453,15 @@ impl DockerExecConnection {
|
||||
|
||||
let src_path_display = src_path.display().to_string();
|
||||
let dest_path_str = dest_path.display(self.path_style());
|
||||
let full_server_path = format!("{}/{}", remote_dir_for_server, dest_path_str);
|
||||
|
||||
let mut command = util::command::new_smol_command(self.docker_cli());
|
||||
command.arg("cp");
|
||||
command.arg("-a");
|
||||
command.arg(&src_path_display);
|
||||
command.arg(format!(
|
||||
"{}:{}/{}",
|
||||
&self.connection_options.container_id, remote_dir_for_server, dest_path_str
|
||||
));
|
||||
|
||||
let output = command.output().await?;
|
||||
|
||||
if output.status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
log::debug!(
|
||||
"failed to upload file via docker cp {src_path_display} -> {dest_path_str}: {stderr}",
|
||||
);
|
||||
anyhow::bail!(
|
||||
"failed to upload file via docker cp {} -> {}: {}",
|
||||
Self::upload_and_chown(
|
||||
self.docker_cli().to_string(),
|
||||
self.connection_options.clone(),
|
||||
src_path_display,
|
||||
dest_path_str,
|
||||
stderr,
|
||||
);
|
||||
full_server_path,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn run_docker_command(
|
||||
@@ -440,6 +496,9 @@ impl DockerExecConnection {
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
args.push("-u".to_string());
|
||||
args.push(self.connection_options.remote_user.clone());
|
||||
|
||||
for (k, v) in env.iter() {
|
||||
args.push("-e".to_string());
|
||||
let env_declaration = format!("{}={}", k, v);
|
||||
@@ -581,6 +640,8 @@ impl RemoteConnection for DockerExecConnection {
|
||||
}
|
||||
|
||||
docker_args.extend([
|
||||
"-u".to_string(),
|
||||
self.connection_options.remote_user.to_string(),
|
||||
"-w".to_string(),
|
||||
self.remote_dir_for_server.clone(),
|
||||
"-i".to_string(),
|
||||
@@ -641,24 +702,14 @@ impl RemoteConnection for DockerExecConnection {
|
||||
let dest_path_str = dest_path.to_string();
|
||||
let src_path_display = src_path.display().to_string();
|
||||
|
||||
let mut command = util::command::new_smol_command(self.docker_cli());
|
||||
command.arg("cp");
|
||||
command.arg("-a"); // Archive mode is required to assign the file ownership to the default docker exec user
|
||||
command.arg(src_path_display);
|
||||
command.arg(format!(
|
||||
"{}:{}",
|
||||
self.connection_options.container_id, dest_path_str
|
||||
));
|
||||
let upload_task = Self::upload_and_chown(
|
||||
self.docker_cli().to_string(),
|
||||
self.connection_options.clone(),
|
||||
src_path_display,
|
||||
dest_path_str,
|
||||
);
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let output = command.output().await?;
|
||||
|
||||
if output.status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Failed to upload directory"))
|
||||
}
|
||||
})
|
||||
cx.background_spawn(upload_task)
|
||||
}
|
||||
|
||||
async fn kill(&self) -> Result<()> {
|
||||
@@ -706,7 +757,11 @@ impl RemoteConnection for DockerExecConnection {
|
||||
inner_program.push("-l".to_string());
|
||||
};
|
||||
|
||||
let mut docker_args = vec!["exec".to_string()];
|
||||
let mut docker_args = vec![
|
||||
"exec".to_string(),
|
||||
"-u".to_string(),
|
||||
self.connection_options.remote_user.clone(),
|
||||
];
|
||||
|
||||
if let Some(parsed_working_dir) = parsed_working_dir {
|
||||
docker_args.push("-w".to_string());
|
||||
|
||||
@@ -988,6 +988,7 @@ pub struct RemoteSettingsContent {
|
||||
)]
|
||||
pub struct DevContainerConnection {
|
||||
pub name: String,
|
||||
pub remote_user: String,
|
||||
pub container_id: String,
|
||||
pub use_podman: bool,
|
||||
}
|
||||
|
||||
@@ -1425,7 +1425,7 @@ impl WorkspaceDb {
|
||||
options: RemoteConnectionOptions,
|
||||
) -> Result<RemoteConnectionId> {
|
||||
let kind;
|
||||
let mut user = None;
|
||||
let user: Option<String>;
|
||||
let mut host = None;
|
||||
let mut port = None;
|
||||
let mut distro = None;
|
||||
@@ -1448,12 +1448,14 @@ impl WorkspaceDb {
|
||||
kind = RemoteConnectionKind::Docker;
|
||||
container_id = Some(options.container_id);
|
||||
name = Some(options.name);
|
||||
use_podman = Some(options.use_podman)
|
||||
use_podman = Some(options.use_podman);
|
||||
user = Some(options.remote_user);
|
||||
}
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
RemoteConnectionOptions::Mock(options) => {
|
||||
kind = RemoteConnectionKind::Ssh;
|
||||
host = Some(format!("mock-{}", options.id));
|
||||
user = Some(format!("mock-user-{}", options.id));
|
||||
}
|
||||
}
|
||||
Self::get_or_create_remote_connection_query(
|
||||
@@ -1691,6 +1693,7 @@ impl WorkspaceDb {
|
||||
Some(RemoteConnectionOptions::Docker(DockerConnectionOptions {
|
||||
container_id: container_id?,
|
||||
name: name?,
|
||||
remote_user: user?,
|
||||
upload_binary_over_docker_exec: false,
|
||||
use_podman: use_podman?,
|
||||
}))
|
||||
|
||||
Reference in new issue
Block a user