Skip to content

Commit

Permalink
Create Fleet: panel implementation (#1161)
Browse files Browse the repository at this point in the history
  • Loading branch information
JunyuQian authored Jan 15, 2025
1 parent bd32f2d commit c8788ff
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/commands/utils/arm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { getCredential, getEnvironment } from "../../auth/azureAuth";
import { ReadyAzureSessionProvider } from "../../auth/types";
import { Errorable, getErrorMessage } from "./errorable";
import { ComputeManagementClient } from "@azure/arm-compute";
import { ContainerServiceFleetClient } from "@azure/arm-containerservicefleet";

export function getSubscriptionClient(sessionProvider: ReadyAzureSessionProvider): SubscriptionClient {
return new SubscriptionClient(getCredential(sessionProvider), { endpoint: getArmEndpoint() });
Expand All @@ -36,6 +37,15 @@ export function getAksClient(
return new ContainerServiceClient(getCredential(sessionProvider), subscriptionId, { endpoint: getArmEndpoint() });
}

export function getAksFleetClient(
sessionProvider: ReadyAzureSessionProvider,
subscriptionId: string,
): ContainerServiceFleetClient {
return new ContainerServiceFleetClient(getCredential(sessionProvider), subscriptionId, {
endpoint: getArmEndpoint(),
});
}

export function getMonitorClient(sessionProvider: ReadyAzureSessionProvider, subscriptionId: string): MonitorClient {
return new MonitorClient(getCredential(sessionProvider), subscriptionId, { endpoint: getArmEndpoint() });
}
Expand Down
118 changes: 118 additions & 0 deletions src/panels/CreateFleetPanel.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,122 @@
import { ContainerServiceFleetClient, Fleet } from "@azure/arm-containerservicefleet";
import { BasePanel, PanelDataProvider } from "./BasePanel";
import { Uri, window } from "vscode";
import { MessageHandler, MessageSink } from "../webview-contract/messaging";
import {
InitialState,
ProgressEventType,
ToVsCodeMsgDef,
ToWebViewMsgDef,
} from "../webview-contract/webviewDefinitions/createFleet";
import { TelemetryDefinition } from "../webview-contract/webviewTypes";
import { ResourceManagementClient } from "@azure/arm-resources";
import { ReadyAzureSessionProvider } from "../auth/types";
import { getAksFleetClient, getResourceManagementClient } from "../commands/utils/arm";
import { getResourceGroups } from "../commands/utils/resourceGroups";
import { failed } from "../commands/utils/errorable";

export class CreateFleetPanel extends BasePanel<"createFleet"> {
constructor(extensionUri: Uri) {
super(extensionUri, "createFleet", {
getLocationsResponse: null,
getResourceGroupsResponse: null,
progressUpdate: null,
});
}
}

export class CreateFleetDataProvider implements PanelDataProvider<"createFleet"> {
private readonly resourceManagementClient: ResourceManagementClient;
private readonly fleetClient: ContainerServiceFleetClient;

constructor(
private readonly sessionProvider: ReadyAzureSessionProvider,
private readonly subscriptionId: string,
private readonly subscriptionName: string,
) {
this.resourceManagementClient = getResourceManagementClient(sessionProvider, this.subscriptionId);
this.fleetClient = getAksFleetClient(sessionProvider, this.subscriptionId);
}

getTitle(): string {
return `Create Fleet in ${this.subscriptionName}`;
}

getInitialState(): InitialState {
return {
subscriptionId: this.subscriptionId,
subscriptionName: this.subscriptionName,
};
}

getTelemetryDefinition(): TelemetryDefinition<"createFleet"> {
return {
getResourceGroupsRequest: false,
getLocationsRequest: false,
createFleetRequest: true,
};
}

getMessageHandler(webview: MessageSink<ToWebViewMsgDef>): MessageHandler<ToVsCodeMsgDef> {
return {
getLocationsRequest: () => this.handleGetLocationsRequest(webview),
getResourceGroupsRequest: () => this.handleGetResourceGroupsRequest(webview),
createFleetRequest: (args) =>
this.handleCreateFleetRequest(args.resourceGroupName, args.location, args.name),
};
}

private async handleGetLocationsRequest(webview: MessageSink<ToWebViewMsgDef>) {
const provider = await this.resourceManagementClient.providers.get("Microsoft.ContainerService");
const resourceTypes = provider.resourceTypes?.filter((type) => type.resourceType === "fleets");
if (!resourceTypes || resourceTypes.length > 1) {
window.showErrorMessage(
`Unexpected number of fleets resource types for provider (${resourceTypes?.length || 0}).`,
);
return;
}

const resourceType = resourceTypes[0];
if (!resourceType.locations || resourceType.locations.length === 0) {
window.showErrorMessage("No locations found for fleets resource type.");
return;
}

webview.postGetLocationsResponse({ locations: resourceType.locations });
}

private async handleGetResourceGroupsRequest(webview: MessageSink<ToWebViewMsgDef>) {
const groups = await getResourceGroups(this.sessionProvider, this.subscriptionId);
if (failed(groups)) {
webview.postProgressUpdate({
event: ProgressEventType.Failed,
operationDescription: "Retrieving resource groups for fleet creation",
errorMessage: groups.error,
deploymentPortalUrl: null,
createdFleet: null,
});
return;
}

const usableGroups = groups.result
.map((group) => ({
label: `${group.name} (${group.location})`,
name: group.name,
location: group.location,
}))
.sort((a, b) => (a.name > b.name ? 1 : -1));

webview.postGetResourceGroupsResponse({ groups: usableGroups });
}

private async handleCreateFleetRequest(resourceGroupName: string, location: string, name: string) {
const resource = {
location: location,
};

await createFleet(this.fleetClient, resourceGroupName, name, resource);
}
}

export async function createFleet(
client: ContainerServiceFleetClient,
Expand Down
52 changes: 52 additions & 0 deletions src/webview-contract/webviewDefinitions/createFleet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { WebviewDefinition } from "../webviewTypes";

export interface InitialState {
subscriptionId: string;
subscriptionName: string;
}

export interface ResourceGroup {
name: string;
location: string;
}

export enum ProgressEventType {
InProgress,
Cancelled,
Failed,
Success,
}

export type CreatedFleet = {
portalUrl: string;
};

export interface CreateFleetParams {
resourceGroupName: string;
location: string;
name: string;
}

export type ToVsCodeMsgDef = {
getLocationsRequest: void;
getResourceGroupsRequest: void;
createFleetRequest: CreateFleetParams;
};

export type ToWebViewMsgDef = {
getLocationsResponse: {
locations: string[];
};
getResourceGroupsResponse: {
groups: ResourceGroup[];
};
progressUpdate: {
operationDescription: string;
event: ProgressEventType;
errorMessage: string | null;
deploymentPortalUrl: string | null;
createdFleet: CreatedFleet | null;
};
};

export type CreateFleetDefinition = WebviewDefinition<InitialState, ToVsCodeMsgDef, ToWebViewMsgDef>;
2 changes: 2 additions & 0 deletions src/webview-contract/webviewTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { PeriscopeDefinition } from "./webviewDefinitions/periscope";
import { RetinaCaptureDefinition } from "./webviewDefinitions/retinaCapture";
import { TCPDumpDefinition } from "./webviewDefinitions/tcpDump";
import { TestStyleViewerDefinition } from "./webviewDefinitions/testStyleViewer";
import { CreateFleetDefinition } from "./webviewDefinitions/createFleet";

/**
* Groups all the related types for a single webview.
Expand Down Expand Up @@ -56,6 +57,7 @@ type AllWebviewDefinitions = {
kaitoModels: KaitoModelsDefinition;
kaitoManage: KaitoManageDefinition;
kaitoTest: KaitoTestDefinition;
createFleet: CreateFleetDefinition;
};

type ContentIdLookup = {
Expand Down
7 changes: 7 additions & 0 deletions webview-ui/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ function getVsCodeContent(): JSX.Element {
kaitoModels: () => <KaitoModels {...getInitialState()} />,
kaitoManage: () => <KaitoManage {...getInitialState()} />,
kaitoTest: () => <KaitoTest {...getInitialState()} />,
createFleet: function (): JSX.Element {
// Hardcoded: Only to ensure the dependencies are resolved for compilation.
// TODO: Replace with the actual scenarios when available.
return <div>createFleet testcases not implemented.</div>; // createFleet scenarios are not yet available.
// Testcases for createFleet will be added in the next PR, together with the webpage for user input.
// User experience will not be affected, as the right-click entry point for createFleet is not yet visible.
},
};

return rendererLookup[vscodeContentId]();
Expand Down
5 changes: 5 additions & 0 deletions webview-ui/src/manualTest/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ const contentTestScenarios: Record<ContentId, Scenario[]> = {
kaitoModels: getKaitoModelScenarios(),
kaitoManage: getKaitoManageScenarios(),
kaitoTest: getKaitoTestScenarios(),
// Hardcoded createFleet: Only to ensure the dependencies are resolved for compilation.
// TODO: Replace with the actual scenarios when available.
createFleet: [], // createFleet scenarios are not yet available.
// Testcases for createFleet will be added in the next PR, together with the webpage for user input.
// User experience will not be affected, as the right-click entry point for createFleet is not yet visible.
};

const testScenarios = Object.values(contentTestScenarios).flatMap((s) => s);
Expand Down

0 comments on commit c8788ff

Please sign in to comment.