# Importing Azure Resources into Pulumi

This document provides detailed procedures for importing existing Azure resources into Pulumi state and resolving preview diffs to achieve zero-diff validation.

**Key Principle**: Azure will return many default values it has set dynamically that are not represented in code/state. You must systematically resolve each diff type to achieve zero-diff.

## IMPORT APPROACH: INLINE IMPORT IDS

**ARM migration uses inline imports.** Use Pulumi's `import` resource option to specify Azure Resource IDs directly in the code.

### Example

**TypeScript:**

```typescript
const storageAccount = new azure_native.storage.StorageAccount("storageAccount", {
    accountName: "mystorageaccount",
    resourceGroupName: "myResourceGroup",
    location: "eastus",
    sku: { name: "Standard_LRS" },
    kind: "StorageV2",
}, {
    import: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.Storage/storageAccounts/mystorageaccount"
});
```

**Python:**

```python
storage_account = storage.StorageAccount("storageAccount",
    account_name="mystorageaccount",
    resource_group_name="myResourceGroup",
    location="eastus",
    sku=storage.SkuArgs(name="Standard_LRS"),
    kind="StorageV2",
    opts=pulumi.ResourceOptions(
        import_="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.Storage/storageAccounts/mystorageaccount"
    ))
```

**Go:**

```go
storageAccount, err := storage.NewStorageAccount(ctx, "storageAccount", &storage.StorageAccountArgs{
    AccountName:       pulumi.String("mystorageaccount"),
    ResourceGroupName: pulumi.String("myResourceGroup"),
    Location:          pulumi.String("eastus"),
    Sku: &storage.SkuArgs{
        Name: pulumi.String("Standard_LRS"),
    },
    Kind: pulumi.String("StorageV2"),
}, pulumi.Import(pulumi.ID("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.Storage/storageAccounts/mystorageaccount")))
```

**C#:**

```csharp
var storageAccount = new StorageAccount("storageAccount", new StorageAccountArgs
{
    AccountName = "mystorageaccount",
    ResourceGroupName = "myResourceGroup",
    Location = "eastus",
    Sku = new SkuArgs { Name = "Standard_LRS" },
    Kind = "StorageV2",
}, new CustomResourceOptions
{
    ImportId = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.Storage/storageAccounts/mystorageaccount"
});
```

**Java:**

```java
var storageAccount = new StorageAccount("storageAccount", StorageAccountArgs.builder()
    .accountName("mystorageaccount")
    .resourceGroupName("myResourceGroup")
    .location("eastus")
    .sku(SkuArgs.builder().name("Standard_LRS").build())
    .kind("StorageV2")
    .build(), CustomResourceOptions.builder()
        .importId("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.Storage/storageAccounts/mystorageaccount")
        .build());
```

**YAML:**

```yaml
resources:
  storageAccount:
    type: azure-native:storage:StorageAccount
    properties:
      accountName: mystorageaccount
      resourceGroupName: myResourceGroup
      location: eastus
      sku:
        name: Standard_LRS
      kind: StorageV2
    options:
      import: /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.Storage/storageAccounts/mystorageaccount
```

## FINDING IMPORT IDS

Azure Resource IDs follow a predictable pattern and can be generated by convention or queried.

### Azure Resource ID Format

```text
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}
```

For child resources, the pattern extends:

```text
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{parentResourceType}/{parentResourceName}/{childResourceType}/{childResourceName}
```

**Examples:**

```text
# Storage Account
/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRG/providers/Microsoft.Storage/storageAccounts/mystorageaccount

# Web App
/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRG/providers/Microsoft.Web/sites/mywebapp

# Virtual Network Subnet (child resource)
/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myRG/providers/Microsoft.Network/virtualNetworks/myVNet/subnets/mySubnet
```

**Documentation:** [Azure Resource ID Documentation](https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/template-functions-resource)

### Method 1: Convention-Based Generation

```typescript
// Pattern: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}

const subscriptionId = "00000000-0000-0000-0000-000000000000";
const resourceGroupName = "myResourceGroup";
const storageAccountName = "mystorageaccount";

const importId = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.Storage/storageAccounts/${storageAccountName}`;
```

### Method 2: Query Azure API

```bash
# Get resource ID directly
az resource show \
  --name mystorageaccount \
  --resource-group myResourceGroup \
  --resource-type "Microsoft.Storage/storageAccounts" \
  --query "id" \
  --output tsv

# List all resources with IDs
az resource list \
  --resource-group myResourceGroup \
  --query "[].{Name:name, Type:type, ID:id}" \
  --output table

# Get ID for specific resource type
az storage account show \
  --name mystorageaccount \
  --resource-group myResourceGroup \
  --query "id" \
  --output tsv
```

### Finding Import IDs in Pulumi Registry

Each resource type in the Pulumi Registry has an "Import" section with the expected ID format:

**Example:** [Storage Account Import](https://www.pulumi.com/registry/packages/azure-native/api-docs/storage/storageaccount/#import)

## HANDLING CHILD RESOURCES

Some ARM properties are separate resources in Pulumi and must be imported separately:

### Example: WebApp Application Settings

**ARM Template (Inline):**

```json
{
  "type": "Microsoft.Web/sites",
  "properties": {
    "siteConfig": {
      "appSettings": [
        {"name": "WEBSITE_NODE_DEFAULT_VERSION", "value": "14.17.0"}
      ]
    }
  }
}
```

**Pulumi (Separate Resource):**

```typescript
// Main Web App
const webApp = new azure_native.web.WebApp("webApp", {
    name: "mywebapp",
    resourceGroupName: resourceGroup.name,
    // ... other properties
}, {
    import: "/subscriptions/.../Microsoft.Web/sites/mywebapp"
});

// Application Settings (separate resource)
const appSettings = new azure_native.web.WebAppApplicationSettings("appSettings", {
    name: webApp.name,
    resourceGroupName: resourceGroup.name,
    properties: {
        "WEBSITE_NODE_DEFAULT_VERSION": "14.17.0",
    },
}, {
    import: "/subscriptions/.../Microsoft.Web/sites/mywebapp/config/appsettings"
});
```

**Other common examples:**

- `WebAppAuthSettings` - Authentication settings for Web Apps
- `WebAppConnectionStrings` - Connection strings for Web Apps
- Network Security Group Rules - May be inline or separate depending on ARM template

**Documentation:** [WebAppApplicationSettings](https://www.pulumi.com/registry/packages/azure-native/api-docs/web/webappapplicationsettings/)

## PREVIEW AFTER IMPORT - ZERO DIFF VALIDATION

After importing resources, you MUST run `pulumi preview` to ensure there are no changes. The goal is **zero diff**:

- NO updates
- NO replaces
- NO creates
- NO deletes

**If there are changes, follow the Preview Resolution Workflow below.**

## PREVIEW RESOLUTION WORKFLOW

**IMPORTANT:** Follow these instructions. Avoid over-use of ignoreChanges resource option.

Azure will return many default values it has set dynamically that are not represented in code/state. You must resolve each diff type systematically:

### Removed Properties (-) - Missing Defaults

**Symptom:** Preview shows properties being removed that exist in Azure:

```text
~ azure-native:storage:StorageAccount: (update)
    - minimumTlsVersion: "TLS1_2"
    - allowBlobPublicAccess: false
```

**Resolution:** These properties were set by Azure with default values but are missing from your Pulumi code. **ADD them to your code:**

```typescript
const storageAccount = new azure_native.storage.StorageAccount("storageAccount", {
    accountName: "mystorageaccount",
    resourceGroupName: resourceGroup.name,
    // ... other properties
    minimumTlsVersion: azure_native.storage.MinimumTlsVersion.TLS1_2, // ADD this
    allowBlobPublicAccess: false, // ADD this
}, {
    import: "...",
});
```

**How to find the correct values:**

```bash
# Query Azure for actual property values
az resource show \
  --ids "/subscriptions/.../Microsoft.Storage/storageAccounts/mystorageaccount" \
  --query "properties.minimumTlsVersion" \
  --output tsv

# Get full properties
az storage account show \
  --name mystorageaccount \
  --resource-group myResourceGroup \
  --query "properties" \
  --output json
```

### Added Properties (+) - Computed Properties

**Symptom:** Preview shows properties being added that don't exist in the ARM template:

```text
~ azure-native:storage:StorageAccount: (update)
    + creationTime: "2024-01-15T10:30:00Z"
    + statusOfPrimary: "available"
```

**Resolution:** These are **computed/read-only properties** set by Azure. Use `ignoreChanges` for these properties ONLY:

```typescript
const storageAccount = new azure_native.storage.StorageAccount("storageAccount", {
    // ... properties
}, {
    import: "...",
    ignoreChanges: ["creationTime", "statusOfPrimary"],
});
```

**CRITICAL:** Only use `ignoreChanges` for properties that:

1. Are shown as added (+) in the preview diff
2. Do NOT exist in the source ARM template
3. Are computed/read-only properties set by Azure

**Documentation:** [ignoreChanges Resource Option](https://www.pulumi.com/docs/iac/concepts/options/ignorechanges/)

### Changed Properties (~) - Value Mismatches

**Symptom:** Preview shows properties with different values:

```text
~ azure-native:network:VirtualNetwork: (update)
    ~ enableDdosProtection: true => false
```

**Resolution:** Evaluate the change using preview diff and/or Azure API:

**Step 1:** Query Azure for the actual value:

```bash
az network vnet show \
  --name myVNet \
  --resource-group myResourceGroup \
  --query "enableDdosProtection" \
  --output tsv
```

**Step 2:** Determine the correct value:

- If Azure shows `true`, update your code to `true`
- If the ARM template specified `false`, investigate why Azure has `true` (manual change?)
- Match the **desired state** (what you want), not necessarily what's in Azure

**Step 3:** Update your code to match the desired state:

```typescript
const vnet = new azure_native.network.VirtualNetwork("vnet", {
    virtualNetworkName: "myVNet",
    resourceGroupName: resourceGroup.name,
    enableDdosProtection: true, // Match actual Azure state if that's desired
}, {
    import: "...",
});
```

## DEBUGGING WORKFLOW FOR DIFFS

**Step-by-step debugging process:**

1. **Run preview with details:**

   ```bash
   pulumi preview --diff --show-config --show-secrets
   ```

2. **For each diff, identify the property path:**

   ```text
   ~ azure-native:web:WebApp: (update)
       [urn=...]
       ~ siteConfig.alwaysOn: false => true
   ```

3. **Query Azure for actual value:**

   ```bash
   az webapp show \
     --name mywebapp \
     --resource-group myResourceGroup \
     --query "siteConfig.alwaysOn" \
     --output tsv
   ```

4. **Check Pulumi Registry for property documentation:**
   - Search for the resource type in [Pulumi Registry](https://www.pulumi.com/registry/)
   - Check property descriptions, types, and defaults

5. **Determine resolution strategy:**
   - **Added (+) and not in ARM template** -> `ignoreChanges`
   - **Removed (-) and exists in Azure** -> Add to code
   - **Changed (~)** -> Update code to desired value

6. **Apply fix and re-run preview** until zero diff is achieved

## COMMON AZURE PROPERTIES REQUIRING ATTENTION

### Storage Accounts

- `creationTime`, `statusOfPrimary`, `statusOfSecondary` -> ignore (computed)
- `minimumTlsVersion`, `allowBlobPublicAccess` -> often need to be added
- `encryption.services.*` -> may need to be added with defaults

### Web Apps

- `state`, `hostNames`, `repositorySiteName` -> ignore (computed)
- `httpsOnly`, `clientAffinityEnabled` -> often need to be added
- `siteConfig` nested properties -> many defaults need explicit values

### Virtual Networks

- `subnets[*].id`, `subnets[*].etag` -> ignore (computed)
- `enableDdosProtection`, `enableVmProtection` -> may need to be added

### Network Security Groups

- `securityRules[*].etag`, `securityRules[*].id` -> ignore (computed)
- Default rules -> Azure adds default rules; handle via ignoreChanges or import separately

## ITERATION PROCESS

The preview resolution process is iterative:

1. Import resources with initial code
2. Run `pulumi preview`
3. Identify diffs (+ / - / ~)
4. Apply resolution strategy for each diff
5. Run `pulumi preview` again
6. Repeat until zero diff

**Expected iterations:** 2-5 preview runs for complex resources

## COMMON PITFALLS TO AVOID

- Using `ignoreChanges` for properties that exist in ARM template
- Not querying Azure API to verify actual property values
- Stopping at first preview without resolving all diffs
- Forgetting to import child resources separately (like WebAppApplicationSettings)
- Not documenting which properties were ignored vs. added
