Skip to content

Commit 8a74ad8

Browse files
committed
cli: show service status in tunnel log
Fixes microsoft#183714
1 parent 979ae39 commit 8a74ad8

File tree

10 files changed

+80
-29
lines changed

10 files changed

+80
-29
lines changed

cli/src/commands/args.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use std::{fmt, path::PathBuf};
77

88
use crate::{constants, log, options, tunnels::code_server::CodeServerArgs};
9-
use clap::{ValueEnum, Args, Parser, Subcommand};
9+
use clap::{Args, Parser, Subcommand, ValueEnum};
1010
use const_format::concatcp;
1111

1212
const CLI_NAME: &str = concatcp!(constants::PRODUCT_NAME_LONG, " CLI");

cli/src/commands/tunnels.rs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
use async_trait::async_trait;
7-
use base64::{Engine as _, engine::general_purpose as b64};
7+
use base64::{engine::general_purpose as b64, Engine as _};
8+
use serde::Serialize;
89
use sha2::{Digest, Sha256};
910
use std::{str::FromStr, time::Duration};
1011
use sysinfo::Pid;
@@ -247,26 +248,39 @@ pub async fn kill(ctx: CommandContext) -> Result<i32, AnyError> {
247248
.map_err(|e| e.into())
248249
}
249250

251+
#[derive(Serialize)]
252+
pub struct StatusOutput {
253+
pub tunnel: Option<protocol::singleton::TunnelState>,
254+
pub service_installed: bool,
255+
}
256+
250257
pub async fn status(ctx: CommandContext) -> Result<i32, AnyError> {
251-
let status = do_single_rpc_call::<_, protocol::singleton::Status>(
258+
let tunnel_status = do_single_rpc_call::<_, protocol::singleton::Status>(
252259
&ctx.paths.tunnel_lockfile(),
253260
ctx.log.clone(),
254261
protocol::singleton::METHOD_STATUS,
255262
protocol::EmptyObject {},
256263
)
257264
.await;
258265

259-
match status {
260-
Err(CodeError::NoRunningTunnel) => {
261-
ctx.log.result(CodeError::NoRunningTunnel.to_string());
262-
Ok(1)
263-
}
264-
Err(e) => Err(e.into()),
265-
Ok(s) => {
266-
ctx.log.result(serde_json::to_string(&s).unwrap());
267-
Ok(0)
268-
}
269-
}
266+
let service_installed = create_service_manager(ctx.log.clone(), &ctx.paths)
267+
.is_installed()
268+
.await
269+
.unwrap_or(false);
270+
271+
ctx.log.result(
272+
serde_json::to_string(&StatusOutput {
273+
service_installed,
274+
tunnel: match tunnel_status {
275+
Ok(s) => Some(s.tunnel),
276+
Err(CodeError::NoRunningTunnel) => None,
277+
Err(e) => return Err(e.into()),
278+
},
279+
})
280+
.unwrap(),
281+
);
282+
283+
Ok(0)
270284
}
271285

272286
/// Removes unused servers.

cli/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ pub mod tunnels;
1818
pub mod update_service;
1919
pub mod util;
2020

21-
mod download_cache;
2221
mod async_pipe;
22+
mod download_cache;
2323
mod json_rpc;
2424
mod msgpack_rpc;
2525
mod rpc;

cli/src/tunnels/service.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ pub trait ServiceManager {
4141
/// Show logs from the running service to standard out.
4242
async fn show_logs(&self) -> Result<(), AnyError>;
4343

44+
/// Gets whether the tunnel service is installed.
45+
async fn is_installed(&self) -> Result<bool, AnyError>;
46+
4447
/// Unregisters the current executable as a service.
4548
async fn unregister(&self) -> Result<(), AnyError>;
4649
}

cli/src/tunnels/service_linux.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ impl SystemdService {
4040
async fn connect() -> Result<Connection, AnyError> {
4141
let connection = Connection::session()
4242
.await
43-
.map_err(|e| wrap(e, "error creating dbus session"))?;
43+
.map_err(|e| wrap(e, "Error creating dbus session. This command uses systemd for managing services, you should check that systemd is installed and running as a user. If it's already installed, you may need to:\n\n- Install the `dbus-user-session` package and reboot\n- Start the user dbus session with `systemctl --user enable dbus --now`. \n\nThe error encountered was"))?;
4444
Ok(connection)
4545
}
4646

@@ -113,6 +113,20 @@ impl ServiceManager for SystemdService {
113113
Ok(())
114114
}
115115

116+
async fn is_installed(&self) -> Result<bool, AnyError> {
117+
let connection = SystemdService::connect().await?;
118+
let proxy = SystemdService::proxy(&connection).await?;
119+
let state = proxy
120+
.get_unit_file_state(SystemdService::service_name_string())
121+
.await;
122+
123+
if let Ok(s) = state {
124+
Ok(s == "enabled")
125+
} else {
126+
Ok(false)
127+
}
128+
}
129+
116130
async fn run(
117131
self,
118132
launcher_paths: crate::state::LauncherPaths,
@@ -219,6 +233,8 @@ trait SystemdManagerDbus {
219233
force: bool,
220234
) -> zbus::Result<(bool, Vec<(String, String, String)>)>;
221235

236+
fn get_unit_file_state(&self, file: String) -> zbus::Result<String>;
237+
222238
fn link_unit_files(
223239
&self,
224240
files: Vec<String>,

cli/src/tunnels/service_macos.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ impl ServiceManager for LaunchdService {
7575
handle.run_service(self.log, launcher_paths).await
7676
}
7777

78+
async fn is_installed(&self) -> Result<bool, AnyError> {
79+
let cmd = capture_command_and_check_status("launchctl", &["list"]).await?;
80+
Ok(String::from_utf8_lossy(&cmd.stdout).contains(&get_service_label()))
81+
}
82+
7883
async fn unregister(&self) -> Result<(), crate::util::errors::AnyError> {
7984
let service_file = get_service_file_path()?;
8085

cli/src/tunnels/service_windows.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ impl CliServiceManager for WindowsService {
114114
Ok(())
115115
}
116116

117+
async fn is_installed(&self) -> Result<bool, AnyError> {
118+
let key = WindowsService::open_key()?;
119+
Ok(key.get_raw_value(TUNNEL_ACTIVITY_NAME).is_ok())
120+
}
121+
117122
async fn unregister(&self) -> Result<(), AnyError> {
118123
let key = WindowsService::open_key()?;
119124
key.delete_value(TUNNEL_ACTIVITY_NAME)

cli/src/util/tar.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::util::errors::{wrap, WrappedError};
66

77
use flate2::read::GzDecoder;
88
use std::fs;
9-
use std::io::{Seek, SeekFrom};
9+
use std::io::Seek;
1010
use std::path::{Path, PathBuf};
1111
use tar::Archive;
1212

@@ -65,7 +65,7 @@ where
6565

6666
// reset since skip logic read the tar already:
6767
tar_gz
68-
.seek(SeekFrom::Start(0))
68+
.rewind()
6969
.map_err(|e| wrap(e, "error resetting seek position"))?;
7070

7171
let tar = GzDecoder::new(tar_gz);

cli/src/util/zipper.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,7 @@ where
8888
use std::io::Read;
8989
use std::os::unix::ffi::OsStringExt;
9090

91-
if matches!(file.unix_mode(), Some(mode) if mode & (S_IFLNK as u32) == (S_IFLNK as u32))
92-
{
91+
if matches!(file.unix_mode(), Some(mode) if mode & S_IFLNK == S_IFLNK) {
9392
let mut link_to = Vec::new();
9493
file.read_to_end(&mut link_to).map_err(|e| {
9594
wrap(

src/vs/platform/remoteTunnel/node/remoteTunnelService.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ
165165
}
166166

167167
async startTunnel(session: IRemoteTunnelSession): Promise<TunnelStatus> {
168-
if (isSameSession(session, this._session)) {
168+
if (isSameSession(session, this._session) && this._tunnelStatus.type !== 'disconnected') {
169169
return this._tunnelStatus;
170170
}
171171
this.setSession(session);
@@ -195,7 +195,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ
195195
}
196196
};
197197
try {
198-
await this.runCodeTunneCommand('stop', ['kill'], onOutput);
198+
await this.runCodeTunnelCommand('stop', ['kill'], onOutput);
199199
} catch (e) {
200200
this._logger.error(e);
201201
}
@@ -213,26 +213,35 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ
213213
}
214214

215215
let isAttached = false;
216+
let output = '';
216217

217218
const onOutput = (a: string, isErr: boolean) => {
218219
if (isErr) {
219220
this._logger.error(a);
220221
} else {
221-
this._logger.info(a);
222+
output += a;
222223
}
223224
if (!this.environmentService.isBuilt && a.startsWith(' Compiling')) {
224225
this.setTunnelStatus(TunnelStates.connecting(localize('remoteTunnelService.building', 'Building CLI from sources')));
225226
}
226227
};
227228

228-
const statusProcess = this.runCodeTunneCommand('status', ['status'], onOutput);
229+
const statusProcess = this.runCodeTunnelCommand('status', ['status'], onOutput);
229230
this._tunnelProcess = statusProcess;
230231
try {
231-
const status = await statusProcess;
232+
await statusProcess;
232233
if (this._tunnelProcess !== statusProcess) {
233234
return;
234235
}
235-
isAttached = status === 0;
236+
237+
// split and find the line, since in dev builds additional noise is
238+
// added by cargo to the output.
239+
const status: {
240+
service_installed: boolean;
241+
tunnel: object | null;
242+
} = JSON.parse(output.trim().split('\n').find(l => l.startsWith('{'))!);
243+
244+
isAttached = !!status.tunnel;
236245
this._logger.info(isAttached ? 'Other tunnel running, attaching...' : 'No other tunnel running');
237246
if (!isAttached && !this._session) {
238247
this._tunnelProcess = undefined;
@@ -255,7 +264,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ
255264
a = a.replaceAll(token, '*'.repeat(4));
256265
onOutput(a, isErr);
257266
};
258-
const loginProcess = this.runCodeTunneCommand('login', ['user', 'login', '--provider', session.providerId, '--access-token', token, '--log', LogLevelToString(this._logger.getLevel())], onLoginOutput);
267+
const loginProcess = this.runCodeTunnelCommand('login', ['user', 'login', '--provider', session.providerId, '--access-token', token, '--log', LogLevelToString(this._logger.getLevel())], onLoginOutput);
259268
this._tunnelProcess = loginProcess;
260269
try {
261270
await loginProcess;
@@ -286,7 +295,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ
286295
if (this._preventSleep()) {
287296
args.push('--no-sleep');
288297
}
289-
const serveCommand = this.runCodeTunneCommand('tunnel', args, (message: string, isErr: boolean) => {
298+
const serveCommand = this.runCodeTunnelCommand('tunnel', args, (message: string, isErr: boolean) => {
290299
if (isErr) {
291300
this._logger.error(message);
292301
} else {
@@ -315,7 +324,7 @@ export class RemoteTunnelService extends Disposable implements IRemoteTunnelServ
315324
});
316325
}
317326

318-
private runCodeTunneCommand(logLabel: string, commandArgs: string[], onOutput: (message: string, isError: boolean) => void = () => { }): CancelablePromise<number> {
327+
private runCodeTunnelCommand(logLabel: string, commandArgs: string[], onOutput: (message: string, isError: boolean) => void = () => { }): CancelablePromise<number> {
319328
return createCancelablePromise<number>(token => {
320329
return new Promise((resolve, reject) => {
321330
if (token.isCancellationRequested) {

0 commit comments

Comments
 (0)