pulumi-arm-to-pulumi
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseIMPORTANT: PLAN ADJUSTMENT REQUIRED
重要提示:需调整计划
If you have already generated a migration plan before loading this skill, you MUST:
- Review your existing plan against the workflows and requirements in this skill
- Identify any gaps, missing steps, or incorrect assumptions
- Update and revise your plan to align with this skill's guidance
- Communicate the adjusted plan to the user before proceeding
如果您在加载此技能前已生成迁移计划,必须执行以下操作:
- 对照此技能中的工作流和要求,审核现有计划
- 找出任何缺口、缺失步骤或错误假设
- 更新并修订计划,使其与本技能的指导方针保持一致
- 在继续操作前,将调整后的计划告知用户
CRITICAL SUCCESS REQUIREMENTS
关键成功要求
The migration output MUST meet all of the following:
-
Complete Resource Coverage
- Every ARM template resource MUST:
- Be represented in the Pulumi program OR
- Be explicitly justified in the final report.
- Every ARM template resource MUST:
-
Successful Deployment
- The produced Pulumi program must be structurally valid and capable of a successful (assuming proper config).
pulumi preview
- The produced Pulumi program must be structurally valid and capable of a successful
-
Zero-Diff Import Validation (if importing existing resources)
- After import, must show:
pulumi preview- NO updates
- NO replaces
- NO creates
- NO deletes
- Any diffs must be resolved using the Preview Resolution Workflow. See arm-import.md.
- After import,
-
Final Migration Report
- Always output a formal migration report suitable for a Pull Request.
- Include:
- ARM → Pulumi resource mapping
- Provider decisions (azure-native vs azure)
- Behavioral differences
- Missing or manually required steps
- Validation instructions
迁移输出必须满足以下所有条件:
-
完整资源覆盖
- 每个ARM模板资源必须:
- 在Pulumi程序中有所体现 或者
- 在最终报告中给出明确的合理说明。
- 每个ARM模板资源必须:
-
部署成功
- 生成的Pulumi程序必须结构有效,并且在配置正确的情况下能够成功执行。
pulumi preview
- 生成的Pulumi程序必须结构有效,并且在配置正确的情况下能够成功执行
-
零差异导入验证(如果导入现有资源)
- 导入后,必须显示:
pulumi preview- 无更新
- 无替换
- 无创建
- 无删除
- 任何差异必须使用预览解决工作流来处理。请参阅arm-import.md。
- 导入后,
-
最终迁移报告
- 始终输出适合用于拉取请求的正式迁移报告。
- 报告需包含:
- ARM → Pulumi资源映射
- 提供商选择决策(azure-native vs azure)
- 行为差异
- 缺失或需要手动执行的步骤
- 验证说明
WHEN INFORMATION IS MISSING
当信息缺失时
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.
如果用户提供的ARM模板不完整、模糊或缺少相关构件,在生成Pulumi代码前请提出针对性问题。
如果在导入时对如何处理特定资源属性存在疑问,在修改Pulumi代码前请提出针对性问题。
MIGRATION WORKFLOW
迁移工作流
Follow this workflow exactly and in this order:
请严格按照以下顺序执行此工作流:
1. INFORMATION GATHERING
1. 信息收集
1.1 Verify Azure Credentials
1.1 验证Azure凭据
Running Azure CLI commands (e.g., , ). Requires initial login using ESC and
az resource listaz resource showaz login- If the user has already provided an ESC environment, use it.
- If no ESC environment is specified, ask the user which ESC environment to use before proceeding with Azure CLI commands.
Setting up Azure CLI using ESC:
- ESC environments can provide Azure credentials through environment variables or Azure CLI configuration
- Login to Azure using ESC to provide credentials, e.g: . ESC is not required after establishing the session
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"' - Verify credentials are working:
az account show - Confirm subscription:
az account list --query "[].{Name:name, SubscriptionId:id, IsDefault:isDefault}" -o table
For detailed ESC information: Load the skill by calling the tool "Skill" with name = "pulumi-esc"
pulumi-esc运行Azure CLI命令(例如:、)。需要先使用ESC和登录
az resource listaz resource showaz login- 如果用户已提供ESC环境,请使用该环境。
- 如果未指定ESC环境,在执行Azure CLI命令前请询问用户使用哪个ESC环境。
使用ESC设置Azure CLI:
- ESC环境可通过环境变量或Azure CLI配置提供Azure凭据
- 使用ESC登录Azure以提供凭据,例如:。建立会话后无需再使用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"' - 验证凭据是否可用:
az account show - 确认订阅:
az account list --query "[].{Name:name, SubscriptionId:id, IsDefault:isDefault}" -o table
如需详细的ESC信息: 调用工具"Skill"并指定名称为"pulumi-esc"来加载技能
pulumi-esc1.2 Analyze ARM Template Structure
1.2 分析ARM模板结构
ARM templates do not have the concept of "stacks" like CloudFormation. Read the ARM template JSON file directly:
bash
undefinedARM模板没有像CloudFormation那样的“栈”概念。直接读取ARM模板JSON文件:
bash
undefinedView template structure
查看模板结构
cat template.json | jq '.resources[] | {type: .type, name: .name}'
cat template.json | jq '.resources[] | {type: .type, name: .name}'
View parameters
查看参数
cat template.json | jq '.parameters'
cat template.json | jq '.parameters'
View variables
查看变量
cat template.json | jq '.variables'
Extract:
- Resource types and names
- Parameters and their default values
- Variables and expressions
- Dependencies (dependsOn arrays)
- Nested templates or linked templates
- Copy loops (iteration constructs)
- Conditional deployments (condition property)
**Documentation:** [ARM Template Structure](https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/syntax)cat template.json | jq '.variables'
提取以下信息:
- 资源类型和名称
- 参数及其默认值
- 变量和表达式
- 依赖关系(dependsOn数组)
- 嵌套模板或链接模板
- 复制循环(迭代结构)
- 条件部署(condition属性)
**文档:** [ARM模板结构](https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/syntax)1.3 Build Resource Inventory (if importing existing resources)
1.3 构建资源清单(如果导入现有资源)
If the ARM template has already been deployed and you're importing existing resources:
bash
undefined如果ARM模板已部署,且您要导入现有资源:
bash
undefinedList all resources in a resource group
列出资源组中的所有资源
az resource list
--resource-group <resource-group-name>
--output json
--resource-group <resource-group-name>
--output json
az resource list
--resource-group <resource-group-name>
--output json
--resource-group <resource-group-name>
--output json
Get specific resource details
获取特定资源的详细信息
az resource show
--ids <resource-id>
--output json
--ids <resource-id>
--output json
az resource show
--ids <resource-id>
--output json
--ids <resource-id>
--output json
Query specific properties using JMESPath
使用JMESPath查询特定属性
az resource show
--ids <resource-id>
--query "{name:name, location:location, properties:properties}"
--output json
--ids <resource-id>
--query "{name:name, location:location, properties:properties}"
--output json
**Documentation:** [Azure CLI Documentation](https://learn.microsoft.com/en-us/cli/azure/)az resource show
--ids <resource-id>
--query "{name:name, location:location, properties:properties}"
--output json
--ids <resource-id>
--query "{name:name, location:location, properties:properties}"
--output json
**文档:** [Azure CLI文档](https://learn.microsoft.com/en-us/cli/azure/)2. CODE CONVERSION (ARM → PULUMI)
2. 代码转换(ARM → PULUMI)
IMPORTANT: ARM to Pulumi conversion requires manual translation. There is NO automated conversion tool for ARM templates. You are responsible for the complete conversion.
重要提示: ARM到Pulumi的转换需要手动翻译。目前没有针对ARM模板的自动化转换工具。您需负责完成完整的转换工作。
Key Conversion Principles
核心转换原则
-
Provider Strategy:
- Default: Use for full Azure Resource Manager API coverage
@pulumi/azure-native - Fallback: Use (classic provider) when azure-native doesn't support specific features or when you need simplified abstractions
@pulumi/azure
Documentation: - Default: Use
-
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
-
Complete Coverage:
- Convert ALL resources in the ARM template
- Preserve all conditionals, loops, and dependencies
- Maintain parameter and variable logic
-
提供商策略:
- 默认选择:使用以获得完整的Azure资源管理器API覆盖
@pulumi/azure-native - 备选方案:当azure-native不支持特定功能,或者您需要简化的抽象时,使用(经典提供商)
@pulumi/azure
文档: - 默认选择:使用
-
语言支持:
- TypeScript/JavaScript:最常用,具备出色的IDE支持
- Python:非常适合数据团队和ML工作流
- C#:.NET团队的自然选择
- Go:高性能,强类型
- Java:面向企业Java团队
- YAML:简单的声明式方法
- 根据用户偏好或现有代码库选择合适的语言
-
完整覆盖:
- 转换ARM模板中的所有资源
- 保留所有条件、循环和依赖关系
- 维护参数和变量逻辑
ARM Template Conversion Patterns
ARM模板转换模式
Basic Resource Conversion
基础资源转换
ARM Template:
json
{
"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:
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模板:
json
{
"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:
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 → Pulumi Config
ARM参数 → Pulumi配置
ARM Template:
json
{
"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:
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模板:
json
{
"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:
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 → Pulumi Variables
ARM变量 → Pulumi变量
ARM Template:
json
{
"variables": {
"storageAccountName": "[concat('storage', uniqueString(resourceGroup().id))]",
"webAppName": "[concat(parameters('prefix'), '-webapp')]"
}
}Pulumi TypeScript:
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模板:
json
{
"variables": {
"storageAccountName": "[concat('storage', uniqueString(resourceGroup().id))]",
"webAppName": "[concat(parameters('prefix'), '-webapp')]"
}
}Pulumi TypeScript:
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 Copy Loops → Pulumi Loops
ARM复制循环 → Pulumi循环
ARM Template:
json
{
"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:
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模板:
json
{
"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:
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 Conditional Resources → Pulumi Conditionals
ARM条件资源 → Pulumi条件语句
ARM Template:
json
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2023-05-01",
"condition": "[parameters('createPublicIP')]",
"name": "[variables('publicIPName')]",
"location": "[parameters('location')]"
}Pulumi TypeScript:
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模板:
json
{
"type": "Microsoft.Network/publicIPAddresses",
"apiVersion": "2023-05-01",
"condition": "[parameters('createPublicIP')]",
"name": "[variables('publicIPName')]",
"location": "[parameters('location')]"
}Pulumi TypeScript:
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 DependsOn → Pulumi Dependencies
ARM DependsOn → Pulumi依赖关系
ARM Template:
json
{
"type": "Microsoft.Web/sites",
"apiVersion": "2023-01-01",
"name": "[variables('webAppName')]",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
]
}Pulumi TypeScript:
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模板:
json
{
"type": "Microsoft.Web/sites",
"apiVersion": "2023-01-01",
"name": "[variables('webAppName')]",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
]
}Pulumi TypeScript:
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], // 显式依赖
});Nested Templates → Pulumi Component Resources
嵌套模板 → Pulumi组件资源
ARM Template:
json
{
"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:
typescript
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模板:
json
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2021-04-01",
"name": "nestedTemplate",
"properties": {
"mode": "Incremental",
"template": {
"resources": [...]
}
}
}Pulumi实现方式:
不使用嵌套模板,而是使用Pulumi ComponentResource对相关资源进行分组:
typescript
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 → Pulumi Exports
ARM输出 → Pulumi导出
ARM Template:
json
{
"outputs": {
"storageAccountName": {
"type": "string",
"value": "[variables('storageAccountName')]"
},
"storageAccountId": {
"type": "string",
"value": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
}
}
}Pulumi TypeScript:
typescript
export const storageAccountName = storageAccount.name;
export const storageAccountId = storageAccount.id;ARM模板:
json
{
"outputs": {
"storageAccountName": {
"type": "string",
"value": "[variables('storageAccountName')]"
},
"storageAccountId": {
"type": "string",
"value": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
}
}
}Pulumi TypeScript:
typescript
export const storageAccountName = storageAccount.name;
export const storageAccountId = storageAccount.id;Azure Classic Provider Examples
Azure经典提供商示例
In some cases, you may need to use the Azure Classic provider () instead of Azure Native. The Classic provider offers simplified abstractions and may be easier to work with for certain resources.
@pulumi/azure在某些情况下,您可能需要使用Azure经典提供商()而非Azure Native。经典提供商提供简化的抽象,对于某些资源来说可能更易于使用。
@pulumi/azureVirtual Network with Classic Provider
使用经典提供商创建虚拟网络
ARM Template:
json
{
"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):
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",
},
],
});Note: The Classic provider allows defining subnets inline within the VirtualNetwork resource, which can be simpler than managing them as separate resources.
ARM模板:
json
{
"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(经典提供商):
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资源内联定义子网,这比将子网作为单独资源管理更简单。
App Service Plan and Web App with Classic Provider
使用经典提供商创建应用服务计划和Web应用
ARM Template:
json
{
"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):
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",
},
});Note: The Classic provider has dedicated resources like and that provide better type safety and clearer configuration options compared to the generic resource.
LinuxWebAppWindowsWebAppWebAppARM模板:
json
{
"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(经典提供商):
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",
},
});注意: 经典提供商有专门的资源,如和,与通用的资源相比,它们提供了更好的类型安全性和更清晰的配置选项。
LinuxWebAppWindowsWebAppWebAppHandling Azure-Specific Considerations
处理Azure特定注意事项
TypeScript Output Handling
TypeScript输出处理
Azure Native outputs often include undefined. Avoid non-null assertions. Always safely unwrap with :
!.apply()typescript
// ❌ WRONG - Will cause TypeScript errors
const webAppUrl = `https://${webApp.defaultHostName!}`;
// ✅ CORRECT - Handle undefined safely
const webAppUrl = webApp.defaultHostName.apply(hostname =>
hostname ? `https://${hostname}` : ""
);Azure Native输出通常包含undefined。避免使用非空断言。始终使用安全地解包:
!.apply()typescript
// ❌ 错误写法 - 会导致TypeScript错误
const webAppUrl = `https://${webApp.defaultHostName!}`;
// ✅ 正确写法 - 安全处理undefined
const webAppUrl = webApp.defaultHostName.apply(hostname =>
hostname ? `https://${hostname}` : ""
);Resource Naming Conventions
资源命名约定
ARM template property maps to specific naming fields in Pulumi:
nametypescript
// ARM: "name": "myStorageAccount"
// Pulumi:
new azure_native.storage.StorageAccount("logicalName", {
accountName: "mystorageaccount", // Actual Azure resource name
// ...
});ARM模板的属性对应Pulumi中的特定命名字段:
nametypescript
// ARM: "name": "myStorageAccount"
// Pulumi:
new azure_native.storage.StorageAccount("logicalName", {
accountName: "mystorageaccount", // Azure资源的实际名称
// ...
});API Versions
API版本
ARM templates require explicit API versions. Pulumi providers use recent stable API versions by default:
json
// ARM: "apiVersion": "2023-01-01"
// Pulumi: API version is embedded in the providerCheck the Pulumi Registry documentation for which API version each resource uses.
ARM模板需要显式指定API版本。Pulumi提供商默认使用最新的稳定API版本:
json
// ARM: "apiVersion": "2023-01-01"
// Pulumi: API版本已嵌入到提供商中请查看Pulumi Registry文档了解每个资源使用的API版本。
Common Pitfalls to Avoid
需避免的常见陷阱
- ❌ Not handling Output types properly (missing in TypeScript)
.apply() - ❌ Assuming ARM property names match Pulumi property names exactly
- ❌ Using provider when
azureis availableazure-native - ❌ Missing resource dependencies in conversion
- ❌ Not preserving ARM template conditionals and loops
- ❌ Forgetting to convert ARM functions like ,
concat(), etc.uniqueString()
- ❌ 未正确处理Output类型(在TypeScript中缺少)
.apply() - ❌ 假设ARM属性名称与Pulumi属性名称完全匹配
- ❌ 在有azure-native可用的情况下使用提供商
azure - ❌ 转换时遗漏资源依赖关系
- ❌ 未保留ARM模板中的条件语句和循环
- ❌ 忘记转换ARM函数,如、
concat()等uniqueString()
3. RESOURCE IMPORT (EXISTING RESOURCES) - OPTIONAL
3. 资源导入(现有资源)- 可选
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 ID patterns and examples
- Azure Resource ID format conventions
- Child resource handling (e.g., WebAppApplicationSettings)
- Preview Resolution Workflow for achieving zero-diff after import
- Step-by-step debugging for property conflicts
转换完成后,您可以选择将现有资源导入到Pulumi中进行管理。如果用户未提出此请求,可建议将其作为转换后的后续步骤。
关键提示:当用户请求将现有Azure资源导入到Pulumi时,请参阅arm-import.md了解详细的导入流程和零差异验证工作流。
arm-import.md包含:
- 内联导入ID模式和示例
- Azure资源ID格式约定
- 子资源处理(例如WebAppApplicationSettings)
- 预览解决工作流,用于在导入后实现零差异
- 属性冲突的分步调试方法
Key Import Principles
核心导入原则
-
Inline Import Approach:
- Use resource option with Azure Resource IDs
import - No separate import tool (unlike )
pulumi-cdk-importer
- Use
-
Azure Resource IDs:
- Follow predictable pattern:
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName} - Can be generated by convention or queried via Azure CLI
- Follow predictable pattern:
-
Zero-Diff Validation:
- Run after import
pulumi preview - Resolve all diffs using Preview Resolution Workflow
- Goal: NO updates, replaces, creates, or deletes
- Run
-
内联导入方法:
- 使用带Azure资源ID的资源选项
import - 无需单独的导入工具(与不同)
pulumi-cdk-importer
- 使用带Azure资源ID的
-
Azure资源ID:
- 遵循可预测的模式:
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName} - 可通过约定生成或通过Azure CLI查询获取
- 遵循可预测的模式:
-
零差异验证:
- 导入后运行
pulumi preview - 使用预览解决工作流解决所有差异
- 目标:无更新、无替换、无创建、无删除
- 导入后运行
4. PULUMI CONFIGURATION
4. PULUMI配置
Set up stack configuration matching ARM template parameters:
bash
undefined设置与ARM模板参数匹配的栈配置:
bash
undefinedSet Azure region
设置Azure区域
pulumi config set azure-native:location eastus --stack dev
pulumi config set azure-native:location eastus --stack dev
Set application parameters
设置应用参数
pulumi config set storageAccountName mystorageaccount --stack dev
pulumi config set storageAccountName mystorageaccount --stack dev
Set secret parameters
设置机密参数
pulumi config set --secret adminPassword MyS3cr3tP@ssw0rd --stack dev
undefinedpulumi config set --secret adminPassword MyS3cr3tP@ssw0rd --stack dev
undefined5. VALIDATION
5. 验证
After achieving zero diff in preview (if importing), validate the migration:
-
Review all exports:bash
pulumi stack output -
Verify resource relationships:bash
pulumi stack graph -
Test application functionality (if applicable)
-
Document any manual steps required post-migration
如果进行了导入操作,且预览已实现零差异后,验证迁移结果:
-
查看所有导出内容:bash
pulumi stack output -
验证资源关系:bash
pulumi stack graph -
测试应用功能(如适用)
-
记录迁移后需要手动执行的步骤
WORKING WITH THE USER
与用户协作
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.
如果用户请求帮助规划或执行ARM到Pulumi的迁移,请使用上述信息指导用户完成转换和导入流程。
FOR DETAILED DOCUMENTATION
如需详细文档
When the user wants additional information, use the web-fetch tool to get content from the official Pulumi documentation:
- ARM Migration Guide: https://www.pulumi.com/docs/iac/adopting-pulumi/migrating-to-pulumi/from-arm/
- Azure Native Provider: https://www.pulumi.com/registry/packages/azure-native/
- Azure Classic Provider: https://www.pulumi.com/registry/packages/azure/
Microsoft Azure Documentation:
- ARM Template Reference: https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/
- Azure CLI Reference: https://learn.microsoft.com/en-us/cli/azure/
- Azure Resource IDs: https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/template-functions-resource
当用户需要更多信息时,使用web-fetch工具从Pulumi官方文档获取内容:
- ARM迁移指南: https://www.pulumi.com/docs/iac/adopting-pulumi/migrating-to-pulumi/from-arm/
- Azure Native提供商: https://www.pulumi.com/registry/packages/azure-native/
- Azure经典提供商: https://www.pulumi.com/registry/packages/azure/
Microsoft Azure文档:
OUTPUT FORMAT (REQUIRED)
输出格式(必填)
When performing a migration, always produce:
- Overview (high-level description)
- Migration Plan Summary
- ARM template resources identified
- Conversion strategy (language, providers)
- Import approach (if applicable)
- Pulumi Code Outputs (organized by file)
- Main program file
- Component resources (if any)
- Configuration instructions
- Resource Mapping Table (ARM → Pulumi)
- ARM resource type → Pulumi resource type
- ARM resource name → Pulumi logical name
- Import ID (if importing)
- Preview Resolution Notes (if importing)
- Diffs encountered
- Resolution strategy applied
- Properties ignored vs. added
- Final Migration Report (PR-ready)
- Summary of changes
- Testing instructions
- Known limitations
- Next steps
- Configuration Setup
- Required config values
- Example commands
pulumi config set
Keep code syntactically valid and clearly separated by files.
执行迁移时,必须生成以下内容:
- 概述(高层级描述)
- 迁移计划摘要
- 已识别的ARM模板资源
- 转换策略(语言、提供商)
- 导入方法(如适用)
- Pulumi代码输出(按文件组织)
- 主程序文件
- 组件资源(如有)
- 配置说明
- 资源映射表(ARM → Pulumi)
- ARM资源类型 → Pulumi资源类型
- ARM资源名称 → Pulumi逻辑名称
- 导入ID(如导入)
- 预览解决说明(如导入)
- 遇到的差异
- 应用的解决策略
- 忽略或添加的属性
- 最终迁移报告(适合拉取请求)
- 变更摘要
- 测试说明
- 已知限制
- 后续步骤
- 配置设置
- 必填配置值
- 示例命令
pulumi config set
确保代码语法有效,并按文件清晰分隔。