AI scaffolded NodeJS version of the App.
This commit is contained in:
117
app-new/backend/src/azure-service.ts
Normal file
117
app-new/backend/src/azure-service.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { ComputeManagementClient } from "@azure/arm-compute";
|
||||
import { DefaultAzureCredential } from "@azure/identity";
|
||||
import { MemoryCache } from "./cache";
|
||||
import { sortImageVersionsIfSemantic } from "./version";
|
||||
import type { LocationOption } from "./types";
|
||||
|
||||
const CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
|
||||
export class AzureImageService {
|
||||
private readonly credential = new DefaultAzureCredential();
|
||||
|
||||
private readonly computeClient: ComputeManagementClient;
|
||||
|
||||
private readonly cache = new MemoryCache();
|
||||
|
||||
public constructor(private readonly subscriptionId: string) {
|
||||
this.computeClient = new ComputeManagementClient(this.credential, subscriptionId);
|
||||
}
|
||||
|
||||
public async getLocations(): Promise<LocationOption[]> {
|
||||
const cacheKey = "locations";
|
||||
const cached = this.cache.get<LocationOption[]>(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const token = await this.credential.getToken("https://management.azure.com/.default");
|
||||
const url = `https://management.azure.com/subscriptions/${this.subscriptionId}/locations?api-version=2022-12-01`;
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token?.token ?? ""}`
|
||||
}
|
||||
});
|
||||
|
||||
const payload = (await response.json()) as { value?: Array<{ name?: string; displayName?: string; metadata?: { regionType?: string } }> };
|
||||
|
||||
const locations = (payload.value ?? [])
|
||||
.filter((loc) => loc.metadata?.regionType === "Physical" && Boolean(loc.name))
|
||||
.map((loc) => ({
|
||||
name: loc.name as string,
|
||||
displayName: loc.displayName ?? (loc.name as string)
|
||||
}));
|
||||
|
||||
locations.sort((a, b) => a.name.localeCompare(b.name));
|
||||
this.cache.set(cacheKey, locations, CACHE_TTL_MS);
|
||||
return locations;
|
||||
}
|
||||
|
||||
public async getPublishers(location: string): Promise<string[]> {
|
||||
const cacheKey = `publishers:${location}`;
|
||||
const cached = this.cache.get<string[]>(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const response = await this.computeClient.virtualMachineImages.listPublishers(location);
|
||||
const publishers = this.extractNames(response);
|
||||
|
||||
this.cache.set(cacheKey, publishers, CACHE_TTL_MS);
|
||||
return publishers;
|
||||
}
|
||||
|
||||
public async getOffers(location: string, publisher: string): Promise<string[]> {
|
||||
const cacheKey = `offers:${location}:${publisher}`;
|
||||
const cached = this.cache.get<string[]>(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const response = await this.computeClient.virtualMachineImages.listOffers(location, publisher);
|
||||
const offers = this.extractNames(response);
|
||||
|
||||
this.cache.set(cacheKey, offers, CACHE_TTL_MS);
|
||||
return offers;
|
||||
}
|
||||
|
||||
public async getSkus(location: string, publisher: string, offer: string): Promise<string[]> {
|
||||
const cacheKey = `skus:${location}:${publisher}:${offer}`;
|
||||
const cached = this.cache.get<string[]>(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const response = await this.computeClient.virtualMachineImages.listSkus(location, publisher, offer);
|
||||
const skus = this.extractNames(response);
|
||||
|
||||
this.cache.set(cacheKey, skus, CACHE_TTL_MS);
|
||||
return skus;
|
||||
}
|
||||
|
||||
public async getVersions(location: string, publisher: string, offer: string, sku: string): Promise<string[]> {
|
||||
const cacheKey = `versions:${location}:${publisher}:${offer}:${sku}`;
|
||||
const cached = this.cache.get<string[]>(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const response = await this.computeClient.virtualMachineImages.list(location, publisher, offer, sku);
|
||||
const versions = this.extractNames(response);
|
||||
|
||||
const sorted = sortImageVersionsIfSemantic(versions);
|
||||
this.cache.set(cacheKey, sorted, CACHE_TTL_MS);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private extractNames(source: unknown): string[] {
|
||||
const items = Array.isArray(source)
|
||||
? source
|
||||
: typeof source === "object" && source !== null && "value" in source && Array.isArray((source as { value?: unknown }).value)
|
||||
? ((source as { value: unknown[] }).value as unknown[])
|
||||
: [];
|
||||
|
||||
return items
|
||||
.map((item) => (typeof item === "object" && item !== null && "name" in item ? (item as { name?: string }).name : undefined))
|
||||
.filter((value): value is string => Boolean(value));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user