From 4b14f17b5975526660ec196e64303a132e81eec6 Mon Sep 17 00:00:00 2001 From: Hemal Shah Date: Wed, 16 Sep 2026 13:09:13 +0100 Subject: [PATCH 1/2] feat(azure): add APIM autoscaling support --- .changeset/apim-autoscaling.md | 6 +++ packages/azure/src/construct/rest-api/main.ts | 43 +++++++++++++++++++ .../azure/src/construct/rest-api/types.ts | 3 ++ .../test/common/config/rest-api-new.json | 32 ++++++++++++++ .../azure/test/constructs/rest-api.test.ts | 31 +++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 .changeset/apim-autoscaling.md diff --git a/.changeset/apim-autoscaling.md b/.changeset/apim-autoscaling.md new file mode 100644 index 000000000..17908785f --- /dev/null +++ b/.changeset/apim-autoscaling.md @@ -0,0 +1,6 @@ +--- +'@gradientedge/cdk-utils-azure': patch +'@gradientedge/cdk-utils': patch +--- + +Add configurable Azure API Management autoscaling for v2 tiers with dynamic resource targeting. diff --git a/packages/azure/src/construct/rest-api/main.ts b/packages/azure/src/construct/rest-api/main.ts index 1edfdca25..4c439b8d6 100644 --- a/packages/azure/src/construct/rest-api/main.ts +++ b/packages/azure/src/construct/rest-api/main.ts @@ -6,8 +6,10 @@ import { import { getComponentOutput, GetComponentResult } from '@pulumi/azure-native/applicationinsights/index.js' import { PrincipalType } from '@pulumi/azure-native/authorization/index.js' import { getVaultOutput } from '@pulumi/azure-native/keyvault/index.js' +import { AutoscaleSetting } from '@pulumi/azure-native/monitor/index.js' import * as pulumi from '@pulumi/pulumi' import { Output } from '@pulumi/pulumi' +import _ from 'lodash' import { CommonAzureConstruct } from '../../common/index.js' import { RoleDefinitionId } from '../../services/index.js' @@ -36,6 +38,8 @@ export class AzureRestApi extends CommonAzureConstruct { api: AzureApi = {} as AzureApi /** The resolved Application Insights component for telemetry */ applicationInsights: Output + /** Azure Monitor autoscale setting for the API Management service */ + apiManagementAutoscaleSetting?: AutoscaleSetting /** * @summary Create a new AzureRestApi @@ -57,6 +61,7 @@ export class AzureRestApi extends CommonAzureConstruct { this.resolveCommonLogAnalyticsWorkspace() this.resolveApplicationInsights() this.createApiManagement() + this.createApiManagementAutoscale() this.createNamespaceSecretRole() this.createNamespaceSecret() this.createSubscriptionKeySecret() @@ -168,6 +173,44 @@ export class AzureRestApi extends CommonAzureConstruct { }) } + /** Configure Azure Monitor custom autoscale for v2 API Management tiers. */ + protected createApiManagementAutoscale() { + const apiManagementService = this.api.apim + const autoscaleProps = this.props.apiManagementAutoscaling + + if (this.props.apiManagement.useExistingApiManagement || !apiManagementService || !autoscaleProps) return + + const configuredProfiles = autoscaleProps.profiles as unknown as + Array<{ rules?: Array> }> | undefined + const profiles = configuredProfiles?.map(profile => ({ + ...profile, + rules: profile.rules?.map(rule => + _.merge({}, rule, { + metricTrigger: { + metricResourceUri: apiManagementService.id, + metricResourceLocation: this.resourceGroup.location, + }, + }) + ), + })) + + this.apiManagementAutoscaleSetting = this.monitorManager.createMonitorAutoscaleSettings( + `${this.id}-apim-autoscale`, + this, + { + ..._.merge({}, autoscaleProps, { profiles }), + name: `${this.id}-apim-autoscale`, + enabled: autoscaleProps.enabled ?? true, + location: this.resourceGroup.location, + resourceGroupName: this.resourceGroup.name, + targetResourceUri: apiManagementService.id, + }, + { dependsOn: [apiManagementService] } + ) + + return this.apiManagementAutoscaleSetting + } + /** * @summary Method to create the Key Vault role assignment for the API Management identity */ diff --git a/packages/azure/src/construct/rest-api/types.ts b/packages/azure/src/construct/rest-api/types.ts index f21304a71..779815e42 100644 --- a/packages/azure/src/construct/rest-api/types.ts +++ b/packages/azure/src/construct/rest-api/types.ts @@ -9,6 +9,7 @@ import { CommonAzureStackProps, MonitorDiagnosticSettingProps, CertificateProps, + MonitorAutoscaleSettingProps, } from '../../index.js' /** @@ -38,6 +39,8 @@ export interface AzureRestApiProps extends CommonAzureStackProps { apiManagementDiagnosticSettings: MonitorDiagnosticSettingProps /** API Management certificate properties */ apiManagementCertificate: CertificateProps + /** API Management autoscaling settings for v2 tiers */ + apiManagementAutoscaling?: MonitorAutoscaleSettingProps } /** diff --git a/packages/azure/test/common/config/rest-api-new.json b/packages/azure/test/common/config/rest-api-new.json index e10f55599..202357a1b 100644 --- a/packages/azure/test/common/config/rest-api-new.json +++ b/packages/azure/test/common/config/rest-api-new.json @@ -13,6 +13,38 @@ "capacity": 0 } }, + "apiManagementAutoscaling": { + "profiles": [ + { + "name": "default", + "capacity": { + "minimum": "1", + "default": "2", + "maximum": "10" + }, + "rules": [ + { + "metricTrigger": { + "metricName": "CpuPercent_Gateway", + "metricNamespace": "Microsoft.ApiManagement/service", + "operator": "GreaterThan", + "statistic": "Average", + "threshold": 60, + "timeAggregation": "Average", + "timeGrain": "PT1M", + "timeWindow": "PT30M" + }, + "scaleAction": { + "cooldown": "PT60M", + "direction": "Increase", + "type": "ChangeCount", + "value": "1" + } + } + ] + } + ] + }, "apiManagementDiagnostic": { "diagnosticId": "applicationinsights", "apiId": "/subscriptions/test-sub/resourceGroups/test-rg-dev/providers/Microsoft.ApiManagement/service/test-api-management-dev/apis/test-api" diff --git a/packages/azure/test/constructs/rest-api.test.ts b/packages/azure/test/constructs/rest-api.test.ts index 0fa066c05..2b4498042 100644 --- a/packages/azure/test/constructs/rest-api.test.ts +++ b/packages/azure/test/constructs/rest-api.test.ts @@ -118,6 +118,7 @@ class TestRestApiNewApiConstruct extends AzureRestApi { this.resolveCommonLogAnalyticsWorkspace() this.resolveApplicationInsights() this.createApiManagement() + this.createApiManagementAutoscale() this.createNamespaceSecretRole() this.createNamespaceSecret() this.createSubscriptionKeySecret() @@ -159,6 +160,7 @@ class TestRestApiNewApiWithCertConstruct extends AzureRestApi { this.resolveCommonLogAnalyticsWorkspace() this.resolveApplicationInsights() this.createApiManagement() + this.createApiManagementAutoscale() this.createNamespaceSecretRole() this.createNamespaceSecret() this.createSubscriptionKeySecret() @@ -367,6 +369,35 @@ describe('TestAzureRestApiNewApiConstruct', () => { }) }) +describe('TestAzureRestApiNewApiConstruct', () => { + test('provisions enabled APIM autoscale with dynamic target fields', async () => { + await outputToPromise( + pulumi + .all([ + stackNewApi.construct.apiManagementAutoscaleSetting?.enabled, + stackNewApi.construct.apiManagementAutoscaleSetting?.profiles, + stackNewApi.construct.api.apim.id, + stackNewApi.construct.resourceGroup.location, + ]) + .apply(([enabled, profiles, apiManagementId, location]) => { + expect(enabled).toBe(true) + expect(profiles).toEqual([ + expect.objectContaining({ + rules: [ + expect.objectContaining({ + metricTrigger: expect.objectContaining({ + metricResourceUri: apiManagementId, + metricResourceLocation: location, + }), + }), + ], + }), + ]) + }) + ) + }) +}) + describe('TestAzureRestApiNewApiConstruct', () => { test('creates namespace secret role as expected', async () => { expect(stackNewApi.construct.api.namedValueRoleAssignment).toBeDefined() From a205e87582559b19dc3aafd0925c9854875a5edf Mon Sep 17 00:00:00 2001 From: Hemal Shah Date: Wed, 16 Sep 2026 13:17:29 +0100 Subject: [PATCH 2/2] feat: feat(azure): add APIM autoscaling support --- .changeset/apim-autoscaling.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/apim-autoscaling.md b/.changeset/apim-autoscaling.md index 17908785f..2c58e5d71 100644 --- a/.changeset/apim-autoscaling.md +++ b/.changeset/apim-autoscaling.md @@ -1,6 +1,5 @@ --- '@gradientedge/cdk-utils-azure': patch -'@gradientedge/cdk-utils': patch --- Add configurable Azure API Management autoscaling for v2 tiers with dynamic resource targeting.