Skip to content

Commit 919f141

Browse files
Vandita2020Vandita Patidar
authored andcommitted
feat(lambda): download serverless land patterns in IDE (aws#6612)
## Problem This pull request introduces the functionality to allow users to download the selected pattern from the Serverless Land QuickPick flow into a directory of their choice. ## Solution The key changes made are as follows: 1. The `metadata.json` file was updated to accommodate the addition of an `asset_name` field for each IaC and Runtime pair. 2. Consequently, the way the metadata is extracted in the `metadataManager.ts` file had to be modified to account for these changes in the `metadata.json` file. 3. To enable users to navigate back to the previous QuickPick options, a switch functionality was implemented in the `wizard.ts` file. This helps maintain the state of the current QuickPick and directs the user to the previous QuickPick. --- - Treat all work as PUBLIC. Private `feature/x` branches will not be squash-merged at release time. - Your code changes must meet the guidelines in [CONTRIBUTING.md](https://github.com/aws/aws-toolkit-vscode/blob/master/CONTRIBUTING.md#guidelines). - License: I confirm that my contribution is made under the terms of the Apache 2.0 license. --------- Co-authored-by: Vandita Patidar <[email protected]>
1 parent fee8ae5 commit 919f141

File tree

4 files changed

+254
-203
lines changed

4 files changed

+254
-203
lines changed

packages/core/src/awsService/appBuilder/serverlessLand/main.ts

Lines changed: 79 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,22 @@
66
import * as nls from 'vscode-nls'
77
const localize = nls.loadMessageBundle()
88
import * as path from 'path'
9+
import * as vscode from 'vscode'
910
import { getTelemetryReason, getTelemetryResult } from '../../../shared/errors'
1011
import { getLogger } from '../../../shared/logger/logger'
11-
import globals from '../../../shared/extensionGlobals'
1212
import { checklogs } from '../../../shared/localizedText'
1313
import { Result, telemetry } from '../../../shared/telemetry/telemetry'
14-
import { CreateServerlessLandWizard } from './wizard'
14+
import { CreateServerlessLandWizardForm, CreateServerlessLandWizard } from './wizard'
1515
import { ExtContext } from '../../../shared/extensions'
1616
import { addFolderToWorkspace } from '../../../shared/utilities/workspaceUtils'
17+
import { ToolkitError } from '../../../shared/errors'
18+
import { fs } from '../../../shared/fs/fs'
19+
import { getPattern } from '../../../shared/utilities/downloadPatterns'
20+
import { MetadataManager } from './metadataManager'
1721

1822
export const readmeFile: string = 'README.md'
23+
const serverlessLandOwner = 'aws-samples'
24+
const serverlessLandRepo = 'serverless-patterns'
1925

2026
/**
2127
* Creates a new Serverless Land project using the provided extension context
@@ -31,38 +37,33 @@ export const readmeFile: string = 'README.md'
3137
* 6. Handles errors and emits telemetry
3238
*/
3339
export async function createNewServerlessLandProject(extContext: ExtContext): Promise<void> {
34-
const awsContext = extContext.awsContext
3540
let createResult: Result = 'Succeeded'
3641
let reason: string | undefined
42+
let metadataManager: MetadataManager
3743

3844
try {
39-
const credentials = await awsContext.getCredentials()
40-
const defaultRegion = awsContext.getCredentialDefaultRegion()
41-
45+
metadataManager = MetadataManager.getInstance()
4246
// Launch the project creation wizard
43-
const config = await new CreateServerlessLandWizard({
44-
credentials,
45-
defaultRegion,
46-
}).run()
47+
const config = await launchProjectCreationWizard(extContext)
4748
if (!config) {
4849
createResult = 'Cancelled'
4950
reason = 'userCancelled'
5051
return
5152
}
53+
const assetName = metadataManager.getAssetName(config.pattern, config.runtime, config.iac)
5254

53-
// Add the project folder to the workspace
55+
await downloadPatternCode(config, assetName)
56+
await openReadmeFile(config)
5457
await addFolderToWorkspace(
5558
{
56-
uri: config.location,
57-
name: path.basename(config.location.fsPath),
59+
uri: vscode.Uri.joinPath(config.location, config.name),
60+
name: path.basename(config.name),
5861
},
5962
true
6063
)
6164
} catch (err) {
6265
createResult = getTelemetryResult(err)
6366
reason = getTelemetryReason(err)
64-
65-
globals.outputChannel.show(true)
6667
getLogger().error(
6768
localize(
6869
'AWS.serverlessland.initWizard.general.error',
@@ -80,3 +81,66 @@ export async function createNewServerlessLandProject(extContext: ExtContext): Pr
8081
})
8182
}
8283
}
84+
85+
async function launchProjectCreationWizard(
86+
extContext: ExtContext
87+
): Promise<CreateServerlessLandWizardForm | undefined> {
88+
const awsContext = extContext.awsContext
89+
const credentials = await awsContext.getCredentials()
90+
const defaultRegion = awsContext.getCredentialDefaultRegion()
91+
92+
return new CreateServerlessLandWizard({
93+
credentials,
94+
defaultRegion,
95+
}).run()
96+
}
97+
98+
async function downloadPatternCode(config: CreateServerlessLandWizardForm, assetName: string): Promise<void> {
99+
const fullAssetName = assetName + '.zip'
100+
const location = vscode.Uri.joinPath(config.location, config.name)
101+
try {
102+
await getPattern(serverlessLandOwner, serverlessLandRepo, fullAssetName, location, true)
103+
} catch (error) {
104+
if (error instanceof ToolkitError) {
105+
throw error
106+
}
107+
throw new ToolkitError(`Failed to download pattern: ${error}`)
108+
}
109+
}
110+
111+
async function openReadmeFile(config: CreateServerlessLandWizardForm): Promise<void> {
112+
try {
113+
const readmeUri = await getProjectUri(config, readmeFile)
114+
if (!readmeUri) {
115+
getLogger().warn('README.md file not found in the project directory')
116+
return
117+
}
118+
119+
await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup')
120+
await vscode.window.showTextDocument(readmeUri)
121+
} catch (err) {
122+
getLogger().error(`Error in openReadmeFile: ${err}`)
123+
throw new ToolkitError('Error processing README file')
124+
}
125+
}
126+
127+
async function getProjectUri(
128+
config: Pick<CreateServerlessLandWizardForm, 'location' | 'name'>,
129+
file: string
130+
): Promise<vscode.Uri | undefined> {
131+
if (!file) {
132+
throw Error('expected "file" parameter to have at least one item')
133+
}
134+
const cfnTemplatePath = path.resolve(config.location.fsPath, config.name, file)
135+
if (await fs.exists(cfnTemplatePath)) {
136+
return vscode.Uri.file(cfnTemplatePath)
137+
}
138+
void vscode.window.showWarningMessage(
139+
localize(
140+
'AWS.serverlessLand.initWizard.source.error.notFound',
141+
'Project created successfully, but {0} file not found: {1}',
142+
file!,
143+
cfnTemplatePath!
144+
)
145+
)
146+
}

packages/core/src/awsService/appBuilder/serverlessLand/metadata.json

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,56 +3,52 @@
33
"s3-lambda-resizing-sam": {
44
"name": "Resizing image",
55
"description": "Lambda, S3 • Python, Javascript, Java, .NET • SAM",
6-
"runtimes": [
6+
"implementation": [
77
{
8-
"id": "python",
9-
"name": "Python"
8+
"iac": "sam",
9+
"runtime": "python",
10+
"assetName": "s3-lambda-resizing-python"
1011
},
1112
{
12-
"id": "javascript",
13-
"name": "Javascript"
13+
"iac": "sam",
14+
"runtime": "javascript",
15+
"assetName": "s3-lambda"
1416
},
1517
{
16-
"id": "dotnet",
17-
"name": "Dotnet"
18+
"iac": "sam",
19+
"runtime": "java",
20+
"assetName": "s3-lambda-resizing-java"
1821
},
1922
{
20-
"id": "java",
21-
"name": "Java"
22-
}
23-
],
24-
"iac": [
25-
{
26-
"id": "sam",
27-
"name": "SAM"
23+
"iac": "sam",
24+
"runtime": "dotnet",
25+
"assetName": "s3-lambda-dotnet"
2826
}
2927
]
3028
},
3129
"apigw-rest-api-lambda-sam": {
3230
"name": "Rest API",
3331
"description": "Lambda, API Gateway • Python, Javascript, Java, .NET • SAM",
34-
"runtimes": [
32+
"implementation": [
3533
{
36-
"id": "python",
37-
"name": "Python"
34+
"iac": "sam",
35+
"runtime": "python",
36+
"assetName": "apigw-rest-api-lambda-python"
3837
},
3938
{
40-
"id": "javascript",
41-
"name": "Javascript"
39+
"iac": "sam",
40+
"runtime": "javascript",
41+
"assetName": "apigw-rest-api-lambda-node"
4242
},
4343
{
44-
"id": "dotnet",
45-
"name": "Dotnet"
44+
"iac": "sam",
45+
"runtime": "java",
46+
"assetName": "apigw-rest-api-lambda-java"
4647
},
4748
{
48-
"id": "java",
49-
"name": "Java"
50-
}
51-
],
52-
"iac": [
53-
{
54-
"id": "sam",
55-
"name": "AWS SAM"
49+
"iac": "sam",
50+
"runtime": "dotnet",
51+
"assetName": "apigw-rest-api-lambda-dotnet"
5652
}
5753
]
5854
}

packages/core/src/awsService/appBuilder/serverlessLand/metadataManager.ts

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,17 @@
44
*/
55
import * as nodefs from 'fs' // eslint-disable-line no-restricted-imports
66
import { ToolkitError } from '../../../shared/errors'
7+
import path from 'path'
78

8-
interface IaC {
9-
id: string
10-
name: string
11-
}
12-
interface Runtime {
13-
id: string
14-
name: string
15-
version: string
9+
interface Implementation {
10+
iac: string
11+
runtime: string
12+
assetName: string
1613
}
1714
interface PatternData {
1815
name: string
1916
description: string
20-
runtimes: Runtime[]
21-
iac: IaC[]
17+
implementation: Implementation[]
2218
}
2319

2420
export interface ProjectMetadata {
@@ -32,6 +28,14 @@ export interface ProjectMetadata {
3228
export class MetadataManager {
3329
private static instance: MetadataManager
3430
private metadata: ProjectMetadata | undefined
31+
private static readonly metadataPath = path.join(
32+
path.resolve(__dirname, '../../../../../'),
33+
'src',
34+
'awsService',
35+
'appBuilder',
36+
'serverlessLand',
37+
'metadata.json'
38+
)
3539

3640
private constructor() {}
3741

@@ -42,6 +46,14 @@ export class MetadataManager {
4246
return MetadataManager.instance
4347
}
4448

49+
public static initialize(): MetadataManager {
50+
const instance = MetadataManager.getInstance()
51+
instance.loadMetadata(MetadataManager.metadataPath).catch((err) => {
52+
throw new ToolkitError(`Failed to load metadata: ${err}`)
53+
})
54+
return instance
55+
}
56+
4557
/**
4658
* Loads metadata from a JSON file
4759
* @param metadataPath Path to the metadata JSON file
@@ -96,11 +108,12 @@ export class MetadataManager {
96108
*/
97109
public getRuntimes(pattern: string): { label: string }[] {
98110
const patternData = this.metadata?.patterns?.[pattern]
99-
if (!patternData || !patternData.runtimes) {
111+
if (!patternData || !patternData.implementation) {
100112
return []
101113
}
102-
return patternData.runtimes.map((runtime) => ({
103-
label: runtime.name,
114+
const uniqueRuntimes = new Set(patternData.implementation.map((item) => item.runtime))
115+
return Array.from(uniqueRuntimes).map((runtime) => ({
116+
label: runtime,
104117
}))
105118
}
106119

@@ -111,11 +124,22 @@ export class MetadataManager {
111124
*/
112125
public getIacOptions(pattern: string): { label: string }[] {
113126
const patternData = this.metadata?.patterns?.[pattern]
114-
if (!patternData || !patternData.iac) {
127+
if (!patternData || !patternData.implementation) {
115128
return []
116129
}
117-
return patternData.iac.map((iac) => ({
118-
label: iac.name,
130+
const uniqueIaCs = new Set(patternData.implementation.map((item) => item.iac))
131+
return Array.from(uniqueIaCs).map((iac) => ({
132+
label: iac,
119133
}))
120134
}
135+
public getAssetName(selectedPattern: string, selectedRuntime: string, selectedIaC: string): string {
136+
const patternData = this.metadata?.patterns?.[selectedPattern]
137+
if (!patternData || !patternData.implementation) {
138+
return ''
139+
}
140+
const matchingImplementation = patternData.implementation.find(
141+
(impl) => impl.runtime === selectedRuntime && impl.iac === selectedIaC
142+
)
143+
return matchingImplementation?.assetName ?? ''
144+
}
121145
}

0 commit comments

Comments
 (0)