pulumi-arm-to-pulumi by pulumi/agent-skills
npx skills add https://github.com/pulumi/agent-skills --skill pulumi-arm-to-pulumi如果您在加载此技能之前已经生成了迁移计划,您必须:
迁移输出必须满足以下所有条件:
完整的资源覆盖
成功部署
pulumi preview(假设配置正确)。零差异导入验证(如果导入现有资源)
pulumi preview 必须显示:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
最终迁移报告
如果用户提供的 ARM 模板不完整、不明确或缺少工件,请在生成 Pulumi 代码之前提出有针对性的问题。
如果在导入时如何处理特定资源属性存在歧义,请在修改 Pulumi 代码之前提出有针对性的问题。
请严格按照此顺序遵循此工作流程:
运行 Azure CLI 命令(例如 az resource list、az resource show)。需要使用 ESC 和 az login 进行初始登录
使用 ESC 设置 Azure CLI:
pulumi env run {org}/{project}/{environment} -- bash -c 'az login --service-principal -u "$ARM_CLIENT_ID" --tenant "$ARM_TENANT_ID" --federated-token "$ARM_OIDC_TOKEN"'。建立会话后不需要 ESCaz account showaz account list --query "[].{Name:name, SubscriptionId:id, IsDefault:isDefault}" -o table有关 ESC 的详细信息: 通过调用工具 "Skill" 并设置 name = "pulumi-esc" 来加载 pulumi-esc 技能
ARM 模板没有像 CloudFormation 那样的"堆栈"概念。直接读取 ARM 模板 JSON 文件:
# 查看模板结构
cat template.json | jq '.resources[] | {type: .type, name: .name}'
# 查看参数
cat template.json | jq '.parameters'
# 查看变量
cat template.json | jq '.variables'
提取:
文档: ARM 模板结构
如果 ARM 模板已部署并且您要导入现有资源:
# 列出资源组中的所有资源
az resource list \
--resource-group <resource-group-name> \
--output json
# 获取特定资源详细信息
az resource show \
--ids <resource-id> \
--output json
# 使用 JMESPath 查询特定属性
az resource show \
--ids <resource-id> \
--query "{name:name, location:location, properties:properties}" \
--output json
文档: Azure CLI 文档
重要: ARM 到 Pulumi 的转换需要手动翻译。ARM 模板没有自动转换工具。您负责完整的转换。
提供者策略:
@pulumi/azure-native 以获得完整的 Azure 资源管理器 API 覆盖@pulumi/azure(经典提供者)文档:
* [Azure Native Provider](https://www.pulumi.com/registry/packages/azure-native/)
* [Azure Classic Provider](https://www.pulumi.com/registry/packages/azure/)
2. 语言支持:
* **TypeScript/JavaScript**:最常见,IDE 支持优秀
* **Python**:非常适合数据团队和机器学习工作流
* **C#**:.NET 团队的天然选择
* **Go**:高性能,强类型
* **Java**:企业 Java 团队
* **YAML**:简单的声明式方法
* 根据用户偏好或现有代码库选择
3. 完整覆盖:
* 转换 ARM 模板中的所有资源
* 保留所有条件语句、循环和依赖关系
* 维护参数和变量逻辑
ARM 模板:
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "[parameters('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2",
"properties": {
"supportsHttpsTrafficOnly": true
}
}
Pulumi TypeScript:
import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";
const config = new pulumi.Config();
const storageAccountName = config.require("storageAccountName");
const location = config.require("location");
const resourceGroupName = config.require("resourceGroupName");
const storageAccount = new azure_native.storage.StorageAccount("storageAccount", {
accountName: storageAccountName,
location: location,
resourceGroupName: resourceGroupName,
sku: {
name: azure_native.storage.SkuName.Standard_LRS,
},
kind: azure_native.storage.Kind.StorageV2,
enableHttpsTrafficOnly: true,
});
ARM 模板:
{
"parameters": {
"location": {
"type": "string",
"defaultValue": "eastus",
"metadata": {
"description": "Location for resources"
}
},
"instanceCount": {
"type": "int",
"defaultValue": 2,
"minValue": 1,
"maxValue": 10
},
"enableBackup": {
"type": "bool",
"defaultValue": true
},
"secretValue": {
"type": "securestring"
}
}
}
Pulumi TypeScript:
const config = new pulumi.Config();
const location = config.get("location") || "eastus";
const instanceCount = config.getNumber("instanceCount") || 2;
const enableBackup = config.getBoolean("enableBackup") ?? true;
const secretValue = config.requireSecret("secretValue"); // 返回 Output<string>
ARM 模板:
{
"variables": {
"storageAccountName": "[concat('storage', uniqueString(resourceGroup().id))]",
"webAppName": "[concat(parameters('prefix'), '-webapp')]"
}
}
Pulumi TypeScript:
import * as pulumi from "@pulumi/pulumi";
const config = new pulumi.Config();
const prefix = config.require("prefix");
const resourceGroupId = config.require("resourceGroupId");
// 简单变量
const webAppName = `${prefix}-webapp`;
// 对于 uniqueString 等效项,使用堆栈名称或生成哈希
const storageAccountName = `storage${resourceGroupId}`.toLowerCase();
ARM 模板:
{
"type": "Microsoft.Network/virtualNetworks/subnets",
"apiVersion": "2023-05-01",
"name": "[concat(variables('vnetName'), '/subnet-', copyIndex())]",
"copy": {
"name": "subnetCopy",
"count": "[parameters('subnetCount')]"
},
"properties": {
"addressPrefix": "[concat('10.0.', copyIndex(), '.0/24')]"
}
}
Pulumi TypeScript:
const config = new pulumi.Config();
const subnetCount = config.getNumber("subnetCount") || 3;
const subnets: azure_native.network.Subnet[] = [];
for (let i = 0; i < subnetCount; i++) {
subnets.push(new azure_native.network.Subnet(`subnet-${i}`, {
subnetName: `subnet-${i}`,
virtualNetworkName: vnet.name,
resourceGroupName: resourceGroup.name,
addressPrefix: `10.0.${i}.0/24`,
}));
}
ARM 模板:
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2023-05-01",
"condition": "[parameters('createPublicIP')]",
"name": "[variables('publicIPName')]",
"location": "[parameters('location')]"
}
Pulumi TypeScript:
const config = new pulumi.Config();
const createPublicIP = config.getBoolean("createPublicIP") ?? false;
let publicIP: azure_native.network.PublicIPAddress | undefined;
if (createPublicIP) {
publicIP = new azure_native.network.PublicIPAddress("publicIP", {
publicIpAddressName: publicIPName,
location: location,
resourceGroupName: resourceGroup.name,
});
}
// 处理可选引用
const publicIPId = publicIP ? publicIP.id : pulumi.output(undefined);
ARM 模板:
{
"type": "Microsoft.Web/sites",
"apiVersion": "2023-01-01",
"name": "[variables('webAppName')]",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
]
}
Pulumi TypeScript:
// 隐式依赖(首选)
const webApp = new azure_native.web.WebApp("webApp", {
name: webAppName,
resourceGroupName: resourceGroup.name,
serverFarmId: appServicePlan.id, // 通过属性引用实现隐式依赖
});
// 显式依赖(需要时)
const webApp = new azure_native.web.WebApp("webApp", {
name: webAppName,
resourceGroupName: resourceGroup.name,
serverFarmId: appServicePlan.id,
}, {
dependsOn: [appServicePlan], // 显式依赖
});
ARM 模板:
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2021-04-01",
"name": "nestedTemplate",
"properties": {
"mode": "Incremental",
"template": {
"resources": [...]
}
}
}
Pulumi 方法:
使用 Pulumi ComponentResource 来分组相关资源,而不是嵌套模板:
class NetworkComponent extends pulumi.ComponentResource {
public readonly vnet: azure_native.network.VirtualNetwork;
public readonly subnets: azure_native.network.Subnet[];
constructor(name: string, args: NetworkComponentArgs, opts?: pulumi.ComponentResourceOptions) {
super("custom:azure:NetworkComponent", name, {}, opts);
const defaultOptions = { parent: this };
this.vnet = new azure_native.network.VirtualNetwork(`${name}-vnet`, {
virtualNetworkName: args.vnetName,
resourceGroupName: args.resourceGroupName,
location: args.location,
addressSpace: {
addressPrefixes: [args.addressPrefix],
},
}, defaultOptions);
this.subnets = args.subnets.map((subnet, i) =>
new azure_native.network.Subnet(`${name}-subnet-${i}`, {
subnetName: subnet.name,
virtualNetworkName: this.vnet.name,
resourceGroupName: args.resourceGroupName,
addressPrefix: subnet.addressPrefix,
}, defaultOptions)
);
this.registerOutputs({
vnetId: this.vnet.id,
subnetIds: this.subnets.map(s => s.id),
});
}
}
ARM 模板:
{
"outputs": {
"storageAccountName": {
"type": "string",
"value": "[variables('storageAccountName')]"
},
"storageAccountId": {
"type": "string",
"value": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
}
}
}
Pulumi TypeScript:
export const storageAccountName = storageAccount.name;
export const storageAccountId = storageAccount.id;
在某些情况下,您可能需要使用 Azure 经典提供者(@pulumi/azure)而不是 Azure Native。经典提供者提供了简化的抽象,对于某些资源可能更容易使用。
ARM 模板:
{
"type": "Microsoft.Network/virtualNetworks",
"apiVersion": "2023-05-01",
"name": "[parameters('vnetName')]",
"location": "[parameters('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"10.0.0.0/16"
]
},
"subnets": [
{
"name": "default",
"properties": {
"addressPrefix": "10.0.1.0/24"
}
},
{
"name": "apps",
"properties": {
"addressPrefix": "10.0.2.0/24"
}
}
]
}
}
Pulumi TypeScript(经典提供者):
import * as pulumi from "@pulumi/pulumi";
import * as azure from "@pulumi/azure";
const config = new pulumi.Config();
const vnetName = config.require("vnetName");
const location = config.require("location");
const resourceGroupName = config.require("resourceGroupName");
const vnet = new azure.network.VirtualNetwork("vnet", {
name: vnetName,
location: location,
resourceGroupName: resourceGroupName,
addressSpaces: ["10.0.0.0/16"],
subnets: [
{
name: "default",
addressPrefix: "10.0.1.0/24",
},
{
name: "apps",
addressPrefix: "10.0.2.0/24",
},
],
});
注意: 经典提供者允许在 VirtualNetwork 资源内联定义子网,这比将它们作为单独资源管理更简单。
ARM 模板:
{
"resources": [
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2023-01-01",
"name": "[parameters('appServicePlanName')]",
"location": "[parameters('location')]",
"sku": {
"name": "B1",
"tier": "Basic",
"size": "B1",
"capacity": 1
},
"kind": "linux",
"properties": {
"reserved": true
}
},
{
"type": "Microsoft.Web/sites",
"apiVersion": "2023-01-01",
"name": "[parameters('webAppName')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]"
],
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]",
"siteConfig": {
"linuxFxVersion": "NODE|18-lts",
"appSettings": [
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "~18"
}
]
}
}
}
]
}
Pulumi TypeScript(经典提供者):
import * as pulumi from "@pulumi/pulumi";
import * as azure from "@pulumi/azure";
const config = new pulumi.Config();
const appServicePlanName = config.require("appServicePlanName");
const webAppName = config.require("webAppName");
const location = config.require("location");
const resourceGroupName = config.require("resourceGroupName");
const appServicePlan = new azure.appservice.ServicePlan("appServicePlan", {
name: appServicePlanName,
location: location,
resourceGroupName: resourceGroupName,
osType: "Linux",
skuName: "B1",
});
const webApp = new azure.appservice.LinuxWebApp("webApp", {
name: webAppName,
location: location,
resourceGroupName: resourceGroupName,
servicePlanId: appServicePlan.id,
siteConfig: {
applicationStack: {
nodeVersion: "18-lts",
},
},
appSettings: {
"WEBSITE_NODE_DEFAULT_VERSION": "~18",
},
});
注意: 经典提供者具有专门的资源,如 LinuxWebApp 和 WindowsWebApp,与通用 WebApp 资源相比,它们提供了更好的类型安全性和更清晰的配置选项。
Azure Native 输出通常包含 undefined。避免使用 ! 非空断言。始终使用 .apply() 安全地解包:
// ❌ 错误 - 会导致 TypeScript 错误
const webAppUrl = `https://${webApp.defaultHostName!}`;
// ✅ 正确 - 安全地处理 undefined
const webAppUrl = webApp.defaultHostName.apply(hostname =>
hostname ? `https://${hostname}` : ""
);
ARM 模板的 name 属性映射到 Pulumi 中的特定命名字段:
// ARM: "name": "myStorageAccount"
// Pulumi:
new azure_native.storage.StorageAccount("logicalName", {
accountName: "mystorageaccount", // 实际的 Azure 资源名称
// ...
});
ARM 模板需要显式的 API 版本。Pulumi 提供者默认使用最新的稳定 API 版本:
// ARM: "apiVersion": "2023-01-01"
// Pulumi: API 版本嵌入在提供者中
请查阅 Pulumi Registry 文档以了解每个资源使用的 API 版本。
.apply())azure 提供者concat()、uniqueString() 等转换后,您可以选择性地导入现有资源由 Pulumi 管理。如果用户未请求此操作,建议将其作为转换的后续步骤。
关键: 当用户请求将现有 Azure 资源导入 Pulumi 时,请参阅 arm-import.md 以获取详细的导入程序和零差异验证工作流程。
arm-import.md 提供:
内联导入方法:
import 资源选项pulumi-cdk-importer 不同)Azure 资源 ID:
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}零差异验证:
pulumi preview设置与 ARM 模板参数匹配的堆栈配置:
# 设置 Azure 区域
pulumi config set azure-native:location eastus --stack dev
# 设置应用程序参数
pulumi config set storageAccountName mystorageaccount --stack dev
# 设置秘密参数
pulumi config set --secret adminPassword MyS3cr3tP@ssw0rd --stack dev
在预览中实现零差异后(如果导入),验证迁移:
pulumi stack output
pulumi stack graph
测试应用程序功能(如果适用)
记录迁移后所需的任何手动步骤
如果用户请求帮助规划或执行 ARM 到 Pulumi 的迁移,请使用上述信息指导用户完成转换和导入过程。
当用户需要额外信息时,使用 web-fetch 工具从官方 Pulumi 文档获取内容:
Microsoft Azure 文档:
执行迁移时,始终生成:
pulumi config set 命令保持代码语法有效并按文件清晰分隔。
每周安装
1.4K
仓库
GitHub 星标
30
首次出现
2026年1月28日
安全审计
安装于
opencode1.2K
codex1.2K
github-copilot1.2K
gemini-cli1.2K
amp1.2K
kimi-cli1.2K
If you have already generated a migration plan before loading this skill, you MUST:
The migration output MUST meet all of the following:
Complete Resource Coverage
Successful Deployment
pulumi preview (assuming proper config).Zero-Diff Import Validation (if importing existing resources)
pulumi preview must show:
Final Migration Report
If a user-provided ARM template is incomplete, ambiguous, or missing artifacts, ask targeted questions before generating Pulumi code.
If there is ambiguity on how to handle a specific resource property on import, ask targeted questions before altering Pulumi code.
Follow this workflow exactly and in this order:
Running Azure CLI commands (e.g., az resource list, az resource show). Requires initial login using ESC and az login
Setting up Azure CLI using ESC:
pulumi env run {org}/{project}/{environment} -- bash -c 'az login --service-principal -u "$ARM_CLIENT_ID" --tenant "$ARM_TENANT_ID" --federated-token "$ARM_OIDC_TOKEN"'. ESC is not required after establishing the sessionaz account showaz account list --query "[].{Name:name, SubscriptionId:id, IsDefault:isDefault}" -o tableFor detailed ESC information: Load the pulumi-esc skill by calling the tool "Skill" with name = "pulumi-esc"
ARM templates do not have the concept of "stacks" like CloudFormation. Read the ARM template JSON file directly:
# View template structure
cat template.json | jq '.resources[] | {type: .type, name: .name}'
# View parameters
cat template.json | jq '.parameters'
# View variables
cat template.json | jq '.variables'
Extract:
Documentation: ARM Template Structure
If the ARM template has already been deployed and you're importing existing resources:
# List all resources in a resource group
az resource list \
--resource-group <resource-group-name> \
--output json
# Get specific resource details
az resource show \
--ids <resource-id> \
--output json
# Query specific properties using JMESPath
az resource show \
--ids <resource-id> \
--query "{name:name, location:location, properties:properties}" \
--output json
Documentation: Azure CLI Documentation
IMPORTANT: ARM to Pulumi conversion requires manual translation. There is NO automated conversion tool for ARM templates. You are responsible for the complete conversion.
Provider Strategy :
@pulumi/azure-native for full Azure Resource Manager API coverage@pulumi/azure (classic provider) when azure-native doesn't support specific features or when you need simplified abstractionsDocumentation:
* [Azure Native Provider](https://www.pulumi.com/registry/packages/azure-native/)
* [Azure Classic Provider](https://www.pulumi.com/registry/packages/azure/)
2. Language Support :
* **TypeScript/JavaScript** : Most common, excellent IDE support
* **Python** : Great for data teams and ML workflows
* **C#** : Natural fit for .NET teams
* **Go** : High performance, strong typing
* **Java** : Enterprise Java teams
* **YAML** : Simple declarative approach
* Choose based on user preference or existing codebase
3. Complete Coverage :
* Convert ALL resources in the ARM template
* Preserve all conditionals, loops, and dependencies
* Maintain parameter and variable logic
ARM Template:
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "[parameters('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2",
"properties": {
"supportsHttpsTrafficOnly": true
}
}
Pulumi TypeScript:
import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";
const config = new pulumi.Config();
const storageAccountName = config.require("storageAccountName");
const location = config.require("location");
const resourceGroupName = config.require("resourceGroupName");
const storageAccount = new azure_native.storage.StorageAccount("storageAccount", {
accountName: storageAccountName,
location: location,
resourceGroupName: resourceGroupName,
sku: {
name: azure_native.storage.SkuName.Standard_LRS,
},
kind: azure_native.storage.Kind.StorageV2,
enableHttpsTrafficOnly: true,
});
ARM Template:
{
"parameters": {
"location": {
"type": "string",
"defaultValue": "eastus",
"metadata": {
"description": "Location for resources"
}
},
"instanceCount": {
"type": "int",
"defaultValue": 2,
"minValue": 1,
"maxValue": 10
},
"enableBackup": {
"type": "bool",
"defaultValue": true
},
"secretValue": {
"type": "securestring"
}
}
}
Pulumi TypeScript:
const config = new pulumi.Config();
const location = config.get("location") || "eastus";
const instanceCount = config.getNumber("instanceCount") || 2;
const enableBackup = config.getBoolean("enableBackup") ?? true;
const secretValue = config.requireSecret("secretValue"); // Returns Output<string>
ARM Template:
{
"variables": {
"storageAccountName": "[concat('storage', uniqueString(resourceGroup().id))]",
"webAppName": "[concat(parameters('prefix'), '-webapp')]"
}
}
Pulumi TypeScript:
import * as pulumi from "@pulumi/pulumi";
const config = new pulumi.Config();
const prefix = config.require("prefix");
const resourceGroupId = config.require("resourceGroupId");
// Simple variable
const webAppName = `${prefix}-webapp`;
// For uniqueString equivalent, use stack name or generate hash
const storageAccountName = `storage${resourceGroupId}`.toLowerCase();
ARM Template:
{
"type": "Microsoft.Network/virtualNetworks/subnets",
"apiVersion": "2023-05-01",
"name": "[concat(variables('vnetName'), '/subnet-', copyIndex())]",
"copy": {
"name": "subnetCopy",
"count": "[parameters('subnetCount')]"
},
"properties": {
"addressPrefix": "[concat('10.0.', copyIndex(), '.0/24')]"
}
}
Pulumi TypeScript:
const config = new pulumi.Config();
const subnetCount = config.getNumber("subnetCount") || 3;
const subnets: azure_native.network.Subnet[] = [];
for (let i = 0; i < subnetCount; i++) {
subnets.push(new azure_native.network.Subnet(`subnet-${i}`, {
subnetName: `subnet-${i}`,
virtualNetworkName: vnet.name,
resourceGroupName: resourceGroup.name,
addressPrefix: `10.0.${i}.0/24`,
}));
}
ARM Template:
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2023-05-01",
"condition": "[parameters('createPublicIP')]",
"name": "[variables('publicIPName')]",
"location": "[parameters('location')]"
}
Pulumi TypeScript:
const config = new pulumi.Config();
const createPublicIP = config.getBoolean("createPublicIP") ?? false;
let publicIP: azure_native.network.PublicIPAddress | undefined;
if (createPublicIP) {
publicIP = new azure_native.network.PublicIPAddress("publicIP", {
publicIpAddressName: publicIPName,
location: location,
resourceGroupName: resourceGroup.name,
});
}
// Handle optional references
const publicIPId = publicIP ? publicIP.id : pulumi.output(undefined);
ARM Template:
{
"type": "Microsoft.Web/sites",
"apiVersion": "2023-01-01",
"name": "[variables('webAppName')]",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
]
}
Pulumi TypeScript:
// Implicit dependency (preferred)
const webApp = new azure_native.web.WebApp("webApp", {
name: webAppName,
resourceGroupName: resourceGroup.name,
serverFarmId: appServicePlan.id, // Implicit dependency through property reference
});
// Explicit dependency (when needed)
const webApp = new azure_native.web.WebApp("webApp", {
name: webAppName,
resourceGroupName: resourceGroup.name,
serverFarmId: appServicePlan.id,
}, {
dependsOn: [appServicePlan], // Explicit dependency
});
ARM Template:
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2021-04-01",
"name": "nestedTemplate",
"properties": {
"mode": "Incremental",
"template": {
"resources": [...]
}
}
}
Pulumi Approach:
Instead of nested templates, use Pulumi ComponentResource to group related resources:
class NetworkComponent extends pulumi.ComponentResource {
public readonly vnet: azure_native.network.VirtualNetwork;
public readonly subnets: azure_native.network.Subnet[];
constructor(name: string, args: NetworkComponentArgs, opts?: pulumi.ComponentResourceOptions) {
super("custom:azure:NetworkComponent", name, {}, opts);
const defaultOptions = { parent: this };
this.vnet = new azure_native.network.VirtualNetwork(`${name}-vnet`, {
virtualNetworkName: args.vnetName,
resourceGroupName: args.resourceGroupName,
location: args.location,
addressSpace: {
addressPrefixes: [args.addressPrefix],
},
}, defaultOptions);
this.subnets = args.subnets.map((subnet, i) =>
new azure_native.network.Subnet(`${name}-subnet-${i}`, {
subnetName: subnet.name,
virtualNetworkName: this.vnet.name,
resourceGroupName: args.resourceGroupName,
addressPrefix: subnet.addressPrefix,
}, defaultOptions)
);
this.registerOutputs({
vnetId: this.vnet.id,
subnetIds: this.subnets.map(s => s.id),
});
}
}
ARM Template:
{
"outputs": {
"storageAccountName": {
"type": "string",
"value": "[variables('storageAccountName')]"
},
"storageAccountId": {
"type": "string",
"value": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
}
}
}
Pulumi TypeScript:
export const storageAccountName = storageAccount.name;
export const storageAccountId = storageAccount.id;
In some cases, you may need to use the Azure Classic provider (@pulumi/azure) instead of Azure Native. The Classic provider offers simplified abstractions and may be easier to work with for certain resources.
ARM Template:
{
"type": "Microsoft.Network/virtualNetworks",
"apiVersion": "2023-05-01",
"name": "[parameters('vnetName')]",
"location": "[parameters('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"10.0.0.0/16"
]
},
"subnets": [
{
"name": "default",
"properties": {
"addressPrefix": "10.0.1.0/24"
}
},
{
"name": "apps",
"properties": {
"addressPrefix": "10.0.2.0/24"
}
}
]
}
}
Pulumi TypeScript (Classic Provider):
import * as pulumi from "@pulumi/pulumi";
import * as azure from "@pulumi/azure";
const config = new pulumi.Config();
const vnetName = config.require("vnetName");
const location = config.require("location");
const resourceGroupName = config.require("resourceGroupName");
const vnet = new azure.network.VirtualNetwork("vnet", {
name: vnetName,
location: location,
resourceGroupName: resourceGroupName,
addressSpaces: ["10.0.0.0/16"],
subnets: [
{
name: "default",
addressPrefix: "10.0.1.0/24",
},
{
name: "apps",
addressPrefix: "10.0.2.0/24",
},
],
});
Note: The Classic provider allows defining subnets inline within the VirtualNetwork resource, which can be simpler than managing them as separate resources.
ARM Template:
{
"resources": [
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2023-01-01",
"name": "[parameters('appServicePlanName')]",
"location": "[parameters('location')]",
"sku": {
"name": "B1",
"tier": "Basic",
"size": "B1",
"capacity": 1
},
"kind": "linux",
"properties": {
"reserved": true
}
},
{
"type": "Microsoft.Web/sites",
"apiVersion": "2023-01-01",
"name": "[parameters('webAppName')]",
"location": "[parameters('location')]",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]"
],
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]",
"siteConfig": {
"linuxFxVersion": "NODE|18-lts",
"appSettings": [
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "~18"
}
]
}
}
}
]
}
Pulumi TypeScript (Classic Provider):
import * as pulumi from "@pulumi/pulumi";
import * as azure from "@pulumi/azure";
const config = new pulumi.Config();
const appServicePlanName = config.require("appServicePlanName");
const webAppName = config.require("webAppName");
const location = config.require("location");
const resourceGroupName = config.require("resourceGroupName");
const appServicePlan = new azure.appservice.ServicePlan("appServicePlan", {
name: appServicePlanName,
location: location,
resourceGroupName: resourceGroupName,
osType: "Linux",
skuName: "B1",
});
const webApp = new azure.appservice.LinuxWebApp("webApp", {
name: webAppName,
location: location,
resourceGroupName: resourceGroupName,
servicePlanId: appServicePlan.id,
siteConfig: {
applicationStack: {
nodeVersion: "18-lts",
},
},
appSettings: {
"WEBSITE_NODE_DEFAULT_VERSION": "~18",
},
});
Note: The Classic provider has dedicated resources like LinuxWebApp and WindowsWebApp that provide better type safety and clearer configuration options compared to the generic WebApp resource.
Azure Native outputs often include undefined. Avoid ! non-null assertions. Always safely unwrap with .apply():
// ❌ WRONG - Will cause TypeScript errors
const webAppUrl = `https://${webApp.defaultHostName!}`;
// ✅ CORRECT - Handle undefined safely
const webAppUrl = webApp.defaultHostName.apply(hostname =>
hostname ? `https://${hostname}` : ""
);
ARM template name property maps to specific naming fields in Pulumi:
// ARM: "name": "myStorageAccount"
// Pulumi:
new azure_native.storage.StorageAccount("logicalName", {
accountName: "mystorageaccount", // Actual Azure resource name
// ...
});
ARM templates require explicit API versions. Pulumi providers use recent stable API versions by default:
// ARM: "apiVersion": "2023-01-01"
// Pulumi: API version is embedded in the provider
Check the Pulumi Registry documentation for which API version each resource uses.
.apply() in TypeScript)azure provider when azure-native is availableconcat(), uniqueString(), etc.After conversion, you can optionally import existing resources to be managed by Pulumi. If the user does not request this, suggest it as a follow-up step to conversion.
CRITICAL : When the user requests importing existing Azure resources into Pulumi, see arm-import.md for detailed import procedures and zero-diff validation workflows.
arm-import.md provides:
Inline Import Approach :
import resource option with Azure Resource IDspulumi-cdk-importer)Azure Resource IDs :
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}Zero-Diff Validation :
pulumi preview after importSet up stack configuration matching ARM template parameters:
# Set Azure region
pulumi config set azure-native:location eastus --stack dev
# Set application parameters
pulumi config set storageAccountName mystorageaccount --stack dev
# Set secret parameters
pulumi config set --secret adminPassword MyS3cr3tP@ssw0rd --stack dev
After achieving zero diff in preview (if importing), validate the migration:
Review all exports:
pulumi stack output
Verify resource relationships:
pulumi stack graph
Test application functionality (if applicable)
Document any manual steps required post-migration
If the user asks for help planning or performing an ARM to Pulumi migration, use the information above to guide the user through the conversion and import process.
When the user wants additional information, use the web-fetch tool to get content from the official Pulumi documentation:
Microsoft Azure Documentation:
When performing a migration, always produce:
pulumi config set commandsKeep code syntactically valid and clearly separated by files.
Weekly Installs
1.4K
Repository
GitHub Stars
30
First Seen
Jan 28, 2026
Security Audits
Gen Agent Trust HubFailSocketPassSnykFail
Installed on
opencode1.2K
codex1.2K
github-copilot1.2K
gemini-cli1.2K
amp1.2K
kimi-cli1.2K
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装