* Create subscription

* Delete subscriptions

* test commit

* Add Subscriptions
This commit is contained in:
RupengLiu 2021-04-22 14:14:59 -07:00 коммит произвёл GitHub
Родитель ecf1f01f98
Коммит abf3e0f7cb
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 276 добавлений и 13 удалений

2
package-lock.json сгенерированный
Просмотреть файл

@ -5,7 +5,7 @@
"requires": true,
"dependencies": {
"@azure/abort-controller": {
"version": "1.0.2",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.0.2.tgz",
"integrity": "sha512-XUyTo+bcyxHEf+jlN2MXA7YU9nxVehaubngHV1MIZZaqYmZqykkoeAz/JMMEeR7t3TcyDwbFa3Zw8BZywmIx4g==",
"requires": {

Просмотреть файл

@ -2,7 +2,7 @@
"name": "vscode-apimanagement",
"displayName": "Azure API Management",
"description": "An Azure API Management extension for Visual Studio Code.",
"version": "1.0.2-beta",
"version": "1.0.3",
"publisher": "ms-azuretools",
"icon": "resources/apim-icon-newone.png",
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
@ -72,7 +72,9 @@
"onCommand:azureApiManagement.debugPolicy",
"onCommand:azureApiManagement.generateFunctions",
"onCommand:azureApiManagement.revisions",
"onCommand:azureApiManagement.setCustomHostName"
"onCommand:azureApiManagement.setCustomHostName",
"onCommand:azureApiManagement.createSubscription",
"onCommand:azureApiManagement.deleteSubscription"
],
"main": "main",
"contributes": {
@ -243,6 +245,16 @@
"title": "%azureApiManagement.createNamedValue%",
"category": "Azure API Management"
},
{
"command": "azureApiManagement.createSubscription",
"title": "%azureApiManagement.createSubscription%",
"category": "Azure API Management"
},
{
"command": "azureApiManagement.deleteSubscription",
"title": "%azureApiManagement.deleteSubscription%",
"category": "Azure API Management"
},
{
"command": "azureApiManagement.deleteNamedValue",
"title": "%azureApiManagement.deleteNamedValue%",
@ -613,6 +625,16 @@
"command": "azureApiManagement.generateNewGatewayToken",
"when": "view == azureApiManagementExplorer && viewItem == azureApiManagementGatewayTreeItem",
"group": "1@3"
},
{
"command": "azureApiManagement.createSubscription",
"when": "view == azureApiManagementExplorer && viewItem == azureApiManagementSubscriptions",
"group": "1@1"
},
{
"command": "azureApiManagement.deleteSubscription",
"when": "view == azureApiManagementExplorer && viewItem == azureApiManagementSubscriptionTreeItem",
"group": "1@1"
}
]
},

Просмотреть файл

@ -44,5 +44,7 @@
"azureApiManagement.generateFunctions": "Scaffold Azure Functions",
"azureApiManagement.scaffoldAzureFunctions": "(Experimental Feature) Enables scaffolding Azure Functions from API definition.",
"azureApiManagement.revisions": "API Revisions",
"azureApiManagement.setCustomHostName": "Select Gateway Host Name"
"azureApiManagement.setCustomHostName": "Select Gateway Host Name",
"azureApiManagement.createSubscription": "Create a new Subscription",
"azureApiManagement.deleteSubscription": "Delete Subscription"
}

Просмотреть файл

@ -0,0 +1,31 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
<defs>
<linearGradient id="b0670cdb-9407-42e5-ae8f-f3558902da1a" x1="7.89" y1="6.9" x2="7.89" y2="19.35" gradientUnits="userSpaceOnUse">
<stop offset="0.22" stop-color="#32d4f5" />
<stop offset="1" stop-color="#198ab3" />
</linearGradient>
<linearGradient id="fd04ffc0-49c3-4a18-9b27-999b23712bcb" x1="7.53" y1="0.22" x2="8.44" y2="11.53" gradientUnits="userSpaceOnUse">
<stop offset="0.22" stop-color="#32d4f5" />
<stop offset="1" stop-color="#198ab3" />
</linearGradient>
<radialGradient id="aa3ecbb1-1061-42c8-aaf2-d5c01a3fcfd9" cx="-19.24" cy="6.51" r="6.13" gradientTransform="matrix(0.94, 0.01, -0.01, 0.94, 32.03, 6.26)" gradientUnits="userSpaceOnUse">
<stop offset="0.27" stop-color="#ffd70f" />
<stop offset="1" stop-color="#fea11b" />
</radialGradient>
</defs>
<g id="b245b541-7d80-40be-a5d7-51667bcba1b3">
<g>
<g>
<path d="M14.05,17.11a1.34,1.34,0,0,0,1.34-1.33.81.81,0,0,0,0-.16C14.86,11.42,12.47,8,7.9,8S.86,10.9.4,15.63A1.34,1.34,0,0,0,1.59,17.1H14.05Z" fill="url(#b0670cdb-9407-42e5-ae8f-f3558902da1a)" />
<path d="M7.9,9a4.09,4.09,0,0,1-2.27-.67l2.25,5.89,2.24-5.85A4.17,4.17,0,0,1,7.9,9Z" fill="#fff" opacity="0.8" />
<circle cx="7.9" cy="4.8" r="4.21" fill="url(#fd04ffc0-49c3-4a18-9b27-999b23712bcb)" />
</g>
<g>
<path id="f2ddd4d7-46fc-4e48-ae24-8fde036c39bb" d="M17.27,11.45a1.13,1.13,0,0,0,0-1.6h0l-1.94-2a1.12,1.12,0,0,0-1.6,0h0l-2,1.94a1.14,1.14,0,0,0,0,1.61l1.61,1.64a.31.31,0,0,1,.09.22l0,3a.36.36,0,0,0,.12.28l.73.75a.27.27,0,0,0,.37,0l.72-.72h0l.42-.43a.14.14,0,0,0,0-.2l-.31-.31a.17.17,0,0,1,0-.23l.31-.31a.13.13,0,0,0,0-.2l-.3-.31a.17.17,0,0,1,0-.23l.31-.31a.14.14,0,0,0,0-.2l-.42-.43V13.3ZM14.54,8.34a.66.66,0,0,1,.64.65.63.63,0,0,1-.65.64.65.65,0,0,1,0-1.29Z" fill="url(#aa3ecbb1-1061-42c8-aaf2-d5c01a3fcfd9)" />
<path id="e15034b6-eebb-4253-ac69-86068a1d4276" d="M14,16.38h0a.14.14,0,0,0,.24-.1V13.83a.16.16,0,0,0-.06-.13h0a.14.14,0,0,0-.22.12v2.46A.13.13,0,0,0,14,16.38Z" fill="#ff9300" opacity="0.75" />
<rect id="f3d2a589-08f4-4e99-9635-cc67abadc8f4" x="14.38" y="9.07" width="0.38" height="3.21" rx="0.17" transform="translate(3.8 25.17) rotate(-89.65)" fill="#ff9300" opacity="0.75" />
<rect id="bc7793e0-f7bc-4cc4-abb0-181c6c62350c" x="14.37" y="9.68" width="0.38" height="3.21" rx="0.17" transform="translate(3.18 25.78) rotate(-89.65)" fill="#ff9300" opacity="0.75" />
</g>
</g>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 2.5 KiB

Просмотреть файл

@ -0,0 +1,31 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
<defs>
<linearGradient id="b0670cdb-9407-42e5-ae8f-f3558902da1a" x1="7.89" y1="6.9" x2="7.89" y2="19.35" gradientUnits="userSpaceOnUse">
<stop offset="0.22" stop-color="#32d4f5" />
<stop offset="1" stop-color="#198ab3" />
</linearGradient>
<linearGradient id="fd04ffc0-49c3-4a18-9b27-999b23712bcb" x1="7.53" y1="0.22" x2="8.44" y2="11.53" gradientUnits="userSpaceOnUse">
<stop offset="0.22" stop-color="#32d4f5" />
<stop offset="1" stop-color="#198ab3" />
</linearGradient>
<radialGradient id="aa3ecbb1-1061-42c8-aaf2-d5c01a3fcfd9" cx="-19.24" cy="6.51" r="6.13" gradientTransform="matrix(0.94, 0.01, -0.01, 0.94, 32.03, 6.26)" gradientUnits="userSpaceOnUse">
<stop offset="0.27" stop-color="#ffd70f" />
<stop offset="1" stop-color="#fea11b" />
</radialGradient>
</defs>
<g id="b245b541-7d80-40be-a5d7-51667bcba1b3">
<g>
<g>
<path d="M14.05,17.11a1.34,1.34,0,0,0,1.34-1.33.81.81,0,0,0,0-.16C14.86,11.42,12.47,8,7.9,8S.86,10.9.4,15.63A1.34,1.34,0,0,0,1.59,17.1H14.05Z" fill="url(#b0670cdb-9407-42e5-ae8f-f3558902da1a)" />
<path d="M7.9,9a4.09,4.09,0,0,1-2.27-.67l2.25,5.89,2.24-5.85A4.17,4.17,0,0,1,7.9,9Z" fill="#fff" opacity="0.8" />
<circle cx="7.9" cy="4.8" r="4.21" fill="url(#fd04ffc0-49c3-4a18-9b27-999b23712bcb)" />
</g>
<g>
<path id="f2ddd4d7-46fc-4e48-ae24-8fde036c39bb" d="M17.27,11.45a1.13,1.13,0,0,0,0-1.6h0l-1.94-2a1.12,1.12,0,0,0-1.6,0h0l-2,1.94a1.14,1.14,0,0,0,0,1.61l1.61,1.64a.31.31,0,0,1,.09.22l0,3a.36.36,0,0,0,.12.28l.73.75a.27.27,0,0,0,.37,0l.72-.72h0l.42-.43a.14.14,0,0,0,0-.2l-.31-.31a.17.17,0,0,1,0-.23l.31-.31a.13.13,0,0,0,0-.2l-.3-.31a.17.17,0,0,1,0-.23l.31-.31a.14.14,0,0,0,0-.2l-.42-.43V13.3ZM14.54,8.34a.66.66,0,0,1,.64.65.63.63,0,0,1-.65.64.65.65,0,0,1,0-1.29Z" fill="url(#aa3ecbb1-1061-42c8-aaf2-d5c01a3fcfd9)" />
<path id="e15034b6-eebb-4253-ac69-86068a1d4276" d="M14,16.38h0a.14.14,0,0,0,.24-.1V13.83a.16.16,0,0,0-.06-.13h0a.14.14,0,0,0-.22.12v2.46A.13.13,0,0,0,14,16.38Z" fill="#ff9300" opacity="0.75" />
<rect id="f3d2a589-08f4-4e99-9635-cc67abadc8f4" x="14.38" y="9.07" width="0.38" height="3.21" rx="0.17" transform="translate(3.8 25.17) rotate(-89.65)" fill="#ff9300" opacity="0.75" />
<rect id="bc7793e0-f7bc-4cc4-abb0-181c6c62350c" x="14.37" y="9.68" width="0.38" height="3.21" rx="0.17" transform="translate(3.18 25.78) rotate(-89.65)" fill="#ff9300" opacity="0.75" />
</g>
</g>
</g>
</svg>

После

Ширина:  |  Высота:  |  Размер: 2.5 KiB

Просмотреть файл

@ -3,6 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SubscriptionState } from "@azure/arm-apimanagement/src/models";
export interface IFunctionContract {
id: string;
name: string;
@ -55,6 +57,15 @@ export interface IFunctionConfigItem {
route?: string;
}
export interface ISubscriptionContract {
scope: string;
displayName: string;
name: string;
allowTracing: boolean;
ownerId: string;
state: SubscriptionState;
}
export interface IFunctionKeys {
masterKey: string;
functionKeys: {

Просмотреть файл

@ -0,0 +1,134 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ApiContract, ProductContract, UserContract } from "@azure/arm-apimanagement/src/models";
import { ProgressLocation, window } from "vscode";
import { IActionContext } from "vscode-azureextensionui";
import { ISubscriptionContract } from "../azure/webApp/contracts";
import * as Constants from "../constants";
import { ServiceTreeItem } from "../explorer/ServiceTreeItem";
import { SubscriptionsTreeItem } from "../explorer/SubscriptionsTreeItem";
import { ext } from "../extensionVariables";
import { localize } from "../localize";
// tslint:disable: no-non-null-assertion
// tslint:disable-next-line: export-name
export async function createSubscription(context: IActionContext, node?: SubscriptionsTreeItem): Promise<void> {
if (!node) {
const serviceNode = <ServiceTreeItem>await ext.tree.showTreeItemPicker(ServiceTreeItem.contextValue, context);
node = serviceNode.subscriptionsTreeItem;
}
const name = await askName(node);
const displayName = await askDisplayname();
const allowTrace = await askTrace();
const user = await askUser(node);
let scope = await askScope();
if (scope === "All APIs") {
scope = "/apis";
} else if (scope === "API") {
const api = await askAPI(node);
scope = "/apis/".concat(api.value.id!);
} else if (scope === "Product") {
const product = await askProduct(node);
scope = "/apis/".concat(product.value.id!);
}
const subContract: ISubscriptionContract = {
scope: scope,
displayName: displayName,
name: name,
allowTracing: (allowTrace === "Yes") ? true : false,
ownerId: user.value.id!,
state: "active" // should we always make it active
};
await window.withProgress(
{
location: ProgressLocation.Notification,
title: localize("subscription", "Create new subscription..."),
cancellable: false
},
async () => {
await node!.root.client.subscription.createOrUpdate(node!.root.resourceGroupName, node!.root.serviceName, name, subContract);
await node!.refresh(context);
}
).then(async () => {
window.showInformationMessage(localize("createSubscription", "Subscription has been created successfully."));
});
}
async function askName(node: SubscriptionsTreeItem): Promise<string> {
const subNames = (await node.root.client.subscription.list(
node.root.resourceGroupName,
node.root.serviceName)).map(s => s.name!);
const subNamePrompt: string = localize('subNamePrompt', 'Enter Subscription Name.');
return (await ext.ui.showInputBox({
prompt: subNamePrompt,
validateInput: async (value: string | undefined): Promise<string | undefined> => {
value = value ? value.trim() : '';
return validateSubscriptionName(value, subNames);
}
})).trim();
}
function validateSubscriptionName(subName: string, subNames: string[]): string | undefined {
if (subName.length > Constants.maxApiNameLength) {
return localize("subNameMaxLength", `API name cannot be more than ${Constants.maxApiNameLength} characters long.`);
}
if (subName.match(/^[^*#&+:<>?]+$/) === null) {
return localize("subNameInvalid", 'Invalid API Name.');
}
if (subNames.indexOf(subName) !== -1) {
return localize("subNameInvalid", 'Subscription already exist.');
}
return undefined;
}
async function askDisplayname(): Promise<string> {
const idPrompt: string = localize('idPrompt', 'Enter Subscription DisplayName.');
return (await ext.ui.showInputBox({
prompt: idPrompt,
validateInput: async (value: string): Promise<string | undefined> => {
value = value ? value.trim() : '';
return validateDisplayName(value);
}
})).trim();
}
function validateDisplayName(id: string): string | undefined {
const test = "^[\w]+$)|(^[\w][\w\-]+[\w]$";
if (id.match(test) === null) {
return localize("subscriptionInvalid", 'Invalid Subscription Name.');
}
return undefined;
}
async function askScope(): Promise<string> {
const items : string[] = ["All APIs", "API", "Product"];
return (await ext.ui.showQuickPick(items.map((s) => { return {label: s}; }), { canPickMany: false, placeHolder: 'Select Scope'})).label;
}
async function askUser(node: SubscriptionsTreeItem): Promise<{ label: string; value: UserContract; }> {
const users = await node.root.client.user.listByService(node.root.resourceGroupName, node.root.serviceName);
return (await ext.ui.showQuickPick(users.map((s) => { return {label: s.firstName!.concat(s.lastName!).concat(" (").concat(s.email!).concat(")"), value: s}; }), { canPickMany: false, placeHolder: 'Select User'}));
}
async function askAPI(node: SubscriptionsTreeItem): Promise<{ label: string; value: ApiContract; }> {
const apis = await node.root.client.api.listByService(node.root.resourceGroupName, node.root.serviceName);
return (await ext.ui.showQuickPick(apis.map((s) => { return {label: s.displayName!, value: s}; }), { canPickMany: false, placeHolder: 'Select API'}));
}
async function askProduct(node: SubscriptionsTreeItem): Promise<{ label: string; value: ProductContract; }> {
const products = await node.root.client.product.listByService(node.root.resourceGroupName, node.root.serviceName);
return (await ext.ui.showQuickPick(products.map((s) => { return {label: s.displayName, value: s}; }), { canPickMany: false, placeHolder: 'Select Product'}));
}
async function askTrace(): Promise<string> {
const allowTrace = ["Yes", "No"];
return (await ext.ui.showQuickPick(allowTrace.map((s) => { return {label: s}; }), { canPickMany: false, placeHolder: 'Allow Trace?'})).label;
}

Просмотреть файл

@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IServiceTreeRoot } from "./IServiceTreeRoot";
export interface ISubscriptionTreeRoot extends IServiceTreeRoot {
subscriptionSid: string;
}

Просмотреть файл

@ -4,18 +4,20 @@
*--------------------------------------------------------------------------------------------*/
import { SubscriptionContract } from "@azure/arm-apimanagement/src/models";
import { AzureTreeItem, ISubscriptionContext } from "vscode-azureextensionui";
import { ProgressLocation, window } from "vscode";
import { AzureTreeItem, DialogResponses, ISubscriptionContext, UserCancelledError } from "vscode-azureextensionui";
import { localize } from "../localize";
import { nonNullProp } from "../utils/nonNull";
import { treeUtils } from "../utils/treeUtils";
import { IServiceTreeRoot } from "./IServiceTreeRoot";
import { ISubscriptionTreeRoot } from "./ISubscriptionTreeRoot";
import { SubscriptionsTreeItem } from "./SubscriptionsTreeItem";
export class SubscriptionTreeItem extends AzureTreeItem<IServiceTreeRoot> {
export class SubscriptionTreeItem extends AzureTreeItem<ISubscriptionTreeRoot> {
public static contextValue: string = 'azureApiManagementSubscriptionTreeItem';
public contextValue: string = SubscriptionTreeItem.contextValue;
private _label: string;
private _root: IServiceTreeRoot;
private _root: ISubscriptionTreeRoot;
constructor(
parent: SubscriptionsTreeItem,
@ -34,21 +36,37 @@ export class SubscriptionTreeItem extends AzureTreeItem<IServiceTreeRoot> {
return this._label;
}
public get root(): IServiceTreeRoot {
public get root(): ISubscriptionTreeRoot {
return this._root;
}
public get iconPath(): { light: string, dark: string } {
return treeUtils.getThemedIconPath('gateway');
return treeUtils.getThemedIconPath('subscription');
}
public hasMoreChildrenImpl(): boolean {
return false;
}
private createRoot(subRoot: ISubscriptionContext): IServiceTreeRoot {
return Object.assign({}, <IServiceTreeRoot>subRoot, {
subscriptionName: nonNullProp(this.subscriptionContract, 'name')
public async deleteTreeItemImpl() : Promise<void> {
const message: string = localize("confirmDeleteSubscription", `Are you sure you want to delete subscription '${this.root.subscriptionSid}'?`);
const result = await window.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel);
if (result === DialogResponses.deleteResponse) {
const deletingMessage: string = localize("", `Deleting subscription "${this.root.subscriptionSid}"...`);
await window.withProgress({ location: ProgressLocation.Notification, title: deletingMessage }, async () => {
await this.root.client.subscription.deleteMethod(this.root.resourceGroupName, this.root.serviceName, this.root.subscriptionSid, '*');
});
window.showInformationMessage(localize("deletedSubscription", `Successfully deleted Subscription "${this.root.subscriptionSid}".`));
} else {
throw new UserCancelledError();
}
}
private createRoot(subRoot: ISubscriptionContext): ISubscriptionTreeRoot {
return Object.assign({}, <ISubscriptionTreeRoot>subRoot, {
// tslint:disable-next-line: no-non-null-assertion
subscriptionSid: this.subscriptionContract.name!
});
}
}

Просмотреть файл

@ -24,6 +24,7 @@ import { importFunctionAppToApi } from './commands/importFunctionApp/importFunct
import { importOpenApi } from './commands/importOpenApi';
import { importWebApp, importWebAppToApi } from './commands/importWebApp/importWebApp';
import { createNamedValue, updateNamedValue } from './commands/manageNamedValue';
import { createSubscription } from './commands/manageSubscriptions';
import { openDiffEditor } from './commands/openDiffEditor';
import { openInPortal } from './commands/openInPortal';
import { openWorkingFolder } from './commands/openWorkingFolder';
@ -58,6 +59,7 @@ import { ProductPolicyTreeItem } from './explorer/ProductPolicyTreeItem';
import { ProductTreeItem } from './explorer/ProductTreeItem';
import { ServicePolicyTreeItem } from './explorer/ServicePolicyTreeItem';
import { ServiceTreeItem } from './explorer/ServiceTreeItem';
import { SubscriptionTreeItem } from './explorer/SubscriptionTreeItem';
import { ext } from './extensionVariables';
// this method is called when your extension is activated
@ -129,6 +131,8 @@ function registerCommands(tree: AzExtTreeDataProvider): void {
registerCommand('azureApiManagement.generateFunctions', generateFunctions);
registerCommand('azureApiManagement.revisions', revisions);
registerCommand('azureApiManagement.setCustomHostName', setCustomHostName);
registerCommand('azureApiManagement.createSubscription', createSubscription);
registerCommand('azureApiManagement.deleteSubscription', async (context: IActionContext, node?: AzureTreeItem) => await deleteNode(context, SubscriptionTreeItem.contextValue, node));
}
// tslint:disable-next-line: max-func-body-length