-
Notifications
You must be signed in to change notification settings - Fork 39
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
Update docker alias migrations #1589
Changes from all commits
dfa9fa8
fa5357e
eebc3e2
fe8094d
be3cb16
2f2818a
644b36b
38af866
914c995
0fbab04
ea388e1
aed6be9
ddc7f13
4cf0e59
88fa4e9
be46a00
722629c
62fd83a
a85751c
a6ed9fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ import { ComposeNetwork, ComposeServiceNetwork } from "@dappnode/types"; | |
import Dockerode from "dockerode"; | ||
import { uniq } from "lodash-es"; | ||
import { PackageContainer } from "@dappnode/common"; | ||
import { getPrivateNetworkAlias } from "../../domains.js"; | ||
import { getPrivateNetworkAliases } from "../../domains.js"; | ||
import { logs } from "@dappnode/logger"; | ||
import { params } from "@dappnode/params"; | ||
import { parseComposeSemver } from "../../utils/sanitizeVersion.js"; | ||
|
@@ -26,140 +26,164 @@ const dncoreNetworkName = params.DNP_PRIVATE_NETWORK_NAME; | |
* DAPPMANAGER updates from <= v0.2.38 must manually add aliases | ||
* to all running containers. | ||
* This will run every single time dappmanager restarts and will list al packages | ||
* and do docker inspect. | ||
* and do docker inspect. This migration tries to assure that: | ||
* Having a package name "example.dnp.dappnode.eth" the aliases should be: | ||
* "example.dappnode" if the package is mono service | ||
* "service1.example.dappnode" if the package is multiservice | ||
* "service1.example.dappnode" and "example.dappnode" if the package is multiservice and has in manifest mainservice | ||
*/ | ||
export async function addAliasToRunningContainers(): Promise<void> { | ||
for (const container of await listContainers()) { | ||
const containerName = container.containerName; | ||
const alias = getPrivateNetworkAlias(container); | ||
|
||
try { | ||
// Info from docker inspect and compose file might be not-syncrhnonyzed | ||
// So this function must be before the check hasAlias() | ||
migrateCoreNetworkAndAliasInCompose(container, alias); | ||
|
||
const currentEndpointConfig = await getDnCoreNetworkContainerConfig( | ||
containerName | ||
); | ||
if (hasAlias(currentEndpointConfig, alias)) continue; | ||
const endpointConfig: Partial<Dockerode.NetworkInfo> = { | ||
...currentEndpointConfig, | ||
Aliases: [...(currentEndpointConfig?.Aliases || []), alias] | ||
}; | ||
|
||
// Wifi and VPN containers needs a refresh connect due to its own network configuration | ||
if ( | ||
container.containerName === params.vpnContainerName || | ||
container.containerName === params.wifiContainerName | ||
) { | ||
await shell(`docker rm ${containerName} --force`); | ||
await dockerComposeUp( | ||
getPath.dockerCompose(container.dnpName, container.isCore) | ||
); | ||
} else { | ||
await dockerNetworkDisconnect(dncoreNetworkName, containerName); | ||
await dockerNetworkConnect( | ||
dncoreNetworkName, | ||
containerName, | ||
endpointConfig | ||
); | ||
} | ||
logs.info(`Added alias to running container ${container.containerName}`); | ||
} catch (e) { | ||
logs.error(`Error adding alias to container ${containerName}`, e); | ||
} | ||
try { | ||
const containers = await listContainers(); | ||
await addAliasToGivenContainers(containers); | ||
} catch (error) { | ||
logs.error('Error adding alias to running containers:', error); | ||
} | ||
} | ||
|
||
/** Return true if endpoint config exists and has alias */ | ||
function hasAlias( | ||
endpointConfig: Dockerode.NetworkInfo | null, | ||
alias: string | ||
): boolean { | ||
return Boolean( | ||
endpointConfig && | ||
endpointConfig.Aliases && | ||
Array.isArray(endpointConfig.Aliases) && | ||
endpointConfig.Aliases.includes(alias) | ||
); | ||
export async function addAliasToGivenContainers(containers: PackageContainer[]): Promise<void> { | ||
for (const container of containers) { | ||
|
||
const isMainOrMonoService = container.isMain ?? false; // Set a default value of false if isMain is undefined | ||
const service = { serviceName: container.serviceName, dnpName: container.dnpName, isMainOrMonoService } | ||
const aliases = getPrivateNetworkAliases(service) | ||
|
||
// Adds aliases to the compose file that generated the container | ||
migrateCoreNetworkAndAliasInCompose(container, aliases); | ||
|
||
// Adds aliases to the container network | ||
for (const alias of aliases) { | ||
const currentEndpointConfig = await getDnCoreNetworkContainerConfig(container.containerName); | ||
if (!hasAlias(currentEndpointConfig, alias)) { | ||
const updatedConfig = updateEndpointConfig(currentEndpointConfig, alias); | ||
await updateContainerNetwork(dncoreNetworkName, container, updatedConfig); | ||
logs.info(`alias ${alias} added to ${container.containerName}`); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Get compose file network and compose network settings from dncore_network | ||
* And rewrites the compose with the core network edited | ||
/** Gets the docker-compose.yml file of the given `container` and adds one or more alias | ||
* to the service that started `container`. All alias are added to the network defined by | ||
* `params.DNP_PRIVATE_NETWORK_NAME`. | ||
* | ||
* @param container PackageContainer | ||
* @param aliases string[] | ||
* @returns void | ||
*/ | ||
export function migrateCoreNetworkAndAliasInCompose( | ||
container: PackageContainer, | ||
alias: string | ||
aliases: string[] | ||
): void { | ||
const compose = new ComposeFileEditor(container.dnpName, container.isCore); | ||
|
||
// 1. Get compose network settings | ||
const composeNetwork = compose.getComposeNetwork( | ||
params.DNP_PRIVATE_NETWORK_NAME | ||
); | ||
|
||
// 2. Get compose service network settings | ||
const composeService = compose.services()[container.serviceName]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I liked this better, looks cleaner, but as you prefer |
||
|
||
// Gets all the networks defined in the service | ||
const serviceNetworks = parseServiceNetworks( | ||
composeService.get().networks || {} | ||
compose.services()[container.serviceName].get().networks || {} | ||
); | ||
|
||
// Gets current aliases of "params.DNP_PRIVATE_NETWORK_NAME", usually dncore_network | ||
const currentAliases = serviceNetworks[params.DNP_PRIVATE_NETWORK_NAME]?.aliases || []; | ||
|
||
const serviceNetwork = | ||
serviceNetworks[params.DNP_PRIVATE_NETWORK_NAME_FROM_CORE] ?? null; | ||
//add new aliases to current aliases set | ||
const newAliases = uniq([...currentAliases, ...aliases]); | ||
|
||
// 3. Check if migration was done | ||
if ( | ||
isComposeNetworkAndAliasMigrated( | ||
composeNetwork, | ||
serviceNetwork, | ||
compose.compose.version, | ||
alias | ||
) | ||
) | ||
return; | ||
// Check if migration was done | ||
const composeNetwork = compose.getComposeNetwork(params.DNP_PRIVATE_NETWORK_NAME); | ||
const serviceNetwork = serviceNetworks[params.DNP_PRIVATE_NETWORK_NAME] ?? null; | ||
|
||
// 4. Ensure compose file version 3.5 | ||
// Return if migration was done, compose is already updated | ||
if (isComposeNetworkAndAliasMigrated(composeNetwork, serviceNetwork, compose.compose.version, newAliases)) return | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
// Ensure/update compose file version 3.5 | ||
compose.compose = { | ||
...compose.compose, | ||
version: params.MINIMUM_COMPOSE_VERSION | ||
}; | ||
|
||
// 5. Add network and alias | ||
if (composeNetwork || serviceNetwork) | ||
// composeNetwork and serviceNetwork might be null and have different values (eitherway it should be the same) | ||
// Only remove network if exists | ||
composeService.removeNetwork(params.DNP_PRIVATE_NETWORK_NAME_FROM_CORE); | ||
|
||
const aliases = uniq([...(serviceNetwork?.aliases || []), alias]); | ||
composeService.addNetwork( | ||
// This adds the new network with the new aliases into the compose file | ||
compose.services()[container.serviceName].addNetwork( | ||
params.DNP_PRIVATE_NETWORK_NAME, | ||
{ ...serviceNetwork, aliases }, | ||
{ external: true, name: params.DNP_PRIVATE_NETWORK_NAME } //...networkConfig, | ||
{ ...serviceNetwork, aliases: newAliases }, | ||
{ external: true, name: params.DNP_PRIVATE_NETWORK_NAME } | ||
); | ||
|
||
compose.write(); | ||
} | ||
|
||
// function isMainServiceOfMultiServicePackage(container: PackageContainer): boolean { | ||
// const compose = new ComposeFileEditor(container.dnpName, container.isCore); | ||
// const services = compose.services(); // Invoke the services function | ||
// if (Object.keys(services).length > 1 && container.isMain) return true; | ||
// return false; | ||
// } | ||
|
||
function updateEndpointConfig(currentEndpointConfig: Dockerode.NetworkInfo | null, alias: string) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would call this function |
||
return { | ||
...currentEndpointConfig, | ||
Aliases: [...(currentEndpointConfig?.Aliases || []), alias] | ||
}; | ||
} | ||
|
||
async function updateContainerNetwork(networkName: string, container: any, endpointConfig: Partial<Dockerode.NetworkInfo>): Promise<void> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe |
||
const containerName = container.containerName; | ||
|
||
// Wifi and VPN containers need a refresh connect due to their own network configuration | ||
if (containerName === params.vpnContainerName || containerName === params.wifiContainerName) { | ||
await shell(`docker rm ${containerName} --force`); | ||
await dockerComposeUp(getPath.dockerCompose(container.dnpName, container.isCore)); | ||
} else { | ||
await dockerNetworkDisconnect(networkName, containerName); | ||
console.log(`new alias for: ${containerName}`); | ||
await dockerNetworkConnect(networkName, containerName, endpointConfig); | ||
} | ||
} | ||
|
||
/** Return true if endpoint config exists, has an array of Alisases and it contains the alias | ||
* @param alias | ||
* @returns boolean | ||
*/ | ||
function hasAlias( | ||
endpointConfig: Dockerode.NetworkInfo | null, | ||
alias: string | ||
): boolean { | ||
return Boolean( | ||
endpointConfig && | ||
endpointConfig.Aliases && | ||
Array.isArray(endpointConfig.Aliases) && | ||
endpointConfig.Aliases.includes(alias) | ||
); | ||
} | ||
|
||
/** Return true if docker-compose.yml file has already been updated with aliases, false otherwise. | ||
* @param aliases | ||
* @returns boolean | ||
*/ | ||
function isComposeNetworkAndAliasMigrated( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could divide this into: This way you would keep the aim of each function unique and error logging would be more verbose |
||
composeNetwork: ComposeNetwork | null, | ||
serviceNetwork: ComposeServiceNetwork | null, | ||
composeVersion: string, | ||
alias: string | ||
aliases: string[] | ||
): boolean { | ||
// 1. Migration undone for aliases or networks or both => return false | ||
if (!composeNetwork || !serviceNetwork) return false; // Consider as not migrated if either composeNetwork or serviceNetwork are not present | ||
// 2. Migration done for aliases and networks => return true | ||
// 1. Expected network is not present either in compose or in service => not migrated | ||
if (!composeNetwork || !serviceNetwork) return false; | ||
|
||
// 2. Aside from being at least version 3.5, to consider the docker-compose.yml file as migrated, the network defined in the compose file must: | ||
// - be external | ||
// - have the expected name | ||
// - have the expected aliases in each service | ||
if ( | ||
composeNetwork?.name === params.DNP_PRIVATE_NETWORK_NAME && // Check property name is defined | ||
composeNetwork?.name === params.DNP_PRIVATE_NETWORK_NAME && // Check expected name | ||
composeNetwork?.external && // Check is external network | ||
gte( | ||
parseComposeSemver(composeVersion), | ||
parseComposeSemver(params.MINIMUM_COMPOSE_VERSION) | ||
) && // Check version is at least 3.5 | ||
serviceNetwork.aliases?.includes(alias) // Check alias has been added | ||
aliases.every(alias => serviceNetwork.aliases?.includes(alias)) // Check every alias is already present | ||
) | ||
return true; | ||
|
||
return false; // In other cases return false | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we additionally supported
service.example.dappnode
for mono service packages?@Marketen @3alpha