From b530f48d9353eca1774652a5e730e48c6fb68c74 Mon Sep 17 00:00:00 2001
From: Chris Marget <cmarget@juniper.net>
Date: Wed, 18 Dec 2024 17:25:45 -0500
Subject: [PATCH] update state existence detection in batch id plan modifiers

---
 .../primitives/bgp_peering_generic_sytem.go     | 17 +++++++++++++++--
 .../primitives/bgp_peering_ip_endpoint.go       | 13 ++++++++++---
 .../primitives/dynamic_bgp_peering.go           | 13 ++++++++++---
 .../primitives/ip_link.go                       | 13 ++++++++++---
 .../primitives/virtual_network_single.go        | 13 ++++++++++---
 5 files changed, 55 insertions(+), 14 deletions(-)

diff --git a/apstra/blueprint/connectivity_templates/primitives/bgp_peering_generic_sytem.go b/apstra/blueprint/connectivity_templates/primitives/bgp_peering_generic_sytem.go
index 1229fb59..8a333a2c 100644
--- a/apstra/blueprint/connectivity_templates/primitives/bgp_peering_generic_sytem.go
+++ b/apstra/blueprint/connectivity_templates/primitives/bgp_peering_generic_sytem.go
@@ -361,15 +361,28 @@ func (o bgpPeeringGenericSystemBatchIdPlanModifier) MarkdownDescription(ctx cont
 }
 
 func (o bgpPeeringGenericSystemBatchIdPlanModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
-	var plan BgpPeeringGenericSystem
+	var plan, state BgpPeeringGenericSystem
+
+	// unpacking the parent object's plan should always work
 	resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, req.Path.ParentPath(), &plan)...)
 	if resp.Diagnostics.HasError() {
 		return
 	}
 
-	// do we have any children?
+	// attempting to unpack the parent object's state indicates whether state *exists*
+	d := req.State.GetAttribute(ctx, req.Path.ParentPath(), &state)
+	stateDoesNotExist := d.HasError()
+
+	// do we have zero children?
 	if len(plan.RoutingPolicies.Elements()) == 0 {
+		resp.PlanValue = types.StringNull() // with no children the batch id should be null
+		return
+	}
+
+	// are we a new object?
+	if stateDoesNotExist {
 		resp.PlanValue = types.StringUnknown() // we are a new object. the batch id is not knowable
+		return
 	}
 
 	// we're not new, and we have children. use the old value
diff --git a/apstra/blueprint/connectivity_templates/primitives/bgp_peering_ip_endpoint.go b/apstra/blueprint/connectivity_templates/primitives/bgp_peering_ip_endpoint.go
index 6003b264..9eb2f93d 100644
--- a/apstra/blueprint/connectivity_templates/primitives/bgp_peering_ip_endpoint.go
+++ b/apstra/blueprint/connectivity_templates/primitives/bgp_peering_ip_endpoint.go
@@ -326,21 +326,28 @@ func (o bgpPeeringIpEndpointBatchIdPlanModifier) MarkdownDescription(ctx context
 }
 
 func (o bgpPeeringIpEndpointBatchIdPlanModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
-	var plan BgpPeeringIpEndpoint
+	var plan, state BgpPeeringIpEndpoint
+
+	// unpacking the parent object's plan should always work
 	resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, req.Path.ParentPath(), &plan)...)
 	if resp.Diagnostics.HasError() {
 		return
 	}
 
-	// do we have any children?
+	// attempting to unpack the parent object's state indicates whether state *exists*
+	d := req.State.GetAttribute(ctx, req.Path.ParentPath(), &state)
+	stateDoesNotExist := d.HasError()
+
+	// do we have zero children?
 	if len(plan.RoutingPolicies.Elements()) == 0 {
 		resp.PlanValue = types.StringNull() // with no children the batch id should be null
 		return
 	}
 
 	// are we a new object?
-	if plan.Id.IsUnknown() {
+	if stateDoesNotExist {
 		resp.PlanValue = types.StringUnknown() // we are a new object. the batch id is not knowable
+		return
 	}
 
 	// we're not new, and we have children. use the old value
diff --git a/apstra/blueprint/connectivity_templates/primitives/dynamic_bgp_peering.go b/apstra/blueprint/connectivity_templates/primitives/dynamic_bgp_peering.go
index 8cda93a3..6001e865 100644
--- a/apstra/blueprint/connectivity_templates/primitives/dynamic_bgp_peering.go
+++ b/apstra/blueprint/connectivity_templates/primitives/dynamic_bgp_peering.go
@@ -337,21 +337,28 @@ func (o dynamicBgpPeeringBatchIdPlanModifier) MarkdownDescription(ctx context.Co
 }
 
 func (o dynamicBgpPeeringBatchIdPlanModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
-	var plan DynamicBgpPeering
+	var plan, state DynamicBgpPeering
+
+	// unpacking the parent object's plan should always work
 	resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, req.Path.ParentPath(), &plan)...)
 	if resp.Diagnostics.HasError() {
 		return
 	}
 
-	// do we have any children?
+	// attempting to unpack the parent object's state indicates whether state *exists*
+	d := req.State.GetAttribute(ctx, req.Path.ParentPath(), &state)
+	stateDoesNotExist := d.HasError()
+
+	// do we have zero children?
 	if len(plan.RoutingPolicies.Elements()) == 0 {
 		resp.PlanValue = types.StringNull() // with no children the batch id should be null
 		return
 	}
 
 	// are we a new object?
-	if plan.Id.IsUnknown() {
+	if stateDoesNotExist {
 		resp.PlanValue = types.StringUnknown() // we are a new object. the batch id is not knowable
+		return
 	}
 
 	// we're not new, and we have children. use the old value
diff --git a/apstra/blueprint/connectivity_templates/primitives/ip_link.go b/apstra/blueprint/connectivity_templates/primitives/ip_link.go
index c9ffd202..18696d42 100644
--- a/apstra/blueprint/connectivity_templates/primitives/ip_link.go
+++ b/apstra/blueprint/connectivity_templates/primitives/ip_link.go
@@ -335,13 +335,19 @@ func (o ipLinkBatchIdPlanModifier) MarkdownDescription(ctx context.Context) stri
 }
 
 func (o ipLinkBatchIdPlanModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
-	var plan IpLink
+	var plan, state IpLink
+
+	// unpacking the parent object's plan should always work
 	resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, req.Path.ParentPath(), &plan)...)
 	if resp.Diagnostics.HasError() {
 		return
 	}
 
-	// do we have any children?
+	// attempting to unpack the parent object's state indicates whether state *exists*
+	d := req.State.GetAttribute(ctx, req.Path.ParentPath(), &state)
+	stateDoesNotExist := d.HasError()
+
+	// do we have zero children?
 	if len(plan.BgpPeeringGenericSystems.Elements())+
 		len(plan.BgpPeeringIpEndpoints.Elements())+
 		len(plan.DynamicBgpPeerings.Elements())+
@@ -351,8 +357,9 @@ func (o ipLinkBatchIdPlanModifier) PlanModifyString(ctx context.Context, req pla
 	}
 
 	// are we a new object?
-	if plan.Id.IsUnknown() {
+	if stateDoesNotExist {
 		resp.PlanValue = types.StringUnknown() // we are a new object. the batch id is not knowable
+		return
 	}
 
 	// we're not new, and we have children. use the old value
diff --git a/apstra/blueprint/connectivity_templates/primitives/virtual_network_single.go b/apstra/blueprint/connectivity_templates/primitives/virtual_network_single.go
index db7af973..6bf93d8a 100644
--- a/apstra/blueprint/connectivity_templates/primitives/virtual_network_single.go
+++ b/apstra/blueprint/connectivity_templates/primitives/virtual_network_single.go
@@ -208,13 +208,19 @@ func (o virtualNetworkSingleBatchIdPlanModifier) MarkdownDescription(ctx context
 }
 
 func (o virtualNetworkSingleBatchIdPlanModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
-	var plan VirtualNetworkSingle
+	var plan, state VirtualNetworkSingle
+
+	// unpacking the parent object's plan should always work
 	resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, req.Path.ParentPath(), &plan)...)
 	if resp.Diagnostics.HasError() {
 		return
 	}
 
-	// do we have any children?
+	// attempting to unpack the parent object's state indicates whether state *exists*
+	d := req.State.GetAttribute(ctx, req.Path.ParentPath(), &state)
+	stateDoesNotExist := d.HasError()
+
+	// do we have zero children?
 	if len(plan.BgpPeeringGenericSystems.Elements())+
 		len(plan.StaticRoutes.Elements()) == 0 {
 		resp.PlanValue = types.StringNull() // with no children the batch id should be null
@@ -222,8 +228,9 @@ func (o virtualNetworkSingleBatchIdPlanModifier) PlanModifyString(ctx context.Co
 	}
 
 	// are we a new object?
-	if plan.Id.IsUnknown() {
+	if stateDoesNotExist {
 		resp.PlanValue = types.StringUnknown() // we are a new object. the batch id is not knowable
+		return
 	}
 
 	// we're not new, and we have children. use the old value