Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

☁️ Azure Virtual Machine Automanage Configuration Assignment Terraform Module

Onboards one virtual machine to an Automanage configuration profile β€” and carries the fact that the provider does not: the service retires on 30 September 2027. Targets hashicorp/azurerm ~> 4.0.

Terraform azurerm Module Type Resources Caveat


🧩 Overview

  • πŸŽ›οΈ Enrolls a virtual machine in an Automanage configuration profile.
  • 1️⃣ Exactly one assignment per machine β€” the provider hard-codes the name, so there is no name argument.
  • ⏳ Emits the retirement date and the migration target as values, not as prose.
  • πŸ” Reports where the profile lives, so the read permission can be granted in the right place.
  • ⚠️ Names what the assignment causes: Azure onboards the machine to the services the profile lists, unobserved by Terraform, and removing the assignment undoes none of it.

πŸ’‘ Why it matters: Microsoft has announced that the Azure Automanage Best Practices service retires on 30 September 2027, with Azure Policy as the documented migration target β€” and nothing in Terraform says so. The resource is not flagged deprecated in the provider schema and the provider's documentation carries no notice, so a caller reading only Terraform has no way to find out. This module makes that fact part of the plan.


❀️ Support this project

If this module saved you time:


πŸ—ΊοΈ Where this fits in the family

flowchart TB
    CFG["terraform-azurerm-automanage-configuration -- the profile"]
    VM["terraform-azurerm-linux-virtual-machine or -windows-virtual-machine"]
    ASSIGN["terraform-azurerm-virtual-machine-automanage-configuration-assignment"]
    SVC["services the PROFILE enables -- backup, update management, monitoring"]
    POLICY["Azure Policy -- the documented migration target after 2027-09-30"]

    CFG -->|"id to configuration_id"| ASSIGN
    VM -->|"id to virtual_machine_id -- ONE assignment per machine, name fixed to default"| ASSIGN
    ASSIGN -->|"Automanage onboards the machine, unobserved by Terraform"| SVC
    ASSIGN -.->|"THE SERVICE RETIRES 2027-09-30 -- migrate to this"| POLICY

    classDef self fill:#0078D4,stroke:#004578,color:#ffffff
    classDef keystone fill:#004578,stroke:#002B4A,color:#ffffff
    classDef ext fill:#F3F2F1,stroke:#8A8886,color:#201F1E
    class ASSIGN self
    class VM keystone
    class CFG,SVC,POLICY ext
Loading

The dotted edge is the one to read. It does not represent anything this module builds β€” it records where Microsoft says configurations should end up after the retirement date, and it is drawn because a family diagram that omitted it would be quietly out of date.


🧬 What this module builds

flowchart TB
    IN1["virtual_machine_id -- force-new"]
    IN2["configuration_id -- force-new, either namespace casing accepted"]
    IN3["timeouts -- THREE keys, there is no update function"]
    RES["azurerm_virtual_machine_automanage_configuration_assignment.this -- name fixed to default"]
    OUT1["id ending in the literal segment default"]
    OUT2["the retirement date and the migration target"]
    OUT3["spans_subscriptions and the parsed names"]
    OUT4["posture -- onboarding is unobserved, removal undoes nothing"]

    IN1 --> RES
    IN2 --> RES
    IN3 --> RES
    RES --> OUT1
    RES --> OUT2
    RES --> OUT3
    RES --> OUT4

    classDef self fill:#0078D4,stroke:#004578,color:#ffffff
    classDef ext fill:#F3F2F1,stroke:#8A8886,color:#201F1E
    class RES self
    class IN1,IN2,IN3,OUT1,OUT2,OUT3,OUT4 ext
Loading
Resource Name Cardinality
azurerm_virtual_machine_automanage_configuration_assignment this single β€” and one per machine, because the name is fixed to default

Both arguments are force-new and there is no update function, so the timeouts type carries three keys.


βœ… Provider / Versions

Requirement Value
Terraform >= 1.12.0
hashicorp/azurerm ~> 4.0
Provider block None in this module β€” the caller configures provider "azurerm" { features {} }, including authentication

Schema notes that bite

  • πŸ”΄ The service is retiring on 2027-09-30 and the schema says nothing. deprecated is false on this resource, and the provider documentation carries no notice. Microsoft states that as a result of the retirement, creating a new configuration profile or onboarding a new subscription will return an error.
  • πŸ”΄ The assignment name is HARD-CODED to default. The provider's own comment says the API requires it. There is no name argument, and a machine has one Automanage assignment or none.
  • πŸ”΄ Changing the profile leaves the machine briefly onboarded to nothing. Both arguments are force-new and the replacement reuses the identical Resource ID, so create-before-destroy is impossible.
  • 🟠 The real namespace casing is Microsoft.AutoManage β€” capital M β€” in both the provider's import example and the sibling configuration resource. A case-sensitive check on the other spelling would reject the sibling module's own id output, so this module accepts either.
  • 🟠 This IS a real ARM record, unlike the gallery application assignment in the same family. Its ID is returned by Azure rather than composed, and its delete is a real delete.
  • 🟠 A successful create means the assignment exists, not that the machine is configured. Automanage onboards the machine to the profile's services on its own schedule, and no timeout here bounds that.
  • 🟑 The ID uses the CALLER'S provider subscription, with the machine's resource group and name.
  • 🟑 No lock is taken on the machine β€” unlike both of its neighbours in this family, which are read-modify-write operations and do lock it.
  • βœ… An import guard exists, and since the name is fixed it is effectively a test of whether the machine is onboarded at all.
  • βœ… No tags, no sensitive field, no credential β€” the resource names two Resource IDs and nothing else.

πŸ”‘ Required Azure RBAC Roles / Permissions

Permission Scope Why
Microsoft.AutoManage/configurationProfileAssignments/read the target machine Refresh, and the create's import check.
Microsoft.AutoManage/configurationProfileAssignments/write the target machine Create.
Microsoft.AutoManage/configurationProfileAssignments/delete the target machine Destroy β€” and, since there is no update, every change.
Microsoft.Compute/virtualMachines/read the target machine The provider resolves the machine.
Microsoft.AutoManage/configurationProfiles/read the configuration profile Resolving the profile β€” which may be in another subscription.

πŸ”’ The permission understates what applying this does. Onboarding a machine causes Azure to create and configure resources this module never mentions β€” a Recovery Services vault, an Automation account, a Log Analytics workspace, agents on the machine. None is a Terraform object, none appears in any plan, and terraform destroy removes none of them. Read the profile before assigning it.

⚠️ Microsoft documents wider permissions for first-time enablement: Owner, or Contributor together with User Access Administrator, at the subscription, because the service creates role assignments of its own. The table above covers an assignment on a subscription already onboarded.

ℹ️ Automanage acts continuously with its own identity. It monitors for drift and remediates it, so a setting changed by hand on the machine may be changed back with no Terraform run involved.


Azure Prerequisites

  • The Microsoft.Automanage resource provider registered in the target subscription.
  • The subscription already onboarded to Automanage, or the wider first-time permissions above.
  • The configuration profile already created β€” the sibling module produces one, and Microsoft publishes built-in production and dev/test profiles.
  • The machine running, with a healthy Azure VM agent, on a supported operating system.
  • πŸ”΄ A plan for 30 September 2027. After that date the service is retired and Microsoft's migration target is Azure Policy.

πŸ“ Module Structure

terraform-azurerm-virtual-machine-automanage-configuration-assignment/
β”œβ”€β”€ providers.tf    # required_version >= 1.12.0, azurerm ~> 4.0, no provider block
β”œβ”€β”€ variables.tf    # 3 inputs, 2 validations -- the machine, the profile, the deadlines
β”œβ”€β”€ main.tf         # one keystone `this`; ID parsing read from the END; a THREE-key timeouts block
β”œβ”€β”€ outputs.tf      # 30 outputs -- id first, then the retirement facts, then posture
β”œβ”€β”€ README.md       # this file
β”œβ”€β”€ SCOPE.md        # the cross-module contract
β”œβ”€β”€ LICENSE         # MIT
└── .gitignore

βš™οΈ Quick Start

provider "azurerm" {
  features {}
}

module "automanage_assignment" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-machine-automanage-configuration-assignment.git?ref=v1.0.0"

  virtual_machine_id = module.app_vm.id
  configuration_id   = module.production_profile.id
}

⚠️ Read the_automanage_service_is_announced_for_retirement before adopting this. See example 1.


πŸ”Œ Cross-Module Contract

Consumes

Input Type Source
virtual_machine_id string terraform-azurerm-linux-virtual-machine β†’ id
configuration_id string terraform-azurerm-automanage-configuration β†’ id
timeouts object(...) the caller β€” three keys

Emits

Output Description Consumed by
id .../virtualMachines/<vm>/providers/Microsoft.AutoManage/configurationProfileAssignments/default reporting
automanage_retirement_date 2027-09-30, as a comparable value planning
automanage_migration_target Azure Policy planning
spans_subscriptions Where the read permission on the profile is needed pre-apply review

πŸ“š Example Library

1 Β· The retirement, first
module "automanage_assignment" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-machine-automanage-configuration-assignment.git?ref=v1.0.0"

  virtual_machine_id = module.app_vm.id
  configuration_id   = module.production_profile.id
}

output "automanage_end_of_life" {
  value = {
    retiring   = module.automanage_assignment.the_automanage_service_is_announced_for_retirement
    on         = module.automanage_assignment.automanage_retirement_date
    migrate_to = module.automanage_assignment.automanage_migration_target
  }
}

πŸ”΄ Microsoft has announced that the Azure Automanage Best Practices service retires on 30 September 2027, and states that as a result, creating a new configuration profile or onboarding a new subscription to the service will return an error. The documented migration target is Azure Policy.

⚠️ Nothing in Terraform tells you this. The resource is not flagged deprecated in the provider schema and the provider's documentation carries no notice β€” which is exactly why this module emits it. A schema that says deprecated = false is not evidence that a service has a future.

πŸ’‘ The date is emitted as a value, not only as a boolean, so a policy check can compare it against a planning horizon instead of hard-coding it in every consuming configuration.

2 Β· One assignment per machine, and why there is no name
module "automanage_assignment" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-machine-automanage-configuration-assignment.git?ref=v1.0.0"

  virtual_machine_id = module.app_vm.id
  configuration_id   = module.production_profile.id
}

# A SECOND assignment on the same machine is not a second assignment.
module "automanage_assignment_devtest" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-machine-automanage-configuration-assignment.git?ref=v1.0.0"

  virtual_machine_id = module.app_vm.id       # <-- the same machine
  configuration_id   = var.devtest_profile_id
}

πŸ”΄ The provider hard-codes the assignment name to the literal string default, with a comment stating the API requires it. Both module instances above therefore compose the same Resource ID, and the second fails its create with a requires-import error.

πŸ’‘ That is the good outcome β€” a loud failure rather than one profile silently replacing the other. But it is worth knowing before you write the second block, not after.

ℹ️ This is also why the module has no name variable. Its absence is a design fact, reported through one_assignment_per_machine_the_name_is_hard_coded.

3 Β· Changing the profile leaves a gap
module "automanage_assignment" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-machine-automanage-configuration-assignment.git?ref=v1.0.0"

  virtual_machine_id = module.app_vm.id

  # Changing this DELETES the assignment and creates a new one.
  configuration_id = var.hardened_profile_id
}

⚠️ Both arguments are force-new, and the replacement reuses the identical Resource ID β€” because the name is fixed. Terraform therefore cannot create the new assignment before destroying the old one, and there is a window during which the machine is onboarded to nothing.

ℹ️ Usually harmless; occasionally not, if the profile is what enables backup on a machine that matters. changing_the_profile_leaves_the_machine_unonboarded_briefly is a constant true so the fact is visible rather than discovered.

πŸ’‘ fields_that_can_change_after_creation returns an empty list, deliberately. There is no update operation on this resource at all.

4 Β· Either namespace casing is accepted
# Both of these are accepted. Azure and the provider emit the FIRST spelling.
configuration_id = "/subscriptions/.../providers/Microsoft.AutoManage/configurationProfiles/prod"
configuration_id = "/subscriptions/.../providers/Microsoft.Automanage/configurationProfiles/prod"

ℹ️ The real casing has a capital M: Microsoft.AutoManage. It is what the provider's own import example uses and what the sibling configuration resource emits.

πŸ”’ A case-sensitive check on the other spelling would have rejected the sibling module's own id output β€” a validation that refuses legal input, which is the worst kind, because a validation {} failure blocks terraform destroy as well as apply. This module accepts either casing for exactly that reason.

5 Β· A profile in another subscription
module "automanage_assignment" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-machine-automanage-configuration-assignment.git?ref=v1.0.0"

  virtual_machine_id = module.app_vm.id
  configuration_id   = var.platform_production_profile_id # a central profile
}

check "cross_subscription_read_was_granted" {
  assert {
    condition     = !module.automanage_assignment.spans_subscriptions || var.platform_profile_reader_granted
    error_message = "The profile is in another subscription and read access must be granted there."
  }
}

⚠️ Legal, common, and a likely cause of a confusing failure. A central platform profile in its own subscription is good practice, but a role assignment scoped to the machine's own subscription does not reach it.

πŸ’‘ spans_subscriptions and spans_resource_groups exist so a review can see where the read permission has to be granted, before the apply rather than after.

6 Β· What NOT to do
# WRONG -- an Arc-enabled server where a virtual machine belongs.
virtual_machine_id = var.arc_machine_id

ℹ️ Refused at terraform plan, offline and without credentials. Arc machines have their own Automanage assignment resource. The two are easy to confuse because Microsoft's Automanage documentation covers both in one place.

# WRONG -- a scale set.
virtual_machine_id = var.app_scale_set_id

ℹ️ Also refused. This resource targets a single machine.

# WRONG -- a child of the profile.
configuration_id = var.profile_version_id

ℹ️ Refused. The pattern is anchored at both ends, so anything below the profile cannot pass.

# WRONG -- a virtual machine ID where the profile belongs.
configuration_id = var.app_vm_id

ℹ️ Refused, with a message naming the shape that is expected.

7 Β· A green apply is not a configured machine
module "automanage_assignment" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-machine-automanage-configuration-assignment.git?ref=v1.0.0"

  virtual_machine_id = module.app_vm.id
  configuration_id   = module.production_profile.id

  timeouts = {
    create = "45m"
  }
}

⚠️ The timeout bounds recording the assignment, not the onboarding. Automanage then enrolls the machine in each service the profile names β€” backup, update management, monitoring β€” on its own schedule, remediating drift continuously thereafter. None of that is observed by this resource or bounded by any deadline on it.

ℹ️ a_successful_create_does_not_mean_the_machine_is_configured is a constant true for this reason. The machine's actual state is visible in the Automanage blade in the portal, not in a Terraform plan.

8 Β· The profile decides the blast radius
module "production_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-automanage-configuration.git?ref=v1.0.0"

  name                = "conf-production"
  resource_group_name = module.rg.name
  location            = module.rg.location
}

module "automanage_assignment" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-machine-automanage-configuration-assignment.git?ref=v1.0.0"

  virtual_machine_id = module.app_vm.id
  configuration_id   = module.production_profile.id
}

πŸ”’ Applying the assignment can create resources this module never mentions β€” a Recovery Services vault, an Automation account, a Log Analytics workspace, agents on the machine, and their contents. They are not Terraform objects, they appear in no plan, and terraform destroy removes none of them.

⚠️ The profile decides all of that; the assignment is only the switch. Read the profile before assigning it, and read it again when someone edits it, because the edit changes what every assigned machine gets without any of the assignments changing.

ℹ️ this_resource_configures_services_it_does_not_name and removing_the_assignment_does_not_undo_what_it_configured are both constant true.

9 Β· The agent change that already happened
output "automanage_agent_caveat" {
  value = module.automanage_assignment.monitoring_agent_dependency_was_already_cut
}

⚠️ From 1 February 2025 Automanage began halting support and enforcement for services that depended on the retired Microsoft Monitoring Agent β€” change tracking, VM insights, update management and Azure Automation among them. The replacement is the Azure Monitor Agent.

ℹ️ A profile written before that change may therefore name services Automanage no longer enforces, and nothing in this resource, in the plan, or in the assignment's state reveals which. This is emitted separately from the retirement because it is not a future date β€” it has already taken effect.

10 Β· Removing the assignment
# Removing this block stops Automanage MANAGING the machine.
# It does not undo what Automanage configured.

πŸ”΄ Destroy stops the management; it does not reverse the onboarding. The backup vault, the automation account, the workspace and the agents all remain, and any setting the profile changed on the machine stays changed. Nothing in Terraform tracks those effects.

⚠️ If the assignment is removed outside Terraform β€” through the portal's disable flow, for instance β€” the next refresh marks it gone and the following plan proposes to create it again. Re-enrolling is not a quiet no-op: Automanage onboards the machine to the profile's services once more.

11 Β· No lock, and what that means
module "automanage_assignment" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-machine-automanage-configuration-assignment.git?ref=v1.0.0"

  virtual_machine_id = module.app_vm.id
  configuration_id   = module.production_profile.id
}

module "restore_disk" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-machine-implicit-data-disk-from-source.git?ref=v1.0.0"

  name               = "disk-restored-01"
  virtual_machine_id = module.app_vm.id # the SAME machine
  source_resource_id = var.nightly_snapshot_id
  create_option      = "Copy"
  disk_size_gb       = 256
  lun                = 1
}

ℹ️ This resource takes no lock on the machine, because it is a genuine ARM child record rather than a read-modify-write of the machine. Its two neighbours in this family β€” the implicit data disk above, and the gallery application assignment β€” both rewrite the machine and both lock it.

⚠️ The corollary is that this resource does not serialise against them either. An Automanage assignment and a disk attachment can reach one machine concurrently. That is usually fine, because they touch different things, and it is worth knowing when it is not.

12 Β· πŸ—οΈ End-to-end composition
provider "azurerm" {
  features {}
}

module "rg" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"

  name     = "rg-app-eastus"
  location = "eastus"

  tags = {
    environment = "production"
    workload    = "line-of-business-app"
  }
}

module "app_vnet" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-network.git?ref=v1.0.0"

  name                = "vnet-app-eastus"
  resource_group_name = module.rg.name
  location            = module.rg.location
  address_space       = ["10.90.0.0/16"]

  subnets = {
    snet-app = {
      address_prefixes = ["10.90.1.0/24"]
    }
  }

  tags = module.rg.tags
}

module "app_nic" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-network-interface.git?ref=v1.0.0"

  name                = "nic-app01"
  resource_group_name = module.rg.name
  location            = module.rg.location

  ip_configurations = {
    internal = {
      subnet_id                     = module.app_vnet.subnet_ids["snet-app"]
      private_ip_address_allocation = "Dynamic"
    }
  }

  tags = module.rg.tags
}

module "app_vm" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-linux-virtual-machine.git?ref=v1.0.0"

  name                = "vm-app01"
  resource_group_name = module.rg.name
  location            = module.rg.location

  size           = "Standard_D2s_v5"
  admin_username = "azureadmin"

  network_interface_ids = [module.app_nic.id]

  tags = module.rg.tags
}

# The profile decides which services the machine is enrolled in.
module "production_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-automanage-configuration.git?ref=v1.0.0"

  name                = "conf-production"
  resource_group_name = module.rg.name
  location            = module.rg.location
}

module "automanage_assignment" {
  source = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-machine-automanage-configuration-assignment.git?ref=v1.0.0"

  virtual_machine_id = module.app_vm.id
  configuration_id   = module.production_profile.id
}

output "automanage" {
  value = {
    id            = module.automanage_assignment.id
    profile       = module.automanage_assignment.configuration_name
    machine       = module.automanage_assignment.virtual_machine_name
    cross_sub     = module.automanage_assignment.spans_subscriptions
    retires_on    = module.automanage_assignment.automanage_retirement_date
    migrate_to    = module.automanage_assignment.automanage_migration_target
    agent_caveat  = module.automanage_assignment.monitoring_agent_dependency_was_already_cut
  }
}

πŸ”΄ The retirement date is in the composition's own output, deliberately. A configuration adopting Automanage today should record, in its plan, when it is expected to stop working and where Microsoft says it should go instead.

⚠️ This is also the shape most affected by the retirement notice: it creates the profile as well as the assignment, and Microsoft's statement says profile creation is what errors first.

πŸ’‘ spans_subscriptions is false here because the profile is in the same subscription. A platform topology would put it elsewhere β€” see example 5.


πŸ“₯ Inputs

Required: virtual_machine_id, configuration_id Optional: timeouts

There is no name variable β€” the provider hard-codes the assignment name β€” and no tags variable, because the resource exposes none.

Full input schemas
variable "virtual_machine_id" {
  type = string
  # REQUIRED, FORCE-NEW. Anchored to .../virtualMachines/<vm> at both ends.
  # Arc-enabled servers use a DIFFERENT resource and are refused here.
}

variable "configuration_id" {
  type = string
  # REQUIRED, FORCE-NEW. Anchored to .../configurationProfiles/<name>.
  # EITHER namespace casing is accepted -- Microsoft.AutoManage is what Azure
  # and the sibling configuration module actually emit.
}

variable "timeouts" {
  type = object({
    create = optional(string)
    read   = optional(string)
    delete = optional(string)
  })
  default = null
  # THREE KEYS. Both arguments are force-new, so there is no update operation
  # and no update timeout; Terraform would discard an `update` key silently.
}

🧾 Outputs

Output Type Notes
id string Ends in the literal segment default
virtual_machine_id / virtual_machine_name string The onboarded machine
resource_group_name / subscription_id string Parsed from the machine's ID
configuration_id / configuration_name string The profile
configuration_resource_group_name / configuration_subscription_id string Where the profile lives
spans_subscriptions / spans_resource_groups bool Where read access on the profile is needed
the_automanage_service_is_announced_for_retirement bool Constant true
automanage_retirement_date string 2027-09-30
automanage_migration_target string Azure Policy
monitoring_agent_dependency_was_already_cut bool Constant true β€” already in force
one_assignment_per_machine_the_name_is_hard_coded bool Constant true
changing_the_profile_leaves_the_machine_unonboarded_briefly bool Constant true
a_successful_create_does_not_mean_the_machine_is_configured bool Constant true
this_resource_configures_services_it_does_not_name bool Constant true
removing_the_assignment_does_not_undo_what_it_configured bool Constant true
the_provider_takes_no_lock_on_the_machine bool Constant true
this_is_a_real_azure_resource_not_a_patch_of_the_machine bool Constant true
an_import_guard_exists_on_this_resource bool Constant true
a_removed_assignment_disappears_from_state_without_an_error bool Constant true
arc_enabled_servers_use_a_different_resource bool Constant true
no_secret_is_accepted_or_emitted_by_this_module bool Constant true
this_resource_supports_no_azure_resource_tags bool Constant true
force_new_fields list(string) Both arguments
fields_that_can_change_after_creation list(string) Empty, deliberately
fields_azure_returns_on_read list(string) Both, re-parsed and normalised

🧠 Architecture Notes

The retirement is the first architectural fact. Microsoft has announced that the Azure Automanage Best Practices service retires on 30 September 2027, and states that as a result, creating a new configuration profile or onboarding a new subscription will return an error; the migration target is Azure Policy. Nothing in Terraform says so β€” the schema's deprecated flag is false and the provider documentation carries no notice. That combination has now been seen three times in this library, and it is why a schema flag is treated as necessary but never sufficient evidence that a resource has a future. The module emits the date as a value so a policy check can act on it.

The module is authored rather than withheld, which is a deliberate departure from how Azure Blueprints and the Key Vault managed storage account were handled. Those were services already gone or already refusing new work. This one has a future end date, a live resource in the pinned provider, and an already-authored sibling that creates the profiles β€” so withholding it would leave the library able to create a profile it cannot assign. Honest documentation beats an absent module here.

There is no name argument, and that shapes everything. The provider hard-codes the assignment name to default because the API requires it, so a machine has one Automanage assignment or none. Two module instances on one machine compose the same Resource ID and the second fails on the import guard; changing the profile is a delete-then-create on that same ID, which makes create-before-destroy impossible and leaves the machine briefly onboarded to nothing.

This is a real ARM record, unlike its neighbours. The gallery application assignment and the implicit data disk are both read-modify-write operations on the machine with synthetic IDs; this one creates a genuine configurationProfileAssignments object whose ID Azure returns, whose delete is a real delete, and which can be used where an ARM Resource ID is expected. It also takes no lock β€” and therefore does not serialise against the two that do.

The permission table understates the effect. The profile, not the assignment, decides which Azure services are enabled and how; applying an assignment can create a Recovery Services vault, an Automation account, a Log Analytics workspace and their contents. None is a Terraform object and terraform destroy removes none of them. Read the profile before assigning it β€” and note that editing the profile changes what every assigned machine gets, without any assignment changing.

The namespace casing nearly became a defect. Azure and the provider emit Microsoft.AutoManage with a capital M; a case-sensitive check on the other spelling would have rejected the sibling configuration module's own id output. Because a validation {} failure blocks terraform destroy as well as apply, that would have been the worst class of defect β€” a check that refuses legal input. This module accepts either casing, as the sibling snapshot module in this library does for its own ID check.

lifecycle is not valid inside a module block, so a caller cannot add prevent_destroy here.


🧱 Design Principles

Concern This module's position What the caller must type to change it
The retirement Emitted as a boolean, a date and a migration target β€” comparable, not just readable β€”
Authoring at all Authored with the end date attached, because the sibling profile module already exists β€”
Namespace casing Either casing accepted β€” a strict check would reject the sibling's own output β€”
The absent name Documented as a design fact, with its three consequences emitted separately β€”
Resource-ID inputs Anchored at both ends β€” Arc machines, scale sets and children refused β€”
What the profile causes Named in the permissions table, not left to the profile's own documentation β€”
tags Absent by design β€” the resource exposes none tag the machine or the profile

πŸ”’ The security question here is not a setting on this resource. It is what the profile enables, and who can edit the profile.


πŸš€ Runbook

terraform init -backend=false
terraform validate
terraform fmt -check

Pin the module by tag β€” ?ref=v1.0.0 β€” never a branch.

Plan-only: this repository performs no cloud apply. A human applies from CI β€” and for this module, after someone has read both the profile and the retirement date.


πŸ§ͺ Testing

What terraform validate covers, offline and without credentials:

  • The anchored virtual machine ID check, including refusing Arc machines and scale sets.
  • The anchored configuration profile ID check, in both namespace casings.

What only terraform plan exercises (credentials required):

  • That the machine and the profile exist.
  • The import guard, if the machine is already onboarded by any means.

What only terraform apply reveals:

  • Whether the subscription is onboarded to Automanage.
  • Whether the machine's operating system is supported.
  • Whether the identity can read a profile in another subscription.

What nothing reveals at all:

  • Whether Automanage has finished onboarding the machine.
  • Which of the profile's services are still enforced after the 2025 monitoring-agent change.
  • What the profile will create in your subscription.

ℹ️ terraform console fires root-module variable validations, unlike terraform validate on a calling configuration. It is the honest offline harness for the first list and for every derived flag β€” including both branches of each span flag and both namespace casings.


πŸ’¬ Example Output

Outputs:

id                                                = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-app-eastus/providers/Microsoft.Compute/virtualMachines/vm-app01/providers/Microsoft.AutoManage/configurationProfileAssignments/default"
virtual_machine_name                              = "vm-app01"
configuration_name                                = "conf-production"
spans_subscriptions                               = false
spans_resource_groups                             = false
the_automanage_service_is_announced_for_retirement = true
automanage_retirement_date                        = "2027-09-30"
automanage_migration_target                       = "Azure Policy"
monitoring_agent_dependency_was_already_cut       = true
fields_that_can_change_after_creation             = []

ℹ️ Look at the last segment of the id. It is the literal string default, not a name you chose β€” and the two providers segments mark this as an extension resource living under the machine.


πŸ” Troubleshooting

Symptom Cause Fix
must be a virtual machine Resource ID An Arc machine, a scale set, or a child of the machine Arc servers have their own assignment resource
configuration profile Resource ID A machine ID, or a child of the profile The pattern is anchored at both ends; either namespace casing is fine
Apply fails with a requires-import error The machine is already onboarded, by any means There is one assignment per machine; import it or remove the other
Apply fails with an authorization error on the profile The profile is in another subscription See spans_subscriptions; grant read at the profile
Apply fails on a first-time subscription Automanage was never enabled here Microsoft documents wider permissions for first-time enablement
Apply succeeds, the machine shows nothing Onboarding runs after the assignment is recorded Expected; check the Automanage blade, not the plan
A service in the profile is not being enforced The 2025 monitoring-agent change See monitoring_agent_dependency_was_already_cut; move to Azure Monitor Agent
Changing the profile produced a destroy and a create Both arguments are force-new and the ID is fixed Expected. There is a brief window with no assignment
A setting keeps reverting on the machine Automanage remediates drift continuously That is the service working; change the profile, not the machine
The assignment vanished Someone disabled it in the portal The refresh marks it gone; the next apply re-enrolls the machine
terraform destroy left a backup vault behind Destroy stops management, it does not undo onboarding Remove what the profile created, deliberately
Planning for the service ending It retires 2027-09-30 automanage_retirement_date and automanage_migration_target

πŸ”— Related Docs


πŸ’™ "Infrastructure as Code should be standardized, consistent, and secure."