import-infrastructure-as-code by github/awesome-copilot
npx skills add https://github.com/github/awesome-copilot --skill import-infrastructure-as-code使用发现数据和 Azure 已验证模块,将现有 Azure 基础设施转换为可维护的 Terraform 代码。
当用户要求以下操作时使用此技能:
azurerm_* 资源az login)| 参数 | 必需 | 默认值 | 描述 |
|---|---|---|---|
subscription-id | 否 | 活动的 CLI 上下文 |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 用于订阅范围发现和上下文设置的 Azure 订阅 |
resource-group-name | 否 | 无 | 用于资源组范围发现的 Azure 资源组 |
resource-id | 否 | 无 | 一个或多个用于特定资源范围发现的 Azure ARM 资源 ID |
至少需要 subscription-id、resource-group-name 或 resource-id 中的一个。
在运行发现命令之前,请求以下范围之一:
<subscription-id><resource-group-name><resource-id> 值范围处理规则:
/subscriptions/.../providers/...)视为云资源标识符,而非本地文件系统路径。--ids 参数一起使用(例如 az resource show --ids <resource-id>)。cat、ls、read_file、通配符搜索)。如果缺少范围,请明确询问并停止。
仅运行所选范围所需的命令。
对于订阅范围:
az login
az account set --subscription <subscription-id>
az account show --query "{subscriptionId:id, name:name, tenantId:tenantId}" -o json
预期输出:包含 subscriptionId、name 和 tenantId 的 JSON 对象。
对于资源组或特定资源范围,仍然需要 az login,但如果活动上下文已正确,则 az account set 是可选的。
使用特定资源范围时,优先使用基于 --ids 的直接命令,除非具体命令需要,否则避免为订阅或资源组进行额外的发现提示。
使用所选范围发现资源。确保获取所有必要信息以生成准确的 Terraform 代码。
# 订阅范围
az resource list --subscription <subscription-id> -o json
# 资源组范围
az resource list --resource-group <resource-group-name> -o json
# 特定资源范围
az resource show --ids <resource-id-1> <resource-id-2> ... -o json
预期输出:包含 Azure 资源元数据(id、type、name、location、tags、properties)的 JSON 对象或数组。
解析导出的 JSON 并映射:
properties 中的跨资源引用重要:生成以下文档并保存到项目根目录的 docs 文件夹中。
exported-resources.json:包含所有已发现资源及其元数据,包括依赖关系和引用。EXPORTED-ARCHITECTURE.MD 文件:基于已发现资源及其关系,提供人类可读的架构概述。为每种资源类型使用最新的 AVM 版本。
注意: 以下链接始终指向主分支上最新版本的 CSV 文件。正如设计意图,这意味着文件可能会随时间变化。如果需要特定时间点的版本,请考虑在 URL 中使用特定的发布标签。
https://raw.githubusercontent.com/Azure/Azure-Verified-Modules/refs/heads/main/docs/static/module-indexes/TerraformResourceModules.csvhttps://raw.githubusercontent.com/Azure/Azure-Verified-Modules/refs/heads/main/docs/static/module-indexes/TerraformPatternModules.csvhttps://raw.githubusercontent.com/Azure/Azure-Verified-Modules/refs/heads/main/docs/static/module-indexes/TerraformUtilityModules.csv如果本地 .terraform 文件夹中没有模块信息,请使用 web 工具或其他合适的 MCP 方法来获取模块信息。
使用 AVM 源:
https://registry.terraform.io/modules/Azure/<module>/azurerm/latesthttps://github.com/Azure/terraform-azurerm-avm-res-<service>-<resource>当存在 AVM 模块时,优先使用 AVM 模块而非手写的 azurerm_* 资源。
从 GitHub 仓库获取模块信息时,仓库根目录下的 README.md 文件通常包含有关模块的所有详细信息,例如:https://raw.githubusercontent.com/Azure/terraform-azurerm-avm-res--/refs/heads/main/README.md
此步骤不是可选的。 在为模块编写任何一行 HCL 代码之前,请获取并阅读该模块的完整 README。不要依赖对原始 azurerm 提供程序的了解或之前使用其他 AVM 模块的经验。
对于每个选定的 AVM 模块,获取其 README:
https://raw.githubusercontent.com/Azure/terraform-azurerm-avm-res-<service>-<resource>/refs/heads/main/README.md
或者,如果在 terraform init 后模块已下载:
cat .terraform/modules/<module_key>/README.md
从 README 中,在编写代码之前 提取并记录:
type。不要假设它们与原始 azurerm 提供程序的参数名称或块结构匹配。parent_id 与 resource_group_name),子资源如何表达(内联映射与独立模块),以及每个输入期望的语法。使用以下经验教训作为类型不匹配的示例,这种不匹配常常导致导入失败。不要假设这些确切的名称适用于每个 AVM 模块。始终验证每个选定模块的 README 和 variables.tf。
avm-res-compute-virtualmachine(任何版本)
network_interfaces 是一个必需输入。NIC 由 VM 模块拥有。切勿在 VM 模块旁边创建独立的 avm-res-network-networkinterface 模块 — 在 network_interfaces 下内联定义每个 NIC。secure_boot_enabled = true 和 vtpm_enabled = true 来表达。security_type 参数仅存在于 os_disk 下,用于机密 VM 磁盘加密,不得用于 TrustedLaunch。boot_diagnostics 是一个 bool,而不是对象。使用 boot_diagnostics = true;如果需要存储 URI,请使用单独的 boot_diagnostics_storage_account_uri 变量。extensions 映射在模块内部管理。不要创建独立的扩展资源。avm-res-network-virtualnetwork(任何版本)
azurerm。使用 parent_id(完整的资源组资源 ID 字符串)来指定资源组,而不是 resource_group_name。parent_id;没有显示 resource_group_name 的。所有 AVM 模块的通用要点:
variables.tf 确定接受的变量名称和类型。azurerm_* 资源推断参数名称。在 terraform init 下载模块后,检查每个模块的源文件,以确定确切的 Terraform 资源地址,然后再编写任何 import {} 块。切勿凭记忆编写导入地址。
grep "^resource" .terraform/modules/<module_key>/main*.tf
这将揭示模块使用的是 azurerm_* 还是 azapi_resource 标签。例如,avm-res-network-virtualnetwork 暴露的是 azapi_resource "vnet",而不是 azurerm_virtual_network "this"。
grep "^module" .terraform/modules/<module_key>/main*.tf
如果子资源在子模块中管理(子网、扩展等),则导入地址必须包含每个中间模块标签:
module.<root_module_key>.module.<child_module_key>["<map_key>"].<resource_type>.<label>[<index>]
count 与 for_eachgrep -n "count\|for_each" .terraform/modules/<module_key>/main*.tf
任何使用 count 的资源都需要在导入地址中包含索引。当 count = 1 时(例如,有条件的 Linux 与 Windows 选择),地址必须以 [0] 结尾。使用 for_each 的资源使用字符串键,而不是数字索引。
这些只是示例。将它们用作推理的模板,然后从当前导入的模块的已下载源代码中推导出确切的地址。
| 资源 | 正确的导入 to 地址模式 |
|---|---|
| AzAPI 支持的 VNet | module.<vnet_key>.azapi_resource.vnet |
| 子网(嵌套,基于 count) | module.<vnet_key>.module.subnet["<subnet_name>"].azapi_resource.subnet[0] |
| Linux VM(基于 count) | module.<vm_key>.azurerm_linux_virtual_machine.this[0] |
| VM NIC | module.<vm_key>.azurerm_network_interface.virtualmachine_network_interfaces["<nic_key>"] |
| VM 扩展(默认 deploy_sequence=5) | module.<vm_key>.module.extension["<ext_name>"].azurerm_virtual_machine_extension.this |
| VM 扩展(deploy_sequence=1–4) | module.<vm_key>.module.extension_<n>["<ext_name>"].azurerm_virtual_machine_extension.this |
| NSG-NIC 关联 | module.<vm_key>.azurerm_network_interface_security_group_association.this["<nic_key>-<nsg_key>"] |
生成:
providers.tf:包含 azurerm 提供程序和所需的版本约束main.tf:包含 AVM 模块块和显式依赖关系variables.tf:用于环境特定值outputs.tf:用于关键 ID 和端点terraform.tfvars.example:包含占位符值编写初始配置后,将每个已发现的实时资源的每个非零属性与相应 AVM 模块的 variables.tf 中声明的默认值进行比较。任何实时值与模块默认值不同的属性都必须在 Terraform 配置中显式设置。
特别注意以下属性类别,它们是导致静默配置漂移的常见来源:
idle_timeout_in_minutes 默认为 4;实时部署通常使用 30)private_endpoint_network_policies 默认为 "Enabled";现有子网通常为 "Disabled")sku、allocation_method)使用显式的 az 命令检索完整的实时属性,例如:
az network public-ip show --ids <resource_id> --query "{idleTimeout:idleTimeoutInMinutes, sku:sku.name, zones:zones}" -o json
az network vnet subnet show --ids <resource_id> --query "{privateEndpointPolicies:privateEndpointNetworkPolicies, delegation:delegations}" -o json
不要仅依赖 az resource list 的输出,它可能省略嵌套或计算属性。
显式固定模块版本:
module "example" {
source = "Azure/<module>/azurerm"
version = "<latest-compatible-version>"
}
运行:
terraform init
terraform fmt -recursive
terraform validate
terraform plan
预期输出:无语法错误,无验证错误,并且计划与已发现的基础设施意图匹配。
| 问题 | 可能原因 | 操作 |
|---|---|---|
az 命令因授权错误失败 | 错误的租户/订阅或缺少 RBAC 角色 | 重新运行 az login,验证订阅上下文,确认所需权限 |
| 发现输出为空 | 范围不正确或范围内无资源 | 重新检查范围输入并再次运行范围列表/显示命令 |
| 未找到资源类型的 AVM 模块 | 资源类型尚未被 AVM 覆盖 | 对该类型使用原生的 azurerm_* 资源并记录差距 |
terraform validate 失败 | 缺少变量或未解决的依赖关系 | 添加必需的变量和显式依赖关系,然后重新运行验证 |
| 未知参数或模块中未找到变量 | AVM 变量名称与 azurerm 提供程序参数名称不同 | 阅读模块 README 的 variables.tf 或可选输入部分以获取正确的名称 |
| 导入块失败 — 在地址处未找到资源 | 错误的提供程序标签(azurerm_ 与 azapi_)、缺少子模块路径或缺少 [0] 索引 | 运行 grep "^resource" .terraform/modules/<key>/main*.tf 和 grep "^module" 以查找确切地址 |
terraform plan 显示导入的资源出现意外的 ~ update | 实时值与 AVM 模块默认值不同 | 使用 az <resource> show 获取实时属性,与模块默认值比较,添加显式值 |
| 子资源模块给出“提供程序配置不存在” | 子资源被声明为独立模块,即使父模块拥有它们 | 检查 README 中的必需输入,删除不正确的独立模块,并使用父模块文档化的输入结构对子资源进行建模 |
| 嵌套子资源导入失败,提示“未找到资源” | 缺少中间模块路径、错误的映射键或缺少索引 | 检查源中的模块块和 count/for_each;构建完整的嵌套导入地址,包括所有模块段以及所需的键/索引 |
| 工具尝试将 ARM 资源 ID 读取为文件路径或重复询问范围问题 | 资源 ID 未被当作 --ids 输入处理,或代理不信任已提供的范围 | 将 ARM ID 严格视为云标识符,使用 az ... --ids ...,并且在存在一个有效范围后停止重新提示 |
返回结果时,请提供:
parent_id 与 resource_group_name)。跳过 README 是基于 AVM 的导入中代码错误的唯一最常见原因。terraform init 之后,grep 已下载的模块源,以发现实际的提供程序(azurerm 与 azapi)、资源标签、子模块嵌套以及 count 与 for_each 的使用情况,然后再编写任何 import {} 块。--ids 参数和 API 查询,而不是文件 IO 工具。仅当提供了真实的工作空间路径时才读取本地文件。terraform plan 显示 0 个销毁和 0 个不需要的更改之前,不要声明导入完成。 遥测 + create 资源是可以接受的。任何对真实基础设施资源的 ~ update 或 - destroy 都必须解决。每周安装次数
5.4K
仓库
GitHub 星标数
26.7K
首次出现
2026年3月2日
安全审计
安装于
codex5.4K
gemini-cli5.4K
opencode5.3K
cursor5.3K
github-copilot5.3K
kimi-cli5.3K
Convert existing Azure infrastructure into maintainable Terraform code using discovery data and Azure Verified Modules.
Use this skill when the user asks to:
azurerm_* resourcesaz login)| Parameter | Required | Default | Description |
|---|---|---|---|
subscription-id | No | Active CLI context | Azure subscription used for subscription-scope discovery and context setting |
resource-group-name | No | None | Azure resource group used for resource-group-scope discovery |
resource-id | No | None | One or more Azure ARM resource IDs used for specific-resource-scope discovery |
At least one of subscription-id, resource-group-name, or resource-id is required.
Request one of these scopes before running discovery commands:
<subscription-id><resource-group-name><resource-id> valuesScope handling rules:
/subscriptions/.../providers/...) as cloud resource identifiers, not local file system paths.--ids arguments (for example az resource show --ids <resource-id>).cat, ls, read_file, glob searches) unless the user explicitly says they are local file paths.If scope is missing, ask for it explicitly and stop.
Run only the commands required for the selected scope.
For subscription scope:
az login
az account set --subscription <subscription-id>
az account show --query "{subscriptionId:id, name:name, tenantId:tenantId}" -o json
Expected output: JSON object with subscriptionId, name, and tenantId.
For resource group or specific resource scope, az login is still required but az account set is optional if the active context is already correct.
When using specific resource scope, prefer direct --ids-based commands first and avoid extra discovery prompts for subscription or resource group unless needed for a concrete command.
Discover resources using the selected scopes. Ensure to fetch all necessary information for accurate Terraform generation.
# Subscription scope
az resource list --subscription <subscription-id> -o json
# Resource group scope
az resource list --resource-group <resource-group-name> -o json
# Specific resource scope
az resource show --ids <resource-id-1> <resource-id-2> ... -o json
Expected output: JSON object or array containing Azure resource metadata (id, type, name, location, tags, properties).
Parse exported JSON and map:
propertiesIMPORTANT: Generate the following documentation and save it to a docs folder in the root of the project.
exported-resources.json with all discovered resources and their metadata, including dependencies and references.EXPORTED-ARCHITECTURE.MD file with a human-readable architecture overview based on the discovered resources and their relationships.Use the latest AVM version for each resource type.
Note: The following links always point to the latest version of the CSV files on the main branch. As intended, this means the files may change over time. If you require a point-in-time version, consider using a specific release tag in the URL.
https://raw.githubusercontent.com/Azure/Azure-Verified-Modules/refs/heads/main/docs/static/module-indexes/TerraformResourceModules.csvhttps://raw.githubusercontent.com/Azure/Azure-Verified-Modules/refs/heads/main/docs/static/module-indexes/TerraformPatternModules.csvhttps://raw.githubusercontent.com/Azure/Azure-Verified-Modules/refs/heads/main/docs/static/module-indexes/TerraformUtilityModules.csvUse the web tool or another suitable MCP method to get module information if not available locally in the .terraform folder.
Use AVM sources:
https://registry.terraform.io/modules/Azure/<module>/azurerm/latesthttps://github.com/Azure/terraform-azurerm-avm-res-<service>-<resource>Prefer AVM modules over handwritten azurerm_* resources when an AVM module exists.
When fetching module information from GitHub repositories, the README.md file in the root of the repository typically contains all detailed information about the module, for example: https://raw.githubusercontent.com/Azure/terraform-azurerm-avm-res--/refs/heads/main/README.md
This step is not optional. Before writing a single line of HCL for a module, fetch and read the full README for that module. Do not rely on knowledge of the raw azurerm provider or prior experience with other AVM modules.
For each selected AVM module, fetch its README:
https://raw.githubusercontent.com/Azure/terraform-azurerm-avm-res-<service>-<resource>/refs/heads/main/README.md
Or if the module is already downloaded after terraform init:
cat .terraform/modules/<module_key>/README.md
From the README, extract and record before writing code :
type. Do not assume they match the raw azurerm provider argument names or block shapes.parent_id vs resource_group_name), how child resources are expressed (inline map vs separate module), and what syntax each input expects.Use the lessons below as examples of the type of mismatch that often causes imports to fail. Do not assume these exact names apply to every AVM module. Always verify each selected module's README and variables.tf.
avm-res-compute-virtualmachine (any version)
network_interfaces is a Required Input. NICs are owned by the VM module. Never create standalone avm-res-network-networkinterface modules alongside a VM module — define every NIC inline under network_interfaces.secure_boot_enabled = true and vtpm_enabled = true. The security_type argument exists only under os_disk for Confidential VM disk encryption and must not be used for TrustedLaunch.boot_diagnostics is a bool, not an object. Use boot_diagnostics = true; use the separate variable if a storage URI is needed.avm-res-network-virtualnetwork (any version)
azurerm. Use parent_id (the full resource group resource ID string) to specify the resource group, not resource_group_name.parent_id; none show resource_group_name.Generalized takeaway for all AVM modules:
variables.tf.azurerm_* resources.After terraform init downloads the modules, inspect each module's source files to determine the exact Terraform resource addresses before writing any import {} blocks. Never write import addresses from memory.
grep "^resource" .terraform/modules/<module_key>/main*.tf
This reveals whether the module uses azurerm_* or azapi_resource labels. For example, avm-res-network-virtualnetwork exposes azapi_resource "vnet", not azurerm_virtual_network "this".
grep "^module" .terraform/modules/<module_key>/main*.tf
If child resources are managed in a sub-module (subnets, extensions, etc.), the import address must include every intermediate module label:
module.<root_module_key>.module.<child_module_key>["<map_key>"].<resource_type>.<label>[<index>]
count vs for_eachgrep -n "count\|for_each" .terraform/modules/<module_key>/main*.tf
Any resource using count requires an index in the import address. When count = 1 (e.g., conditional Linux vs Windows selection), the address must end with [0]. Resources using for_each use string keys, not numeric indexes.
These are examples only. Use them as templates for reasoning, then derive the exact addresses from the downloaded source code for the modules in your current import.
| Resource | Correct import to address pattern |
|---|---|
| AzAPI-backed VNet | module.<vnet_key>.azapi_resource.vnet |
| Subnet (nested, count-based) | module.<vnet_key>.module.subnet["<subnet_name>"].azapi_resource.subnet[0] |
| Linux VM (count-based) | module.<vm_key>.azurerm_linux_virtual_machine.this[0] |
| VM NIC | module.<vm_key>.azurerm_network_interface.virtualmachine_network_interfaces["<nic_key>"] |
| VM extension (default deploy_sequence=5) | module.<vm_key>.module.extension["<ext_name>"].azurerm_virtual_machine_extension.this |
Produce:
providers.tf with azurerm provider and required version constraintsmain.tf with AVM module blocks and explicit dependenciesvariables.tf for environment-specific valuesoutputs.tf for key IDs and endpointsterraform.tfvars.example with placeholder valuesAfter writing the initial configuration, compare every non-zero property of each discovered live resource against the default value declared in the corresponding AVM module's variables.tf. Any property where the live value differs from the module default must be set explicitly in the Terraform configuration.
Pay particular attention to the following property categories, which are common sources of silent configuration drift:
idle_timeout_in_minutes defaults to 4; live deployments often use 30)private_endpoint_network_policies defaults to "Enabled"; existing subnets often have "Disabled")sku, allocation_method)Retrieve full live properties with explicit az commands, for example:
az network public-ip show --ids <resource_id> --query "{idleTimeout:idleTimeoutInMinutes, sku:sku.name, zones:zones}" -o json
az network vnet subnet show --ids <resource_id> --query "{privateEndpointPolicies:privateEndpointNetworkPolicies, delegation:delegations}" -o json
Do not rely solely on az resource list output, which may omit nested or computed properties.
Pin module versions explicitly:
module "example" {
source = "Azure/<module>/azurerm"
version = "<latest-compatible-version>"
}
Run:
terraform init
terraform fmt -recursive
terraform validate
terraform plan
Expected output: no syntax errors, no validation errors, and a plan that matches discovered infrastructure intent.
| Problem | Likely Cause | Action |
|---|---|---|
az command fails with authorization errors | Wrong tenant/subscription or missing RBAC role | Re-run az login, verify subscription context, confirm required permissions |
| Discovery output is empty | Incorrect scope or no resources in scope | Re-check scope input and run scoped list/show command again |
| No AVM module found for a resource type | Resource type not yet covered by AVM | Use native azurerm_* resource for that type and document the gap |
terraform validate fails | Missing variables or unresolved dependencies | Add required variables and explicit dependencies, then re-run validation |
| Unknown argument or variable not found in module |
When returning results, provide:
parent_id vs resource_group_name). Skipping the README is the single most common cause of code errors in AVM-based imports.terraform init, grep the downloaded module source to discover the actual provider (azurerm vs azapi), resource labels, sub-module nesting, and count vs for_each usage before writing any block.Weekly Installs
5.4K
Repository
GitHub Stars
26.7K
First Seen
Mar 2, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
codex5.4K
gemini-cli5.4K
opencode5.3K
cursor5.3K
github-copilot5.3K
kimi-cli5.3K
97,600 周安装
boot_diagnostics_storage_account_uriextensions map. Do not create standalone extension resources.| VM extension (deploy_sequence=1–4) | module.<vm_key>.module.extension_<n>["<ext_name>"].azurerm_virtual_machine_extension.this |
| NSG-NIC association | module.<vm_key>.azurerm_network_interface_security_group_association.this["<nic_key>-<nsg_key>"] |
AVM variable name differs from azurerm provider argument name |
Read the module README variables.tf or Optional Inputs section for the correct name |
| Import block fails — resource not found at address | Wrong provider label (azurerm_ vs azapi_), missing sub-module path, or missing [0] index | Run grep "^resource" .terraform/modules/<key>/main*.tf and grep "^module" to find exact address |
terraform plan shows unexpected ~ update on imported resource | Live value differs from AVM module default | Fetch live property with az <resource> show, compare to module default, add explicit value |
| Child-resource module gives "provider configuration not present" | Child resources declared as standalone modules even though parent module owns them | Check Required Inputs in README, remove incorrect standalone modules, and model child resources using the parent module's documented input structure |
| Nested child resource import fails with "resource not found" | Missing intermediate module path, wrong map key, or missing index | Inspect module blocks and count/for_each in source; build full nested import address including all module segments and required key/index |
| Tool tries to read ARM resource ID as file path or asks repeated scope questions | Resource ID not treated as --ids input, or agent did not trust already-provided scope | Treat ARM IDs strictly as cloud identifiers, use az ... --ids ..., and stop re-prompting once one valid scope is present |
import {}--ids arguments and API queries, not file IO tools. Only read local files when a real workspace path is provided.terraform plan shows 0 destroys and 0 unwanted changes. Telemetry + create resources are acceptable. Any ~ update or - destroy on real infrastructure resources must be resolved.