mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-23 00:16:25 +01:00
Add a version service (#729)
- Checks versions against https://version.unleash.run - Generates a unique instance id (uuid)
This commit is contained in:
parent
4902161b39
commit
b83387a84a
@ -2,6 +2,8 @@
|
||||
|
||||
## 3.x.x
|
||||
|
||||
- feat: check latest version
|
||||
- feat: expose current and latest version to ui-config
|
||||
- feat: Use express-session backed by postgres
|
||||
|
||||
## 3.12.0
|
||||
|
@ -100,3 +100,11 @@ curl --location --request PUT 'http://localhost:4242/api/admin/features/Feature.
|
||||
]\
|
||||
}'\
|
||||
```
|
||||
|
||||
## Version check
|
||||
|
||||
- Unleash checks that it uses the latest version by making a call to https://version.unleash.run.
|
||||
- This is a cloud function storing instance id to our database for statistics.
|
||||
- This request includes a unique instance id for your server.
|
||||
- If you do not wish to check for upgrades define the environment variable `CHECK_VERSION` to anything else other than `true` before starting, and Unleash won't make any calls
|
||||
- `export CHECK_VERSION=false`
|
||||
|
@ -69,6 +69,12 @@ function defaultOptions() {
|
||||
enableLegacyRoutes: false,
|
||||
extendedPermissions: false,
|
||||
publicFolder,
|
||||
versionCheck: {
|
||||
url:
|
||||
process.env.UNLEASH_VERSION_URL ||
|
||||
'https://version.unleash.run',
|
||||
enable: process.env.CHECK_VERSION || 'true',
|
||||
},
|
||||
enableRequestLogger: false,
|
||||
adminAuthentication: process.env.ADMIN_AUTHENTICATION || 'unsecure',
|
||||
ui: {},
|
||||
|
@ -3,16 +3,24 @@
|
||||
const Controller = require('../controller');
|
||||
|
||||
class ConfigController extends Controller {
|
||||
constructor(config) {
|
||||
constructor(config, { versionService }) {
|
||||
super(config);
|
||||
this.uiConfig = { ...config.ui, version: config.version };
|
||||
|
||||
this.versionService = versionService;
|
||||
this.uiConfig = {
|
||||
...config.ui,
|
||||
version: config.version,
|
||||
};
|
||||
this.get('/', this.getUIConfig);
|
||||
}
|
||||
|
||||
async getUIConfig(req, res) {
|
||||
const config = this.uiConfig;
|
||||
res.json(config);
|
||||
if (this.versionService) {
|
||||
const versionInfo = this.versionService.getVersionInfo();
|
||||
res.json({ ...config, versionInfo });
|
||||
} else {
|
||||
res.json(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ const TagService = require('./tag-service');
|
||||
const StrategyService = require('./strategy-service');
|
||||
const AddonService = require('./addon-service');
|
||||
const ContextService = require('./context-service');
|
||||
const VersionService = require('./version-service');
|
||||
|
||||
module.exports.createServices = (stores, config) => {
|
||||
const featureToggleService = new FeatureToggleService(stores, config);
|
||||
@ -18,6 +19,7 @@ module.exports.createServices = (stores, config) => {
|
||||
const clientMetricsService = new ClientMetricsService(stores, config);
|
||||
const addonService = new AddonService(stores, config, tagTypeService);
|
||||
const contextService = new ContextService(stores, config);
|
||||
const versionService = new VersionService(stores, config);
|
||||
|
||||
return {
|
||||
addonService,
|
||||
@ -29,5 +31,6 @@ module.exports.createServices = (stores, config) => {
|
||||
tagService,
|
||||
clientMetricsService,
|
||||
contextService,
|
||||
versionService,
|
||||
};
|
||||
};
|
||||
|
62
src/lib/services/version-service.js
Normal file
62
src/lib/services/version-service.js
Normal file
@ -0,0 +1,62 @@
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
const TWO_DAYS = 48 * 60 * 60 * 1000;
|
||||
class VersionService {
|
||||
constructor(
|
||||
{ settingStore },
|
||||
{ getLogger, versionCheck, version, enterpriseVersion },
|
||||
) {
|
||||
this.logger = getLogger('lib/services/version-service.js');
|
||||
this.settingStore = settingStore;
|
||||
this.current = {
|
||||
oss: version,
|
||||
enterprise: enterpriseVersion,
|
||||
};
|
||||
if (versionCheck) {
|
||||
if (versionCheck.url) {
|
||||
this.versionCheckUrl = versionCheck.url;
|
||||
}
|
||||
|
||||
if (versionCheck.enable === 'true') {
|
||||
this.enabled = true;
|
||||
this.checkLatestVersion();
|
||||
setInterval(this.checkLatestVersion, TWO_DAYS);
|
||||
} else {
|
||||
this.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async checkLatestVersion() {
|
||||
if (this.enabled) {
|
||||
const { id } = await this.settingStore.get('instanceInfo');
|
||||
try {
|
||||
const data = await fetch(this.versionCheckUrl, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
versions: this.current,
|
||||
id,
|
||||
}),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}).then(res => res.json());
|
||||
this.latest = {
|
||||
oss: data.versions.oss,
|
||||
enterprise: data.versions.enterprise,
|
||||
};
|
||||
this.isLatest = data.latest;
|
||||
} catch (err) {
|
||||
this.logger.info('Could not check newest version', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getVersionInfo() {
|
||||
return {
|
||||
current: this.current,
|
||||
latest: this.latest || {},
|
||||
isLatest: this.isLatest || false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VersionService;
|
118
src/lib/services/versions-service.test.js
Normal file
118
src/lib/services/versions-service.test.js
Normal file
@ -0,0 +1,118 @@
|
||||
const test = require('ava');
|
||||
const proxyquire = require('proxyquire').noCallThru();
|
||||
const fetchMock = require('fetch-mock').sandbox();
|
||||
const stores = require('../../test/fixtures/store');
|
||||
const getLogger = require('../../test/fixtures/no-logger');
|
||||
const version = require('../util/version');
|
||||
|
||||
const VersionService = proxyquire('./version-service', {
|
||||
'node-fetch': fetchMock,
|
||||
});
|
||||
|
||||
test.serial('yields current versions', async t => {
|
||||
const testurl = 'https://version.test';
|
||||
const { settingStore } = stores.createStores();
|
||||
await settingStore.insert({
|
||||
name: 'instanceInfo',
|
||||
content: { id: '1234abc' },
|
||||
});
|
||||
const latest = {
|
||||
oss: '4.0.0',
|
||||
enterprise: '4.0.0',
|
||||
};
|
||||
fetchMock.mock(
|
||||
{ url: testurl, method: 'POST' },
|
||||
{
|
||||
latest: false,
|
||||
versions: latest,
|
||||
},
|
||||
);
|
||||
const service = new VersionService(
|
||||
{ settingStore },
|
||||
{ getLogger, versionCheck: { url: testurl, enable: 'true' }, version },
|
||||
);
|
||||
await service.checkLatestVersion();
|
||||
fetchMock.done();
|
||||
const versionInfo = service.getVersionInfo();
|
||||
t.is(versionInfo.current.oss, version);
|
||||
t.falsy(versionInfo.current.enterprise);
|
||||
t.is(versionInfo.latest.oss, latest.oss);
|
||||
t.is(versionInfo.latest.enterprise, latest.enterprise);
|
||||
});
|
||||
|
||||
test.serial('supports setting enterprise version as well', async t => {
|
||||
const testurl = `https://version.test${Math.random() * 1000}`;
|
||||
const { settingStore } = stores.createStores();
|
||||
const enterpriseVersion = '3.7.0';
|
||||
await settingStore.insert({
|
||||
name: 'instanceInfo',
|
||||
content: { id: '1234abc' },
|
||||
});
|
||||
const latest = {
|
||||
oss: '4.0.0',
|
||||
enterprise: '4.0.0',
|
||||
};
|
||||
fetchMock.mock(
|
||||
{ url: testurl, method: 'POST' },
|
||||
{
|
||||
latest: false,
|
||||
versions: latest,
|
||||
},
|
||||
);
|
||||
const service = new VersionService(
|
||||
{ settingStore },
|
||||
{
|
||||
getLogger,
|
||||
versionCheck: { url: testurl, enable: 'true' },
|
||||
version,
|
||||
enterpriseVersion,
|
||||
},
|
||||
);
|
||||
await service.checkLatestVersion();
|
||||
fetchMock.done();
|
||||
const versionInfo = service.getVersionInfo();
|
||||
t.is(versionInfo.current.oss, version);
|
||||
t.is(versionInfo.current.enterprise, enterpriseVersion);
|
||||
t.is(versionInfo.latest.oss, latest.oss);
|
||||
t.is(versionInfo.latest.enterprise, latest.enterprise);
|
||||
});
|
||||
|
||||
test.serial(
|
||||
'if version check is not enabled should not make any calls',
|
||||
async t => {
|
||||
const testurl = `https://version.test${Math.random() * 1000}`;
|
||||
const { settingStore } = stores.createStores();
|
||||
const enterpriseVersion = '3.7.0';
|
||||
await settingStore.insert({
|
||||
name: 'instanceInfo',
|
||||
content: { id: '1234abc' },
|
||||
});
|
||||
const latest = {
|
||||
oss: '4.0.0',
|
||||
enterprise: '4.0.0',
|
||||
};
|
||||
fetchMock.mock(
|
||||
{ url: testurl, method: 'POST' },
|
||||
{
|
||||
latest: false,
|
||||
versions: latest,
|
||||
},
|
||||
);
|
||||
const service = new VersionService(
|
||||
{ settingStore },
|
||||
{
|
||||
getLogger,
|
||||
versionCheck: { url: testurl, enable: false },
|
||||
version,
|
||||
enterpriseVersion,
|
||||
},
|
||||
);
|
||||
await service.checkLatestVersion();
|
||||
t.false(fetchMock.called(testurl));
|
||||
const versionInfo = service.getVersionInfo();
|
||||
t.is(versionInfo.current.oss, version);
|
||||
t.is(versionInfo.current.enterprise, enterpriseVersion);
|
||||
t.falsy(versionInfo.latest.oss, latest.oss);
|
||||
t.falsy(versionInfo.latest.enterprise, latest.enterprise);
|
||||
},
|
||||
);
|
19
src/migrations/20210218090213-generate-server-identifier.js
Normal file
19
src/migrations/20210218090213-generate-server-identifier.js
Normal file
@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
exports.up = function(db, cb) {
|
||||
db.runSql(
|
||||
`
|
||||
INSERT INTO settings(name, content) VALUES ('instanceInfo', json_build_object('id', gen_random_uuid()));
|
||||
`,
|
||||
cb,
|
||||
);
|
||||
};
|
||||
|
||||
exports.down = function(db, cb) {
|
||||
db.runSql(
|
||||
`
|
||||
DROP FROM settings WHERE name = 'instanceInfo'
|
||||
`,
|
||||
cb,
|
||||
);
|
||||
};
|
20
src/test/fixtures/fake-setting-store.js
vendored
20
src/test/fixtures/fake-setting-store.js
vendored
@ -1,6 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = () => ({
|
||||
insert: () => Promise.resolve(),
|
||||
get: () => Promise.resolve(),
|
||||
});
|
||||
module.exports = () => {
|
||||
const _settings = [];
|
||||
return {
|
||||
insert: setting => {
|
||||
_settings.push(setting);
|
||||
return Promise.resolve();
|
||||
},
|
||||
get: name => {
|
||||
const setting = _settings.find(s => s.name === name);
|
||||
if (setting) {
|
||||
return Promise.resolve(setting.content);
|
||||
}
|
||||
return Promise.reject(new Error('Could not find setting'));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user