updater: move app update logic and attach the manual update reason
This commit is contained in:
102
src/apps.js
102
src/apps.js
@@ -79,9 +79,6 @@ exports = module.exports = {
|
||||
|
||||
checkManifest,
|
||||
|
||||
canAutoupdateApp,
|
||||
autoupdateApps,
|
||||
|
||||
restoreApps,
|
||||
configureApps,
|
||||
schedulePendingTasks,
|
||||
@@ -761,6 +758,38 @@ function postProcess(result) {
|
||||
delete result.devicesJson;
|
||||
}
|
||||
|
||||
// note: this value cannot be cached as it depends on enableAutomaticUpdate and runState
|
||||
function canAutoupdateApp(app, updateInfo) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof updateInfo, 'object');
|
||||
|
||||
const manifest = updateInfo.manifest;
|
||||
|
||||
if (!app.enableAutomaticUpdate) return { code: false, reason: 'Automatic updates for the app is disabled' };
|
||||
|
||||
// for invalid subscriptions the appstore does not return a dockerImage
|
||||
if (!manifest.dockerImage) return { code: false, reason: 'Invalid or Expired subscription '};
|
||||
|
||||
if (updateInfo.unstable) return { code: false, reason: 'Update is marked as unstable' }; // only manual update allowed for unstable updates
|
||||
|
||||
if ((semver.major(app.manifest.version) !== 0) && (semver.major(app.manifest.version) !== semver.major(manifest.version))) {
|
||||
return { code: false, reason: 'Major package version requires review of breaking changes' }; // major changes are blocking
|
||||
}
|
||||
|
||||
if (app.runState === exports.RSTATE_STOPPED) return { code: false, reason: 'Stopped apps cannot run migration scripts' };
|
||||
|
||||
const newTcpPorts = manifest.tcpPorts || {};
|
||||
const newUdpPorts = manifest.udpPorts || {};
|
||||
const portBindings = app.portBindings; // this is never null
|
||||
|
||||
for (const portName in portBindings) {
|
||||
if (!(portName in newTcpPorts) && !(portName in newUdpPorts)) return { code: false, reason: `${portName} port was in use but new update removes it` };
|
||||
}
|
||||
|
||||
// it's fine if one or more (unused) port keys got removed
|
||||
return { code: true, reason: '' };
|
||||
}
|
||||
|
||||
// attaches computed properties
|
||||
function attachProperties(app, domainObjectMap) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
@@ -771,7 +800,14 @@ function attachProperties(app, domainObjectMap) {
|
||||
app.secondaryDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, ad.domain); });
|
||||
app.redirectDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, ad.domain); });
|
||||
app.aliasDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, ad.domain); });
|
||||
app.updateInfo = updateChecker.getAppUpdateInfoSync(app.id);
|
||||
|
||||
const updateInfo = updateChecker.getAppUpdateInfoSync(app.id);
|
||||
if (updateInfo) {
|
||||
const { code, reason } = canAutoupdateApp(app, updateInfo); // isAutoUpdatable is not cached since it depends on enableAutomaticUpdate and runState
|
||||
updateInfo.isAutoUpdatable = code;
|
||||
updateInfo.manualUpdateReason = reason;
|
||||
}
|
||||
app.updateInfo = updateInfo;
|
||||
}
|
||||
|
||||
function isAdmin(user) {
|
||||
@@ -2728,64 +2764,6 @@ async function getExec(app, execId) {
|
||||
return await docker.getExec(execId);
|
||||
}
|
||||
|
||||
function canAutoupdateApp(app, updateInfo) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof updateInfo, 'object');
|
||||
|
||||
const manifest = updateInfo.manifest;
|
||||
|
||||
if (!app.enableAutomaticUpdate) return false;
|
||||
|
||||
// for invalid subscriptions the appstore does not return a dockerImage
|
||||
if (!manifest.dockerImage) return false;
|
||||
|
||||
if (updateInfo.unstable) return false; // only manual update allowed for unstable updates
|
||||
|
||||
if ((semver.major(app.manifest.version) !== 0) && (semver.major(app.manifest.version) !== semver.major(manifest.version))) return false; // major changes are blocking
|
||||
|
||||
if (app.runState === exports.RSTATE_STOPPED) return false; // stopped apps won't run migration scripts and shouldn't be updated
|
||||
|
||||
const newTcpPorts = manifest.tcpPorts || { };
|
||||
const newUdpPorts = manifest.udpPorts || { };
|
||||
const portBindings = app.portBindings; // this is never null
|
||||
|
||||
for (const portName in portBindings) {
|
||||
if (!(portName in newTcpPorts) && !(portName in newUdpPorts)) return false; // portName was in use but new update removes it
|
||||
}
|
||||
|
||||
// it's fine if one or more (unused) keys got removed
|
||||
return true;
|
||||
}
|
||||
|
||||
async function autoupdateApps(updateInfo, auditSource) { // updateInfo is { appId -> { manifest } }
|
||||
assert.strictEqual(typeof updateInfo, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
|
||||
for (const appId of Object.keys(updateInfo)) {
|
||||
const [getError, app] = await safe(get(appId));
|
||||
if (getError) {
|
||||
debug(`Cannot autoupdate app ${appId}: ${getError.message}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!canAutoupdateApp(app, updateInfo[appId])) {
|
||||
debug(`app ${app.fqdn} requires manual update`);
|
||||
await notifications.pin(notifications.TYPE_MANUAL_APP_UPDATE_NEEDED, `${app.manifest.title} at ${app.fqdn} requires manual update to version ${updateInfo[appId].manifest.version}`,
|
||||
`Changelog:\n${updateInfo[appId].manifest.changelog}\n`, { context: app.id });
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = {
|
||||
manifest: updateInfo[appId].manifest,
|
||||
force: false
|
||||
};
|
||||
|
||||
debug(`app ${app.fqdn} will be automatically updated`);
|
||||
const [updateError] = await safe(updateApp(app, data, auditSource));
|
||||
if (updateError) debug(`Error autoupdating ${appId}. ${updateError.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function backup(app, auditSource) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
|
||||
Reference in New Issue
Block a user