mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2026-07-20 11:50:53 +02:00
feat(core): Make detaching floatable entitlements on shutdown configurable (#14266)
Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
This commit is contained in:
committed by
GitHub
parent
39e2d35a71
commit
c9565fc0be
@@ -99,7 +99,7 @@
|
||||
"@n8n/task-runner": "workspace:*",
|
||||
"@n8n/typeorm": "0.3.20-12",
|
||||
"@n8n_io/ai-assistant-sdk": "1.13.0",
|
||||
"@n8n_io/license-sdk": "2.17.0",
|
||||
"@n8n_io/license-sdk": "2.19.0",
|
||||
"@oclif/core": "4.0.7",
|
||||
"@rudderstack/rudder-sdk-node": "2.0.9",
|
||||
"@sentry/node": "catalog:",
|
||||
|
||||
@@ -25,7 +25,7 @@ function makeDateWithHourOffset(offsetInHours: number): Date {
|
||||
const licenseConfig: GlobalConfig['license'] = {
|
||||
serverUrl: MOCK_SERVER_URL,
|
||||
autoRenewalEnabled: true,
|
||||
autoRenewOffset: MOCK_RENEW_OFFSET,
|
||||
detachFloatingOnShutdown: true,
|
||||
activationKey: MOCK_ACTIVATION_KEY,
|
||||
tenantId: 1,
|
||||
cert: '',
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
import { SETTINGS_LICENSE_CERT_KEY } from '@/constants';
|
||||
import { SettingsRepository } from '@/databases/repositories/settings.repository';
|
||||
import { License } from '@/license';
|
||||
|
||||
import { BaseCommand } from '../base-command';
|
||||
|
||||
export class ClearLicenseCommand extends BaseCommand {
|
||||
static description = 'Clear license';
|
||||
static description = 'Clear local license certificate';
|
||||
|
||||
static examples = ['$ n8n license:clear'];
|
||||
|
||||
@@ -17,20 +15,12 @@ export class ClearLicenseCommand extends BaseCommand {
|
||||
// Attempt to invoke shutdown() to force any floating entitlements to be released
|
||||
const license = Container.get(License);
|
||||
await license.init({ isCli: true });
|
||||
try {
|
||||
await license.shutdown();
|
||||
} catch {
|
||||
this.logger.info('License shutdown failed. Continuing with clearing license from database.');
|
||||
}
|
||||
|
||||
await Container.get(SettingsRepository).delete({
|
||||
key: SETTINGS_LICENSE_CERT_KEY,
|
||||
});
|
||||
await license.clear();
|
||||
this.logger.info('Done. Restart n8n to take effect.');
|
||||
}
|
||||
|
||||
async catch(error: Error) {
|
||||
this.logger.error('Error updating database. See log messages for details.');
|
||||
this.logger.error('Error. See log messages for details.');
|
||||
this.logger.error('\nGOT ERROR');
|
||||
this.logger.info('====================================');
|
||||
this.logger.error(error.message);
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
LICENSE_QUOTAS,
|
||||
N8N_VERSION,
|
||||
SETTINGS_LICENSE_CERT_KEY,
|
||||
Time,
|
||||
UNLIMITED_LICENSE_QUOTA,
|
||||
} from './constants';
|
||||
import type { BooleanLicenseFeature, NumericLicenseFeature } from './interfaces';
|
||||
@@ -60,7 +61,7 @@ export class License {
|
||||
const isMainInstance = instanceType === 'main';
|
||||
const server = this.globalConfig.license.serverUrl;
|
||||
const offlineMode = !isMainInstance;
|
||||
const autoRenewOffset = this.globalConfig.license.autoRenewOffset;
|
||||
const autoRenewOffset = 72 * Time.hours.toSeconds;
|
||||
const saveCertStr = isMainInstance
|
||||
? async (value: TLicenseBlock) => await this.saveCertStr(value)
|
||||
: async () => {};
|
||||
@@ -92,6 +93,7 @@ export class License {
|
||||
autoRenewEnabled: shouldRenew,
|
||||
renewOnInit: shouldRenew,
|
||||
autoRenewOffset,
|
||||
detachFloatingOnShutdown: this.globalConfig.license.detachFloatingOnShutdown,
|
||||
offlineMode,
|
||||
logger: this.logger,
|
||||
loadCertStr: async () => await this.loadCertStr(),
|
||||
@@ -194,6 +196,15 @@ export class License {
|
||||
this.logger.debug('License renewed');
|
||||
}
|
||||
|
||||
async clear() {
|
||||
if (!this.manager) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.manager.clear();
|
||||
this.logger.info('License cleared');
|
||||
}
|
||||
|
||||
@OnShutdown()
|
||||
async shutdown() {
|
||||
// Shut down License manager to unclaim any floating entitlements
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
import { ClearLicenseCommand } from '@/commands/license/clear';
|
||||
import { SETTINGS_LICENSE_CERT_KEY } from '@/constants';
|
||||
import { SettingsRepository } from '@/databases/repositories/settings.repository';
|
||||
import { License } from '@/license';
|
||||
import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
|
||||
import { setupTestCommand } from '@test-integration/utils/test-command';
|
||||
@@ -10,26 +8,30 @@ import { setupTestCommand } from '@test-integration/utils/test-command';
|
||||
import { mockInstance } from '../../shared/mocking';
|
||||
|
||||
mockInstance(LoadNodesAndCredentials);
|
||||
const license = mockInstance(License);
|
||||
const command = setupTestCommand(ClearLicenseCommand);
|
||||
|
||||
test('license:clear invokes shutdown() to release any floating entitlements', async () => {
|
||||
await command.run();
|
||||
test('license:clear invokes clear() to release any floating entitlements and deletes the license cert from the DB', async () => {
|
||||
const license = Container.get(License);
|
||||
|
||||
expect(license.init).toHaveBeenCalledTimes(1);
|
||||
expect(license.shutdown).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
const manager = {
|
||||
clear: jest.fn().mockImplementation(async () => {
|
||||
await license.saveCertStr('');
|
||||
}),
|
||||
};
|
||||
|
||||
test('license:clear deletes the license from the DB even if shutdown() fails', async () => {
|
||||
license.shutdown.mockRejectedValueOnce(new Error('shutdown failed'));
|
||||
|
||||
const settingsRepository = Container.get(SettingsRepository);
|
||||
|
||||
settingsRepository.delete = jest.fn();
|
||||
|
||||
await command.run();
|
||||
|
||||
expect(settingsRepository.delete).toHaveBeenCalledWith({
|
||||
key: SETTINGS_LICENSE_CERT_KEY,
|
||||
const initSpy = jest.spyOn(license, 'init').mockImplementation(async () => {
|
||||
Object.defineProperty(license, 'manager', {
|
||||
value: manager,
|
||||
writable: true,
|
||||
});
|
||||
});
|
||||
|
||||
const clearSpy = jest.spyOn(license, 'clear');
|
||||
const saveCertStrSpy = jest.spyOn(license, 'saveCertStr');
|
||||
|
||||
await command.run();
|
||||
|
||||
expect(initSpy).toHaveBeenCalledTimes(1);
|
||||
expect(clearSpy).toHaveBeenCalledTimes(1);
|
||||
expect(saveCertStrSpy).toHaveBeenCalledWith('');
|
||||
});
|
||||
|
||||
@@ -12,7 +12,6 @@ import type { SuperAgentTest } from './shared/types';
|
||||
import * as utils from './shared/utils/';
|
||||
|
||||
const MOCK_SERVER_URL = 'https://server.com/v1';
|
||||
const MOCK_RENEW_OFFSET = 259200;
|
||||
|
||||
let owner: User;
|
||||
let member: User;
|
||||
@@ -30,7 +29,6 @@ beforeAll(async () => {
|
||||
|
||||
config.set('license.serverUrl', MOCK_SERVER_URL);
|
||||
config.set('license.autoRenewEnabled', true);
|
||||
config.set('license.autoRenewOffset', MOCK_RENEW_OFFSET);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
||||
Reference in New Issue
Block a user