-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #177 from dappnode/v0.1.20
v0.1.20 Release DAPPMANAGER Former-commit-id: 4cfc518
- Loading branch information
Showing
48 changed files
with
1,763 additions
and
1,127 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,20 @@ | ||
const parse = require('utils/parse'); | ||
const merge = require('utils/merge'); | ||
const {eventBus, eventBusTag} = require('eventBus'); | ||
const logs = require('logs.js')(module); | ||
const db = require('db'); | ||
// Modules | ||
const packages = require('modules/packages'); | ||
const dappGet = require('modules/dappGet'); | ||
const dappGetBasic = require('modules/dappGet/basic'); | ||
const getManifest = require('modules/getManifest'); | ||
const lockPorts = require('modules/lockPorts'); | ||
// Utils | ||
const logUI = require('utils/logUI'); | ||
const parse = require('utils/parse'); | ||
const merge = require('utils/merge'); | ||
const isIpfsRequest = require('utils/isIpfsRequest'); | ||
const getManifest = require('modules/getManifest'); | ||
const {eventBus, eventBusTag} = require('eventBus'); | ||
const isSyncing = require('utils/isSyncing'); | ||
const lockPorts = require('modules/lockPorts'); | ||
const shouldOpenPorts = require('modules/shouldOpenPorts'); | ||
const envsHelper = require('utils/envsHelper'); | ||
const parseManifestPorts = require('utils/parseManifestPorts'); | ||
|
||
/* eslint-disable max-len */ | ||
|
||
|
@@ -28,6 +33,10 @@ const shouldOpenPorts = require('modules/shouldOpenPorts'); | |
* | ||
* @param {Object} kwargs = { | ||
* id: package .eth name {String} | ||
* userSetEnvs: { | ||
* "kovan.dnp.dappnode.eth": { | ||
* "ENV_NAME": "VALUE1" | ||
* }, ... } | ||
* userSetVols: user set volumes {Object} = { | ||
* "kovan.dnp.dappnode.eth": { | ||
* "kovan:/root/.local/share/io.parity.ethereum/": "different_name" | ||
|
@@ -38,24 +47,22 @@ const shouldOpenPorts = require('modules/shouldOpenPorts'); | |
* "30303/udp": "31313:30303/udp" | ||
* }, ... } | ||
* logId: task id {String} | ||
* options: { | ||
* BYPASS_RESOLVER: true, | ||
* ... | ||
* } | ||
* } | ||
* @return {Object} A formated success message. | ||
* result: empty | ||
*/ | ||
const installPackage = async ({ | ||
id, | ||
userSetVols = {}, | ||
userSetPorts = {}, | ||
logId, | ||
options = {}, | ||
}) => { | ||
const installPackage = async ({id, userSetEnvs = {}, userSetVols = {}, userSetPorts = {}, logId, options = {}}) => { | ||
// 1. Parse the id into a request | ||
// id = '[email protected]' | ||
// req = { name: 'otpweb.dnp.dappnode.eth', ver: '0.1.4' } | ||
const req = parse.packageReq(id); | ||
|
||
// If the request is not from IPFS, check if the chain is syncing | ||
if (!isIpfsRequest(req) && await isSyncing()) { | ||
if (!isIpfsRequest(req) && (await isSyncing())) { | ||
throw Error('Mainnet is syncing'); | ||
} | ||
|
||
|
@@ -64,92 +71,90 @@ const installPackage = async ({ | |
// success: {'bind.dnp.dappnode.eth': '0.1.4'} | ||
// alreadyUpdated: {'bind.dnp.dappnode.eth': '0.1.2'} | ||
// } | ||
const result = options.BYPASS_RESOLVER | ||
? await dappGetBasic(req) | ||
: await dappGet(req); | ||
// Return error if the req couldn't be resolved | ||
const result = options.BYPASS_RESOLVER ? await dappGetBasic(req) : await dappGet(req); | ||
if (!result.success) { | ||
const errorMessage = `Request ${req.name}@${req.ver} could not be resolved: ${result.message}`; | ||
if (result.e) { | ||
result.e.message = errorMessage; | ||
throw result.e; | ||
} else { | ||
throw Error(errorMessage); | ||
} | ||
throw Error(`Request ${req.name}@${req.ver} could not be resolved: ${result.message}`); | ||
} | ||
logs.debug(`Successfully resolved request ${JSON.stringify(req)}:\n ${JSON.stringify(result, null, 2)}`); | ||
|
||
// 3. Format the request and filter out already updated packages | ||
Object.keys(result.alreadyUpdated || {}).forEach((name) => { | ||
logUI({logId, name, msg: 'Already updated'}); | ||
}); | ||
|
||
let pkgs = await Promise.all(Object.keys(result.success).map(async (name) => { | ||
// 3.2 Fetch manifest | ||
const ver = result.success[name]; | ||
let manifest = await getManifest({name, ver}); | ||
if (!manifest) throw Error('Missing manifest for '+name); | ||
|
||
// 3.3 Verify dncore condition | ||
// Prevent default values. Someone can try to spoof "isCore" in the manifest | ||
manifest.isCore = false; | ||
if (manifest.type == 'dncore') { | ||
if (options.BYPASS_CORE_RESTRICTION) { | ||
manifest.isCore = true; | ||
} else if ( | ||
// The origin must be the registry controlled by the DAppNode team, | ||
// and it must NOT come from ipfs, thus APM | ||
name.endsWith('.dnp.dappnode.eth') && !ver.startsWith('/ipfs/') | ||
) { | ||
let pkgs = await Promise.all( | ||
Object.keys(result.success).map(async (name) => { | ||
// 3.2 Fetch manifest | ||
const ver = result.success[name]; | ||
let manifest = await getManifest({name, ver}); | ||
if (!manifest) throw Error('Missing manifest for ' + name); | ||
|
||
// 3.3 Verify dncore condition | ||
// Prevent default values. Someone can try to spoof "isCore" in the manifest | ||
// The origin must be the registry controlled by the DAppNode team, and it must NOT come from ipfs, thus APM | ||
if (manifest.type == 'dncore') { | ||
if (!options.BYPASS_CORE_RESTRICTION && (!name.endsWith('.dnp.dappnode.eth') || ver.startsWith('/ipfs/'))) { | ||
throw Error(`Unverified core package ${name}, only allowed origin is .dnp.dappnode.eth APM registy`); | ||
} | ||
manifest.isCore = true; | ||
} else { | ||
throw Error(`Unverified core package ${name}, only allowed origin is .dnp.dappnode.eth APM registy`); | ||
} | ||
} | ||
|
||
// 3.4 Merge user set vols and ports | ||
manifest = merge.manifest.vols(manifest, userSetVols); | ||
manifest = merge.manifest.ports(manifest, userSetPorts); | ||
// 3.4 Merge user set vols and ports | ||
manifest = merge.manifest.vols(manifest, userSetVols); | ||
manifest = merge.manifest.ports(manifest, userSetPorts); | ||
|
||
// Return pkg object | ||
return { | ||
name, | ||
ver, | ||
manifest, | ||
}; | ||
})); | ||
// Return pkg object | ||
return {name, ver, manifest}; | ||
}) | ||
); | ||
logs.debug(`Processed manifests for: ${pkgs.map(({name}) => name).join(', ')}`); | ||
|
||
// 4. Download requested packages | ||
// 4. Download requested packages in paralel | ||
await Promise.all(pkgs.map((pkg) => packages.download({pkg, logId}))); | ||
logs.debug(`Successfully downloaded DNPs ${pkgs.map(({name}) => name).join(', ')}`); | ||
|
||
// 5. Run requested packages | ||
// Patch, install the dappmanager the last always | ||
const isDappmanager = (pkg) => pkg.manifest && pkg.manifest.name | ||
&& pkg.manifest.name.includes('dappmanager.dnp.dappnode.eth'); | ||
|
||
await Promise.all(pkgs | ||
.filter((pkg) => !isDappmanager(pkg)) | ||
.map((pkg) => packages.run({pkg, logId})) | ||
); | ||
|
||
const dappmanagerPkg = pkgs.find(isDappmanager); | ||
if (dappmanagerPkg) { | ||
await packages.run({pkg: dappmanagerPkg, logId}); | ||
} | ||
|
||
// 6. P2P ports: modify docker-compose + open ports | ||
// - lockPorts modifies the docker-compose and returns | ||
// portsToOpen = [ {number: 32769, type: 'UDP'}, ... ] | ||
// - managePorts calls UPnP to open the ports | ||
|
||
await Promise.all(pkgs.map(async (pkg) => { | ||
const portsToOpen = await lockPorts({pkg}); | ||
// Abort if there are no ports to open | ||
// Don't attempt to call UPnP if not necessary | ||
if (portsToOpen.length && await shouldOpenPorts()) { | ||
const kwargs = {action: 'open', ports: portsToOpen}; | ||
eventBus.emit(eventBusTag.call, {callId: 'managePorts', kwargs}); | ||
const isDappmanager = (pkg) => (pkg.manifest.name || '').includes('dappmanager.dnp.dappnode.eth'); | ||
for (const pkg of pkgs.sort((pkg) => (isDappmanager(pkg) ? 1 : -1))) { | ||
// 5. Set ENVs. Set userSetEnvs + the manifest defaults (if not previously set) | ||
const {name, isCore} = pkg.manifest; | ||
const defaultEnvs = envsHelper.getManifestEnvs(pkg.manifest) || {}; | ||
const previousEnvs = envsHelper.load(name, isCore) || {}; | ||
const _userSetEnvs = userSetEnvs[pkg.manifest.name] || {}; | ||
// Merge ENVs by priority, first userSet on installation, then previously set (on updates), or defaults (manifest) | ||
const envs = {...defaultEnvs, ...previousEnvs, ..._userSetEnvs}; | ||
envsHelper.write(name, isCore, envs); | ||
logs.debug(`Wrote envs for DNP ${name} ${isCore ? '(Core)' : ''}:\n ${JSON.stringify(envs, null, 2)}`); | ||
|
||
// 6. Run requested packages | ||
await packages.run({pkg, logId}); | ||
logs.debug(`Started (docker-compose up) DNP ${pkg.name}`); | ||
|
||
// 7. Open ports | ||
// 7A. Mapped ports: mappedPortsToOpen = [ {number: '30303', type: 'TCP'}, ... ] | ||
const mappedPortsToOpen = parseManifestPorts(pkg.manifest); | ||
|
||
// 7B. P2P ports: modify docker-compose + open ports | ||
// - lockPorts modifies the docker-compose and returns | ||
// lockedPortsToOpen = [ {number: '32769', type: 'UDP'}, ... ] | ||
// - managePorts calls UPnP to open the ports | ||
const lockedPortsToOpen = await lockPorts({pkg}); | ||
logs.debug(`Locked ${lockedPortsToOpen.length} ports of DNP ${pkg.name}: ${JSON.stringify(lockedPortsToOpen)}`); | ||
|
||
// Skip if there are no ports to open or if UPnP is not available | ||
const portsToOpen = [...mappedPortsToOpen, ...lockedPortsToOpen]; | ||
const upnpAvailable = await db.get('upnpAvailable'); | ||
if (portsToOpen.length && upnpAvailable) { | ||
eventBus.emit(eventBusTag.call, { | ||
callId: 'managePorts', | ||
kwargs: { | ||
action: 'open', | ||
ports: portsToOpen, | ||
}, | ||
}); | ||
logs.debug(`Emitted internal call to open ports: ${JSON.stringify(portsToOpen)}`); | ||
} | ||
})); | ||
} | ||
|
||
// Emit packages update | ||
eventBus.emit(eventBusTag.emitPackages); | ||
|
@@ -162,5 +167,4 @@ const installPackage = async ({ | |
}; | ||
}; | ||
|
||
|
||
module.exports = installPackage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.