mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2025-06-17 14:58:09 +00:00
Update to @balena/lint 5.x
Change-type: patch
This commit is contained in:
2769
package-lock.json
generated
2769
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,7 @@
|
|||||||
"test:build": "npm run typescript:test-build && npm run coffeescript:test && npm run testitems:copy && npm run packagejson:copy",
|
"test:build": "npm run typescript:test-build && npm run coffeescript:test && npm run testitems:copy && npm run packagejson:copy",
|
||||||
"test:fast": "TEST=1 mocha --opts test/fast-mocha.opts",
|
"test:fast": "TEST=1 mocha --opts test/fast-mocha.opts",
|
||||||
"test:debug": "npm run test:build && TEST=1 mocha --inspect-brk",
|
"test:debug": "npm run test:build && TEST=1 mocha --inspect-brk",
|
||||||
"prettify": "balena-lint -e ts -e js --typescript --fix src/ test/ typings/",
|
"prettify": "balena-lint -e ts -e js --typescript --fix src/ test/ typings/ build-utils/",
|
||||||
"typescript:test-build": "tsc --project tsconfig.json",
|
"typescript:test-build": "tsc --project tsconfig.json",
|
||||||
"typescript:release": "tsc --project tsconfig.release.json && cp -r build/src/* build && rm -rf build/src",
|
"typescript:release": "tsc --project tsconfig.release.json && cp -r build/src/* build && rm -rf build/src",
|
||||||
"coffeescript:test": "coffee -m -c -o build .",
|
"coffeescript:test": "coffee -m -c -o build .",
|
||||||
@ -38,7 +38,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@balena/contrato": "^0.2.1",
|
"@balena/contrato": "^0.2.1",
|
||||||
"@balena/lint": "^4.1.0",
|
"@balena/lint": "^5.1.0",
|
||||||
"@types/bluebird": "^3.5.30",
|
"@types/bluebird": "^3.5.30",
|
||||||
"@types/chai": "^4.2.11",
|
"@types/chai": "^4.2.11",
|
||||||
"@types/chai-as-promised": "^7.1.2",
|
"@types/chai-as-promised": "^7.1.2",
|
||||||
|
@ -206,7 +206,7 @@ export class APIBinder {
|
|||||||
// When we've provisioned, try to load the backup. We
|
// When we've provisioned, try to load the backup. We
|
||||||
// must wait for the provisioning because we need a
|
// must wait for the provisioning because we need a
|
||||||
// target state on which to apply the backup
|
// target state on which to apply the backup
|
||||||
globalEventBus.getInstance().once('targetStateChanged', async state => {
|
globalEventBus.getInstance().once('targetStateChanged', async (state) => {
|
||||||
await loadBackupFromMigration(
|
await loadBackupFromMigration(
|
||||||
this.deviceState,
|
this.deviceState,
|
||||||
state,
|
state,
|
||||||
@ -394,7 +394,7 @@ export class APIBinder {
|
|||||||
},
|
},
|
||||||
})) as Array<Dictionary<unknown>>;
|
})) as Array<Dictionary<unknown>>;
|
||||||
|
|
||||||
return tags.map(tag => {
|
return tags.map((tag) => {
|
||||||
// Do some type safe decoding and throw if we get an unexpected value
|
// Do some type safe decoding and throw if we get an unexpected value
|
||||||
const id = t.number.decode(tag.id);
|
const id = t.number.decode(tag.id);
|
||||||
const name = t.string.decode(tag.tag_key);
|
const name = t.string.decode(tag.tag_key);
|
||||||
@ -577,7 +577,7 @@ export class APIBinder {
|
|||||||
this.deviceState.triggerApplyTarget({ force, isFromApi });
|
this.deviceState.triggerApplyTarget({ force, isFromApi });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.tapCatch(ContractValidationError, ContractViolationError, e => {
|
.tapCatch(ContractValidationError, ContractViolationError, (e) => {
|
||||||
log.error(`Could not store target state for device: ${e}`);
|
log.error(`Could not store target state for device: ${e}`);
|
||||||
// the dashboard does not display lines correctly,
|
// the dashboard does not display lines correctly,
|
||||||
// split them explcitly here
|
// split them explcitly here
|
||||||
@ -593,7 +593,7 @@ export class APIBinder {
|
|||||||
e instanceof ContractValidationError ||
|
e instanceof ContractValidationError ||
|
||||||
e instanceof ContractViolationError
|
e instanceof ContractViolationError
|
||||||
),
|
),
|
||||||
err => {
|
(err) => {
|
||||||
log.error(`Failed to get target state for device: ${err}`);
|
log.error(`Failed to get target state for device: ${err}`);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -944,7 +944,7 @@ export class APIBinder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private lockGetTarget() {
|
private lockGetTarget() {
|
||||||
return writeLock('getTarget').disposer(release => {
|
return writeLock('getTarget').disposer((release) => {
|
||||||
release();
|
release();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -960,7 +960,7 @@ export class APIBinder {
|
|||||||
if (apiBinder.readyForUpdates) {
|
if (apiBinder.readyForUpdates) {
|
||||||
this.config
|
this.config
|
||||||
.get('instantUpdates')
|
.get('instantUpdates')
|
||||||
.then(instantUpdates => {
|
.then((instantUpdates) => {
|
||||||
if (instantUpdates) {
|
if (instantUpdates) {
|
||||||
apiBinder
|
apiBinder
|
||||||
.getAndSetTargetState(req.body.force, true)
|
.getAndSetTargetState(req.body.force, true)
|
||||||
|
@ -42,7 +42,7 @@ import { serviceAction } from './device-api/common';
|
|||||||
const readFileAsync = Promise.promisify(fs.readFile);
|
const readFileAsync = Promise.promisify(fs.readFile);
|
||||||
|
|
||||||
// TODO: move this to an Image class?
|
// TODO: move this to an Image class?
|
||||||
const imageForService = service => ({
|
const imageForService = (service) => ({
|
||||||
name: service.imageName,
|
name: service.imageName,
|
||||||
appId: service.appId,
|
appId: service.appId,
|
||||||
serviceId: service.serviceId,
|
serviceId: service.serviceId,
|
||||||
@ -52,7 +52,7 @@ const imageForService = service => ({
|
|||||||
dependent: 0,
|
dependent: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchAction = service => ({
|
const fetchAction = (service) => ({
|
||||||
action: 'fetch',
|
action: 'fetch',
|
||||||
image: imageForService(service),
|
image: imageForService(service),
|
||||||
serviceId: service.serviceId,
|
serviceId: service.serviceId,
|
||||||
@ -216,7 +216,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
this.db,
|
this.db,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.config.on('change', changedConfig => {
|
this.config.on('change', (changedConfig) => {
|
||||||
if (changedConfig.appUpdatePollInterval) {
|
if (changedConfig.appUpdatePollInterval) {
|
||||||
this.images.appUpdatePollInterval = changedConfig.appUpdatePollInterval;
|
this.images.appUpdatePollInterval = changedConfig.appUpdatePollInterval;
|
||||||
}
|
}
|
||||||
@ -231,10 +231,10 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
images: this.images,
|
images: this.images,
|
||||||
config: this.config,
|
config: this.config,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
containerStarted: id => {
|
containerStarted: (id) => {
|
||||||
this._containerStarted[id] = true;
|
this._containerStarted[id] = true;
|
||||||
},
|
},
|
||||||
containerKilled: id => {
|
containerKilled: (id) => {
|
||||||
delete this._containerStarted[id];
|
delete this._containerStarted[id];
|
||||||
},
|
},
|
||||||
fetchStart: () => {
|
fetchStart: () => {
|
||||||
@ -243,10 +243,10 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
fetchEnd: () => {
|
fetchEnd: () => {
|
||||||
this.fetchesInProgress -= 1;
|
this.fetchesInProgress -= 1;
|
||||||
},
|
},
|
||||||
fetchTime: time => {
|
fetchTime: (time) => {
|
||||||
this.timeSpentFetching += time;
|
this.timeSpentFetching += time;
|
||||||
},
|
},
|
||||||
stateReport: state => this.reportCurrentState(state),
|
stateReport: (state) => this.reportCurrentState(state),
|
||||||
bestDeltaSource: this.bestDeltaSource,
|
bestDeltaSource: this.bestDeltaSource,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -265,13 +265,15 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
init() {
|
init() {
|
||||||
return this.config
|
return this.config
|
||||||
.get('appUpdatePollInterval')
|
.get('appUpdatePollInterval')
|
||||||
.then(interval => {
|
.then((interval) => {
|
||||||
this.images.appUpdatePollInterval = interval;
|
this.images.appUpdatePollInterval = interval;
|
||||||
return this.images.cleanupDatabase();
|
return this.images.cleanupDatabase();
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
return this.docker.listContainers({ all: true }).then(containers => {
|
return this.docker
|
||||||
|
.listContainers({ all: true })
|
||||||
|
.then((containers) => {
|
||||||
return this.logger.clearOutOfDateDBLogs(_.map(containers, 'Id'));
|
return this.logger.clearOutOfDateDBLogs(_.map(containers, 'Id'));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -422,7 +424,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
// multi-app warning!
|
// multi-app warning!
|
||||||
// This is just wrong on every level
|
// This is just wrong on every level
|
||||||
_.each(apps, app => {
|
_.each(apps, (app) => {
|
||||||
app.commit = currentCommit;
|
app.commit = currentCommit;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -450,7 +452,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTargetApp(appId) {
|
getTargetApp(appId) {
|
||||||
return this.targetStateWrapper.getTargetApp(appId).then(app => {
|
return this.targetStateWrapper.getTargetApp(appId).then((app) => {
|
||||||
if (app == null) {
|
if (app == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -524,7 +526,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
// Returns true if a service matches its target except it should be running and it is not, but we've
|
// Returns true if a service matches its target except it should be running and it is not, but we've
|
||||||
// already started it before. In this case it means it just exited so we don't want to start it again.
|
// already started it before. In this case it means it just exited so we don't want to start it again.
|
||||||
const alreadyStarted = serviceId => {
|
const alreadyStarted = (serviceId) => {
|
||||||
return (
|
return (
|
||||||
currentServicesPerId[serviceId].isEqualExceptForRunningState(
|
currentServicesPerId[serviceId].isEqualExceptForRunningState(
|
||||||
targetServicesPerId[serviceId],
|
targetServicesPerId[serviceId],
|
||||||
@ -537,7 +539,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
const needUpdate = _.filter(
|
const needUpdate = _.filter(
|
||||||
toBeMaybeUpdated,
|
toBeMaybeUpdated,
|
||||||
serviceId =>
|
(serviceId) =>
|
||||||
!currentServicesPerId[serviceId].isEqual(
|
!currentServicesPerId[serviceId].isEqual(
|
||||||
targetServicesPerId[serviceId],
|
targetServicesPerId[serviceId],
|
||||||
containerIds,
|
containerIds,
|
||||||
@ -572,7 +574,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
const toBeUpdated = _.filter(
|
const toBeUpdated = _.filter(
|
||||||
_.intersection(targetNames, currentNames),
|
_.intersection(targetNames, currentNames),
|
||||||
name => !current[name].isEqualConfig(target[name]),
|
(name) => !current[name].isEqualConfig(target[name]),
|
||||||
);
|
);
|
||||||
for (const name of toBeUpdated) {
|
for (const name of toBeUpdated) {
|
||||||
outputPairs.push({
|
outputPairs.push({
|
||||||
@ -605,7 +607,8 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
const hasNetwork = _.some(
|
const hasNetwork = _.some(
|
||||||
networkPairs,
|
networkPairs,
|
||||||
pair => `${service.appId}_${pair.current?.name}` === service.networkMode,
|
(pair) =>
|
||||||
|
`${service.appId}_${pair.current?.name}` === service.networkMode,
|
||||||
);
|
);
|
||||||
if (hasNetwork) {
|
if (hasNetwork) {
|
||||||
return true;
|
return true;
|
||||||
@ -614,7 +617,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
const name = _.split(volume, ':')[0];
|
const name = _.split(volume, ':')[0];
|
||||||
return _.some(
|
return _.some(
|
||||||
volumePairs,
|
volumePairs,
|
||||||
pair => `${service.appId}_${pair.current?.name}` === name,
|
(pair) => `${service.appId}_${pair.current?.name}` === name,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return hasVolume;
|
return hasVolume;
|
||||||
@ -629,8 +632,8 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
pendingPairs,
|
pendingPairs,
|
||||||
) {
|
) {
|
||||||
// for dependsOn, check no install or update pairs have that service
|
// for dependsOn, check no install or update pairs have that service
|
||||||
const dependencyUnmet = _.some(target.dependsOn, dependency =>
|
const dependencyUnmet = _.some(target.dependsOn, (dependency) =>
|
||||||
_.some(pendingPairs, pair => pair.target?.serviceName === dependency),
|
_.some(pendingPairs, (pair) => pair.target?.serviceName === dependency),
|
||||||
);
|
);
|
||||||
if (dependencyUnmet) {
|
if (dependencyUnmet) {
|
||||||
return false;
|
return false;
|
||||||
@ -639,7 +642,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
if (
|
if (
|
||||||
_.some(
|
_.some(
|
||||||
networkPairs,
|
networkPairs,
|
||||||
pair => `${target.appId}_${pair.target?.name}` === target.networkMode,
|
(pair) => `${target.appId}_${pair.target?.name}` === target.networkMode,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
@ -652,7 +655,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
return _.some(
|
return _.some(
|
||||||
volumePairs,
|
volumePairs,
|
||||||
pair => `${target.appId}_${pair.target?.name}` === sourceName,
|
(pair) => `${target.appId}_${pair.target?.name}` === sourceName,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return !volumeUnmet;
|
return !volumeUnmet;
|
||||||
@ -681,7 +684,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
if (
|
if (
|
||||||
!_.some(
|
!_.some(
|
||||||
availableImages,
|
availableImages,
|
||||||
image =>
|
(image) =>
|
||||||
image.dockerImageId === dependencyService.image ||
|
image.dockerImageId === dependencyService.image ||
|
||||||
Images.isSameImage(image, { name: dependencyService.imageName }),
|
Images.isSameImage(image, { name: dependencyService.imageName }),
|
||||||
)
|
)
|
||||||
@ -702,7 +705,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
) {
|
) {
|
||||||
// Check none of the currentApp.services use this network or volume
|
// Check none of the currentApp.services use this network or volume
|
||||||
if (current != null) {
|
if (current != null) {
|
||||||
const dependencies = _.filter(currentApp.services, service =>
|
const dependencies = _.filter(currentApp.services, (service) =>
|
||||||
dependencyComparisonFn(service, current),
|
dependencyComparisonFn(service, current),
|
||||||
);
|
);
|
||||||
if (_.isEmpty(dependencies)) {
|
if (_.isEmpty(dependencies)) {
|
||||||
@ -815,7 +818,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
if (!localMode) {
|
if (!localMode) {
|
||||||
needsDownload = !_.some(
|
needsDownload = !_.some(
|
||||||
availableImages,
|
availableImages,
|
||||||
image =>
|
(image) =>
|
||||||
image.dockerImageId === target?.config.image ||
|
image.dockerImageId === target?.config.image ||
|
||||||
Images.isSameImage(image, { name: target.imageName }),
|
Images.isSameImage(image, { name: target.imageName }),
|
||||||
);
|
);
|
||||||
@ -1021,7 +1024,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const appId = targetApp.appId ?? currentApp.appId;
|
const appId = targetApp.appId ?? currentApp.appId;
|
||||||
return _.map(steps, step => _.assign({}, step, { appId }));
|
return _.map(steps, (step) => _.assign({}, step, { appId }));
|
||||||
}
|
}
|
||||||
|
|
||||||
normaliseAppForDB(app) {
|
normaliseAppForDB(app) {
|
||||||
@ -1033,7 +1036,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
service.commit = app.commit;
|
service.commit = app.commit;
|
||||||
return service;
|
return service;
|
||||||
});
|
});
|
||||||
return Promise.map(services, service => {
|
return Promise.map(services, (service) => {
|
||||||
service.image = this.images.normalise(service.image);
|
service.image = this.images.normalise(service.image);
|
||||||
return Promise.props(service);
|
return Promise.props(service);
|
||||||
}).then(function ($services) {
|
}).then(function ($services) {
|
||||||
@ -1134,9 +1137,9 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return Promise.map(JSON.parse(app.services), service =>
|
return Promise.map(JSON.parse(app.services), (service) =>
|
||||||
this.createTargetService(service, configOpts),
|
this.createTargetService(service, configOpts),
|
||||||
).then(services => {
|
).then((services) => {
|
||||||
// If a named volume is defined in a service but NOT in the volumes of the compose file, we add it app-wide so that we can track it and purge it
|
// If a named volume is defined in a service but NOT in the volumes of the compose file, we add it app-wide so that we can track it and purge it
|
||||||
// !! DEPRECATED, WILL BE REMOVED IN NEXT MAJOR RELEASE !!
|
// !! DEPRECATED, WILL BE REMOVED IN NEXT MAJOR RELEASE !!
|
||||||
for (const s of services) {
|
for (const s of services) {
|
||||||
@ -1174,7 +1177,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
return appClone;
|
return appClone;
|
||||||
});
|
});
|
||||||
return Promise.map(appsArray, this.normaliseAppForDB)
|
return Promise.map(appsArray, this.normaliseAppForDB)
|
||||||
.then(appsForDB => {
|
.then((appsForDB) => {
|
||||||
return this.targetStateWrapper.setTargetApps(appsForDB, trx);
|
return this.targetStateWrapper.setTargetApps(appsForDB, trx);
|
||||||
})
|
})
|
||||||
.then(() =>
|
.then(() =>
|
||||||
@ -1259,7 +1262,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
clearTargetVolatileForServices(imageIds) {
|
clearTargetVolatileForServices(imageIds) {
|
||||||
return imageIds.map(
|
return imageIds.map(
|
||||||
imageId => (this._targetVolatilePerImageId[imageId] = {}),
|
(imageId) => (this._targetVolatilePerImageId[imageId] = {}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1268,9 +1271,9 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
this.targetStateWrapper.getTargetApps(),
|
this.targetStateWrapper.getTargetApps(),
|
||||||
this.normaliseAndExtendAppFromDB,
|
this.normaliseAndExtendAppFromDB,
|
||||||
)
|
)
|
||||||
.map(app => {
|
.map((app) => {
|
||||||
if (!_.isEmpty(app.services)) {
|
if (!_.isEmpty(app.services)) {
|
||||||
app.services = _.map(app.services, service => {
|
app.services = _.map(app.services, (service) => {
|
||||||
if (this._targetVolatilePerImageId[service.imageId] != null) {
|
if (this._targetVolatilePerImageId[service.imageId] != null) {
|
||||||
_.merge(service, this._targetVolatilePerImageId[service.imageId]);
|
_.merge(service, this._targetVolatilePerImageId[service.imageId]);
|
||||||
}
|
}
|
||||||
@ -1279,7 +1282,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
return app;
|
return app;
|
||||||
})
|
})
|
||||||
.then(apps => _.keyBy(apps, 'appId'));
|
.then((apps) => _.keyBy(apps, 'appId'));
|
||||||
}
|
}
|
||||||
|
|
||||||
getDependentTargets() {
|
getDependentTargets() {
|
||||||
@ -1314,8 +1317,8 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
// - are locally available (i.e. an image with the same digest exists)
|
// - are locally available (i.e. an image with the same digest exists)
|
||||||
// - are not saved to the DB with all their metadata (serviceId, serviceName, etc)
|
// - are not saved to the DB with all their metadata (serviceId, serviceName, etc)
|
||||||
_compareImages(current, target, available, localMode) {
|
_compareImages(current, target, available, localMode) {
|
||||||
const allImagesForTargetApp = app => _.map(app.services, imageForService);
|
const allImagesForTargetApp = (app) => _.map(app.services, imageForService);
|
||||||
const allImagesForCurrentApp = app =>
|
const allImagesForCurrentApp = (app) =>
|
||||||
_.map(app.services, function (service) {
|
_.map(app.services, function (service) {
|
||||||
const img =
|
const img =
|
||||||
_.find(available, {
|
_.find(available, {
|
||||||
@ -1324,13 +1327,13 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}) ?? _.find(available, { dockerImageId: service.config.image });
|
}) ?? _.find(available, { dockerImageId: service.config.image });
|
||||||
return _.omit(img, ['dockerImageId', 'id']);
|
return _.omit(img, ['dockerImageId', 'id']);
|
||||||
});
|
});
|
||||||
const allImageDockerIdsForTargetApp = app =>
|
const allImageDockerIdsForTargetApp = (app) =>
|
||||||
_(app.services)
|
_(app.services)
|
||||||
.map(svc => [svc.imageName, svc.config.image])
|
.map((svc) => [svc.imageName, svc.config.image])
|
||||||
.filter(img => img[1] != null)
|
.filter((img) => img[1] != null)
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
const availableWithoutIds = _.map(available, image =>
|
const availableWithoutIds = _.map(available, (image) =>
|
||||||
_.omit(image, ['dockerImageId', 'id']),
|
_.omit(image, ['dockerImageId', 'id']),
|
||||||
);
|
);
|
||||||
const currentImages = _.flatMap(current.local.apps, allImagesForCurrentApp);
|
const currentImages = _.flatMap(current.local.apps, allImagesForCurrentApp);
|
||||||
@ -1341,16 +1344,16 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
const availableAndUnused = _.filter(
|
const availableAndUnused = _.filter(
|
||||||
availableWithoutIds,
|
availableWithoutIds,
|
||||||
image =>
|
(image) =>
|
||||||
!_.some(currentImages.concat(targetImages), imageInUse =>
|
!_.some(currentImages.concat(targetImages), (imageInUse) =>
|
||||||
_.isEqual(image, imageInUse),
|
_.isEqual(image, imageInUse),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const imagesToDownload = _.filter(
|
const imagesToDownload = _.filter(
|
||||||
targetImages,
|
targetImages,
|
||||||
targetImage =>
|
(targetImage) =>
|
||||||
!_.some(available, availableImage =>
|
!_.some(available, (availableImage) =>
|
||||||
Images.isSameImage(availableImage, targetImage),
|
Images.isSameImage(availableImage, targetImage),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -1359,7 +1362,9 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
let imagesToSave = [];
|
let imagesToSave = [];
|
||||||
if (!localMode) {
|
if (!localMode) {
|
||||||
imagesToSave = _.filter(targetImages, function (targetImage) {
|
imagesToSave = _.filter(targetImages, function (targetImage) {
|
||||||
const isActuallyAvailable = _.some(available, function(availableImage) {
|
const isActuallyAvailable = _.some(available, function (
|
||||||
|
availableImage,
|
||||||
|
) {
|
||||||
if (Images.isSameImage(availableImage, targetImage)) {
|
if (Images.isSameImage(availableImage, targetImage)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1371,21 +1376,21 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
const isNotSaved = !_.some(availableWithoutIds, img =>
|
const isNotSaved = !_.some(availableWithoutIds, (img) =>
|
||||||
_.isEqual(img, targetImage),
|
_.isEqual(img, targetImage),
|
||||||
);
|
);
|
||||||
return isActuallyAvailable && isNotSaved;
|
return isActuallyAvailable && isNotSaved;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const deltaSources = _.map(imagesToDownload, image => {
|
const deltaSources = _.map(imagesToDownload, (image) => {
|
||||||
return this.bestDeltaSource(image, available);
|
return this.bestDeltaSource(image, available);
|
||||||
});
|
});
|
||||||
const proxyvisorImages = this.proxyvisor.imagesInUse(current, target);
|
const proxyvisorImages = this.proxyvisor.imagesInUse(current, target);
|
||||||
|
|
||||||
const potentialDeleteThenDownload = _.filter(
|
const potentialDeleteThenDownload = _.filter(
|
||||||
current.local.apps.services,
|
current.local.apps.services,
|
||||||
svc =>
|
(svc) =>
|
||||||
svc.config.labels['io.balena.update.strategy'] ===
|
svc.config.labels['io.balena.update.strategy'] ===
|
||||||
'delete-then-download' && svc.status === 'Stopped',
|
'delete-then-download' && svc.status === 'Stopped',
|
||||||
);
|
);
|
||||||
@ -1394,7 +1399,9 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
availableAndUnused.concat(potentialDeleteThenDownload),
|
availableAndUnused.concat(potentialDeleteThenDownload),
|
||||||
function (image) {
|
function (image) {
|
||||||
const notUsedForDelta = !_.includes(deltaSources, image.name);
|
const notUsedForDelta = !_.includes(deltaSources, image.name);
|
||||||
const notUsedByProxyvisor = !_.some(proxyvisorImages, proxyvisorImage =>
|
const notUsedByProxyvisor = !_.some(
|
||||||
|
proxyvisorImages,
|
||||||
|
(proxyvisorImage) =>
|
||||||
Images.isSameImage(image, { name: proxyvisorImage }),
|
Images.isSameImage(image, { name: proxyvisorImage }),
|
||||||
);
|
);
|
||||||
return notUsedForDelta && notUsedByProxyvisor;
|
return notUsedForDelta && notUsedByProxyvisor;
|
||||||
@ -1437,8 +1444,10 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
// multi-app warning: this will break
|
// multi-app warning: this will break
|
||||||
let appsForVolumeRemoval;
|
let appsForVolumeRemoval;
|
||||||
if (!localMode) {
|
if (!localMode) {
|
||||||
const currentAppIds = _.keys(current.local.apps).map(n => checkInt(n));
|
const currentAppIds = _.keys(current.local.apps).map((n) =>
|
||||||
const targetAppIds = _.keys(target.local.apps).map(n => checkInt(n));
|
checkInt(n),
|
||||||
|
);
|
||||||
|
const targetAppIds = _.keys(target.local.apps).map((n) => checkInt(n));
|
||||||
appsForVolumeRemoval = _.difference(currentAppIds, targetAppIds);
|
appsForVolumeRemoval = _.difference(currentAppIds, targetAppIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1519,7 +1528,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const newDownloads = nextSteps.filter(s => s.action === 'fetch').length;
|
const newDownloads = nextSteps.filter((s) => s.action === 'fetch').length;
|
||||||
|
|
||||||
if (!ignoreImages && delta && newDownloads > 0) {
|
if (!ignoreImages && delta && newDownloads > 0) {
|
||||||
// Check that this is not the first pull for an
|
// Check that this is not the first pull for an
|
||||||
@ -1551,7 +1560,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
nextSteps.push({ action: 'noop' });
|
nextSteps.push({ action: 'noop' });
|
||||||
}
|
}
|
||||||
return _.uniqWith(nextSteps, _.isEqual);
|
return _.uniqWith(nextSteps, _.isEqual);
|
||||||
}).then(nextSteps =>
|
}).then((nextSteps) =>
|
||||||
Promise.all(volumePromises).then(function (volSteps) {
|
Promise.all(volumePromises).then(function (volSteps) {
|
||||||
nextSteps = nextSteps.concat(_.flatten(volSteps));
|
nextSteps = nextSteps.concat(_.flatten(volSteps));
|
||||||
return nextSteps;
|
return nextSteps;
|
||||||
@ -1561,7 +1570,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
|
|
||||||
stopAll({ force = false, skipLock = false } = {}) {
|
stopAll({ force = false, skipLock = false } = {}) {
|
||||||
return Promise.resolve(this.services.getAll())
|
return Promise.resolve(this.services.getAll())
|
||||||
.map(service => {
|
.map((service) => {
|
||||||
return this._lockingIfNecessary(
|
return this._lockingIfNecessary(
|
||||||
service.appId,
|
service.appId,
|
||||||
{ force, skipLock },
|
{ force, skipLock },
|
||||||
@ -1583,8 +1592,8 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
return this.config
|
return this.config
|
||||||
.get('lockOverride')
|
.get('lockOverride')
|
||||||
.then(lockOverride => lockOverride || force)
|
.then((lockOverride) => lockOverride || force)
|
||||||
.then(lockOverridden =>
|
.then((lockOverridden) =>
|
||||||
updateLock.lock(appId, { force: lockOverridden }, fn),
|
updateLock.lock(appId, { force: lockOverridden }, fn),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1607,7 +1616,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
.keys()
|
.keys()
|
||||||
.concat(_.keys(targetState.local.apps))
|
.concat(_.keys(targetState.local.apps))
|
||||||
.uniq()
|
.uniq()
|
||||||
.each(id => {
|
.each((id) => {
|
||||||
const intId = checkInt(id);
|
const intId = checkInt(id);
|
||||||
if (intId == null) {
|
if (intId == null) {
|
||||||
throw new Error(`Invalid id: ${id}`);
|
throw new Error(`Invalid id: ${id}`);
|
||||||
@ -1615,7 +1624,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
containerIdsByAppId[intId] = this.services.getContainerIdMap(intId);
|
containerIdsByAppId[intId] = this.services.getContainerIdMap(intId);
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.config.get('localMode').then(localMode => {
|
return this.config.get('localMode').then((localMode) => {
|
||||||
return Promise.props({
|
return Promise.props({
|
||||||
cleanupNeeded: this.images.isCleanupNeeded(),
|
cleanupNeeded: this.images.isCleanupNeeded(),
|
||||||
availableImages: this.images.getAvailable(),
|
availableImages: this.images.getAvailable(),
|
||||||
@ -1656,7 +1665,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
ignoreImages,
|
ignoreImages,
|
||||||
conf,
|
conf,
|
||||||
containerIds,
|
containerIds,
|
||||||
).then(nextSteps => {
|
).then((nextSteps) => {
|
||||||
if (ignoreImages && _.some(nextSteps, { action: 'fetch' })) {
|
if (ignoreImages && _.some(nextSteps, { action: 'fetch' })) {
|
||||||
throw new Error('Cannot fetch images while executing an API action');
|
throw new Error('Cannot fetch images while executing an API action');
|
||||||
}
|
}
|
||||||
@ -1668,7 +1677,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
targetState,
|
targetState,
|
||||||
nextSteps,
|
nextSteps,
|
||||||
)
|
)
|
||||||
.then(proxyvisorSteps => nextSteps.concat(proxyvisorSteps));
|
.then((proxyvisorSteps) => nextSteps.concat(proxyvisorSteps));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1681,7 +1690,7 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
const app = apps[appId];
|
const app = apps[appId];
|
||||||
const service = _.find(
|
const service = _.find(
|
||||||
app.services,
|
app.services,
|
||||||
svc => svc.serviceId === serviceId,
|
(svc) => svc.serviceId === serviceId,
|
||||||
);
|
);
|
||||||
if (service?.serviceName == null) {
|
if (service?.serviceName == null) {
|
||||||
throw new InternalInconsistencyError(
|
throw new InternalInconsistencyError(
|
||||||
@ -1697,8 +1706,8 @@ export class ApplicationManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeAllVolumesForApp(appId) {
|
removeAllVolumesForApp(appId) {
|
||||||
return this.volumes.getAllByAppId(appId).then(volumes =>
|
return this.volumes.getAllByAppId(appId).then((volumes) =>
|
||||||
volumes.map(v => ({
|
volumes.map((v) => ({
|
||||||
action: 'removeVolume',
|
action: 'removeVolume',
|
||||||
current: v,
|
current: v,
|
||||||
})),
|
})),
|
||||||
|
@ -144,7 +144,7 @@ export function getExecutors(app: {
|
|||||||
callbacks: CompositionCallbacks;
|
callbacks: CompositionCallbacks;
|
||||||
}) {
|
}) {
|
||||||
const executors: Executors<CompositionStepAction> = {
|
const executors: Executors<CompositionStepAction> = {
|
||||||
stop: step => {
|
stop: (step) => {
|
||||||
return app.lockFn(
|
return app.lockFn(
|
||||||
step.current.appId,
|
step.current.appId,
|
||||||
{
|
{
|
||||||
@ -161,7 +161,7 @@ export function getExecutors(app: {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
kill: step => {
|
kill: (step) => {
|
||||||
return app.lockFn(
|
return app.lockFn(
|
||||||
step.current.appId,
|
step.current.appId,
|
||||||
{
|
{
|
||||||
@ -177,12 +177,12 @@ export function getExecutors(app: {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
remove: async step => {
|
remove: async (step) => {
|
||||||
// Only called for dead containers, so no need to
|
// Only called for dead containers, so no need to
|
||||||
// take locks
|
// take locks
|
||||||
await app.services.remove(step.current);
|
await app.services.remove(step.current);
|
||||||
},
|
},
|
||||||
updateMetadata: step => {
|
updateMetadata: (step) => {
|
||||||
const skipLock =
|
const skipLock =
|
||||||
step.skipLock ||
|
step.skipLock ||
|
||||||
checkTruthy(step.current.config.labels['io.balena.legacy-container']);
|
checkTruthy(step.current.config.labels['io.balena.legacy-container']);
|
||||||
@ -197,7 +197,7 @@ export function getExecutors(app: {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
restart: step => {
|
restart: (step) => {
|
||||||
return app.lockFn(
|
return app.lockFn(
|
||||||
step.current.appId,
|
step.current.appId,
|
||||||
{
|
{
|
||||||
@ -212,20 +212,20 @@ export function getExecutors(app: {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
stopAll: async step => {
|
stopAll: async (step) => {
|
||||||
await app.applications.stopAll({
|
await app.applications.stopAll({
|
||||||
force: step.force,
|
force: step.force,
|
||||||
skipLock: step.skipLock,
|
skipLock: step.skipLock,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
start: async step => {
|
start: async (step) => {
|
||||||
const container = await app.services.start(step.target);
|
const container = await app.services.start(step.target);
|
||||||
app.callbacks.containerStarted(container.id);
|
app.callbacks.containerStarted(container.id);
|
||||||
},
|
},
|
||||||
updateCommit: async step => {
|
updateCommit: async (step) => {
|
||||||
await app.config.set({ currentCommit: step.target });
|
await app.config.set({ currentCommit: step.target });
|
||||||
},
|
},
|
||||||
handover: step => {
|
handover: (step) => {
|
||||||
return app.lockFn(
|
return app.lockFn(
|
||||||
step.current.appId,
|
step.current.appId,
|
||||||
{
|
{
|
||||||
@ -237,7 +237,7 @@ export function getExecutors(app: {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
fetch: async step => {
|
fetch: async (step) => {
|
||||||
const startTime = process.hrtime();
|
const startTime = process.hrtime();
|
||||||
app.callbacks.fetchStart();
|
app.callbacks.fetchStart();
|
||||||
const [fetchOpts, availableImages] = await Promise.all([
|
const [fetchOpts, availableImages] = await Promise.all([
|
||||||
@ -253,7 +253,7 @@ export function getExecutors(app: {
|
|||||||
await app.images.triggerFetch(
|
await app.images.triggerFetch(
|
||||||
step.image,
|
step.image,
|
||||||
opts,
|
opts,
|
||||||
async success => {
|
async (success) => {
|
||||||
app.callbacks.fetchEnd();
|
app.callbacks.fetchEnd();
|
||||||
const elapsed = process.hrtime(startTime);
|
const elapsed = process.hrtime(startTime);
|
||||||
const elapsedMs = elapsed[0] * 1000 + elapsed[1] / 1e6;
|
const elapsedMs = elapsed[0] * 1000 + elapsed[1] / 1e6;
|
||||||
@ -269,10 +269,10 @@ export function getExecutors(app: {
|
|||||||
step.serviceName,
|
step.serviceName,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
removeImage: async step => {
|
removeImage: async (step) => {
|
||||||
await app.images.remove(step.image);
|
await app.images.remove(step.image);
|
||||||
},
|
},
|
||||||
saveImage: async step => {
|
saveImage: async (step) => {
|
||||||
await app.images.save(step.image);
|
await app.images.save(step.image);
|
||||||
},
|
},
|
||||||
cleanup: async () => {
|
cleanup: async () => {
|
||||||
@ -281,16 +281,16 @@ export function getExecutors(app: {
|
|||||||
await app.images.cleanup();
|
await app.images.cleanup();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
createNetwork: async step => {
|
createNetwork: async (step) => {
|
||||||
await app.networks.create(step.target);
|
await app.networks.create(step.target);
|
||||||
},
|
},
|
||||||
createVolume: async step => {
|
createVolume: async (step) => {
|
||||||
await app.volumes.create(step.target);
|
await app.volumes.create(step.target);
|
||||||
},
|
},
|
||||||
removeNetwork: async step => {
|
removeNetwork: async (step) => {
|
||||||
await app.networks.remove(step.current);
|
await app.networks.remove(step.current);
|
||||||
},
|
},
|
||||||
removeVolume: async step => {
|
removeVolume: async (step) => {
|
||||||
await app.volumes.remove(step.current);
|
await app.volumes.remove(step.current);
|
||||||
},
|
},
|
||||||
ensureSupervisorNetwork: async () => {
|
ensureSupervisorNetwork: async () => {
|
||||||
|
@ -194,10 +194,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getByDockerId(id: string): Promise<Image> {
|
public async getByDockerId(id: string): Promise<Image> {
|
||||||
return await this.db
|
return await this.db.models('image').where({ dockerImageId: id }).first();
|
||||||
.models('image')
|
|
||||||
.where({ dockerImageId: id })
|
|
||||||
.first();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async removeByDockerId(id: string): Promise<void> {
|
public async removeByDockerId(id: string): Promise<void> {
|
||||||
@ -216,7 +213,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
|
|||||||
cb: (dockerImages: NormalisedDockerImage[], composeImages: Image[]) => T,
|
cb: (dockerImages: NormalisedDockerImage[], composeImages: Image[]) => T,
|
||||||
) {
|
) {
|
||||||
const [normalisedImages, dbImages] = await Promise.all([
|
const [normalisedImages, dbImages] = await Promise.all([
|
||||||
Bluebird.map(this.docker.listImages({ digests: true }), async image => {
|
Bluebird.map(this.docker.listImages({ digests: true }), async (image) => {
|
||||||
const newImage = _.clone(image) as NormalisedDockerImage;
|
const newImage = _.clone(image) as NormalisedDockerImage;
|
||||||
newImage.NormalisedRepoTags = await this.getNormalisedTags(image);
|
newImage.NormalisedRepoTags = await this.getNormalisedTags(image);
|
||||||
return newImage;
|
return newImage;
|
||||||
@ -240,7 +237,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
|
|||||||
): boolean {
|
): boolean {
|
||||||
return (
|
return (
|
||||||
_.includes(dockerImage.NormalisedRepoTags, image.name) ||
|
_.includes(dockerImage.NormalisedRepoTags, image.name) ||
|
||||||
_.some(dockerImage.RepoDigests, digest =>
|
_.some(dockerImage.RepoDigests, (digest) =>
|
||||||
Images.hasSameDigest(image.name, digest),
|
Images.hasSameDigest(image.name, digest),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -252,7 +249,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
|
|||||||
): boolean {
|
): boolean {
|
||||||
return _.some(
|
return _.some(
|
||||||
dockerImages,
|
dockerImages,
|
||||||
dockerImage =>
|
(dockerImage) =>
|
||||||
this.matchesTagOrDigest(image, dockerImage) ||
|
this.matchesTagOrDigest(image, dockerImage) ||
|
||||||
image.dockerImageId === dockerImage.Id,
|
image.dockerImageId === dockerImage.Id,
|
||||||
);
|
);
|
||||||
@ -261,7 +258,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
|
|||||||
public async getAvailable(): Promise<Image[]> {
|
public async getAvailable(): Promise<Image[]> {
|
||||||
const images = await this.withImagesFromDockerAndDB(
|
const images = await this.withImagesFromDockerAndDB(
|
||||||
(dockerImages, supervisedImages) =>
|
(dockerImages, supervisedImages) =>
|
||||||
_.filter(supervisedImages, image =>
|
_.filter(supervisedImages, (image) =>
|
||||||
this.isAvailableInDocker(image, dockerImages),
|
this.isAvailableInDocker(image, dockerImages),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -288,7 +285,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
|
|||||||
// some entries in the db might need to have the dockerImageId populated
|
// some entries in the db might need to have the dockerImageId populated
|
||||||
if (supervisedImage.dockerImageId == null) {
|
if (supervisedImage.dockerImageId == null) {
|
||||||
const id = _.get(
|
const id = _.get(
|
||||||
_.find(dockerImages, dockerImage =>
|
_.find(dockerImages, (dockerImage) =>
|
||||||
this.matchesTagOrDigest(supervisedImage, dockerImage),
|
this.matchesTagOrDigest(supervisedImage, dockerImage),
|
||||||
),
|
),
|
||||||
'Id',
|
'Id',
|
||||||
@ -303,20 +300,14 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _.reject(supervisedImages, image =>
|
return _.reject(supervisedImages, (image) =>
|
||||||
this.isAvailableInDocker(image, dockerImages),
|
this.isAvailableInDocker(image, dockerImages),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const ids = _(imagesToRemove)
|
const ids = _(imagesToRemove).map('id').compact().value();
|
||||||
.map('id')
|
await this.db.models('image').del().whereIn('id', ids);
|
||||||
.compact()
|
|
||||||
.value();
|
|
||||||
await this.db
|
|
||||||
.models('image')
|
|
||||||
.del()
|
|
||||||
.whereIn('id', ids);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getStatus() {
|
public async getStatus() {
|
||||||
@ -362,7 +353,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
|
|||||||
this.db
|
this.db
|
||||||
.models('image')
|
.models('image')
|
||||||
.select('dockerImageId')
|
.select('dockerImageId')
|
||||||
.then(vals => vals.map((img: Image) => img.dockerImageId)),
|
.then((vals) => vals.map((img: Image) => img.dockerImageId)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const supervisorRepos = [supervisorImageInfo.imageName];
|
const supervisorRepos = [supervisorImageInfo.imageName];
|
||||||
@ -381,7 +372,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
|
|||||||
tagName: string;
|
tagName: string;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
_.some(supervisorRepos, repo => imageName === repo) &&
|
_.some(supervisorRepos, (repo) => imageName === repo) &&
|
||||||
tagName !== supervisorImageInfo.tagName
|
tagName !== supervisorImageInfo.tagName
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -408,7 +399,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
|
|||||||
return _(images)
|
return _(images)
|
||||||
.uniq()
|
.uniq()
|
||||||
.filter(
|
.filter(
|
||||||
image =>
|
(image) =>
|
||||||
this.imageCleanupFailures[image] == null ||
|
this.imageCleanupFailures[image] == null ||
|
||||||
Date.now() - this.imageCleanupFailures[image] >
|
Date.now() - this.imageCleanupFailures[image] >
|
||||||
constants.imageCleanupErrorIgnoreTimeout,
|
constants.imageCleanupErrorIgnoreTimeout,
|
||||||
@ -505,10 +496,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
|
|||||||
|
|
||||||
// We first fetch the image from the DB to ensure it exists,
|
// We first fetch the image from the DB to ensure it exists,
|
||||||
// and get the dockerImageId and any other missing fields
|
// and get the dockerImageId and any other missing fields
|
||||||
const images = await this.db
|
const images = await this.db.models('image').select().where(image);
|
||||||
.models('image')
|
|
||||||
.select()
|
|
||||||
.where(image);
|
|
||||||
|
|
||||||
if (images.length === 0) {
|
if (images.length === 0) {
|
||||||
removed = false;
|
removed = false;
|
||||||
@ -547,7 +535,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
|
|||||||
if (
|
if (
|
||||||
dockerImage.RepoTags.length > 1 &&
|
dockerImage.RepoTags.length > 1 &&
|
||||||
_.includes(dockerImage.RepoTags, img.name) &&
|
_.includes(dockerImage.RepoTags, img.name) &&
|
||||||
_.some(dockerImage.RepoTags, t =>
|
_.some(dockerImage.RepoTags, (t) =>
|
||||||
_.some(differentTags, { name: t }),
|
_.some(differentTags, { name: t }),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@ -568,10 +556,7 @@ export class Images extends (EventEmitter as new () => ImageEventEmitter) {
|
|||||||
this.reportChange(image.imageId);
|
this.reportChange(image.imageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.db
|
await this.db.models('image').del().where({ id: img.id });
|
||||||
.models('image')
|
|
||||||
.del()
|
|
||||||
.where({ id: img.id });
|
|
||||||
|
|
||||||
if (removed) {
|
if (removed) {
|
||||||
this.logger.logSystemEvent(LogTypes.deleteImageSuccess, { image });
|
this.logger.logSystemEvent(LogTypes.deleteImageSuccess, { image });
|
||||||
|
@ -26,7 +26,7 @@ export class NetworkManager {
|
|||||||
return this.docker
|
return this.docker
|
||||||
.getNetwork(network.Name)
|
.getNetwork(network.Name)
|
||||||
.inspect()
|
.inspect()
|
||||||
.then(net => {
|
.then((net) => {
|
||||||
return Network.fromDockerNetwork(
|
return Network.fromDockerNetwork(
|
||||||
{
|
{
|
||||||
docker: this.docker,
|
docker: this.docker,
|
||||||
@ -93,7 +93,7 @@ export class NetworkManager {
|
|||||||
.getNetwork(constants.supervisorNetworkInterface)
|
.getNetwork(constants.supervisorNetworkInterface)
|
||||||
.inspect();
|
.inspect();
|
||||||
})
|
})
|
||||||
.then(network => {
|
.then((network) => {
|
||||||
return (
|
return (
|
||||||
network.Options['com.docker.network.bridge.name'] ===
|
network.Options['com.docker.network.bridge.name'] ===
|
||||||
constants.supervisorNetworkInterface &&
|
constants.supervisorNetworkInterface &&
|
||||||
@ -119,7 +119,7 @@ export class NetworkManager {
|
|||||||
return Bluebird.resolve(
|
return Bluebird.resolve(
|
||||||
this.docker.getNetwork(constants.supervisorNetworkInterface).inspect(),
|
this.docker.getNetwork(constants.supervisorNetworkInterface).inspect(),
|
||||||
)
|
)
|
||||||
.then(net => {
|
.then((net) => {
|
||||||
if (
|
if (
|
||||||
net.Options['com.docker.network.bridge.name'] !==
|
net.Options['com.docker.network.bridge.name'] !==
|
||||||
constants.supervisorNetworkInterface ||
|
constants.supervisorNetworkInterface ||
|
||||||
|
@ -60,7 +60,7 @@ export class Network {
|
|||||||
driver: network.Driver,
|
driver: network.Driver,
|
||||||
ipam: {
|
ipam: {
|
||||||
driver: network.IPAM.Driver,
|
driver: network.IPAM.Driver,
|
||||||
config: _.map(network.IPAM.Config, conf => {
|
config: _.map(network.IPAM.Config, (conf) => {
|
||||||
const newConf: NetworkConfig['ipam']['config'][0] = {};
|
const newConf: NetworkConfig['ipam']['config'][0] = {};
|
||||||
|
|
||||||
if (conf.Subnet != null) {
|
if (conf.Subnet != null) {
|
||||||
@ -155,7 +155,7 @@ export class Network {
|
|||||||
CheckDuplicate: true,
|
CheckDuplicate: true,
|
||||||
IPAM: {
|
IPAM: {
|
||||||
Driver: this.config.ipam.driver,
|
Driver: this.config.ipam.driver,
|
||||||
Config: _.map(this.config.ipam.config, conf => {
|
Config: _.map(this.config.ipam.config, (conf) => {
|
||||||
const ipamConf: DockerIPAMConfig = {};
|
const ipamConf: DockerIPAMConfig = {};
|
||||||
if (conf.subnet != null) {
|
if (conf.subnet != null) {
|
||||||
ipamConf.Subnet = conf.subnet;
|
ipamConf.Subnet = conf.subnet;
|
||||||
@ -194,7 +194,7 @@ export class Network {
|
|||||||
this.docker
|
this.docker
|
||||||
.getNetwork(Network.generateDockerName(this.appId, this.name))
|
.getNetwork(Network.generateDockerName(this.appId, this.name))
|
||||||
.remove(),
|
.remove(),
|
||||||
).tapCatch(error => {
|
).tapCatch((error) => {
|
||||||
this.logger.logSystemEvent(logTypes.removeNetworkError, {
|
this.logger.logSystemEvent(logTypes.removeNetworkError, {
|
||||||
network: { name: this.name, appId: this.appId },
|
network: { name: this.name, appId: this.appId },
|
||||||
error,
|
error,
|
||||||
|
@ -70,7 +70,7 @@ export class PortMap {
|
|||||||
this.ports.internalStart,
|
this.ports.internalStart,
|
||||||
this.ports.internalEnd,
|
this.ports.internalEnd,
|
||||||
);
|
);
|
||||||
return _.map(internalRange, internal => {
|
return _.map(internalRange, (internal) => {
|
||||||
return `${internal}/${this.ports.protocol}`;
|
return `${internal}/${this.ports.protocol}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -118,9 +118,9 @@ export class PortMap {
|
|||||||
public static normalisePortMaps(portMaps: PortMap[]): PortMap[] {
|
public static normalisePortMaps(portMaps: PortMap[]): PortMap[] {
|
||||||
// Fold any ranges into each other if possible
|
// Fold any ranges into each other if possible
|
||||||
return _(portMaps)
|
return _(portMaps)
|
||||||
.sortBy(p => p.ports.protocol)
|
.sortBy((p) => p.ports.protocol)
|
||||||
.sortBy(p => p.ports.host)
|
.sortBy((p) => p.ports.host)
|
||||||
.sortBy(p => p.ports.internalStart)
|
.sortBy((p) => p.ports.internalStart)
|
||||||
.reduce((res: PortMap[], p: PortMap) => {
|
.reduce((res: PortMap[], p: PortMap) => {
|
||||||
const last = _.last(res);
|
const last = _.last(res);
|
||||||
|
|
||||||
@ -141,7 +141,7 @@ export class PortMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static fromComposePorts(ports: string[]): PortMap[] {
|
public static fromComposePorts(ports: string[]): PortMap[] {
|
||||||
return PortMap.normalisePortMaps(ports.map(p => new PortMap(p)));
|
return PortMap.normalisePortMaps(ports.map((p) => new PortMap(p)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private parsePortString(portStr: string): void {
|
private parsePortString(portStr: string): void {
|
||||||
|
@ -69,7 +69,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
|
|||||||
const filterLabels = ['supervised'].concat(extraLabelFilters);
|
const filterLabels = ['supervised'].concat(extraLabelFilters);
|
||||||
const containers = await this.listWithBothLabels(filterLabels);
|
const containers = await this.listWithBothLabels(filterLabels);
|
||||||
|
|
||||||
const services = await Bluebird.map(containers, async container => {
|
const services = await Bluebird.map(containers, async (container) => {
|
||||||
try {
|
try {
|
||||||
const serviceInspect = await this.docker
|
const serviceInspect = await this.docker
|
||||||
.getContainer(container.Id)
|
.getContainer(container.Id)
|
||||||
@ -90,7 +90,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return services.filter(s => s != null) as Service[];
|
return services.filter((s) => s != null) as Service[];
|
||||||
}
|
}
|
||||||
|
|
||||||
public async get(service: Service) {
|
public async get(service: Service) {
|
||||||
@ -98,7 +98,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
|
|||||||
const containerIds = await this.getContainerIdMap(service.appId!);
|
const containerIds = await this.getContainerIdMap(service.appId!);
|
||||||
const services = (
|
const services = (
|
||||||
await this.getAll(`service-id=${service.serviceId}`)
|
await this.getAll(`service-id=${service.serviceId}`)
|
||||||
).filter(currentService =>
|
).filter((currentService) =>
|
||||||
currentService.isEqualConfig(service, containerIds),
|
currentService.isEqualConfig(service, containerIds),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -399,7 +399,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
|
|||||||
filters: { type: ['container'] } as any,
|
filters: { type: ['container'] } as any,
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.on('error', e => {
|
stream.on('error', (e) => {
|
||||||
log.error(`Error on docker events stream:`, e);
|
log.error(`Error on docker events stream:`, e);
|
||||||
});
|
});
|
||||||
const parser = JSONStream.parse();
|
const parser = JSONStream.parse();
|
||||||
@ -462,7 +462,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
|
|||||||
};
|
};
|
||||||
|
|
||||||
Bluebird.resolve(listen())
|
Bluebird.resolve(listen())
|
||||||
.catch(e => {
|
.catch((e) => {
|
||||||
log.error('Error listening to events:', e, e.stack);
|
log.error('Error listening to events:', e, e.stack);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@ -554,7 +554,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
|
|||||||
return containerObj.remove({ v: true });
|
return containerObj.remove({ v: true });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((e) => {
|
||||||
// Get the statusCode from the original cause and make sure it's
|
// Get the statusCode from the original cause and make sure it's
|
||||||
// definitely an int for comparison reasons
|
// definitely an int for comparison reasons
|
||||||
const maybeStatusCode = PermissiveNumber.decode(e.statusCode);
|
const maybeStatusCode = PermissiveNumber.decode(e.statusCode);
|
||||||
@ -585,7 +585,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
|
|||||||
delete this.containerHasDied[containerId];
|
delete this.containerHasDied[containerId];
|
||||||
this.logger.logSystemEvent(LogTypes.stopServiceSuccess, { service });
|
this.logger.logSystemEvent(LogTypes.stopServiceSuccess, { service });
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((e) => {
|
||||||
this.logger.logSystemEvent(LogTypes.stopServiceError, {
|
this.logger.logSystemEvent(LogTypes.stopServiceError, {
|
||||||
service,
|
service,
|
||||||
error: e,
|
error: e,
|
||||||
@ -611,7 +611,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
|
|||||||
this.docker.listContainers({
|
this.docker.listContainers({
|
||||||
all: true,
|
all: true,
|
||||||
filters: {
|
filters: {
|
||||||
label: _.map(labelList, v => `${prefix}${v}`),
|
label: _.map(labelList, (v) => `${prefix}${v}`),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -646,7 +646,7 @@ export class ServiceManager extends (EventEmitter as new () => ServiceManagerEve
|
|||||||
|
|
||||||
const wait = (): Bluebird<void> =>
|
const wait = (): Bluebird<void> =>
|
||||||
Bluebird.any(
|
Bluebird.any(
|
||||||
handoverCompletePaths.map(file =>
|
handoverCompletePaths.map((file) =>
|
||||||
fs.stat(file).then(() => fs.unlink(file).catch(_.noop)),
|
fs.stat(file).then(() => fs.unlink(file).catch(_.noop)),
|
||||||
),
|
),
|
||||||
).catch(async () => {
|
).catch(async () => {
|
||||||
|
@ -130,7 +130,7 @@ export class Service {
|
|||||||
// First process the networks correctly
|
// First process the networks correctly
|
||||||
let networks: ServiceConfig['networks'] = {};
|
let networks: ServiceConfig['networks'] = {};
|
||||||
if (_.isArray(config.networks)) {
|
if (_.isArray(config.networks)) {
|
||||||
_.each(config.networks, name => {
|
_.each(config.networks, (name) => {
|
||||||
networks[name] = {};
|
networks[name] = {};
|
||||||
});
|
});
|
||||||
} else if (_.isObject(config.networks)) {
|
} else if (_.isObject(config.networks)) {
|
||||||
@ -139,7 +139,7 @@ export class Service {
|
|||||||
// Prefix the network entries with the app id
|
// Prefix the network entries with the app id
|
||||||
networks = _.mapKeys(networks, (_v, k) => `${service.appId}_${k}`);
|
networks = _.mapKeys(networks, (_v, k) => `${service.appId}_${k}`);
|
||||||
// Ensure that we add an alias of the service name
|
// Ensure that we add an alias of the service name
|
||||||
networks = _.mapValues(networks, v => {
|
networks = _.mapValues(networks, (v) => {
|
||||||
if (v.aliases == null) {
|
if (v.aliases == null) {
|
||||||
v.aliases = [];
|
v.aliases = [];
|
||||||
}
|
}
|
||||||
@ -305,7 +305,7 @@ export class Service {
|
|||||||
);
|
);
|
||||||
expose = expose.concat(_.keys(imageExposedPorts));
|
expose = expose.concat(_.keys(imageExposedPorts));
|
||||||
// Also add any exposed ports which are implied from the portMaps
|
// Also add any exposed ports which are implied from the portMaps
|
||||||
const exposedFromPortMappings = _.flatMap(portMaps, port =>
|
const exposedFromPortMappings = _.flatMap(portMaps, (port) =>
|
||||||
port.toExposedPortArray(),
|
port.toExposedPortArray(),
|
||||||
);
|
);
|
||||||
expose = expose.concat(exposedFromPortMappings);
|
expose = expose.concat(exposedFromPortMappings);
|
||||||
@ -326,11 +326,13 @@ export class Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_.isArray(config.sysctls)) {
|
if (_.isArray(config.sysctls)) {
|
||||||
config.sysctls = _.fromPairs(_.map(config.sysctls, v => _.split(v, '=')));
|
config.sysctls = _.fromPairs(
|
||||||
|
_.map(config.sysctls, (v) => _.split(v, '=')),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
config.sysctls = _.mapValues(config.sysctls, String);
|
config.sysctls = _.mapValues(config.sysctls, String);
|
||||||
|
|
||||||
_.each(['cpuShares', 'cpuQuota', 'oomScoreAdj'], key => {
|
_.each(['cpuShares', 'cpuQuota', 'oomScoreAdj'], (key) => {
|
||||||
const numVal = checkInt(config[key]);
|
const numVal = checkInt(config[key]);
|
||||||
if (numVal) {
|
if (numVal) {
|
||||||
config[key] = numVal;
|
config[key] = numVal;
|
||||||
@ -458,7 +460,7 @@ export class Service {
|
|||||||
|
|
||||||
const portMaps = PortMap.fromDockerOpts(container.HostConfig.PortBindings);
|
const portMaps = PortMap.fromDockerOpts(container.HostConfig.PortBindings);
|
||||||
let expose = _.flatMap(
|
let expose = _.flatMap(
|
||||||
_.flatMap(portMaps, p => p.toDockerOpts().exposedPorts),
|
_.flatMap(portMaps, (p) => p.toDockerOpts().exposedPorts),
|
||||||
_.keys,
|
_.keys,
|
||||||
);
|
);
|
||||||
if (container.Config.ExposedPorts != null) {
|
if (container.Config.ExposedPorts != null) {
|
||||||
@ -578,7 +580,7 @@ export class Service {
|
|||||||
const { exposedPorts, portBindings } = this.generateExposeAndPorts();
|
const { exposedPorts, portBindings } = this.generateExposeAndPorts();
|
||||||
|
|
||||||
const tmpFs: Dictionary<''> = {};
|
const tmpFs: Dictionary<''> = {};
|
||||||
_.each(this.config.tmpfs, tmp => {
|
_.each(this.config.tmpfs, (tmp) => {
|
||||||
tmpFs[tmp] = '';
|
tmpFs[tmp] = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -814,7 +816,7 @@ export class Service {
|
|||||||
this.appId || 0,
|
this.appId || 0,
|
||||||
this.serviceName || '',
|
this.serviceName || '',
|
||||||
);
|
);
|
||||||
const validVolumes = _.map(this.config.volumes, volume => {
|
const validVolumes = _.map(this.config.volumes, (volume) => {
|
||||||
if (_.includes(defaults, volume) || !_.includes(volume, ':')) {
|
if (_.includes(defaults, volume) || !_.includes(volume, ':')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -855,7 +857,7 @@ export class Service {
|
|||||||
} {
|
} {
|
||||||
const binds: string[] = [];
|
const binds: string[] = [];
|
||||||
const volumes: { [volName: string]: {} } = {};
|
const volumes: { [volName: string]: {} } = {};
|
||||||
_.each(this.config.volumes, volume => {
|
_.each(this.config.volumes, (volume) => {
|
||||||
if (_.includes(volume, ':')) {
|
if (_.includes(volume, ':')) {
|
||||||
binds.push(volume);
|
binds.push(volume);
|
||||||
} else {
|
} else {
|
||||||
@ -870,7 +872,7 @@ export class Service {
|
|||||||
const exposed: DockerPortOptions['exposedPorts'] = {};
|
const exposed: DockerPortOptions['exposedPorts'] = {};
|
||||||
const ports: DockerPortOptions['portBindings'] = {};
|
const ports: DockerPortOptions['portBindings'] = {};
|
||||||
|
|
||||||
_.each(this.config.portMaps, pmap => {
|
_.each(this.config.portMaps, (pmap) => {
|
||||||
const { exposedPorts, portBindings } = pmap.toDockerOpts();
|
const { exposedPorts, portBindings } = pmap.toDockerOpts();
|
||||||
_.merge(exposed, exposedPorts);
|
_.merge(exposed, exposedPorts);
|
||||||
_.mergeWith(ports, portBindings, (destVal, srcVal) => {
|
_.mergeWith(ports, portBindings, (destVal, srcVal) => {
|
||||||
@ -884,7 +886,7 @@ export class Service {
|
|||||||
// We also want to merge the compose and image exposedPorts
|
// We also want to merge the compose and image exposedPorts
|
||||||
// into the list of exposedPorts
|
// into the list of exposedPorts
|
||||||
const composeExposed: DockerPortOptions['exposedPorts'] = {};
|
const composeExposed: DockerPortOptions['exposedPorts'] = {};
|
||||||
_.each(this.config.expose, port => {
|
_.each(this.config.expose, (port) => {
|
||||||
composeExposed[port] = {};
|
composeExposed[port] = {};
|
||||||
});
|
});
|
||||||
_.merge(exposed, composeExposed);
|
_.merge(exposed, composeExposed);
|
||||||
@ -948,9 +950,9 @@ export class Service {
|
|||||||
const [currentAliases, targetAliases] = [
|
const [currentAliases, targetAliases] = [
|
||||||
current.aliases,
|
current.aliases,
|
||||||
target.aliases,
|
target.aliases,
|
||||||
].map(aliases =>
|
].map((aliases) =>
|
||||||
_.sortBy(
|
_.sortBy(
|
||||||
aliases.filter(a => !_.startsWith(this.containerId || '', a)),
|
aliases.filter((a) => !_.startsWith(this.containerId || '', a)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1006,7 +1008,7 @@ export class Service {
|
|||||||
): ServiceConfig['volumes'] {
|
): ServiceConfig['volumes'] {
|
||||||
let volumes: ServiceConfig['volumes'] = [];
|
let volumes: ServiceConfig['volumes'] = [];
|
||||||
|
|
||||||
_.each(composeVolumes, volume => {
|
_.each(composeVolumes, (volume) => {
|
||||||
const isBind = _.includes(volume, ':');
|
const isBind = _.includes(volume, ':');
|
||||||
if (isBind) {
|
if (isBind) {
|
||||||
const [bindSource, bindDest, mode] = volume.split(':');
|
const [bindSource, bindDest, mode] = volume.split(':');
|
||||||
|
@ -41,7 +41,7 @@ export class VolumeManager {
|
|||||||
|
|
||||||
public async getAll(): Promise<Volume[]> {
|
public async getAll(): Promise<Volume[]> {
|
||||||
const volumeInspect = await this.listWithBothLabels();
|
const volumeInspect = await this.listWithBothLabels();
|
||||||
return volumeInspect.map(inspect =>
|
return volumeInspect.map((inspect) =>
|
||||||
Volume.fromDockerVolume(
|
Volume.fromDockerVolume(
|
||||||
{ logger: this.logger, docker: this.docker },
|
{ logger: this.logger, docker: this.docker },
|
||||||
inspect,
|
inspect,
|
||||||
@ -144,11 +144,11 @@ export class VolumeManager {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const containerVolumes = _(dockerContainers)
|
const containerVolumes = _(dockerContainers)
|
||||||
.flatMap(c => c.Mounts)
|
.flatMap((c) => c.Mounts)
|
||||||
.filter(m => m.Type === 'volume')
|
.filter((m) => m.Type === 'volume')
|
||||||
// We know that the name must be set, if the mount is
|
// We know that the name must be set, if the mount is
|
||||||
// a volume
|
// a volume
|
||||||
.map(m => m.Name as string)
|
.map((m) => m.Name as string)
|
||||||
.uniq()
|
.uniq()
|
||||||
.value();
|
.value();
|
||||||
const volumeNames = _.map(dockerVolumes.Volumes, 'Name');
|
const volumeNames = _.map(dockerVolumes.Volumes, 'Name');
|
||||||
@ -161,7 +161,7 @@ export class VolumeManager {
|
|||||||
referencedVolumes,
|
referencedVolumes,
|
||||||
);
|
);
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
volumesToRemove.map(v => this.docker.getVolume(v).remove()),
|
volumesToRemove.map((v) => this.docker.getVolume(v).remove()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ export class RPiConfigBackend extends DeviceConfigBackend {
|
|||||||
confStatements.push(`${key} ${value}`);
|
confStatements.push(`${key} ${value}`);
|
||||||
} else if (_.isArray(value)) {
|
} else if (_.isArray(value)) {
|
||||||
confStatements = confStatements.concat(
|
confStatements = confStatements.concat(
|
||||||
_.map(value, entry => `${key}=${entry}`),
|
_.map(value, (entry) => `${key}=${entry}`),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
confStatements.push(`${key}=${value}`);
|
confStatements.push(`${key}=${value}`);
|
||||||
@ -334,7 +334,7 @@ export class ExtlinuxConfigBackend extends DeviceConfigBackend {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const appendLine = _.filter(defaultEntry.APPEND.split(' '), entry => {
|
const appendLine = _.filter(defaultEntry.APPEND.split(' '), (entry) => {
|
||||||
const lhs = entry.split('=');
|
const lhs = entry.split('=');
|
||||||
return !this.isSupportedConfig(lhs[0]);
|
return !this.isSupportedConfig(lhs[0]);
|
||||||
});
|
});
|
||||||
@ -384,7 +384,7 @@ export class ExtlinuxConfigBackend extends DeviceConfigBackend {
|
|||||||
|
|
||||||
// Firstly split by line and filter any comments and empty lines
|
// Firstly split by line and filter any comments and empty lines
|
||||||
let lines = confStr.split(/\r?\n/);
|
let lines = confStr.split(/\r?\n/);
|
||||||
lines = _.filter(lines, l => {
|
lines = _.filter(lines, (l) => {
|
||||||
const trimmed = _.trimStart(l);
|
const trimmed = _.trimStart(l);
|
||||||
return trimmed !== '' && !_.startsWith(trimmed, '#');
|
return trimmed !== '' && !_.startsWith(trimmed, '#');
|
||||||
});
|
});
|
||||||
@ -647,7 +647,7 @@ export class ConfigfsConfigBackend extends DeviceConfigBackend {
|
|||||||
return [value];
|
return [value];
|
||||||
} else {
|
} else {
|
||||||
// or, it could be parsable as the content of a JSON array; "value" | "value1","value2"
|
// or, it could be parsable as the content of a JSON array; "value" | "value1","value2"
|
||||||
return value.split(',').map(v => v.replace('"', '').trim());
|
return value.split(',').map((v) => v.replace('"', '').trim());
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return value;
|
return value;
|
||||||
|
@ -30,9 +30,9 @@ export default class ConfigJsonConfigBackend {
|
|||||||
this.schema = schema;
|
this.schema = schema;
|
||||||
|
|
||||||
this.writeLockConfigJson = () =>
|
this.writeLockConfigJson = () =>
|
||||||
writeLock('config.json').disposer(release => release());
|
writeLock('config.json').disposer((release) => release());
|
||||||
this.readLockConfigJson = () =>
|
this.readLockConfigJson = () =>
|
||||||
readLock('config.json').disposer(release => release());
|
readLock('config.json').disposer((release) => release());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async set<T extends Schema.SchemaKey>(
|
public async set<T extends Schema.SchemaKey>(
|
||||||
@ -94,12 +94,12 @@ export default class ConfigJsonConfigBackend {
|
|||||||
private write(): Promise<void> {
|
private write(): Promise<void> {
|
||||||
let atomicWritePossible = true;
|
let atomicWritePossible = true;
|
||||||
return this.pathOnHost()
|
return this.pathOnHost()
|
||||||
.catch(err => {
|
.catch((err) => {
|
||||||
log.error('There was an error detecting the config.json path', err);
|
log.error('There was an error detecting the config.json path', err);
|
||||||
atomicWritePossible = false;
|
atomicWritePossible = false;
|
||||||
return constants.configJsonNonAtomicPath;
|
return constants.configJsonNonAtomicPath;
|
||||||
})
|
})
|
||||||
.then(configPath => {
|
.then((configPath) => {
|
||||||
if (atomicWritePossible) {
|
if (atomicWritePossible) {
|
||||||
return writeFileAtomic(configPath, JSON.stringify(this.cache));
|
return writeFileAtomic(configPath, JSON.stringify(this.cache));
|
||||||
} else {
|
} else {
|
||||||
|
@ -24,7 +24,7 @@ export const fnSchema = {
|
|||||||
provisioned: (config: Config) => {
|
provisioned: (config: Config) => {
|
||||||
return config
|
return config
|
||||||
.getMany(['uuid', 'apiEndpoint', 'registered_at', 'deviceId'])
|
.getMany(['uuid', 'apiEndpoint', 'registered_at', 'deviceId'])
|
||||||
.then(requiredValues => {
|
.then((requiredValues) => {
|
||||||
return _.every(_.values(requiredValues));
|
return _.every(_.values(requiredValues));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -64,7 +64,7 @@ export const fnSchema = {
|
|||||||
'registered_at',
|
'registered_at',
|
||||||
'deviceId',
|
'deviceId',
|
||||||
])
|
])
|
||||||
.then(conf => {
|
.then((conf) => {
|
||||||
return {
|
return {
|
||||||
uuid: conf.uuid,
|
uuid: conf.uuid,
|
||||||
applicationId: conf.applicationId,
|
applicationId: conf.applicationId,
|
||||||
@ -80,7 +80,7 @@ export const fnSchema = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
mixpanelHost: (config: Config) => {
|
mixpanelHost: (config: Config) => {
|
||||||
return config.get('apiEndpoint').then(apiEndpoint => {
|
return config.get('apiEndpoint').then((apiEndpoint) => {
|
||||||
if (!apiEndpoint) {
|
if (!apiEndpoint) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -117,7 +117,7 @@ export const fnSchema = {
|
|||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
unmanaged: (config: Config) => {
|
unmanaged: (config: Config) => {
|
||||||
return config.get('apiEndpoint').then(apiEndpoint => {
|
return config.get('apiEndpoint').then((apiEndpoint) => {
|
||||||
return !apiEndpoint;
|
return !apiEndpoint;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -70,7 +70,7 @@ export class Config extends (EventEmitter as new () => ConfigEventEmitter) {
|
|||||||
if (Schema.schema.hasOwnProperty(key)) {
|
if (Schema.schema.hasOwnProperty(key)) {
|
||||||
const schemaKey = key as Schema.SchemaKey;
|
const schemaKey = key as Schema.SchemaKey;
|
||||||
|
|
||||||
return this.getSchema(schemaKey, db).then(value => {
|
return this.getSchema(schemaKey, db).then((value) => {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
const defaultValue = schemaTypes[key].default;
|
const defaultValue = schemaTypes[key].default;
|
||||||
if (defaultValue instanceof t.Type) {
|
if (defaultValue instanceof t.Type) {
|
||||||
@ -118,7 +118,7 @@ export class Config extends (EventEmitter as new () => ConfigEventEmitter) {
|
|||||||
keys: T[],
|
keys: T[],
|
||||||
trx?: Transaction,
|
trx?: Transaction,
|
||||||
): Bluebird<{ [key in T]: SchemaReturn<key> }> {
|
): Bluebird<{ [key in T]: SchemaReturn<key> }> {
|
||||||
return Bluebird.map(keys, (key: T) => this.get(key, trx)).then(values => {
|
return Bluebird.map(keys, (key: T) => this.get(key, trx)).then((values) => {
|
||||||
return _.zipObject(keys, values);
|
return _.zipObject(keys, values);
|
||||||
}) as Bluebird<{ [key in T]: SchemaReturn<key> }>;
|
}) as Bluebird<{ [key in T]: SchemaReturn<key> }>;
|
||||||
}
|
}
|
||||||
@ -198,10 +198,7 @@ export class Config extends (EventEmitter as new () => ConfigEventEmitter) {
|
|||||||
if (Schema.schema[key].source === 'config.json') {
|
if (Schema.schema[key].source === 'config.json') {
|
||||||
return this.configJsonBackend.remove(key);
|
return this.configJsonBackend.remove(key);
|
||||||
} else if (Schema.schema[key].source === 'db') {
|
} else if (Schema.schema[key].source === 'db') {
|
||||||
await this.db
|
await this.db.models('config').del().where({ key });
|
||||||
.models('config')
|
|
||||||
.del()
|
|
||||||
.where({ key });
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unknown or unsupported config backend: ${Schema.schema[key].source}`,
|
`Unknown or unsupported config backend: ${Schema.schema[key].source}`,
|
||||||
@ -247,9 +244,7 @@ export class Config extends (EventEmitter as new () => ConfigEventEmitter) {
|
|||||||
value = await this.configJsonBackend.get(key);
|
value = await this.configJsonBackend.get(key);
|
||||||
break;
|
break;
|
||||||
case 'db':
|
case 'db':
|
||||||
const [conf] = await db('config')
|
const [conf] = await db('config').select('value').where({ key });
|
||||||
.select('value')
|
|
||||||
.where({ key });
|
|
||||||
if (conf != null) {
|
if (conf != null) {
|
||||||
return conf.value;
|
return conf.value;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ export const PermissiveBoolean = new t.Type<boolean, t.TypeOf<PermissiveType>>(
|
|||||||
'PermissiveBoolean',
|
'PermissiveBoolean',
|
||||||
_.isBoolean,
|
_.isBoolean,
|
||||||
(m, c) =>
|
(m, c) =>
|
||||||
either.chain(permissiveValue.validate(m, c), v => {
|
either.chain(permissiveValue.validate(m, c), (v) => {
|
||||||
switch (typeof v) {
|
switch (typeof v) {
|
||||||
case 'string':
|
case 'string':
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
@ -51,7 +51,7 @@ export const PermissiveNumber = new t.Type<number, string | number>(
|
|||||||
'PermissiveNumber',
|
'PermissiveNumber',
|
||||||
_.isNumber,
|
_.isNumber,
|
||||||
(m, c) =>
|
(m, c) =>
|
||||||
either.chain(t.union([t.string, t.number]).validate(m, c), v => {
|
either.chain(t.union([t.string, t.number]).validate(m, c), (v) => {
|
||||||
switch (typeof v) {
|
switch (typeof v) {
|
||||||
case 'number':
|
case 'number':
|
||||||
return t.success(v);
|
return t.success(v);
|
||||||
@ -82,7 +82,7 @@ export class StringJSON<T> extends t.Type<T, string> {
|
|||||||
(m, c) =>
|
(m, c) =>
|
||||||
// Accept either an object, or a string which represents the
|
// Accept either an object, or a string which represents the
|
||||||
// object
|
// object
|
||||||
either.chain(t.union([t.string, type]).validate(m, c), v => {
|
either.chain(t.union([t.string, type]).validate(m, c), (v) => {
|
||||||
let obj: T;
|
let obj: T;
|
||||||
if (typeof v === 'string') {
|
if (typeof v === 'string') {
|
||||||
obj = JSON.parse(v);
|
obj = JSON.parse(v);
|
||||||
|
@ -28,7 +28,7 @@ export const initialiseConfigBackend = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getConfigBackend(deviceType: string): DeviceConfigBackend | undefined {
|
function getConfigBackend(deviceType: string): DeviceConfigBackend | undefined {
|
||||||
return _.find(configBackends, backend => backend.matches(deviceType));
|
return _.find(configBackends, (backend) => backend.matches(deviceType));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function envToBootConfig(
|
export function envToBootConfig(
|
||||||
@ -54,7 +54,7 @@ export function bootConfigToEnv(
|
|||||||
): EnvVarObject {
|
): EnvVarObject {
|
||||||
return _(config)
|
return _(config)
|
||||||
.mapKeys((_val, key) => configBackend.createConfigVarName(key))
|
.mapKeys((_val, key) => configBackend.createConfigVarName(key))
|
||||||
.mapValues(val => {
|
.mapValues((val) => {
|
||||||
if (_.isArray(val)) {
|
if (_.isArray(val)) {
|
||||||
return JSON.stringify(val).replace(/^\[(.*)\]$/, '$1');
|
return JSON.stringify(val).replace(/^\[(.*)\]$/, '$1');
|
||||||
}
|
}
|
||||||
|
@ -49,9 +49,7 @@ export class DB {
|
|||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const knex = trx || this.knex;
|
const knex = trx || this.knex;
|
||||||
|
|
||||||
const n = await knex(modelName)
|
const n = await knex(modelName).update(obj).where(id);
|
||||||
.update(obj)
|
|
||||||
.where(id);
|
|
||||||
if (n === 0) {
|
if (n === 0) {
|
||||||
return knex(modelName).insert(obj);
|
return knex(modelName).insert(obj);
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ export function doPurge(applications, appId, force) {
|
|||||||
// remove the volumes, we must do this here, as the
|
// remove the volumes, we must do this here, as the
|
||||||
// application-manager will not remove any volumes
|
// application-manager will not remove any volumes
|
||||||
// which are part of an active application
|
// which are part of an active application
|
||||||
return Bluebird.each(volumes.getAllByAppId(appId), vol =>
|
return Bluebird.each(volumes.getAllByAppId(appId), (vol) =>
|
||||||
vol.remove(),
|
vol.remove(),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@ -73,7 +73,7 @@ export function doPurge(applications, appId, force) {
|
|||||||
.tap(() =>
|
.tap(() =>
|
||||||
logger.logSystemMessage('Purged data', { appId }, 'Purge data success'),
|
logger.logSystemMessage('Purged data', { appId }, 'Purge data success'),
|
||||||
)
|
)
|
||||||
.tapCatch(err =>
|
.tapCatch((err) =>
|
||||||
logger.logSystemMessage(
|
logger.logSystemMessage(
|
||||||
`Error purging data: ${err}`,
|
`Error purging data: ${err}`,
|
||||||
{ appId, error: err },
|
{ appId, error: err },
|
||||||
|
@ -62,14 +62,14 @@ export const createV1Api = function(router, applications) {
|
|||||||
return service;
|
return service;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(service2 =>
|
.then((service2) =>
|
||||||
res.status(200).json({ containerId: service2.containerId }),
|
res.status(200).json({ containerId: service2.containerId }),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(next);
|
.catch(next);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createV1StopOrStartHandler = action =>
|
const createV1StopOrStartHandler = (action) =>
|
||||||
_.partial(v1StopOrStart, _, _, _, action);
|
_.partial(v1StopOrStart, _, _, _, action);
|
||||||
|
|
||||||
router.post('/v1/apps/:appId/stop', createV1StopOrStartHandler('stop'));
|
router.post('/v1/apps/:appId/stop', createV1StopOrStartHandler('stop'));
|
||||||
|
@ -39,7 +39,7 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
|
|||||||
return _lockingIfNecessary(appId, { force }, () => {
|
return _lockingIfNecessary(appId, { force }, () => {
|
||||||
return applications
|
return applications
|
||||||
.getCurrentApp(appId)
|
.getCurrentApp(appId)
|
||||||
.then(app => {
|
.then((app) => {
|
||||||
if (app == null) {
|
if (app == null) {
|
||||||
res.status(404).send(appNotFoundMessage);
|
res.status(404).send(appNotFoundMessage);
|
||||||
return;
|
return;
|
||||||
@ -54,11 +54,11 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
|
|||||||
|
|
||||||
let service: Service | undefined;
|
let service: Service | undefined;
|
||||||
if (imageId != null) {
|
if (imageId != null) {
|
||||||
service = _.find(app.services, svc => svc.imageId === imageId);
|
service = _.find(app.services, (svc) => svc.imageId === imageId);
|
||||||
} else {
|
} else {
|
||||||
service = _.find(
|
service = _.find(
|
||||||
app.services,
|
app.services,
|
||||||
svc => svc.serviceName === serviceName,
|
(svc) => svc.serviceName === serviceName,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (service == null) {
|
if (service == null) {
|
||||||
@ -173,7 +173,7 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
|
|||||||
|
|
||||||
const appNameById: { [id: number]: string } = {};
|
const appNameById: { [id: number]: string } = {};
|
||||||
|
|
||||||
apps.forEach(app => {
|
apps.forEach((app) => {
|
||||||
const appId = parseInt(app.appId, 10);
|
const appId = parseInt(app.appId, 10);
|
||||||
response[app.name] = {
|
response[app.name] = {
|
||||||
appId,
|
appId,
|
||||||
@ -184,7 +184,7 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
|
|||||||
appNameById[appId] = app.name;
|
appNameById[appId] = app.name;
|
||||||
});
|
});
|
||||||
|
|
||||||
images.forEach(img => {
|
images.forEach((img) => {
|
||||||
const appName = appNameById[img.appId];
|
const appName = appNameById[img.appId];
|
||||||
if (appName == null) {
|
if (appName == null) {
|
||||||
log.warn(
|
log.warn(
|
||||||
@ -224,7 +224,7 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
|
|||||||
// Get all services and their statuses, and return it
|
// Get all services and their statuses, and return it
|
||||||
applications
|
applications
|
||||||
.getStatus()
|
.getStatus()
|
||||||
.then(apps => {
|
.then((apps) => {
|
||||||
res.status(200).json(apps);
|
res.status(200).json(apps);
|
||||||
})
|
})
|
||||||
.catch(next);
|
.catch(next);
|
||||||
@ -258,14 +258,14 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
|
|||||||
target.local = {
|
target.local = {
|
||||||
name: targetState.local.name,
|
name: targetState.local.name,
|
||||||
config: _.cloneDeep(targetState.local.config),
|
config: _.cloneDeep(targetState.local.config),
|
||||||
apps: _.mapValues(targetState.local.apps, app => ({
|
apps: _.mapValues(targetState.local.apps, (app) => ({
|
||||||
appId: app.appId,
|
appId: app.appId,
|
||||||
name: app.name,
|
name: app.name,
|
||||||
commit: app.commit,
|
commit: app.commit,
|
||||||
releaseId: app.releaseId,
|
releaseId: app.releaseId,
|
||||||
services: _.map(app.services, s => s.toComposeObject()),
|
services: _.map(app.services, (s) => s.toComposeObject()),
|
||||||
volumes: _.mapValues(app.volumes, v => v.toComposeObject()),
|
volumes: _.mapValues(app.volumes, (v) => v.toComposeObject()),
|
||||||
networks: _.mapValues(app.networks, n => n.toComposeObject()),
|
networks: _.mapValues(app.networks, (n) => n.toComposeObject()),
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -369,7 +369,10 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
|
|||||||
|
|
||||||
if (req.query.serviceName != null || req.query.service != null) {
|
if (req.query.serviceName != null || req.query.service != null) {
|
||||||
const serviceName = req.query.serviceName || req.query.service;
|
const serviceName = req.query.serviceName || req.query.service;
|
||||||
const service = _.find(services, svc => svc.serviceName === serviceName);
|
const service = _.find(
|
||||||
|
services,
|
||||||
|
(svc) => svc.serviceName === serviceName,
|
||||||
|
);
|
||||||
if (service != null) {
|
if (service != null) {
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
status: 'success',
|
status: 'success',
|
||||||
@ -396,7 +399,7 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
|
|||||||
const currentRelease = await applications.config.get('currentCommit');
|
const currentRelease = await applications.config.get('currentCommit');
|
||||||
|
|
||||||
const pending = applications.deviceState.applyInProgress;
|
const pending = applications.deviceState.applyInProgress;
|
||||||
const containerStates = (await applications.services.getAll()).map(svc =>
|
const containerStates = (await applications.services.getAll()).map((svc) =>
|
||||||
_.pick(
|
_.pick(
|
||||||
svc,
|
svc,
|
||||||
'status',
|
'status',
|
||||||
@ -411,7 +414,7 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
|
|||||||
|
|
||||||
let downloadProgressTotal = 0;
|
let downloadProgressTotal = 0;
|
||||||
let downloads = 0;
|
let downloads = 0;
|
||||||
const imagesStates = (await applications.images.getStatus()).map(img => {
|
const imagesStates = (await applications.images.getStatus()).map((img) => {
|
||||||
if (img.downloadProgress != null) {
|
if (img.downloadProgress != null) {
|
||||||
downloadProgressTotal += img.downloadProgress;
|
downloadProgressTotal += img.downloadProgress;
|
||||||
downloads += 1;
|
downloads += 1;
|
||||||
@ -483,8 +486,8 @@ export function createV2Api(router: Router, applications: ApplicationManager) {
|
|||||||
router.get('/v2/cleanup-volumes', async (_req, res) => {
|
router.get('/v2/cleanup-volumes', async (_req, res) => {
|
||||||
const targetState = await applications.getTargetApps();
|
const targetState = await applications.getTargetApps();
|
||||||
const referencedVolumes: string[] = [];
|
const referencedVolumes: string[] = [];
|
||||||
_.each(targetState, app => {
|
_.each(targetState, (app) => {
|
||||||
_.each(app.volumes, vol => {
|
_.each(app.volumes, (vol) => {
|
||||||
referencedVolumes.push(Volume.generateDockerName(vol.appId, vol.name));
|
referencedVolumes.push(Volume.generateDockerName(vol.appId, vol.name));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -156,7 +156,7 @@ export class DeviceConfig {
|
|||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
|
|
||||||
this.actionExecutors = {
|
this.actionExecutors = {
|
||||||
changeConfig: async step => {
|
changeConfig: async (step) => {
|
||||||
try {
|
try {
|
||||||
if (step.humanReadableTarget) {
|
if (step.humanReadableTarget) {
|
||||||
this.logger.logConfigChange(step.humanReadableTarget);
|
this.logger.logConfigChange(step.humanReadableTarget);
|
||||||
@ -203,7 +203,7 @@ export class DeviceConfig {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setBootConfig: async step => {
|
setBootConfig: async (step) => {
|
||||||
const configBackend = await this.getConfigBackend();
|
const configBackend = await this.getConfigBackend();
|
||||||
if (!_.isObject(step.target)) {
|
if (!_.isObject(step.target)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -330,7 +330,7 @@ export class DeviceConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public resetRateLimits() {
|
public resetRateLimits() {
|
||||||
_.each(this.rateLimits, action => {
|
_.each(this.rateLimits, (action) => {
|
||||||
action.lastAttempt = null;
|
action.lastAttempt = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -468,7 +468,7 @@ export class DeviceConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
steps = _.map(steps, step => {
|
steps = _.map(steps, (step) => {
|
||||||
const action = step.action;
|
const action = step.action;
|
||||||
if (action in this.rateLimits) {
|
if (action in this.rateLimits) {
|
||||||
const lastAttempt = this.rateLimits[action].lastAttempt;
|
const lastAttempt = this.rateLimits[action].lastAttempt;
|
||||||
@ -640,7 +640,7 @@ export class DeviceConfig {
|
|||||||
if (!_.includes(conf.dtoverlay, field)) {
|
if (!_.includes(conf.dtoverlay, field)) {
|
||||||
conf.dtoverlay.push(field);
|
conf.dtoverlay.push(field);
|
||||||
}
|
}
|
||||||
conf.dtoverlay = conf.dtoverlay.filter(s => !_.isEmpty(s));
|
conf.dtoverlay = conf.dtoverlay.filter((s) => !_.isEmpty(s));
|
||||||
|
|
||||||
return conf;
|
return conf;
|
||||||
}
|
}
|
||||||
|
@ -121,8 +121,8 @@ function createDeviceStateRouter(deviceState: DeviceState) {
|
|||||||
router.get('/v1/device/host-config', (_req, res) =>
|
router.get('/v1/device/host-config', (_req, res) =>
|
||||||
hostConfig
|
hostConfig
|
||||||
.get()
|
.get()
|
||||||
.then(conf => res.json(conf))
|
.then((conf) => res.json(conf))
|
||||||
.catch(err =>
|
.catch((err) =>
|
||||||
res.status(503).send(err?.message ?? err ?? 'Unknown error'),
|
res.status(503).send(err?.message ?? err ?? 'Unknown error'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -131,7 +131,7 @@ function createDeviceStateRouter(deviceState: DeviceState) {
|
|||||||
hostConfig
|
hostConfig
|
||||||
.patch(req.body, deviceState.config)
|
.patch(req.body, deviceState.config)
|
||||||
.then(() => res.status(200).send('OK'))
|
.then(() => res.status(200).send('OK'))
|
||||||
.catch(err =>
|
.catch((err) =>
|
||||||
res.status(503).send(err?.message ?? err ?? 'Unknown error'),
|
res.status(503).send(err?.message ?? err ?? 'Unknown error'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -271,7 +271,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
|
|||||||
apiBinder,
|
apiBinder,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('error', err => log.error('deviceState error: ', err));
|
this.on('error', (err) => log.error('deviceState error: ', err));
|
||||||
this.on('apply-target-state-end', function (err) {
|
this.on('apply-target-state-end', function (err) {
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
if (!(err instanceof UpdatesLockedError)) {
|
if (!(err instanceof UpdatesLockedError)) {
|
||||||
@ -285,7 +285,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
|
|||||||
return this.deviceConfig.resetRateLimits();
|
return this.deviceConfig.resetRateLimits();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.applications.on('change', d => this.reportCurrentState(d));
|
this.applications.on('change', (d) => this.reportCurrentState(d));
|
||||||
this.router = createDeviceStateRouter(this);
|
this.router = createDeviceStateRouter(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,7 +307,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async init() {
|
public async init() {
|
||||||
this.config.on('change', changedConfig => {
|
this.config.on('change', (changedConfig) => {
|
||||||
if (changedConfig.loggingEnabled != null) {
|
if (changedConfig.loggingEnabled != null) {
|
||||||
this.logger.enable(changedConfig.loggingEnabled);
|
this.logger.enable(changedConfig.loggingEnabled);
|
||||||
}
|
}
|
||||||
@ -389,7 +389,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
|
|||||||
network.startConnectivityCheck(
|
network.startConnectivityCheck(
|
||||||
apiEndpoint,
|
apiEndpoint,
|
||||||
connectivityCheckEnabled,
|
connectivityCheckEnabled,
|
||||||
connected => {
|
(connected) => {
|
||||||
return (this.connected = connected);
|
return (this.connected = connected);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -402,7 +402,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
|
|||||||
});
|
});
|
||||||
log.debug('Starting periodic check for IP addresses');
|
log.debug('Starting periodic check for IP addresses');
|
||||||
|
|
||||||
await network.startIPAddressUpdate()(async addresses => {
|
await network.startIPAddressUpdate()(async (addresses) => {
|
||||||
await this.reportCurrentState({
|
await this.reportCurrentState({
|
||||||
ip_address: addresses.join(' '),
|
ip_address: addresses.join(' '),
|
||||||
});
|
});
|
||||||
@ -433,11 +433,11 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readLockTarget = () =>
|
private readLockTarget = () =>
|
||||||
this.readLock('target').disposer(release => release());
|
this.readLock('target').disposer((release) => release());
|
||||||
private writeLockTarget = () =>
|
private writeLockTarget = () =>
|
||||||
this.writeLock('target').disposer(release => release());
|
this.writeLock('target').disposer((release) => release());
|
||||||
private inferStepsLock = () =>
|
private inferStepsLock = () =>
|
||||||
this.writeLock('inferSteps').disposer(release => release());
|
this.writeLock('inferSteps').disposer((release) => release());
|
||||||
private usingReadLockTarget(fn: () => any) {
|
private usingReadLockTarget(fn: () => any) {
|
||||||
return Bluebird.using(this.readLockTarget, () => fn());
|
return Bluebird.using(this.readLockTarget, () => fn());
|
||||||
}
|
}
|
||||||
@ -463,7 +463,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
|
|||||||
const apiEndpoint = await this.config.get('apiEndpoint');
|
const apiEndpoint = await this.config.get('apiEndpoint');
|
||||||
|
|
||||||
await this.usingWriteLockTarget(async () => {
|
await this.usingWriteLockTarget(async () => {
|
||||||
await this.db.transaction(async trx => {
|
await this.db.transaction(async (trx) => {
|
||||||
await this.config.set({ name: target.local.name }, trx);
|
await this.config.set({ name: target.local.name }, trx);
|
||||||
await this.deviceConfig.setTarget(target.local.config, trx);
|
await this.deviceConfig.setTarget(target.local.config, trx);
|
||||||
|
|
||||||
@ -762,7 +762,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
|
|||||||
if (!intermediate) {
|
if (!intermediate) {
|
||||||
this.reportCurrentState({ update_pending: true });
|
this.reportCurrentState({ update_pending: true });
|
||||||
}
|
}
|
||||||
if (_.every(steps, step => step.action === 'noop')) {
|
if (_.every(steps, (step) => step.action === 'noop')) {
|
||||||
if (backoff) {
|
if (backoff) {
|
||||||
retryCount += 1;
|
retryCount += 1;
|
||||||
// Backoff to a maximum of 10 minutes
|
// Backoff to a maximum of 10 minutes
|
||||||
@ -774,7 +774,7 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
steps.map(s => this.applyStep(s, { force, initial, skipLock })),
|
steps.map((s) => this.applyStep(s, { force, initial, skipLock })),
|
||||||
);
|
);
|
||||||
|
|
||||||
await Bluebird.delay(nextDelay);
|
await Bluebird.delay(nextDelay);
|
||||||
@ -798,20 +798,20 @@ export class DeviceState extends (EventEmitter as new () => DeviceStateEventEmit
|
|||||||
JSON.stringify(_.map(steps, 'action')),
|
JSON.stringify(_.map(steps, 'action')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).catch((err) => {
|
||||||
return this.applyError(err, { force, initial, intermediate });
|
return this.applyError(err, { force, initial, intermediate });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public pausingApply(fn: () => any) {
|
public pausingApply(fn: () => any) {
|
||||||
const lock = () => {
|
const lock = () => {
|
||||||
return this.writeLock('pause').disposer(release => release());
|
return this.writeLock('pause').disposer((release) => release());
|
||||||
};
|
};
|
||||||
// TODO: This function is a bit of a mess
|
// TODO: This function is a bit of a mess
|
||||||
const pause = () => {
|
const pause = () => {
|
||||||
return Bluebird.try(() => {
|
return Bluebird.try(() => {
|
||||||
let res;
|
let res;
|
||||||
this.applyBlocker = new Promise(resolve => {
|
this.applyBlocker = new Promise((resolve) => {
|
||||||
res = resolve;
|
res = resolve;
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
|
@ -92,7 +92,7 @@ export class EventTracker {
|
|||||||
(event: string) => {
|
(event: string) => {
|
||||||
// Call this function at maximum once every minute
|
// Call this function at maximum once every minute
|
||||||
return _.throttle(
|
return _.throttle(
|
||||||
properties => {
|
(properties) => {
|
||||||
if (this.client != null) {
|
if (this.client != null) {
|
||||||
this.client.track(event, properties);
|
this.client.track(event, properties);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ const memoizedAuthRegex = _.memoize(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const memoizedRegex = _.memoize(
|
const memoizedRegex = _.memoize(
|
||||||
proxyField => new RegExp(proxyField + '\\s*=\\s*([^;\\s]*)\\s*;'),
|
(proxyField) => new RegExp(proxyField + '\\s*=\\s*([^;\\s]*)\\s*;'),
|
||||||
);
|
);
|
||||||
|
|
||||||
async function readProxy(): Promise<ProxyConfig | undefined> {
|
async function readProxy(): Promise<ProxyConfig | undefined> {
|
||||||
|
@ -63,10 +63,7 @@ function isValidRequirementType(
|
|||||||
export function containerContractsFulfilled(
|
export function containerContractsFulfilled(
|
||||||
serviceContracts: ServiceContracts,
|
serviceContracts: ServiceContracts,
|
||||||
): ApplicationContractResult {
|
): ApplicationContractResult {
|
||||||
const containers = _(serviceContracts)
|
const containers = _(serviceContracts).map('contract').compact().value();
|
||||||
.map('contract')
|
|
||||||
.compact()
|
|
||||||
.value();
|
|
||||||
|
|
||||||
const blueprintMembership: Dictionary<number> = {};
|
const blueprintMembership: Dictionary<number> = {};
|
||||||
for (const component of _.keys(contractRequirementVersions)) {
|
for (const component of _.keys(contractRequirementVersions)) {
|
||||||
@ -91,7 +88,7 @@ export function containerContractsFulfilled(
|
|||||||
[
|
[
|
||||||
...getContractsFromVersions(contractRequirementVersions),
|
...getContractsFromVersions(contractRequirementVersions),
|
||||||
...containers,
|
...containers,
|
||||||
].map(c => new Contract(c)),
|
].map((c) => new Contract(c)),
|
||||||
);
|
);
|
||||||
|
|
||||||
const solution = blueprint.reproduce(universe);
|
const solution = blueprint.reproduce(universe);
|
||||||
@ -132,13 +129,13 @@ export function containerContractsFulfilled(
|
|||||||
|
|
||||||
const [fulfilledServices, unfulfilledServices] = _.partition(
|
const [fulfilledServices, unfulfilledServices] = _.partition(
|
||||||
_.keys(serviceContracts),
|
_.keys(serviceContracts),
|
||||||
serviceName => {
|
(serviceName) => {
|
||||||
const { contract } = serviceContracts[serviceName];
|
const { contract } = serviceContracts[serviceName];
|
||||||
if (!contract) {
|
if (!contract) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// Did we find the contract in the generated state?
|
// Did we find the contract in the generated state?
|
||||||
return _.some(children, child =>
|
return _.some(children, (child) =>
|
||||||
_.isEqual((child as any).raw, contract),
|
_.isEqual((child as any).raw, contract),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -146,7 +143,7 @@ export function containerContractsFulfilled(
|
|||||||
|
|
||||||
const [unmetAndRequired, unmetAndOptional] = _.partition(
|
const [unmetAndRequired, unmetAndOptional] = _.partition(
|
||||||
unfulfilledServices,
|
unfulfilledServices,
|
||||||
serviceName => {
|
(serviceName) => {
|
||||||
return !serviceContracts[serviceName].optional;
|
return !serviceContracts[serviceName].optional;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -281,7 +281,7 @@ export class DockerUtils extends DockerToolbelt {
|
|||||||
.on('progress', onProgress)
|
.on('progress', onProgress)
|
||||||
.on('retry', onProgress)
|
.on('retry', onProgress)
|
||||||
.on('error', reject)
|
.on('error', reject)
|
||||||
.on('response', res => {
|
.on('response', (res) => {
|
||||||
if (res.statusCode !== 200) {
|
if (res.statusCode !== 200) {
|
||||||
reject(
|
reject(
|
||||||
new Error(
|
new Error(
|
||||||
@ -297,8 +297,8 @@ export class DockerUtils extends DockerToolbelt {
|
|||||||
});
|
});
|
||||||
res
|
res
|
||||||
.pipe(deltaStream)
|
.pipe(deltaStream)
|
||||||
.on('id', id => resolve(`sha256:${id}`))
|
.on('id', (id) => resolve(`sha256:${id}`))
|
||||||
.on('error', err => {
|
.on('error', (err) => {
|
||||||
logFn(`Delta stream emitted error: ${err}`);
|
logFn(`Delta stream emitted error: ${err}`);
|
||||||
req.abort();
|
req.abort();
|
||||||
reject(err);
|
reject(err);
|
||||||
|
@ -5,7 +5,7 @@ import * as constants from './constants';
|
|||||||
import { ENOENT } from './errors';
|
import { ENOENT } from './errors';
|
||||||
|
|
||||||
export function writeAndSyncFile(path: string, data: string): Bluebird<void> {
|
export function writeAndSyncFile(path: string, data: string): Bluebird<void> {
|
||||||
return Bluebird.resolve(fs.open(path, 'w')).then(fd => {
|
return Bluebird.resolve(fs.open(path, 'w')).then((fd) => {
|
||||||
fs.write(fd, data, 0, 'utf8')
|
fs.write(fd, data, 0, 'utf8')
|
||||||
.then(() => fs.fsync(fd))
|
.then(() => fs.fsync(fd))
|
||||||
.then(() => fs.close(fd));
|
.then(() => fs.close(fd));
|
||||||
|
@ -11,8 +11,8 @@ function applyIptablesArgs(args: string): Bluebird<void> {
|
|||||||
// We want to run both commands regardless, but also rethrow an error
|
// We want to run both commands regardless, but also rethrow an error
|
||||||
// if one of them fails
|
// if one of them fails
|
||||||
return execAsync(`iptables ${args}`)
|
return execAsync(`iptables ${args}`)
|
||||||
.catch(e => (err = e))
|
.catch((e) => (err = e))
|
||||||
.then(() => execAsync(`ip6tables ${args}`).catch(e => (err = e)))
|
.then(() => execAsync(`ip6tables ${args}`).catch((e) => (err = e)))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (err != null) {
|
if (err != null) {
|
||||||
throw err;
|
throw err;
|
||||||
@ -42,7 +42,7 @@ export function rejectOnAllInterfacesExcept(
|
|||||||
): Bluebird<void> {
|
): Bluebird<void> {
|
||||||
// We delete each rule and create it again to ensure ordering (all ACCEPTs before the REJECT/DROP).
|
// We delete each rule and create it again to ensure ordering (all ACCEPTs before the REJECT/DROP).
|
||||||
// This is especially important after a supervisor update.
|
// This is especially important after a supervisor update.
|
||||||
return Bluebird.each(allowedInterfaces, iface =>
|
return Bluebird.each(allowedInterfaces, (iface) =>
|
||||||
clearAndInsertIptablesRule(
|
clearAndInsertIptablesRule(
|
||||||
`INPUT -p tcp --dport ${port} -i ${iface} -j ACCEPT`,
|
`INPUT -p tcp --dport ${port} -i ${iface} -j ACCEPT`,
|
||||||
),
|
),
|
||||||
|
@ -166,10 +166,7 @@ export async function normaliseLegacyDatabase(
|
|||||||
log.warn(
|
log.warn(
|
||||||
`No compatible releases found in API, removing ${app.appId} from target state`,
|
`No compatible releases found in API, removing ${app.appId} from target state`,
|
||||||
);
|
);
|
||||||
await db
|
await db.models('app').where({ appId: app.appId }).del();
|
||||||
.models('app')
|
|
||||||
.where({ appId: app.appId })
|
|
||||||
.del();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to get the release.id, serviceId, image.id and updated imageUrl
|
// We need to get the release.id, serviceId, image.id and updated imageUrl
|
||||||
@ -187,7 +184,7 @@ export async function normaliseLegacyDatabase(
|
|||||||
const imageFromDocker = await application.docker
|
const imageFromDocker = await application.docker
|
||||||
.getImage(service.image)
|
.getImage(service.image)
|
||||||
.inspect()
|
.inspect()
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
if (error instanceof NotFoundError) {
|
if (error instanceof NotFoundError) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -203,9 +200,7 @@ export async function normaliseLegacyDatabase(
|
|||||||
try {
|
try {
|
||||||
if (imagesFromDatabase.length > 0) {
|
if (imagesFromDatabase.length > 0) {
|
||||||
log.debug('Deleting existing image entry in db');
|
log.debug('Deleting existing image entry in db');
|
||||||
await trx('image')
|
await trx('image').where({ name: service.image }).del();
|
||||||
.where({ name: service.image })
|
|
||||||
.del();
|
|
||||||
} else {
|
} else {
|
||||||
log.debug('No image in db to delete');
|
log.debug('No image in db to delete');
|
||||||
}
|
}
|
||||||
@ -243,9 +238,7 @@ export async function normaliseLegacyDatabase(
|
|||||||
|
|
||||||
log.debug('Updating app entry in db');
|
log.debug('Updating app entry in db');
|
||||||
log.success('Successfully migrated legacy application');
|
log.success('Successfully migrated legacy application');
|
||||||
await trx('app')
|
await trx('app').update(app).where({ appId: app.appId });
|
||||||
.update(app)
|
|
||||||
.where({ appId: app.appId });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -312,10 +305,10 @@ export async function loadBackupFromMigration(
|
|||||||
// If the volume exists (from a previous incomplete run of this restoreBackup), we delete it first
|
// If the volume exists (from a previous incomplete run of this restoreBackup), we delete it first
|
||||||
await deviceState.applications.volumes
|
await deviceState.applications.volumes
|
||||||
.get({ appId, name: volumeName })
|
.get({ appId, name: volumeName })
|
||||||
.then(volume => {
|
.then((volume) => {
|
||||||
return volume.remove();
|
return volume.remove();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
if (error instanceof NotFoundError) {
|
if (error instanceof NotFoundError) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ const maxLevelLength = _(levels)
|
|||||||
|
|
||||||
const uncolorize = winston.format.uncolorize();
|
const uncolorize = winston.format.uncolorize();
|
||||||
|
|
||||||
const formatter = winston.format.printf(args => {
|
const formatter = winston.format.printf((args) => {
|
||||||
const { level, message } = args;
|
const { level, message } = args;
|
||||||
const { level: strippedLevel } = uncolorize.transform(args, {
|
const { level: strippedLevel } = uncolorize.transform(args, {
|
||||||
level: true,
|
level: true,
|
||||||
@ -64,7 +64,7 @@ winston.addColors(colors);
|
|||||||
const messageFormatter = (printFn: (message: string) => void) => {
|
const messageFormatter = (printFn: (message: string) => void) => {
|
||||||
return (...parts: any[]) => {
|
return (...parts: any[]) => {
|
||||||
parts
|
parts
|
||||||
.map(p => {
|
.map((p) => {
|
||||||
if (p instanceof Error) {
|
if (p instanceof Error) {
|
||||||
return p.stack;
|
return p.stack;
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ export function lockPath(appId: number, serviceName: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function lockFilesOnHost(appId: number, serviceName: string): string[] {
|
function lockFilesOnHost(appId: number, serviceName: string): string[] {
|
||||||
return ['updates.lock', 'resin-updates.lock'].map(filename =>
|
return ['updates.lock', 'resin-updates.lock'].map((filename) =>
|
||||||
path.join(constants.rootMountPoint, lockPath(appId, serviceName), filename),
|
path.join(constants.rootMountPoint, lockPath(appId, serviceName), filename),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ export const readLock: LockFn = Bluebird.promisify(locker.async.readLock, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function dispose(release: () => void): Bluebird<void> {
|
function dispose(release: () => void): Bluebird<void> {
|
||||||
return Bluebird.map(_.keys(locksTaken), lockName => {
|
return Bluebird.map(_.keys(locksTaken), (lockName) => {
|
||||||
delete locksTaken[lockName];
|
delete locksTaken[lockName];
|
||||||
return lockFile.unlockAsync(lockName);
|
return lockFile.unlockAsync(lockName);
|
||||||
})
|
})
|
||||||
@ -82,10 +82,10 @@ export function lock(
|
|||||||
|
|
||||||
return Bluebird.resolve(fs.readdir(lockDir))
|
return Bluebird.resolve(fs.readdir(lockDir))
|
||||||
.catchReturn(ENOENT, [])
|
.catchReturn(ENOENT, [])
|
||||||
.mapSeries(serviceName => {
|
.mapSeries((serviceName) => {
|
||||||
return Bluebird.mapSeries(
|
return Bluebird.mapSeries(
|
||||||
lockFilesOnHost(appId, serviceName),
|
lockFilesOnHost(appId, serviceName),
|
||||||
tmpLockName => {
|
(tmpLockName) => {
|
||||||
return Bluebird.try(() => {
|
return Bluebird.try(() => {
|
||||||
if (force) {
|
if (force) {
|
||||||
return lockFile.unlockAsync(tmpLockName);
|
return lockFile.unlockAsync(tmpLockName);
|
||||||
@ -97,7 +97,7 @@ export function lock(
|
|||||||
})
|
})
|
||||||
.catchReturn(ENOENT, undefined);
|
.catchReturn(ENOENT, undefined);
|
||||||
},
|
},
|
||||||
).catch(err => {
|
).catch((err) => {
|
||||||
return dispose(release).throw(
|
return dispose(release).throw(
|
||||||
new UpdatesLockedError(`Updates are locked: ${err.message}`),
|
new UpdatesLockedError(`Updates are locked: ${err.message}`),
|
||||||
);
|
);
|
||||||
|
@ -479,7 +479,7 @@ export function isValidDependentDevicesObject(devices: any): boolean {
|
|||||||
|
|
||||||
return _.every(
|
return _.every(
|
||||||
a as TargetState['dependent']['devices'][any]['apps'],
|
a as TargetState['dependent']['devices'][any]['apps'],
|
||||||
app => {
|
(app) => {
|
||||||
app = _.defaults(_.clone(app), {
|
app = _.defaults(_.clone(app), {
|
||||||
config: undefined,
|
config: undefined,
|
||||||
environment: undefined,
|
environment: undefined,
|
||||||
|
@ -83,7 +83,7 @@ export class LocalModeManager {
|
|||||||
|
|
||||||
public async init() {
|
public async init() {
|
||||||
// Setup a listener to catch state changes relating to local mode
|
// Setup a listener to catch state changes relating to local mode
|
||||||
this.config.on('change', changed => {
|
this.config.on('change', (changed) => {
|
||||||
if (changed.localMode != null) {
|
if (changed.localMode != null) {
|
||||||
const local = changed.localMode || false;
|
const local = changed.localMode || false;
|
||||||
|
|
||||||
@ -125,16 +125,16 @@ export class LocalModeManager {
|
|||||||
public async collectEngineSnapshot(): Promise<EngineSnapshotRecord> {
|
public async collectEngineSnapshot(): Promise<EngineSnapshotRecord> {
|
||||||
const containersPromise = this.docker
|
const containersPromise = this.docker
|
||||||
.listContainers()
|
.listContainers()
|
||||||
.then(resp => _.map(resp, 'Id'));
|
.then((resp) => _.map(resp, 'Id'));
|
||||||
const imagesPromise = this.docker
|
const imagesPromise = this.docker
|
||||||
.listImages()
|
.listImages()
|
||||||
.then(resp => _.map(resp, 'Id'));
|
.then((resp) => _.map(resp, 'Id'));
|
||||||
const volumesPromise = this.docker
|
const volumesPromise = this.docker
|
||||||
.listVolumes()
|
.listVolumes()
|
||||||
.then(resp => _.map(resp.Volumes, 'Name'));
|
.then((resp) => _.map(resp.Volumes, 'Name'));
|
||||||
const networksPromise = this.docker
|
const networksPromise = this.docker
|
||||||
.listNetworks()
|
.listNetworks()
|
||||||
.then(resp => _.map(resp, 'Id'));
|
.then((resp) => _.map(resp, 'Id'));
|
||||||
|
|
||||||
const [containers, images, volumes, networks] = await Bluebird.all([
|
const [containers, images, volumes, networks] = await Bluebird.all([
|
||||||
containersPromise,
|
containersPromise,
|
||||||
@ -155,8 +155,8 @@ export class LocalModeManager {
|
|||||||
return new EngineSnapshot(
|
return new EngineSnapshot(
|
||||||
[inspectInfo.Id],
|
[inspectInfo.Id],
|
||||||
[inspectInfo.Image],
|
[inspectInfo.Image],
|
||||||
inspectInfo.Mounts.filter(m => m.Name != null).map(m => m.Name!),
|
inspectInfo.Mounts.filter((m) => m.Name != null).map((m) => m.Name!),
|
||||||
_.map(inspectInfo.NetworkSettings.Networks, n => n.NetworkID),
|
_.map(inspectInfo.NetworkSettings.Networks, (n) => n.NetworkID),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,29 +237,29 @@ export class LocalModeManager {
|
|||||||
log.debug(`Going to delete the following objects: ${objects}`);
|
log.debug(`Going to delete the following objects: ${objects}`);
|
||||||
|
|
||||||
// Delete engine objects. We catch every deletion error, so that we can attempt other objects deletions.
|
// Delete engine objects. We catch every deletion error, so that we can attempt other objects deletions.
|
||||||
await Bluebird.map(objects.containers, cId => {
|
await Bluebird.map(objects.containers, (cId) => {
|
||||||
return this.docker
|
return this.docker
|
||||||
.getContainer(cId)
|
.getContainer(cId)
|
||||||
.remove({ force: true })
|
.remove({ force: true })
|
||||||
.catch(e => log.error(`Unable to delete container ${cId}`, e));
|
.catch((e) => log.error(`Unable to delete container ${cId}`, e));
|
||||||
});
|
});
|
||||||
await Bluebird.map(objects.images, iId => {
|
await Bluebird.map(objects.images, (iId) => {
|
||||||
return this.docker
|
return this.docker
|
||||||
.getImage(iId)
|
.getImage(iId)
|
||||||
.remove({ force: true })
|
.remove({ force: true })
|
||||||
.catch(e => log.error(`Unable to delete image ${iId}`, e));
|
.catch((e) => log.error(`Unable to delete image ${iId}`, e));
|
||||||
});
|
});
|
||||||
await Bluebird.map(objects.networks, nId => {
|
await Bluebird.map(objects.networks, (nId) => {
|
||||||
return this.docker
|
return this.docker
|
||||||
.getNetwork(nId)
|
.getNetwork(nId)
|
||||||
.remove()
|
.remove()
|
||||||
.catch(e => log.error(`Unable to delete network ${nId}`, e));
|
.catch((e) => log.error(`Unable to delete network ${nId}`, e));
|
||||||
});
|
});
|
||||||
await Bluebird.map(objects.volumes, vId => {
|
await Bluebird.map(objects.volumes, (vId) => {
|
||||||
return this.docker
|
return this.docker
|
||||||
.getVolume(vId)
|
.getVolume(vId)
|
||||||
.remove()
|
.remove()
|
||||||
.catch(e => log.error(`Unable to delete volume ${vId}`, e));
|
.catch((e) => log.error(`Unable to delete volume ${vId}`, e));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove any local mode state added to the database.
|
// Remove any local mode state added to the database.
|
||||||
@ -267,7 +267,7 @@ export class LocalModeManager {
|
|||||||
.models('app')
|
.models('app')
|
||||||
.del()
|
.del()
|
||||||
.where({ source: 'local' })
|
.where({ source: 'local' })
|
||||||
.catch(e =>
|
.catch((e) =>
|
||||||
log.error('Cannot delete local app entries in the database', e),
|
log.error('Cannot delete local app entries in the database', e),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -158,7 +158,7 @@ export class Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public lock(containerId: string): Bluebird.Disposer<() => void> {
|
public lock(containerId: string): Bluebird.Disposer<() => void> {
|
||||||
return writeLock(containerId).disposer(release => {
|
return writeLock(containerId).disposer((release) => {
|
||||||
release();
|
release();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -177,11 +177,11 @@ export class Logger {
|
|||||||
return Bluebird.using(this.lock(containerId), async () => {
|
return Bluebird.using(this.lock(containerId), async () => {
|
||||||
const logs = new ContainerLogs(containerId, docker);
|
const logs = new ContainerLogs(containerId, docker);
|
||||||
this.containerLogs[containerId] = logs;
|
this.containerLogs[containerId] = logs;
|
||||||
logs.on('error', err => {
|
logs.on('error', (err) => {
|
||||||
log.error('Container log retrieval error', err);
|
log.error('Container log retrieval error', err);
|
||||||
delete this.containerLogs[containerId];
|
delete this.containerLogs[containerId];
|
||||||
});
|
});
|
||||||
logs.on('log', async logMessage => {
|
logs.on('log', async (logMessage) => {
|
||||||
this.log(_.merge({}, serviceInfo, logMessage));
|
this.log(_.merge({}, serviceInfo, logMessage));
|
||||||
|
|
||||||
// Take the timestamp and set it in the database as the last
|
// Take the timestamp and set it in the database as the last
|
||||||
|
@ -124,7 +124,7 @@ export class BalenaLogBackend extends LogBackend {
|
|||||||
// Since we haven't sent the request body yet, and never will,the
|
// Since we haven't sent the request body yet, and never will,the
|
||||||
// only reason for the server to prematurely respond is to
|
// only reason for the server to prematurely respond is to
|
||||||
// communicate an error. So teardown the connection immediately
|
// communicate an error. So teardown the connection immediately
|
||||||
this.req.on('response', res => {
|
this.req.on('response', (res) => {
|
||||||
log.error(
|
log.error(
|
||||||
'LogBackend: server responded with status code:',
|
'LogBackend: server responded with status code:',
|
||||||
res.statusCode,
|
res.statusCode,
|
||||||
@ -134,7 +134,7 @@ export class BalenaLogBackend extends LogBackend {
|
|||||||
|
|
||||||
this.req.on('timeout', () => this.teardown());
|
this.req.on('timeout', () => this.teardown());
|
||||||
this.req.on('close', () => this.teardown());
|
this.req.on('close', () => this.teardown());
|
||||||
this.req.on('error', err => {
|
this.req.on('error', (err) => {
|
||||||
log.error('LogBackend: unexpected error:', err);
|
log.error('LogBackend: unexpected error:', err);
|
||||||
this.teardown();
|
this.teardown();
|
||||||
});
|
});
|
||||||
|
@ -43,7 +43,7 @@ export class ContainerLogs extends (EventEmitter as new () => LogsEventEmitter)
|
|||||||
[stderrStream, false],
|
[stderrStream, false],
|
||||||
].forEach(([stream, isStdout]: [Stream.Readable, boolean]) => {
|
].forEach(([stream, isStdout]: [Stream.Readable, boolean]) => {
|
||||||
stream
|
stream
|
||||||
.on('error', err => {
|
.on('error', (err) => {
|
||||||
this.emit(
|
this.emit(
|
||||||
'error',
|
'error',
|
||||||
new Error(`Error on container logs: ${err} ${err.stack}`),
|
new Error(`Error on container logs: ${err} ${err.stack}`),
|
||||||
@ -59,7 +59,7 @@ export class ContainerLogs extends (EventEmitter as new () => LogsEventEmitter)
|
|||||||
this.emit('log', { isStdout, ...logMsg });
|
this.emit('log', { isStdout, ...logMsg });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on('error', err => {
|
.on('error', (err) => {
|
||||||
this.emit(
|
this.emit(
|
||||||
'error',
|
'error',
|
||||||
new Error(`Error on container logs: ${err} ${err.stack}`),
|
new Error(`Error on container logs: ${err} ${err.stack}`),
|
||||||
@ -76,7 +76,7 @@ export class ContainerLogs extends (EventEmitter as new () => LogsEventEmitter)
|
|||||||
// https://docs.docker.com/engine/api/v1.30/#operation/ContainerAttach
|
// https://docs.docker.com/engine/api/v1.30/#operation/ContainerAttach
|
||||||
if (
|
if (
|
||||||
_.includes([0, 1, 2], msgBuf[0]) &&
|
_.includes([0, 1, 2], msgBuf[0]) &&
|
||||||
_.every(msgBuf.slice(1, 7), c => c === 0)
|
_.every(msgBuf.slice(1, 7), (c) => c === 0)
|
||||||
) {
|
) {
|
||||||
// Take the header from this message, and parse it as normal
|
// Take the header from this message, and parse it as normal
|
||||||
msgBuf = msgBuf.slice(8);
|
msgBuf = msgBuf.slice(8);
|
||||||
|
@ -44,12 +44,12 @@ export class LocalLogBackend extends LogBackend {
|
|||||||
})
|
})
|
||||||
.then((msg: LogMessage | null) => {
|
.then((msg: LogMessage | null) => {
|
||||||
if (msg != null) {
|
if (msg != null) {
|
||||||
_.each(this.globalListeners, listener => {
|
_.each(this.globalListeners, (listener) => {
|
||||||
listener.push(`${JSON.stringify(msg)}\n`);
|
listener.push(`${JSON.stringify(msg)}\n`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch((e) => {
|
||||||
log.error('Error streaming local log output:', e);
|
log.error('Error streaming local log output:', e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,25 +9,29 @@
|
|||||||
|
|
||||||
exports.up = function (knex) {
|
exports.up = function (knex) {
|
||||||
const addColumn = function (table, column, type) {
|
const addColumn = function (table, column, type) {
|
||||||
return knex.schema.hasColumn(table, column).then(exists => {
|
return knex.schema.hasColumn(table, column).then((exists) => {
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return knex.schema.table(table, t => {
|
return knex.schema.table(table, (t) => {
|
||||||
return t[type](column);
|
return t[type](column);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const dropColumn = function (table, column) {
|
const dropColumn = function (table, column) {
|
||||||
return knex.schema.hasColumn(table, column).then(exists => {
|
return knex.schema.hasColumn(table, column).then((exists) => {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
return knex.schema.table(table, t => {
|
return knex.schema.table(table, (t) => {
|
||||||
return t.dropColumn(column);
|
return t.dropColumn(column);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const createTableOrRun = function(tableName, tableCreator, runIfTableExists) {
|
const createTableOrRun = function (
|
||||||
return knex.schema.hasTable(tableName).then(exists => {
|
tableName,
|
||||||
|
tableCreator,
|
||||||
|
runIfTableExists,
|
||||||
|
) {
|
||||||
|
return knex.schema.hasTable(tableName).then((exists) => {
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return knex.schema.createTable(tableName, tableCreator);
|
return knex.schema.createTable(tableName, tableCreator);
|
||||||
} else if (runIfTableExists != null) {
|
} else if (runIfTableExists != null) {
|
||||||
@ -36,24 +40,24 @@ exports.up = function(knex) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
const dropTable = function (tableName) {
|
const dropTable = function (tableName) {
|
||||||
return knex.schema.hasTable(tableName).then(exists => {
|
return knex.schema.hasTable(tableName).then((exists) => {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
return knex.schema.dropTable(tableName);
|
return knex.schema.dropTable(tableName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
createTableOrRun('config', t => {
|
createTableOrRun('config', (t) => {
|
||||||
t.string('key').primary();
|
t.string('key').primary();
|
||||||
t.string('value');
|
t.string('value');
|
||||||
}),
|
}),
|
||||||
createTableOrRun('deviceConfig', t => {
|
createTableOrRun('deviceConfig', (t) => {
|
||||||
t.json('values');
|
t.json('values');
|
||||||
t.json('targetValues');
|
t.json('targetValues');
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return knex('deviceConfig')
|
return knex('deviceConfig')
|
||||||
.select()
|
.select()
|
||||||
.then(deviceConfigs => {
|
.then((deviceConfigs) => {
|
||||||
if (deviceConfigs.length === 0) {
|
if (deviceConfigs.length === 0) {
|
||||||
return knex('deviceConfig').insert({
|
return knex('deviceConfig').insert({
|
||||||
values: '{}',
|
values: '{}',
|
||||||
@ -64,7 +68,7 @@ exports.up = function(knex) {
|
|||||||
}),
|
}),
|
||||||
createTableOrRun(
|
createTableOrRun(
|
||||||
'app',
|
'app',
|
||||||
t => {
|
(t) => {
|
||||||
t.increments('id').primary();
|
t.increments('id').primary();
|
||||||
t.string('name');
|
t.string('name');
|
||||||
t.string('containerName');
|
t.string('containerName');
|
||||||
@ -99,7 +103,7 @@ exports.up = function(knex) {
|
|||||||
),
|
),
|
||||||
createTableOrRun(
|
createTableOrRun(
|
||||||
'dependentApp',
|
'dependentApp',
|
||||||
t => {
|
(t) => {
|
||||||
t.increments('id').primary();
|
t.increments('id').primary();
|
||||||
t.string('appId');
|
t.string('appId');
|
||||||
t.string('parentAppId');
|
t.string('parentAppId');
|
||||||
@ -115,7 +119,7 @@ exports.up = function(knex) {
|
|||||||
),
|
),
|
||||||
createTableOrRun(
|
createTableOrRun(
|
||||||
'dependentDevice',
|
'dependentDevice',
|
||||||
t => {
|
(t) => {
|
||||||
t.increments('id').primary();
|
t.increments('id').primary();
|
||||||
t.string('uuid');
|
t.string('uuid');
|
||||||
t.string('appId');
|
t.string('appId');
|
||||||
|
@ -103,7 +103,7 @@ var imageForDependentApp = function(app) {
|
|||||||
|
|
||||||
exports.up = function (knex) {
|
exports.up = function (knex) {
|
||||||
return Bluebird.resolve(
|
return Bluebird.resolve(
|
||||||
knex.schema.createTable('image', t => {
|
knex.schema.createTable('image', (t) => {
|
||||||
t.increments('id').primary();
|
t.increments('id').primary();
|
||||||
t.string('name');
|
t.string('name');
|
||||||
t.integer('appId');
|
t.integer('appId');
|
||||||
@ -120,7 +120,7 @@ exports.up = function(knex) {
|
|||||||
.whereNot({ markedForDeletion: true })
|
.whereNot({ markedForDeletion: true })
|
||||||
.orWhereNull('markedForDeletion'),
|
.orWhereNull('markedForDeletion'),
|
||||||
)
|
)
|
||||||
.tap(apps => {
|
.tap((apps) => {
|
||||||
if (apps.length > 0) {
|
if (apps.length > 0) {
|
||||||
return knex('config').insert({
|
return knex('config').insert({
|
||||||
key: 'legacyAppsPresent',
|
key: 'legacyAppsPresent',
|
||||||
@ -132,7 +132,7 @@ exports.up = function(knex) {
|
|||||||
// We're in a transaction, and it's easier to drop and recreate
|
// We're in a transaction, and it's easier to drop and recreate
|
||||||
// than to migrate each field...
|
// than to migrate each field...
|
||||||
return knex.schema.dropTable('app').then(() => {
|
return knex.schema.dropTable('app').then(() => {
|
||||||
return knex.schema.createTable('app', t => {
|
return knex.schema.createTable('app', (t) => {
|
||||||
t.increments('id').primary();
|
t.increments('id').primary();
|
||||||
t.string('name');
|
t.string('name');
|
||||||
t.integer('releaseId');
|
t.integer('releaseId');
|
||||||
@ -144,7 +144,7 @@ exports.up = function(knex) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.map(app => {
|
.map((app) => {
|
||||||
const migratedApp = singleToMulticontainerApp(app);
|
const migratedApp = singleToMulticontainerApp(app);
|
||||||
return knex('app')
|
return knex('app')
|
||||||
.insert(jsonifyAppFields(migratedApp))
|
.insert(jsonifyAppFields(migratedApp))
|
||||||
@ -157,7 +157,7 @@ exports.up = function(knex) {
|
|||||||
// to the config table.
|
// to the config table.
|
||||||
return knex('deviceConfig')
|
return knex('deviceConfig')
|
||||||
.select()
|
.select()
|
||||||
.then(deviceConf => {
|
.then((deviceConf) => {
|
||||||
return knex.schema.dropTable('deviceConfig').then(() => {
|
return knex.schema.dropTable('deviceConfig').then(() => {
|
||||||
const values = JSON.parse(deviceConf[0].values);
|
const values = JSON.parse(deviceConf[0].values);
|
||||||
const configKeys = {
|
const configKeys = {
|
||||||
@ -172,7 +172,7 @@ exports.up = function(knex) {
|
|||||||
RESIN_SUPERVISOR_DELTA_RETRY_INTERVAL: 'deltaRequestTimeout',
|
RESIN_SUPERVISOR_DELTA_RETRY_INTERVAL: 'deltaRequestTimeout',
|
||||||
RESIN_SUPERVISOR_OVERRIDE_LOCK: 'lockOverride',
|
RESIN_SUPERVISOR_OVERRIDE_LOCK: 'lockOverride',
|
||||||
};
|
};
|
||||||
return Bluebird.map(Object.keys(values), envVarName => {
|
return Bluebird.map(Object.keys(values), (envVarName) => {
|
||||||
if (configKeys[envVarName] != null) {
|
if (configKeys[envVarName] != null) {
|
||||||
return knex('config').insert({
|
return knex('config').insert({
|
||||||
key: configKeys[envVarName],
|
key: configKeys[envVarName],
|
||||||
@ -183,18 +183,18 @@ exports.up = function(knex) {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return knex.schema.createTable('deviceConfig', t => {
|
return knex.schema.createTable('deviceConfig', (t) => {
|
||||||
t.json('targetValues');
|
t.json('targetValues');
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => knex('deviceConfig').insert({ targetValues: '{}' }));
|
.then(() => knex('deviceConfig').insert({ targetValues: '{}' }));
|
||||||
})
|
})
|
||||||
.then(() => knex('dependentApp').select())
|
.then(() => knex('dependentApp').select())
|
||||||
.then(dependentApps => {
|
.then((dependentApps) => {
|
||||||
return knex.schema
|
return knex.schema
|
||||||
.dropTable('dependentApp')
|
.dropTable('dependentApp')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return knex.schema.createTable('dependentApp', t => {
|
return knex.schema.createTable('dependentApp', (t) => {
|
||||||
t.increments('id').primary();
|
t.increments('id').primary();
|
||||||
t.integer('appId');
|
t.integer('appId');
|
||||||
t.integer('parentApp');
|
t.integer('parentApp');
|
||||||
@ -208,7 +208,7 @@ exports.up = function(knex) {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return knex.schema.createTable('dependentAppTarget', t => {
|
return knex.schema.createTable('dependentAppTarget', (t) => {
|
||||||
t.increments('id').primary();
|
t.increments('id').primary();
|
||||||
t.integer('appId');
|
t.integer('appId');
|
||||||
t.integer('parentApp');
|
t.integer('parentApp');
|
||||||
@ -222,7 +222,7 @@ exports.up = function(knex) {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return Bluebird.map(dependentApps, app => {
|
return Bluebird.map(dependentApps, (app) => {
|
||||||
const newApp = {
|
const newApp = {
|
||||||
appId: parseInt(app.appId, 10),
|
appId: parseInt(app.appId, 10),
|
||||||
parentApp: parseInt(app.parentAppId, 10),
|
parentApp: parseInt(app.parentAppId, 10),
|
||||||
@ -242,11 +242,11 @@ exports.up = function(knex) {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => knex('dependentDevice').select())
|
.then(() => knex('dependentDevice').select())
|
||||||
.then(dependentDevices => {
|
.then((dependentDevices) => {
|
||||||
return knex.schema
|
return knex.schema
|
||||||
.dropTable('dependentDevice')
|
.dropTable('dependentDevice')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return knex.schema.createTable('dependentDevice', t => {
|
return knex.schema.createTable('dependentDevice', (t) => {
|
||||||
t.increments('id').primary();
|
t.increments('id').primary();
|
||||||
t.string('uuid');
|
t.string('uuid');
|
||||||
t.integer('appId');
|
t.integer('appId');
|
||||||
@ -270,7 +270,7 @@ exports.up = function(knex) {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return knex.schema.createTable('dependentDeviceTarget', t => {
|
return knex.schema.createTable('dependentDeviceTarget', (t) => {
|
||||||
t.increments('id').primary();
|
t.increments('id').primary();
|
||||||
t.string('uuid');
|
t.string('uuid');
|
||||||
t.string('name');
|
t.string('name');
|
||||||
@ -278,7 +278,7 @@ exports.up = function(knex) {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return Bluebird.map(dependentDevices, device => {
|
return Bluebird.map(dependentDevices, (device) => {
|
||||||
const newDevice = _.clone(device);
|
const newDevice = _.clone(device);
|
||||||
newDevice.appId = parseInt(device.appId, 10);
|
newDevice.appId = parseInt(device.appId, 10);
|
||||||
newDevice.deviceId = parseInt(device.deviceId, 10);
|
newDevice.deviceId = parseInt(device.deviceId, 10);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Adds a dockerImageId column to the image table to identify images downloaded with deltas
|
// Adds a dockerImageId column to the image table to identify images downloaded with deltas
|
||||||
exports.up = function (knex) {
|
exports.up = function (knex) {
|
||||||
return knex.schema.table('image', t => {
|
return knex.schema.table('image', (t) => {
|
||||||
t.string('dockerImageId');
|
t.string('dockerImageId');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ const fs = require('fs');
|
|||||||
const configJsonPath = process.env.CONFIG_MOUNT_POINT;
|
const configJsonPath = process.env.CONFIG_MOUNT_POINT;
|
||||||
|
|
||||||
exports.up = function (knex) {
|
exports.up = function (knex) {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
if (!configJsonPath) {
|
if (!configJsonPath) {
|
||||||
console.log(
|
console.log(
|
||||||
'Unable to locate config.json! Things may fail unexpectedly!',
|
'Unable to locate config.json! Things may fail unexpectedly!',
|
||||||
@ -27,9 +27,9 @@ exports.up = function(knex) {
|
|||||||
resolve({});
|
resolve({});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).then(config => {
|
}).then((config) => {
|
||||||
return knex.schema
|
return knex.schema
|
||||||
.table('app', t => {
|
.table('app', (t) => {
|
||||||
// Create a new column on the table and add the apiEndpoint config json
|
// Create a new column on the table and add the apiEndpoint config json
|
||||||
// field if it exists
|
// field if it exists
|
||||||
t.string('source');
|
t.string('source');
|
||||||
|
@ -3,7 +3,7 @@ const fs = require('fs');
|
|||||||
const configJsonPath = process.env.CONFIG_MOUNT_POINT;
|
const configJsonPath = process.env.CONFIG_MOUNT_POINT;
|
||||||
|
|
||||||
exports.up = function (knex) {
|
exports.up = function (knex) {
|
||||||
return new Bluebird(resolve => {
|
return new Bluebird((resolve) => {
|
||||||
if (!configJsonPath) {
|
if (!configJsonPath) {
|
||||||
console.log(
|
console.log(
|
||||||
'Unable to locate config.json! Things may fail unexpectedly!',
|
'Unable to locate config.json! Things may fail unexpectedly!',
|
||||||
@ -33,20 +33,20 @@ exports.up = function(knex) {
|
|||||||
.tap(() => {
|
.tap(() => {
|
||||||
// take the logsChannelSecret, and the apiEndpoint config field,
|
// take the logsChannelSecret, and the apiEndpoint config field,
|
||||||
// and store them in a new table
|
// and store them in a new table
|
||||||
return knex.schema.hasTable('logsChannelSecret').then(exists => {
|
return knex.schema.hasTable('logsChannelSecret').then((exists) => {
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
return knex.schema.createTable('logsChannelSecret', t => {
|
return knex.schema.createTable('logsChannelSecret', (t) => {
|
||||||
t.string('backend');
|
t.string('backend');
|
||||||
t.string('secret');
|
t.string('secret');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(config => {
|
.then((config) => {
|
||||||
return knex('config')
|
return knex('config')
|
||||||
.where({ key: 'logsChannelSecret' })
|
.where({ key: 'logsChannelSecret' })
|
||||||
.select('value')
|
.select('value')
|
||||||
.then(results => {
|
.then((results) => {
|
||||||
if (results.length === 0) {
|
if (results.length === 0) {
|
||||||
return { config, secret: null };
|
return { config, secret: null };
|
||||||
}
|
}
|
||||||
@ -60,9 +60,7 @@ exports.up = function(knex) {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return knex('config')
|
return knex('config').where('key', 'logsChannelSecret').del();
|
||||||
.where('key', 'logsChannelSecret')
|
|
||||||
.del();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
exports.up = knex => {
|
exports.up = (knex) => {
|
||||||
return knex.schema.createTable('engineSnapshot', t => {
|
return knex.schema.createTable('engineSnapshot', (t) => {
|
||||||
t.string('snapshot'); // Engine snapshot encoded as JSON.
|
t.string('snapshot'); // Engine snapshot encoded as JSON.
|
||||||
t.string('timestamp'); // When the snapshot was created.
|
t.string('timestamp'); // When the snapshot was created.
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,7 @@ const _ = require('lodash');
|
|||||||
exports.up = function (knex) {
|
exports.up = function (knex) {
|
||||||
return knex('deviceConfig')
|
return knex('deviceConfig')
|
||||||
.select('targetValues')
|
.select('targetValues')
|
||||||
.then(devConfigs => {
|
.then((devConfigs) => {
|
||||||
const devConfig = devConfigs[0];
|
const devConfig = devConfigs[0];
|
||||||
const targetValues = JSON.parse(devConfig.targetValues);
|
const targetValues = JSON.parse(devConfig.targetValues);
|
||||||
const filteredTargetValues = _.mapKeys(targetValues, (_v, k) => {
|
const filteredTargetValues = _.mapKeys(targetValues, (_v, k) => {
|
||||||
|
@ -7,7 +7,7 @@ exports.up = function(knex) {
|
|||||||
return knex('config')
|
return knex('config')
|
||||||
.where({ key: 'localMode' })
|
.where({ key: 'localMode' })
|
||||||
.select('value')
|
.select('value')
|
||||||
.then(results => {
|
.then((results) => {
|
||||||
if (results.length === 0) {
|
if (results.length === 0) {
|
||||||
// We don't need to do anything
|
// We don't need to do anything
|
||||||
return;
|
return;
|
||||||
@ -16,7 +16,7 @@ exports.up = function(knex) {
|
|||||||
let value = checkTruthy(results[0].value);
|
let value = checkTruthy(results[0].value);
|
||||||
value = value != null ? value : false;
|
value = value != null ? value : false;
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
if (!configJsonPath) {
|
if (!configJsonPath) {
|
||||||
console.log(
|
console.log(
|
||||||
'Unable to locate config.json! Things may fail unexpectedly!',
|
'Unable to locate config.json! Things may fail unexpectedly!',
|
||||||
@ -37,7 +37,7 @@ exports.up = function(knex) {
|
|||||||
// Assign the local mode value
|
// Assign the local mode value
|
||||||
parsed.localMode = value;
|
parsed.localMode = value;
|
||||||
|
|
||||||
fs.writeFile(configJsonPath, JSON.stringify(parsed), err2 => {
|
fs.writeFile(configJsonPath, JSON.stringify(parsed), (err2) => {
|
||||||
if (err2) {
|
if (err2) {
|
||||||
console.log(
|
console.log(
|
||||||
'Failed to write config.json! Things may fail unexpectedly!',
|
'Failed to write config.json! Things may fail unexpectedly!',
|
||||||
@ -54,9 +54,7 @@ exports.up = function(knex) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return knex('config')
|
return knex('config').where('key', 'localMode').del();
|
||||||
.where('key', 'localMode')
|
|
||||||
.del();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ const configJsonPath = process.env.CONFIG_MOUNT_POINT;
|
|||||||
const { checkTruthy } = require('../lib/validation');
|
const { checkTruthy } = require('../lib/validation');
|
||||||
|
|
||||||
exports.up = function (knex) {
|
exports.up = function (knex) {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
if (!configJsonPath) {
|
if (!configJsonPath) {
|
||||||
console.log(
|
console.log(
|
||||||
'Unable to locate config.json! Things may fail unexpectedly!',
|
'Unable to locate config.json! Things may fail unexpectedly!',
|
||||||
@ -32,7 +32,7 @@ exports.up = function(knex) {
|
|||||||
return resolve(false);
|
return resolve(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).then(localMode => {
|
}).then((localMode) => {
|
||||||
// We can be sure that this does not already exist in the db because of the previous
|
// We can be sure that this does not already exist in the db because of the previous
|
||||||
// migration
|
// migration
|
||||||
return knex('config').insert({
|
return knex('config').insert({
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
exports.up = function (knex) {
|
exports.up = function (knex) {
|
||||||
return knex.schema.createTable('containerLogs', table => {
|
return knex.schema.createTable('containerLogs', (table) => {
|
||||||
table.string('containerId');
|
table.string('containerId');
|
||||||
table.integer('lastSentTimestamp');
|
table.integer('lastSentTimestamp');
|
||||||
});
|
});
|
||||||
|
@ -89,7 +89,7 @@ export const startConnectivityCheck = _.once(
|
|||||||
path: parsedUrl.path || '/',
|
path: parsedUrl.path || '/',
|
||||||
interval: 10 * 1000,
|
interval: 10 * 1000,
|
||||||
},
|
},
|
||||||
connected => {
|
(connected) => {
|
||||||
onChangeCallback?.(connected);
|
onChangeCallback?.(connected);
|
||||||
if (connected) {
|
if (connected) {
|
||||||
log.info('Internet Connectivity: OK');
|
log.info('Internet Connectivity: OK');
|
||||||
@ -127,7 +127,7 @@ export function getIPAddresses(): string[] {
|
|||||||
// - custom docker network bridges (br- + 12 hex characters)
|
// - custom docker network bridges (br- + 12 hex characters)
|
||||||
return _(os.networkInterfaces())
|
return _(os.networkInterfaces())
|
||||||
.omitBy((_interfaceFields, interfaceName) => IP_REGEX.test(interfaceName))
|
.omitBy((_interfaceFields, interfaceName) => IP_REGEX.test(interfaceName))
|
||||||
.flatMap(validInterfaces => {
|
.flatMap((validInterfaces) => {
|
||||||
return _(validInterfaces)
|
return _(validInterfaces)
|
||||||
.pickBy({ family: 'IPv4' })
|
.pickBy({ family: 'IPv4' })
|
||||||
.map('address')
|
.map('address')
|
||||||
@ -144,11 +144,7 @@ export function startIPAddressUpdate(): (
|
|||||||
return (cb, interval) => {
|
return (cb, interval) => {
|
||||||
const getAndReportIP = () => {
|
const getAndReportIP = () => {
|
||||||
const ips = getIPAddresses();
|
const ips = getIPAddresses();
|
||||||
if (
|
if (!_(ips).xor(lastIPValues).isEmpty()) {
|
||||||
!_(ips)
|
|
||||||
.xor(lastIPValues)
|
|
||||||
.isEmpty()
|
|
||||||
) {
|
|
||||||
lastIPValues = ips;
|
lastIPValues = ips;
|
||||||
cb(ips);
|
cb(ips);
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ const parseDeviceFields = function(device) {
|
|||||||
return _.omit(device, 'markedForDeletion', 'logs_channel');
|
return _.omit(device, 'markedForDeletion', 'logs_channel');
|
||||||
};
|
};
|
||||||
|
|
||||||
const tarDirectory = appId => `/data/dependent-assets/${appId}`;
|
const tarDirectory = (appId) => `/data/dependent-assets/${appId}`;
|
||||||
|
|
||||||
const tarFilename = (appId, commit) => `${appId}-${commit}.tar`;
|
const tarFilename = (appId, commit) => `${appId}-${commit}.tar`;
|
||||||
|
|
||||||
@ -61,18 +61,18 @@ const cleanupTars = function(appId, commit) {
|
|||||||
if (fileToKeep != null) {
|
if (fileToKeep != null) {
|
||||||
files = _.reject(files, fileToKeep);
|
files = _.reject(files, fileToKeep);
|
||||||
}
|
}
|
||||||
return Promise.map(files, file => fs.unlink(path.join(dir, file)));
|
return Promise.map(files, (file) => fs.unlink(path.join(dir, file)));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTargetAsState = device => ({
|
const formatTargetAsState = (device) => ({
|
||||||
appId: parseInt(device.appId, 10),
|
appId: parseInt(device.appId, 10),
|
||||||
commit: device.targetCommit,
|
commit: device.targetCommit,
|
||||||
environment: device.targetEnvironment,
|
environment: device.targetEnvironment,
|
||||||
config: device.targetConfig,
|
config: device.targetConfig,
|
||||||
});
|
});
|
||||||
|
|
||||||
const formatCurrentAsState = device => ({
|
const formatCurrentAsState = (device) => ({
|
||||||
appId: parseInt(device.appId, 10),
|
appId: parseInt(device.appId, 10),
|
||||||
commit: device.commit,
|
commit: device.commit,
|
||||||
environment: device.environment,
|
environment: device.environment,
|
||||||
@ -89,8 +89,8 @@ const createProxyvisorRouter = function(proxyvisor) {
|
|||||||
.models('dependentDevice')
|
.models('dependentDevice')
|
||||||
.select()
|
.select()
|
||||||
.map(parseDeviceFields)
|
.map(parseDeviceFields)
|
||||||
.then(devices => res.json(devices))
|
.then((devices) => res.json(devices))
|
||||||
.catch(err =>
|
.catch((err) =>
|
||||||
res.status(503).send(err?.message || err || 'Unknown error'),
|
res.status(503).send(err?.message || err || 'Unknown error'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -286,12 +286,7 @@ const createProxyvisorRouter = function(proxyvisor) {
|
|||||||
.update(fieldsToUpdateOnDB)
|
.update(fieldsToUpdateOnDB)
|
||||||
.where({ uuid }),
|
.where({ uuid }),
|
||||||
)
|
)
|
||||||
.then(() =>
|
.then(() => db.models('dependentDevice').select().where({ uuid }))
|
||||||
db
|
|
||||||
.models('dependentDevice')
|
|
||||||
.select()
|
|
||||||
.where({ uuid }),
|
|
||||||
)
|
|
||||||
.then(function ([dbDevice]) {
|
.then(function ([dbDevice]) {
|
||||||
return res.json(parseDeviceFields(dbDevice));
|
return res.json(parseDeviceFields(dbDevice));
|
||||||
});
|
});
|
||||||
@ -317,7 +312,7 @@ const createProxyvisorRouter = function(proxyvisor) {
|
|||||||
.catch(() =>
|
.catch(() =>
|
||||||
Promise.using(
|
Promise.using(
|
||||||
proxyvisor.docker.imageRootDirMounted(app.image),
|
proxyvisor.docker.imageRootDirMounted(app.image),
|
||||||
rootDir => getTarArchive(rootDir + '/assets', dest),
|
(rootDir) => getTarArchive(rootDir + '/assets', dest),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.then(() => res.sendFile(dest));
|
.then(() => res.sendFile(dest));
|
||||||
@ -332,13 +327,13 @@ const createProxyvisorRouter = function(proxyvisor) {
|
|||||||
db
|
db
|
||||||
.models('dependentApp')
|
.models('dependentApp')
|
||||||
.select()
|
.select()
|
||||||
.map(app => ({
|
.map((app) => ({
|
||||||
id: parseInt(app.appId, 10),
|
id: parseInt(app.appId, 10),
|
||||||
commit: app.commit,
|
commit: app.commit,
|
||||||
name: app.name,
|
name: app.name,
|
||||||
config: JSON.parse(app.config ?? '{}'),
|
config: JSON.parse(app.config ?? '{}'),
|
||||||
}))
|
}))
|
||||||
.then(apps => res.json(apps))
|
.then((apps) => res.json(apps))
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
log.error(`Error on ${req.method} ${url.parse(req.url).pathname}`, err);
|
log.error(`Error on ${req.method} ${url.parse(req.url).pathname}`, err);
|
||||||
return res.status(503).send(err?.message || err || 'Unknown error');
|
return res.status(503).send(err?.message || err || 'Unknown error');
|
||||||
@ -375,7 +370,7 @@ export class Proxyvisor {
|
|||||||
this.lastRequestForDevice = {};
|
this.lastRequestForDevice = {};
|
||||||
this.router = createProxyvisorRouter(this);
|
this.router = createProxyvisorRouter(this);
|
||||||
this.actionExecutors = {
|
this.actionExecutors = {
|
||||||
updateDependentTargets: step => {
|
updateDependentTargets: (step) => {
|
||||||
return this.config
|
return this.config
|
||||||
.getMany(['currentApiKey', 'apiTimeout'])
|
.getMany(['currentApiKey', 'apiTimeout'])
|
||||||
.then(({ currentApiKey, apiTimeout }) => {
|
.then(({ currentApiKey, apiTimeout }) => {
|
||||||
@ -383,12 +378,10 @@ export class Proxyvisor {
|
|||||||
// - if update returns 0, then use APIBinder to fetch the device, then store it to the db
|
// - if update returns 0, then use APIBinder to fetch the device, then store it to the db
|
||||||
// - set markedForDeletion: true for devices that are not in the step.devices list
|
// - set markedForDeletion: true for devices that are not in the step.devices list
|
||||||
// - update dependentApp with step.app
|
// - update dependentApp with step.app
|
||||||
return Promise.map(step.devices, device => {
|
return Promise.map(step.devices, (device) => {
|
||||||
const { uuid } = device;
|
const { uuid } = device;
|
||||||
// Only consider one app per dependent device for now
|
// Only consider one app per dependent device for now
|
||||||
const appId = _(device.apps)
|
const appId = _(device.apps).keys().head();
|
||||||
.keys()
|
|
||||||
.head();
|
|
||||||
if (appId == null) {
|
if (appId == null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Could not find an app for the dependent device',
|
'Could not find an app for the dependent device',
|
||||||
@ -409,7 +402,7 @@ export class Proxyvisor {
|
|||||||
name: device.name,
|
name: device.name,
|
||||||
})
|
})
|
||||||
.where({ uuid })
|
.where({ uuid })
|
||||||
.then(n => {
|
.then((n) => {
|
||||||
if (n !== 0) {
|
if (n !== 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -417,7 +410,7 @@ export class Proxyvisor {
|
|||||||
// so we need to fetch it.
|
// so we need to fetch it.
|
||||||
return this.apiBinder
|
return this.apiBinder
|
||||||
.fetchDevice(uuid, currentApiKey, apiTimeout)
|
.fetchDevice(uuid, currentApiKey, apiTimeout)
|
||||||
.then(dev => {
|
.then((dev) => {
|
||||||
const deviceForDB = {
|
const deviceForDB = {
|
||||||
uuid,
|
uuid,
|
||||||
appId,
|
appId,
|
||||||
@ -446,7 +439,7 @@ export class Proxyvisor {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return this.normaliseDependentAppForDB(step.app);
|
return this.normaliseDependentAppForDB(step.app);
|
||||||
})
|
})
|
||||||
.then(appForDB => {
|
.then((appForDB) => {
|
||||||
return this.db.upsertModel('dependentApp', appForDB, {
|
return this.db.upsertModel('dependentApp', appForDB, {
|
||||||
appId: step.appId,
|
appId: step.appId,
|
||||||
});
|
});
|
||||||
@ -455,12 +448,12 @@ export class Proxyvisor {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
sendDependentHooks: step => {
|
sendDependentHooks: (step) => {
|
||||||
return Promise.join(
|
return Promise.join(
|
||||||
this.config.get('apiTimeout'),
|
this.config.get('apiTimeout'),
|
||||||
this.getHookEndpoint(step.appId),
|
this.getHookEndpoint(step.appId),
|
||||||
(apiTimeout, endpoint) => {
|
(apiTimeout, endpoint) => {
|
||||||
return Promise.mapSeries(step.devices, device => {
|
return Promise.mapSeries(step.devices, (device) => {
|
||||||
return Promise.try(() => {
|
return Promise.try(() => {
|
||||||
if (this.lastRequestForDevice[device.uuid] != null) {
|
if (this.lastRequestForDevice[device.uuid] != null) {
|
||||||
const diff =
|
const diff =
|
||||||
@ -482,17 +475,15 @@ export class Proxyvisor {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
removeDependentApp: step => {
|
removeDependentApp: (step) => {
|
||||||
// find step.app and delete it from the DB
|
// find step.app and delete it from the DB
|
||||||
// find devices with step.appId and delete them from the DB
|
// find devices with step.appId and delete them from the DB
|
||||||
return this.db.transaction(trx =>
|
return this.db.transaction((trx) =>
|
||||||
trx('dependentApp')
|
trx('dependentApp')
|
||||||
.where({ appId: step.appId })
|
.where({ appId: step.appId })
|
||||||
.del()
|
.del()
|
||||||
.then(() =>
|
.then(() =>
|
||||||
trx('dependentDevice')
|
trx('dependentDevice').where({ appId: step.appId }).del(),
|
||||||
.where({ appId: step.appId })
|
|
||||||
.del(),
|
|
||||||
)
|
)
|
||||||
.then(() => cleanupTars(step.appId)),
|
.then(() => cleanupTars(step.appId)),
|
||||||
);
|
);
|
||||||
@ -597,8 +588,8 @@ export class Proxyvisor {
|
|||||||
return appClone;
|
return appClone;
|
||||||
});
|
});
|
||||||
return Promise.map(appsArray, this.normaliseDependentAppForDB)
|
return Promise.map(appsArray, this.normaliseDependentAppForDB)
|
||||||
.tap(appsForDB => {
|
.tap((appsForDB) => {
|
||||||
return Promise.map(appsForDB, app => {
|
return Promise.map(appsForDB, (app) => {
|
||||||
return this.db.upsertModel(
|
return this.db.upsertModel(
|
||||||
'dependentAppTarget',
|
'dependentAppTarget',
|
||||||
app,
|
app,
|
||||||
@ -607,7 +598,7 @@ export class Proxyvisor {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(appsForDB =>
|
.then((appsForDB) =>
|
||||||
trx('dependentAppTarget')
|
trx('dependentAppTarget')
|
||||||
.whereNotIn('appId', _.map(appsForDB, 'appId'))
|
.whereNotIn('appId', _.map(appsForDB, 'appId'))
|
||||||
.del(),
|
.del(),
|
||||||
@ -620,14 +611,14 @@ export class Proxyvisor {
|
|||||||
devClone.uuid = uuid;
|
devClone.uuid = uuid;
|
||||||
return devClone;
|
return devClone;
|
||||||
});
|
});
|
||||||
return Promise.map(devicesArray, device => {
|
return Promise.map(devicesArray, (device) => {
|
||||||
const appId = _.keys(device.apps)[0];
|
const appId = _.keys(device.apps)[0];
|
||||||
return this.normaliseDependentDeviceTargetForDB(
|
return this.normaliseDependentDeviceTargetForDB(
|
||||||
device,
|
device,
|
||||||
dependent.apps[appId]?.commit,
|
dependent.apps[appId]?.commit,
|
||||||
);
|
);
|
||||||
}).then(devicesForDB => {
|
}).then((devicesForDB) => {
|
||||||
return Promise.map(devicesForDB, device => {
|
return Promise.map(devicesForDB, (device) => {
|
||||||
return this.db.upsertModel(
|
return this.db.upsertModel(
|
||||||
'dependentDeviceTarget',
|
'dependentDeviceTarget',
|
||||||
device,
|
device,
|
||||||
@ -783,7 +774,7 @@ export class Proxyvisor {
|
|||||||
});
|
});
|
||||||
currentDeviceTargets = _.filter(
|
currentDeviceTargets = _.filter(
|
||||||
currentDeviceTargets,
|
currentDeviceTargets,
|
||||||
dev => !_.isNull(dev),
|
(dev) => !_.isNull(dev),
|
||||||
);
|
);
|
||||||
return !_.isEmpty(
|
return !_.isEmpty(
|
||||||
_.xorWith(currentDeviceTargets, targetDevices, _.isEqual),
|
_.xorWith(currentDeviceTargets, targetDevices, _.isEqual),
|
||||||
@ -819,7 +810,7 @@ export class Proxyvisor {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.some(stepsInProgress, step => step.appId === target.parentApp)) {
|
if (_.some(stepsInProgress, (step) => step.appId === target.parentApp)) {
|
||||||
return [{ action: 'noop' }];
|
return [{ action: 'noop' }];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -886,8 +877,8 @@ export class Proxyvisor {
|
|||||||
|
|
||||||
let steps = [];
|
let steps = [];
|
||||||
for (const appId of allAppIds) {
|
for (const appId of allAppIds) {
|
||||||
const devicesForApp = devices =>
|
const devicesForApp = (devices) =>
|
||||||
_.filter(devices, d => _.has(d.apps, appId));
|
_.filter(devices, (d) => _.has(d.apps, appId));
|
||||||
|
|
||||||
const currentDevices = devicesForApp(current.dependent.devices);
|
const currentDevices = devicesForApp(current.dependent.devices);
|
||||||
const targetDevices = devicesForApp(target.dependent.devices);
|
const targetDevices = devicesForApp(target.dependent.devices);
|
||||||
@ -915,13 +906,13 @@ export class Proxyvisor {
|
|||||||
.then(([{ parentApp }]) => {
|
.then(([{ parentApp }]) => {
|
||||||
return this.applications.getTargetApp(parentApp);
|
return this.applications.getTargetApp(parentApp);
|
||||||
})
|
})
|
||||||
.then(parentApp => {
|
.then((parentApp) => {
|
||||||
return Promise.map(parentApp?.services ?? [], service => {
|
return Promise.map(parentApp?.services ?? [], (service) => {
|
||||||
return this.docker.getImageEnv(service.image);
|
return this.docker.getImageEnv(service.image);
|
||||||
}).then(function (imageEnvs) {
|
}).then(function (imageEnvs) {
|
||||||
const imageHookAddresses = _.map(
|
const imageHookAddresses = _.map(
|
||||||
imageEnvs,
|
imageEnvs,
|
||||||
env =>
|
(env) =>
|
||||||
env.BALENA_DEPENDENT_DEVICES_HOOK_ADDRESS ??
|
env.BALENA_DEPENDENT_DEVICES_HOOK_ADDRESS ??
|
||||||
env.RESIN_DEPENDENT_DEVICES_HOOK_ADDRESS,
|
env.RESIN_DEPENDENT_DEVICES_HOOK_ADDRESS,
|
||||||
);
|
);
|
||||||
@ -941,7 +932,7 @@ export class Proxyvisor {
|
|||||||
|
|
||||||
sendUpdate(device, timeout, endpoint) {
|
sendUpdate(device, timeout, endpoint) {
|
||||||
return Promise.resolve(request.getRequestInstance())
|
return Promise.resolve(request.getRequestInstance())
|
||||||
.then(instance =>
|
.then((instance) =>
|
||||||
instance.putAsync(`${endpoint}${device.uuid}`, {
|
instance.putAsync(`${endpoint}${device.uuid}`, {
|
||||||
json: true,
|
json: true,
|
||||||
body: device.target,
|
body: device.target,
|
||||||
@ -958,42 +949,36 @@ export class Proxyvisor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => log.error(`Error updating device ${device.uuid}`, err));
|
.catch((err) => log.error(`Error updating device ${device.uuid}`, err));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendDeleteHook({ uuid }, timeout, endpoint) {
|
sendDeleteHook({ uuid }, timeout, endpoint) {
|
||||||
return Promise.resolve(request.getRequestInstance())
|
return Promise.resolve(request.getRequestInstance())
|
||||||
.then(instance => instance.delAsync(`${endpoint}${uuid}`))
|
.then((instance) => instance.delAsync(`${endpoint}${uuid}`))
|
||||||
.timeout(timeout)
|
.timeout(timeout)
|
||||||
.spread((response, body) => {
|
.spread((response, body) => {
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
return this.db
|
return this.db.models('dependentDevice').del().where({ uuid });
|
||||||
.models('dependentDevice')
|
|
||||||
.del()
|
|
||||||
.where({ uuid });
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Hook returned ${response.statusCode}: ${body}`);
|
throw new Error(`Hook returned ${response.statusCode}: ${body}`);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => log.error(`Error deleting device ${uuid}`, err));
|
.catch((err) => log.error(`Error deleting device ${uuid}`, err));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendUpdates({ uuid }) {
|
sendUpdates({ uuid }) {
|
||||||
return Promise.join(
|
return Promise.join(
|
||||||
this.db
|
this.db.models('dependentDevice').where({ uuid }).select(),
|
||||||
.models('dependentDevice')
|
|
||||||
.where({ uuid })
|
|
||||||
.select(),
|
|
||||||
this.config.get('apiTimeout'),
|
this.config.get('apiTimeout'),
|
||||||
([dev], apiTimeout) => {
|
([dev], apiTimeout) => {
|
||||||
if (dev == null) {
|
if (dev == null) {
|
||||||
log.warn(`Trying to send update to non-existent device ${uuid}`);
|
log.warn(`Trying to send update to non-existent device ${uuid}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return this.normaliseDependentDeviceFromDB(dev).then(device => {
|
return this.normaliseDependentDeviceFromDB(dev).then((device) => {
|
||||||
const currentState = formatCurrentAsState(device);
|
const currentState = formatCurrentAsState(device);
|
||||||
const targetState = formatTargetAsState(device);
|
const targetState = formatTargetAsState(device);
|
||||||
return this.getHookEndpoint(device.appId).then(endpoint => {
|
return this.getHookEndpoint(device.appId).then((endpoint) => {
|
||||||
if (device.markedForDeletion) {
|
if (device.markedForDeletion) {
|
||||||
return this.sendDeleteHook(device, apiTimeout, endpoint);
|
return this.sendDeleteHook(device, apiTimeout, endpoint);
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -71,7 +71,7 @@ const expressLogger = morgan(
|
|||||||
'ms',
|
'ms',
|
||||||
].join(' '),
|
].join(' '),
|
||||||
{
|
{
|
||||||
stream: { write: d => log.api(d.toString().trimRight()) },
|
stream: { write: (d) => log.api(d.toString().trimRight()) },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ export class SupervisorAPI {
|
|||||||
|
|
||||||
this.api.get('/v1/healthy', async (_req, res) => {
|
this.api.get('/v1/healthy', async (_req, res) => {
|
||||||
try {
|
try {
|
||||||
const healths = await Promise.all(this.healthchecks.map(fn => fn()));
|
const healths = await Promise.all(this.healthchecks.map((fn) => fn()));
|
||||||
if (!_.every(healths)) {
|
if (!_.every(healths)) {
|
||||||
log.error('Healthcheck failed');
|
log.error('Healthcheck failed');
|
||||||
return res.status(500).send('Unhealthy');
|
return res.status(500).send('Unhealthy');
|
||||||
@ -194,7 +194,7 @@ export class SupervisorAPI {
|
|||||||
await this.applyRules(localMode || false, port, allowedInterfaces);
|
await this.applyRules(localMode || false, port, allowedInterfaces);
|
||||||
// Monitor the switching of local mode, and change which interfaces will
|
// Monitor the switching of local mode, and change which interfaces will
|
||||||
// be listened to based on that
|
// be listened to based on that
|
||||||
this.config.on('change', changedConfig => {
|
this.config.on('change', (changedConfig) => {
|
||||||
if (changedConfig.localMode != null) {
|
if (changedConfig.localMode != null) {
|
||||||
this.applyRules(
|
this.applyRules(
|
||||||
changedConfig.localMode || false,
|
changedConfig.localMode || false,
|
||||||
@ -204,7 +204,7 @@ export class SupervisorAPI {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
this.server = this.api.listen(port, () => {
|
this.server = this.api.listen(port, () => {
|
||||||
log.info(`Supervisor API successfully started on port ${port}`);
|
log.info(`Supervisor API successfully started on port ${port}`);
|
||||||
if (this.server) {
|
if (this.server) {
|
||||||
|
@ -31,7 +31,7 @@ export class TargetStateAccessor {
|
|||||||
// If we switch backend, the target state also needs to
|
// If we switch backend, the target state also needs to
|
||||||
// be invalidated (this includes switching to and from
|
// be invalidated (this includes switching to and from
|
||||||
// local mode)
|
// local mode)
|
||||||
this.config.on('change', conf => {
|
this.config.on('change', (conf) => {
|
||||||
if (conf.apiEndpoint != null || conf.localMode != null) {
|
if (conf.apiEndpoint != null || conf.localMode != null) {
|
||||||
this.targetState = undefined;
|
this.targetState = undefined;
|
||||||
}
|
}
|
||||||
@ -45,7 +45,7 @@ export class TargetStateAccessor {
|
|||||||
await this.getTargetApps();
|
await this.getTargetApps();
|
||||||
}
|
}
|
||||||
|
|
||||||
return _.find(this.targetState, app => app.appId === appId);
|
return _.find(this.targetState, (app) => app.appId === appId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getTargetApps(): Promise<DatabaseApps> {
|
public async getTargetApps(): Promise<DatabaseApps> {
|
||||||
@ -70,7 +70,7 @@ export class TargetStateAccessor {
|
|||||||
this.targetState = undefined;
|
this.targetState = undefined;
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
apps.map(app =>
|
apps.map((app) =>
|
||||||
this.db.upsertModel('app', app, { appId: app.appId }, trx),
|
this.db.upsertModel('app', app, { appId: app.appId }, trx),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -22,23 +22,25 @@ async function createOldDatabase(path: string) {
|
|||||||
name: string,
|
name: string,
|
||||||
fn: (trx: Knex.CreateTableBuilder) => void,
|
fn: (trx: Knex.CreateTableBuilder) => void,
|
||||||
) =>
|
) =>
|
||||||
knex.schema.createTable(name, t => {
|
knex.schema.createTable(name, (t) => {
|
||||||
if (fn != null) {
|
if (fn != null) {
|
||||||
return fn(t);
|
return fn(t);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await createEmptyTable('app', t => {
|
await createEmptyTable('app', (t) => {
|
||||||
t.increments('id').primary();
|
t.increments('id').primary();
|
||||||
t.boolean('privileged');
|
t.boolean('privileged');
|
||||||
return t.string('containerId');
|
return t.string('containerId');
|
||||||
});
|
});
|
||||||
await createEmptyTable('config', t => {
|
await createEmptyTable('config', (t) => {
|
||||||
t.string('key');
|
t.string('key');
|
||||||
return t.string('value');
|
return t.string('value');
|
||||||
});
|
});
|
||||||
await createEmptyTable('dependentApp', t => t.increments('id').primary());
|
await createEmptyTable('dependentApp', (t) => t.increments('id').primary());
|
||||||
await createEmptyTable('dependentDevice', t => t.increments('id').primary());
|
await createEmptyTable('dependentDevice', (t) =>
|
||||||
|
t.increments('id').primary(),
|
||||||
|
);
|
||||||
return knex;
|
return knex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,6 +105,6 @@ describe('DB', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('allows performing transactions', () => {
|
it('allows performing transactions', () => {
|
||||||
return db.transaction(trx => expect(trx.commit()).to.be.fulfilled);
|
return db.transaction((trx) => expect(trx.commit()).to.be.fulfilled);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -111,8 +111,8 @@ describe('Config', () => {
|
|||||||
expect(conf.get('unknownInvalidValue' as any)).to.be.rejected;
|
expect(conf.get('unknownInvalidValue' as any)).to.be.rejected;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('emits a change event when values are set', done => {
|
it('emits a change event when values are set', (done) => {
|
||||||
conf.on('change', val => {
|
conf.on('change', (val) => {
|
||||||
expect(val).to.deep.equal({ name: 'someValue' });
|
expect(val).to.deep.equal({ name: 'someValue' });
|
||||||
return done();
|
return done();
|
||||||
});
|
});
|
||||||
|
@ -223,7 +223,7 @@ describe('deviceState', () => {
|
|||||||
track: console.log,
|
track: console.log,
|
||||||
};
|
};
|
||||||
|
|
||||||
stub(Service as any, 'extendEnvVars').callsFake(env => {
|
stub(Service as any, 'extendEnvVars').callsFake((env) => {
|
||||||
env['ADDITIONAL_ENV_VAR'] = 'foo';
|
env['ADDITIONAL_ENV_VAR'] = 'foo';
|
||||||
return env;
|
return env;
|
||||||
});
|
});
|
||||||
@ -308,12 +308,8 @@ describe('deviceState', () => {
|
|||||||
(deviceState as any).deviceConfig.getCurrent.restore();
|
(deviceState as any).deviceConfig.getCurrent.restore();
|
||||||
|
|
||||||
const pinned = await config.get('pinDevice');
|
const pinned = await config.get('pinDevice');
|
||||||
expect(pinned)
|
expect(pinned).to.have.property('app').that.equals(1234);
|
||||||
.to.have.property('app')
|
expect(pinned).to.have.property('commit').that.equals('abcdef');
|
||||||
.that.equals(1234);
|
|
||||||
expect(pinned)
|
|
||||||
.to.have.property('commit')
|
|
||||||
.that.equals('abcdef');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('emits a change event when a new state is reported', () => {
|
it('emits a change event when a new state is reported', () => {
|
||||||
@ -349,7 +345,7 @@ describe('deviceState', () => {
|
|||||||
expect(deviceState.setTarget(testTargetInvalid as any)).to.be.rejected;
|
expect(deviceState.setTarget(testTargetInvalid as any)).to.be.rejected;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows triggering applying the target state', done => {
|
it('allows triggering applying the target state', (done) => {
|
||||||
stub(deviceState as any, 'applyTarget').returns(Promise.resolve());
|
stub(deviceState as any, 'applyTarget').returns(Promise.resolve());
|
||||||
|
|
||||||
deviceState.triggerApplyTarget({ force: true });
|
deviceState.triggerApplyTarget({ force: true });
|
||||||
@ -365,7 +361,7 @@ describe('deviceState', () => {
|
|||||||
}, 5);
|
}, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cancels current promise applying the target state', done => {
|
it('cancels current promise applying the target state', (done) => {
|
||||||
(deviceState as any).scheduledApply = { force: false, delay: 100 };
|
(deviceState as any).scheduledApply = { force: false, delay: 100 };
|
||||||
(deviceState as any).applyInProgress = true;
|
(deviceState as any).applyInProgress = true;
|
||||||
(deviceState as any).applyCancelled = false;
|
(deviceState as any).applyCancelled = false;
|
||||||
|
@ -14,7 +14,7 @@ describe('EventTracker', () => {
|
|||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
initStub = stub(mixpanel, 'init').callsFake(
|
initStub = stub(mixpanel, 'init').callsFake(
|
||||||
token =>
|
(token) =>
|
||||||
(({
|
(({
|
||||||
token,
|
token,
|
||||||
track: stub().returns(undefined),
|
track: stub().returns(undefined),
|
||||||
|
@ -14,7 +14,7 @@ describe('Logger', function() {
|
|||||||
this._req.end = sinon.spy();
|
this._req.end = sinon.spy();
|
||||||
|
|
||||||
this._req.body = '';
|
this._req.body = '';
|
||||||
this._req.pipe(zlib.createGunzip()).on('data', chunk => {
|
this._req.pipe(zlib.createGunzip()).on('data', (chunk) => {
|
||||||
this._req.body += chunk;
|
this._req.body += chunk;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -87,15 +87,9 @@ describe('Logger', function() {
|
|||||||
expect(lines[2]).to.equal('');
|
expect(lines[2]).to.equal('');
|
||||||
|
|
||||||
let msg = JSON.parse(lines[0]);
|
let msg = JSON.parse(lines[0]);
|
||||||
expect(msg)
|
expect(msg).to.have.property('message').that.equals('foobar');
|
||||||
.to.have.property('message')
|
expect(msg).to.have.property('serviceId').that.equals(15);
|
||||||
.that.equals('foobar');
|
expect(msg).to.have.property('timestamp').that.is.at.least(timestamp);
|
||||||
expect(msg)
|
|
||||||
.to.have.property('serviceId')
|
|
||||||
.that.equals(15);
|
|
||||||
expect(msg)
|
|
||||||
.to.have.property('timestamp')
|
|
||||||
.that.is.at.least(timestamp);
|
|
||||||
msg = JSON.parse(lines[1]);
|
msg = JSON.parse(lines[1]);
|
||||||
expect(msg).to.deep.equal({
|
expect(msg).to.deep.equal({
|
||||||
timestamp: 1337,
|
timestamp: 1337,
|
||||||
@ -122,15 +116,9 @@ describe('Logger', function() {
|
|||||||
expect(lines[1]).to.equal('');
|
expect(lines[1]).to.equal('');
|
||||||
|
|
||||||
const msg = JSON.parse(lines[0]);
|
const msg = JSON.parse(lines[0]);
|
||||||
expect(msg)
|
expect(msg).to.have.property('message').that.equals('Hello there!');
|
||||||
.to.have.property('message')
|
expect(msg).to.have.property('isSystem').that.equals(true);
|
||||||
.that.equals('Hello there!');
|
expect(msg).to.have.property('timestamp').that.is.at.least(timestamp);
|
||||||
expect(msg)
|
|
||||||
.to.have.property('isSystem')
|
|
||||||
.that.equals(true);
|
|
||||||
expect(msg)
|
|
||||||
.to.have.property('timestamp')
|
|
||||||
.that.is.at.least(timestamp);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -45,9 +45,7 @@ APPEND \${cbootargs} \${resin_kernel_root} ro rootwait\
|
|||||||
expect(parsed.globals)
|
expect(parsed.globals)
|
||||||
.to.have.property('DEFAULT')
|
.to.have.property('DEFAULT')
|
||||||
.that.equals('primary');
|
.that.equals('primary');
|
||||||
expect(parsed.globals)
|
expect(parsed.globals).to.have.property('TIMEOUT').that.equals('30');
|
||||||
.to.have.property('TIMEOUT')
|
|
||||||
.that.equals('30');
|
|
||||||
expect(parsed.globals)
|
expect(parsed.globals)
|
||||||
.to.have.property('MENU TITLE')
|
.to.have.property('MENU TITLE')
|
||||||
.that.equals('Boot Options');
|
.that.equals('Boot Options');
|
||||||
@ -57,9 +55,7 @@ APPEND \${cbootargs} \${resin_kernel_root} ro rootwait\
|
|||||||
expect(primary)
|
expect(primary)
|
||||||
.to.have.property('MENU LABEL')
|
.to.have.property('MENU LABEL')
|
||||||
.that.equals('primary Image');
|
.that.equals('primary Image');
|
||||||
expect(primary)
|
expect(primary).to.have.property('LINUX').that.equals('/Image');
|
||||||
.to.have.property('LINUX')
|
|
||||||
.that.equals('/Image');
|
|
||||||
expect(primary)
|
expect(primary)
|
||||||
.to.have.property('APPEND')
|
.to.have.property('APPEND')
|
||||||
.that.equals('${cbootargs} ${resin_kernel_root} ro rootwait');
|
.that.equals('${cbootargs} ${resin_kernel_root} ro rootwait');
|
||||||
@ -82,15 +78,11 @@ APPEND test4\
|
|||||||
|
|
||||||
// @ts-ignore accessing private method
|
// @ts-ignore accessing private method
|
||||||
const parsed = ExtlinuxConfigBackend.parseExtlinuxFile(text);
|
const parsed = ExtlinuxConfigBackend.parseExtlinuxFile(text);
|
||||||
expect(parsed.labels)
|
expect(parsed.labels).to.have.property('primary').that.deep.equals({
|
||||||
.to.have.property('primary')
|
|
||||||
.that.deep.equals({
|
|
||||||
LINUX: 'test1',
|
LINUX: 'test1',
|
||||||
APPEND: 'test2',
|
APPEND: 'test2',
|
||||||
});
|
});
|
||||||
expect(parsed.labels)
|
expect(parsed.labels).to.have.property('secondary').that.deep.equals({
|
||||||
.to.have.property('secondary')
|
|
||||||
.that.deep.equals({
|
|
||||||
LINUX: 'test3',
|
LINUX: 'test3',
|
||||||
APPEND: 'test4',
|
APPEND: 'test4',
|
||||||
});
|
});
|
||||||
@ -112,9 +104,7 @@ APPEND \${cbootargs} \${resin_kernel_root} ro rootwait isolcpus=3\
|
|||||||
let readFileStub = stub(fs, 'readFile').resolves(text);
|
let readFileStub = stub(fs, 'readFile').resolves(text);
|
||||||
let parsed = extlinuxBackend.getBootConfig();
|
let parsed = extlinuxBackend.getBootConfig();
|
||||||
|
|
||||||
expect(parsed)
|
expect(parsed).to.eventually.have.property('isolcpus').that.equals('3');
|
||||||
.to.eventually.have.property('isolcpus')
|
|
||||||
.that.equals('3');
|
|
||||||
readFileStub.restore();
|
readFileStub.restore();
|
||||||
|
|
||||||
text = `\
|
text = `\
|
||||||
|
@ -28,12 +28,8 @@ describe('Compose volumes', () => {
|
|||||||
Scope: 'local',
|
Scope: 'local',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(volume)
|
expect(volume).to.have.property('appId').that.equals(1032480);
|
||||||
.to.have.property('appId')
|
expect(volume).to.have.property('name').that.equals('one_volume');
|
||||||
.that.equals(1032480);
|
|
||||||
expect(volume)
|
|
||||||
.to.have.property('name')
|
|
||||||
.that.equals('one_volume');
|
|
||||||
expect(volume)
|
expect(volume)
|
||||||
.to.have.property('config')
|
.to.have.property('config')
|
||||||
.that.has.property('labels')
|
.that.has.property('labels')
|
||||||
@ -65,12 +61,8 @@ describe('Compose volumes', () => {
|
|||||||
opts,
|
opts,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(volume)
|
expect(volume).to.have.property('appId').that.equals(1032480);
|
||||||
.to.have.property('appId')
|
expect(volume).to.have.property('name').that.equals('one_volume');
|
||||||
.that.equals(1032480);
|
|
||||||
expect(volume)
|
|
||||||
.to.have.property('name')
|
|
||||||
.that.equals('one_volume');
|
|
||||||
expect(volume)
|
expect(volume)
|
||||||
.to.have.property('config')
|
.to.have.property('config')
|
||||||
.that.has.property('labels')
|
.that.has.property('labels')
|
||||||
@ -106,12 +98,8 @@ describe('Compose volumes', () => {
|
|||||||
opts,
|
opts,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(volume)
|
expect(volume).to.have.property('appId').that.equals(1032480);
|
||||||
.to.have.property('appId')
|
expect(volume).to.have.property('name').that.equals('one_volume');
|
||||||
.that.equals(1032480);
|
|
||||||
expect(volume)
|
|
||||||
.to.have.property('name')
|
|
||||||
.that.equals('one_volume');
|
|
||||||
expect(volume)
|
expect(volume)
|
||||||
.to.have.property('config')
|
.to.have.property('config')
|
||||||
.that.has.property('labels')
|
.that.has.property('labels')
|
||||||
|
@ -44,10 +44,7 @@ describe('SupervisorAPI', () => {
|
|||||||
|
|
||||||
describe('/ping', () => {
|
describe('/ping', () => {
|
||||||
it('responds with OK (without auth)', async () => {
|
it('responds with OK (without auth)', async () => {
|
||||||
await request
|
await request.get('/ping').set('Accept', 'application/json').expect(200);
|
||||||
.get('/ping')
|
|
||||||
.set('Accept', 'application/json')
|
|
||||||
.expect(200);
|
|
||||||
});
|
});
|
||||||
it('responds with OK (with auth)', async () => {
|
it('responds with OK (with auth)', async () => {
|
||||||
await request
|
await request
|
||||||
@ -71,7 +68,7 @@ describe('SupervisorAPI', () => {
|
|||||||
.set('Authorization', `Bearer ${VALID_SECRET}`)
|
.set('Authorization', `Bearer ${VALID_SECRET}`)
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(sampleResponses.V2.GET['/device/vpn'].statusCode)
|
.expect(sampleResponses.V2.GET['/device/vpn'].statusCode)
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
expect(response.body).to.deep.equal(
|
expect(response.body).to.deep.equal(
|
||||||
sampleResponses.V2.GET['/device/vpn'].body,
|
sampleResponses.V2.GET['/device/vpn'].body,
|
||||||
);
|
);
|
||||||
|
@ -27,7 +27,7 @@ describe('LocalModeManager', () => {
|
|||||||
.models('engineSnapshot')
|
.models('engineSnapshot')
|
||||||
.count('* as cnt')
|
.count('* as cnt')
|
||||||
.first()
|
.first()
|
||||||
.then(r => r.cnt);
|
.then((r) => r.cnt);
|
||||||
|
|
||||||
// Cleanup the database (to make sure nothing is left since last tests).
|
// Cleanup the database (to make sure nothing is left since last tests).
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -235,7 +235,7 @@ describe('LocalModeManager', () => {
|
|||||||
|
|
||||||
await localMode.handleLocalModeStateChange(false);
|
await localMode.handleLocalModeStateChange(false);
|
||||||
|
|
||||||
removeStubs.forEach(s => expect(s.remove.calledTwice).to.be.true);
|
removeStubs.forEach((s) => expect(s.remove.calledTwice).to.be.true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps objects from the previous snapshot on local mode exit', async () => {
|
it('keeps objects from the previous snapshot on local mode exit', async () => {
|
||||||
@ -255,7 +255,7 @@ describe('LocalModeManager', () => {
|
|||||||
.true;
|
.true;
|
||||||
expect(dockerStub.getVolume.calledWithExactly('volume-2')).to.be.true;
|
expect(dockerStub.getVolume.calledWithExactly('volume-2')).to.be.true;
|
||||||
expect(dockerStub.getNetwork.calledWithExactly('network-2')).to.be.true;
|
expect(dockerStub.getNetwork.calledWithExactly('network-2')).to.be.true;
|
||||||
removeStubs.forEach(s => expect(s.remove.calledOnce).to.be.true);
|
removeStubs.forEach((s) => expect(s.remove.calledOnce).to.be.true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs but consumes cleanup errors on local mode exit', async () => {
|
it('logs but consumes cleanup errors on local mode exit', async () => {
|
||||||
@ -267,7 +267,7 @@ describe('LocalModeManager', () => {
|
|||||||
await localMode.handleLocalModeStateChange(false);
|
await localMode.handleLocalModeStateChange(false);
|
||||||
|
|
||||||
// Even though remove method throws, we still attempt all removals.
|
// Even though remove method throws, we still attempt all removals.
|
||||||
removeStubs.forEach(s => expect(s.remove.calledTwice).to.be.true);
|
removeStubs.forEach((s) => expect(s.remove.calledTwice).to.be.true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('skips cleanup without previous snapshot on local mode exit', async () => {
|
it('skips cleanup without previous snapshot on local mode exit', async () => {
|
||||||
@ -279,7 +279,7 @@ describe('LocalModeManager', () => {
|
|||||||
expect(dockerStub.getContainer.notCalled).to.be.true;
|
expect(dockerStub.getContainer.notCalled).to.be.true;
|
||||||
expect(dockerStub.getVolume.notCalled).to.be.true;
|
expect(dockerStub.getVolume.notCalled).to.be.true;
|
||||||
expect(dockerStub.getNetwork.notCalled).to.be.true;
|
expect(dockerStub.getNetwork.notCalled).to.be.true;
|
||||||
removeStubs.forEach(s => expect(s.remove.notCalled).to.be.true);
|
removeStubs.forEach((s) => expect(s.remove.notCalled).to.be.true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can be awaited', async () => {
|
it('can be awaited', async () => {
|
||||||
@ -292,7 +292,7 @@ describe('LocalModeManager', () => {
|
|||||||
// Await like it's done by DeviceState.
|
// Await like it's done by DeviceState.
|
||||||
await localMode.switchCompletion();
|
await localMode.switchCompletion();
|
||||||
|
|
||||||
removeStubs.forEach(s => expect(s.remove.calledTwice).to.be.true);
|
removeStubs.forEach((s) => expect(s.remove.calledTwice).to.be.true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cleans the last snapshot so that nothing is done on restart', async () => {
|
it('cleans the last snapshot so that nothing is done on restart', async () => {
|
||||||
@ -312,7 +312,7 @@ describe('LocalModeManager', () => {
|
|||||||
expect(dockerStub.getContainer.callCount).to.be.equal(3); // +1 for supervisor inspect call.
|
expect(dockerStub.getContainer.callCount).to.be.equal(3); // +1 for supervisor inspect call.
|
||||||
expect(dockerStub.getVolume.callCount).to.be.equal(2);
|
expect(dockerStub.getVolume.callCount).to.be.equal(2);
|
||||||
expect(dockerStub.getNetwork.callCount).to.be.equal(2);
|
expect(dockerStub.getNetwork.callCount).to.be.equal(2);
|
||||||
removeStubs.forEach(s => expect(s.remove.callCount).to.be.equal(2));
|
removeStubs.forEach((s) => expect(s.remove.callCount).to.be.equal(2));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('skips cleanup in case of data corruption', async () => {
|
it('skips cleanup in case of data corruption', async () => {
|
||||||
@ -330,7 +330,7 @@ describe('LocalModeManager', () => {
|
|||||||
expect(dockerStub.getContainer.notCalled).to.be.true;
|
expect(dockerStub.getContainer.notCalled).to.be.true;
|
||||||
expect(dockerStub.getVolume.notCalled).to.be.true;
|
expect(dockerStub.getVolume.notCalled).to.be.true;
|
||||||
expect(dockerStub.getNetwork.notCalled).to.be.true;
|
expect(dockerStub.getNetwork.notCalled).to.be.true;
|
||||||
removeStubs.forEach(s => expect(s.remove.notCalled).to.be.true);
|
removeStubs.forEach((s) => expect(s.remove.notCalled).to.be.true);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with supervisor being updated', () => {
|
describe('with supervisor being updated', () => {
|
||||||
@ -362,7 +362,7 @@ describe('LocalModeManager', () => {
|
|||||||
// Current engine objects include 2 entities of each type.
|
// Current engine objects include 2 entities of each type.
|
||||||
// Container-1, network-1, image-1, and volume-1 are resources associated with currently running supervisor.
|
// Container-1, network-1, image-1, and volume-1 are resources associated with currently running supervisor.
|
||||||
// Only xxx-2 objects must be deleted.
|
// Only xxx-2 objects must be deleted.
|
||||||
removeStubs.forEach(s => expect(s.remove.calledOnce).to.be.true);
|
removeStubs.forEach((s) => expect(s.remove.calledOnce).to.be.true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -82,10 +82,12 @@ describe('Container contracts', () => {
|
|||||||
// package.json will, we generate values which are above
|
// package.json will, we generate values which are above
|
||||||
// and below the current value, and use these to reason
|
// and below the current value, and use these to reason
|
||||||
// about the contract engine results
|
// about the contract engine results
|
||||||
const supervisorVersionGreater = `${semver.major(supervisorVersion)! +
|
const supervisorVersionGreater = `${
|
||||||
1}.0.0`;
|
semver.major(supervisorVersion)! + 1
|
||||||
const supervisorVersionLesser = `${semver.major(supervisorVersion)! -
|
}.0.0`;
|
||||||
1}.0.0`;
|
const supervisorVersionLesser = `${
|
||||||
|
semver.major(supervisorVersion)! - 1
|
||||||
|
}.0.0`;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
// We ensure that the versions we're using for testing
|
// We ensure that the versions we're using for testing
|
||||||
@ -275,9 +277,7 @@ describe('Container contracts', () => {
|
|||||||
optional: false,
|
optional: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(fulfilled)
|
expect(fulfilled).to.have.property('valid').that.equals(false);
|
||||||
.to.have.property('valid')
|
|
||||||
.that.equals(false);
|
|
||||||
expect(fulfilled)
|
expect(fulfilled)
|
||||||
.to.have.property('unmetServices')
|
.to.have.property('unmetServices')
|
||||||
.that.deep.equals(['service']);
|
.that.deep.equals(['service']);
|
||||||
@ -298,9 +298,7 @@ describe('Container contracts', () => {
|
|||||||
optional: false,
|
optional: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(fulfilled)
|
expect(fulfilled).to.have.property('valid').that.equals(false);
|
||||||
.to.have.property('valid')
|
|
||||||
.that.equals(false);
|
|
||||||
expect(fulfilled)
|
expect(fulfilled)
|
||||||
.to.have.property('unmetServices')
|
.to.have.property('unmetServices')
|
||||||
.that.deep.equals(['service2']);
|
.that.deep.equals(['service2']);
|
||||||
@ -335,9 +333,7 @@ describe('Container contracts', () => {
|
|||||||
optional: false,
|
optional: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(fulfilled)
|
expect(fulfilled).to.have.property('valid').that.equals(false);
|
||||||
.to.have.property('valid')
|
|
||||||
.that.equals(false);
|
|
||||||
expect(fulfilled)
|
expect(fulfilled)
|
||||||
.to.have.property('unmetServices')
|
.to.have.property('unmetServices')
|
||||||
.that.deep.equals(['service2']);
|
.that.deep.equals(['service2']);
|
||||||
|
@ -6,12 +6,12 @@ import { expect } from './lib/chai-config';
|
|||||||
describe('journald', () => {
|
describe('journald', () => {
|
||||||
let spawn: SinonStub;
|
let spawn: SinonStub;
|
||||||
|
|
||||||
beforeEach(done => {
|
beforeEach((done) => {
|
||||||
spawn = stub(require('child_process'), 'spawn');
|
spawn = stub(require('child_process'), 'spawn');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(done => {
|
afterEach((done) => {
|
||||||
spawn.restore();
|
spawn.restore();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -50,7 +50,7 @@ describe('journald', () => {
|
|||||||
expect(actualCommand).deep.equal(expectedCommand);
|
expect(actualCommand).deep.equal(expectedCommand);
|
||||||
expect(actualCoreArgs).deep.equal(expectedCoreArgs);
|
expect(actualCoreArgs).deep.equal(expectedCoreArgs);
|
||||||
|
|
||||||
expectedOptionalArgs.forEach(arg => {
|
expectedOptionalArgs.forEach((arg) => {
|
||||||
expect(actualOptionalArgs).to.include(arg);
|
expect(actualOptionalArgs).to.include(arg);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -40,7 +40,7 @@ api.balenaBackend = {
|
|||||||
: null;
|
: null;
|
||||||
if (uuid != null) {
|
if (uuid != null) {
|
||||||
return res.json({
|
return res.json({
|
||||||
d: _.filter(api.balenaBackend!.devices, dev => dev.uuid === uuid),
|
d: _.filter(api.balenaBackend!.devices, (dev) => dev.uuid === uuid),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return res.json({ d: [] });
|
return res.json({ d: [] });
|
||||||
|
Reference in New Issue
Block a user