Skip to content

Commit

Permalink
Merge pull request #177 from dappnode/v0.1.20
Browse files Browse the repository at this point in the history
v0.1.20 Release DAPPMANAGER

Former-commit-id: 4cfc518
  • Loading branch information
eduadiez authored Feb 18, 2019
2 parents bbac606 + 812f920 commit 7f73bc3
Show file tree
Hide file tree
Showing 48 changed files with 1,763 additions and 1,127 deletions.
16 changes: 7 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ stages:

jobs:
include:
# In app tests
# In app unit tests
##############
- stage: test
env:
- IN_APP_TEST
name: In app unit tests
node_js:
- "8.9.4"
cache:
Expand All @@ -32,11 +31,10 @@ jobs:
after_success: # Upload coverage reports
- COVERALLS_REPO_TOKEN=$coveralls_repo_token npm run coveralls

# integration tests
# Integration tests
###################
- stage: test
env:
- INTEGRATION_TEST
name: Integration tests
node_js:
- "8.9.4"
services:
Expand Down Expand Up @@ -69,8 +67,8 @@ jobs:
script:
- npm install -g @dappnode/dappnodesdk
- docker-compose build
- dappnodesdk publish patch -p infura

- TYPE=${PUBLISH:-patch}
- dappnodesdk publish ${TYPE} -p infura
before_deploy:
- wget https://raw.githubusercontent.com/dappnode/DAppNode/master/scripts/before_deploy.sh
- source before_deploy.sh
Expand All @@ -91,4 +89,4 @@ jobs:
condition: "$TRAVIS_TAG =~ ^release*$"
after_deploy:
- wget https://raw.githubusercontent.com/dappnode/DAppNode/master/scripts/after_deploy.sh
- source after_deploy.sh
- source after_deploy.sh
172 changes: 88 additions & 84 deletions build/src/src/calls/installPackage.js
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 */

Expand All @@ -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"
Expand All @@ -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');
}

Expand All @@ -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);
Expand All @@ -162,5 +167,4 @@ const installPackage = async ({
};
};


module.exports = installPackage;
31 changes: 12 additions & 19 deletions build/src/src/calls/listPackages.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
const fs = require('fs');
const params = require('params');
const logs = require('logs.js')(module);
// Modules
const dockerList = require('modules/dockerList');
const docker = require('modules/docker');
// Utils
const parseDockerSystemDf = require('utils/parseDockerSystemDf');
const getPath = require('utils/getPath');
const parse = require('utils/parse');
const params = require('params');
const logs = require('logs.js')(module);
const envsHelper = require('utils/envsHelper');

// This call can fail because of:
// Error response from daemon: a disk usage operation is already running
Expand All @@ -20,7 +22,6 @@ async function dockerSystemDf() {
return cacheResult;
}


/**
* Returns the list of current containers associated to packages
*
Expand Down Expand Up @@ -55,26 +56,19 @@ const listPackages = async () => {
const dockerSystemDfData = await dockerSystemDf();
dnpList = parseDockerSystemDf({data: dockerSystemDfData, dnpList});
} catch (e) {
logs.error('Error appending volume info in listPackages call: '+e.stack);
logs.error('Error appending volume info in listPackages call: ' + e.stack);
}


// Append envFile and manifest
dnpList.map((dnp) => {
const PACKAGE_NAME = dnp.name;
const IS_CORE = dnp.isCORE;

// Add env info
const ENV_FILE = getPath.envFile(PACKAGE_NAME, params, IS_CORE);
if (fs.existsSync(ENV_FILE)) {
let envFileData = fs.readFileSync(ENV_FILE, 'utf8');
dnp.envs = parse.envFile(envFileData);
}
// Add env info, only if there are ENVs
const envs = envsHelper.load(dnp.name, dnp.isCORE || dnp.isCore);
if (Object.keys(envs).length) dnp.envs = envs;

// Add manifest
let MANIFEST_FILE = getPath.manifest(PACKAGE_NAME, params, IS_CORE);
if (fs.existsSync(MANIFEST_FILE)) {
let manifestFileData = fs.readFileSync(MANIFEST_FILE, 'utf8');
const manifestPath = getPath.manifest(dnp.name, params, dnp.isCORE || dnp.isCore);
if (fs.existsSync(manifestPath)) {
const manifestFileData = fs.readFileSync(manifestPath, 'utf8');
dnp.manifest = JSON.parse(manifestFileData);
}
});
Expand All @@ -85,5 +79,4 @@ const listPackages = async () => {
};
};


module.exports = listPackages;
Loading

0 comments on commit 7f73bc3

Please sign in to comment.