Skip to content

Commit 44672d9

Browse files
authored
feat(cloudformation): Additional CloudFormation features and improvements
* Remove resource type from list using right click * Few startup fixes for CloudFormation extension * Add exec permissions and support node versions * Add node to AWS Explorer CFN panel * Add prompt for deploymentMode and plumb to deployment * Coordinate stack views and add overview to CFN console * Fix extract to parameter cursor command
2 parents e19e476 + d5a5bd9 commit 44672d9

40 files changed

+1740
-680
lines changed

packages/core/src/awsService/cloudformation/cfn/resourceRequestTypes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ export const ResourceTypesRequest = new RequestType<ResourceTypesParams, Resourc
4242
'aws/cfn/resources/types'
4343
)
4444

45+
export const RemoveResourceTypeRequest = new RequestType<string, void, void>('aws/cfn/resources/list/remove')
46+
4547
export type ResourceSelection = {
4648
resourceType: string
4749
resourceIdentifiers: string[]

packages/core/src/awsService/cloudformation/commands/cfnCommands.ts

Lines changed: 97 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import { commands, env, Uri, window, workspace, Range, Selection, TextEditorRevealType, ProgressLocation } from 'vscode'
7-
import { commandKey, extractErrorMessage, findParameterDescriptionPosition } from '../utils'
7+
import { commandKey, extractErrorMessage, findParameterDescriptionPosition, isStackInTransientState } from '../utils'
88
import { LanguageClient } from 'vscode-languageclient/node'
99
import { Command } from 'vscode-languageclient/node'
1010
import * as yaml from 'js-yaml'
@@ -25,15 +25,14 @@ import {
2525
getOnStackFailure,
2626
getIncludeNestedStacks,
2727
getImportExistingResources,
28+
getDeploymentMode,
2829
shouldUploadToS3,
2930
getS3Bucket,
3031
getS3Key,
3132
shouldSaveFlagsToFile,
3233
getFilePath,
3334
} from '../ui/inputBox'
34-
import { setContext } from '../../../shared/vscode/setContext'
3535
import { DiffWebviewProvider } from '../ui/diffWebviewProvider'
36-
import { StackResourcesWebviewProvider } from '../ui/stackResourcesWebviewProvider'
3736
import { showErrorMessage } from '../ui/message'
3837
import { getLastValidation, setLastValidation, Validation } from '../stacks/actions/validationWorkflow'
3938
import {
@@ -49,21 +48,23 @@ import {
4948
TemplateParameter,
5049
ResourceToImport,
5150
ChangeSetReference,
51+
DeploymentMode,
5252
} from '../stacks/actions/stackActionRequestType'
53-
import { StackInfo } from '../stacks/actions/stackActionRequestType'
5453
import { ResourceNode } from '../explorer/nodes/resourceNode'
5554
import { ResourcesManager } from '../resources/resourcesManager'
5655
import { RelatedResourcesManager } from '../relatedResources/relatedResourcesManager'
5756
import { DocumentManager } from '../documents/documentManager'
5857
import { CfnEnvironmentManager } from '../cfn-init/cfnEnvironmentManager'
5958

6059
import { StackOverviewWebviewProvider } from '../ui/stackOverviewWebviewProvider'
61-
import { StackEventsWebviewProvider } from '../ui/stackEventsWebviewProvider'
6260
import { StackOutputsWebviewProvider } from '../ui/stackOutputsWebviewProvider'
61+
import { StackResourcesWebviewProvider } from '../ui/stackResourcesWebviewProvider'
62+
import { StackViewCoordinator } from '../ui/stackViewCoordinator'
6363
import { ResourceContextValue } from '../explorer/contextValue'
6464
import { getLogger } from '../../../shared/logger/logger'
6565
import { CloudFormationExplorer } from '../explorer/explorer'
6666
import { StacksNode } from '../explorer/nodes/stacksNode'
67+
import { StackNode } from '../explorer/nodes/stackNode'
6768
import { ResourcesNode } from '../explorer/nodes/resourcesNode'
6869
import { ResourceTypeNode } from '../explorer/nodes/resourceTypeNode'
6970
import { StackChangeSetsNode } from '../explorer/nodes/stackChangeSetsNode'
@@ -72,7 +73,6 @@ import { CfnInitUiInterface } from '../cfn-init/cfnInitUiInterface'
7273
import { ChangeSetDeletion } from '../stacks/actions/changeSetDeletionWorkflow'
7374
import { fs } from '../../../shared/fs/fs'
7475
import { convertParametersToRecord, convertTagsToRecord } from '../cfn-init/utils'
75-
import { StackNode } from '../explorer/nodes/stackNode'
7676
import { DescribeStackRequest } from '../stacks/actions/stackActionProtocol'
7777

7878
export function validateDeploymentCommand(
@@ -127,12 +127,12 @@ export function deployTemplateFromStacksMenuCommand() {
127127
})
128128
}
129129

130-
export function executeChangeSetCommand(client: LanguageClient) {
130+
export function executeChangeSetCommand(client: LanguageClient, coordinator: StackViewCoordinator) {
131131
return commands.registerCommand(
132132
commandKey('api.executeChangeSet'),
133133
async (stackName: string, changeSetName: string) => {
134134
try {
135-
const deployment = new Deployment(stackName, changeSetName, client)
135+
const deployment = new Deployment(stackName, changeSetName, client, coordinator)
136136

137137
await deployment.deploy()
138138
} catch (error) {
@@ -174,9 +174,7 @@ export function viewChangeSetCommand(client: LanguageClient, diffProvider: DiffW
174174
stackName: params.stackName,
175175
})
176176

177-
void setContext('aws.cloudformation.stacks.diffVisible', true)
178-
179-
diffProvider.updateData(params.stackName, describeChangeSetResult.changes, params.changeSetName, true)
177+
void diffProvider.updateData(params.stackName, describeChangeSetResult.changes, params.changeSetName, true)
180178
void commands.executeCommand(commandKey('diff.focus'))
181179
} catch (error) {
182180
showErrorMessage(`Error viewing change set: ${extractErrorMessage(error)}`)
@@ -259,6 +257,18 @@ type OptionalFlagSelection = ChangeSetOptionalFlags & {
259257
shouldSaveOptions?: boolean
260258
}
261259

260+
function shouldPromptForDeploymentMode(
261+
stackDetails: Stack | undefined,
262+
importExistingResources: boolean | undefined,
263+
includeNestedStacks: boolean | undefined,
264+
onStackFailure: OnStackFailure | undefined
265+
): boolean {
266+
const isCreate = !stackDetails
267+
const hasDisableRollback = onStackFailure === OnStackFailure.DO_NOTHING
268+
269+
return !isCreate && !importExistingResources && !includeNestedStacks && !hasDisableRollback
270+
}
271+
262272
export async function promptForOptionalFlags(
263273
fileFlags?: ChangeSetOptionalFlags,
264274
stackDetails?: Stack
@@ -281,29 +291,60 @@ export async function promptForOptionalFlags(
281291
includeNestedStacks: fileFlags?.includeNestedStacks,
282292
tags: fileFlags?.tags,
283293
importExistingResources: fileFlags?.importExistingResources,
294+
// default to REVERT_DRIFT if possible because it's generally useful
295+
deploymentMode:
296+
fileFlags?.deploymentMode ??
297+
(shouldPromptForDeploymentMode(
298+
stackDetails,
299+
fileFlags?.importExistingResources,
300+
fileFlags?.includeNestedStacks,
301+
fileFlags?.onStackFailure
302+
)
303+
? DeploymentMode.REVERT_DRIFT
304+
: undefined),
284305
shouldSaveOptions: false,
285306
}
286307

287308
break
288-
case OptionalFlagMode.Input:
309+
case OptionalFlagMode.Input: {
310+
const onStackFailure = fileFlags?.onStackFailure ?? (await getOnStackFailure(!!stackDetails))
311+
const includeNestedStacks = fileFlags?.includeNestedStacks ?? (await getIncludeNestedStacks())
312+
const importExistingResources = fileFlags?.importExistingResources ?? (await getImportExistingResources())
313+
314+
let deploymentMode = fileFlags?.deploymentMode
315+
if (
316+
!deploymentMode &&
317+
shouldPromptForDeploymentMode(
318+
stackDetails,
319+
importExistingResources,
320+
includeNestedStacks,
321+
onStackFailure
322+
)
323+
) {
324+
deploymentMode = await getDeploymentMode()
325+
}
326+
289327
optionalFlags = {
290-
onStackFailure: fileFlags?.onStackFailure ?? (await getOnStackFailure()),
291-
includeNestedStacks: fileFlags?.includeNestedStacks ?? (await getIncludeNestedStacks()),
328+
onStackFailure,
329+
includeNestedStacks,
292330
tags: fileFlags?.tags ?? (await getTags(stackDetails?.Tags)),
293-
importExistingResources: fileFlags?.importExistingResources ?? (await getImportExistingResources()),
331+
importExistingResources,
332+
deploymentMode,
294333
}
295334

296335
if (!fileFlags && Object.values(optionalFlags).some((val) => val !== undefined)) {
297336
optionalFlags.shouldSaveOptions = true
298337
}
299338

300339
break
340+
}
301341
case OptionalFlagMode.DevFriendly:
302342
optionalFlags = {
303343
onStackFailure: OnStackFailure.DO_NOTHING,
304344
includeNestedStacks: true,
305345
tags: fileFlags?.tags ?? (await getTags(stackDetails?.Tags)),
306346
importExistingResources: true,
347+
deploymentMode: undefined,
307348
}
308349

309350
if (!fileFlags && optionalFlags.tags) {
@@ -341,6 +382,7 @@ export async function promptToSaveToFile(
341382
'on-stack-failure': optionalFlags?.onStackFailure,
342383
'include-nested-stacks': optionalFlags?.includeNestedStacks,
343384
'import-existing-resources': optionalFlags?.importExistingResources,
385+
'deployment-mode': optionalFlags?.deploymentMode,
344386
}
345387

346388
// Determine file type and format accordingly
@@ -590,23 +632,17 @@ async function getTemplateParameters(client: LanguageClient, templateUri: string
590632
}
591633
}
592634

593-
export const SelectResourceTypeCommand: Command = {
594-
title: 'Select Resource Types',
595-
command: commandKey('api.selectResourceTypes'),
596-
arguments: [],
597-
}
598-
599-
export function selectResourceTypesCommand(resourcesManager: ResourcesManager) {
635+
export function addResourceTypesCommand(resourcesManager: ResourcesManager) {
600636
return commands.registerCommand(
601-
commandKey('api.selectResourceTypes'),
637+
commandKey('api.addResourceTypes'),
602638
async () => await resourcesManager.selectResourceTypes()
603639
)
604640
}
605641

606-
export function addResourceTypesCommand(resourcesManager: ResourcesManager) {
642+
export function removeResourceTypeCommand(resourcesManager: ResourcesManager) {
607643
return commands.registerCommand(
608-
commandKey('api.addResourceTypes'),
609-
async () => await resourcesManager.selectResourceTypes()
644+
commandKey('removeResourceType'),
645+
async (node: ResourceTypeNode) => await resourcesManager.removeResourceType(node.typeName)
610646
)
611647
}
612648

@@ -684,24 +720,6 @@ export function refreshResourceListCommand(resourcesManager: ResourcesManager, e
684720
})
685721
}
686722

687-
export function viewStackDiffCommand() {
688-
return commands.registerCommand(commandKey('stacks.viewDiff'), () => {
689-
void setContext('aws.cloudformation.stacks.diffVisible', true)
690-
void commands.executeCommand(commandKey('diff.focus'))
691-
})
692-
}
693-
694-
export function viewStackDetailCommand(resourcesProvider: StackResourcesWebviewProvider) {
695-
return commands.registerCommand(commandKey('stacks.viewDetail'), async (node?: any) => {
696-
void setContext('aws.cloudformation.stacks.detailVisible', true)
697-
698-
const stackName = node?.stackName || 'Unknown Stack'
699-
700-
await resourcesProvider.updateData(stackName)
701-
void commands.executeCommand(commandKey('detail.focus'))
702-
})
703-
}
704-
705723
export function focusDiffCommand() {
706724
return commands.registerCommand(commandKey('diff.focus'), () => {
707725
void commands.executeCommand('workbench.view.extension.cfn-diff')
@@ -714,7 +732,7 @@ export function getStackManagementInfoCommand(resourcesManager: ResourcesManager
714732
})
715733
}
716734

717-
export function extractToParameterPositionCursorCommand() {
735+
export function extractToParameterPositionCursorCommand(client: LanguageClient) {
718736
return commands.registerCommand(
719737
'aws.cloudformation.extractToParameter.positionCursor',
720738
async (
@@ -725,9 +743,12 @@ export function extractToParameterPositionCursorCommand() {
725743
actionType?: string
726744
) => {
727745
try {
728-
// Track code action acceptance if tracking parameters provided
746+
// Track code action acceptance on the server if tracking parameters provided
729747
if (trackingCommand && actionType) {
730-
await commands.executeCommand(trackingCommand, actionType)
748+
await client.sendRequest('workspace/executeCommand', {
749+
command: trackingCommand,
750+
arguments: [actionType],
751+
})
731752
}
732753

733754
const uri = Uri.parse(documentUri)
@@ -846,23 +867,37 @@ export function loadMoreChangeSetsCommand(explorer: CloudFormationExplorer) {
846867
})
847868
}
848869

849-
export function showStackOverviewCommand(overviewProvider: StackOverviewWebviewProvider) {
850-
return commands.registerCommand(commandKey('api.showStackOverview'), async (stack: StackInfo) => {
851-
await overviewProvider.showStackOverview(stack)
852-
})
853-
}
870+
export function viewStackCommand(
871+
coordinator: StackViewCoordinator,
872+
overviewProvider: StackOverviewWebviewProvider,
873+
outputsProvider: StackOutputsWebviewProvider,
874+
resourcesProvider: StackResourcesWebviewProvider
875+
) {
876+
return commands.registerCommand(commandKey('stack.view'), async (node?: StackNode) => {
877+
let stackName: string | undefined
854878

855-
export function showStackEventsCommand(eventsProvider: StackEventsWebviewProvider) {
856-
return commands.registerCommand(commandKey('stack.events.show'), async (stackName: string) => {
857-
await eventsProvider.showStackEvents(stackName)
858-
await commands.executeCommand(commandKey('stack.events.focus'))
859-
})
860-
}
879+
if (node?.stack.StackName) {
880+
stackName = node.stack.StackName
881+
} else {
882+
stackName = await getStackName()
883+
if (!stackName) {
884+
return
885+
}
886+
}
887+
888+
await coordinator.setStack(stackName)
889+
890+
await overviewProvider.showStackOverview(stackName)
891+
892+
const stackStatus = coordinator.currentStackStatus
893+
894+
await resourcesProvider.updateData(stackName)
895+
896+
if (stackStatus && !isStackInTransientState(stackStatus)) {
897+
await outputsProvider.showOutputs(stackName)
898+
}
861899

862-
export function showStackOutputsCommand(outputsProvider: StackOutputsWebviewProvider) {
863-
return commands.registerCommand(commandKey('stack.outputs.show'), async (stackName: string) => {
864-
await outputsProvider.showOutputs(stackName)
865-
await commands.executeCommand(commandKey('stack.outputs.focus'))
900+
await commands.executeCommand(commandKey('stack.overview.focus'))
866901
})
867902
}
868903

packages/core/src/awsService/cloudformation/commands/lspCommands.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.

packages/core/src/awsService/cloudformation/explorer/nodes/stackEventsNode.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

packages/core/src/awsService/cloudformation/explorer/nodes/stackNode.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,6 @@
66
import { TreeItemCollapsibleState, ThemeIcon, ThemeColor } from 'vscode'
77
import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase'
88
import { StackSummary } from '@aws-sdk/client-cloudformation'
9-
import { StackStatusNode } from './stackStatusNode'
10-
import { StackOverviewNode } from './stackOverviewNode'
11-
import { StackEventsNode } from './stackEventsNode'
12-
import { StackOutputsNode } from './stackOutputsNode'
13-
import { StackResourcesNode } from './stackResourcesNode'
149
import { StackChangeSetsNode } from './stackChangeSetsNode'
1510
import { ChangeSetsManager } from '../../stacks/changeSetsManager'
1611

@@ -43,18 +38,9 @@ export class StackNode extends AWSTreeNodeBase {
4338

4439
public override async getChildren(): Promise<AWSTreeNodeBase[]> {
4540
const stackName = this.stack.StackName ?? ''
46-
const stackStatus = this.stack.StackStatus ?? 'UNKNOWN'
4741

48-
// Pre-load change sets to get accurate count
4942
await this.changeSetsManager.getChangeSets(stackName)
5043

51-
return [
52-
new StackStatusNode(stackStatus),
53-
new StackOverviewNode(this.stack),
54-
new StackEventsNode(stackName),
55-
new StackOutputsNode(stackName),
56-
new StackResourcesNode(stackName),
57-
new StackChangeSetsNode(stackName, this.changeSetsManager),
58-
]
44+
return [new StackChangeSetsNode(stackName, this.changeSetsManager)]
5945
}
6046
}

0 commit comments

Comments
 (0)