Skip to content

Commit

Permalink
feat: allow to ssh to podman virtual machine - backend changes
Browse files Browse the repository at this point in the history
Signed-off-by: Evzen Gasta <[email protected]>
  • Loading branch information
gastoner committed Oct 17, 2024
1 parent 77d715f commit 6a2c5b1
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 23 deletions.
23 changes: 0 additions & 23 deletions packages/extension-api/src/extension-api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -789,29 +789,6 @@ declare module '@podman-desktop/api' {
connection: ContainerProviderConnection;
}

export interface ProviderConnectionShellAccess {
onData: Event<ProviderConnectionShellAccessData>;
onError: Event<ProviderConnectionShellAccessError>;
onEnd: Event<void>;
write(data: string): void;
startConnection(): void;
stopConnection(): void;
setWindow(dimensions: ShellDimensions): void;
}

export interface ShellDimensions {
rows: number;
cols: number;
}

export interface ProviderConnectionShellAccessError {
error: string;
}

export interface ProviderConnectionShellAccessData {
data: string;
}

/**
* Callback for openning shell session
*/
Expand Down
61 changes: 61 additions & 0 deletions packages/main/src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,67 @@ export class PluginSystem {
},
);

const providerRegistryShellInProviderConnectionSendCallback = new Map<
number,
{
write: (param: string) => void;
resize: (dimensions: containerDesktopAPI.ProviderConnectionShellDimensions) => void;
}
>();
this.ipcHandle(
'provider-registry:shellInProviderConnection',
async (
_listener,
internalProviderId: string,
connectionInfo: ProviderContainerConnectionInfo | ProviderKubernetesConnectionInfo,
onDataId: number,
): Promise<number> => {
// provide the data content to the remote side
const shellInProviderConnectionInvocation = await providerRegistry.shellInProviderConnection(
internalProviderId,
connectionInfo,
(content: string) => {
this.getWebContentsSender().send('provider-registry:shellInProviderConnection-onData', onDataId, content);
},
(error: string) => {
this.getWebContentsSender().send('provider-registry:shellInProviderConnection-onError', onDataId, error);
},
() => {
this.getWebContentsSender().send('provider-registry:shellInProviderConnection-onEnd', onDataId);
// delete the callback
providerRegistryShellInProviderConnectionSendCallback.delete(onDataId);
},
);
// store the callback
providerRegistryShellInProviderConnectionSendCallback.set(onDataId, shellInProviderConnectionInvocation);
return onDataId;
},
);

this.ipcHandle(
'provider-registry:shellInProviderConnectionSend',
async (_listener, onDataId: number, content: string): Promise<void> => {
const callback = providerRegistryShellInProviderConnectionSendCallback.get(onDataId);
if (callback) {
callback.write(content);
}
},
);

this.ipcHandle(
'provider-registry:shellInProviderConnectionResize',
async (
_listener,
onDataId: number,
dimensions: containerDesktopAPI.ProviderConnectionShellDimensions,
): Promise<void> => {
const callback = providerRegistryShellInProviderConnectionSendCallback.get(onDataId);
if (callback) {
callback.resize(dimensions);
}
},
);

const containerProviderRegistryAttachContainerSendCallback = new Map<number, (param: string) => void>();
this.ipcHandle(
'container-provider-registry:attachContainer',
Expand Down
44 changes: 44 additions & 0 deletions packages/main/src/plugin/provider-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import type {
ProviderCleanup,
ProviderCleanupAction,
ProviderCleanupExecuteOptions,
ProviderConnectionShellAccess,
ProviderConnectionShellAccessSession,
ProviderConnectionShellDimensions,
ProviderConnectionStatus,
ProviderContainerConnection,
ProviderDetectionCheck,
Expand Down Expand Up @@ -1264,4 +1267,45 @@ export class ProviderRegistry {
this.apiSender.send('provider-change', {});
});
}

async shellInProviderConnection(
internalProviderId: string,
providerConnectionInfo: ProviderContainerConnectionInfo | ProviderKubernetesConnectionInfo,
onData: (data: string) => void,
onError: (error: string) => void,
onEnd: () => void,
): Promise<{ write: (param: string) => void; resize: (dimensions: ProviderConnectionShellDimensions) => void }> {
try {
const containerConnection = this.getMatchingConnectionFromProvider(internalProviderId, providerConnectionInfo);
let shellAccess: ProviderConnectionShellAccess | undefined;
let connection: ProviderConnectionShellAccessSession | undefined;
if (this.isContainerConnection(containerConnection) && providerConnectionInfo.status === 'started') {
shellAccess = containerConnection.shellAccess;
connection = shellAccess?.open();
connection?.onData(data => {
onData(data.data);
});
connection?.onError(error => {
onError(error.error);
});
connection?.onEnd(onEnd);
}

return {
write: (data: string): void => {
if (connection) {
connection.write(data);
}
},
resize: (dimension: ProviderConnectionShellDimensions): void => {
if (connection) {
connection.resize(dimension);
}
},
};
} catch (error) {
this.telemetryService.track('shellInProviderConnection.error', error);
throw error;
}
}
}
78 changes: 78 additions & 0 deletions packages/preload/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,84 @@ export function initExposure(): void {
},
);

// callbacks for shellInProviderConnection
let onDataCallbacksShellInProviderConnectionId = 0;
const onDataCallbacksShellInProviderConnection = new Map<
number,
{ onData: (data: string) => void; onError: (error: string) => void; onEnd: () => void }
>();
contextBridge.exposeInMainWorld(
'shellInProviderConnection',
async (
internalProviderId: string,
connectionInfo: ProviderContainerConnectionInfo | ProviderKubernetesConnectionInfo,
onData: (data: string) => void,
onError: (error: string) => void,
onEnd: () => void,
): Promise<number> => {
onDataCallbacksShellInProviderConnectionId++;
onDataCallbacksShellInProviderConnection.set(onDataCallbacksShellInProviderConnectionId, {
onData,
onError,
onEnd,
});
return ipcInvoke(
'provider-registry:shellInProviderConnection',
internalProviderId,
connectionInfo,
onDataCallbacksShellInProviderConnectionId,
);
},
);

contextBridge.exposeInMainWorld(
'shellInProviderConnectionSend',
async (dataId: number, content: string): Promise<void> => {
return ipcInvoke('provider-registry:shellInProviderConnectionSend', dataId, content);
},
);

contextBridge.exposeInMainWorld(
'shellInProviderConnectionResize',
async (dataId: number, dimensions: containerDesktopAPI.ProviderConnectionShellDimensions) => {
return ipcInvoke('provider-registry:shellInProviderConnectionResize', dataId, dimensions);
},
);

ipcRenderer.on(
'provider-registry:shellInProviderConnection-onData',
(_, onDataCallbacksShellInProviderConnectionId: number, data: string) => {
// grab callback from the map
const callback = onDataCallbacksShellInProviderConnection.get(onDataCallbacksShellInProviderConnectionId);
if (callback) {
callback.onData(data);
}
},
);
ipcRenderer.on(
'provider-registry:shellInProviderConnection-onError',
(_, onDataCallbacksShellInProviderConnectionId: number, error: string) => {
// grab callback from the map
const callback = onDataCallbacksShellInProviderConnection.get(onDataCallbacksShellInProviderConnectionId);
if (callback) {
callback.onError(error);
}
},
);

ipcRenderer.on(
'provider-registry:shellInProviderConnection-onEnd',
(_, onDataCallbacksShellInProviderConnectionId: number) => {
// grab callback from the map
const callback = onDataCallbacksShellInProviderConnection.get(onDataCallbacksShellInProviderConnectionId);
if (callback) {
callback.onEnd();
// remove callback from the map
onDataCallbacksShellInProviderConnection.delete(onDataCallbacksShellInProviderConnectionId);
}
},
);

// callbacks for attachContainer
let onDataCallbacksAttachContainerId = 0;
const onDataCallbacksAttachContainer = new Map<
Expand Down

0 comments on commit 6a2c5b1

Please sign in to comment.