Skip to content

Commit d6f2245

Browse files
authored
Merge branch 'main' into fix/extract-to-parameter
2 parents acc1f22 + 2046b8a commit d6f2245

30 files changed

+2361
-42
lines changed

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export default tseslint.config([
2626
'**/*.md',
2727
'src/services/guard/assets/**',
2828
'sbom/',
29+
'vendor/',
2930
]),
3031
eslint.configs.recommended,
3132
...tseslint.configs.recommendedTypeChecked,

package-lock.json

Lines changed: 7 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"bundle:beta": "rm -rf out && webpack --env mode=production --env env=beta",
3838
"bundle:prod": "rm -rf out && webpack --env mode=production --env env=prod",
3939
"download-wheels": "tsx tools/download-wheels.ts",
40+
"build-cfn-guard": "tsx tools/build-cfn-guard.ts",
4041
"benchmark": "cross-env NODE_ENV=test AWS_ENV=alpha node --max-old-space-size=16384 --expose-gc -r ts-node/register tools/benchmark.ts",
4142
"generate-metrics": "cross-env NODE_ENV=development AWS_ENV=alpha node --max-old-space-size=16384 -r ts-node/register tools/telemetry-generator.ts",
4243
"debug-tree": "node -r ts-node/register tools/debug_tree.ts",
@@ -74,7 +75,7 @@
7475
"archiver": "7.0.1",
7576
"async-mutex": "0.5.0",
7677
"axios": "1.11.0",
77-
"cfn-guard": "https://gitpkg.now.sh/aws-cloudformation/cloudformation-guard/guard/ts-lib?33d9931",
78+
"cfn-guard": "file:./vendor/cfn-guard",
7879
"deep-object-diff": "1.1.9",
7980
"fast-deep-equal": "3.1.3",
8081
"fuse.js": "7.1.0",
@@ -151,7 +152,6 @@
151152
"@opentelemetry/sdk-trace-base",
152153
"@opentelemetry/sdk-trace-node",
153154
"@tree-sitter-grammars/tree-sitter-yaml",
154-
"cfn-guard",
155155
"lmdb",
156156
"pino",
157157
"pino-pretty",

src/server/CfnExternal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export class CfnExternal implements Configurables, Closeable {
6363
overrides.guardService ??
6464
new GuardService(core.documentManager, core.diagnosticCoordinator, core.syntaxTreeManager);
6565

66-
this.onlineStatus = overrides.onlineStatus ?? new OnlineStatus(core.clientMessage);
66+
this.onlineStatus = overrides.onlineStatus ?? new OnlineStatus();
6767
this.featureFlags = overrides.featureFlags ?? new FeatureFlagProvider(getFromGitHub);
6868
this.onlineFeatureGuard = overrides.onlineFeatureGuard ?? new OnlineFeatureGuard(core.awsCredentials);
6969
}

src/services/OnlineStatus.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { lookup } from 'node:dns/promises';
2-
import { MessageType } from 'vscode-languageserver-protocol';
3-
import { ClientMessage } from '../telemetry/ClientMessage';
2+
import { LoggerFactory } from '../telemetry/LoggerFactory';
43
import { Closeable } from '../utils/Closeable';
54

5+
const logger = LoggerFactory.getLogger('OnlineStatus');
6+
67
export class OnlineStatus implements Closeable {
78
private _isOnline: boolean = false;
89
private notifiedOnce: boolean = false;
910
private readonly timeout: NodeJS.Timeout;
1011

11-
constructor(private readonly clientMessage: ClientMessage) {
12+
constructor() {
1213
void this.hasInternet();
1314

1415
this.timeout = setInterval(
@@ -26,21 +27,14 @@ export class OnlineStatus implements Closeable {
2627
} catch {
2728
this._isOnline = false;
2829
} finally {
29-
await this.notify();
30+
this.notify();
3031
}
3132
}
3233

33-
private async notify() {
34+
private notify() {
3435
if (!this.notifiedOnce && !this._isOnline) {
35-
try {
36-
await this.clientMessage.showMessageNotification(
37-
MessageType.Warning,
38-
'Internet connection lost. Some AWS CloudFormation features may not work properly.',
39-
);
40-
this.notifiedOnce = true;
41-
} catch {
42-
// Nothing to do here
43-
}
36+
logger.warn('Internet connection lost. Some AWS CloudFormation features may not work properly.');
37+
this.notifiedOnce = true;
4438
}
4539
}
4640

src/services/cfnLint/CfnLintService.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class CfnLintService implements SettingsConfigurable, Closeable {
8383
) {
8484
this.settings = DefaultSettings.diagnostics.cfnLint;
8585
this.delayer = delayer ?? new Delayer<void>(this.settings.delayMs);
86-
this.workerManager = workerManager ?? new PyodideWorkerManager(this.settings.initialization);
86+
this.workerManager = workerManager ?? new PyodideWorkerManager(this.settings.initialization, this.settings);
8787
}
8888

8989
configure(settingsManager: ISettingsSubscriber): void {
@@ -103,6 +103,7 @@ export class CfnLintService implements SettingsConfigurable, Closeable {
103103

104104
private onSettingsChanged(newSettings: CfnLintSettings): void {
105105
this.settings = newSettings;
106+
this.workerManager.updateSettings(newSettings);
106107
// Note: Delayer delay is immutable, set at construction time
107108
// The new delayMs will be used for future operations that check this.settings.delayMs
108109
}

src/services/cfnLint/PyodideWorkerManager.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import path from 'path';
22
import { Worker } from 'worker_threads';
33
import { PublishDiagnosticsParams } from 'vscode-languageserver';
44
import { CloudFormationFileType } from '../../document/Document';
5-
import { CfnLintInitializationSettings } from '../../settings/Settings';
5+
import { CfnLintInitializationSettings, CfnLintSettings } from '../../settings/Settings';
66
import { LoggerFactory } from '../../telemetry/LoggerFactory';
77
import { retryWithExponentialBackoff } from '../../utils/Retry';
88
import { WorkerNotInitializedError } from './CfnLintErrors';
@@ -33,6 +33,7 @@ export class PyodideWorkerManager {
3333

3434
constructor(
3535
private readonly retryConfig: CfnLintInitializationSettings,
36+
private cfnLintSettings: CfnLintSettings,
3637
private readonly log = LoggerFactory.getLogger(PyodideWorkerManager),
3738
) {}
3839

@@ -169,15 +170,29 @@ export class PyodideWorkerManager {
169170
uri: string,
170171
fileType: CloudFormationFileType,
171172
): Promise<PublishDiagnosticsParams[]> {
172-
return await this.executeTask<PublishDiagnosticsParams[]>('lint', { content, uri, fileType });
173+
return await this.executeTask<PublishDiagnosticsParams[]>('lint', {
174+
content,
175+
uri,
176+
fileType,
177+
settings: this.cfnLintSettings,
178+
});
173179
}
174180

175181
public async lintFile(
176182
path: string,
177183
uri: string,
178184
fileType: CloudFormationFileType,
179185
): Promise<PublishDiagnosticsParams[]> {
180-
return await this.executeTask<PublishDiagnosticsParams[]>('lintFile', { path, uri, fileType });
186+
return await this.executeTask<PublishDiagnosticsParams[]>('lintFile', {
187+
path,
188+
uri,
189+
fileType,
190+
settings: this.cfnLintSettings,
191+
});
192+
}
193+
194+
public updateSettings(settings: CfnLintSettings): void {
195+
this.cfnLintSettings = settings;
181196
}
182197

183198
public async mountFolder(fsDir: string, mountDir: string): Promise<void> {

src/services/cfnLint/pyodide-worker.ts

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ if (parentPort) {
5454
payload.content as string,
5555
payload.uri as string,
5656
payload.fileType as CloudFormationFileType,
57+
payload.settings as Record<string, unknown>,
5758
);
5859
break;
5960
}
@@ -62,6 +63,7 @@ if (parentPort) {
6263
payload.path as string,
6364
payload.uri as string,
6465
payload.fileType as CloudFormationFileType,
66+
payload.settings as Record<string, unknown>,
6567
);
6668
break;
6769
}
@@ -247,30 +249,71 @@ async function initializePyodide(): Promise<InitializeResult> {
247249
248250
return results
249251
250-
def lint_str(template_str, uri, template_args=None):
252+
def parse_cfn_lint_settings(settings):
253+
"""Parse cfn-lint settings into ManualArgs format"""
254+
config = {}
255+
if not settings:
256+
return config
257+
258+
if settings.get('ignoreChecks'):
259+
config['ignore_checks'] = settings['ignoreChecks']
260+
if settings.get('includeChecks'):
261+
config['include_checks'] = settings['includeChecks']
262+
if settings.get('mandatoryChecks'):
263+
config['mandatory_checks'] = settings['mandatoryChecks']
264+
if settings.get('includeExperimental'):
265+
config['include_experimental'] = settings['includeExperimental']
266+
if settings.get('configureRules'):
267+
# Parse configure rules from string format "RuleId:key=value"
268+
configure_rules = {}
269+
for rule_config in settings['configureRules']:
270+
if ':' in rule_config:
271+
rule_id, config_str = rule_config.split(':', 1)
272+
if '=' in config_str:
273+
key, value = config_str.split('=', 1)
274+
if rule_id not in configure_rules:
275+
configure_rules[rule_id] = {}
276+
# Convert string values to appropriate types
277+
if value.lower() == 'true':
278+
configure_rules[rule_id][key] = True
279+
elif value.lower() == 'false':
280+
configure_rules[rule_id][key] = False
281+
else:
282+
try:
283+
configure_rules[rule_id][key] = int(value)
284+
except ValueError:
285+
configure_rules[rule_id][key] = value
286+
if configure_rules:
287+
config['configure_rules'] = configure_rules
288+
if settings.get('regions'):
289+
config['regions'] = settings['regions']
290+
return config
291+
292+
def lint_str(template_str, uri, settings=None):
251293
"""
252294
Lint a CloudFormation template string and return LSP diagnostics
253295
254296
Args:
255297
template_str (str): CloudFormation template as a string
256298
uri (str): Document URI
257-
template_args (dict, optional): Additional template arguments
299+
settings (dict, optional): cfn-lint settings
258300
259301
Returns:
260302
dict: LSP PublishDiagnosticsParams
261303
"""
304+
config = parse_cfn_lint_settings(settings)
305+
return match_to_diagnostics(lint(template_str, config=ManualArgs(**config) if config else None), uri)
262306
263-
return match_to_diagnostics(lint(template_str), uri)
264-
265-
def lint_uri(lint_path, uri, lint_type, template_args=None):
266-
args = ManualArgs()
307+
def lint_uri(lint_path, uri, lint_type, settings=None):
308+
config = parse_cfn_lint_settings(settings)
267309
path = Path(lint_path)
310+
268311
if lint_type == "template":
269-
args["templates"] = [str(path)]
312+
config["templates"] = [str(path)]
270313
elif lint_type == "gitsync-deployment":
271-
args["deployment_files"] = [str(path)]
314+
config["deployment_files"] = [str(path)]
272315
273-
return match_to_diagnostics(lint_by_config(args), uri)
316+
return match_to_diagnostics(lint_by_config(ManualArgs(**config)), uri)
274317
`);
275318

276319
// Create result object first
@@ -309,6 +352,7 @@ async function lintTemplate(
309352
content: string,
310353
uri: string,
311354
_fileType: CloudFormationFileType,
355+
settings?: Record<string, unknown>,
312356
): Promise<PublishDiagnosticsParams[]> {
313357
if (!initialized || !pyodide) {
314358
throw new Error('Pyodide not initialized');
@@ -319,9 +363,11 @@ async function lintTemplate(
319363
const pyUri = pyodide.toPy(uri);
320364
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
321365
const pyContent = pyodide.toPy(content.replaceAll('"""', '\\"\\"\\"'));
366+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
367+
const pySettings = pyodide.toPy(settings ?? {});
322368

323369
// Execute Python code and get result
324-
const pythonCode = `lint_str(r"""${pyContent}""", r"""${pyUri}""")`;
370+
const pythonCode = `lint_str(r"""${pyContent}""", r"""${pyUri}""", ${pySettings})`;
325371
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
326372
const result = await pyodide.runPythonAsync(pythonCode);
327373

@@ -333,13 +379,17 @@ async function lintFile(
333379
path: string,
334380
uri: string,
335381
fileType: CloudFormationFileType,
382+
settings?: Record<string, unknown>,
336383
): Promise<PublishDiagnosticsParams[]> {
337384
if (!initialized || !pyodide) {
338385
throw new Error('Pyodide not initialized');
339386
}
340387

388+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
389+
const pySettings = pyodide.toPy(settings ?? {});
390+
341391
// Execute Python code and get result
342-
const pythonCode = `lint_uri(r"""${path}""", r"""${uri}""", r"""${fileType}""")`;
392+
const pythonCode = `lint_uri(r"""${path}""", r"""${uri}""", r"""${fileType}""", ${pySettings})`;
343393
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
344394
const result = await pyodide.runPythonAsync(pythonCode);
345395

src/settings/Settings.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ export type CfnLintSettings = Toggleable<{
2929
delayMs: number;
3030
lintOnChange: boolean;
3131
initialization: CfnLintInitializationSettings;
32+
ignoreChecks: readonly string[];
33+
includeChecks: readonly string[];
34+
mandatoryChecks: readonly string[];
35+
includeExperimental: boolean;
36+
configureRules: readonly string[];
37+
regions: readonly string[];
38+
customRules: readonly string[];
39+
appendRules: readonly string[];
40+
overrideSpec: string;
41+
registrySchemas: readonly string[];
3242
}>;
3343

3444
export type GuardSettings = Toggleable<{
@@ -87,6 +97,16 @@ export const DefaultSettings: DeepReadonly<Settings> = {
8797
backoffMultiplier: 2,
8898
totalTimeoutMs: 120_000, // 2 minutes total timeout
8999
},
100+
ignoreChecks: [],
101+
includeChecks: [],
102+
mandatoryChecks: [],
103+
includeExperimental: false,
104+
configureRules: [],
105+
regions: [],
106+
customRules: [],
107+
appendRules: [],
108+
overrideSpec: '',
109+
registrySchemas: [],
90110
},
91111
cfnGuard: {
92112
enabled: true,

0 commit comments

Comments
 (0)