broad-alligator-55470
08/19/2026, 9:11 AMbroad-alligator-55470
08/19/2026, 10:25 AMfuture-hairdresser-70637
08/19/2026, 12:56 PMimport * as pulumi from "@pulumi/pulumi";
import * as azure from "@pulumi/azure-native";
import { ManagementLocksClient } from "@azure/arm-locks";
import { DefaultAzureCredential } from "@azure/identity";
const config = new pulumi.Config("azure-native");
const subscriptionId = config.require("subscriptionId");
const locks = new ManagementLocksClient(new DefaultAzureCredential(), subscriptionId);
const LOCK_NAME = "auto-lock";
const LOCK_LEVEL = "CanNotDelete"; // switch to "ReadOnly" only if you also wire the beforeUpdate/afterUpdate hooks below
// --- Hooks: apply the lock after create/replace, remove it before delete/replace ---
const applyLock = new pulumi.ResourceHook("apply-lock", async (args) => {
const scope = args.newOutputs.id as string;
await locks.managementLocks.createOrUpdateByScope(scope, LOCK_NAME, {
level: LOCK_LEVEL,
notes: "Applied automatically via Pulumi resource hook",
});
});
const removeLock = new pulumi.ResourceHook(
"remove-lock",
async (args) => {
const scope = args.oldOutputs.id as string;
await locks.managementLocks.deleteByScope(scope, LOCK_NAME);
},
{ ignoreErrors: false } // if removal fails, the delete/update must NOT proceed
);
// --- Stack transform: attach the hooks + protect to every matching resource, stack-wide ---
const LOCK_ELIGIBLE_TYPES = new Set([
"azure-native:storage:StorageAccount",
"azure-native:network:PublicIPAddress",
]);
pulumi.runtime.registerResourceTransform(args => {
if (!LOCK_ELIGIBLE_TYPES.has(args.type)) {
return undefined;
}
return {
props: args.props,
opts: pulumi.mergeOptions(args.opts, {
protect: true,
hooks: {
afterCreate: [applyLock],
afterUpdate: [applyLock],
beforeDelete: [removeLock],
// beforeUpdate: [removeLock], // only needed if LOCK_LEVEL is "ReadOnly"
},
}),
};
});
// --- Resources: no per-resource lock wiring needed, the transform handles it ---
const resourceGroup = new azure.resources.ResourceGroup("rg", {
resourceGroupName: "locked-example-rg",
location: "East US",
});
const storageAccount = new azure.storage.StorageAccount("account", {
resourceGroupName: resourceGroup.name,
location: resourceGroup.location,
sku: { name: azure.storage.SkuName.Standard_LRS },
kind: azure.storage.Kind.StorageV2,
});
const publicIp = new azure.network.PublicIPAddress("pip", {
resourceGroupName: resourceGroup.name,
location: resourceGroup.location,
publicIPAllocationMethod: azure.network.IPAllocationMethod.Static,
});
export const storageAccountId = storageAccount.id;
export const publicIpId = publicIp.id;broad-alligator-55470
08/19/2026, 8:28 PMfuture-hairdresser-70637
08/20/2026, 4:15 PM