Skip to content

Commit

Permalink
Show Fleet Properties (#1245)
Browse files Browse the repository at this point in the history
  • Loading branch information
JunyuQian authored Feb 20, 2025
1 parent dc98cb8 commit 201b1c9
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 58 deletions.
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,10 @@
{
"command": "aks.aksCreateFleet",
"title": "Create Fleet"
},
{
"command": "aks.aksFleetProperties",
"title": "Show Fleet Properties"
}
],
"menus": {
Expand Down Expand Up @@ -475,6 +479,10 @@
{
"submenu": "aks.fleetMangerSubMenu",
"when": "view == kubernetes.cloudExplorer && viewItem =~ /aks\\.subscription/i"
},
{
"command": "aks.aksFleetProperties",
"when": "view == kubernetes.cloudExplorer && viewItem =~ /aks\\.fleet/i"
}
],
"aks.createClusterSubMenu": [
Expand Down
39 changes: 39 additions & 0 deletions src/commands/aksFleetProperties/askFleetProperties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as vscode from "vscode";
import * as k8s from "vscode-kubernetes-tools-api";
import { IActionContext } from "@microsoft/vscode-azext-utils";
import { getExtension } from "../utils/host";
import { failed } from "../utils/errorable";
import { getReadySessionProvider } from "../../auth/azureAuth";
import { getAksFleetTreeNode } from "../utils/fleet";
import { FleetPropertiesDataProvider, FleetPropertiesPanel } from "../../panels/FleetPropertiesPanel";

export default async function aksFleetProperties(_context: IActionContext, target: unknown): Promise<void> {
const cloudExplorer = await k8s.extension.cloudExplorer.v1;
const sessionProvider = await getReadySessionProvider();
if (failed(sessionProvider)) {
vscode.window.showErrorMessage(sessionProvider.error);
return;
}

const fleetNode = getAksFleetTreeNode(target, cloudExplorer);
if (failed(fleetNode)) {
vscode.window.showErrorMessage(fleetNode.error);
return;
}

const extension = getExtension();
if (failed(extension)) {
vscode.window.showErrorMessage(extension.error);
return;
}

const dataProvider = new FleetPropertiesDataProvider(
sessionProvider.result,
fleetNode.result.subscriptionId,
fleetNode.result.resourceGroupName,
fleetNode.result.name,
);

const panel = new FleetPropertiesPanel(extension.result.extensionUri);
panel.show(dataProvider);
}
105 changes: 105 additions & 0 deletions src/commands/utils/fleet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { ContainerServiceFleetClient, Fleet } from "@azure/arm-containerservicefleet";
import { getEnvironment } from "../../auth/azureAuth";
import { getDeploymentPortalUrl, getPortalResourceUrl } from "../../commands/utils/env";
import { MessageSink } from "../../webview-contract/messaging";
import { ProgressEventType, ToWebViewMsgDef } from "../../webview-contract/webviewDefinitions/createFleet";
import { window } from "vscode";
import { API, CloudExplorerV1 } from "vscode-kubernetes-tools-api";
import { Errorable } from "./errorable";
import { FleetTreeNode } from "../../tree/fleetTreeItem";

export async function createFleet(
client: ContainerServiceFleetClient,
resourceGroupName: string,
name: string,
resource: Fleet,
webview: MessageSink<ToWebViewMsgDef>,
) {
const operationDescription = `Creating fleet ${name}`;
webview.postProgressUpdate({
event: ProgressEventType.InProgress,
operationDescription,
errorMessage: null,
deploymentPortalUrl: null,
createdFleet: null,
});

const environment = getEnvironment();
try {
const result = await client.fleets.beginCreateOrUpdateAndWait(resourceGroupName, name, resource);
const errorMessage = `Fleet creation failed: No ID returned.
Resource Group Name: ${resourceGroupName},
Fleet Name: ${name},
Location: ${resource.location}.`;
if (!result || !result.id) {
window.showWarningMessage(errorMessage);
throw new Error(errorMessage);
}
const deploymentPortalUrl = getDeploymentPortalUrl(environment, result.id);
webview.postProgressUpdate({
event: ProgressEventType.Success,
operationDescription,
errorMessage: null,
deploymentPortalUrl,
createdFleet: {
portalUrl: getPortalResourceUrl(environment, result.id),
},
});
} catch (error) {
webview.postProgressUpdate({
event: ProgressEventType.Failed,
operationDescription,
errorMessage: (error as Error).message,
deploymentPortalUrl: null,
createdFleet: null,
});
}
}

export function getAksFleetTreeNode(
commandTarget: unknown,
cloudExplorer: API<CloudExplorerV1>,
): Errorable<FleetTreeNode> {
if (!cloudExplorer.available) {
return { succeeded: false, error: "Cloud explorer is unavailable." };
}

const cloudTarget = cloudExplorer.api.resolveCommandTarget(
commandTarget,
) as CloudExplorerV1.CloudExplorerResourceNode;

const isFleetTarget =
cloudTarget !== undefined &&
cloudTarget.cloudName === "Azure" &&
cloudTarget.cloudResource?.nodeType === "fleet";

if (!isFleetTarget) {
return { succeeded: false, error: "This command only applies to AKS Fleet managers." };
}

return { succeeded: true, result: cloudTarget.cloudResource };
}

function isValidFleet(fleet: Fleet): boolean {
return (
fleet.id !== undefined &&
fleet.name !== undefined &&
fleet.location !== undefined &&
fleet.provisioningState !== undefined
);
}

export async function getFleet(
client: ContainerServiceFleetClient,
resourceGroupName: string,
name: string,
): Promise<Errorable<Fleet>> {
try {
const fleet = await client.fleets.get(resourceGroupName, name);
return isValidFleet(fleet)
? { succeeded: true, result: fleet }
: { succeeded: false, error: `Invalid Fleet data for ${name}` };
} catch (e) {
return { succeeded: false, error: `Error retrieving Fleet ${name}: ${e}` };
}
}
2 changes: 2 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import { aksOpenKubectlPanel } from "./commands/aksOpenKubectlPanel/aksOpenKubec
import aksClusterFilter from "./commands/utils/clusterfilter";
//import aksAutomatedDeployments from "./commands/devhub/aksAutomatedDeployments";
import aksCreateFleet from "./commands/aksFleet/aksFleetManager";
import aksFleetProperties from "./commands/aksFleetProperties/askFleetProperties";

export async function activate(context: vscode.ExtensionContext) {
const cloudExplorer = await k8s.extension.cloudExplorer.v1;
Expand Down Expand Up @@ -127,6 +128,7 @@ export async function activate(context: vscode.ExtensionContext) {
registerCommandWithTelemetry("aks.clusterFilter", aksClusterFilter);
//registerCommandWithTelemetry("aks.aksAutomatedDeployments", aksAutomatedDeployments);
registerCommandWithTelemetry("aks.aksCreateFleet", aksCreateFleet);
registerCommandWithTelemetry("aks.aksFleetProperties", aksFleetProperties);

await registerAzureServiceNodes(context);

Expand Down
59 changes: 2 additions & 57 deletions src/panels/CreateFleetPanel.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ContainerServiceFleetClient, Fleet } from "@azure/arm-containerservicefleet";
import { ContainerServiceFleetClient } from "@azure/arm-containerservicefleet";
import { BasePanel, PanelDataProvider } from "./BasePanel";
import { Uri, window } from "vscode";
import { MessageHandler, MessageSink } from "../webview-contract/messaging";
Expand All @@ -15,8 +15,7 @@ import { ReadyAzureSessionProvider } from "../auth/types";
import { getAksFleetClient, getResourceManagementClient } from "../commands/utils/arm";
import { getResourceGroups } from "../commands/utils/resourceGroups";
import { failed } from "../commands/utils/errorable";
import { getEnvironment } from "../auth/azureAuth";
import { getDeploymentPortalUrl, getPortalResourceUrl } from "../commands/utils/env";
import { createFleet } from "../commands/utils/fleet";

export class CreateFleetPanel extends BasePanel<"createFleet"> {
constructor(extensionUri: Uri) {
Expand Down Expand Up @@ -143,57 +142,3 @@ export class CreateFleetDataProvider implements PanelDataProvider<"createFleet">
await createFleet(this.fleetClient, resourceGroupName, name, resource, webview);
}
}

async function createFleet(
client: ContainerServiceFleetClient,
resourceGroupName: string,
name: string,
resource: Fleet,
webview: MessageSink<ToWebViewMsgDef>,
) {
const operationDescription = `Creating fleet ${name}`;
webview.postProgressUpdate({
event: ProgressEventType.InProgress,
operationDescription,
errorMessage: null,
deploymentPortalUrl: null,
createdFleet: null,
});

const environment = getEnvironment();
try {
const result = await client.fleets.beginCreateOrUpdateAndWait(resourceGroupName, name, resource);
if (!result || !result.id) {
window.showWarningMessage(
`Fleet creation failed: No ID returned.
Resource Group Name: ${resourceGroupName},
Fleet Name: ${name},
Location: ${resource.location}.`,
);
throw new Error(
`Fleet creation failed: No ID returned.
Resource Group Name: ${resourceGroupName},
Fleet Name: ${name},
Location: ${resource.location}.`,
);
}
const deploymentPortalUrl = getDeploymentPortalUrl(environment, result.id);
webview.postProgressUpdate({
event: ProgressEventType.Success,
operationDescription,
errorMessage: null,
deploymentPortalUrl,
createdFleet: {
portalUrl: getPortalResourceUrl(environment, result.id),
},
});
} catch (error) {
webview.postProgressUpdate({
event: ProgressEventType.Failed,
operationDescription,
errorMessage: (error as Error).message,
deploymentPortalUrl: null,
createdFleet: null,
});
}
}
81 changes: 81 additions & 0 deletions src/panels/FleetPropertiesPanel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Uri } from "vscode";
import { BasePanel, PanelDataProvider } from "./BasePanel";
import { ContainerServiceFleetClient, Fleet } from "@azure/arm-containerservicefleet";
import { ReadyAzureSessionProvider } from "../auth/types";
import { getAksFleetClient } from "../commands/utils/arm";
import {
FleetInfo,
InitialState,
ToVsCodeMsgDef,
ToWebViewMsgDef,
} from "../webview-contract/webviewDefinitions/fleetProperties";
import { TelemetryDefinition } from "../webview-contract/webviewTypes";
import { MessageHandler, MessageSink } from "../webview-contract/messaging";
import { getFleet } from "../commands/utils/fleet";
import { failed } from "../commands/utils/errorable";
import { HubMode } from "../webview-contract/webviewDefinitions/createFleet";

export class FleetPropertiesPanel extends BasePanel<"fleetProperties"> {
constructor(extensionUri: Uri) {
super(extensionUri, "fleetProperties", {
getPropertiesResponse: null,
errorNotification: null,
});
}
}

export class FleetPropertiesDataProvider implements PanelDataProvider<"fleetProperties"> {
private readonly fleetClient: ContainerServiceFleetClient;
constructor(
private readonly sessionProvider: ReadyAzureSessionProvider,
readonly subscriptionId: string,
readonly resourceGroup: string,
readonly fleetName: string,
) {
this.fleetClient = getAksFleetClient(this.sessionProvider, this.subscriptionId);
}

getTitle(): string {
return `Fleet Properties for ${this.fleetName}`;
}

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

getTelemetryDefinition(): TelemetryDefinition<"fleetProperties"> {
return {
getPropertiesRequest: false,
refreshRequest: true,
};
}

getMessageHandler(webview: MessageSink<ToWebViewMsgDef>): MessageHandler<ToVsCodeMsgDef> {
return {
getPropertiesRequest: () => this.handleGetPropertiesRequest(webview),
refreshRequest: () => this.handleGetPropertiesRequest(webview),
};
}

private async handleGetPropertiesRequest(webview: MessageSink<ToWebViewMsgDef>) {
const fleet = await getFleet(this.fleetClient, this.resourceGroup, this.fleetName);
if (failed(fleet)) {
webview.postErrorNotification(fleet.error);
return;
}

webview.postGetPropertiesResponse(this.asFleetInfo(fleet.result));
}

asFleetInfo(fleet: Fleet): FleetInfo {
return {
resourceGroup: this.resourceGroup,
provisioningState: fleet.provisioningState!, // getFleet() ensures this is defined
location: fleet.location!,
hubClusterMode: fleet.hubProfile === undefined ? HubMode.Without : HubMode.With,
fqdn: fleet.hubProfile === undefined ? undefined : fleet.hubProfile.fqdn,
};
}
}
2 changes: 1 addition & 1 deletion src/tree/fleetTreeItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class FleetTreeItem extends AzExtParentTreeItem implements FleetTreeNode {
this.name = this.fleetResource.name;
}

public readonly contextValue: string = `fleet.hub`;
public readonly contextValue: string = `aks.fleet`;

public get label(): string {
return this.name;
Expand Down
26 changes: 26 additions & 0 deletions src/webview-contract/webviewDefinitions/fleetProperties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { WebviewDefinition } from "../webviewTypes";
import { HubMode } from "./createFleet";

export interface InitialState {
fleetName: string;
}

export type FleetInfo = {
resourceGroup: string;
provisioningState: string;
location: string;
hubClusterMode: HubMode;
fqdn: undefined | string;
};

export type ToVsCodeMsgDef = {
getPropertiesRequest: void;
refreshRequest: void;
};

export type ToWebViewMsgDef = {
getPropertiesResponse: FleetInfo;
errorNotification: string;
};

export type FleetProperties = 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 @@ -20,6 +20,7 @@ import { TCPDumpDefinition } from "./webviewDefinitions/tcpDump";
import { TestStyleViewerDefinition } from "./webviewDefinitions/testStyleViewer";
import { AutomatedDeploymentsDefinition } from "./webviewDefinitions/automatedDeployments";
import { CreateFleetDefinition } from "./webviewDefinitions/createFleet";
import { FleetProperties } from "./webviewDefinitions/fleetProperties";

/**
* Groups all the related types for a single webview.
Expand Down Expand Up @@ -60,6 +61,7 @@ type AllWebviewDefinitions = {
kaitoTest: KaitoTestDefinition;
automatedDeployments: AutomatedDeploymentsDefinition;
createFleet: CreateFleetDefinition;
fleetProperties: FleetProperties;
};

type ContentIdLookup = {
Expand Down
Loading

0 comments on commit 201b1c9

Please sign in to comment.