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: update per API change for importing ruleset #228

Merged
merged 12 commits into from
Aug 9, 2024
8 changes: 4 additions & 4 deletions src/azure/ApiCenter/ApiCenterRestAPIs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ export const APICenterRestAPIs = {
GetAPIVersions: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/versions?api-version=${restApiVersion}`,
GetAPIDeployments: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/deployments?api-version=${restApiVersion}`,
GetAPIDefinition: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, apiVersion: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/versions/${apiVersion}/definitions?api-version=${restApiVersion}`,
GetRulesetConfig: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/staticAnalyzers/spectral?api-version=${restApiVersion}`,
GetRulesetConfig: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/analyzerConfigs/spectral-openapi?api-version=${restApiVersion}`,

CreateAPI: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}?api-version=${restApiVersion}`,
CreateAPIVersion: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, apiVersion: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/versions/${apiVersion}?api-version=${restApiVersion}`,
CreateAPIDeployment: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, deploymentName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/deployments/${deploymentName}?api-version=${restApiVersion}`,
CreateAPIDefinition: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, apiVersion: string, definationName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/versions/${apiVersion}/definitions/${definationName}?api-version=${restApiVersion}`,
CreateRulesetConfig: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/staticAnalyzers/spectral?api-version=${restApiVersion}`,
CreateRulesetConfig: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/analyzerConfigs/spectral-openapi?api-version=${restApiVersion}`,


ImportAPISpecification: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, apiVersion: string, definationName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/versions/${apiVersion}/definitions/${definationName}/importSpecification?api-version=${restApiVersion}`,
ExportApiSpecification: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, apiName: string, apiVersion: string, definationName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/apis/${apiName}/versions/${apiVersion}/definitions/${definationName}/exportSpecification?api-version=${restApiVersion}`,

ImportRuleset: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/staticAnalyzers/spectral/importRuleset?api-version=${restApiVersion}`,
ExportRuleset: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/staticAnalyzers/spectral/exportRuleset?api-version=${restApiVersion}`,
ImportRuleset: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/analyzerConfigs/spectral-openapi/importRuleset?api-version=${restApiVersion}`,
ExportRuleset: (subscriptionId: string, resourceGroupName: string, apiCenterName: string, restApiVersion: string) => `https://management.azure.com/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.ApiCenter/services/${apiCenterName}/workspaces/default/analyzerConfigs/spectral-openapi/exportRuleset?api-version=${restApiVersion}`,
};
48 changes: 38 additions & 10 deletions src/azure/ApiCenter/ApiCenterService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
import { ISubscriptionContext } from "@microsoft/vscode-azext-utils";
import { getCredentialForToken } from "../../utils/credentialUtil";
import { APICenterRestAPIs } from "./ApiCenterRestAPIs";
import { ApiCenter, ApiCenterApi, ApiCenterApiDeployment, ApiCenterApiVersion, ApiCenterApiVersionDefinition, ApiCenterApiVersionDefinitionExport, ApiCenterApiVersionDefinitionImport, ApiCenterEnvironment, ApiCenterRulesetConfig, ApiCenterRulesetExport, ApiCenterRulesetImport } from "./contracts";
import { ApiCenter, ApiCenterApi, ApiCenterApiDeployment, ApiCenterApiVersion, ApiCenterApiVersionDefinition, ApiCenterApiVersionDefinitionExport, ApiCenterApiVersionDefinitionImport, ApiCenterEnvironment, ApiCenterRulesetConfig, ApiCenterRulesetExport, ApiCenterRulesetImport, ApiCenterRulesetImportResult, ApiCenterRulesetImportStatus, ArmAsyncOperationStatus } from "./contracts";

export class ApiCenterService {
private susbcriptionContext: ISubscriptionContext;
private resourceGroupName: string;
private apiCenterName: string;
private apiVersion: string = "2023-07-01-preview";
private apiVersionPreview: string = "2024-03-15-preview";
private apiVersionNew: string = "2024-03-01";
constructor(susbcriptionContext: ISubscriptionContext, resourceGroupName: string, apiCenterName: string) {
this.susbcriptionContext = susbcriptionContext;
this.apiCenterName = apiCenterName;
Expand Down Expand Up @@ -93,7 +93,7 @@
const client = new ServiceClient(creds);
const options: RequestPrepareOptions = {
method: "GET",
url: APICenterRestAPIs.GetRulesetConfig(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionPreview)
url: APICenterRestAPIs.GetRulesetConfig(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionNew)

Check warning on line 96 in src/azure/ApiCenter/ApiCenterService.ts

View check run for this annotation

Codecov / codecov/patch

src/azure/ApiCenter/ApiCenterService.ts#L96

Added line #L96 was not covered by tests
};
const response = await client.sendRequest(options);
return response;
Expand Down Expand Up @@ -170,7 +170,7 @@
const client = new ServiceClient(creds);
const options: RequestPrepareOptions = {
method: "PUT",
url: APICenterRestAPIs.CreateRulesetConfig(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionPreview),
url: APICenterRestAPIs.CreateRulesetConfig(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionNew),

Check warning on line 173 in src/azure/ApiCenter/ApiCenterService.ts

View check run for this annotation

Codecov / codecov/patch

src/azure/ApiCenter/ApiCenterService.ts#L173

Added line #L173 was not covered by tests
body: {
properties: apiCenterRulesetConfig.properties
}
Expand Down Expand Up @@ -226,24 +226,52 @@
return response.parsedBody;
}

public async importRuleset(importPayload: ApiCenterRulesetImport): Promise<HttpOperationResponse> {
public async importRuleset(importPayload: ApiCenterRulesetImport): Promise<ApiCenterRulesetImportResult> {
const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken());
const client = new ServiceClient(creds);
const options: RequestPrepareOptions = {
let options: RequestPrepareOptions = {
method: "POST",
url: APICenterRestAPIs.ImportRuleset(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionPreview),
url: APICenterRestAPIs.ImportRuleset(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionNew),
body: importPayload
};
const response = await client.sendRequest(options);
return response;
let response = await client.sendRequest(options);

if (response.status === 202) {
const location = response.headers.get("Location");

if (!location) {
return { isSuccessful: false };
formulahendry marked this conversation as resolved.
Show resolved Hide resolved
}

Check warning on line 244 in src/azure/ApiCenter/ApiCenterService.ts

View check run for this annotation

Codecov / codecov/patch

src/azure/ApiCenter/ApiCenterService.ts#L243-L244

Added lines #L243 - L244 were not covered by tests

options = {
method: "GET",
url: location,
};

const timeout = 60000; // 1 minute in milliseconds
let startTime = Date.now();
do {
response = await client.sendRequest(options);
const responseBody: ApiCenterRulesetImportStatus = response.parsedBody;
if (responseBody?.status === ArmAsyncOperationStatus.Succeeded) {
return { isSuccessful: true };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the response no need to return?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just return ApiCenterRulesetImportResult

}
if (responseBody?.status === ArmAsyncOperationStatus.Failed) {
return { isSuccessful: false, message: responseBody.properties?.comment };
}
} while (Date.now() - startTime < timeout);
return { isSuccessful: false };

Check warning on line 263 in src/azure/ApiCenter/ApiCenterService.ts

View check run for this annotation

Codecov / codecov/patch

src/azure/ApiCenter/ApiCenterService.ts#L263

Added line #L263 was not covered by tests
} else {
return { isSuccessful: false, message: response.bodyAsText };
}
}

public async exportRuleset(): Promise<ApiCenterRulesetExport> {
const creds = getCredentialForToken(await this.susbcriptionContext.credentials.getToken());
const client = new ServiceClient(creds);
const options: RequestPrepareOptions = {
method: "POST",
url: APICenterRestAPIs.ExportRuleset(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionPreview)
url: APICenterRestAPIs.ExportRuleset(this.susbcriptionContext.subscriptionId, this.resourceGroupName, this.apiCenterName, this.apiVersionNew)

Check warning on line 274 in src/azure/ApiCenter/ApiCenterService.ts

View check run for this annotation

Codecov / codecov/patch

src/azure/ApiCenter/ApiCenterService.ts#L274

Added line #L274 was not covered by tests
};
const response = await client.sendRequest(options);
return response.parsedBody;
Expand Down
26 changes: 26 additions & 0 deletions src/azure/ApiCenter/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,20 @@ export type ApiCenterRulesetImport = {
value: string;
};

export type ApiCenterRulesetImportStatus = {
id: string;
name: string;
status: ArmAsyncOperationStatus;
properties: {
comment?: string;
};
};

export type ApiCenterRulesetImportResult = {
isSuccessful: boolean;
message?: string | null;
};

export type ApiCenterRulesetExport = {
format: string;
value: string;
Expand Down Expand Up @@ -135,3 +149,15 @@ export enum ApiSpecExportResultFormat {
inline = 'inline',
link = 'link',
};

export enum ArmAsyncOperationStatus {
NotStarted = 'NotStarted',
InProgress = 'InProgress',
Succeeded = 'Succeeded',
Failed = 'Failed',
Canceled = 'Canceled',
}

export enum ApiCenterRulesetImportFormat {
InlineZip = 'inline-zip',
};
33 changes: 19 additions & 14 deletions src/commands/rules/deployRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { IActionContext } from "@microsoft/vscode-azext-utils";
import * as vscode from 'vscode';
import { ApiCenterService } from "../../azure/ApiCenter/ApiCenterService";
import { ApiCenterRulesetImport } from "../../azure/ApiCenter/contracts";
import { ApiCenterRulesetImport, ApiCenterRulesetImportFormat } from "../../azure/ApiCenter/contracts";

Check warning on line 7 in src/commands/rules/deployRules.ts

View check run for this annotation

Codecov / codecov/patch

src/commands/rules/deployRules.ts#L7

Added line #L7 was not covered by tests
import { RulesTreeItem } from "../../tree/rules/RulesTreeItem";
import { UiStrings } from "../../uiStrings";
import { hasFiles } from "../../utils/fsUtil";
Expand All @@ -18,20 +18,25 @@
return;
}

const content = (await zipFolderToBuffer(rulesFolderPath)).toString('base64');
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: UiStrings.DeployRules
}, async (progress, token) => {
const content = (await zipFolderToBuffer(rulesFolderPath)).toString('base64');

Check warning on line 25 in src/commands/rules/deployRules.ts

View check run for this annotation

Codecov / codecov/patch

src/commands/rules/deployRules.ts#L21-L25

Added lines #L21 - L25 were not covered by tests

const resourceGroupName = getResourceGroupFromId(node.apiCenter.id);
const apiCenterService = new ApiCenterService(node.parent?.subscription!, resourceGroupName, node.apiCenter.name);
const resourceGroupName = getResourceGroupFromId(node.apiCenter.id);
const apiCenterService = new ApiCenterService(node.parent?.subscription!, resourceGroupName, node.apiCenter.name);

Check warning on line 28 in src/commands/rules/deployRules.ts

View check run for this annotation

Codecov / codecov/patch

src/commands/rules/deployRules.ts#L27-L28

Added lines #L27 - L28 were not covered by tests

const importPayload: ApiCenterRulesetImport = {
value: content,
format: "InlineZip",
};
const response = await apiCenterService.importRuleset(importPayload);
const importPayload: ApiCenterRulesetImport = {
value: content,
format: ApiCenterRulesetImportFormat.InlineZip,
};
const response = await apiCenterService.importRuleset(importPayload);

Check warning on line 34 in src/commands/rules/deployRules.ts

View check run for this annotation

Codecov / codecov/patch

src/commands/rules/deployRules.ts#L30-L34

Added lines #L30 - L34 were not covered by tests

if (response.status === 200) {
vscode.window.showInformationMessage(vscode.l10n.t(UiStrings.RulesDeployed, node.apiCenter.name));
} else {
vscode.window.showErrorMessage(vscode.l10n.t(UiStrings.FailedToDeployRules, response.bodyAsText ?? `status code ${response.status}`));
}
if (response.isSuccessful) {
vscode.window.showInformationMessage(vscode.l10n.t(UiStrings.RulesDeployed, node.apiCenter.name));
} else {
throw new Error(vscode.l10n.t(UiStrings.FailedToDeployRules, response.message ? `Error: ${response.message}` : ""));
}
});

Check warning on line 41 in src/commands/rules/deployRules.ts

View check run for this annotation

Codecov / codecov/patch

src/commands/rules/deployRules.ts#L36-L41

Added lines #L36 - L41 were not covered by tests
}
70 changes: 35 additions & 35 deletions src/commands/rules/enableRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,50 @@ import { IActionContext } from "@microsoft/vscode-azext-utils";
import * as path from 'path';
import * as vscode from 'vscode';
import { ApiCenterService } from "../../azure/ApiCenter/ApiCenterService";
import { ApiCenterRulesetConfig, ApiCenterRulesetImport } from "../../azure/ApiCenter/contracts";
import { ApiCenterRulesetConfig, ApiCenterRulesetImport, ApiCenterRulesetImportFormat } from "../../azure/ApiCenter/contracts";
import { ext } from "../../extensionVariables";
import { RulesTreeItem } from "../../tree/rules/RulesTreeItem";
import { UiStrings } from "../../uiStrings";
import { zipFolderToBuffer } from "../../utils/zipUtils";

const apiCenterRulesetConfig: ApiCenterRulesetConfig = {
properties: {
analyzerVersion: "1.0.0",
apiType: "rest",
lifecycleStage: "testing",
analyzerType: "spectral"
}
};

export async function enableRules(context: IActionContext, node: RulesTreeItem) {
const resourceGroupName = getResourceGroupFromId(node.apiCenter.id);
const apiCenterService = new ApiCenterService(node.parent?.subscription!, resourceGroupName, node.apiCenter.name);

let response = await apiCenterService.createOrUpdateApiCenterRulesetConfig(apiCenterRulesetConfig);

if (response.status === 200) {
vscode.window.showInformationMessage((vscode.l10n.t(UiStrings.RulesEnabled, node.apiCenter.name)));
} else {
vscode.window.showErrorMessage(vscode.l10n.t(UiStrings.FailedToEnableRules, response.bodyAsText ?? `status code ${response.status}`));
return;
}

// Temporary workaround to deploy default rules after enabling rules
// In future, default rules need to be generated in control plane
const defaultRulesFolderPath = path.join(ext.context.extensionPath, 'templates', 'rules', 'default-ruleset');

const content = (await zipFolderToBuffer(defaultRulesFolderPath)).toString('base64');

const importPayload: ApiCenterRulesetImport = {
value: content,
format: "InlineZip",
};
response = await apiCenterService.importRuleset(importPayload);

if (response.status === 200) {
vscode.window.showInformationMessage(vscode.l10n.t(UiStrings.RulesDeployed, node.apiCenter.name));
node.updateStatusToEnable();
await node.refresh(context);
} else {
vscode.window.showErrorMessage(vscode.l10n.t(UiStrings.FailedToDeployRules, response.bodyAsText ?? `status code ${response.status}`));
}
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: UiStrings.EnableRules
}, async (progress, token) => {
const resourceGroupName = getResourceGroupFromId(node.apiCenter.id);
const apiCenterService = new ApiCenterService(node.parent?.subscription!, resourceGroupName, node.apiCenter.name);

const response = await apiCenterService.createOrUpdateApiCenterRulesetConfig(apiCenterRulesetConfig);

if (response.status !== 200) {
throw new Error(vscode.l10n.t(UiStrings.FailedToEnableRules, response.bodyAsText ? `Error: ${response.bodyAsText}` : `Status Code: ${response.status}`));
}

// Temporary workaround to deploy default rules after enabling rules
// In future, default rules need to be generated in control plane
const defaultRulesFolderPath = path.join(ext.context.extensionPath, 'templates', 'rules', 'default-ruleset');

const content = (await zipFolderToBuffer(defaultRulesFolderPath)).toString('base64');

const importPayload: ApiCenterRulesetImport = {
value: content,
format: ApiCenterRulesetImportFormat.InlineZip,
};
const importRulesetResponse = await apiCenterService.importRuleset(importPayload);

if (importRulesetResponse.isSuccessful) {
vscode.window.showInformationMessage(vscode.l10n.t(UiStrings.RulesEnabled, node.apiCenter.name));
node.updateStatusToEnable();
await node.refresh(context);
} else {
throw new Error(vscode.l10n.t(UiStrings.FailedToEnableRules, importRulesetResponse.message ? `Error: ${importRulesetResponse.message}` : ""));
}
});
}
Loading
Loading