Skip to content

Commit fbc19f9

Browse files
committed
Support Docker resolution of container link names
1 parent a8d57f6 commit fbc19f9

File tree

1 file changed

+68
-35
lines changed

1 file changed

+68
-35
lines changed

src/interceptors/docker/docker-networking.ts

Lines changed: 68 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -226,13 +226,11 @@ class DockerNetworkMonitor {
226226
}
227227

228228
private async refreshContainerHosts(container: Docker.ContainerInspectInfo) {
229-
this.setContainerMapping(container.Id, {
230-
// The container can send itself requests to its own hostname:
231-
[container.Config.Hostname]: new Set(container.NetworkSettings.IPAddress),
232-
// It can also have hosts configured via --add-host=host:ip, which adds them to
229+
this.setContainerMapping(container.Id,
230+
// Containers may have hosts configured via --add-host=host:ip, which adds them to
233231
// /etc/hosts. Note that we ignore conflicts here, and just pick the first result,
234232
// which seems to match how resolution against /etc/hosts works in general.
235-
...(_(container.HostConfig.ExtraHosts ?? [])
233+
_(container.HostConfig.ExtraHosts ?? [])
236234
.reverse() // We want first conflict to win, not last
237235
.map((hostPair) => hostPair.split(':'))
238236
.keyBy((hostParts) => hostParts[0])
@@ -245,8 +243,7 @@ class DockerNetworkMonitor {
245243
: new Set([address])
246244
)
247245
.valueOf()
248-
)
249-
});
246+
);
250247
}
251248

252249
private async refreshAllNetworks() {
@@ -273,34 +270,70 @@ class DockerNetworkMonitor {
273270
return {};
274271
}
275272

276-
// Build a map of each container's aliases to its IP on this network
277-
return networkContainers.reduce((aliasMap, container) => {
278-
const networkConfig: Docker.EndpointSettings | undefined =
279-
_.find(container.NetworkSettings.Networks, { NetworkID: networkId });
280-
281-
if (!networkConfig || !networkConfig.IPAddress) return aliasMap;
282-
283-
/*
284-
* What names are resolveable on a network?
285-
*
286-
* On a default bridge network: hostnames are self-resolveable (handled by
287-
* refreshContainerHosts), and that's it unless links are used. No aliases are defined.
288-
*
289-
* On a custom bridge network: hostnames are fully resolveable, as are container
290-
* ids, plus any custom aliases defined in network config. All defined in Aliases.
291-
*
292-
* On a host network: everything resolves as on the host (so we do nothing). Since
293-
* there's no actual network involved, we never get here anyway.
294-
*
295-
* Overlay etc out of scope for now.
296-
*/
297-
298-
const aliases = networkConfig.Aliases ?? [];
299-
300-
aliases.forEach((alias) => {
301-
if (!aliasMap[alias]) aliasMap[alias] = new Set();
302-
aliasMap[alias].add(networkConfig.IPAddress!);
303-
});
273+
/*
274+
* So, what names are resolveable on a network?
275+
*
276+
* On a default bridge network: hostnames are self-resolveable,
277+
* and that's it unless links are used. No aliases are defined.
278+
*
279+
* On a custom bridge network: hostnames are fully resolveable, as are container
280+
* ids, plus any custom aliases defined in network config. All defined in Aliases.
281+
*
282+
* On a host network: everything resolves as on the host (so we do nothing). Since
283+
* there's no actual network involved, we never get here anyway.
284+
*
285+
* On any network: linked containers can be referenced by their real name or by
286+
* their link alias name.
287+
*
288+
* Overlay etc out of scope for now.
289+
*/
290+
291+
const aliasPairs = await Promise.all(
292+
networkContainers.map(async (container): Promise<Array<readonly [string, string]>> => {
293+
const networkConfig: Docker.EndpointSettings | undefined =
294+
_.find(container.NetworkSettings.Networks, { NetworkID: networkId });
295+
296+
if (!networkConfig || !networkConfig.IPAddress) return [];
297+
298+
const aliasNames = [
299+
...(networkConfig.Aliases || []),
300+
// We resolve hostnames here, not on container creation, because the IP isn't
301+
// set at container creation time.
302+
container.Config.Hostname
303+
];
304+
305+
const aliasMap = aliasNames.map((alias) => [alias, networkConfig.IPAddress!] as const);
306+
307+
// This queries afresh for each linked container's info. Ignoring for now, since it's very cheap
308+
// and container links are a legacy Docker feature anyway.
309+
const linkStrings: string[] = container.HostConfig.Links || [];
310+
const linkMap = await Promise.all(linkStrings.map(async (link) => {
311+
// Aliases are of the form:
312+
// /compose_default-service-a_1_HTK8000:/compose_linked-service-b_1_HTK8000/a
313+
// I.e. service-a is linked by service-b with alias 'a'.
314+
const endOfContainerName = link.indexOf(':/');
315+
const aliasIndex = link.lastIndexOf('/');
316+
317+
const linkedContainerName = link.slice(1, endOfContainerName);
318+
const linkAlias = link.slice(aliasIndex + 1); // +1 to drop leading slash
319+
320+
const linkedContainer = await this.docker.getContainer(linkedContainerName).inspect();
321+
const linkedContainerIp = linkedContainer.NetworkSettings.Networks[networkId]?.IPAddress ||
322+
linkedContainer.NetworkSettings.IPAddress;
323+
return [linkAlias, linkedContainerIp] as const;
324+
}));
325+
326+
return [
327+
...aliasMap,
328+
...linkMap
329+
];
330+
})
331+
);
332+
333+
// Turn those arrays of pairs into a single string -> set map.
334+
return _.flatten(aliasPairs).reduce((aliasMap, [alias, target]) => {
335+
if (!aliasMap[alias]) aliasMap[alias] = new Set();
336+
aliasMap[alias].add(target);
304337
return aliasMap;
305338
}, {} as { [alias: string]: Set<string> });
306339
}

0 commit comments

Comments
 (0)