Skip to content

Commit c7e9487

Browse files
authored
Feat/load specific JSON based on the provider version (#14)
* enable json load * fix multiple json issue * update pre-release.yml * 1.fix the logic of JSON files match. 2.load json files with sync(await). 3.rm pre-release trigger when PR merged.
1 parent 04c8dbc commit c7e9487

File tree

11 files changed

+39986
-37412
lines changed

11 files changed

+39986
-37412
lines changed

.github/workflows/pre-release.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
name: pre-release
22
on:
3-
pull_request:
4-
types:
5-
- closed
63
push:
74
tags:
85
- "v*"
@@ -61,6 +58,6 @@ jobs:
6158
repo_token: "${{ secrets.GITHUB_TOKEN }}"
6259
automatic_release_tag: ${{ env.NEW_TAG }}
6360
prerelease: true
64-
title: "v${{ env.NEW_TAG }}-beta"
61+
title: "${{ env.NEW_TAG }}-beta"
6562
files: |
6663
vscode-tencentcloud-terraform-*.vsix

config/tips/tiat-resources_bak.json

Lines changed: 0 additions & 37366 deletions
This file was deleted.

config/tips/v1.81.54.json

Lines changed: 39771 additions & 0 deletions
Large diffs are not rendered by default.

src/autocomplete/TerraformHoverProvider.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { HoverProvider, TextDocument, Position, CancellationToken, CompletionItem, CompletionItemKind, Hover, ProviderResult } from "vscode";
2-
import resources from '../../config/tips/tiat-resources.json';
32
import * as _ from "lodash";
43

54
var topLevelTypes = ["output", "provider", "resource", "variable", "data"];

src/autocomplete/TerraformTipsProvider.ts

Lines changed: 191 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import { CompletionItemProvider, TextDocument, Position, CancellationToken, CompletionItem, CompletionItemKind } from "vscode";
2-
import resources from '../../config/tips/tiat-resources.json';
2+
// import resources from '../../config/tips/tiat-resources.json';
33
import * as _ from "lodash";
44
import * as vscode from 'vscode';
5-
5+
import { executeCommandByExec } from "@/utils/cpUtils";
6+
import * as fs from "fs";
7+
import * as path from "path";
8+
import * as workspaceUtils from "@/utils/workspaceUtils";
9+
import * as TelemetryWrapper from "vscode-extension-telemetry-wrapper";
10+
11+
const LATEST_VERSION = "latest";
12+
const versionPattern = /^v\d+(\.\d+){2}\.json$/;
613
let topLevelTypes = ["output", "provider", "resource", "variable", "data"];
714
let topLevelRegexes = topLevelTypes.map(o => {
815
return {
@@ -15,6 +22,29 @@ interface TerraformCompletionContext extends vscode.CompletionContext {
1522
resourceType?: string;
1623
}
1724

25+
interface Argument {
26+
name: string;
27+
description: string;
28+
options?: Array<string>;
29+
detail?: Array<Argument>;
30+
}
31+
32+
interface Attribute {
33+
name: string;
34+
description: string;
35+
detail?: Array<Attribute>;
36+
}
37+
38+
interface Tips {
39+
version: string;
40+
resource: {
41+
[key: string]: {
42+
args: Array<Argument>;
43+
attrs: Array<Attribute>;
44+
};
45+
};
46+
}
47+
1848
const TEXT_MIN_SORT = "a";
1949
const TEXT_FILTER = " ";
2050

@@ -26,8 +56,12 @@ export class TerraformTipsProvider implements CompletionItemProvider {
2656
position: Position;
2757
token: CancellationToken;
2858
resourceType: string | null = null;
59+
private extensionPath: string;
60+
constructor(extensionPath: string) {
61+
this.extensionPath = extensionPath;
62+
}
2963

30-
public provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken, context: TerraformCompletionContext): CompletionItem[] {
64+
public async provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken, context: TerraformCompletionContext): Promise<CompletionItem[]> {
3165
this.document = document;
3266
this.position = position;
3367
this.token = token;
@@ -36,7 +70,7 @@ export class TerraformTipsProvider implements CompletionItemProvider {
3670
const lineText = document.lineAt(position.line).text;
3771
const lineTillCurrentPosition = lineText.substring(0, position.character);
3872

39-
// Are we trying to type a top type?
73+
// handle top level definition
4074
if (this.isTopLevelType(lineTillCurrentPosition)) {
4175
return this.getTopLevelType(lineTillCurrentPosition);
4276
}
@@ -76,24 +110,32 @@ export class TerraformTipsProvider implements CompletionItemProvider {
76110
// We're trying to type the exported field for the let
77111
const resourceType = parts[0];
78112
let resourceName = parts[1];
79-
let attrs = resources[resourceType].attrs;
80-
let result = _.map(attrs, o => {
81-
let c = new CompletionItem(`${o.name} (${resourceType})`, CompletionItemKind.Property);
82-
c.detail = o.description;
83-
c.insertText = o.name;
84-
c.sortText = TEXT_MIN_SORT;
85-
return c;
86-
});
87-
return result;
113+
try {
114+
// async load resource config
115+
const tips = await loadResource(this.extensionPath);
116+
const resources = tips.resource;
117+
let attrs = resources[resourceType].attrs;
118+
let result = _.map(attrs, o => {
119+
let c = new CompletionItem(`${o.name}(${resourceType})`, CompletionItemKind.Property);
120+
c.detail = o.description;
121+
c.insertText = o.name;
122+
c.sortText = TEXT_MIN_SORT;
123+
return c;
124+
});
125+
return result;
126+
127+
} catch (error) {
128+
console.error(`Can not load resource from json. error:[${error}]`);
129+
}
88130
}
89131

90132
// Which part are we completing for?
91133
return [];
92134
}
93135

94136
// Are we trying to type a parameter to a resource?
95-
let possibleResources = this.checkTopLevelResource(lineTillCurrentPosition);
96-
// typing a resource type
137+
let possibleResources = await this.checkTopLevelResource(lineTillCurrentPosition);
138+
// handle resource type
97139
if (possibleResources.length > 0) {
98140
return this.getHintsForStrings(possibleResources);
99141
}
@@ -106,28 +148,43 @@ export class TerraformTipsProvider implements CompletionItemProvider {
106148
if (endwithEqual) {
107149
const lineBeforeEqualSign = lineTillCurrentPosition.substring(0, includeEqual).trim();
108150
// load options
109-
const name = lineBeforeEqualSign;
110-
const argStrs = this.findArgByName(resources[this.resourceType].args, name);
111-
const options = this.getOptionsFormArg(argStrs);
112-
// clear resource type
113-
this.resourceType = "";
114-
return (options).length ? options : [];
151+
try {
152+
// async load resource config
153+
const tips = await loadResource(this.extensionPath);
154+
const name = lineBeforeEqualSign;
155+
const resources = tips.resource;
156+
const argStrs = this.findArgByName(resources[this.resourceType].args, name);
157+
const options = this.getOptionsFormArg(argStrs);
158+
// clear resource type
159+
this.resourceType = "";
160+
return (options).length ? options : [];
161+
} catch (error) {
162+
console.error(`Can not load resource from json when loading options. error:[${error}]`);
163+
}
115164
}
116165
this.resourceType = "";
117166
return [];
118167
}
119168

120-
// Check if we're in a resource definition
169+
// handle argument
121170
if (includeEqual < 0 && !endwithEqual) {
122171
// we're not in options case
123172
for (let i = position.line - 1; i >= 0; i--) {
124173
let line = document.lineAt(i).text;
125174
let parentType = this.getParentType(line);
126175
if (parentType && parentType.type === "resource") {
127-
// typing a arg in resource
176+
// typing a argument in resource
128177
const resourceType = this.getResourceTypeFromLine(line);
129-
const ret = this.getItemsForArgs(resources[resourceType].args, resourceType);
130-
return ret;
178+
try {
179+
// async load resource config
180+
const tips = await loadResource(this.extensionPath);
181+
const resources = tips.resource;
182+
const ret = this.getItemsForArgs(resources[resourceType].args, resourceType);
183+
return ret;
184+
} catch (error) {
185+
console.error(`Can not load resource from json when loading argument. error:[${error}]`);
186+
return [];
187+
}
131188
}
132189
else if (parentType && parentType.type !== "resource") {
133190
// We don't want to accidentally include some other containers stuff
@@ -237,18 +294,27 @@ export class TerraformTipsProvider implements CompletionItemProvider {
237294
return "";
238295
}
239296

240-
checkTopLevelResource(lineTillCurrentPosition: string): any[] {
297+
async checkTopLevelResource(lineTillCurrentPosition: string): Promise<any[]> {
241298
let parts = lineTillCurrentPosition.split(" ");
242299
if (parts.length === 2 && parts[0] === "resource") {
243300
let r = parts[1].replace(/"/g, '');
244301
let regex = new RegExp("^" + r);
245-
let possibleResources = _.filter(_.keys(resources), k => {
246-
if (regex.test(k)) {
247-
return true;
248-
}
249-
return false;
250-
});
251-
return possibleResources;
302+
// handle resource
303+
try {
304+
// async load resource config
305+
const tips = await loadResource(this.extensionPath);
306+
const resources = tips.resource;
307+
let possibleResources = _.filter(_.keys(resources), k => {
308+
if (regex.test(k)) {
309+
return true;
310+
}
311+
return false;
312+
});
313+
return possibleResources;
314+
} catch (error) {
315+
console.error(`Can not load resource from json when loading resource type. error:[${error}]`);
316+
return [];
317+
}
252318
}
253319
return [];
254320
}
@@ -295,7 +361,7 @@ export class TerraformTipsProvider implements CompletionItemProvider {
295361
}
296362

297363
const changes = event.contentChanges[0];
298-
if (changes.text === TIPS_OPTIONS_TRIGGER_CHARACTER) {
364+
if (changes && changes.text === TIPS_OPTIONS_TRIGGER_CHARACTER) {
299365
const position = activeEditor.selection.active;
300366
const resourceType = this.findResourceType(event.document, position);
301367

@@ -305,4 +371,94 @@ export class TerraformTipsProvider implements CompletionItemProvider {
305371
}
306372
}
307373
}
308-
}
374+
}
375+
376+
async function sortJsonFiles(dir: string) {
377+
let jsonFiles: string[];
378+
try {
379+
const files = fs.readdirSync(dir);
380+
jsonFiles = files.filter(file => path.extname(file) === '.json' && versionPattern.test(file));
381+
// const jsonFiles: string[] = ["v1.81.50.json", "v1.81.54.json"]; // debug data
382+
} catch (error) {
383+
console.error(`read dir failed. error:[${error}]`);
384+
return null;
385+
}
386+
387+
// import files
388+
const versions = await Promise.all(jsonFiles.map(async file => {
389+
const jsonPath = path.join("../config/tips/", file);
390+
// const json = await import(jsonPath);
391+
const json = require(jsonPath);
392+
const version = json.version as string;
393+
return {
394+
json,
395+
version
396+
};
397+
}));
398+
399+
// sort with version desc
400+
versions.sort((a, b) => compareVersions(b.version, a.version));
401+
return versions;
402+
}
403+
404+
function compareVersions(a, b) {
405+
if (a && !b) { return 1; }
406+
if (!a && b) { return -1; }
407+
if (a === 'latest') { return 1; }
408+
if (b === 'latest') { return -1; }
409+
const aParts = a.split('.').map(Number);
410+
const bParts = b.split('.').map(Number);
411+
412+
for (let i = 0; i < aParts.length; i++) {
413+
if (aParts[i] > bParts[i]) {
414+
return 1;
415+
} else if (aParts[i] < bParts[i]) {
416+
return -1;
417+
}
418+
}
419+
//equal
420+
return 0;
421+
}
422+
423+
// load resource config from json files based on the appropriate version
424+
async function loadResource(extPath: string): Promise<Tips> {
425+
let tfVersion: string;
426+
const cwd = workspaceUtils.getActiveEditorPath();
427+
if (!cwd) {
428+
TelemetryWrapper.sendError(Error("noWorkspaceSelected"));
429+
console.error(`can not get path from active editor`);
430+
}
431+
432+
await executeCommandByExec("terraform version", cwd).then(output => {
433+
let match = RegExp(/tencentcloudstack\/tencentcloud (v\d+\.\d+\.\d+)/).exec(output);
434+
435+
if (match) {
436+
tfVersion = match[1];
437+
} else {
438+
// gives the latest JSON if not tf provider version found
439+
tfVersion = LATEST_VERSION;
440+
}
441+
console.log(`tf provider version:[${tfVersion}], cwd:[${cwd}]`);
442+
}).catch(error => {
443+
console.error(`execute terraform version failed: ${error}`);
444+
});
445+
446+
let result: Tips | null = null;
447+
const tipsDir = path.join(extPath, 'config', 'tips');
448+
const tipFiles = await sortJsonFiles(tipsDir);
449+
450+
tipFiles.some(file => {
451+
if (compareVersions(tfVersion, file.version) >= 0) {
452+
result = file.json as Tips;
453+
return true;
454+
}
455+
// gives the latest JSON if not one JSON files matched
456+
result = file.json as Tips;
457+
return false;
458+
});
459+
460+
console.log(`Loaded json. tf version:[${tfVersion}], json version:[${result.version}]`);
461+
// vscode.window.showInformationMessage(`Loaded json. tf version:[${tfVersion}], json version:[${result.version}]`);
462+
463+
return result;
464+
}

src/client/runner/terraformRunner.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export class TerraformRunner extends BaseRunner {
3434

3535
this.setAKSK();
3636

37+
terraformShellManager.getShell().runTerraformCmd(TerraformCommand.Version);
3738
terraformShellManager.getShell().runTerraformCmd(TerraformCommand.Init);
3839

3940
return "init success";
@@ -61,7 +62,7 @@ export class TerraformRunner extends BaseRunner {
6162
}
6263

6364
public async preImport(cwd: string, args: any, file: string): Promise<{ importArgs: string, tfFile: string }> {
64-
const fileName = (file === undefined) ? args.resource.type + '.tf' : file;
65+
const fileName = file ?? args.resource.type + '.tf';
6566

6667
const defaultContents = `resource "${args.resource.type}" "${args.resource.name}" {}`;
6768
const resAddress = `${args.resource.type}.${args.resource.name}`;

src/commons/customCmdRegister.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export enum TerraformCommand {
1616
Destroy = "terraform destroy",
1717
Validate = "terraform validate",
1818
Show = "terraform show",
19-
State = "terraform state"
19+
State = "terraform state",
20+
Version = "terraform version"
2021
}
2122

2223
export enum TerraformerCommand {

src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export async function activate(context: vscode.ExtensionContext) {
7171

7272
// tips
7373
console.log('activate the tips(options and doc) feature');
74-
const tipsProvider = new TerraformTipsProvider();
74+
const tipsProvider = new TerraformTipsProvider(context.extensionPath);
7575
context.subscriptions.push(
7676
vscode.workspace.onDidChangeTextDocument((event) => {
7777
tipsProvider.handleCharacterEvent(event);

src/utils/cpUtils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,18 @@ export async function executeCommand(command: string, args: string[], options: c
4242
});
4343
});
4444
}
45+
46+
export async function executeCommandByExec(command: string, cwd?: string): Promise<string> {
47+
return new Promise((resolve, reject) => {
48+
const options = {
49+
cwd,
50+
};
51+
cp.exec(command, options, (error, stdout, stderr) => {
52+
if (error) {
53+
reject(`child_process exec failed: error:[${error}], stderr:[${stderr}]`);
54+
} else {
55+
resolve(stdout);
56+
}
57+
});
58+
});
59+
}

src/utils/gitUtils.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ export class GitUtils {
1616

1717
public async submitToGit(): Promise<any> {
1818
console.debug("[DEBUG]#### GitUtils submitToGit begin.");
19-
const activeDocumentPath = workspaceUtils.getActiveEditorPath();
20-
const gitRootPath = path.dirname(activeDocumentPath);
19+
const gitRootPath = workspaceUtils.getActiveEditorPath();
2120
if (!gitRootPath) {
2221
vscode.window.showErrorMessage('Please open a workspace folder first!');
2322
return;

0 commit comments

Comments
 (0)