AgentSkillsCN

azure-workload-generator

利用 Bicep 和 Terraform 构建生产就绪的 Azure 辐条工作负载基础设施,包含虚拟网络、子网、虚拟机,以及可选的数据磁盘。在创建 Azure 工作负载、辐条网络、虚拟机,或当用户提及工作负载部署、IaC 生成,或辐条基础设施时,此技能将助您事半功倍。遵循 naming-convention.md 和 ip-addressing-scheme.md 标准。

SKILL.md
--- frontmatter
name: azure-workload-generator
description: Generate production-ready Azure spoke workload infrastructure with VNet, subnet, VM, and optional data disk using Bicep and Terraform. Use when creating Azure workloads, spoke networks, virtual machines, or when user mentions workload deployment, IaC generation, or spoke infrastructure. Follows naming-convention.md and ip-addressing-scheme.md standards.
allowed-tools: Read, Write, Edit, Glob, Grep, AskUserQuestion

Azure Workload Generator

Generate complete Azure spoke workload infrastructure in both Bicep and Terraform, following enterprise naming conventions and IP addressing standards.

Overview

This skill creates production-ready infrastructure code for Azure spoke workloads including:

  • Virtual Network (VNet) with custom address space
  • Subnet with configurable size
  • Virtual Machine (Windows or Linux)
  • Optional data disk
  • Proper resource naming and tagging

Output: Both Bicep and Terraform code with proper directory structure under azure-workloads/Customer/<customerCode>/


⛔ MANDATORY REQUIREMENTS GATE - DO NOT SKIP

CRITICAL: You MUST gather ALL required inputs from the user BEFORE generating ANY code. NEVER assume, guess, or use placeholder values for required inputs. ALWAYS use the AskUserQuestion tool to collect missing information.

Why This Matters

The naming convention (docs/standards/naming-convention.md) requires specific inputs to generate valid resource names:

InputUsed In Naming PatternExample
Customer Code{customerCode}- prefix on ALL resourcescc-vnet-...
Workload Name{workload} componentcc-vnet-webapp-...
Environment{environment} code (d/t/a/p)cc-vnet-webapp-p-...
Region{region} code (default: weu)cc-vnet-webapp-p-weu-01

Without these inputs, you CANNOT generate compliant resource names.

Minimum Required Inputs (MUST HAVE)

Before proceeding to code generation, you MUST have explicit user confirmation for:

  1. Customer Code - 2-4 character lowercase identifier
  2. Workload Name - Purpose/name of this workload
  3. Environment Code - d (dev), t (test), a (acceptance), p (production)
  4. VM Operating System - Windows or Linux
  5. Resources Needed - What resources to create (VNet, VM, disks, etc.)

Recommended Additional Inputs

These have sensible defaults but should be confirmed:

  1. 📋 VNet Address Space (default: from ip-addressing-scheme.md)
  2. 📋 Subnet Name and Size
  3. 📋 VM SKU (default: Standard_D2s_v3)
  4. 📋 Data Disk requirements (yes/no, size)
  5. 📋 Tag values (CostCenter, Owner, Criticality)

STOP AND ASK

If the user's request is missing ANY of the 5 required inputs above:

code
STOP → Use AskUserQuestion → Gather missing inputs → THEN proceed

Example: If user says "create an exact-financials workload", you are MISSING:

  • Customer code ❌
  • Environment ❌
  • OS type ❌
  • Resources needed ❌

You MUST ask for these before generating any code.


Instructions

Phase 1: Gather Required Information (MANDATORY - NEVER SKIP)

Use the AskUserQuestion tool to collect the following information from the user:

  1. Customer Code (e.g., "contoso", "fabrikam")

    • Used for directory structure and resource naming
    • Should be lowercase, alphanumeric
  2. Workload Name (e.g., "webapp", "database", "api")

    • Describes the purpose of this workload
    • Used in resource naming
  3. Environment Code

    • d = Development
    • t = Test
    • a = Acceptance
    • p = Production
  4. VNet Address Space (e.g., "10.10.0.0/16", "172.16.0.0/16")

    • CIDR notation for the virtual network
    • Should align with IP addressing scheme (but not validated)
  5. Subnet Name (e.g., "app", "data", "web")

    • Logical name for the subnet
  6. Subnet Size (e.g., "/24", "/26")

    • CIDR suffix only (e.g., "/24" for 256 addresses)
  7. VM Operating System

    • Windows or Linux
  8. VM SKU (e.g., "Standard_D2s_v3", "Standard_B2ms")

    • Azure VM size
  9. Data Disk Required

    • yes or no
  10. Data Disk Size in GB (if data disk required)

    • Size in GB (e.g., 128, 256, 512)
  11. Instance Number (default: "01")

    • Two-digit instance identifier per naming-convention.md
    • Default to "01" for first instance
  12. Tag Values (for mandatory tags)

    • CostCenter: Department or cost allocation code
    • Owner: Team or individual responsible
    • Criticality: Low, Medium, High, Critical

Phase 2: Read Standards Documentation

Before generating code, read the following standards:

  1. Read docs/standards/naming-convention.md to understand:

    • Resource naming patterns
    • Character limits
    • Tagging requirements
    • Environment codes
    • Region codes (default: weu for West Europe)
  2. Reference docs/standards/ip-addressing-scheme.md for context:

    • Hub-spoke topology patterns
    • Subscription-based VNet allocation
    • Platform vs workload IP ranges

Phase 3: Generate Resource Names

Based on the naming convention standard, generate resource names following these patterns:

Resource Naming Format: <resourceType>-<workload>-<environment>-<region>-<instance>

  • VNet: vnet-<workload>-<environment>-weu-<instance>

    • Example: vnet-webapp-p-weu-01
  • Subnet: snet-<subnetName>-<environment>-weu-<instance>

    • Example: snet-app-p-weu-01
  • VM: vm<workload><environment>weu<instance> (no hyphens, max 15 chars for Windows)

    • Example: vmwebapppweu01
    • For Linux: vm-<workload>-<environment>-weu-<instance> (up to 64 chars)
  • NIC: nic-<workload>-<environment>-weu-<instance>

    • Example: nic-webapp-p-weu-01
  • Data Disk (if required): disk-<workload>-data-<environment>-weu-<instance>

    • Example: disk-webapp-data-p-weu-01
  • Public IP (if required): pip-<workload>-<environment>-weu-<instance>

    • Example: pip-webapp-p-weu-01
  • NSG: nsg-<workload>-<environment>-weu-<instance>

    • Example: nsg-webapp-p-weu-01

Validate Name Lengths:

  • Windows VM names: Max 15 characters (remove hyphens if needed)
  • Linux VM names: Max 64 characters
  • Other resources: Check naming-convention.md for specific limits

Phase 4: Prepare Mandatory Tags

Create a tags object with all mandatory tags:

json
{
  "Environment": "<d|t|a|p - full name>",
  "CostCenter": "<from user input or placeholder>",
  "Owner": "<from user input or placeholder>",
  "ManagedBy": "Terraform" or "Bicep",
  "Workload": "<workload name>",
  "Criticality": "<from user input or placeholder>"
}

Tag Values:

  • Environment: "Development", "Test", "Acceptance", "Production"
  • ManagedBy: "Bicep" or "Terraform" depending on file
  • Use placeholders like "TO_BE_DEFINED" if user doesn't provide values

Phase 5: Create Directory Structure

Create the following directory structure under azure-workloads/:

code
azure-workloads/
├── Customer/
│   ├── bicep-modules/        (for reusable modules)
│   ├── terraform-modules/    (for reusable modules)
│   └── <customerCode>/       (e.g., contoso/)
│       ├── bicep/
│       │   ├── <workloadname>.bicep
│       │   └── <workloadname>.bicepparam
│       └── terraform/
│           ├── <workloadname>.tf
│           ├── variables.tf
│           └── terraform.tfvars

Use the Write tool to create directories and files.

Phase 6: Generate Bicep Code

Create two files in azure-workloads/Customer/<customerCode>/bicep/:

File 1: <workloadname>.bicep

Structure:

bicep
// Azure Spoke Workload: <workload>
// Generated by azure-workload-generator
// Customer: <customerCode>
// Environment: <environment>

targetScope = 'resourceGroup'

// ============================================================================
// PARAMETERS
// ============================================================================

@description('Location for all resources')
param location string = 'westeurope'

@description('VNet address space')
param vnetAddressSpace string

@description('Subnet address prefix')
param subnetAddressPrefix string

@description('Virtual Machine SKU')
param vmSize string

@description('Admin username for the VM')
@secure()
param adminUsername string

@description('Admin password for the VM')
@secure()
param adminPassword string

@description('Data disk size in GB (0 for no data disk)')
param dataDiskSizeGB int

@description('Resource tags')
param tags object

// ============================================================================
// VARIABLES
// ============================================================================

var vnetName = '<generated-vnet-name>'
var subnetName = '<generated-subnet-name>'
var vmName = '<generated-vm-name>'
var nicName = '<generated-nic-name>'
var nsgName = '<generated-nsg-name>'
var osDiskName = '${vmName}-osdisk'
var dataDiskName = '<generated-datadisk-name>'

// ============================================================================
// RESOURCES
// ============================================================================

// Network Security Group
resource nsg 'Microsoft.Network/networkSecurityGroups@2023-05-01' = {
  name: nsgName
  location: location
  tags: tags
  properties: {
    securityRules: [
      // Add security rules based on OS type
    ]
  }
}

// Virtual Network
resource vnet 'Microsoft.Network/virtualNetworks@2023-05-01' = {
  name: vnetName
  location: location
  tags: tags
  properties: {
    addressSpace: {
      addressPrefixes: [
        vnetAddressSpace
      ]
    }
    subnets: [
      {
        name: subnetName
        properties: {
          addressPrefix: subnetAddressPrefix
          networkSecurityGroup: {
            id: nsg.id
          }
        }
      }
    ]
  }
}

// Network Interface
resource nic 'Microsoft.Network/networkInterfaces@2023-05-01' = {
  name: nicName
  location: location
  tags: tags
  properties: {
    ipConfigurations: [
      {
        name: 'ipconfig1'
        properties: {
          subnet: {
            id: vnet.properties.subnets[0].id
          }
          privateIPAllocationMethod: 'Dynamic'
        }
      }
    ]
  }
}

// Virtual Machine
resource vm 'Microsoft.Compute/virtualMachines@2023-03-01' = {
  name: vmName
  location: location
  tags: tags
  properties: {
    hardwareProfile: {
      vmSize: vmSize
    }
    osProfile: {
      computerName: vmName
      adminUsername: adminUsername
      adminPassword: adminPassword
    }
    storageProfile: {
      imageReference: {
        // Set based on OS type (Windows or Linux)
        publisher: '<publisher>'
        offer: '<offer>'
        sku: '<sku>'
        version: 'latest'
      }
      osDisk: {
        name: osDiskName
        createOption: 'FromImage'
        managedDisk: {
          storageAccountType: 'Premium_LRS'
        }
      }
      dataDisks: dataDiskSizeGB > 0 ? [
        {
          name: dataDiskName
          diskSizeGB: dataDiskSizeGB
          lun: 0
          createOption: 'Empty'
          managedDisk: {
            storageAccountType: 'Premium_LRS'
          }
        }
      ] : []
    }
    networkProfile: {
      networkInterfaces: [
        {
          id: nic.id
        }
      ]
    }
  }
}

// ============================================================================
// OUTPUTS
// ============================================================================

output vnetId string = vnet.id
output vnetName string = vnet.name
output subnetId string = vnet.properties.subnets[0].id
output vmId string = vm.id
output vmName string = vm.name
output privateIPAddress string = nic.properties.ipConfigurations[0].properties.privateIPAddress

Key Considerations:

  • Reference shared Bicep modules from bicep-modules/ directory if they exist
  • Use conditional logic for data disk (only create if dataDiskSizeGB > 0)
  • Set correct image reference based on OS type:
    • Windows: publisher='MicrosoftWindowsServer', offer='WindowsServer', sku='2022-datacenter-azure-edition'
    • Linux: publisher='Canonical', offer='0001-com-ubuntu-server-jammy', sku='22_04-lts-gen2'
  • Include comprehensive comments
  • Use latest API versions

File 2: <workloadname>.bicepparam

Structure:

bicep
using './<workloadname>.bicep'

param location = 'westeurope'
param vnetAddressSpace = '<user-provided-vnet-address>'
param subnetAddressPrefix = '<calculated-subnet-cidr>'
param vmSize = '<user-provided-vm-sku>'
param adminUsername = '<placeholder-or-keyvault-reference>'
param adminPassword = '<placeholder-or-keyvault-reference>'
param dataDiskSizeGB = <user-provided-size-or-0>
param tags = {
  Environment: '<Development|Test|Acceptance|Production>'
  CostCenter: 'TO_BE_DEFINED'
  Owner: 'TO_BE_DEFINED'
  ManagedBy: 'Bicep'
  Workload: '<workload-name>'
  Criticality: 'TO_BE_DEFINED'
}

Subnet Calculation:

  • Calculate subnet CIDR from VNet address space and subnet size
  • Example: VNet "10.10.0.0/16" + subnet size "/24" = "10.10.0.0/24"

Phase 7: Generate Terraform Code

Create three files in azure-workloads/Customer/<customerCode>/terraform/:

File 1: <workloadname>.tf

Structure:

hcl
# Azure Spoke Workload: <workload>
# Generated by azure-workload-generator
# Customer: <customerCode>
# Environment: <environment>

terraform {
  required_version = ">= 1.5.0"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

provider "azurerm" {
  features {}
}

# ============================================================================
# LOCAL VARIABLES
# ============================================================================

locals {
  vnet_name      = "<generated-vnet-name>"
  subnet_name    = "<generated-subnet-name>"
  vm_name        = "<generated-vm-name>"
  nic_name       = "<generated-nic-name>"
  nsg_name       = "<generated-nsg-name>"
  os_disk_name   = "${local.vm_name}-osdisk"
  data_disk_name = "<generated-datadisk-name>"

  common_tags = {
    Environment  = var.environment
    CostCenter   = var.cost_center
    Owner        = var.owner
    ManagedBy    = "Terraform"
    Workload     = var.workload_name
    Criticality  = var.criticality
  }
}

# ============================================================================
# RESOURCE GROUP
# ============================================================================

# Assumes resource group already exists
data "azurerm_resource_group" "main" {
  name = var.resource_group_name
}

# ============================================================================
# NETWORK SECURITY GROUP
# ============================================================================

resource "azurerm_network_security_group" "main" {
  name                = local.nsg_name
  location            = data.azurerm_resource_group.main.location
  resource_group_name = data.azurerm_resource_group.main.name
  tags                = local.common_tags

  # Add security rules based on OS type
}

# ============================================================================
# VIRTUAL NETWORK
# ============================================================================

resource "azurerm_virtual_network" "main" {
  name                = local.vnet_name
  location            = data.azurerm_resource_group.main.location
  resource_group_name = data.azurerm_resource_group.main.name
  address_space       = [var.vnet_address_space]
  tags                = local.common_tags
}

resource "azurerm_subnet" "main" {
  name                 = local.subnet_name
  resource_group_name  = data.azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = [var.subnet_address_prefix]
}

resource "azurerm_subnet_network_security_group_association" "main" {
  subnet_id                 = azurerm_subnet.main.id
  network_security_group_id = azurerm_network_security_group.main.id
}

# ============================================================================
# NETWORK INTERFACE
# ============================================================================

resource "azurerm_network_interface" "main" {
  name                = local.nic_name
  location            = data.azurerm_resource_group.main.location
  resource_group_name = data.azurerm_resource_group.main.name
  tags                = local.common_tags

  ip_configuration {
    name                          = "ipconfig1"
    subnet_id                     = azurerm_subnet.main.id
    private_ip_address_allocation = "Dynamic"
  }
}

# ============================================================================
# VIRTUAL MACHINE
# ============================================================================

resource "azurerm_<windows|linux>_virtual_machine" "main" {
  name                = local.vm_name
  location            = data.azurerm_resource_group.main.location
  resource_group_name = data.azurerm_resource_group.main.name
  size                = var.vm_size
  admin_username      = var.admin_username
  admin_password      = var.admin_password
  tags                = local.common_tags

  network_interface_ids = [
    azurerm_network_interface.main.id
  ]

  os_disk {
    name                 = local.os_disk_name
    caching              = "ReadWrite"
    storage_account_type = "Premium_LRS"
  }

  # Set source_image_reference based on OS type
  source_image_reference {
    publisher = "<publisher>"
    offer     = "<offer>"
    sku       = "<sku>"
    version   = "latest"
  }
}

# ============================================================================
# DATA DISK (CONDITIONAL)
# ============================================================================

resource "azurerm_managed_disk" "data" {
  count                = var.data_disk_size_gb > 0 ? 1 : 0
  name                 = local.data_disk_name
  location             = data.azurerm_resource_group.main.location
  resource_group_name  = data.azurerm_resource_group.main.name
  storage_account_type = "Premium_LRS"
  create_option        = "Empty"
  disk_size_gb         = var.data_disk_size_gb
  tags                 = local.common_tags
}

resource "azurerm_virtual_machine_data_disk_attachment" "data" {
  count              = var.data_disk_size_gb > 0 ? 1 : 0
  managed_disk_id    = azurerm_managed_disk.data[0].id
  virtual_machine_id = azurerm_<windows|linux>_virtual_machine.main.id
  lun                = 0
  caching            = "ReadWrite"
}

# ============================================================================
# OUTPUTS
# ============================================================================

output "vnet_id" {
  description = "The ID of the Virtual Network"
  value       = azurerm_virtual_network.main.id
}

output "vnet_name" {
  description = "The name of the Virtual Network"
  value       = azurerm_virtual_network.main.name
}

output "subnet_id" {
  description = "The ID of the Subnet"
  value       = azurerm_subnet.main.id
}

output "vm_id" {
  description = "The ID of the Virtual Machine"
  value       = azurerm_<windows|linux>_virtual_machine.main.id
}

output "vm_name" {
  description = "The name of the Virtual Machine"
  value       = azurerm_<windows|linux>_virtual_machine.main.name
}

output "private_ip_address" {
  description = "The private IP address of the VM"
  value       = azurerm_network_interface.main.private_ip_address
}

Key Considerations:

  • Use azurerm_windows_virtual_machine for Windows, azurerm_linux_virtual_machine for Linux
  • Reference shared Terraform modules from terraform-modules/ directory if they exist
  • Use count for conditional data disk creation
  • Set correct image reference based on OS type:
    • Windows: publisher='MicrosoftWindowsServer', offer='WindowsServer', sku='2022-datacenter-azure-edition'
    • Linux: publisher='Canonical', offer='0001-com-ubuntu-server-jammy', sku='22_04-lts-gen2'
  • For Windows VMs, add disable_password_authentication = false is not applicable
  • For Linux VMs, consider SSH key authentication instead of password

File 2: variables.tf

Structure:

hcl
# Variable definitions for <workload> workload

variable "resource_group_name" {
  description = "Name of the resource group"
  type        = string
}

variable "vnet_address_space" {
  description = "Address space for the virtual network"
  type        = string
}

variable "subnet_address_prefix" {
  description = "Address prefix for the subnet"
  type        = string
}

variable "vm_size" {
  description = "Size of the virtual machine"
  type        = string
}

variable "admin_username" {
  description = "Admin username for the VM"
  type        = string
  sensitive   = true
}

variable "admin_password" {
  description = "Admin password for the VM"
  type        = string
  sensitive   = true
}

variable "data_disk_size_gb" {
  description = "Size of the data disk in GB (0 for no data disk)"
  type        = number
  default     = 0
}

variable "environment" {
  description = "Environment name (Development, Test, Acceptance, Production)"
  type        = string
}

variable "cost_center" {
  description = "Cost center tag value"
  type        = string
  default     = "TO_BE_DEFINED"
}

variable "owner" {
  description = "Owner tag value"
  type        = string
  default     = "TO_BE_DEFINED"
}

variable "workload_name" {
  description = "Workload name for tagging"
  type        = string
}

variable "criticality" {
  description = "Criticality level (Low, Medium, High, Critical)"
  type        = string
  default     = "TO_BE_DEFINED"
}

File 3: terraform.tfvars

Structure:

hcl
# Terraform variables for <workload> workload
# Customer: <customerCode>
# Environment: <environment>

resource_group_name    = "rg-<workload>-<environment>-weu-01"
vnet_address_space     = "<user-provided-vnet-address>"
subnet_address_prefix  = "<calculated-subnet-cidr>"
vm_size                = "<user-provided-vm-sku>"
admin_username         = "azureadmin"  # Change as needed
admin_password         = "CHANGE_ME_SECURE_PASSWORD"  # Use Key Vault or environment variable
data_disk_size_gb      = <user-provided-size-or-0>
environment            = "<Development|Test|Acceptance|Production>"
workload_name          = "<workload-name>"
cost_center            = "TO_BE_DEFINED"
owner                  = "TO_BE_DEFINED"
criticality            = "TO_BE_DEFINED"

Phase 8: Validation and Summary

After generating all files:

  1. Validate that all files are created in the correct directory structure
  2. Verify resource names follow naming-convention.md patterns
  3. Check that all mandatory tags are included
  4. Confirm that both Bicep and Terraform code are consistent
  5. Review subnet CIDR calculation is correct

Provide a summary to the user:

code
✓ Generated Azure Workload: <workload>
✓ Customer: <customerCode>
✓ Environment: <environment>

Files Created:
- azure-workloads/Customer/<customerCode>/bicep/<workloadname>.bicep
- azure-workloads/Customer/<customerCode>/bicep/<workloadname>.bicepparam
- azure-workloads/Customer/<customerCode>/terraform/<workloadname>.tf
- azure-workloads/Customer/<customerCode>/terraform/variables.tf
- azure-workloads/Customer/<customerCode>/terraform/terraform.tfvars

Resources:
- VNet: <vnet-name> (<vnet-address-space>)
- Subnet: <subnet-name> (<subnet-cidr>)
- VM: <vm-name> (<vm-sku>, <os-type>)
- Data Disk: <Yes/No> (<size-if-yes>)

Next Steps:
1. Review and update placeholder tag values (CostCenter, Owner, Criticality)
2. Update admin credentials in parameter files
3. Create resource group: rg-<workload>-<environment>-weu-01
4. Deploy using:
   - Bicep: az deployment group create --resource-group <rg-name> --parameters <workloadname>.bicepparam
   - Terraform: terraform init && terraform plan && terraform apply

Examples

Example 1: Web Application Workload

User Input:

  • Customer Code: contoso
  • Workload Name: webapp
  • Environment: p (Production)
  • VNet Address Space: 10.10.0.0/16
  • Subnet Name: app
  • Subnet Size: /24
  • VM OS: Linux
  • VM SKU: Standard_D2s_v3
  • Data Disk: yes
  • Data Disk Size: 128 GB
  • Instance: 01
  • Tags: CostCenter=CC-12345, Owner=WebTeam, Criticality=High

Generated Resources:

  • VNet: vnet-webapp-p-weu-01
  • Subnet: snet-app-p-weu-01
  • VM: vm-webapp-p-weu-01 (Linux, up to 64 chars allowed)
  • NIC: nic-webapp-p-weu-01
  • NSG: nsg-webapp-p-weu-01
  • Data Disk: disk-webapp-data-p-weu-01

Files Created:

code
azure-workloads/Customer/contoso/
├── bicep/
│   ├── webapp.bicep
│   └── webapp.bicepparam
└── terraform/
    ├── webapp.tf
    ├── variables.tf
    └── terraform.tfvars

Example 2: Database Workload (Windows)

User Input:

  • Customer Code: fabrikam
  • Workload Name: sqldb
  • Environment: t (Test)
  • VNet Address Space: 172.16.0.0/16
  • Subnet Name: data
  • Subnet Size: /26
  • VM OS: Windows
  • VM SKU: Standard_E4s_v3
  • Data Disk: yes
  • Data Disk Size: 512 GB
  • Instance: 01
  • Tags: CostCenter=CC-67890, Owner=DataTeam, Criticality=Critical

Generated Resources:

  • VNet: vnet-sqldb-t-weu-01
  • Subnet: snet-data-t-weu-01
  • VM: vmsqldbtweu01 (Windows, max 15 chars, no hyphens)
  • NIC: nic-sqldb-t-weu-01
  • NSG: nsg-sqldb-t-weu-01
  • Data Disk: disk-sqldb-data-t-weu-01

Files Created:

code
azure-workloads/Customer/fabrikam/
├── bicep/
│   ├── sqldb.bicep
│   └── sqldb.bicepparam
└── terraform/
    ├── sqldb.tf
    ├── variables.tf
    └── terraform.tfvars

Requirements

Documentation References

  • CRITICAL: Must read docs/standards/naming-convention.md before generating names
  • REFERENCE: docs/standards/ip-addressing-scheme.md for network context

Azure Permissions

Users deploying the generated code need:

  • Contributor role on target resource group
  • Network Contributor for VNet operations

Tools Required for Deployment

  • Bicep: Azure CLI 2.20.0+ with Bicep
  • Terraform: Terraform 1.5.0+, Azure CLI for authentication

Best Practices

Security

  • Never hardcode credentials in generated files
  • Use Azure Key Vault references for secrets in production
  • Implement network security group rules appropriate for workload type
  • Consider using managed identities instead of passwords where possible

Naming

  • Strictly follow naming-convention.md patterns
  • Validate Windows VM names are ≤15 characters (remove hyphens)
  • Linux VM names can be up to 64 characters (keep hyphens)
  • Use instance number 01 as default, increment for additional instances

Network Design

  • Ensure VNet address space doesn't overlap with existing networks
  • Subnet should be appropriately sized for expected resources
  • Consider future growth when selecting address spaces
  • Follow ip-addressing-scheme.md for subscription-based allocation

Tagging

  • All resources must have mandatory tags
  • Use placeholder values if user doesn't provide tag information
  • ManagedBy tag should reflect IaC tool (Bicep or Terraform)
  • Environment tag should use full name, not code

Code Quality

  • Include comprehensive comments in generated code
  • Use latest stable API versions
  • Implement conditional logic for optional resources (data disk)
  • Generate consistent code across Bicep and Terraform
  • Reference shared modules when available

Advanced Usage

Module References

If reusable Bicep or Terraform modules exist in the bicep-modules/ or terraform-modules/ directories, the skill should reference them instead of inline resources:

Bicep Module Reference Example:

bicep
module vnet '../../../bicep-modules/vnet/main.bicep' = {
  name: 'vnet-deployment'
  params: {
    vnetName: vnetName
    addressSpace: vnetAddressSpace
    location: location
    tags: tags
  }
}

Terraform Module Reference Example:

hcl
module "vnet" {
  source = "../../../terraform-modules/vnet"

  vnet_name     = local.vnet_name
  address_space = var.vnet_address_space
  location      = data.azurerm_resource_group.main.location
  tags          = local.common_tags
}

Multi-Instance Deployments

For deploying multiple instances of the same workload:

  1. Use instance number 01, 02, 03, etc.
  2. Ensure each instance has unique IP ranges
  3. Consider creating separate parameter files per instance

Integration with CI/CD

The generated code can be integrated into CI/CD pipelines:

  • Bicep: Use Azure DevOps or GitHub Actions with az deployment group create
  • Terraform: Use Terraform Cloud, Azure DevOps, or GitHub Actions with terraform apply

Troubleshooting

Common Issues

Issue: VM name exceeds 15 characters for Windows Solution: Remove hyphens and shorten workload name. Example: vmsqldbtweu01 instead of vm-sqldb-t-weu-01

Issue: Subnet CIDR calculation incorrect Solution: Ensure subnet size suffix is applied to VNet base address correctly

Issue: IP address space conflicts Solution: Verify VNet address doesn't overlap with existing networks or hub VNets

Issue: Mandatory tags missing Solution: Ensure all six mandatory tags are included in tags object

Issue: Module references not found Solution: Check if shared modules exist in bicep-modules/ or terraform-modules/ directories

Summary

The azure-workload-generator skill automates the creation of production-ready Azure spoke workload infrastructure following enterprise standards. It generates both Bicep and Terraform code with proper:

  • Resource naming per naming-convention.md
  • Mandatory tagging
  • Network topology aligned with ip-addressing-scheme.md
  • Security best practices
  • Conditional resource creation
  • Comprehensive documentation

Use this skill whenever you need to create new Azure workload infrastructure for customers.