Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: expose create/start Pod and replicatePodmanContainer #5648

Merged
merged 6 commits into from Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 23 additions & 0 deletions packages/extension-api/src/extension-api.d.ts
Expand Up @@ -259,6 +259,21 @@ declare module '@podman-desktop/api' {
status(): ProviderConnectionStatus;
}

export interface PodCreatePortOptions {
host_ip: string;
container_port: number;
host_port: number;
protocol: string;
range: number;
}

export interface PodCreateOptions {
name: string;
portmappings?: PodCreatePortOptions[];
// Set the provider to use, if not we will try select the first one available (sorted in favor of Podman).
provider?: ProviderContainerConnectionInfo | ContainerProviderConnection;
}

export interface KubernetesProviderConnectionEndpoint {
apiURL: string;
}
Expand Down Expand Up @@ -2280,6 +2295,14 @@ declare module '@podman-desktop/api' {
export function listVolumes(): Promise<VolumeListInfo[]>;
export function createVolume(options?: VolumeCreateOptions): Promise<VolumeCreateResponseInfo>;
export function deleteVolume(volumeName: string, options?: VolumeDeleteOptions): Promise<void>;

export function createPod(podOptions: PodCreateOptions): Promise<{ engineId: string; Id: string }>;
export function replicatePodmanContainer(
source: { engineId: string; id: string },
target: { engineId: string },
overrideParameters: ContainerCreateOptions,
): Promise<{ Id: string; Warnings: string[] }>;
export function startPod(engineId: string, podId: string): Promise<void>;
}

/**
Expand Down
208 changes: 208 additions & 0 deletions packages/main/src/plugin/container-registry.spec.ts
Expand Up @@ -212,6 +212,12 @@ class TestContainerProviderRegistry extends ContainerProviderRegistry {
return super.getMatchingContainer(engineId, containerId);
}

public getMatchingContainerProvider(
providerContainerConnectionInfo: ProviderContainerConnectionInfo | podmanDesktopAPI.ContainerProviderConnection,
): InternalContainerProvider {
return super.getMatchingContainerProvider(providerContainerConnectionInfo);
}

addInternalProvider(name: string, provider: InternalContainerProvider): void {
this.internalProviders.set(name, provider);
}
Expand Down Expand Up @@ -661,6 +667,56 @@ test('getFirstRunningConnection', async () => {
expect(connection[0].endpoint.socketPath).toBe('/podman1.socket');
});

test('getFirstRunningPodmanContainerProvider', async () => {
const fakeDockerode = {} as Dockerode;

// set providers with docker being first
containerRegistry.addInternalProvider('docker1', {
name: 'docker1',
id: 'docker1',
connection: {
type: 'docker',
},
api: fakeDockerode,
} as InternalContainerProvider);
containerRegistry.addInternalProvider('podman1', {
name: 'podman1',
id: 'podman1',
connection: {
type: 'podman',
},
api: fakeDockerode,
} as unknown as InternalContainerProvider);

containerRegistry.addInternalProvider('docker2', {
name: 'docker2',
id: 'docker2',
connection: {
type: 'docker',
},
api: fakeDockerode,
} as InternalContainerProvider);

containerRegistry.addInternalProvider('podman2', {
name: 'podman2',
id: 'podman2',
connection: {
type: 'podman',
endpoint: {
socketPath: '/podman1.socket',
},
},
api: fakeDockerode,
libpodApi: fakeDockerode,
} as unknown as InternalContainerProvider);

const connection = containerRegistry.getFirstRunningPodmanContainerProvider();

// first should be podman 1 as we're first ordering podman providers
expect(connection.name).toBe('podman2');
expect(connection.connection.endpoint.socketPath).toBe('/podman1.socket');
});

describe('listContainers', () => {
test('list containers with Podman API', async () => {
const containersWithPodmanAPI = [
Expand Down Expand Up @@ -2850,3 +2906,155 @@ test('check volume mounted is replicated when executing replicatePodmanContainer
mounts: fakeContainerInspectInfo.Mounts,
});
});

test('check createPod uses running podman connection if no selectedProvider is provided', async () => {
const createPodMock = vi.fn().mockResolvedValue({
Id: 'id',
});
const fakeDockerode = {
createPod: createPodMock,
} as unknown as Dockerode;

const internalProvider = {
name: 'podman1',
id: 'podman1',
connection: {
type: 'podman',
},
api: fakeDockerode,
libpodApi: fakeDockerode,
} as unknown as InternalContainerProvider;

containerRegistry.addInternalProvider('podman2', internalProvider);
const result = await containerRegistry.createPod({
name: 'pod',
});
expect(result.Id).equal('id');
expect(result.engineId).equal('podman1');
});

test('check createPod uses running podman connection if ContainerProviderConnection is provided', async () => {
const createPodMock = vi.fn().mockResolvedValue({
Id: 'id',
});
const fakeDockerode = {
createPod: createPodMock,
} as unknown as Dockerode;

const internalProvider = {
name: 'podman1',
id: 'podman1',
connection: {
name: 'podman1',
type: 'podman',
endpoint: {
socketPath: 'podman.sock',
},
},
api: fakeDockerode,
libpodApi: fakeDockerode,
} as unknown as InternalContainerProvider;

containerRegistry.addInternalProvider('podman1', internalProvider);

const containerProviderConnection: podmanDesktopAPI.ContainerProviderConnection = {
name: 'podman1',
endpoint: {
socketPath: 'podman.sock',
},
status: vi.fn(),
type: 'podman',
};

const result = await containerRegistry.createPod({
name: 'pod',
provider: containerProviderConnection,
});
expect(result.Id).equal('id');
expect(result.engineId).equal('podman1');
});

test('check createPod uses running podman connection if ProviderContainerConnectionInfo is provided', async () => {
const createPodMock = vi.fn().mockResolvedValue({
Id: 'id',
});
const fakeDockerode = {
createPod: createPodMock,
} as unknown as Dockerode;

const internalProvider = {
name: 'podman1',
id: 'podman1',
connection: {
name: 'podman1',
type: 'podman',
endpoint: {
socketPath: 'podman.sock',
},
},
api: fakeDockerode,
libpodApi: fakeDockerode,
} as unknown as InternalContainerProvider;

containerRegistry.addInternalProvider('podman1', internalProvider);

const containerProviderConnection: ProviderContainerConnectionInfo = {
name: 'podman1',
endpoint: {
socketPath: 'podman.sock',
},
status: 'started',
type: 'podman',
};

const result = await containerRegistry.createPod({
name: 'pod',
provider: containerProviderConnection,
});
expect(result.Id).equal('id');
expect(result.engineId).equal('podman1');
});

test('check that fails if there is no podman provider running', async () => {
const internalProvider = {
name: 'podman1',
id: 'podman1',
connection: {
name: 'podman1',
type: 'podman',
},
} as unknown as InternalContainerProvider;

containerRegistry.addInternalProvider('podman1', internalProvider);
await expect(
containerRegistry.createPod({
name: 'pod',
}),
).rejects.toThrowError('No podman provider with a running engine');
});

test('check that fails if selected provider is not a podman one', async () => {
const createPodMock = vi.fn().mockResolvedValue({
Id: 'id',
});
const fakeDockerode = {
createPod: createPodMock,
} as unknown as Dockerode;

const internalProvider = {
name: 'podman1',
id: 'podman1',
connection: {
name: 'podman1',
type: 'docker',
},
api: fakeDockerode,
} as unknown as InternalContainerProvider;

containerRegistry.addInternalProvider('podman1', internalProvider);
await expect(
containerRegistry.createPod({
name: 'pod',
}),
).rejects.toThrowError('No podman provider with a running engine');
});
59 changes: 43 additions & 16 deletions packages/main/src/plugin/container-registry.ts
Expand Up @@ -47,7 +47,6 @@ import type { HistoryInfo } from './api/history-info.js';
import type {
LibPod,
PlayKubeInfo,
PodCreateOptions,
ContainerCreateOptions as PodmanContainerCreateOptions,
PodInfo as LibpodPodInfo,
} from './dockerode/libpod-dockerode.js';
Expand Down Expand Up @@ -876,9 +875,38 @@ export class ContainerProviderRegistry {
];
}

/**
* it finds a running podman provider by fetching all internalProviders.
* It filters by checking the libpodApi
* @returns a running podman provider
* @throws if no running podman provider is found
*/
public getFirstRunningPodmanContainerProvider(): InternalContainerProvider {
benoitf marked this conversation as resolved.
Show resolved Hide resolved
// grab the first running podman provider
const matchingPodmanContainerProvider = Array.from(this.internalProviders.values()).find(
containerProvider => containerProvider.libpodApi,
);
if (!matchingPodmanContainerProvider) {
throw new Error('No podman provider with a running engine');
}

return matchingPodmanContainerProvider;
}

protected getMatchingEngineFromConnection(
providerContainerConnectionInfo: ProviderContainerConnectionInfo | containerDesktopAPI.ContainerProviderConnection,
): Dockerode {
// grab all connections
const matchingContainerProvider = this.getMatchingContainerProvider(providerContainerConnectionInfo);
if (!matchingContainerProvider?.api) {
throw new Error('no running provider for the matching container');
}
return matchingContainerProvider.api;
}

protected getMatchingContainerProvider(
lstocchi marked this conversation as resolved.
Show resolved Hide resolved
providerContainerConnectionInfo: ProviderContainerConnectionInfo | containerDesktopAPI.ContainerProviderConnection,
): InternalContainerProvider {
// grab all connections
const matchingContainerProvider = Array.from(this.internalProviders.values()).find(
containerProvider =>
Expand All @@ -888,7 +916,7 @@ export class ContainerProviderRegistry {
if (!matchingContainerProvider?.api) {
throw new Error('no running provider for the matching container');
}
return matchingContainerProvider.api;
return matchingContainerProvider;
}

protected getMatchingContainer(engineId: string, id: string): Dockerode.Container {
Expand Down Expand Up @@ -1131,23 +1159,22 @@ export class ContainerProviderRegistry {
}
}

async createPod(
selectedProvider: ProviderContainerConnectionInfo,
podOptions: PodCreateOptions,
): Promise<{ engineId: string; Id: string }> {
async createPod(podOptions: containerDesktopAPI.PodCreateOptions): Promise<{ engineId: string; Id: string }> {
let telemetryOptions = {};
try {
// grab all connections
const matchingContainerProvider = Array.from(this.internalProviders.values()).find(
containerProvider =>
containerProvider.connection.endpoint.socketPath === selectedProvider.endpoint.socketPath &&
containerProvider.connection.name === selectedProvider.name,
);
if (!matchingContainerProvider?.libpodApi) {
throw new Error('No provider with a running engine');
let internalContainerProvider: InternalContainerProvider;
if (podOptions.provider) {
// grab connection
internalContainerProvider = this.getMatchingContainerProvider(podOptions.provider);
} else {
// Get the first running podman connection
internalContainerProvider = this.getFirstRunningPodmanContainerProvider();
}
if (!internalContainerProvider?.libpodApi) {
throw new Error('No podman provider with a running engine');
}
const result = await matchingContainerProvider.libpodApi.createPod(podOptions);
return { Id: result.Id, engineId: matchingContainerProvider.id };
const result = await internalContainerProvider.libpodApi.createPod(podOptions);
return { Id: result.Id, engineId: internalContainerProvider.id };
} catch (error) {
telemetryOptions = { error: error };
throw error;
Expand Down
13 changes: 13 additions & 0 deletions packages/main/src/plugin/extension-loader.ts
Expand Up @@ -997,6 +997,19 @@ export class ExtensionLoader {
deleteVolume(volumeName: string, options?: containerDesktopAPI.VolumeDeleteOptions): Promise<void> {
return containerProviderRegistry.deleteVolume(volumeName, options);
},
createPod(podOptions: containerDesktopAPI.PodCreateOptions): Promise<{ engineId: string; Id: string }> {
return containerProviderRegistry.createPod(podOptions);
},
replicatePodmanContainer(
source: { engineId: string; id: string },
target: { engineId: string },
overrideParameters: containerDesktopAPI.ContainerCreateOptions,
): Promise<{ Id: string; Warnings: string[] }> {
return containerProviderRegistry.replicatePodmanContainer(source, target, overrideParameters);
},
startPod(engineId: string, podId: string): Promise<void> {
return containerProviderRegistry.startPod(engineId, podId);
},
};

const authenticationProviderRegistry = this.authenticationProviderRegistry;
Expand Down
6 changes: 2 additions & 4 deletions packages/main/src/plugin/index.ts
Expand Up @@ -73,7 +73,6 @@ import type { VolumeInspectInfo, VolumeListInfo } from './api/volume-info.js';
import type { ContainerStatsInfo } from './api/container-stats-info.js';
import type {
PlayKubeInfo,
PodCreateOptions,
ContainerCreateOptions as PodmanContainerCreateOptions,
} from './dockerode/libpod-dockerode.js';
import type Dockerode from 'dockerode';
Expand Down Expand Up @@ -883,10 +882,9 @@ export class PluginSystem {
'container-provider-registry:createPod',
async (
_listener,
selectedProvider: ProviderContainerConnectionInfo,
createOptions: PodCreateOptions,
createOptions: containerDesktopAPI.PodCreateOptions,
): Promise<{ engineId: string; Id: string }> => {
return containerProviderRegistry.createPod(selectedProvider, createOptions);
return containerProviderRegistry.createPod(createOptions);
},
);
this.ipcHandle(
Expand Down