Unwanted Forced Replacement of Resource

We have a Terraform project that includes both vSphere and Active Directory providers. It is designed to create Active Directory containers and objects (users, computers and security groups) before building new VMs, joining them to the domain and placing them in the correct (new) AD container.

The password for the account used to join the VM to the domain is cycled regularly by our PAM tool. So, we update the creds.auto.tf when we need to apply our project code with the updated password.

We have successfully used the project to deploy applications, cloning the project for each new application.

The issue was encountered when we had to make changes to one of the applications. Specifically, we added another user account to the project. Then we updated the creds.auto.tf and tried to apply the project.

The plan indicated the VM resource would be replaced. This was unexpected and will cause a lot of trouble moving forward… a minor change not directly related to the VM resource should not result in a destroy/rebuild.

I’m reaching out for assistance. Is there a way make these updates without replacing the VM?
I would be grateful for any help.

The plan output is attached.
plan_result.txt (7.1 KB)

Thanks

Hi @roy.madden,

If you want to prevent changes in configuration from being applied to existing resources, then you want to use the ignore_changes feature. If you question on using that feature it would help to see the configuration and which attributes exactly you want to ignore.

Hi @jbardin ,

This is exactly what I needed - thanks for the assistance.
The syntax seems straightforward, but please double check my work - I added the lifecycle meta-argument at the end of the resource block:

resource "vsphere_virtual_machine" "vm" {
 for_each                   = var.virtual_machines
 name                       = each.key
 resource_pool_id           = data.vsphere_compute_cluster.cluster.resource_pool_id
 datastore_cluster_id       = data.vsphere_datastore_cluster.datastore_cluster.id
 num_cpus                   = each.value.vm_cpu
 memory                     = each.value.vm_ram
 guest_id                   = data.vsphere_virtual_machine.template.guest_id
 scsi_type                  = data.vsphere_virtual_machine.template.scsi_type
 firmware                   = var.vm_firmware
 wait_for_guest_net_timeout = -1

 network_interface {
   network_id   = data.vsphere_network.network.id
   adapter_type = each.value.vm_adapter
 }
 dynamic "disk" {
   for_each = { for idx, size in each.value.vm_disks : idx => size }
   content {
     label            = "disk${disk.key}"
     unit_number      = disk.key
     size             = disk.value
     thin_provisioned = false
     eagerly_scrub    = false
   }
 }
 clone {
   template_uuid = data.vsphere_virtual_machine.template.id
   customize {
     timeout = 30
     windows_options {
       computer_name = each.key
       # workgroup             = "test"
       join_domain           = var.domain_name
       domain_admin_user     = var.domain_admin_user
       domain_admin_password = var.domain_admin_password
       admin_password        = var.local_adminpass
       # product_key           = var.productkey
       # organization_name     = var.orgname
       # run_once_command_list = var.run_once
       # auto_logon            = var.auto_logon
       # auto_logon_count      = var.auto_logon_count
       time_zone = var.vm_timezone
       # product_key           = var.productkey
       # full_name             = var.full_name
     }
     network_interface {
       ipv4_address    = each.value.vm_ip_address
       ipv4_netmask    = each.value.vm_ip_netmask
       dns_server_list = each.value.vm_dns_list
       dns_domain      = var.domain_name
     }
     ipv4_gateway = each.value.vm_ip_gateway
   }
 }
 lifecycle {
   ignore_changes = [
     domain_admin_user,
     domain_admin_password
   ]
 }
}

(I reformatted your post so I could make sure to get the blocks correct)

You must supply a full path to the attributes you want to specify, not just a pattern to match. Unfortunately with the legacy SDK there’s a bit of a nuance here, because each of the blocks is a list of one object, so you will need to give the index of each level.

I think the paths for this resource will look more like:

lifecycle {
  ignore_changes = [
    clone[0].customize[0].windows_options[0].domain_admin_user,
    clone[0].customize[0].windows_options[0].domain_admin_password,
  ]
}

I’m not sure of the specifics of the resource, but maybe you could make a more general ignored path. If nothing below one of those blocks is useful for updates you can ignore the entire thing.

Thanks for pointing out the full path is required.

Also, thanks for reformatting the post.
How did you get the formatting done?
When I pasted the text, half was formatted properly and the rest was the default font/format.
When I attempted to correct it, all of the text went to the default.

Many online systems use the same markdown for raw text, which is to wrap it in opening and closing sets of 3 backticks (```). You can also click the “Preformatted text” button in the editor.