Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(lambda): enable template parameter prompters for all deploy and sync entry points #6069

Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import * as vscode from 'vscode'
import { Wizard } from '../../../shared/wizards/wizard'
import { createExitPrompter } from '../../../shared/ui/common/exitPrompter'
import * as CloudFormation from '../../../shared/cloudformation/cloudformation'
import { createInputBox } from '../../../shared/ui/inputPrompter'
import { createCommonButtons } from '../../../shared/ui/buttons'
import { getRecentResponse, updateRecentResponse } from '../../../shared/sam/utils'
import { getParameters } from '../../../lambda/config/parameterUtils'

export interface TemplateParametersForm {
[key: string]: any
}

export class TemplateParametersWizard extends Wizard<TemplateParametersForm> {
template: vscode.Uri
preloadedTemplate: CloudFormation.Template | undefined
samTemplateParameters: Map<string, { required: boolean }> | undefined
samCommandUrl: vscode.Uri
commandMementoRootKey: string

public constructor(template: vscode.Uri, samCommandUrl: vscode.Uri, commandMementoRootKey: string) {
super({ exitPrompterProvider: createExitPrompter })
this.template = template
this.samCommandUrl = samCommandUrl
this.commandMementoRootKey = commandMementoRootKey
}

public override async init(): Promise<this> {
this.samTemplateParameters = await getParameters(this.template)
this.preloadedTemplate = await CloudFormation.load(this.template.fsPath)
const samTemplateNames = new Set<string>(this.samTemplateParameters?.keys() ?? [])

samTemplateNames.forEach((name) => {
if (this.preloadedTemplate) {
const defaultValue = this.preloadedTemplate.Parameters
? (this.preloadedTemplate.Parameters[name]?.Default as string)
: undefined
this.form[name].bindPrompter(() =>
this.createParamPromptProvider(name, defaultValue).transform(async (item) => {
await updateRecentResponse(this.commandMementoRootKey, this.template.fsPath, name, item)
return item
})
)
}
})

return this
}

createParamPromptProvider(name: string, defaultValue: string | undefined) {
return createInputBox({
title: `Specify SAM Template parameter value for ${name}`,
buttons: createCommonButtons(this.samCommandUrl),
value: getRecentResponse(this.commandMementoRootKey, this.template.fsPath, name) ?? defaultValue,
})
}
}
304 changes: 95 additions & 209 deletions packages/core/src/shared/sam/deploy.ts

Large diffs are not rendered by default.

130 changes: 99 additions & 31 deletions packages/core/src/shared/sam/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import * as vscode from 'vscode'
import * as path from 'path'
import * as localizedText from '../localizedText'
import { DefaultS3Client } from '../clients/s3Client'
import { Wizard } from '../wizards/wizard'
import { DataQuickPickItem, createMultiPick, createQuickPick } from '../ui/pickerPrompter'
import { DefaultCloudFormationClient } from '../clients/cloudFormationClient'
import * as CloudFormation from '../cloudformation/cloudformation'
Expand All @@ -28,14 +27,14 @@ import { createExitPrompter } from '../ui/common/exitPrompter'
import { getConfigFileUri, SamConfig, validateSamSyncConfig, writeSamconfigGlobal } from './config'
import { cast, Optional } from '../utilities/typeConstructors'
import { pushIf, toRecord } from '../utilities/collectionUtils'
import { getOverriddenParameters } from '../../lambda/config/parameterUtils'
import { getOverriddenParameters, getParameters } from '../../lambda/config/parameterUtils'
import { addTelemetryEnvVar } from './cli/samCliInvokerUtils'
import { samSyncParamUrl, samSyncUrl, samUpgradeUrl } from '../constants'
import { openUrl } from '../utilities/vsCodeUtils'
import { showOnce } from '../utilities/messages'
import { IamConnection } from '../../auth/connection'
import { CloudFormationTemplateRegistry } from '../fs/templateRegistry'
import { TreeNode } from '../treeview/resourceTreeDataProvider'
import { isTreeNode, TreeNode } from '../treeview/resourceTreeDataProvider'
import { getSpawnEnv } from '../env/resolveEnv'
import {
getProjectRoot,
Expand All @@ -52,13 +51,19 @@ import { ParamsSource, createSyncParamsSourcePrompter } from '../ui/sam/paramsSo
import { createEcrPrompter } from '../ui/sam/ecrPrompter'
import { BucketSource, createBucketNamePrompter, createBucketSourcePrompter } from '../ui/sam/bucketPrompter'
import { runInTerminal } from './processTerminal'
import {
TemplateParametersForm,
TemplateParametersWizard,
} from '../../awsService/appBuilder/wizards/templateParametersWizard'
import { CompositeWizard } from '../wizards/compositeWizard'

export interface SyncParams {
readonly paramsSource: ParamsSource
readonly region: string
readonly deployType: 'infra' | 'code'
readonly projectRoot: vscode.Uri
readonly template: TemplateItem
readonly templateParameters: any
readonly stackName: string
readonly bucketSource: BucketSource
readonly bucketName: string
Expand Down Expand Up @@ -147,7 +152,30 @@ export const syncFlagItems: DataQuickPickItem<string>[] = [
},
]

export class SyncWizard extends Wizard<SyncParams> {
export enum SamSyncEntryPoints {
SamTemplateFile,
SamConfigFile,
RegionNodeContextMenu,
AppBuilderNodeButton,
CommandPalette,
}

function getSyncEntryPoint(arg: vscode.Uri | AWSTreeNodeBase | TreeNode | undefined) {
if (arg instanceof vscode.Uri) {
if (arg.path.endsWith('samconfig.toml')) {
return SamSyncEntryPoints.SamConfigFile
}
return SamSyncEntryPoints.SamTemplateFile
} else if (arg instanceof AWSTreeNodeBase) {
return SamSyncEntryPoints.RegionNodeContextMenu
} else if (isTreeNode(arg)) {
return SamSyncEntryPoints.AppBuilderNodeButton
} else {
return SamSyncEntryPoints.CommandPalette
}
}

export class SyncWizard extends CompositeWizard<SyncParams> {
registry: CloudFormationTemplateRegistry
public constructor(
state: Pick<SyncParams, 'deployType'> & Partial<SyncParams>,
Expand All @@ -156,17 +184,38 @@ export class SyncWizard extends Wizard<SyncParams> {
) {
super({ initState: state, exitPrompterProvider: shouldPromptExit ? createExitPrompter : undefined })
this.registry = registry
}

public override async init(): Promise<this> {
this.form.template.bindPrompter(() => createTemplatePrompter(this.registry, syncMementoRootKey, samSyncUrl))
this.form.templateParameters.bindPrompter(
async ({ template }) =>
this.createWizardPrompter<TemplateParametersWizard, TemplateParametersForm>(
TemplateParametersWizard,
template!.uri,
samSyncUrl,
syncMementoRootKey
),
{
showWhen: async ({ template }) => {
const samTemplateParameters = await getParameters(template!.uri)
return !!samTemplateParameters && samTemplateParameters.size > 0
},
}
)

this.form.projectRoot.setDefault(({ template }) => getProjectRoot(template))

this.form.paramsSource.bindPrompter(async ({ projectRoot }) => {
const existValidSamConfig: boolean | undefined = await validateSamSyncConfig(projectRoot)
return createSyncParamsSourcePrompter(existValidSamConfig)
})

this.form.region.bindPrompter(() => createRegionPrompter().transform((r) => r.id), {
showWhen: ({ paramsSource }) =>
paramsSource === ParamsSource.Specify || paramsSource === ParamsSource.SpecifyAndSave,
})

this.form.stackName.bindPrompter(
({ region }) =>
createStackPrompter(new DefaultCloudFormationClient(region!), syncMementoRootKey, samSyncUrl),
Expand Down Expand Up @@ -210,6 +259,7 @@ export class SyncWizard extends Wizard<SyncParams> {
paramsSource === ParamsSource.Specify || paramsSource === ParamsSource.SpecifyAndSave,
}
)
return this
}
}

Expand Down Expand Up @@ -431,21 +481,30 @@ export async function prepareSyncParams(
): Promise<Partial<SyncParams>> {
// Skip creating dependency layers by default for backwards compat
const baseParams: Partial<SyncParams> = { skipDependencyLayer: true }
const entryPoint = getSyncEntryPoint(arg)

if (arg instanceof AWSTreeNodeBase) {
// "Deploy" command was invoked on a regionNode.
return { ...baseParams, region: arg.regionCode }
} else if (arg instanceof vscode.Uri) {
if (arg.path.endsWith('samconfig.toml')) {
// "Deploy" command was invoked on a samconfig.toml file.
// TODO: add step to verify samconfig content to skip param source prompter
const config = await SamConfig.fromConfigFileUri(arg)
switch (entryPoint) {
case SamSyncEntryPoints.SamTemplateFile: {
const entryPointArg = arg as vscode.Uri
const template = {
uri: entryPointArg,
data: await CloudFormation.load(entryPointArg.fsPath, validate),
}

return {
...baseParams,
template: template,
projectRoot: getProjectRootUri(template.uri),
}
}
case SamSyncEntryPoints.SamConfigFile: {
const config = await SamConfig.fromConfigFileUri(arg as vscode.Uri)
const params = getSyncParamsFromConfig(config)
const projectRoot = vscode.Uri.joinPath(config.location, '..')
const templateUri = params.templatePath
? vscode.Uri.file(path.resolve(projectRoot.fsPath, params.templatePath))
: undefined
const template = templateUri
const samConfigFileTemplate = templateUri
? {
uri: templateUri,
data: await CloudFormation.load(templateUri.fsPath),
Expand All @@ -454,29 +513,38 @@ export async function prepareSyncParams(
// Always use the dependency layer if the user specified to do so
const skipDependencyLayer = !config.getCommandParam('sync', 'dependency_layer')

return { ...baseParams, ...params, template, projectRoot, skipDependencyLayer } as SyncParams
return {
...baseParams,
...params,
template: samConfigFileTemplate,
projectRoot,
skipDependencyLayer,
} as SyncParams
}

// "Deploy" command was invoked on a template.yaml file.
const template = {
uri: arg,
data: await CloudFormation.load(arg.fsPath, validate),
case SamSyncEntryPoints.RegionNodeContextMenu: {
const entryPointArg = arg as AWSTreeNodeBase
return { ...baseParams, region: entryPointArg.regionCode }
}

return { ...baseParams, template, projectRoot: getProjectRootUri(template.uri) }
} else if (arg && arg.getTreeItem()) {
// "Deploy" command was invoked on a TreeNode on the AppBuilder.
const templateUri = (arg.getTreeItem() as vscode.TreeItem).resourceUri
if (templateUri) {
const template = {
uri: templateUri,
data: await CloudFormation.load(templateUri.fsPath, validate),
case SamSyncEntryPoints.AppBuilderNodeButton: {
const entryPointArg = arg as TreeNode
const templateUri = (entryPointArg.getTreeItem() as vscode.TreeItem).resourceUri
if (templateUri) {
const template = {
uri: templateUri,
data: await CloudFormation.load(templateUri.fsPath, validate),
}
return {
...baseParams,
template,
projectRoot: getProjectRootUri(templateUri),
}
}
return { ...baseParams, template, projectRoot: getProjectRootUri(template.uri) }
return baseParams
}
case SamSyncEntryPoints.CommandPalette:
default:
return baseParams
}

return baseParams
}

export type SamSyncResult = {
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/shared/ui/wizardPrompter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import { Prompter, PromptResult } from './prompter'
* - {@link SingleNestedWizard}
* - {@link DoubleNestedWizard}
*/

// eslint-disable-next-line @typescript-eslint/naming-convention
export const WIZARD_PROMPTER = 'WIZARD_PROMPTER'
export class WizardPrompter<T> extends Prompter<T> {
public get recentItem(): any {
return undefined
Expand Down Expand Up @@ -56,6 +59,7 @@ export class WizardPrompter<T> extends Prompter<T> {
}
}

// eslint-disable-next-line @typescript-eslint/naming-convention
protected async promptUser(): Promise<PromptResult<T>> {
this.response = await this.wizard.run()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ describe('DeployTypeWizard', function () {
assert.strictEqual(picker.items.length, 2)
picker.acceptItem(picker.items[1])
})
.handleInputBox('Specify SAM parameter value for SourceBucketName', (inputBox) => {
.handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => {
inputBox.acceptValue('my-source-bucket-name')
})
.handleInputBox('Specify SAM parameter value for DestinationBucketName', (inputBox) => {
.handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => {
inputBox.acceptValue('my-destination-bucket-name')
})
.handleQuickPick('Specify parameter source for deploy', async (quickPick) => {
Expand Down Expand Up @@ -98,6 +98,12 @@ describe('DeployTypeWizard', function () {
assert.strictEqual(picker.items.length, 2)
picker.acceptItem(picker.items[0])
})
.handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => {
inputBox.acceptValue('my-source-bucket-name')
})
.handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => {
inputBox.acceptValue('my-destination-bucket-name')
})
.handleQuickPick('Specify parameter source for sync', async (quickPick) => {
// Need time to check samconfig.toml file and generate options
await quickPick.untilReady()
Expand Down
Loading
Loading