Skip to content

Commit

Permalink
Feat/load specific JSON based on the provider version (#14)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
lyu571 authored Dec 28, 2023
1 parent 04c8dbc commit c7e9487
Show file tree
Hide file tree
Showing 11 changed files with 39,986 additions and 37,412 deletions.
5 changes: 1 addition & 4 deletions .github/workflows/pre-release.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
name: pre-release
on:
pull_request:
types:
- closed
push:
tags:
- "v*"
Expand Down Expand Up @@ -61,6 +58,6 @@ jobs:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
automatic_release_tag: ${{ env.NEW_TAG }}
prerelease: true
title: "v${{ env.NEW_TAG }}-beta"
title: "${{ env.NEW_TAG }}-beta"
files: |
vscode-tencentcloud-terraform-*.vsix
37,366 changes: 0 additions & 37,366 deletions config/tips/tiat-resources_bak.json

This file was deleted.

39,771 changes: 39,771 additions & 0 deletions config/tips/v1.81.54.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion src/autocomplete/TerraformHoverProvider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { HoverProvider, TextDocument, Position, CancellationToken, CompletionItem, CompletionItemKind, Hover, ProviderResult } from "vscode";
import resources from '../../config/tips/tiat-resources.json';
import * as _ from "lodash";

var topLevelTypes = ["output", "provider", "resource", "variable", "data"];
Expand Down
226 changes: 191 additions & 35 deletions src/autocomplete/TerraformTipsProvider.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { CompletionItemProvider, TextDocument, Position, CancellationToken, CompletionItem, CompletionItemKind } from "vscode";
import resources from '../../config/tips/tiat-resources.json';
// import resources from '../../config/tips/tiat-resources.json';
import * as _ from "lodash";
import * as vscode from 'vscode';

import { executeCommandByExec } from "@/utils/cpUtils";
import * as fs from "fs";
import * as path from "path";
import * as workspaceUtils from "@/utils/workspaceUtils";
import * as TelemetryWrapper from "vscode-extension-telemetry-wrapper";

const LATEST_VERSION = "latest";
const versionPattern = /^v\d+(\.\d+){2}\.json$/;
let topLevelTypes = ["output", "provider", "resource", "variable", "data"];
let topLevelRegexes = topLevelTypes.map(o => {
return {
Expand All @@ -15,6 +22,29 @@ interface TerraformCompletionContext extends vscode.CompletionContext {
resourceType?: string;
}

interface Argument {
name: string;
description: string;
options?: Array<string>;
detail?: Array<Argument>;
}

interface Attribute {
name: string;
description: string;
detail?: Array<Attribute>;
}

interface Tips {
version: string;
resource: {
[key: string]: {
args: Array<Argument>;
attrs: Array<Attribute>;
};
};
}

const TEXT_MIN_SORT = "a";
const TEXT_FILTER = " ";

Expand All @@ -26,8 +56,12 @@ export class TerraformTipsProvider implements CompletionItemProvider {
position: Position;
token: CancellationToken;
resourceType: string | null = null;
private extensionPath: string;
constructor(extensionPath: string) {
this.extensionPath = extensionPath;
}

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

// Are we trying to type a top type?
// handle top level definition
if (this.isTopLevelType(lineTillCurrentPosition)) {
return this.getTopLevelType(lineTillCurrentPosition);
}
Expand Down Expand Up @@ -76,24 +110,32 @@ export class TerraformTipsProvider implements CompletionItemProvider {
// We're trying to type the exported field for the let
const resourceType = parts[0];
let resourceName = parts[1];
let attrs = resources[resourceType].attrs;
let result = _.map(attrs, o => {
let c = new CompletionItem(`${o.name} (${resourceType})`, CompletionItemKind.Property);
c.detail = o.description;
c.insertText = o.name;
c.sortText = TEXT_MIN_SORT;
return c;
});
return result;
try {
// async load resource config
const tips = await loadResource(this.extensionPath);
const resources = tips.resource;
let attrs = resources[resourceType].attrs;
let result = _.map(attrs, o => {
let c = new CompletionItem(`${o.name}(${resourceType})`, CompletionItemKind.Property);
c.detail = o.description;
c.insertText = o.name;
c.sortText = TEXT_MIN_SORT;
return c;
});
return result;

} catch (error) {
console.error(`Can not load resource from json. error:[${error}]`);
}
}

// Which part are we completing for?
return [];
}

// Are we trying to type a parameter to a resource?
let possibleResources = this.checkTopLevelResource(lineTillCurrentPosition);
// typing a resource type
let possibleResources = await this.checkTopLevelResource(lineTillCurrentPosition);
// handle resource type
if (possibleResources.length > 0) {
return this.getHintsForStrings(possibleResources);
}
Expand All @@ -106,28 +148,43 @@ export class TerraformTipsProvider implements CompletionItemProvider {
if (endwithEqual) {
const lineBeforeEqualSign = lineTillCurrentPosition.substring(0, includeEqual).trim();
// load options
const name = lineBeforeEqualSign;
const argStrs = this.findArgByName(resources[this.resourceType].args, name);
const options = this.getOptionsFormArg(argStrs);
// clear resource type
this.resourceType = "";
return (options).length ? options : [];
try {
// async load resource config
const tips = await loadResource(this.extensionPath);
const name = lineBeforeEqualSign;
const resources = tips.resource;
const argStrs = this.findArgByName(resources[this.resourceType].args, name);
const options = this.getOptionsFormArg(argStrs);
// clear resource type
this.resourceType = "";
return (options).length ? options : [];
} catch (error) {
console.error(`Can not load resource from json when loading options. error:[${error}]`);
}
}
this.resourceType = "";
return [];
}

// Check if we're in a resource definition
// handle argument
if (includeEqual < 0 && !endwithEqual) {
// we're not in options case
for (let i = position.line - 1; i >= 0; i--) {
let line = document.lineAt(i).text;
let parentType = this.getParentType(line);
if (parentType && parentType.type === "resource") {
// typing a arg in resource
// typing a argument in resource
const resourceType = this.getResourceTypeFromLine(line);
const ret = this.getItemsForArgs(resources[resourceType].args, resourceType);
return ret;
try {
// async load resource config
const tips = await loadResource(this.extensionPath);
const resources = tips.resource;
const ret = this.getItemsForArgs(resources[resourceType].args, resourceType);
return ret;
} catch (error) {
console.error(`Can not load resource from json when loading argument. error:[${error}]`);
return [];
}
}
else if (parentType && parentType.type !== "resource") {
// We don't want to accidentally include some other containers stuff
Expand Down Expand Up @@ -237,18 +294,27 @@ export class TerraformTipsProvider implements CompletionItemProvider {
return "";
}

checkTopLevelResource(lineTillCurrentPosition: string): any[] {
async checkTopLevelResource(lineTillCurrentPosition: string): Promise<any[]> {
let parts = lineTillCurrentPosition.split(" ");
if (parts.length === 2 && parts[0] === "resource") {
let r = parts[1].replace(/"/g, '');
let regex = new RegExp("^" + r);
let possibleResources = _.filter(_.keys(resources), k => {
if (regex.test(k)) {
return true;
}
return false;
});
return possibleResources;
// handle resource
try {
// async load resource config
const tips = await loadResource(this.extensionPath);
const resources = tips.resource;
let possibleResources = _.filter(_.keys(resources), k => {
if (regex.test(k)) {
return true;
}
return false;
});
return possibleResources;
} catch (error) {
console.error(`Can not load resource from json when loading resource type. error:[${error}]`);
return [];
}
}
return [];
}
Expand Down Expand Up @@ -295,7 +361,7 @@ export class TerraformTipsProvider implements CompletionItemProvider {
}

const changes = event.contentChanges[0];
if (changes.text === TIPS_OPTIONS_TRIGGER_CHARACTER) {
if (changes && changes.text === TIPS_OPTIONS_TRIGGER_CHARACTER) {
const position = activeEditor.selection.active;
const resourceType = this.findResourceType(event.document, position);

Expand All @@ -305,4 +371,94 @@ export class TerraformTipsProvider implements CompletionItemProvider {
}
}
}
}
}

async function sortJsonFiles(dir: string) {
let jsonFiles: string[];
try {
const files = fs.readdirSync(dir);
jsonFiles = files.filter(file => path.extname(file) === '.json' && versionPattern.test(file));
// const jsonFiles: string[] = ["v1.81.50.json", "v1.81.54.json"]; // debug data
} catch (error) {
console.error(`read dir failed. error:[${error}]`);
return null;
}

// import files
const versions = await Promise.all(jsonFiles.map(async file => {
const jsonPath = path.join("../config/tips/", file);
// const json = await import(jsonPath);
const json = require(jsonPath);
const version = json.version as string;
return {
json,
version
};
}));

// sort with version desc
versions.sort((a, b) => compareVersions(b.version, a.version));
return versions;
}

function compareVersions(a, b) {
if (a && !b) { return 1; }
if (!a && b) { return -1; }
if (a === 'latest') { return 1; }
if (b === 'latest') { return -1; }
const aParts = a.split('.').map(Number);
const bParts = b.split('.').map(Number);

for (let i = 0; i < aParts.length; i++) {
if (aParts[i] > bParts[i]) {
return 1;
} else if (aParts[i] < bParts[i]) {
return -1;
}
}
//equal
return 0;
}

// load resource config from json files based on the appropriate version
async function loadResource(extPath: string): Promise<Tips> {
let tfVersion: string;
const cwd = workspaceUtils.getActiveEditorPath();
if (!cwd) {
TelemetryWrapper.sendError(Error("noWorkspaceSelected"));
console.error(`can not get path from active editor`);
}

await executeCommandByExec("terraform version", cwd).then(output => {
let match = RegExp(/tencentcloudstack\/tencentcloud (v\d+\.\d+\.\d+)/).exec(output);

if (match) {
tfVersion = match[1];
} else {
// gives the latest JSON if not tf provider version found
tfVersion = LATEST_VERSION;
}
console.log(`tf provider version:[${tfVersion}], cwd:[${cwd}]`);
}).catch(error => {
console.error(`execute terraform version failed: ${error}`);
});

let result: Tips | null = null;
const tipsDir = path.join(extPath, 'config', 'tips');
const tipFiles = await sortJsonFiles(tipsDir);

tipFiles.some(file => {
if (compareVersions(tfVersion, file.version) >= 0) {
result = file.json as Tips;
return true;
}
// gives the latest JSON if not one JSON files matched
result = file.json as Tips;
return false;
});

console.log(`Loaded json. tf version:[${tfVersion}], json version:[${result.version}]`);
// vscode.window.showInformationMessage(`Loaded json. tf version:[${tfVersion}], json version:[${result.version}]`);

return result;
}
3 changes: 2 additions & 1 deletion src/client/runner/terraformRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class TerraformRunner extends BaseRunner {

this.setAKSK();

terraformShellManager.getShell().runTerraformCmd(TerraformCommand.Version);
terraformShellManager.getShell().runTerraformCmd(TerraformCommand.Init);

return "init success";
Expand Down Expand Up @@ -61,7 +62,7 @@ export class TerraformRunner extends BaseRunner {
}

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

const defaultContents = `resource "${args.resource.type}" "${args.resource.name}" {}`;
const resAddress = `${args.resource.type}.${args.resource.name}`;
Expand Down
3 changes: 2 additions & 1 deletion src/commons/customCmdRegister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export enum TerraformCommand {
Destroy = "terraform destroy",
Validate = "terraform validate",
Show = "terraform show",
State = "terraform state"
State = "terraform state",
Version = "terraform version"
}

export enum TerraformerCommand {
Expand Down
2 changes: 1 addition & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export async function activate(context: vscode.ExtensionContext) {

// tips
console.log('activate the tips(options and doc) feature');
const tipsProvider = new TerraformTipsProvider();
const tipsProvider = new TerraformTipsProvider(context.extensionPath);
context.subscriptions.push(
vscode.workspace.onDidChangeTextDocument((event) => {
tipsProvider.handleCharacterEvent(event);
Expand Down
15 changes: 15 additions & 0 deletions src/utils/cpUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,18 @@ export async function executeCommand(command: string, args: string[], options: c
});
});
}

export async function executeCommandByExec(command: string, cwd?: string): Promise<string> {
return new Promise((resolve, reject) => {
const options = {
cwd,
};
cp.exec(command, options, (error, stdout, stderr) => {
if (error) {
reject(`child_process exec failed: error:[${error}], stderr:[${stderr}]`);
} else {
resolve(stdout);
}
});
});
}
3 changes: 1 addition & 2 deletions src/utils/gitUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ export class GitUtils {

public async submitToGit(): Promise<any> {
console.debug("[DEBUG]#### GitUtils submitToGit begin.");
const activeDocumentPath = workspaceUtils.getActiveEditorPath();
const gitRootPath = path.dirname(activeDocumentPath);
const gitRootPath = workspaceUtils.getActiveEditorPath();
if (!gitRootPath) {
vscode.window.showErrorMessage('Please open a workspace folder first!');
return;
Expand Down
Loading

0 comments on commit c7e9487

Please sign in to comment.