n8n
Version:
n8n Workflow Automation Tool
280 lines • 12.9 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExternalSecretsProviderConnectionManager = void 0;
const backend_common_1 = require("@n8n/backend-common");
const di_1 = require("@n8n/di");
const ensure_error_1 = require("@n8n/utils/errors/ensure-error");
const n8n_workflow_1 = require("n8n-workflow");
const provider_lifecycle_service_1 = require("./provider-lifecycle.service");
const provider_registry_service_1 = require("./provider-registry.service");
const retry_manager_service_1 = require("./retry-manager.service");
const secrets_cache_service_1 = require("./secrets-cache.service");
const COMPLETED_CONNECTION = {
completion: Promise.resolve(),
};
let ExternalSecretsProviderConnectionManager = class ExternalSecretsProviderConnectionManager {
constructor(logger, providerRegistry, providerLifecycle, retryManager, secretsCache) {
this.logger = logger;
this.providerRegistry = providerRegistry;
this.providerLifecycle = providerLifecycle;
this.retryManager = retryManager;
this.secretsCache = secretsCache;
this.replacementCandidates = new Map();
this.logger = this.logger.scoped('external-secrets');
}
async upsertProviderConnection(input) {
const { completion } = await this.prepareProviderConnection(input);
await completion;
}
async upsertProviderConnections(inputs) {
const completions = [];
for (const input of inputs) {
const { completion } = await this.prepareProviderConnection(input);
completions.push(completion);
}
await Promise.all(completions);
}
shutdown() {
this.replacementCandidates.clear();
this.retryManager.cancelAll();
void this.providerRegistry.disconnectAll();
}
async prepareProviderConnection({ providerKey, providerType, config, }) {
if (this.providerRegistry.has(providerKey) || this.replacementCandidates.has(providerKey)) {
await this.replaceProviderConnection(providerKey, providerType, config);
return COMPLETED_CONNECTION;
}
return await this.addProviderConnection(providerKey, providerType, config);
}
async removeProviderConnection(providerKey) {
this.invalidateReplacement(providerKey);
this.retryManager.cancelRetry(providerKey);
this.logger.debug('Removing external secrets provider connection', {
providerKey,
});
const existingProvider = this.providerRegistry.get(providerKey);
this.providerRegistry.remove(providerKey);
if (existingProvider) {
await this.providerLifecycle.disconnect(existingProvider);
}
}
async connectProviderWithRetry(providerKey) {
return await this.retryManager.runWithRetry(providerKey, async () => await this.connectProvider(providerKey));
}
async disconnectProvider(providerKey) {
this.invalidateReplacement(providerKey);
this.retryManager.cancelRetry(providerKey);
const provider = this.providerRegistry.get(providerKey);
this.providerRegistry.remove(providerKey);
if (provider) {
await this.providerLifecycle.disconnect(provider);
}
}
async addProviderConnection(providerKey, providerType, config) {
this.logger.debug('Adding external secrets provider connection', {
providerKey,
providerType,
});
const result = await this.providerLifecycle.initialize(providerType, config);
if (!result.success || !result.provider) {
this.logger.error('Failed to initialize external secrets provider connection', {
providerKey,
providerType,
error: result.error,
});
return COMPLETED_CONNECTION;
}
this.providerRegistry.set(providerKey, result.provider);
if (config.connected) {
await this.connectProviderWithRetry(providerKey);
}
return {
completion: this.secretsCache.refreshProvider(providerKey, result.provider),
};
}
async replaceProviderConnection(providerKey, providerType, config) {
const candidate = this.beginReplacement(providerKey, providerType);
const initResult = await this.providerLifecycle.initialize(providerType, config);
candidate.provider = initResult.provider;
if (!initResult.success || !initResult.provider) {
await this.settleReplacement(candidate, {
kind: 'failed',
phase: 'initialization',
provider: initResult.provider,
error: initResult.error ?? new n8n_workflow_1.UnexpectedError(`Failed to initialize provider ${providerKey}`),
});
return;
}
if (!config.connected) {
await this.settleReplacement(candidate, {
kind: 'ready',
phase: 'disconnected',
provider: initResult.provider,
});
return;
}
if (!this.isCurrentReplacement(candidate)) {
await this.disposeSupersededCandidate(candidate, 'initialization');
return;
}
await this.retryManager.runWithRetry(providerKey, async () => await this.connectReplacement(candidate));
}
beginReplacement(providerKey, providerType) {
const candidate = { providerKey, providerType };
this.replacementCandidates.set(providerKey, candidate);
this.retryManager.cancelRetry(providerKey);
this.logger.debug('External secrets provider replacement started', {
providerKey,
providerType,
phase: 'initialization',
});
return candidate;
}
async connectReplacement(candidate) {
const provider = candidate.provider;
if (!provider) {
return { success: true };
}
if (this.providerRegistry.get(candidate.providerKey) === provider) {
this.providerRegistry.remove(candidate.providerKey);
}
const connectResult = await this.providerLifecycle.connect(provider);
if (connectResult.success) {
this.startReplacementHydration(candidate, provider);
return { success: true };
}
const published = await this.publishRetryableConnectionFailure(candidate, connectResult.error ??
new n8n_workflow_1.UnexpectedError(`Failed to connect provider ${candidate.providerKey}`));
return published ? connectResult : { success: true };
}
startReplacementHydration(candidate, provider) {
void this.runReplacementHydration(candidate, provider).catch((error) => {
this.logger.error('Unexpected error settling external secrets provider replacement', {
providerKey: candidate.providerKey,
providerType: candidate.providerType,
phase: 'hydration-settlement',
error: (0, ensure_error_1.ensureError)(error),
});
});
}
async runReplacementHydration(candidate, provider) {
try {
await provider.update();
}
catch (error) {
await this.settleReplacement(candidate, {
kind: 'failed',
phase: 'hydration',
provider,
error: (0, ensure_error_1.ensureError)(error),
});
return;
}
await this.settleReplacement(candidate, {
kind: 'ready',
phase: 'hydration',
provider,
});
}
async settleReplacement(candidate, outcome) {
if (!this.isCurrentReplacement(candidate)) {
await this.disposeSupersededCandidate(candidate, outcome.phase);
return;
}
const existingProvider = this.providerRegistry.get(candidate.providerKey);
if (outcome.kind === 'ready') {
this.providerRegistry.set(candidate.providerKey, outcome.provider);
this.logger.debug('External secrets provider replacement activated', {
providerKey: candidate.providerKey,
providerType: candidate.providerType,
phase: outcome.phase,
});
}
else {
outcome.provider?.setState('error', outcome.error);
if (outcome.provider) {
this.providerRegistry.set(candidate.providerKey, outcome.provider);
}
else {
this.providerRegistry.remove(candidate.providerKey);
}
this.logger.error('External secrets provider replacement reached terminal failure', {
providerKey: candidate.providerKey,
providerType: candidate.providerType,
phase: outcome.phase,
error: outcome.error,
});
}
this.replacementCandidates.delete(candidate.providerKey);
if (existingProvider && existingProvider !== outcome.provider) {
await this.providerLifecycle.disconnect(existingProvider);
}
}
async publishRetryableConnectionFailure(candidate, error) {
if (!this.isCurrentReplacement(candidate) || !candidate.provider) {
await this.disposeSupersededCandidate(candidate, 'connection');
return false;
}
candidate.provider.setState('error', error);
this.logger.error('External secrets provider replacement connection attempt failed', {
providerKey: candidate.providerKey,
providerType: candidate.providerType,
phase: 'connection',
error,
});
const existingProvider = this.providerRegistry.get(candidate.providerKey);
const hasConnectedPredecessor = existingProvider !== undefined &&
existingProvider !== candidate.provider &&
existingProvider.state === 'connected';
if (hasConnectedPredecessor) {
return true;
}
this.providerRegistry.set(candidate.providerKey, candidate.provider);
if (existingProvider && existingProvider !== candidate.provider) {
await this.providerLifecycle.disconnect(existingProvider);
}
return true;
}
async disposeSupersededCandidate(candidate, phase) {
const { provider, providerKey, providerType } = candidate;
const shouldDispose = provider !== undefined && this.providerRegistry.get(providerKey) !== provider;
this.logger.debug('External secrets provider superseded replacement discarded', {
providerKey,
providerType,
phase,
disposed: shouldDispose,
});
if (shouldDispose) {
await this.providerLifecycle.disconnect(provider);
}
}
isCurrentReplacement(candidate) {
return this.replacementCandidates.get(candidate.providerKey) === candidate;
}
invalidateReplacement(providerKey) {
this.replacementCandidates.delete(providerKey);
}
async connectProvider(providerKey) {
const provider = this.providerRegistry.get(providerKey);
if (!provider) {
this.logger.warn(`Cannot connect provider ${providerKey}: not found in registry`);
throw new Error(`Provider ${providerKey} not found in registry`);
}
return await this.providerLifecycle.connect(provider);
}
};
exports.ExternalSecretsProviderConnectionManager = ExternalSecretsProviderConnectionManager;
exports.ExternalSecretsProviderConnectionManager = ExternalSecretsProviderConnectionManager = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [backend_common_1.Logger, provider_registry_service_1.ExternalSecretsProviderRegistry, provider_lifecycle_service_1.ExternalSecretsProviderLifecycle, retry_manager_service_1.ExternalSecretsRetryManager, secrets_cache_service_1.ExternalSecretsSecretsCache])
], ExternalSecretsProviderConnectionManager);
//# sourceMappingURL=external-secrets-provider-connection-manager.ee.js.map