Skip to content

Commit 259ccdb

Browse files
authored
Merge together template registry and template registry manager (#1391)
- Merge together template registry and template registry manager - Move template registry to ext as it's global like telemetry, etc where we register one instance at start then use it until deactivation - This is a replacement for the existing singleton
1 parent 5d1f180 commit 259ccdb

17 files changed

+200
-233
lines changed

src/integrationTest/cloudformation/templateRegistryManager.test.ts renamed to src/integrationTest/cloudformation/templateRegistry.test.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@ import * as path from 'path'
77
import * as fs from 'fs-extra'
88

99
import { CloudFormationTemplateRegistry } from '../../shared/cloudformation/templateRegistry'
10-
import { CloudFormationTemplateRegistryManager } from '../../shared/cloudformation/templateRegistryManager'
1110
import { makeSampleSamTemplateYaml, strToYamlFile } from '../../test/shared/cloudformation/cloudformationTestUtils'
1211
import { getTestWorkspaceFolder } from '../integrationTestsUtilities'
1312

1413
/**
1514
* Note: these tests are pretty shallow right now. They do not test the following:
1615
* * Adding/removing workspace folders
1716
*/
18-
describe('CloudFormation Template Registry Manager', async () => {
17+
describe('CloudFormation Template Registry', async () => {
1918
let registry: CloudFormationTemplateRegistry
20-
let manager: CloudFormationTemplateRegistryManager
2119
let workspaceDir: string
2220
let testDir: string
2321
let testDirNested: string
@@ -32,11 +30,10 @@ describe('CloudFormation Template Registry Manager', async () => {
3230
testDirNested = path.join(testDir, 'nested')
3331
await fs.mkdirp(testDirNested)
3432
registry = new CloudFormationTemplateRegistry()
35-
manager = new CloudFormationTemplateRegistryManager(registry)
3633
})
3734

3835
afterEach(async () => {
39-
manager.dispose()
36+
registry.dispose()
4037
await fs.remove(testDir)
4138
dir++
4239
})
@@ -45,13 +42,13 @@ describe('CloudFormation Template Registry Manager', async () => {
4542
await strToYamlFile(makeSampleSamTemplateYaml(true), path.join(testDir, 'test.yaml'))
4643
await strToYamlFile(makeSampleSamTemplateYaml(false), path.join(testDirNested, 'test.yml'))
4744

48-
await manager.addTemplateGlob('**/test.{yaml,yml}')
45+
await registry.addTemplateGlob('**/test.{yaml,yml}')
4946

5047
await registryHasTargetNumberOfFiles(registry, 2)
5148
})
5249

5350
it('adds dynamically-added template files with yaml and yml extensions at various nesting levels', async () => {
54-
await manager.addTemplateGlob('**/test.{yaml,yml}')
51+
await registry.addTemplateGlob('**/test.{yaml,yml}')
5552

5653
await strToYamlFile(makeSampleSamTemplateYaml(false), path.join(testDir, 'test.yml'))
5754
await strToYamlFile(makeSampleSamTemplateYaml(true), path.join(testDirNested, 'test.yaml'))
@@ -60,8 +57,8 @@ describe('CloudFormation Template Registry Manager', async () => {
6057
})
6158

6259
it('Ignores templates matching excluded patterns', async () => {
63-
await manager.addTemplateGlob('**/test.{yaml,yml}')
64-
await manager.addExcludedPattern(/.*nested.*/)
60+
await registry.addTemplateGlob('**/test.{yaml,yml}')
61+
await registry.addExcludedPattern(/.*nested.*/)
6562

6663
await strToYamlFile(makeSampleSamTemplateYaml(false), path.join(testDir, 'test.yml'))
6764
await strToYamlFile(makeSampleSamTemplateYaml(true), path.join(testDirNested, 'test.yaml'))
@@ -73,7 +70,7 @@ describe('CloudFormation Template Registry Manager', async () => {
7370
const filepath = path.join(testDir, 'changeMe.yml')
7471
await strToYamlFile(makeSampleSamTemplateYaml(false), filepath)
7572

76-
await manager.addTemplateGlob('**/changeMe.yml')
73+
await registry.addTemplateGlob('**/changeMe.yml')
7774

7875
await registryHasTargetNumberOfFiles(registry, 1)
7976

@@ -85,7 +82,7 @@ describe('CloudFormation Template Registry Manager', async () => {
8582
})
8683

8784
it('can handle deleted files', async () => {
88-
await manager.addTemplateGlob('**/deleteMe.yml')
85+
await registry.addTemplateGlob('**/deleteMe.yml')
8986

9087
// Specifically creating the file after the watcher is added
9188
// Otherwise, it seems the file is deleted before the file watcher realizes the file exists

src/lambda/local/debugConfiguration.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import * as path from 'path'
77
import * as vscode from 'vscode'
88
import { CloudFormation } from '../../shared/cloudformation/cloudformation'
9-
import { CloudFormationTemplateRegistry } from '../../shared/cloudformation/templateRegistry'
109
import {
1110
AwsSamDebuggerConfiguration,
1211
AWS_SAM_DEBUG_TARGET_TYPES,
@@ -18,6 +17,7 @@ import { localize } from '../../shared/utilities/vsCodeUtils'
1817
import { tryGetAbsolutePath } from '../../shared/utilities/workspaceUtils'
1918
import { RuntimeFamily } from '../models/samLambdaRuntime'
2019
import { SamLaunchRequestArgs } from '../../shared/sam/debugger/awsSamDebugger'
20+
import { ext } from '../../shared/extensionGlobals'
2121

2222
export const DOTNET_CORE_DEBUGGER_PATH = '/tmp/lambci_debug_files/vsdbg'
2323

@@ -155,9 +155,8 @@ export function getTemplate(
155155
return undefined
156156
}
157157
const templateInvoke = config.invokeTarget as TemplateTargetProperties
158-
const cftRegistry = CloudFormationTemplateRegistry.getRegistry()
159158
const fullPath = tryGetAbsolutePath(folder, templateInvoke.templatePath)
160-
const cfnTemplate = cftRegistry.getRegisteredTemplate(fullPath)?.template
159+
const cfnTemplate = ext.templateRegistry.getRegisteredTemplate(fullPath)?.template
161160
return cfnTemplate
162161
}
163162

src/lambda/wizards/samDeployWizard.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import {
2929
} from '../../shared/wizards/multiStepWizard'
3030
import { configureParameterOverrides } from '../config/configureParameterOverrides'
3131
import { getOverriddenParameters, getParameters } from '../utilities/parameterUtils'
32-
import { CloudFormationTemplateRegistry } from '../../shared/cloudformation/templateRegistry'
3332
import { ext } from '../../shared/extensionGlobals'
3433

3534
export interface SamDeployWizardResponse {
@@ -620,8 +619,7 @@ function validateStackName(value: string): string | undefined {
620619
}
621620

622621
async function getTemplateChoices(...workspaceFolders: vscode.Uri[]): Promise<SamTemplateQuickPickItem[]> {
623-
const cfnRegistry = CloudFormationTemplateRegistry.getRegistry()
624-
const templateUris = cfnRegistry.registeredTemplates.map(o => vscode.Uri.file(o.path))
622+
const templateUris = ext.templateRegistry.registeredTemplates.map(o => vscode.Uri.file(o.path))
625623
const uriToLabel: Map<vscode.Uri, string> = new Map<vscode.Uri, string>()
626624
const labelCounts: Map<string, number> = new Map()
627625

src/shared/cloudformation/activation.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { getLogger } from '../logger'
88
import { localize } from '../utilities/vsCodeUtils'
99

1010
import { CloudFormationTemplateRegistry } from './templateRegistry'
11-
import { CloudFormationTemplateRegistryManager } from './templateRegistryManager'
11+
import { ext } from '../extensionGlobals'
1212

1313
export const TEMPLATE_FILE_GLOB_PATTERN = '**/template.{yaml,yml}'
1414

@@ -28,11 +28,11 @@ export const TEMPLATE_FILE_EXCLUDE_PATTERN = /.*[/\\]\.aws-sam([/\\].*|$)/
2828
*/
2929
export async function activate(extensionContext: vscode.ExtensionContext): Promise<void> {
3030
try {
31-
const registry = CloudFormationTemplateRegistry.getRegistry()
32-
const manager = new CloudFormationTemplateRegistryManager(registry)
33-
await manager.addExcludedPattern(TEMPLATE_FILE_EXCLUDE_PATTERN)
34-
await manager.addTemplateGlob(TEMPLATE_FILE_GLOB_PATTERN)
35-
extensionContext.subscriptions.push(manager)
31+
const registry = new CloudFormationTemplateRegistry()
32+
await registry.addExcludedPattern(TEMPLATE_FILE_EXCLUDE_PATTERN)
33+
await registry.addTemplateGlob(TEMPLATE_FILE_GLOB_PATTERN)
34+
extensionContext.subscriptions.push(registry)
35+
ext.templateRegistry = registry
3636
} catch (e) {
3737
vscode.window.showErrorMessage(
3838
localize(

src/shared/cloudformation/templateRegistry.ts

Lines changed: 107 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,80 @@
44
*/
55

66
import * as vscode from 'vscode'
7-
import * as path_ from 'path'
87
import { getLogger } from '../logger/logger'
98
import { CloudFormation } from './cloudformation'
109
import * as pathutils from '../utilities/pathUtils'
10+
import * as path from 'path'
1111
import { isInDirectory } from '../filesystemUtilities'
1212
import { dotNetRuntimes } from '../../lambda/models/samLambdaRuntime'
1313
import { getLambdaDetails } from '../../lambda/utils'
14+
import { ext } from '../extensionGlobals'
1415

1516
export interface TemplateDatum {
1617
path: string
1718
template: CloudFormation.Template
1819
}
1920

20-
export class CloudFormationTemplateRegistry {
21-
private static INSTANCE: CloudFormationTemplateRegistry | undefined
22-
private readonly templateRegistryData: Map<string, CloudFormation.Template>
21+
export class CloudFormationTemplateRegistry implements vscode.Disposable {
22+
private readonly disposables: vscode.Disposable[] = []
23+
private _isDisposed: boolean = false
24+
private readonly globs: vscode.GlobPattern[] = []
25+
private readonly excludedFilePatterns: RegExp[] = []
26+
private readonly templateRegistryData: Map<string, CloudFormation.Template> = new Map<
27+
string,
28+
CloudFormation.Template
29+
>()
2330

2431
public constructor() {
25-
this.templateRegistryData = new Map<string, CloudFormation.Template>()
32+
this.disposables.push(
33+
vscode.workspace.onDidChangeWorkspaceFolders(async () => {
34+
await this.rebuildRegistry()
35+
})
36+
)
2637
}
2738

28-
private assertAbsolute(path: string) {
29-
if (!path_.isAbsolute(path)) {
30-
throw Error(`CloudFormationTemplateRegistry: path is relative: ${path}`)
39+
/**
40+
* Adds a glob pattern to use for lookups and resets the registry to use it.
41+
* Added templates cannot be removed without restarting the extension.
42+
* Throws an error if this manager has already been disposed.
43+
* @param glob vscode.GlobPattern to be used for lookups
44+
*/
45+
public async addTemplateGlob(glob: vscode.GlobPattern): Promise<void> {
46+
if (this._isDisposed) {
47+
throw new Error('Manager has already been disposed!')
48+
}
49+
this.globs.push(glob)
50+
51+
const watcher = vscode.workspace.createFileSystemWatcher(glob)
52+
this.addWatcher(watcher)
53+
54+
await this.rebuildRegistry()
55+
}
56+
57+
/**
58+
* Adds a regex pattern to ignore paths containing the pattern
59+
*/
60+
public async addExcludedPattern(pattern: RegExp): Promise<void> {
61+
if (this._isDisposed) {
62+
throw new Error('Manager has already been disposed!')
3163
}
64+
this.excludedFilePatterns.push(pattern)
65+
66+
await this.rebuildRegistry()
3267
}
3368

3469
/**
3570
* Adds template to registry. Wipes any existing template in its place with newly-parsed copy of the data.
3671
* @param templateUri vscode.Uri containing the template to load in
3772
*/
3873
public async addTemplateToRegistry(templateUri: vscode.Uri, quiet?: boolean): Promise<void> {
74+
const excluded = this.excludedFilePatterns.find(pattern => templateUri.fsPath.match(pattern))
75+
if (excluded) {
76+
getLogger().verbose(
77+
`Manager did not add template ${templateUri.fsPath} matching excluded pattern ${excluded}`
78+
)
79+
return
80+
}
3981
const pathAsString = pathutils.normalize(templateUri.fsPath)
4082
this.assertAbsolute(pathAsString)
4183
try {
@@ -91,6 +133,35 @@ export class CloudFormationTemplateRegistry {
91133
this.templateRegistryData.delete(pathAsString)
92134
}
93135

136+
/**
137+
* Disposes CloudFormationTemplateRegistryManager and marks as disposed.
138+
*/
139+
public dispose(): void {
140+
if (!this._isDisposed) {
141+
while (this.disposables.length > 0) {
142+
const disposable = this.disposables.pop()
143+
if (disposable) {
144+
disposable.dispose()
145+
}
146+
}
147+
this._isDisposed = true
148+
}
149+
}
150+
151+
/**
152+
* Rebuilds registry using current glob and exclusion patterns.
153+
* All functionality is currently internal to class, but can be made public if we want a manual "refresh" button
154+
*/
155+
private async rebuildRegistry(): Promise<void> {
156+
this.reset()
157+
for (const glob of this.globs) {
158+
const templateUris = await vscode.workspace.findFiles(glob)
159+
for (const template of templateUris) {
160+
await this.addTemplateToRegistry(template, true)
161+
}
162+
}
163+
}
164+
94165
/**
95166
* Removes all templates from the registry.
96167
*/
@@ -99,15 +170,31 @@ export class CloudFormationTemplateRegistry {
99170
}
100171

101172
/**
102-
* Returns the CloudFormationTemplateRegistry singleton.
103-
* If the singleton doesn't exist, creates it.
173+
* Sets watcher functionality and adds to this.disposables
174+
* @param watcher vscode.FileSystemWatcher
104175
*/
105-
public static getRegistry(): CloudFormationTemplateRegistry {
106-
if (!CloudFormationTemplateRegistry.INSTANCE) {
107-
CloudFormationTemplateRegistry.INSTANCE = new CloudFormationTemplateRegistry()
108-
}
176+
private addWatcher(watcher: vscode.FileSystemWatcher): void {
177+
this.disposables.push(
178+
watcher,
179+
watcher.onDidChange(async uri => {
180+
getLogger().verbose(`Manager detected a change to template file: ${uri.fsPath}`)
181+
await this.addTemplateToRegistry(uri)
182+
}),
183+
watcher.onDidCreate(async uri => {
184+
getLogger().verbose(`Manager detected a new template file: ${uri.fsPath}`)
185+
await this.addTemplateToRegistry(uri)
186+
}),
187+
watcher.onDidDelete(async uri => {
188+
getLogger().verbose(`Manager detected a deleted template file: ${uri.fsPath}`)
189+
this.removeTemplateFromRegistry(uri)
190+
})
191+
)
192+
}
109193

110-
return CloudFormationTemplateRegistry.INSTANCE
194+
private assertAbsolute(p: string) {
195+
if (!path.isAbsolute(p)) {
196+
throw Error(`CloudFormationTemplateRegistry: path is relative: ${p}`)
197+
}
111198
}
112199
}
113200

@@ -121,7 +208,7 @@ export class CloudFormationTemplateRegistry {
121208
export function getResourcesForHandler(
122209
filepath: string,
123210
handler: string,
124-
unfilteredTemplates: TemplateDatum[] = CloudFormationTemplateRegistry.getRegistry().registeredTemplates
211+
unfilteredTemplates: TemplateDatum[] = ext.templateRegistry.registeredTemplates
125212
): { templateDatum: TemplateDatum; name: string; resourceData: CloudFormation.Resource }[] {
126213
// TODO: Array.flat and Array.flatMap not introduced until >= Node11.x -- migrate when VS Code updates Node ver
127214
const o = unfilteredTemplates.map(templateDatum => {
@@ -150,9 +237,9 @@ export function getResourcesForHandlerFromTemplateDatum(
150237
templateDatum: TemplateDatum
151238
): { name: string; resourceData: CloudFormation.Resource }[] {
152239
const matchingResources: { name: string; resourceData: CloudFormation.Resource }[] = []
153-
const templateDirname = path_.dirname(templateDatum.path)
240+
const templateDirname = path.dirname(templateDatum.path)
154241
// template isn't a parent or sibling of file
155-
if (!isInDirectory(templateDirname, path_.dirname(filepath))) {
242+
if (!isInDirectory(templateDirname, path.dirname(filepath))) {
156243
return []
157244
}
158245

@@ -191,7 +278,7 @@ export function getResourcesForHandlerFromTemplateDatum(
191278
if (
192279
handler === registeredHandler &&
193280
isInDirectory(
194-
pathutils.normalize(path_.join(templateDirname, registeredCodeUri)),
281+
pathutils.normalize(path.join(templateDirname, registeredCodeUri)),
195282
pathutils.normalize(filepath)
196283
)
197284
) {
@@ -210,7 +297,7 @@ export function getResourcesForHandlerFromTemplateDatum(
210297
if (
211298
pathutils.normalize(filepath) ===
212299
pathutils.normalize(
213-
path_.join(templateDirname, registeredCodeUri, parsedLambda.fileName)
300+
path.join(templateDirname, registeredCodeUri, parsedLambda.fileName)
214301
) &&
215302
functionName === parsedLambda.functionName
216303
) {

0 commit comments

Comments
 (0)