Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/handlers/SystemHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { RequestHandler } from 'vscode-languageserver';
import { GetSystemStatusResponse } from '../protocol/LspSystemHandlers';
import { ServerComponents } from '../server/ServerComponents';
import { handleLspError } from '../utils/Errors';

export function getSystemStatusHandler(
components: ServerComponents,
): RequestHandler<void, GetSystemStatusResponse, void> {
return (): GetSystemStatusResponse => {
try {
return {
settingsReady: components.settingsManager.isReady(),
schemasReady: components.schemaReadiness.isReady(),
cfnLintReady: components.cfnLintService.isReady(),
cfnGuardReady: components.guardService.isReady(),
currentSettings: components.settingsManager.getCurrentSettings(),
};
} catch (error) {
handleLspError(error, 'Failed to get system status');
}
};
}
2 changes: 2 additions & 0 deletions src/protocol/LspComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { LspRelatedResourcesHandlers } from './LspRelatedResourcesHandlers';
import { LspResourceHandlers } from './LspResourceHandlers';
import { LspS3Handlers } from './LspS3Handlers';
import { LspStackHandlers } from './LspStackHandlers';
import { LspSystemHandlers } from './LspSystemHandlers';
import { LspWorkspace } from './LspWorkspace';

export class LspComponents {
Expand All @@ -23,5 +24,6 @@ export class LspComponents {
public readonly resourceHandlers: LspResourceHandlers,
public readonly relatedResourcesHandlers: LspRelatedResourcesHandlers,
public readonly s3Handlers: LspS3Handlers,
public readonly systemHandlers: LspSystemHandlers,
) {}
}
4 changes: 4 additions & 0 deletions src/protocol/LspConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { LspRelatedResourcesHandlers } from './LspRelatedResourcesHandlers';
import { LspResourceHandlers } from './LspResourceHandlers';
import { LspS3Handlers } from './LspS3Handlers';
import { LspStackHandlers } from './LspStackHandlers';
import { LspSystemHandlers } from './LspSystemHandlers';
import { LspWorkspace } from './LspWorkspace';

type LspConnectionHandlers = {
Expand All @@ -34,6 +35,7 @@ export class LspConnection {
private readonly resourceHandlers: LspResourceHandlers;
private readonly relatedResourcesHandlers: LspRelatedResourcesHandlers;
private readonly s3Handlers: LspS3Handlers;
private readonly systemHandlers: LspSystemHandlers;

private initializeParams?: InitializeParams;

Expand All @@ -59,6 +61,7 @@ export class LspConnection {
this.resourceHandlers = new LspResourceHandlers(this.connection);
this.relatedResourcesHandlers = new LspRelatedResourcesHandlers(this.connection);
this.s3Handlers = new LspS3Handlers(this.connection);
this.systemHandlers = new LspSystemHandlers(this.connection);

this.communication.console.info(`${ExtensionName} launched from ${__dirname}`);

Expand Down Expand Up @@ -94,6 +97,7 @@ export class LspConnection {
this.resourceHandlers,
this.relatedResourcesHandlers,
this.s3Handlers,
this.systemHandlers,
);
}

Expand Down
21 changes: 21 additions & 0 deletions src/protocol/LspSystemHandlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Connection, RequestHandler, RequestType } from 'vscode-languageserver';
import { Settings } from '../settings/Settings';
import { ReadinessStatus } from '../utils/ReadinessContributor';

export type GetSystemStatusResponse = {
settingsReady: ReadinessStatus;
schemasReady: ReadinessStatus;
cfnLintReady: ReadinessStatus;
cfnGuardReady: ReadinessStatus;
currentSettings: Settings;
};

export const GetSystemStatusRequestType = new RequestType<void, GetSystemStatusResponse, void>('aws/system/status');

export class LspSystemHandlers {
constructor(private readonly connection: Connection) {}

onGetSystemStatus(handler: RequestHandler<void, GetSystemStatusResponse, void>) {
this.connection.onRequest(GetSystemStatusRequestType.method, handler);
}
}
26 changes: 26 additions & 0 deletions src/schema/SchemaReadiness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { SettingsConfigurable, ISettingsSubscriber, SettingsSubscription } from '../settings/ISettingsSubscriber';
import { DefaultSettings, ProfileSettings } from '../settings/Settings';
import { ReadinessContributor, ReadinessStatus } from '../utils/ReadinessContributor';
import { SchemaStore } from './SchemaStore';

export class SchemaReadiness implements ReadinessContributor, SettingsConfigurable {
private settings: ProfileSettings = DefaultSettings.profile;
private settingsSubscription?: SettingsSubscription;

constructor(private readonly schemaStore: SchemaStore) {}

isReady(): ReadinessStatus {
return { ready: this.schemaStore.getPublicSchemaRegions().includes(this.settings.region) };
}

configure(settingsManager: ISettingsSubscriber): void {
// Clean up existing subscription if present
if (this.settingsSubscription) {
this.settingsSubscription.unsubscribe();
}

this.settingsSubscription = settingsManager.subscribe('profile', (newSettings) => {
this.settings = newSettings;
});
}
}
5 changes: 4 additions & 1 deletion src/server/CfnExternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FeatureFlagProvider, getFromGitHub } from '../featureFlag/FeatureFlagPr
import { LspComponents } from '../protocol/LspComponents';
import { getSamSchemas } from '../schema/GetSamSchemaTask';
import { getRemotePrivateSchemas, getRemotePublicSchemas } from '../schema/GetSchemaTask';
import { SchemaReadiness } from '../schema/SchemaReadiness';
import { SchemaRetriever } from '../schema/SchemaRetriever';
import { SchemaStore } from '../schema/SchemaStore';
import { AwsClient } from '../services/AwsClient';
Expand Down Expand Up @@ -31,6 +32,7 @@ export class CfnExternal implements Configurables, Closeable {

readonly schemaStore: SchemaStore;
readonly schemaRetriever: SchemaRetriever;
readonly schemaReadiness: SchemaReadiness;

readonly cfnLintService: CfnLintService;
readonly guardService: GuardService;
Expand Down Expand Up @@ -59,6 +61,7 @@ export class CfnExternal implements Configurables, Closeable {
undefined,
validatePositiveOrUndefined(core.awsMetadata?.schema?.staleDaysThreshold),
);
this.schemaReadiness = overrides.schemaReadiness ?? new SchemaReadiness(this.schemaStore);

this.cfnLintService =
overrides.cfnLintService ??
Expand All @@ -80,7 +83,7 @@ export class CfnExternal implements Configurables, Closeable {
}

configurables(): Configurable[] {
return [this.schemaRetriever, this.cfnLintService, this.guardService];
return [this.schemaRetriever, this.schemaReadiness, this.cfnLintService, this.guardService];
}

async close() {
Expand Down
5 changes: 5 additions & 0 deletions src/server/CfnServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
describeChangeSetHandler,
describeEventsHandler,
} from '../handlers/StackHandler';
import { getSystemStatusHandler } from '../handlers/SystemHandler';
import { LspComponents } from '../protocol/LspComponents';
import { LoggerFactory } from '../telemetry/LoggerFactory';
import { withTelemetryContext } from '../telemetry/TelemetryContext';
Expand Down Expand Up @@ -115,6 +116,10 @@ export class CfnServer {
);
this.lsp.handlers.onCodeLens(withTelemetryContext('CodeLens', codeLensHandler(this.components)));

this.lsp.systemHandlers.onGetSystemStatus(
withTelemetryContext('SystemStatus', getSystemStatusHandler(this.components)),
);

this.lsp.authHandlers.onIamCredentialsUpdate(
withTelemetryContext('Auth.Update', iamCredentialsUpdateHandler(this.components)),
);
Expand Down
10 changes: 9 additions & 1 deletion src/services/cfnLint/CfnLintService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Count, Telemetry } from '../../telemetry/TelemetryDecorator';
import { Closeable } from '../../utils/Closeable';
import { CancellationError, Delayer } from '../../utils/Delayer';
import { extractErrorMessage } from '../../utils/Errors';
import { ReadinessContributor, ReadinessStatus } from '../../utils/ReadinessContributor';
import { byteSize } from '../../utils/String';
import { DiagnosticCoordinator } from '../DiagnosticCoordinator';
import { WorkerNotInitializedError, MountError } from './CfnLintErrors';
Expand Down Expand Up @@ -44,7 +45,7 @@ export function sleep(ms: number): Promise<void> {
});
}

export class CfnLintService implements SettingsConfigurable, Closeable {
export class CfnLintService implements SettingsConfigurable, Closeable, ReadinessContributor {
private static readonly CFN_LINT_SOURCE = 'cfn-lint';

private status: STATUS = STATUS.Uninitialized;
Expand Down Expand Up @@ -128,6 +129,13 @@ export class CfnLintService implements SettingsConfigurable, Closeable {
});
}

isReady(): ReadinessStatus {
if (!this.settings.enabled) {
return { ready: true };
}
return { ready: this.status === STATUS.Initialized };
}

private onSettingsChanged(newSettings: CfnLintSettings): void {
this.settings = newSettings;
this.workerManager.updateSettings(newSettings);
Expand Down
20 changes: 18 additions & 2 deletions src/services/guard/GuardService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Closeable } from '../../utils/Closeable';
import { CancellationError, Delayer } from '../../utils/Delayer';
import { extractErrorMessage } from '../../utils/Errors';
import { readFileIfExistsAsync } from '../../utils/File';
import { ReadinessContributor, ReadinessStatus } from '../../utils/ReadinessContributor';
import { byteSize } from '../../utils/String';
import { DiagnosticCoordinator } from '../DiagnosticCoordinator';
import { getRulesForPack, getAvailableRulePacks, GuardRuleData } from './GeneratedGuardRules';
Expand Down Expand Up @@ -41,7 +42,7 @@ interface ValidationQueueEntry {
reject: (error: Error) => void;
}

export class GuardService implements SettingsConfigurable, Closeable {
export class GuardService implements SettingsConfigurable, Closeable, ReadinessContributor {
private static readonly CFN_GUARD_SOURCE = 'cfn-guard';

private settings: GuardSettings;
Expand All @@ -60,6 +61,9 @@ export class GuardService implements SettingsConfigurable, Closeable {
// Cache loaded rules
private enabledRules: GuardRule[] = [];

// Track async rule loading state
private isLoadingRules = false;

// Validation queuing for concurrent requests
private readonly validationQueue: ValidationQueueEntry[] = [];
private readonly activeValidations = new Map<string, Promise<GuardViolation[]>>();
Expand Down Expand Up @@ -93,6 +97,13 @@ export class GuardService implements SettingsConfigurable, Closeable {
});
}

public isReady(): ReadinessStatus {
if (!this.settings.enabled) {
return { ready: true };
}
return { ready: !this.isLoadingRules && this.enabledRules.length > 0 };
}

/**
* Configure the GuardService with settings manager
* Sets up subscription to diagnostics settings changes
Expand Down Expand Up @@ -138,13 +149,18 @@ export class GuardService implements SettingsConfigurable, Closeable {
// Clear maps only when rule configuration actually changes
this.ruleToPacksMap.clear();
this.ruleCustomMessages.clear();
// Preload rules with new settings

// Track async rule loading
this.isLoadingRules = true;
this.getEnabledRulesByConfiguration()
.then((rules) => {
this.enabledRules = rules;
})
.catch((error) => {
this.log.error(`Failed to preload rules after settings change: ${extractErrorMessage(error)}`);
})
.finally(() => {
this.isLoadingRules = false;
});
this.revalidateAllDocuments();
}
Expand Down
23 changes: 19 additions & 4 deletions src/settings/SettingsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { LspWorkspace } from '../protocol/LspWorkspace';
import { LoggerFactory } from '../telemetry/LoggerFactory';
import { ScopedTelemetry } from '../telemetry/ScopedTelemetry';
import { Measure, Telemetry } from '../telemetry/TelemetryDecorator';
import { ReadinessContributor, ReadinessStatus } from '../utils/ReadinessContributor';
import { AwsRegion } from '../utils/Region';
import { toString } from '../utils/String';
import { PartialDataObserver, SubscriptionManager } from '../utils/SubscriptionManager';
Expand All @@ -14,10 +15,11 @@ import { parseSettings } from './SettingsParser';

const logger = LoggerFactory.getLogger('SettingsManager');

export class SettingsManager implements ISettingsSubscriber {
export class SettingsManager implements ISettingsSubscriber, ReadinessContributor {
@Telemetry() private readonly telemetry!: ScopedTelemetry;
private readonly settingsState = new SettingsState();
private readonly subscriptionManager = new SubscriptionManager<Settings>();
private settingsReady = false;

constructor(
private readonly workspace: LspWorkspace,
Expand All @@ -33,6 +35,10 @@ export class SettingsManager implements ISettingsSubscriber {
return this.settingsState.toSettings();
}

isReady(): ReadinessStatus {
return { ready: this.settingsReady };
}

reset() {
this.validateAndUpdate(DefaultSettings);
}
Expand Down Expand Up @@ -74,6 +80,7 @@ export class SettingsManager implements ISettingsSubscriber {

const settings = parseWithPrettyError(parseSettings, mergedConfig, this.getCurrentSettings());
this.validateAndUpdate(settings);
this.settingsReady = true;
} catch (error) {
logger.error(error, `Failed to sync configuration, keeping previous settings`);
}
Expand Down Expand Up @@ -115,6 +122,8 @@ export class SettingsManager implements ISettingsSubscriber {
*/
@Measure({ name: 'settingsUpdate', captureErrorAttributes: true })
private validateAndUpdate(newSettings: Settings): void {
this.settingsReady = false;

const oldSettings = this.settingsState.toSettings();

newSettings.diagnostics.cfnLint.initialization.maxDelayMs = clipNumber(
Expand Down Expand Up @@ -158,10 +167,16 @@ export class SettingsManager implements ISettingsSubscriber {
const hasChanged = Object.keys(difference).length > 0;

if (hasChanged) {
this.settingsState.update(newSettings);
logger.info(`Settings updated: ${toString(difference)}`);
this.subscriptionManager.notify(newSettings, oldSettings);
try {
this.settingsState.update(newSettings);
logger.info(`Settings updated: ${toString(difference)}`);
this.subscriptionManager.notify(newSettings, oldSettings);
} catch (error) {
logger.error(error, 'Failed to update settings');
}
}

this.settingsReady = true;
}

private registerSettingsGauges(): void {
Expand Down
7 changes: 7 additions & 0 deletions src/utils/ReadinessContributor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type ReadinessStatus = {
readonly ready: boolean;
};

export interface ReadinessContributor {
isReady(): ReadinessStatus;
}
2 changes: 2 additions & 0 deletions tools/telemetry-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ import { LspStackHandlers } from '../src/protocol/LspStackHandlers';
import { LspResourceHandlers } from '../src/protocol/LspResourceHandlers';
import { LspRelatedResourcesHandlers } from '../src/protocol/LspRelatedResourcesHandlers';
import { LspS3Handlers } from '../src/protocol/LspS3Handlers';
import { LspSystemHandlers } from '../src/protocol/LspSystemHandlers';
import { RelationshipSchemaService } from '../src/services/RelationshipSchemaService';
import { LspCfnEnvironmentHandlers } from '../src/protocol/LspCfnEnvironmentHandlers';
import { FeatureFlagProvider, getFromGitHub } from '../src/featureFlag/FeatureFlagProvider';
Expand Down Expand Up @@ -189,6 +190,7 @@ function main() {
stubInterface<LspResourceHandlers>(),
stubInterface<LspRelatedResourcesHandlers>(),
stubInterface<LspS3Handlers>(),
stubInterface<LspSystemHandlers>(),
);

const dataStoreFactory = new MultiDataStoreFactoryProvider();
Expand Down
Loading
Loading