fix: container port duplication

This commit is contained in:
Pujit Mehrotra
2025-11-11 11:12:06 -05:00
parent 79b39fb49b
commit fafdaf989c
2 changed files with 76 additions and 2 deletions

View File

@@ -10,7 +10,11 @@ import { pubsub, PUBSUB_CHANNEL } from '@app/core/pubsub.js';
import { DockerConfigService } from '@app/unraid-api/graph/resolvers/docker/docker-config.service.js';
import { DockerManifestService } from '@app/unraid-api/graph/resolvers/docker/docker-manifest.service.js';
import { DockerTemplateScannerService } from '@app/unraid-api/graph/resolvers/docker/docker-template-scanner.service.js';
import { ContainerState, DockerContainer } from '@app/unraid-api/graph/resolvers/docker/docker.model.js';
import {
ContainerPortType,
ContainerState,
DockerContainer,
} from '@app/unraid-api/graph/resolvers/docker/docker.model.js';
import { DockerService } from '@app/unraid-api/graph/resolvers/docker/docker.service.js';
import { NotificationsService } from '@app/unraid-api/graph/resolvers/notifications/notifications.service.js';
@@ -647,6 +651,53 @@ describe('DockerService', () => {
expect(mockCacheManager.set).not.toHaveBeenCalled(); // Ensure cache is NOT set on error
});
describe('transformContainer', () => {
it('deduplicates ports that only differ by bound IP addresses', () => {
mockEmhttpGetter.mockReturnValue({
networks: [{ ipaddr: ['192.168.0.10'] }],
var: {},
});
const container = {
Id: 'duplicate-ports',
Names: ['/duplicate-ports'],
Image: 'test-image',
ImageID: 'sha256:123',
Command: 'test',
Created: 1700000000,
State: 'running',
Status: 'Up 2 hours',
Ports: [
{ IP: '0.0.0.0', PrivatePort: 8080, PublicPort: 8080, Type: 'tcp' },
{ IP: '::', PrivatePort: 8080, PublicPort: 8080, Type: 'tcp' },
{ IP: '0.0.0.0', PrivatePort: 5000, PublicPort: 5000, Type: 'udp' },
],
Labels: {},
HostConfig: { NetworkMode: 'bridge' },
NetworkSettings: { Networks: {} },
Mounts: [],
} as Docker.ContainerInfo;
const transformed = service.transformContainer(container);
expect(transformed.ports).toEqual([
{
ip: '0.0.0.0',
privatePort: 8080,
publicPort: 8080,
type: ContainerPortType.TCP,
},
{
ip: '0.0.0.0',
privatePort: 5000,
publicPort: 5000,
type: ContainerPortType.UDP,
},
]);
expect(transformed.lanIpPorts).toBe('192.168.0.10:8080, 192.168.0.10:5000');
});
});
describe('getAppInfo', () => {
// Common mock containers for these tests
const mockContainersForMethods = [

View File

@@ -115,6 +115,28 @@ export class DockerService {
return firstName ? firstName.replace(/^\//, '') : null;
}
private deduplicateContainerPorts(
ports: Docker.ContainerInfo['Ports'] | undefined
): Docker.ContainerInfo['Ports'] {
if (!Array.isArray(ports)) {
return [];
}
const seen = new Set<string>();
const uniquePorts: Docker.ContainerInfo['Ports'] = [];
for (const port of ports) {
const key = `${port.PrivatePort ?? ''}-${port.PublicPort ?? ''}-${(port.Type ?? '').toLowerCase()}`;
if (seen.has(key)) {
continue;
}
seen.add(key);
uniquePorts.push(port);
}
return uniquePorts;
}
public getDockerClient() {
return new Docker({
socketPath: '/var/run/docker.sock',
@@ -151,8 +173,9 @@ export class DockerService {
const autoStartEntry = primaryName ? this.autoStartEntryByName.get(primaryName) : undefined;
const lanIp = getLanIp();
const lanPortStrings: string[] = [];
const uniquePorts = this.deduplicateContainerPorts(container.Ports);
const transformedPorts = container.Ports.map((port) => {
const transformedPorts = uniquePorts.map((port) => {
if (port.PublicPort) {
const lanPort = lanIp ? `${lanIp}:${port.PublicPort}` : `${port.PublicPort}`;
if (lanPort) {