@@ -16,6 +16,7 @@ import (
1616 "github.com/hashicorp/go-azure-sdk/resource-manager/postgresql/2024-08-01/virtualendpoints"
1717 "github.com/hashicorp/terraform-provider-azurerm/internal/locks"
1818 "github.com/hashicorp/terraform-provider-azurerm/internal/sdk"
19+ "github.com/hashicorp/terraform-provider-azurerm/internal/services/postgres/migration"
1920 "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
2021 "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation"
2122)
@@ -31,6 +32,8 @@ type PostgresqlFlexibleServerVirtualEndpointModel struct {
3132
3233var _ sdk.ResourceWithUpdate = PostgresqlFlexibleServerVirtualEndpointResource {}
3334
35+ var _ sdk.ResourceWithStateMigration = PostgresqlFlexibleServerVirtualEndpointResource {}
36+
3437func (r PostgresqlFlexibleServerVirtualEndpointResource ) ModelObject () interface {} {
3538 return & PostgresqlFlexibleServerVirtualEndpointModel {}
3639}
@@ -40,13 +43,33 @@ func (r PostgresqlFlexibleServerVirtualEndpointResource) ResourceType() string {
4043}
4144
4245func (r PostgresqlFlexibleServerVirtualEndpointResource ) IDValidationFunc () pluginsdk.SchemaValidateFunc {
43- return virtualendpoints .ValidateVirtualEndpointID
46+ return func (input interface {}, key string ) (warnings []string , errors []error ) {
47+ v , ok := input .(string )
48+ if ! ok {
49+ errors = append (errors , fmt .Errorf ("expected %q to be a string" , key ))
50+ return
51+ }
52+
53+ if _ , err := commonids .ParseCompositeResourceID (v , & virtualendpoints.VirtualEndpointId {}, & virtualendpoints.VirtualEndpointId {}); err != nil {
54+ errors = append (errors , err )
55+ }
56+ return
57+ }
4458}
4559
4660func (r PostgresqlFlexibleServerVirtualEndpointResource ) Attributes () map [string ]* pluginsdk.Schema {
4761 return map [string ]* pluginsdk.Schema {}
4862}
4963
64+ func (r PostgresqlFlexibleServerVirtualEndpointResource ) StateUpgraders () sdk.StateUpgradeData {
65+ return sdk.StateUpgradeData {
66+ SchemaVersion : 1 ,
67+ Upgraders : map [int ]pluginsdk.StateUpgrade {
68+ 0 : migration.PostgresqlFlexibleServerVirtualEndpointV0toV1 {},
69+ },
70+ }
71+ }
72+
5073func (r PostgresqlFlexibleServerVirtualEndpointResource ) Arguments () map [string ]* pluginsdk.Schema {
5174 return map [string ]* pluginsdk.Schema {
5275 "name" : {
@@ -101,28 +124,31 @@ func (r PostgresqlFlexibleServerVirtualEndpointResource) Create() sdk.ResourceFu
101124 return err
102125 }
103126
104- id := virtualendpoints .NewVirtualEndpointID (sourceServerId .SubscriptionId , sourceServerId .ResourceGroupName , sourceServerId .FlexibleServerName , virtualEndpoint .Name )
127+ sourceEndpointId := virtualendpoints .NewVirtualEndpointID (sourceServerId .SubscriptionId , sourceServerId .ResourceGroupName , sourceServerId .FlexibleServerName , virtualEndpoint .Name )
128+ replicaEndpointId := virtualendpoints .NewVirtualEndpointID (replicaServerId .SubscriptionId , replicaServerId .ResourceGroupName , replicaServerId .FlexibleServerName , virtualEndpoint .Name )
105129
106- locks .ByName (id .FlexibleServerName , postgresqlFlexibleServerResourceName )
107- defer locks .UnlockByName (id .FlexibleServerName , postgresqlFlexibleServerResourceName )
130+ locks .ByName (sourceEndpointId .FlexibleServerName , postgresqlFlexibleServerResourceName )
131+ defer locks .UnlockByName (sourceEndpointId .FlexibleServerName , postgresqlFlexibleServerResourceName )
108132
109- if replicaServerId .FlexibleServerName != id .FlexibleServerName {
133+ if replicaServerId .FlexibleServerName != replicaEndpointId .FlexibleServerName {
110134 locks .ByName (replicaServerId .FlexibleServerName , postgresqlFlexibleServerResourceName )
111135 defer locks .UnlockByName (replicaServerId .FlexibleServerName , postgresqlFlexibleServerResourceName )
112136 }
113137
114138 // This API can be a bit flaky if the same named resource is created/destroyed quickly
115139 // usually waiting a minute or two before redeploying is enough to resolve the conflict
116- if err = client .CreateThenPoll (ctx , id , virtualendpoints.VirtualEndpointResource {
140+ if err = client .CreateThenPoll (ctx , sourceEndpointId , virtualendpoints.VirtualEndpointResource {
117141 Name : & virtualEndpoint .Name ,
118142 Properties : & virtualendpoints.VirtualEndpointResourceProperties {
119143 EndpointType : pointer .To (virtualendpoints .VirtualEndpointType (virtualEndpoint .Type )),
120144 Members : & []string {replicaServerId .FlexibleServerName },
121145 },
122146 }); err != nil {
123- return fmt .Errorf ("creating %s: %+v" , id , err )
147+ return fmt .Errorf ("creating %s: %+v" , sourceEndpointId , err )
124148 }
125149
150+ id := commonids .NewCompositeResourceID (& sourceEndpointId , & replicaEndpointId )
151+
126152 metadata .SetID (id )
127153
128154 return nil
@@ -139,40 +165,55 @@ func (r PostgresqlFlexibleServerVirtualEndpointResource) Read() sdk.ResourceFunc
139165
140166 state := PostgresqlFlexibleServerVirtualEndpointModel {}
141167
142- id , err := virtualendpoints . ParseVirtualEndpointID (metadata .ResourceData .Id ())
168+ id , err := commonids . ParseCompositeResourceID (metadata .ResourceData .Id (), & virtualendpoints. VirtualEndpointId {}, & virtualendpoints. VirtualEndpointId {} )
143169 if err != nil {
144170 return err
145171 }
146172
147- resp , err := client .Get (ctx , * id )
173+ // In case of a fail-over, we need to see if the endpoint lives under the source id or the replica id
174+ failOverHasOccurred := false
175+ virtualEndpointId := * id .First
176+ resp , err := client .Get (ctx , virtualEndpointId )
148177 if err != nil {
149178 if response .WasNotFound (resp .HttpResponse ) {
150- log .Printf ("[INFO] %s does not exist - removing from state" , metadata .ResourceData .Id ())
151- return metadata .MarkAsGone (id )
179+ virtualEndpointId = * id .Second
180+ // if the endpoint doesn't exist under the source server, look for it under the replica server
181+ resp , err = client .Get (ctx , virtualEndpointId )
182+ if err != nil {
183+ if response .WasNotFound (resp .HttpResponse ) {
184+ // the endpoint was not found under the source or the replica server so it can safely be removed from state
185+ log .Printf ("[INFO] %s does not exist - removing from state" , metadata .ResourceData .Id ())
186+ return metadata .MarkAsGone (id )
187+ }
188+ return fmt .Errorf ("retrieving %s: %+v" , id , err )
189+ }
190+ failOverHasOccurred = true
191+ }
192+ // if we errored and didn't find the endpoint under the replica id, then we error here
193+ if ! failOverHasOccurred {
194+ return fmt .Errorf ("retrieving %s: %+v" , id , err )
152195 }
153- return fmt .Errorf ("retrieving %s: %+v" , id , err )
154196 }
155197
156- state .Name = id .VirtualEndpointName
198+ state .Name = virtualEndpointId .VirtualEndpointName
157199
158200 if model := resp .Model ; model != nil {
159201 if props := model .Properties ; props != nil {
160202 state .Type = string (pointer .From (props .EndpointType ))
161203
162204 if props .Members == nil || len (* props .Members ) == 0 {
163205 // if members list is nil or empty, this is an endpoint that was previously deleted
164- log .Printf ("[INFO] Postgresql Flexible Server Endpoint %q was previously deleted - removing from state" , id .ID ())
206+ log .Printf ("[INFO] Postgresql Flexible Server Endpoint %q was previously deleted - removing from state" , id .First . ID ())
165207 return metadata .MarkAsGone (id )
166208 }
167209
168- state .SourceServerId = servers .NewFlexibleServerID (id .SubscriptionId , id .ResourceGroupName , (* props .Members )[0 ]).ID ()
169-
210+ state .SourceServerId = servers .NewFlexibleServerID (id .First .SubscriptionId , id .First .ResourceGroupName , (* props .Members )[0 ]).ID ()
170211 // Model.Properties.Members can contain 1 member which means source and replica are identical, or it can contain
171212 // 2 members when source and replica are different => [source_server_id, replication_server_name]
172- replicaServerId := servers . NewFlexibleServerID ( id . SubscriptionId , id . ResourceGroupName , ( * props . Members )[ 0 ]). ID ()
213+ replicaServerId := state . SourceServerId
173214
174215 if len (* props .Members ) == 2 {
175- replicaServer , err := lookupFlexibleServerByName (ctx , flexibleServerClient , id , (* props .Members )[1 ], state .SourceServerId )
216+ replicaServer , err := lookupFlexibleServerByName (ctx , flexibleServerClient , virtualEndpointId , (* props .Members )[1 ], state .SourceServerId )
176217 if err != nil {
177218 return err
178219 }
@@ -191,6 +232,11 @@ func (r PostgresqlFlexibleServerVirtualEndpointResource) Read() sdk.ResourceFunc
191232 }
192233 }
193234
235+ // if a fail-over has occurred, the source/replica ids have swapped so we'll have to swap them back in Terraform to prevent a diff
236+ if failOverHasOccurred {
237+ state .SourceServerId , state .ReplicaServerId = state .ReplicaServerId , state .SourceServerId
238+ }
239+
194240 return metadata .Encode (& state )
195241 },
196242 }
@@ -202,15 +248,39 @@ func (r PostgresqlFlexibleServerVirtualEndpointResource) Delete() sdk.ResourceFu
202248 Func : func (ctx context.Context , metadata sdk.ResourceMetaData ) error {
203249 client := metadata .Client .Postgres .VirtualEndpointClient
204250
205- id , err := virtualendpoints . ParseVirtualEndpointID (metadata .ResourceData .Id ())
251+ id , err := commonids . ParseCompositeResourceID (metadata .ResourceData .Id (), & virtualendpoints. VirtualEndpointId {}, & virtualendpoints. VirtualEndpointId {} )
206252 if err != nil {
207253 return err
208254 }
209255
210- locks .ByName (id .FlexibleServerName , postgresqlFlexibleServerResourceName )
211- defer locks .UnlockByName (id .FlexibleServerName , postgresqlFlexibleServerResourceName )
256+ // In case of a fail-over, we need to see if the endpoint lives under the source id or the replica id before deleting
257+ failOverHasOccurred := false
258+ virtualEndpointId := * id .First
259+ resp , err := client .Get (ctx , virtualEndpointId )
260+ if err != nil {
261+ if response .WasNotFound (resp .HttpResponse ) {
262+ virtualEndpointId = * id .Second
263+ // if the endpoint doesn't exist under the source server, look for it under the replica server
264+ resp , err = client .Get (ctx , virtualEndpointId )
265+ if err != nil {
266+ if response .WasNotFound (resp .HttpResponse ) {
267+ // the endpoint was not found under the source or the replica server so we can exit here
268+ return nil
269+ }
270+ return fmt .Errorf ("retrieving %s: %+v" , id , err )
271+ }
272+ failOverHasOccurred = true
273+ }
274+ // if we errored and didn't find the endpoint under the replica id, then we error here
275+ if ! failOverHasOccurred {
276+ return fmt .Errorf ("retrieving %s: %+v" , id , err )
277+ }
278+ }
212279
213- if err := client .DeleteThenPoll (ctx , * id ); err != nil {
280+ locks .ByName (virtualEndpointId .FlexibleServerName , postgresqlFlexibleServerResourceName )
281+ defer locks .UnlockByName (virtualEndpointId .FlexibleServerName , postgresqlFlexibleServerResourceName )
282+
283+ if err := client .DeleteThenPoll (ctx , virtualEndpointId ); err != nil {
214284 return fmt .Errorf ("deleting %s: %+v" , * id , err )
215285 }
216286
@@ -221,7 +291,7 @@ func (r PostgresqlFlexibleServerVirtualEndpointResource) Delete() sdk.ResourceFu
221291
222292func (r PostgresqlFlexibleServerVirtualEndpointResource ) Update () sdk.ResourceFunc {
223293 return sdk.ResourceFunc {
224- Timeout : 5 * time .Minute ,
294+ Timeout : 10 * time .Minute ,
225295 Func : func (ctx context.Context , metadata sdk.ResourceMetaData ) error {
226296 var virtualEndpoint PostgresqlFlexibleServerVirtualEndpointModel
227297 client := metadata .Client .Postgres .VirtualEndpointClient
@@ -230,25 +300,42 @@ func (r PostgresqlFlexibleServerVirtualEndpointResource) Update() sdk.ResourceFu
230300 return err
231301 }
232302
233- id , err := virtualendpoints . ParseVirtualEndpointID (metadata .ResourceData .Id ())
303+ id , err := commonids . ParseCompositeResourceID (metadata .ResourceData .Id (), & virtualendpoints. VirtualEndpointId {}, & virtualendpoints. VirtualEndpointId {} )
234304 if err != nil {
235305 return err
236306 }
237307
308+ // attempt to retrieve the endpoint and see if a fail-over has occurred, if so error as we shouldn't update to a different replica server with the `source_server_id` and the `replica_server_id` being swapped
309+ virtualEndpointId := * id .First
310+ resp , err := client .Get (ctx , virtualEndpointId )
311+ if err != nil {
312+ if response .WasNotFound (resp .HttpResponse ) {
313+ virtualEndpointId = virtualendpoints .NewVirtualEndpointID (id .Second .SubscriptionId , id .Second .ResourceGroupName , id .Second .FlexibleServerName , id .Second .VirtualEndpointName )
314+ // if the endpoint doesn't exist under the source server, look for it under the replica server
315+ _ , err = client .Get (ctx , virtualEndpointId )
316+ if err != nil {
317+ return fmt .Errorf ("retrieving %s: %+v" , virtualEndpointId , err )
318+ }
319+ return fmt .Errorf ("a fail-over has occurred and the `source_server_id` in the config is no longer the SourceServerId for the virtual endpoint. If you wish to change the `replica_server_id`, remove this resource from state and reimport it back in with the `replica_server_id` and `source_server_id` swapped" )
320+ }
321+ return fmt .Errorf ("retrieving %s: %+v" , virtualEndpointId , err )
322+ }
323+
238324 replicaServerId , err := servers .ParseFlexibleServerID (virtualEndpoint .ReplicaServerId )
239325 if err != nil {
240326 return err
241327 }
242328
243- locks .ByName (id .FlexibleServerName , postgresqlFlexibleServerResourceName )
244- defer locks .UnlockByName (id .FlexibleServerName , postgresqlFlexibleServerResourceName )
329+ locks .ByName (id .First . FlexibleServerName , postgresqlFlexibleServerResourceName )
330+ defer locks .UnlockByName (id .First . FlexibleServerName , postgresqlFlexibleServerResourceName )
245331
246- if replicaServerId .FlexibleServerName != id .FlexibleServerName {
332+ if replicaServerId .FlexibleServerName != id .First . FlexibleServerName {
247333 locks .ByName (replicaServerId .FlexibleServerName , postgresqlFlexibleServerResourceName )
248334 defer locks .UnlockByName (replicaServerId .FlexibleServerName , postgresqlFlexibleServerResourceName )
249335 }
250336
251- if err := client .UpdateThenPoll (ctx , * id , virtualendpoints.VirtualEndpointResourceForPatch {
337+ endpointId := virtualendpoints .NewVirtualEndpointID (id .First .SubscriptionId , id .First .ResourceGroupName , id .First .FlexibleServerName , virtualEndpoint .Name )
338+ if err := client .UpdateThenPoll (ctx , endpointId , virtualendpoints.VirtualEndpointResourceForPatch {
252339 Properties : & virtualendpoints.VirtualEndpointResourceProperties {
253340 EndpointType : pointer .To (virtualendpoints .VirtualEndpointType (virtualEndpoint .Type )),
254341 Members : pointer .To ([]string {replicaServerId .FlexibleServerName }),
@@ -257,14 +344,19 @@ func (r PostgresqlFlexibleServerVirtualEndpointResource) Update() sdk.ResourceFu
257344 return fmt .Errorf ("updating %q: %+v" , id , err )
258345 }
259346
347+ // the id has changed and needs to be updated
348+ replicaEndpointId := virtualendpoints .NewVirtualEndpointID (replicaServerId .SubscriptionId , replicaServerId .ResourceGroupName , replicaServerId .FlexibleServerName , virtualEndpoint .Name )
349+ endPointId := commonids .NewCompositeResourceID (& virtualEndpointId , & replicaEndpointId )
350+ metadata .SetID (endPointId )
351+
260352 return nil
261353 },
262354 }
263355}
264356
265357// The flexible endpoint API does not store the location/rg information on replicas it only stores the name.
266358// This lookup is safe because replicas for a given source server are *not* allowed to have identical names
267- func lookupFlexibleServerByName (ctx context.Context , flexibleServerClient * servers.ServersClient , virtualEndpointId * virtualendpoints.VirtualEndpointId , replicaServerName string , sourceServerId string ) (* servers.Server , error ) {
359+ func lookupFlexibleServerByName (ctx context.Context , flexibleServerClient * servers.ServersClient , virtualEndpointId virtualendpoints.VirtualEndpointId , replicaServerName string , sourceServerId string ) (* servers.Server , error ) {
268360 postgresServers , err := flexibleServerClient .ListCompleteMatchingPredicate (ctx , commonids .NewSubscriptionID (virtualEndpointId .SubscriptionId ), servers.ServerOperationPredicate {
269361 Name : & replicaServerName ,
270362 })
0 commit comments