1
0
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:
Christopher Kolstad 2021-02-19 11:13:25 +01:00 committed by GitHub
parent 4902161b39
commit b83387a84a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 246 additions and 8 deletions

View File

@ -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

View File

@ -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`

View File

@ -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: {},

View File

@ -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);
}
}
}

View File

@ -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,
};
};

View 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;

View 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);
},
);

View 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,
);
};

View File

@ -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'));
},
};
};