Skip to content

Commit

Permalink
feat: reduce API calls when listing containers
Browse files Browse the repository at this point in the history
related to podman-desktop#3376
Signed-off-by: Florent Benoit <[email protected]>
  • Loading branch information
benoitf committed Aug 14, 2023
1 parent 9913871 commit 63465ca
Show file tree
Hide file tree
Showing 6 changed files with 335 additions and 31 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
"check-disk-space": "^3.4.0",
"chokidar": "^3.5.3",
"compare-versions": "^6.1.0",
"date.js": "^0.3.3",
"dockerode": "^3.3.5",
"electron-context-menu": "^3.6.1",
"electron-updater": "6.1.1",
Expand Down
18 changes: 17 additions & 1 deletion packages/main/src/plugin/api/container-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@

import type Dockerode from 'dockerode';

export interface ContainerInfo extends Dockerode.ContainerInfo {
export interface ContainerPortInfo {
IP: string;
PrivatePort: number;
PublicPort: number;
Type: string;
}

export interface ContainerInfo {
engineId: string;
engineName: string;
engineType: 'podman' | 'docker';
Expand All @@ -29,6 +36,15 @@ export interface ContainerInfo extends Dockerode.ContainerInfo {
status: string;
engineId: string;
};
Id: string;
Names: string[];
Image: string;
ImageID: string;
Command: string;
Created: number;
Ports: ContainerPortInfo[];
Labels: { [label: string]: string };
State: string;
}

export interface SimpleContainerInfo extends Dockerode.ContainerInfo {
Expand Down
211 changes: 211 additions & 0 deletions packages/main/src/plugin/container-registry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ import type { ApiSenderType } from '/@/plugin/api.js';
import Dockerode from 'dockerode';
import { EventEmitter } from 'node:events';
import type * as podmanDesktopAPI from '@podman-desktop/api';
import nock from 'nock';
import { LibpodDockerode } from './dockerode/libpod-dockerode.js';
import moment from 'moment';

/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-null/no-null */

const fakeContainerWithComposeProject: Dockerode.ContainerInfo = {
Id: '1234567890',
Expand Down Expand Up @@ -523,3 +527,210 @@ test('getFirstRunningConnection', async () => {
expect(connection[0].name).toBe('podman1');
expect(connection[0].endpoint.socketPath).toBe('/podman1.socket');
});

describe('listContainers', () => {
test('list containers with Podman API', async () => {
const containersWithPodmanAPI = [
{
AutoRemove: false,
Command: ['httpd-foreground'],
Created: '2023-08-10T15:37:44.555961563+02:00',
CreatedAt: '',
Exited: true,
ExitedAt: 1691674673,
ExitCode: 0,
Id: '31a4b282691420be2611817f203765402d8da7e13cd530f80a6ddd1bb4aa63b4',
Image: 'docker.io/library/httpd:latest',
ImageID: '911d72fc5020723f0c003a134a8d2f062b4aea884474a11d1db7dcd28ce61d6a',
IsInfra: false,
Labels: {
'io.buildah.version': '1.30.0',
maintainer: 'Podman Maintainers',
},
Mounts: [],
Names: ['admiring_wing'],
Namespaces: {},
Networks: ['podman'],
Pid: 0,
Pod: '',
PodName: '',
Ports: [
{
host_ip: '',
container_port: 8080,
host_port: 8080,
range: 1,
protocol: 'tcp',
},
],
Restarts: 0,
Size: null,
StartedAt: 1691674664,
State: 'running',
Status: '',
},
];

nock('http://localhost').get('/v4.2.0/libpod/containers/json?all=true').reply(200, containersWithPodmanAPI);

// mock listPods

nock('http://localhost').get('/v4.2.0/libpod/pods/json').reply(200, []);

const dockerAPI = new Dockerode({ protocol: 'http', host: 'localhost' });

const libpod = new LibpodDockerode();
libpod.enhancePrototypeWithLibPod();

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

const containers = await containerRegistry.listContainers();

// ensure the field are correct
expect(containers).toBeDefined();
expect(containers).toHaveLength(1);
const container = containers[0];
expect(container.engineId).toBe('podman1');
expect(container.engineName).toBe('podman');
expect(container.engineType).toBe('podman');
expect(container.StartedAt).toBe('2023-08-10T13:37:44.000Z');
expect(container.pod).toBeUndefined();
expect(container.Id).toBe('31a4b282691420be2611817f203765402d8da7e13cd530f80a6ddd1bb4aa63b4');
expect(container.Command).toBe('httpd-foreground');
expect(container.Names).toStrictEqual(['/admiring_wing']);
expect(container.Image).toBe('docker.io/library/httpd:latest');
expect(container.ImageID).toBe('sha256:911d72fc5020723f0c003a134a8d2f062b4aea884474a11d1db7dcd28ce61d6a');
expect(container.Created).toBe(1691674664);
expect(container.Ports).toStrictEqual([
{
IP: '',
PrivatePort: 8080,
PublicPort: 8080,
Type: 'tcp',
},
]);
expect(container.Labels).toStrictEqual({
'io.buildah.version': '1.30.0',
maintainer: 'Podman Maintainers',
});
expect(container.State).toBe('running');
});

test('list containers with Docker API', async () => {
const containersWithDockerAPI = [
{
Id: '31a4b282691420be2611817f203765402d8da7e13cd530f80a6ddd1bb4aa63b4',
Names: ['/admiring_wing'],
Image: 'docker.io/library/httpd:latest',
ImageID: 'sha256:911d72fc5020723f0c003a134a8d2f062b4aea884474a11d1db7dcd28ce61d6a',
Command: 'httpd-foreground',
Created: 1691674664,
Ports: [
{
PrivatePort: 8080,
PublicPort: 8080,
Type: 'tcp',
},
],
Labels: {
'io.buildah.version': '1.30.0',
maintainer: 'Podman Maintainers',
},
State: 'running',
Status: 'Up 2 minutes',
NetworkSettings: {
Networks: {
podman: {
IPAMConfig: null,
Links: null,
Aliases: ['31a4b2826914'],
NetworkID: 'podman',
EndpointID: '',
Gateway: '10.88.0.1',
IPAddress: '10.88.0.4',
IPPrefixLen: 16,
IPv6Gateway: '',
GlobalIPv6Address: '',
GlobalIPv6PrefixLen: 0,
MacAddress: '7e:49:fe:9b:2e:3a',
DriverOpts: null,
},
},
},
Mounts: [],
Name: '',
Config: null,
NetworkingConfig: null,
Platform: null,
AdjustCPUShares: false,
},
];

nock('http://localhost').get('/containers/json?all=true').reply(200, containersWithDockerAPI);

// mock listPods

nock('http://localhost').get('/v4.2.0/libpod/pods/json').reply(200, []);

const dockerAPI = new Dockerode({ protocol: 'http', host: 'localhost' });

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

const containers = await containerRegistry.listContainers();

// ensure the field are correct
expect(containers).toBeDefined();
expect(containers).toHaveLength(1);
const container = containers[0];
expect(container.engineId).toBe('docker1');
expect(container.engineName).toBe('docker');
expect(container.engineType).toBe('docker');

// grab StartedAt from the containerWithDockerAPI
const started = container.StartedAt;

//convert with moment
const diff = moment.now() - moment(started).toDate().getTime();
const delta = Math.round(moment.duration(diff).asMinutes());

// expect delta to be 2 minutes
expect(delta).toBe(2);
expect(container.pod).toBeUndefined();

expect(container.Id).toBe('31a4b282691420be2611817f203765402d8da7e13cd530f80a6ddd1bb4aa63b4');
expect(container.Command).toBe('httpd-foreground');
expect(container.Names).toStrictEqual(['/admiring_wing']);
expect(container.Image).toBe('docker.io/library/httpd:latest');
expect(container.ImageID).toBe('sha256:911d72fc5020723f0c003a134a8d2f062b4aea884474a11d1db7dcd28ce61d6a');
expect(container.Created).toBe(1691674664);
expect(container.Ports).toStrictEqual([
{
PrivatePort: 8080,
PublicPort: 8080,
Type: 'tcp',
},
]);
expect(container.Labels).toStrictEqual({
'io.buildah.version': '1.30.0',
maintainer: 'Podman Maintainers',
});
expect(container.State).toBe('running');
});
});
Loading

0 comments on commit 63465ca

Please sign in to comment.