Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/load specific JSON based on the provider version #14

Merged
merged 4 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading