Conditional RequireReplace if another resource is being replaced

Hi there, I’m working on the Megaport Terraform Provider and have an interesting issue I’m trying to solve. We have products that can be connected together with a virtual cross connect (VXC) which is a layer 2 point to point connection between two ends.

The issue we’re running into is that when a product connected to the VXC is deleted, it also deletes the VXC, which causes an error with the provider when it tries to update the VXC B or A end to the newly recreated product. Unfortunately we can’t just always replace the VXC if an end product changes because there are some situations where it makes more sense for a user to move the end to another product rather than rebuilding it; for example if one end is connected to a cloud, the user would have to accept a new connection in their console.

The example below shows what would trigger the issue. The change on port_1 causes the port to be rebuilt, and creates a plan to update the VXC which no longer exists.

provider "megaport" {
  environment           = "staging"
  access_key            = "access_key"
  secret_key            = "secret_Key"
  accept_purchase_terms = true
}

data "megaport_location" "loc" {
  name = "NextDC B1"
}

resource "megaport_port" "port_1" {
  product_name           = "Megaport Port A-End"
  port_speed             = 1000 // <= when this is updated the port is rebuilt 
  location_id            = data.megaport_location.loc.id
  contract_term_months   = 1
  marketplace_visibility = false
  cost_centre            = "Megaport Port A-End"
}

resource "megaport_port" "port_2" {
  product_name           = "Megaport Port B-End"
  port_speed             = 1000
  location_id            = data.megaport_location.loc.id
  contract_term_months   = 1
  marketplace_visibility = false
  cost_centre            = "Megaport Port B-End"
}

resource "megaport_vxc" "vxc" {
  product_name         = "Megaport VXC"
  rate_limit           = 1000
  contract_term_months = 1

  a_end = {
    requested_product_uid = megaport_port.port_1.product_uid
  }

  b_end = {
    requested_product_uid = megaport_port.port_2.product_uid
  }
}

I’ve looked into RequiresReplaceIf() but it seems like there isn’t access to state outside of the resource to detect that and generate an appropriate plan where the VXC is replaced.

Hi @mega-alex,

In Terraform, individual resource instances are completely independent, and cannot access the state of other resources. The provider internally could maintain some shared state about the plan progress, but that adds significant complication and would only be advised as a last resort.

Typically Terraform requires users to declare any relationships between resources within the configuration, and those references would be used to trigger replacement. I don’t know the details of your resources, but for example if megaport_port.port_1.product_uid were to change or become unknown when planning a change to port_1, and requested_product_uid forced replacement of the containing resources, then you would get the plan you are looking for.

If there is no suitable attribute to reference (though I would suggest creating one if at all possible for better discoverability), then you can use replace_triggered_by to force replacement when a change is detected.

Hi, thanks for the reply!

The complicated shared internal state in the provider is definitely what I’m trying to avoid, I started to work on that and figured I’d ask here before I got too deep. The issue I have is I can’t make the attribute requested_product_uid be RequireReplace() with a plan modifier because there are several cases where it makes sense to do an update instead of replacing the VXC when that field changes.

The other way I was thinking this could work would be instead of the plan doing:

  1. Delete port_1 (which also deletes vxc because it is attached to port_1
  2. Recreate port_1 with new speed
  3. Update VXC (now fails because vxc was deleted)

We could:

  1. Create new port_1
  2. Update vxc to move the requested_product_uid to the new port_1
  3. Delete old port_1

This would solve the issue of the VXC being deleted when the initial port_1 is deleted, because it would have been moved to the new port_1 before that. I’m not sure if there is a way to force that order in the provider though, but it does seem like create_before_destroy as a lifecycle argument might get us most of the way there though.

Yes, if the resources work with the second order you outlined, that is exactly what create_before_destroy is intended to address. There’s no way for a provider to enforce that order, since the user may need to take other relationships from the configuration into account, so it falls to documentation to ensure the resource is configured correctly.

The first order is the default and should work, but you do need to ensure that the VXC is planned correctly via whatever attributes it can read from the port. This requires being able to detect if the proposed change is going to cause replacement or not.

Again there are probably more details in the resources than I’m aware, but this looks similar to what I’ve referred to as a “registration” pattern, where a resource registers some reference to a dependency that must remain valid. That type of situation almost always requires create_before_destroy to work correctly.

Appreciate the help. It looks like a combination of documentation, and a plan modifier like this

stringplanmodifier.RequiresReplaceIf(func(ctx context.Context, sr planmodifier.StringRequest, rrifr *stringplanmodifier.RequiresReplaceIfFuncResponse) {
								if sr.PlanValue.IsUnknown() {
									rrifr.RequiresReplace = true
								}
							}, "", ""),

should get us most of the way there. Only issue is this requires the user to create the new target product first so the plan value is known, but it does eliminate the error when the product is replaced.