import { ComputeManagementClient } from '@azure/arm-compute';
import { StorageManagementClient } from '@azure/arm-storage';
import { ResourceManagementClient } from '@azure/arm-resources';
import { DefaultAzureCredential } from '@azure/identity';
import type { CloudCredentials, AzureVM, AzureStorageAccount, ResourceStatus } from '../types/index.js';
import { credentialManager } from '../utils/credential-manager.js';
export class AzureAdapter {
private computeClient: ComputeManagementClient | null = null;
private storageClient: StorageManagementClient | null = null;
private resourceClient: ResourceManagementClient | null = null;
private credentials: CloudCredentials | null = null;
private subscriptionId: string;
private location: string;
constructor(subscriptionId?: string, location: string = 'eastus') {
this.subscriptionId = subscriptionId || '';
this.location = location;
}
/**
* Initialize Azure clients with credentials
*/
private async initializeClients(): Promise<void> {
if (!this.credentials) {
this.credentials = await credentialManager.getCredentials('azure');
if (!this.credentials) {
this.credentials = credentialManager.loadFromEnvironment('azure');
}
if (!this.credentials) {
// Try default Azure credential (Managed Identity, Azure CLI, etc.)
try {
const credential = new DefaultAzureCredential();
const subId = this.subscriptionId || '';
// Test credential
this.computeClient = new ComputeManagementClient(credential, subId);
return;
} catch {
throw new Error('Azure credentials not found. Please configure credentials first.');
}
}
}
if (!this.subscriptionId && this.credentials?.subscriptionId) {
this.subscriptionId = this.credentials.subscriptionId;
}
if (!this.subscriptionId) {
throw new Error('Azure Subscription ID is required');
}
// For service principal authentication
if (this.credentials.clientId && this.credentials.clientSecret && this.credentials.tenantId) {
const { ClientSecretCredential } = await import('@azure/identity');
const credential = new ClientSecretCredential(
this.credentials.tenantId,
this.credentials.clientId,
this.credentials.clientSecret
);
this.computeClient = new ComputeManagementClient(credential, this.subscriptionId);
this.storageClient = new StorageManagementClient(credential, this.subscriptionId);
this.resourceClient = new ResourceManagementClient(credential, this.subscriptionId);
} else {
// Use default credential
const credential = new DefaultAzureCredential();
this.computeClient = new ComputeManagementClient(credential, this.subscriptionId);
this.storageClient = new StorageManagementClient(credential, this.subscriptionId);
this.resourceClient = new ResourceManagementClient(credential, this.subscriptionId);
}
}
/**
* List Virtual Machines
*/
async listVirtualMachines(): Promise<AzureVM[]> {
await this.initializeClients();
if (!this.computeClient) throw new Error('Compute client not initialized');
try {
const vms: AzureVM[] = [];
const resourceGroups = await this.listResourceGroups();
for (const rg of resourceGroups) {
const vmList = this.computeClient.virtualMachines.list(rg);
for await (const vm of vmList) {
if (vm.id && vm.name) {
// Get VM status
const instanceView = await this.computeClient.virtualMachines.instanceView(rg, vm.name);
const status = this.mapVMStatus(instanceView.statuses);
// Get network info
const networkInterfaces = vm.networkProfile?.networkInterfaces || [];
let privateIp: string | undefined;
let publicIp: string | undefined;
if (networkInterfaces.length > 0 && this.resourceClient) {
// Simplified - would need to fetch actual IPs from network interfaces
}
vms.push({
id: vm.id,
type: 'instance',
name: vm.name,
resourceGroup: rg,
location: vm.location || this.location,
status,
vmSize: vm.hardwareProfile?.vmSize || '',
osType: vm.storageProfile?.osDisk?.osType || 'Unknown',
privateIp,
publicIp,
provisioningState: vm.provisioningState,
tags: vm.tags,
});
}
}
}
return vms;
} catch (error) {
throw new Error(`Failed to list VMs: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* List Storage Accounts
*/
async listStorageAccounts(): Promise<AzureStorageAccount[]> {
await this.initializeClients();
if (!this.storageClient) throw new Error('Storage client not initialized');
try {
const accounts: AzureStorageAccount[] = [];
const accountList = this.storageClient.storageAccounts.list();
for await (const account of accountList) {
if (account.id && account.name) {
accounts.push({
id: account.id,
type: 'storage',
name: account.name,
resourceGroup: this.extractResourceGroup(account.id),
location: account.location || this.location,
status: 'running',
accountName: account.name,
kind: account.kind || '',
sku: account.sku?.name,
accessTier: account.accessTier,
tags: account.tags,
});
}
}
return accounts;
} catch (error) {
throw new Error(`Failed to list storage accounts: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* List resource groups
*/
private async listResourceGroups(): Promise<string[]> {
if (!this.resourceClient) {
await this.initializeClients();
if (!this.resourceClient) throw new Error('Resource client not initialized');
}
try {
const groups: string[] = [];
const rgList = this.resourceClient.resourceGroups.list();
for await (const rg of rgList) {
if (rg.name) {
groups.push(rg.name);
}
}
return groups;
} catch {
return [];
}
}
/**
* Helper: Extract resource group from resource ID
*/
private extractResourceGroup(resourceId: string): string {
const match = resourceId.match(/\/resourceGroups\/([^\/]+)\//);
return match ? match[1] : '';
}
/**
* Helper: Map VM status
*/
private mapVMStatus(statuses?: Array<{ code?: string }>): ResourceStatus {
if (!statuses || statuses.length === 0) return 'unknown';
const statusCode = statuses[0]?.code || '';
if (statusCode.includes('PowerState/running')) return 'running';
if (statusCode.includes('PowerState/stopped')) return 'stopped';
if (statusCode.includes('PowerState/deallocated')) return 'stopped';
if (statusCode.includes('ProvisioningState/creating')) return 'pending';
return 'unknown';
}
}