Skip to content

Commit 8a2c43c

Browse files
committed
Add capabilities
1 parent 21789f4 commit 8a2c43c

File tree

11 files changed

+257
-57
lines changed

11 files changed

+257
-57
lines changed

src/handlers/TemplateHandler.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
import { ServerRequestHandler, ResponseError, ErrorCodes } from 'vscode-languageserver';
1+
import { ResponseError, ErrorCodes, RequestHandler } from 'vscode-languageserver';
22
import { TopLevelSection } from '../context/ContextType';
33
import { getEntityMap } from '../context/SectionContextBuilder';
44
import { Parameter } from '../context/semantic/Entity';
55
import { parseIdentifiable } from '../protocol/LspParser';
66
import { Identifiable } from '../protocol/LspTypes';
77
import { ServerComponents } from '../server/ServerComponents';
88
import { LoggerFactory } from '../telemetry/LoggerFactory';
9-
import { parseTemplateActionParams, parseGetParametersParams } from '../templates/TemplateParser';
9+
import { analyzeCapabilities } from '../templates/CapabilityAnalyzer';
10+
import { parseTemplateActionParams, parseTemplateMetadataParams } from '../templates/TemplateParser';
1011
import {
11-
GetParametersParams,
12+
TemplateMetadataParams,
1213
GetParametersResult,
14+
GetCapabilitiesResult,
1315
TemplateActionParams,
1416
TemplateActionResult,
1517
TemplateStatusResult,
@@ -21,12 +23,12 @@ const log = LoggerFactory.getLogger('TemplateHandler');
2123

2224
export function templateParametersHandler(
2325
components: ServerComponents,
24-
): ServerRequestHandler<GetParametersParams, GetParametersResult, never, void> {
25-
return (rawParams, _token, _workDoneProgress, _resultProgress) => {
26+
): RequestHandler<TemplateMetadataParams, GetParametersResult, void> {
27+
return (rawParams) => {
2628
log.debug({ Handler: 'TemplateParameters', rawParams });
2729

2830
try {
29-
const params = parseWithPrettyError(parseGetParametersParams, rawParams);
31+
const params = parseWithPrettyError(parseTemplateMetadataParams, rawParams);
3032
const syntaxTree = components.syntaxTreeManager.getSyntaxTree(params.uri);
3133
if (syntaxTree) {
3234
const parametersMap = getEntityMap(syntaxTree, TopLevelSection.Parameters);
@@ -49,7 +51,7 @@ export function templateParametersHandler(
4951

5052
export function templateValidationCreateHandler(
5153
components: ServerComponents,
52-
): ServerRequestHandler<TemplateActionParams, TemplateActionResult, never, void> {
54+
): RequestHandler<TemplateActionParams, TemplateActionResult, void> {
5355
return async (rawParams) => {
5456
log.debug({ Handler: 'TemplateValidationCreate', rawParams });
5557

@@ -64,7 +66,7 @@ export function templateValidationCreateHandler(
6466

6567
export function templateDeploymentCreateHandler(
6668
components: ServerComponents,
67-
): ServerRequestHandler<TemplateActionParams, TemplateActionResult, never, void> {
69+
): RequestHandler<TemplateActionParams, TemplateActionResult, void> {
6870
return async (rawParams) => {
6971
log.debug({ Handler: 'TemplateDeploymentCreate', rawParams });
7072

@@ -79,7 +81,7 @@ export function templateDeploymentCreateHandler(
7981

8082
export function templateValidationStatusHandler(
8183
components: ServerComponents,
82-
): ServerRequestHandler<Identifiable, TemplateStatusResult, never, void> {
84+
): RequestHandler<Identifiable, TemplateStatusResult, void> {
8385
return (rawParams) => {
8486
log.debug({ Handler: 'TemplateValidationStatus', rawParams });
8587

@@ -94,7 +96,7 @@ export function templateValidationStatusHandler(
9496

9597
export function templateDeploymentStatusHandler(
9698
components: ServerComponents,
97-
): ServerRequestHandler<Identifiable, TemplateStatusResult, never, void> {
99+
): RequestHandler<Identifiable, TemplateStatusResult, void> {
98100
return (rawParams) => {
99101
log.debug({ Handler: 'TemplateDeploymentStatus', rawParams });
100102

@@ -107,6 +109,28 @@ export function templateDeploymentStatusHandler(
107109
};
108110
}
109111

112+
export function templateCapabilitiesHandler(
113+
components: ServerComponents,
114+
): RequestHandler<TemplateMetadataParams, GetCapabilitiesResult, void> {
115+
return async (rawParams) => {
116+
log.debug({ Handler: 'TemplateCapabilities', rawParams });
117+
118+
try {
119+
const params = parseWithPrettyError(parseTemplateMetadataParams, rawParams);
120+
const document = components.documentManager.get(params.uri);
121+
if (!document) {
122+
throw new ResponseError(ErrorCodes.InvalidRequest, 'Template body document not available');
123+
}
124+
125+
const capabilities = await analyzeCapabilities(document, components.cfnService);
126+
127+
return { capabilities };
128+
} catch (error) {
129+
handleTemplateError(error, 'Failed to analyze template capabilities');
130+
}
131+
};
132+
}
133+
110134
function handleTemplateError(error: unknown, contextMessage: string): never {
111135
if (error instanceof ResponseError) {
112136
throw error;
Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Connection, ServerRequestHandler } from 'vscode-languageserver';
1+
import { Connection, RequestHandler } from 'vscode-languageserver';
22
import {
33
TemplateActionParams,
44
TemplateActionResult,
@@ -7,32 +7,38 @@ import {
77
TemplateDeploymentStatusRequest,
88
TemplateStatusResult,
99
TemplateValidationStatusRequest,
10-
GetParametersParams,
10+
TemplateMetadataParams,
1111
GetParametersRequest,
1212
GetParametersResult,
13+
GetCapabilitiesRequest,
14+
GetCapabilitiesResult,
1315
} from '../templates/TemplateRequestType';
1416
import { Identifiable } from './LspTypes';
1517

1618
export class LspTemplateHandlers {
1719
constructor(private readonly connection: Connection) {}
1820

19-
onTemplateValidationCreate(handler: ServerRequestHandler<TemplateActionParams, TemplateActionResult, never, void>) {
21+
onTemplateValidationCreate(handler: RequestHandler<TemplateActionParams, TemplateActionResult, void>) {
2022
this.connection.onRequest(TemplateValidationCreateRequest.method, handler);
2123
}
2224

23-
onTemplateDeploymentCreate(handler: ServerRequestHandler<TemplateActionParams, TemplateActionResult, never, void>) {
25+
onTemplateDeploymentCreate(handler: RequestHandler<TemplateActionParams, TemplateActionResult, void>) {
2426
this.connection.onRequest(TemplateDeploymentCreateRequest.method, handler);
2527
}
2628

27-
onTemplateValidationStatus(handler: ServerRequestHandler<Identifiable, TemplateStatusResult, never, void>) {
29+
onTemplateValidationStatus(handler: RequestHandler<Identifiable, TemplateStatusResult, void>) {
2830
this.connection.onRequest(TemplateValidationStatusRequest.method, handler);
2931
}
3032

31-
onTemplateDeploymentStatus(handler: ServerRequestHandler<Identifiable, TemplateStatusResult, never, void>) {
33+
onTemplateDeploymentStatus(handler: RequestHandler<Identifiable, TemplateStatusResult, void>) {
3234
this.connection.onRequest(TemplateDeploymentStatusRequest.method, handler);
3335
}
3436

35-
onGetParameters(handler: ServerRequestHandler<GetParametersParams, GetParametersResult, never, void>) {
37+
onGetParameters(handler: RequestHandler<TemplateMetadataParams, GetParametersResult, void>) {
3638
this.connection.onRequest(GetParametersRequest.method, handler);
3739
}
40+
41+
onGetCapabilities(handler: RequestHandler<TemplateMetadataParams, GetCapabilitiesResult, void>) {
42+
this.connection.onRequest(GetCapabilitiesRequest.method, handler);
43+
}
3844
}

src/server/CfnServer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
templateValidationStatusHandler,
3131
templateDeploymentStatusHandler,
3232
templateParametersHandler,
33+
templateCapabilitiesHandler,
3334
} from '../handlers/TemplateHandler';
3435
import { LspFeatures } from '../protocol/LspConnection';
3536
import { ServerComponents } from './ServerComponents';
@@ -69,6 +70,7 @@ export class CfnServer {
6970
this.features.authHandlers.onSsoTokenChanged(ssoTokenChangedHandler(this.components));
7071

7172
this.features.templateHandlers.onGetParameters(templateParametersHandler(this.components));
73+
this.features.templateHandlers.onGetCapabilities(templateCapabilitiesHandler(this.components));
7274
this.features.templateHandlers.onTemplateValidationCreate(templateValidationCreateHandler(this.components));
7375
this.features.templateHandlers.onTemplateDeploymentCreate(templateDeploymentCreateHandler(this.components));
7476
this.features.templateHandlers.onTemplateValidationStatus(templateValidationStatusHandler(this.components));

src/services/CfnService.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ import {
3434
StackResourceDriftStatus,
3535
Parameter,
3636
RegistryType,
37+
ValidateTemplateCommand,
38+
ValidateTemplateInput,
39+
ValidateTemplateOutput,
3740
Visibility,
3841
TypeSummary,
3942
DescribeTypeOutput,
@@ -275,6 +278,10 @@ export class CfnService {
275278
});
276279
}
277280

281+
public async validateTemplate(params: ValidateTemplateInput): Promise<ValidateTemplateOutput> {
282+
return await this.withClient((client) => client.send(new ValidateTemplateCommand(params)));
283+
}
284+
278285
static create(components: ServerComponents) {
279286
return new CfnService(components.awsClient);
280287
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Capability } from '@aws-sdk/client-cloudformation';
2+
import { Document } from '../document/Document';
3+
import { CfnService } from '../services/CfnService';
4+
import { LoggerFactory } from '../telemetry/LoggerFactory';
5+
6+
const log = LoggerFactory.getLogger('CapabilityAnalyzer');
7+
8+
export async function analyzeCapabilities(document: Document, cfnService: CfnService): Promise<Capability[]> {
9+
try {
10+
const validationResult = await cfnService.validateTemplate({ TemplateBody: document.getText() });
11+
12+
if (!validationResult.Capabilities) {
13+
return [];
14+
}
15+
16+
// ValidateTemplate cannot process transforms, assume all capabilities are required if a transform is detected
17+
if (validationResult.Capabilities.includes(Capability.CAPABILITY_AUTO_EXPAND)) {
18+
return [Capability.CAPABILITY_IAM, Capability.CAPABILITY_NAMED_IAM, Capability.CAPABILITY_AUTO_EXPAND];
19+
}
20+
21+
return validationResult.Capabilities;
22+
} catch (error) {
23+
log.warn({ error }, 'Capability Analysis failed, assuming all capabilities are required');
24+
return [Capability.CAPABILITY_IAM, Capability.CAPABILITY_NAMED_IAM, Capability.CAPABILITY_AUTO_EXPAND];
25+
}
26+
}

src/templates/TemplateParser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Capability } from '@aws-sdk/client-cloudformation';
22
import { z } from 'zod';
3-
import { TemplateActionParams, GetParametersParams } from './TemplateRequestType';
3+
import { TemplateActionParams, TemplateMetadataParams } from './TemplateRequestType';
44

55
const CapabilitySchema = z.enum([
66
Capability.CAPABILITY_AUTO_EXPAND,
@@ -31,6 +31,6 @@ export function parseTemplateActionParams(input: unknown): TemplateActionParams
3131
return TemplateActionParamsSchema.parse(input);
3232
}
3333

34-
export function parseGetParametersParams(input: unknown): GetParametersParams {
34+
export function parseTemplateMetadataParams(input: unknown): TemplateMetadataParams {
3535
return GetParametersParamsSchema.parse(input);
3636
}

src/templates/TemplateRequestType.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export type TemplateActionResult = Identifiable & {
1616
stackName: string;
1717
};
1818

19-
export type GetParametersParams = {
19+
export type TemplateMetadataParams = {
2020
uri: string;
2121
};
2222

@@ -76,6 +76,14 @@ export const TemplateDeploymentStatusRequest = new RequestType<Identifiable, Tem
7676
'aws/cfn/template/deployment/status',
7777
);
7878

79-
export const GetParametersRequest = new RequestType<GetParametersParams, GetParametersResult, void>(
79+
export const GetParametersRequest = new RequestType<TemplateMetadataParams, GetParametersResult, void>(
8080
'aws/cfn/template/parameters',
8181
);
82+
83+
export type GetCapabilitiesResult = {
84+
capabilities: Capability[];
85+
};
86+
87+
export const GetCapabilitiesRequest = new RequestType<TemplateMetadataParams, GetCapabilitiesResult, void>(
88+
'aws/cfn/template/capabilities',
89+
);

0 commit comments

Comments
 (0)