Skip to content

Commit a8a2fd8

Browse files
authored
Source-based codelenses can generate template run configs for image-based Lambdas. (#1485)
1 parent 17588bc commit a8a2fd8

File tree

6 files changed

+316
-54
lines changed

6 files changed

+316
-54
lines changed

src/shared/cloudformation/templateRegistry.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6+
import { readFileSync } from 'fs'
67
import { CloudFormation } from './cloudformation'
78
import * as pathutils from '../utilities/pathUtils'
89
import * as path from 'path'
@@ -11,6 +12,7 @@ import { dotNetRuntimes } from '../../lambda/models/samLambdaRuntime'
1112
import { getLambdaDetails } from '../../lambda/utils'
1213
import { ext } from '../extensionGlobals'
1314
import { WatchedFiles, WatchedItem } from '../watchedFiles'
15+
import { getLogger } from '../logger'
1416

1517
export interface TemplateDatum {
1618
path: string
@@ -96,6 +98,20 @@ export function getResourcesForHandlerFromTemplateDatum(
9698
templateDatum.item
9799
)
98100

101+
// properties for image type templates
102+
const registeredPackageType = CloudFormation.getStringForProperty(
103+
resource.Properties?.PackageType,
104+
templateDatum.item
105+
)
106+
const registeredDockerContext = CloudFormation.getStringForProperty(
107+
resource.Metadata?.DockerContext,
108+
templateDatum.item
109+
)
110+
const registeredDockerFile = CloudFormation.getStringForProperty(
111+
resource.Metadata?.Dockerfile,
112+
templateDatum.item
113+
)
114+
99115
if (registeredRuntime && registeredHandler && registeredCodeUri) {
100116
// .NET is currently a special case in that the filepath and handler aren't specific.
101117
// For now: check if handler matches and check if the code URI contains the filepath.
@@ -130,9 +146,42 @@ export function getResourcesForHandlerFromTemplateDatum(
130146
matchingResources.push({ name: key, resourceData: resource })
131147
}
132148
} catch (e) {
133-
// swallow error from getLambdaDetails: handler not a valid runtime, so skip to the next one
149+
getLogger().warn(
150+
`Resource ${key} in template ${templateDirname} has invalid runtime for handler ${handler}: ${registeredRuntime}`
151+
)
152+
}
153+
}
154+
// not direct-invoke type, attempt image type
155+
} else if (registeredPackageType === 'Image' && registeredDockerContext && registeredDockerFile) {
156+
// path must be inside dockerDir
157+
const dockerDir = path.join(templateDirname, registeredDockerContext)
158+
if (isInDirectory(dockerDir, filepath)) {
159+
let adjustedHandler: string = handler
160+
if (!filepath.endsWith('.cs')) {
161+
// reframe path to be relative to dockerDir instead of package.json
162+
// omit filename and append filename + function name from handler
163+
const relPath = path.relative(dockerDir, path.dirname(filepath))
164+
const handlerParts = pathutils.normalizeSeparator(handler).split('/')
165+
adjustedHandler = pathutils.normalizeSeparator(
166+
path.join(relPath, handlerParts[handlerParts.length - 1])
167+
)
168+
}
169+
try {
170+
// open dockerfile and see if it has a handler that matches the handler represented by this file
171+
const fileText = readFileSync(path.join(dockerDir, registeredDockerFile)).toString()
172+
// exact match within quotes to avoid shorter paths being picked up
173+
if (
174+
new RegExp(`['"]${adjustedHandler}['"]`, 'g').test(pathutils.normalizeSeparator(fileText))
175+
) {
176+
matchingResources.push({ name: key, resourceData: resource })
177+
}
178+
} catch (e) {
179+
// file read error
180+
getLogger().error(e as Error)
134181
}
135182
}
183+
} else {
184+
getLogger().verbose(`Resource ${key} in template ${templateDirname} does not match handler ${handler}`)
136185
}
137186
}
138187
}

src/shared/codelens/codeLensUtils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import * as vscode from 'vscode'
77
import { RuntimeFamily } from '../../lambda/models/samLambdaRuntime'
8+
import { CloudFormation } from '../cloudformation/cloudformation'
89
import { getResourcesForHandler } from '../cloudformation/templateRegistry'
910
import { LambdaHandlerCandidate } from '../lambdaHandlerSearch'
1011
import { getLogger } from '../logger'
@@ -57,9 +58,11 @@ export async function makeCodeLenses({
5758

5859
if (associatedResources.length > 0) {
5960
for (const resource of associatedResources) {
61+
const isImage = CloudFormation.isImageLambdaResource(resource.resourceData.Properties)
6062
templateConfigs.push({
6163
resourceName: resource.name,
6264
rootUri: vscode.Uri.file(resource.templateDatum.path),
65+
runtimeFamily: isImage ? runtimeFamily : undefined,
6366
})
6467
const events = resource.resourceData.Properties?.Events
6568
if (events) {
@@ -71,6 +74,7 @@ export async function makeCodeLenses({
7174
resourceName: resource.name,
7275
rootUri: vscode.Uri.file(resource.templateDatum.path),
7376
apiEvent: { name: key, event: value },
77+
runtimeFamily: isImage ? runtimeFamily : undefined,
7478
})
7579
}
7680
}

src/shared/sam/debugger/awsSamDebugConfiguration.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,14 @@ export function createApiAwsSamDebugConfig(
213213
const workspaceRelativePath = folder ? getNormalizedRelativePath(folder.uri.fsPath, templatePath) : templatePath
214214
const templateParentDir = path.basename(path.dirname(templatePath))
215215

216+
const withRuntime = runtimeName
217+
? {
218+
lambda: {
219+
runtime: runtimeName,
220+
},
221+
}
222+
: undefined
223+
216224
return {
217225
type: AWS_SAM_DEBUG_TYPE,
218226
request: DIRECT_INVOKE_TYPE,
@@ -229,5 +237,6 @@ export function createApiAwsSamDebugConfig(
229237
httpMethod: (preloadedConfig?.httpMethod as APIGatewayProperties['httpMethod']) ?? 'get',
230238
payload: preloadedConfig?.payload ?? { json: {} },
231239
},
240+
...withRuntime,
232241
}
233242
}

src/shared/sam/debugger/commands/addSamDebugConfiguration.ts

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ export async function addSamDebugConfiguration(
5151
let runtimeName = runtimeFamily ? getDefaultRuntime(runtimeFamily) : undefined
5252
let addRuntimeNameToConfig = false
5353

54-
if (type === TEMPLATE_TARGET_TYPE) {
54+
// both of these config types use templates
55+
if (type === TEMPLATE_TARGET_TYPE || type === API_TARGET_TYPE) {
5556
let preloadedConfig = undefined
5657

5758
if (workspaceFolder) {
@@ -63,36 +64,42 @@ export async function addSamDebugConfiguration(
6364
}
6465

6566
if (CloudFormation.isZipLambdaResource(resource.Properties)) {
66-
const handler = CloudFormation.getStringForProperty(resource.Properties.Handler, templateDatum.item)
67-
const existingConfig = await getExistingConfiguration(workspaceFolder, handler ?? '', rootUri)
68-
if (existingConfig) {
69-
const responseMigrate: string = localize(
70-
'AWS.sam.debugger.useExistingConfig.migrate',
71-
'Create based on the legacy config'
67+
if (type === TEMPLATE_TARGET_TYPE) {
68+
const handler = CloudFormation.getStringForProperty(
69+
resource.Properties.Handler,
70+
templateDatum.item
7271
)
73-
const responseNew: string = localize(
74-
'AWS.sam.debugger.useExistingConfig.doNotMigrate',
75-
'Create new config only'
76-
)
77-
const prompt = await vscode.window.showInformationMessage(
78-
localize(
79-
'AWS.sam.debugger.useExistingConfig',
80-
'AWS Toolkit detected an existing legacy configuration for this function. Create the debug config based on the legacy config?'
81-
),
82-
{ modal: true },
83-
responseMigrate,
84-
responseNew
85-
)
86-
if (!prompt) {
87-
// User selected "Cancel". Abandon config creation
88-
return
89-
} else if (prompt === responseMigrate) {
90-
preloadedConfig = existingConfig
72+
const existingConfig = await getExistingConfiguration(workspaceFolder, handler ?? '', rootUri)
73+
if (existingConfig) {
74+
const responseMigrate: string = localize(
75+
'AWS.sam.debugger.useExistingConfig.migrate',
76+
'Create based on the legacy config'
77+
)
78+
const responseNew: string = localize(
79+
'AWS.sam.debugger.useExistingConfig.doNotMigrate',
80+
'Create new config only'
81+
)
82+
const prompt = await vscode.window.showInformationMessage(
83+
localize(
84+
'AWS.sam.debugger.useExistingConfig',
85+
'AWS Toolkit detected an existing legacy configuration for this function. Create the debug config based on the legacy config?'
86+
),
87+
{ modal: true },
88+
responseMigrate,
89+
responseNew
90+
)
91+
if (!prompt) {
92+
// User selected "Cancel". Abandon config creation
93+
return
94+
} else if (prompt === responseMigrate) {
95+
preloadedConfig = existingConfig
96+
}
9197
}
9298
}
93-
} else if (CloudFormation.isImageLambdaResource(resource.Properties) && runtimeFamily === undefined) {
99+
} else if (CloudFormation.isImageLambdaResource(resource.Properties)) {
94100
const quickPick = createRuntimeQuickPick({
95101
showImageRuntimes: false,
102+
runtimeFamily,
96103
})
97104

98105
const choices = await picker.promptUser({
@@ -113,14 +120,32 @@ export async function addSamDebugConfiguration(
113120
}
114121
}
115122
}
116-
samDebugConfig = createTemplateAwsSamDebugConfig(
117-
workspaceFolder,
118-
runtimeName,
119-
addRuntimeNameToConfig,
120-
resourceName,
121-
rootUri.fsPath,
122-
preloadedConfig
123-
)
123+
124+
if (type === TEMPLATE_TARGET_TYPE) {
125+
samDebugConfig = createTemplateAwsSamDebugConfig(
126+
workspaceFolder,
127+
runtimeName,
128+
addRuntimeNameToConfig,
129+
resourceName,
130+
rootUri.fsPath,
131+
preloadedConfig
132+
)
133+
} else {
134+
// If the event has no properties, the default will be used
135+
const apiConfig = {
136+
path: apiEvent?.event.Properties?.Path,
137+
httpMethod: apiEvent?.event.Properties?.Method,
138+
payload: apiEvent?.event.Properties?.Payload,
139+
}
140+
141+
samDebugConfig = createApiAwsSamDebugConfig(
142+
workspaceFolder,
143+
runtimeName,
144+
resourceName,
145+
rootUri.fsPath,
146+
apiConfig
147+
)
148+
}
124149
} else if (type === CODE_TARGET_TYPE) {
125150
const quickPick = createRuntimeQuickPick({
126151
showImageRuntimes: false,
@@ -151,21 +176,6 @@ export async function addSamDebugConfiguration(
151176
// User backed out of runtime selection. Abandon config creation.
152177
return
153178
}
154-
} else if (type === API_TARGET_TYPE) {
155-
// If the event has no properties, the default will be used
156-
const preloadedConfig = {
157-
path: apiEvent?.event.Properties?.Path,
158-
httpMethod: apiEvent?.event.Properties?.Method,
159-
payload: apiEvent?.event.Properties?.Payload,
160-
}
161-
162-
samDebugConfig = createApiAwsSamDebugConfig(
163-
workspaceFolder,
164-
runtimeName,
165-
resourceName,
166-
rootUri.fsPath,
167-
preloadedConfig
168-
)
169179
} else {
170180
throw new Error('Unrecognized debug target type')
171181
}

0 commit comments

Comments
 (0)