mirror of
https://github.com/podman-desktop/podman-desktop
synced 2026-05-24 10:18:53 +00:00
feat: expose create/list/delete volumes for extensions (#5598)
fixes https://github.com/containers/podman-desktop/issues/5564 Signed-off-by: Florent Benoit <fbenoit@redhat.com>
This commit is contained in:
parent
aac0be1cb3
commit
caed92f22f
6 changed files with 264 additions and 13 deletions
49
packages/extension-api/src/extension-api.d.ts
vendored
49
packages/extension-api/src/extension-api.d.ts
vendored
|
|
@ -2174,6 +2174,51 @@ declare module '@podman-desktop/api' {
|
|||
diskUsed?: number;
|
||||
}
|
||||
|
||||
export interface VolumeInfo {
|
||||
engineId: string;
|
||||
engineName: string;
|
||||
CreatedAt: string;
|
||||
containersUsage: { id: string; names: string[] }[];
|
||||
Name: string;
|
||||
Driver: string;
|
||||
Mountpoint: string;
|
||||
Status?: { [key: string]: string };
|
||||
Labels: { [key: string]: string };
|
||||
Scope: 'local' | 'global';
|
||||
Options?: { [key: string]: string } | null;
|
||||
UsageData?: {
|
||||
Size: number;
|
||||
RefCount: number;
|
||||
} | null;
|
||||
}
|
||||
export interface VolumeListInfo {
|
||||
Volumes: VolumeInfo[];
|
||||
Warnings: string[];
|
||||
engineId: string;
|
||||
engineName: string;
|
||||
}
|
||||
export interface VolumeCreateOptions {
|
||||
// name of the volume to create
|
||||
Name: string;
|
||||
// Set the provider to use, if not we will try select the first one available (sorted in favor of Podman).
|
||||
provider?: ProviderContainerConnectionInfo | containerDesktopAPI.ContainerProviderConnection;
|
||||
}
|
||||
|
||||
export interface VolumeDeleteOptions {
|
||||
// Set the provider to use, if not we will try select the first one available (sorted in favor of Podman).
|
||||
provider?: ProviderContainerConnectionInfo | containerDesktopAPI.ContainerProviderConnection;
|
||||
}
|
||||
|
||||
export interface VolumeCreateResponseInfo {
|
||||
Name: string;
|
||||
Driver: string;
|
||||
Mountpoint: string;
|
||||
CreatedAt?: string;
|
||||
Status?: { [key: string]: string };
|
||||
Labels: { [label: string]: string };
|
||||
Scope: string;
|
||||
}
|
||||
|
||||
export namespace containerEngine {
|
||||
export function listContainers(): Promise<ContainerInfo[]>;
|
||||
export function inspectContainer(engineId: string, id: string): Promise<ContainerInspectInfo>;
|
||||
|
|
@ -2222,6 +2267,10 @@ declare module '@podman-desktop/api' {
|
|||
containerProviderConnection: ContainerProviderConnection,
|
||||
networkCreateOptions: NetworkCreateOptions,
|
||||
): Promise<NetworkCreateResult>;
|
||||
|
||||
export function listVolumes(): Promise<VolumeListInfo[]>;
|
||||
export function createVolume(options?: VolumeCreateOptions): Promise<VolumeCreateResponseInfo>;
|
||||
export function deleteVolume(volumeName: string, options?: VolumeDeleteOptions): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -107,3 +107,5 @@ export interface NetworkCreateResult {
|
|||
export interface VolumeCreateOptions {
|
||||
Name?: string;
|
||||
}
|
||||
|
||||
export interface VolumeCreateResponseInfo extends Dockerode.VolumeCreateResponse {}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import type { ProviderContainerConnectionInfo } from './api/provider-info.js';
|
|||
import * as util from '../util.js';
|
||||
import { PassThrough } from 'node:stream';
|
||||
import type { EnvfileParser } from './env-file-parser.js';
|
||||
import type { ProviderRegistry } from './provider-registry.js';
|
||||
const tar: { pack: (dir: string) => NodeJS.ReadableStream } = require('tar-fs');
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
|
|
@ -1829,6 +1830,169 @@ describe('createVolume', () => {
|
|||
// check that it's calling the right nock method
|
||||
await containerRegistry.createVolume(providerConnectionInfo, {});
|
||||
});
|
||||
|
||||
test('provided user API connection', async () => {
|
||||
nock('http://localhost').post('/volumes/create?Name=myFirstVolume').reply(200, '');
|
||||
|
||||
const api = new Dockerode({ protocol: 'http', host: 'localhost' });
|
||||
|
||||
const internalContainerProvider: InternalContainerProvider = {
|
||||
name: 'podman',
|
||||
id: 'podman1',
|
||||
api,
|
||||
connection: {
|
||||
type: 'podman',
|
||||
name: 'podman',
|
||||
endpoint: {
|
||||
socketPath: '/endpoint1.sock',
|
||||
},
|
||||
status: () => 'started',
|
||||
},
|
||||
};
|
||||
|
||||
const containerProviderConnection: podmanDesktopAPI.ContainerProviderConnection = {
|
||||
name: 'podman',
|
||||
type: 'podman',
|
||||
endpoint: {
|
||||
socketPath: '/endpoint1.sock',
|
||||
},
|
||||
status: () => 'started',
|
||||
} as unknown as podmanDesktopAPI.ContainerProviderConnection;
|
||||
|
||||
// set provider
|
||||
containerRegistry.addInternalProvider('podman', internalContainerProvider);
|
||||
|
||||
// check that it's calling the right nock method
|
||||
await containerRegistry.createVolume(containerProviderConnection, { Name: 'myFirstVolume' });
|
||||
});
|
||||
|
||||
test('no provider', async () => {
|
||||
nock('http://localhost').post('/volumes/create?Name=myFirstVolume').reply(200, '');
|
||||
|
||||
const api = new Dockerode({ protocol: 'http', host: 'localhost' });
|
||||
|
||||
const internalContainerProvider: InternalContainerProvider = {
|
||||
name: 'podman',
|
||||
id: 'podman1',
|
||||
api,
|
||||
connection: {
|
||||
type: 'podman',
|
||||
name: 'podman',
|
||||
endpoint: {
|
||||
socketPath: '/endpoint1.sock',
|
||||
},
|
||||
status: () => 'started',
|
||||
},
|
||||
};
|
||||
// set provider
|
||||
containerRegistry.addInternalProvider('podman.podman', internalContainerProvider);
|
||||
|
||||
const containerProviderConnection: podmanDesktopAPI.ContainerProviderConnection = {
|
||||
name: 'podman',
|
||||
type: 'podman',
|
||||
endpoint: {
|
||||
socketPath: '/endpoint1.sock',
|
||||
},
|
||||
status: () => 'started',
|
||||
} as unknown as podmanDesktopAPI.ContainerProviderConnection;
|
||||
|
||||
const podmanProvider = {
|
||||
name: 'podman',
|
||||
id: 'podman',
|
||||
} as unknown as podmanDesktopAPI.Provider;
|
||||
|
||||
const providerRegistry: ProviderRegistry = {
|
||||
onBeforeDidUpdateContainerConnection: vi.fn(),
|
||||
onDidUpdateContainerConnection: vi.fn(),
|
||||
} as unknown as ProviderRegistry;
|
||||
|
||||
containerRegistry.registerContainerConnection(podmanProvider, containerProviderConnection, providerRegistry);
|
||||
|
||||
// check that it's calling the right nock method
|
||||
await containerRegistry.createVolume(undefined, { Name: 'myFirstVolume' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteVolume', () => {
|
||||
test('no provider', async () => {
|
||||
nock('http://localhost').delete('/volumes/myFirstVolume').reply(204, '');
|
||||
|
||||
const api = new Dockerode({ protocol: 'http', host: 'localhost' });
|
||||
|
||||
const internalContainerProvider: InternalContainerProvider = {
|
||||
name: 'podman',
|
||||
id: 'podman1',
|
||||
api,
|
||||
connection: {
|
||||
type: 'podman',
|
||||
name: 'podman',
|
||||
endpoint: {
|
||||
socketPath: '/endpoint1.sock',
|
||||
},
|
||||
status: () => 'started',
|
||||
},
|
||||
};
|
||||
// set provider
|
||||
containerRegistry.addInternalProvider('podman.podman', internalContainerProvider);
|
||||
|
||||
const containerProviderConnection: podmanDesktopAPI.ContainerProviderConnection = {
|
||||
name: 'podman',
|
||||
type: 'podman',
|
||||
endpoint: {
|
||||
socketPath: '/endpoint1.sock',
|
||||
},
|
||||
status: () => 'started',
|
||||
} as unknown as podmanDesktopAPI.ContainerProviderConnection;
|
||||
|
||||
const podmanProvider = {
|
||||
name: 'podman',
|
||||
id: 'podman',
|
||||
} as unknown as podmanDesktopAPI.Provider;
|
||||
|
||||
const providerRegistry: ProviderRegistry = {
|
||||
onBeforeDidUpdateContainerConnection: vi.fn(),
|
||||
onDidUpdateContainerConnection: vi.fn(),
|
||||
} as unknown as ProviderRegistry;
|
||||
|
||||
containerRegistry.registerContainerConnection(podmanProvider, containerProviderConnection, providerRegistry);
|
||||
|
||||
// check that it's calling the right nock method
|
||||
await containerRegistry.deleteVolume('myFirstVolume');
|
||||
});
|
||||
|
||||
test('provided connection', async () => {
|
||||
nock('http://localhost').delete('/volumes/myFirstVolume').reply(204, '');
|
||||
|
||||
const api = new Dockerode({ protocol: 'http', host: 'localhost' });
|
||||
|
||||
const internalContainerProvider: InternalContainerProvider = {
|
||||
name: 'podman',
|
||||
id: 'podman1',
|
||||
api,
|
||||
connection: {
|
||||
type: 'podman',
|
||||
name: 'podman',
|
||||
endpoint: {
|
||||
socketPath: '/endpoint1.sock',
|
||||
},
|
||||
status: () => 'started',
|
||||
},
|
||||
};
|
||||
|
||||
const containerProviderConnection: podmanDesktopAPI.ContainerProviderConnection = {
|
||||
name: 'podman',
|
||||
type: 'podman',
|
||||
endpoint: {
|
||||
socketPath: '/endpoint1.sock',
|
||||
},
|
||||
status: () => 'started',
|
||||
} as unknown as podmanDesktopAPI.ContainerProviderConnection;
|
||||
|
||||
// set provider
|
||||
containerRegistry.addInternalProvider('podman', internalContainerProvider);
|
||||
// check that it's calling the right nock method
|
||||
await containerRegistry.deleteVolume('myFirstVolume', { provider: containerProviderConnection });
|
||||
});
|
||||
});
|
||||
|
||||
test('container logs callback notified when messages arrive', async () => {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import type {
|
|||
NetworkCreateResult,
|
||||
SimpleContainerInfo,
|
||||
VolumeCreateOptions,
|
||||
VolumeCreateResponseInfo,
|
||||
} from './api/container-info.js';
|
||||
import type { ImageInfo } from './api/image-info.js';
|
||||
import type { PodInfo, PodInspectInfo } from './api/pod-info.js';
|
||||
|
|
@ -766,6 +767,29 @@ export class ContainerProviderRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
// method like remove Volume but instead of taking engineId/engineName it's taking connection info
|
||||
async deleteVolume(
|
||||
volumeName: string,
|
||||
options?: { provider?: ProviderContainerConnectionInfo | containerDesktopAPI.ContainerProviderConnection },
|
||||
): Promise<void> {
|
||||
let telemetryOptions = {};
|
||||
try {
|
||||
let matchingContainerProviderApi: Dockerode;
|
||||
if (options?.provider) {
|
||||
// grab all connections
|
||||
matchingContainerProviderApi = this.getMatchingEngineFromConnection(options.provider);
|
||||
} else {
|
||||
// Get the first running connection (preference for podman)
|
||||
matchingContainerProviderApi = this.getFirstRunningConnection()[1];
|
||||
}
|
||||
return matchingContainerProviderApi.getVolume(volumeName).remove();
|
||||
} catch (error) {
|
||||
telemetryOptions = { error: error };
|
||||
throw error;
|
||||
} finally {
|
||||
this.telemetryService.track('removeVolume', telemetryOptions);
|
||||
}
|
||||
}
|
||||
async removeVolume(engineId: string, volumeName: string): Promise<void> {
|
||||
let telemetryOptions = {};
|
||||
try {
|
||||
|
|
@ -1700,21 +1724,21 @@ export class ContainerProviderRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
async createVolume(selectedProvider: ProviderContainerConnectionInfo, options: VolumeCreateOptions): Promise<void> {
|
||||
async createVolume(
|
||||
selectedProvider?: ProviderContainerConnectionInfo | containerDesktopAPI.ContainerProviderConnection,
|
||||
options?: VolumeCreateOptions,
|
||||
): Promise<VolumeCreateResponseInfo> {
|
||||
let telemetryOptions = {};
|
||||
try {
|
||||
// filter from connections
|
||||
const matchingContainerProvider = Array.from(this.internalProviders.values()).find(
|
||||
containerProvider =>
|
||||
containerProvider.connection.endpoint.socketPath === selectedProvider.endpoint.socketPath &&
|
||||
containerProvider.connection.name === selectedProvider.name &&
|
||||
selectedProvider.status === 'started',
|
||||
);
|
||||
if (!matchingContainerProvider?.api) {
|
||||
throw new Error('No provider with a running engine');
|
||||
let matchingContainerProviderApi: Dockerode;
|
||||
if (selectedProvider) {
|
||||
// grab all connections
|
||||
matchingContainerProviderApi = this.getMatchingEngineFromConnection(selectedProvider);
|
||||
} else {
|
||||
// Get the first running connection (preference for podman)
|
||||
matchingContainerProviderApi = this.getFirstRunningConnection()[1];
|
||||
}
|
||||
|
||||
await matchingContainerProvider.api.createVolume(options);
|
||||
return matchingContainerProviderApi.createVolume(options);
|
||||
} catch (error) {
|
||||
telemetryOptions = { error: error };
|
||||
throw error;
|
||||
|
|
|
|||
|
|
@ -982,6 +982,17 @@ export class ExtensionLoader {
|
|||
): Promise<containerDesktopAPI.NetworkCreateResult> {
|
||||
return containerProviderRegistry.createNetwork(providerContainerConnection, networkCreateOptions);
|
||||
},
|
||||
listVolumes(): Promise<containerDesktopAPI.VolumeListInfo[]> {
|
||||
return containerProviderRegistry.listVolumes();
|
||||
},
|
||||
createVolume(
|
||||
volumeCreateOptions?: containerDesktopAPI.VolumeCreateOptions,
|
||||
): Promise<containerDesktopAPI.VolumeCreateResponseInfo> {
|
||||
return containerProviderRegistry.createVolume(volumeCreateOptions?.provider, volumeCreateOptions);
|
||||
},
|
||||
deleteVolume(volumeName: string, options?: containerDesktopAPI.VolumeDeleteOptions): Promise<void> {
|
||||
return containerProviderRegistry.deleteVolume(volumeName, options);
|
||||
},
|
||||
};
|
||||
|
||||
const authenticationProviderRegistry = this.authenticationProviderRegistry;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import type {
|
|||
ContainerInfo,
|
||||
SimpleContainerInfo,
|
||||
VolumeCreateOptions,
|
||||
VolumeCreateResponseInfo,
|
||||
} from './api/container-info.js';
|
||||
import type { ImageInfo } from './api/image-info.js';
|
||||
import type { PullEvent } from './api/pull-event.js';
|
||||
|
|
@ -1080,7 +1081,7 @@ export class PluginSystem {
|
|||
_listener,
|
||||
providerContainerConnectionInfo: ProviderContainerConnectionInfo,
|
||||
options: VolumeCreateOptions,
|
||||
): Promise<void> => {
|
||||
): Promise<VolumeCreateResponseInfo> => {
|
||||
return containerProviderRegistry.createVolume(providerContainerConnectionInfo, options);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue