Skip to content

Commit 6fbe550

Browse files
Merge master into feature/q-dev-execution
2 parents b165859 + b07ec98 commit 6fbe550

File tree

10 files changed

+135
-37
lines changed

10 files changed

+135
-37
lines changed

packages/core/package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
"AWS.command.appBuilder.deploy": "Deploy SAM Application",
9393
"AWS.command.appBuilder.build": "Build SAM Template",
9494
"AWS.command.appBuilder.searchLogs": "Search Logs",
95+
"AWS.command.appBuilder.tailLogs": "Tail Logs",
9596
"AWS.command.refreshappBuilderExplorer": "Refresh Application Builder Explorer",
9697
"AWS.command.applicationComposer.openDialog": "Open Template with Infrastructure Composer...",
9798
"AWS.command.auth.addConnection": "Add New Connection",

packages/core/src/awsService/appBuilder/explorer/nodes/deployedNode.ts

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { createPlaceholderItem } from '../../../../shared/treeview/utils'
1010
import * as nls from 'vscode-nls'
1111

1212
import { getLogger } from '../../../../shared/logger/logger'
13-
import { FunctionConfiguration, LambdaClient, GetFunctionCommand } from '@aws-sdk/client-lambda'
1413
import { DefaultLambdaClient } from '../../../../shared/clients/lambdaClient'
1514
import globals from '../../../../shared/extensionGlobals'
1615
import { defaultPartition } from '../../../../shared/regions/regionProvider'
@@ -28,7 +27,6 @@ import {
2827
s3BucketType,
2928
} from '../../../../shared/cloudformation/cloudformation'
3029
import { ToolkitError } from '../../../../shared'
31-
import { getIAMConnection } from '../../../../auth/utils'
3230

3331
const localize = nls.loadMessageBundle()
3432
export interface DeployedResource {
@@ -89,8 +87,6 @@ export async function generateDeployedNode(
8987
const defaultClient = new DefaultLambdaClient(regionCode)
9088
const lambdaNode = new LambdaNode(regionCode, defaultClient)
9189
let configuration: Lambda.FunctionConfiguration
92-
let v3configuration
93-
let logGroupName
9490
try {
9591
configuration = (await defaultClient.getFunction(deployedResource.PhysicalResourceId))
9692
.Configuration as Lambda.FunctionConfiguration
@@ -101,31 +97,6 @@ export async function generateDeployedNode(
10197
code: 'lambdaClientError',
10298
})
10399
}
104-
const connection = await getIAMConnection({ prompt: false })
105-
if (!connection || connection.type !== 'iam') {
106-
return [
107-
createPlaceholderItem(
108-
localize(
109-
'AWS.appBuilder.explorerNode.unavailableDeployedResource',
110-
'[Failed to retrive deployed resource. Ensure your AWS account is connected.]'
111-
)
112-
),
113-
]
114-
}
115-
const cred = await connection.getCredentials()
116-
const v3Client = new LambdaClient({ region: regionCode, credentials: cred })
117-
118-
const v3command = new GetFunctionCommand({ FunctionName: deployedResource.PhysicalResourceId })
119-
try {
120-
v3configuration = (await v3Client.send(v3command)).Configuration as FunctionConfiguration
121-
logGroupName = v3configuration.LoggingConfig?.LogGroup
122-
} catch (error: any) {
123-
getLogger().error('Error getting Lambda V3 configuration: %O', error)
124-
}
125-
newDeployedResource.configuration = {
126-
...newDeployedResource.configuration,
127-
logGroupName: logGroupName,
128-
} as any
129100
break
130101
}
131102
case s3BucketType: {

packages/core/src/awsService/cloudWatchLogs/activation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ import { getLogger } from '../../shared/logger/logger'
2828
import { ToolkitError } from '../../shared'
2929
import { LiveTailCodeLensProvider } from './document/liveTailCodeLensProvider'
3030

31+
export const liveTailRegistry = LiveTailSessionRegistry.instance
32+
export const liveTailCodeLensProvider = new LiveTailCodeLensProvider(liveTailRegistry)
3133
export async function activate(context: vscode.ExtensionContext, configuration: Settings): Promise<void> {
3234
const registry = LogDataRegistry.instance
33-
const liveTailRegistry = LiveTailSessionRegistry.instance
3435

3536
const documentProvider = new LogDataDocumentProvider(registry)
3637
const liveTailDocumentProvider = new LiveTailDocumentProvider()
37-
const liveTailCodeLensProvider = new LiveTailCodeLensProvider(liveTailRegistry)
3838
context.subscriptions.push(
3939
vscode.languages.registerCodeLensProvider(
4040
{
@@ -150,7 +150,7 @@ export async function activate(context: vscode.ExtensionContext, configuration:
150150
)
151151
}
152152

153-
function getFunctionLogGroupName(configuration: any) {
153+
export function getFunctionLogGroupName(configuration: any) {
154154
const logGroupPrefix = '/aws/lambda/'
155-
return configuration.logGroupName || logGroupPrefix + configuration.FunctionName
155+
return configuration.LoggingConfig?.LogGroup || logGroupPrefix + configuration.FunctionName
156156
}

packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@ import {
1717
import { getLogger, globals, ToolkitError } from '../../../shared'
1818
import { uriToKey } from '../cloudWatchLogsUtils'
1919
import { LiveTailCodeLensProvider } from '../document/liveTailCodeLensProvider'
20+
import { LogStreamFilterResponse } from '../wizard/liveTailLogStreamSubmenu'
2021

2122
export async function tailLogGroup(
2223
registry: LiveTailSessionRegistry,
2324
source: string,
2425
codeLensProvider: LiveTailCodeLensProvider,
25-
logData?: { regionName: string; groupName: string }
26+
logData?: { regionName: string; groupName: string },
27+
logStreamFilterData?: LogStreamFilterResponse
2628
): Promise<void> {
2729
await telemetry.cloudwatchlogs_startLiveTail.run(async (span) => {
28-
const wizard = new TailLogGroupWizard(logData)
30+
const wizard = new TailLogGroupWizard(logData, logStreamFilterData)
2931
const wizardResponse = await wizard.run()
3032
if (!wizardResponse) {
3133
throw new CancellationError('user')

packages/core/src/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export interface TailLogGroupWizardResponse {
2424
}
2525

2626
export class TailLogGroupWizard extends Wizard<TailLogGroupWizardResponse> {
27-
public constructor(logGroupInfo?: CloudWatchLogsGroupInfo) {
27+
public constructor(logGroupInfo?: CloudWatchLogsGroupInfo, logStreamInfo?: LogStreamFilterResponse) {
2828
super({
2929
initState: {
3030
regionLogGroupSubmenuResponse: logGroupInfo
@@ -33,6 +33,7 @@ export class TailLogGroupWizard extends Wizard<TailLogGroupWizardResponse> {
3333
region: logGroupInfo.regionName,
3434
}
3535
: undefined,
36+
logStreamFilter: logStreamInfo ?? undefined,
3637
},
3738
})
3839
this.form.regionLogGroupSubmenuResponse.bindPrompter(createRegionLogGroupSubmenu)

packages/core/src/lambda/activation.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import * as vscode from 'vscode'
7+
import { Lambda } from 'aws-sdk'
78
import { deleteLambda } from './commands/deleteLambda'
89
import { uploadLambdaCommand } from './commands/uploadLambda'
910
import { LambdaFunctionNode } from './explorer/lambdaFunctionNode'
@@ -18,6 +19,11 @@ import { copyLambdaUrl } from './commands/copyLambdaUrl'
1819
import { ResourceNode } from '../awsService/appBuilder/explorer/nodes/resourceNode'
1920
import { isTreeNode, TreeNode } from '../shared/treeview/resourceTreeDataProvider'
2021
import { getSourceNode } from '../shared/utilities/treeNodeUtils'
22+
import { tailLogGroup } from '../awsService/cloudWatchLogs/commands/tailLogGroup'
23+
import { liveTailRegistry, liveTailCodeLensProvider } from '../awsService/cloudWatchLogs/activation'
24+
import { getFunctionLogGroupName } from '../awsService/cloudWatchLogs/activation'
25+
import { ToolkitError, isError } from '../shared'
26+
import { LogStreamFilterResponse } from '../awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu'
2127

2228
/**
2329
* Activates Lambda components.
@@ -76,6 +82,40 @@ export async function activate(context: ExtContext): Promise<void> {
7682

7783
Commands.register('aws.launchDebugConfigForm', async (node: ResourceNode) =>
7884
registerSamDebugInvokeVueCommand(context.extensionContext, { resource: node })
79-
)
85+
),
86+
87+
Commands.register('aws.appBuilder.tailLogs', async (node: LambdaFunctionNode | TreeNode) => {
88+
let functionConfiguration: Lambda.FunctionConfiguration
89+
try {
90+
const sourceNode = getSourceNode<LambdaFunctionNode>(node)
91+
functionConfiguration = sourceNode.configuration
92+
const logGroupInfo = {
93+
regionName: sourceNode.regionCode,
94+
groupName: getFunctionLogGroupName(functionConfiguration),
95+
}
96+
97+
const source = isTreeNode(node) ? 'AppBuilder' : 'AwsExplorerLambdaNode'
98+
// Show all log streams without having to choose
99+
const logStreamFilterData: LogStreamFilterResponse = { type: 'all' }
100+
await tailLogGroup(
101+
liveTailRegistry,
102+
source,
103+
liveTailCodeLensProvider,
104+
logGroupInfo,
105+
logStreamFilterData
106+
)
107+
} catch (err) {
108+
if (isError(err as Error, 'ResourceNotFoundException', "LogGroup doesn't exist.")) {
109+
// If we caught this error, then we know `functionConfiguration` actually has a value
110+
throw ToolkitError.chain(
111+
err,
112+
`Unable to fetch logs. Log group for function '${functionConfiguration!.FunctionName}' does not exist. ` +
113+
'Invoking your function at least once will create the log group.'
114+
)
115+
} else {
116+
throw err
117+
}
118+
}
119+
})
80120
)
81121
}

packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { getTestWindow } from '../../../shared/vscode/window'
1919
import { CloudWatchLogsSettings, uriToKey } from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils'
2020
import { DefaultAwsContext, ToolkitError, waitUntil } from '../../../../shared'
2121
import { LiveTailCodeLensProvider } from '../../../../awsService/cloudWatchLogs/document/liveTailCodeLensProvider'
22+
import { PrompterTester } from '../../../shared/wizards/prompterTester'
2223

2324
describe('TailLogGroup', function () {
2425
const testLogGroup = 'test-log-group'
@@ -142,6 +143,48 @@ describe('TailLogGroup', function () {
142143
assert.strictEqual(stopLiveTailSessionSpy.calledOnce, true)
143144
})
144145

146+
it(`doesn't ask for stream filter if passed as parameter`, async function () {
147+
sandbox.stub(DefaultAwsContext.prototype, 'getCredentialAccountId').returns(testAwsAccountId)
148+
sandbox.stub(DefaultAwsContext.prototype, 'getCredentials').returns(Promise.resolve(testAwsCredentials))
149+
150+
// Returns one frame
151+
async function* generator(): AsyncIterable<StartLiveTailResponseStream> {
152+
yield getSessionUpdateFrame(false, `${testMessage}-1`, 1732276800000)
153+
}
154+
155+
startLiveTailSessionSpy = sandbox
156+
.stub(LiveTailSession.prototype, 'startLiveTailSession')
157+
.returns(Promise.resolve(generator()))
158+
159+
const prompterTester = PrompterTester.init()
160+
.handleInputBox('Provide log event filter pattern', (inputBox) => {
161+
inputBox.acceptValue('filter')
162+
})
163+
.build()
164+
165+
// Set maxLines to 1.
166+
cloudwatchSettingsSpy = sandbox.stub(CloudWatchLogsSettings.prototype, 'get').returns(1)
167+
168+
// The mock stream doesn't 'close', causing tailLogGroup to not return. If we `await`, it will never resolve.
169+
// Run it in the background and use waitUntil to poll its state.
170+
void tailLogGroup(
171+
registry,
172+
testSource,
173+
codeLensProvider,
174+
{
175+
groupName: testLogGroup,
176+
regionName: testRegion,
177+
},
178+
{ type: 'all' }
179+
)
180+
await waitUntil(async () => registry.size !== 0, { interval: 100, timeout: 1000 })
181+
182+
prompterTester.assertCall('Provide log event filter pattern', 1)
183+
184+
assert.strictEqual(startLiveTailSessionSpy.calledOnce, true)
185+
assert.strictEqual(registry.size, 1)
186+
})
187+
145188
it('throws if crendentials are undefined', async function () {
146189
sandbox.stub(DefaultAwsContext.prototype, 'getCredentials').returns(Promise.resolve(undefined))
147190
wizardSpy = sandbox.stub(TailLogGroupWizard.prototype, 'run').callsFake(async function () {

packages/core/src/test/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,21 @@ describe('TailLogGroupWizard', async function () {
4444
tester.filterPattern.assertShowSecond()
4545
})
4646

47+
it('skips logStream filter when logStream info is provided', async function () {
48+
sandbox.stub(DefaultAwsContext.prototype, 'getCredentialAccountId').returns(testAwsAccountId)
49+
const wizard = new TailLogGroupWizard(
50+
{
51+
groupName: testLogGroupName,
52+
regionName: testRegion,
53+
},
54+
{ type: 'specific', filter: 'log-group-name' }
55+
)
56+
const tester = await createWizardTester(wizard)
57+
tester.regionLogGroupSubmenuResponse.assertDoesNotShow()
58+
tester.logStreamFilter.assertDoesNotShow()
59+
tester.filterPattern.assertShowFirst()
60+
})
61+
4762
it('builds LogGroup Arn properly', async function () {
4863
sandbox.stub(DefaultAwsContext.prototype, 'getCredentialAccountId').returns(testAwsAccountId)
4964
const arn = buildLogGroupArn(testLogGroupName, testRegion)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "From the Lambda treeview in AWS Explorer, you can now right-click on a function name and start a CloudWatch Logs Live Tail sessions for the selected function."
4+
}

packages/toolkit/package.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,10 @@
902902
"command": "aws.appBuilderForFileExplorer.refresh",
903903
"when": "false"
904904
},
905+
{
906+
"command": "aws.appBuilder.tailLogs",
907+
"when": "false"
908+
},
905909
{
906910
"command": "aws.appBuilder.viewDocs",
907911
"when": "false"
@@ -1641,6 +1645,11 @@
16411645
"when": "view =~ /^(aws.explorer|aws.appBuilder|aws.appBuilderForFileExplorer)$/ && viewItem =~ /^(awsRegionFunctionNode|awsRegionFunctionNodeDownloadable|awsCloudFormationFunctionNode)$/",
16421646
"group": "0@3"
16431647
},
1648+
{
1649+
"command": "aws.appBuilder.tailLogs",
1650+
"when": "view =~ /^(aws.explorer|aws.appBuilder|aws.appBuilderForFileExplorer)$/ && viewItem =~ /^(awsRegionFunctionNode|awsRegionFunctionNodeDownloadable|awsCloudFormationFunctionNode)$/",
1651+
"group": "0@4"
1652+
},
16441653
{
16451654
"command": "aws.deleteCloudFormation",
16461655
"when": "view == aws.explorer && viewItem == awsCloudFormationNode",
@@ -2140,6 +2149,11 @@
21402149
"command": "aws.appBuilder.searchLogs",
21412150
"when": "view =~ /^(aws.explorer|aws.appBuilder|aws.appBuilderForFileExplorer)$/ && viewItem =~ /^(awsRegionFunctionNode|awsRegionFunctionNodeDownloadable|awsCloudFormationFunctionNode)$/",
21422151
"group": "inline@2"
2152+
},
2153+
{
2154+
"command": "aws.appBuilder.tailLogs",
2155+
"when": "view =~ /^(aws.explorer|aws.appBuilder|aws.appBuilderForFileExplorer)$/ && viewItem =~ /^(awsRegionFunctionNode|awsRegionFunctionNodeDownloadable|awsCloudFormationFunctionNode)$/",
2156+
"group": "inline@3"
21432157
}
21442158
],
21452159
"aws.toolkit.auth": [
@@ -3749,6 +3763,13 @@
37493763
"category": "%AWS.title%",
37503764
"icon": "$(search-view-icon)"
37513765
},
3766+
{
3767+
"command": "aws.appBuilder.tailLogs",
3768+
"title": "%AWS.command.appBuilder.tailLogs%",
3769+
"enablement": "isCloud9 || !aws.isWebExtHost",
3770+
"category": "%AWS.title%",
3771+
"icon": "$(search-show-context)"
3772+
},
37523773
{
37533774
"command": "aws.appBuilder.deploy",
37543775
"title": "%AWS.command.appBuilder.deploy%",

0 commit comments

Comments
 (0)