WIP: ssh remoting: Add upload_binary field to SshConnections (#19748)

This removes the old `remote_server { "download_binary_on_host": bool }`
field and replaces it with a `upload_binary: bool` on every
`ssh_connection`.


@ConradIrwin it compiles, it connects, but I haven't tested it really
yet

Release Notes:

- N/A

---------

Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Thorsten Ball 2024-10-26 01:32:54 +02:00 committed by GitHub
parent 1acebb3c47
commit fc8a72cdd8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 204 additions and 235 deletions

View file

@ -11,7 +11,7 @@ use ui::{
};
use workspace::{notifications::DetachAndPromptErr, ModalView, OpenOptions, Workspace};
use crate::{open_ssh_project, SshSettings};
use crate::open_ssh_project;
enum Host {
RemoteProject,
@ -102,16 +102,6 @@ impl DisconnectedOverlay {
let paths = ssh_project.paths.iter().map(PathBuf::from).collect();
cx.spawn(move |_, mut cx| async move {
let nickname = cx
.update(|cx| {
SshSettings::get_global(cx).nickname_for(
&connection_options.host,
connection_options.port,
&connection_options.username,
)
})
.ok()
.flatten();
open_ssh_project(
connection_options,
paths,
@ -120,7 +110,6 @@ impl DisconnectedOverlay {
replace_window: Some(window),
..Default::default()
},
nickname,
&mut cx,
)
.await?;

View file

@ -1,7 +1,6 @@
pub mod disconnected_overlay;
mod remote_servers;
mod ssh_connections;
use remote::SshConnectionOptions;
pub use ssh_connections::open_ssh_project;
use disconnected_overlay::DisconnectedOverlay;
@ -331,23 +330,12 @@ impl PickerDelegate for RecentProjectsDelegate {
..Default::default()
};
let args = SshSettings::get_global(cx).args_for(
&ssh_project.host,
ssh_project.port,
&ssh_project.user,
);
let nickname = SshSettings::get_global(cx).nickname_for(
&ssh_project.host,
ssh_project.port,
&ssh_project.user,
);
let connection_options = SshConnectionOptions {
host: ssh_project.host.clone(),
username: ssh_project.user.clone(),
port: ssh_project.port,
password: None,
args,
};
let connection_options = SshSettings::get_global(cx)
.connection_options_for(
ssh_project.host.clone(),
ssh_project.port,
ssh_project.user.clone(),
);
let paths = ssh_project.paths.iter().map(PathBuf::from).collect();
@ -357,7 +345,6 @@ impl PickerDelegate for RecentProjectsDelegate {
paths,
app_state,
open_options,
nickname,
&mut cx,
)
.await

View file

@ -197,11 +197,7 @@ impl ProjectPicker {
picker
});
let connection_string = connection.connection_string().into();
let nickname = SshSettings::get_global(cx).nickname_for(
&connection.host,
connection.port,
&connection.username,
);
let nickname = connection.nickname.clone().map(|nick| nick.into());
let _path_task = cx
.spawn({
let workspace = workspace.clone();
@ -414,7 +410,7 @@ impl RemoteServerProjects {
return;
}
};
let ssh_prompt = cx.new_view(|cx| SshPrompt::new(&connection_options, None, cx));
let ssh_prompt = cx.new_view(|cx| SshPrompt::new(&connection_options, cx));
let connection = connect_over_ssh(
connection_options.remote_server_identifier(),
@ -491,12 +487,11 @@ impl RemoteServerProjects {
return;
};
let nickname = ssh_connection.nickname.clone();
let connection_options = ssh_connection.into();
workspace.update(cx, |_, cx| {
cx.defer(move |workspace, cx| {
workspace.toggle_modal(cx, |cx| {
SshConnectionModal::new(&connection_options, Vec::new(), nickname, cx)
SshConnectionModal::new(&connection_options, Vec::new(), cx)
});
let prompt = workspace
.active_modal::<SshConnectionModal>(cx)
@ -584,9 +579,7 @@ impl RemoteServerProjects {
self.create_ssh_server(state.address_editor.clone(), cx);
}
Mode::EditNickname(state) => {
let text = Some(state.editor.read(cx).text(cx))
.filter(|text| !text.is_empty())
.map(SharedString::from);
let text = Some(state.editor.read(cx).text(cx)).filter(|text| !text.is_empty());
let index = state.index;
self.update_settings_file(cx, move |setting, _| {
if let Some(connections) = setting.ssh_connections.as_mut() {
@ -633,7 +626,7 @@ impl RemoteServerProjects {
) -> impl IntoElement {
let (main_label, aux_label) = if let Some(nickname) = ssh_connection.nickname.clone() {
let aux_label = SharedString::from(format!("({})", ssh_connection.host));
(nickname, Some(aux_label))
(nickname.into(), Some(aux_label))
} else {
(ssh_connection.host.clone(), None)
};
@ -746,13 +739,11 @@ impl RemoteServerProjects {
let project = project.clone();
let server = server.clone();
cx.spawn(|remote_server_projects, mut cx| async move {
let nickname = server.nickname.clone();
let result = open_ssh_project(
server.into(),
project.paths.into_iter().map(PathBuf::from).collect(),
app_state,
OpenOptions::default(),
nickname,
&mut cx,
)
.await;
@ -861,6 +852,7 @@ impl RemoteServerProjects {
projects: vec![],
nickname: None,
args: connection_options.args.unwrap_or_default(),
upload_binary_over_ssh: None,
})
});
}
@ -953,7 +945,7 @@ impl RemoteServerProjects {
SshConnectionHeader {
connection_string: connection_string.clone(),
paths: Default::default(),
nickname: connection.nickname.clone(),
nickname: connection.nickname.clone().map(|s| s.into()),
}
.render(cx),
)
@ -1135,13 +1127,14 @@ impl RemoteServerProjects {
};
let connection_string = connection.host.clone();
let nickname = connection.nickname.clone().map(|s| s.into());
v_flex()
.child(
SshConnectionHeader {
connection_string,
paths: Default::default(),
nickname: connection.nickname.clone(),
nickname,
}
.render(cx),
)

View file

@ -26,15 +26,9 @@ use ui::{
};
use workspace::{AppState, ModalView, Workspace};
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct RemoteServerSettings {
pub download_on_host: Option<bool>,
}
#[derive(Deserialize)]
pub struct SshSettings {
pub ssh_connections: Option<Vec<SshConnection>>,
pub remote_server: Option<RemoteServerSettings>,
}
impl SshSettings {
@ -42,39 +36,31 @@ impl SshSettings {
self.ssh_connections.clone().into_iter().flatten()
}
pub fn args_for(
pub fn connection_options_for(
&self,
host: &str,
host: String,
port: Option<u16>,
user: &Option<String>,
) -> Option<Vec<String>> {
self.ssh_connections()
.filter_map(|conn| {
if conn.host == host && &conn.username == user && conn.port == port {
Some(conn.args)
} else {
None
}
})
.next()
}
pub fn nickname_for(
&self,
host: &str,
port: Option<u16>,
user: &Option<String>,
) -> Option<SharedString> {
self.ssh_connections()
.filter_map(|conn| {
if conn.host == host && &conn.username == user && conn.port == port {
Some(conn.nickname)
} else {
None
}
})
.next()
.flatten()
username: Option<String>,
) -> SshConnectionOptions {
for conn in self.ssh_connections() {
if conn.host == host && conn.username == username && conn.port == port {
return SshConnectionOptions {
nickname: conn.nickname,
upload_binary_over_ssh: conn.upload_binary_over_ssh.unwrap_or_default(),
args: Some(conn.args),
host,
port,
username,
password: None,
};
}
}
SshConnectionOptions {
host,
port,
username,
..Default::default()
}
}
}
@ -85,13 +71,20 @@ pub struct SshConnection {
pub username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
pub projects: Vec<SshProject>,
/// Name to use for this server in UI.
#[serde(skip_serializing_if = "Option::is_none")]
pub nickname: Option<SharedString>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub projects: Vec<SshProject>,
/// Name to use for this server in UI.
#[serde(skip_serializing_if = "Option::is_none")]
pub nickname: Option<String>,
// By default Zed will download the binary to the host directly.
// If this is set to true, Zed will download the binary to your local machine,
// and then upload it over the SSH connection. Useful if your SSH server has
// limited outbound internet access.
#[serde(skip_serializing_if = "Option::is_none")]
pub upload_binary_over_ssh: Option<bool>,
}
impl From<SshConnection> for SshConnectionOptions {
@ -102,6 +95,8 @@ impl From<SshConnection> for SshConnectionOptions {
port: val.port,
password: None,
args: Some(val.args),
nickname: val.nickname,
upload_binary_over_ssh: val.upload_binary_over_ssh.unwrap_or_default(),
}
}
}
@ -114,7 +109,6 @@ pub struct SshProject {
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct RemoteSettingsContent {
pub ssh_connections: Option<Vec<SshConnection>>,
pub remote_server: Option<RemoteServerSettings>,
}
impl Settings for SshSettings {
@ -153,10 +147,10 @@ pub struct SshConnectionModal {
impl SshPrompt {
pub(crate) fn new(
connection_options: &SshConnectionOptions,
nickname: Option<SharedString>,
cx: &mut ViewContext<Self>,
) -> Self {
let connection_string = connection_options.connection_string().into();
let nickname = connection_options.nickname.clone().map(|s| s.into());
Self {
connection_string,
@ -276,11 +270,10 @@ impl SshConnectionModal {
pub(crate) fn new(
connection_options: &SshConnectionOptions,
paths: Vec<PathBuf>,
nickname: Option<SharedString>,
cx: &mut ViewContext<Self>,
) -> Self {
Self {
prompt: cx.new_view(|cx| SshPrompt::new(connection_options, nickname, cx)),
prompt: cx.new_view(|cx| SshPrompt::new(connection_options, cx)),
finished: false,
paths,
}
@ -451,13 +444,17 @@ impl remote::SshClientDelegate for SshClientDelegate {
fn get_server_binary(
&self,
platform: SshPlatform,
upload_binary_over_ssh: bool,
cx: &mut AsyncAppContext,
) -> oneshot::Receiver<Result<(ServerBinary, SemanticVersion)>> {
let (tx, rx) = oneshot::channel();
let this = self.clone();
cx.spawn(|mut cx| async move {
tx.send(this.get_server_binary_impl(platform, &mut cx).await)
.ok();
tx.send(
this.get_server_binary_impl(platform, upload_binary_over_ssh, &mut cx)
.await,
)
.ok();
})
.detach();
rx
@ -492,19 +489,14 @@ impl SshClientDelegate {
async fn get_server_binary_impl(
&self,
platform: SshPlatform,
upload_binary_via_ssh: bool,
cx: &mut AsyncAppContext,
) -> Result<(ServerBinary, SemanticVersion)> {
let (version, release_channel, download_binary_on_host) = cx.update(|cx| {
let (version, release_channel) = cx.update(|cx| {
let version = AppVersion::global(cx);
let channel = ReleaseChannel::global(cx);
let ssh_settings = SshSettings::get_global(cx);
let download_binary_on_host = ssh_settings
.remote_server
.as_ref()
.and_then(|server| server.download_on_host)
.unwrap_or(false);
(version, channel, download_binary_on_host)
(version, channel)
})?;
// In dev mode, build the remote server binary from source
@ -529,33 +521,7 @@ impl SshClientDelegate {
cx,
);
if download_binary_on_host {
let (request_url, request_body) = AutoUpdater::get_remote_server_release_url(
platform.os,
platform.arch,
release_channel,
current_version,
cx,
)
.await
.map_err(|e| {
anyhow!(
"Failed to get remote server binary download url (version: {}, os: {}, arch: {}): {}",
version,
platform.os,
platform.arch,
e
)
})?;
Ok((
ServerBinary::ReleaseUrl {
url: request_url,
body: request_body,
},
version,
))
} else {
if upload_binary_via_ssh {
let binary_path = AutoUpdater::download_remote_server_release(
platform.os,
platform.arch,
@ -575,6 +541,32 @@ impl SshClientDelegate {
})?;
Ok((ServerBinary::LocalBinary(binary_path), version))
} else {
let (request_url, request_body) = AutoUpdater::get_remote_server_release_url(
platform.os,
platform.arch,
release_channel,
current_version,
cx,
)
.await
.map_err(|e| {
anyhow!(
"Failed to get remote server binary download url (version: {}, os: {}, arch: {}): {}",
version,
platform.os,
platform.arch,
e
)
})?;
Ok((
ServerBinary::ReleaseUrl {
url: request_url,
body: request_body,
},
version,
))
}
}
@ -715,7 +707,6 @@ pub async fn open_ssh_project(
paths: Vec<PathBuf>,
app_state: Arc<AppState>,
open_options: workspace::OpenOptions,
nickname: Option<SharedString>,
cx: &mut AsyncAppContext,
) -> Result<()> {
let window = if let Some(window) = open_options.replace_window {
@ -740,12 +731,11 @@ pub async fn open_ssh_project(
let (cancel_tx, cancel_rx) = oneshot::channel();
let delegate = window.update(cx, {
let connection_options = connection_options.clone();
let nickname = nickname.clone();
let paths = paths.clone();
move |workspace, cx| {
cx.activate_window();
workspace.toggle_modal(cx, |cx| {
SshConnectionModal::new(&connection_options, paths, nickname.clone(), cx)
SshConnectionModal::new(&connection_options, paths, cx)
});
let ui = workspace