cli: when attaching, always print the connection link (#178445)

Fixes #178090
This commit is contained in:
Connor Peet
2023-03-27 12:52:50 -07:00
committed by GitHub
parent b51e2f3613
commit dfbea0f578
7 changed files with 105 additions and 55 deletions

View File

@@ -73,6 +73,8 @@ pub fn get_default_user_agent() -> String {
)
}
const NO_COLOR_ENV: &str = "NO_COLOR";
lazy_static! {
pub static ref TUNNEL_SERVICE_USER_AGENT: String =
match std::env::var(TUNNEL_SERVICE_USER_AGENT_ENV_VAR) {
@@ -101,5 +103,11 @@ lazy_static! {
option_env!("VSCODE_CLI_SERVER_NAME_MAP").and_then(|s| serde_json::from_str(s).unwrap());
/// Whether i/o interactions are allowed in the current CLI.
pub static ref IS_INTERACTIVE_CLI: bool = atty::is(atty::Stream::Stdin) && std::env::var(NONINTERACTIVE_VAR).is_err();
pub static ref IS_A_TTY: bool = atty::is(atty::Stream::Stdin);
/// Whether i/o interactions are allowed in the current CLI.
pub static ref COLORS_ENABLED: bool = *IS_A_TTY && std::env::var(NO_COLOR_ENV).is_err();
/// Whether i/o interactions are allowed in the current CLI.
pub static ref IS_INTERACTIVE_CLI: bool = *IS_A_TTY && std::env::var(NONINTERACTIVE_VAR).is_err();
}

View File

@@ -10,13 +10,13 @@ use opentelemetry::{
};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::{env, path::Path, sync::Arc};
use std::{
io::Write,
sync::atomic::{AtomicU32, Ordering},
};
use std::{path::Path, sync::Arc};
const NO_COLOR_ENV: &str = "NO_COLOR";
use crate::constants::COLORS_ENABLED;
static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(0);
@@ -71,7 +71,7 @@ impl Level {
}
pub fn color_code(&self) -> Option<&str> {
if env::var(NO_COLOR_ENV).is_ok() || !atty::is(atty::Stream::Stdout) {
if !*COLORS_ENABLED {
return None;
}

View File

@@ -4,7 +4,9 @@
*--------------------------------------------------------------------------------------------*/
use super::paths::{InstalledServer, LastUsedServers, ServerPaths};
use crate::async_pipe::get_socket_name;
use crate::constants::{APPLICATION_NAME, QUALITYLESS_PRODUCT_NAME, QUALITYLESS_SERVER_NAME};
use crate::constants::{
APPLICATION_NAME, EDITOR_WEB_URL, QUALITYLESS_PRODUCT_NAME, QUALITYLESS_SERVER_NAME,
};
use crate::options::{Quality, TelemetryLevel};
use crate::state::LauncherPaths;
use crate::update_service::{
@@ -797,3 +799,40 @@ fn parse_port_from(text: &str) -> Option<u16> {
.and_then(|path| path.as_str().parse::<u16>().ok())
})
}
pub fn print_listening(log: &log::Logger, tunnel_name: &str) {
debug!(
log,
"{} is listening for incoming connections", QUALITYLESS_SERVER_NAME
);
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(""));
let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(""));
let dir = if home_dir == current_dir {
PathBuf::from("")
} else {
current_dir
};
let base_web_url = match EDITOR_WEB_URL {
Some(u) => u,
None => return,
};
let mut addr = url::Url::parse(base_web_url).unwrap();
{
let mut ps = addr.path_segments_mut().unwrap();
ps.push("tunnel");
ps.push(tunnel_name);
for segment in &dir {
let as_str = segment.to_string_lossy();
if !(as_str.len() == 1 && as_str.starts_with(std::path::MAIN_SEPARATOR)) {
ps.push(as_str.as_ref());
}
}
}
let message = &format!("\nOpen this link in your browser {}\n", addr);
log.result(message);
}

View File

@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
use crate::async_pipe::get_socket_rw_stream;
use crate::constants::{CONTROL_PORT, EDITOR_WEB_URL, QUALITYLESS_SERVER_NAME};
use crate::constants::CONTROL_PORT;
use crate::log;
use crate::rpc::{MaybeSync, RpcBuilder, RpcDispatcher, Serialization};
use crate::self_update::SelfUpdate;
@@ -25,8 +25,6 @@ use opentelemetry::trace::SpanKind;
use opentelemetry::KeyValue;
use std::collections::HashMap;
use std::env;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Instant;
@@ -117,43 +115,6 @@ pub struct ServerTermination {
pub tunnel: ActiveTunnel,
}
fn print_listening(log: &log::Logger, tunnel_name: &str) {
debug!(
log,
"{} is listening for incoming connections", QUALITYLESS_SERVER_NAME
);
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from(""));
let current_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from(""));
let dir = if home_dir == current_dir {
PathBuf::from("")
} else {
current_dir
};
let base_web_url = match EDITOR_WEB_URL {
Some(u) => u,
None => return,
};
let mut addr = url::Url::parse(base_web_url).unwrap();
{
let mut ps = addr.path_segments_mut().unwrap();
ps.push("tunnel");
ps.push(tunnel_name);
for segment in &dir {
let as_str = segment.to_string_lossy();
if !(as_str.len() == 1 && as_str.starts_with(std::path::MAIN_SEPARATOR)) {
ps.push(as_str.as_ref());
}
}
}
let message = &format!("\nOpen this link in your browser {}\n", addr);
log.result(message);
}
// Runs the launcher server. Exits on a ctrl+c or when requested by a user.
// Note that client connections may not be closed when this returns; use
// `close_all_clients()` on the ServerTermination to make this happen.
@@ -166,8 +127,6 @@ pub async fn serve(
mut shutdown_rx: Barrier<ShutdownSignal>,
) -> Result<ServerTermination, AnyError> {
let mut port = tunnel.add_port_direct(CONTROL_PORT).await?;
print_listening(log, &tunnel.name);
let mut forwarding = PortForwardingProcessor::new();
let (tx, mut rx) = mpsc::channel::<ServerSignal>(4);
let (exit_barrier, signal_exit) = new_barrier();

View File

@@ -184,6 +184,15 @@ pub mod singleton {
#[derive(Serialize, Deserialize)]
pub struct Status {
pub ok: bool,
pub tunnel: TunnelState,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct LogReplayFinished {}
#[derive(Deserialize, Serialize, Debug)]
pub enum TunnelState {
Disconnected,
Connected { name: String },
}
}

View File

@@ -17,9 +17,10 @@ use tokio::sync::mpsc;
use crate::{
async_pipe::{socket_stream_split, AsyncPipe},
constants::IS_INTERACTIVE_CLI,
json_rpc::{new_json_rpc, start_json_rpc},
json_rpc::{new_json_rpc, start_json_rpc, JsonRpcSerializer},
log,
tunnels::protocol::EmptyObject,
rpc::RpcCaller,
tunnels::{code_server::print_listening, protocol::EmptyObject},
util::sync::Barrier,
};
@@ -34,6 +35,7 @@ pub struct SingletonClientArgs {
struct SingletonServerContext {
log: log::Logger,
exit_entirely: Arc<AtomicBool>,
caller: RpcCaller<JsonRpcSerializer>,
}
const CONTROL_INSTRUCTIONS_COMMON: &str =
@@ -61,7 +63,7 @@ pub async fn start_singleton_client(args: SingletonClientArgs) -> bool {
"An existing tunnel is running on this machine, connecting to it..."
);
let stdin_handle = rpc.get_caller(msg_tx);
let stdin_handle = rpc.get_caller(msg_tx.clone());
thread::spawn(move || {
let mut input = String::new();
loop {
@@ -85,9 +87,11 @@ pub async fn start_singleton_client(args: SingletonClientArgs) -> bool {
}
});
let caller = rpc.get_caller(msg_tx);
let mut rpc = rpc.methods(SingletonServerContext {
log: args.log.clone(),
exit_entirely: exit_entirely.clone(),
caller,
});
rpc.register_sync(protocol::singleton::METHOD_SHUTDOWN, |_: EmptyObject, c| {
@@ -95,15 +99,29 @@ pub async fn start_singleton_client(args: SingletonClientArgs) -> bool {
Ok(())
});
rpc.register_sync(
rpc.register_async(
protocol::singleton::METHOD_LOG_REPLY_DONE,
|_: EmptyObject, c| {
|_: EmptyObject, c| async move {
c.log.result(if *IS_INTERACTIVE_CLI {
CONTROL_INSTRUCTIONS_INTERACTIVE
} else {
CONTROL_INSTRUCTIONS_COMMON
});
let res = c.caller.call::<_, _, protocol::singleton::Status>(
protocol::singleton::METHOD_STATUS,
protocol::EmptyObject {},
);
// we want to ensure the "listening" string always gets printed for
// consumers (i.e. VS Code). Ask for it. If the tunnel is not currently
// connected though, it will be soon, and that'll be in the log replays.
if let Ok(Ok(s)) = res.await {
if let protocol::singleton::TunnelState::Connected { name } = s.tunnel {
print_listening(&c.log, &name);
}
}
Ok(())
},
);

View File

@@ -22,6 +22,7 @@ use crate::{
rpc::{RpcCaller, RpcDispatcher},
singleton::SingletonServer,
state::LauncherPaths,
tunnels::code_server::print_listening,
update_service::Platform,
util::{
errors::{AnyError, CodeError},
@@ -52,11 +53,13 @@ struct SingletonServerContext {
log: log::Logger,
shutdown_tx: broadcast::Sender<ShutdownSignal>,
broadcast_tx: broadcast::Sender<Vec<u8>>,
current_name: Arc<Mutex<Option<String>>>,
}
pub struct RpcServer {
fut: JoinHandle<Result<(), CodeError>>,
shutdown_broadcast: broadcast::Sender<ShutdownSignal>,
current_name: Arc<Mutex<Option<String>>>,
}
pub fn make_singleton_server(
@@ -68,10 +71,12 @@ pub fn make_singleton_server(
let (shutdown_broadcast, _) = broadcast::channel(4);
let rpc = new_json_rpc();
let current_name = Arc::new(Mutex::new(None));
let mut rpc = rpc.methods(SingletonServerContext {
log: log.clone(),
shutdown_tx: shutdown_broadcast.clone(),
broadcast_tx: log_broadcast.get_brocaster(),
current_name: current_name.clone(),
});
rpc.register_sync(
@@ -85,8 +90,13 @@ pub fn make_singleton_server(
rpc.register_sync(
protocol::singleton::METHOD_STATUS,
|_: protocol::EmptyObject, _| {
Ok(protocol::singleton::Status { ok: true }) // mostly placeholder
|_: protocol::EmptyObject, c| {
Ok(protocol::singleton::Status {
tunnel: match c.current_name.lock().unwrap().clone() {
Some(name) => protocol::singleton::TunnelState::Connected { name },
None => protocol::singleton::TunnelState::Disconnected,
},
})
},
);
@@ -114,6 +124,7 @@ pub fn make_singleton_server(
});
RpcServer {
shutdown_broadcast,
current_name,
fut,
}
}
@@ -126,6 +137,12 @@ pub async fn start_singleton_server<'a>(
ShutdownRequest::Derived(Box::new(args.shutdown.clone())),
]);
{
print_listening(&args.log, &args.tunnel.name);
let mut name = args.server.current_name.lock().unwrap();
*name = Some(args.tunnel.name.clone())
}
let serve_fut = super::serve(
&args.log,
args.tunnel,