Skip to content

Commit 0454745

Browse files
bubbletroublesBubbleTroubles
andauthored
azurerm_express_route_port - fix bug where Terraform Apply removes managed identity and update supported ciphers. (hashicorp#30240)
Co-authored-by: BubbleTroubles <bubbletroubles@gmail.com>
1 parent e93c6d8 commit 0454745

File tree

3 files changed

+114
-20
lines changed

3 files changed

+114
-20
lines changed

internal/services/network/express_route_port_resource.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package network
66
import (
77
"fmt"
88
"log"
9+
"strings"
910
"time"
1011

1112
"github.com/hashicorp/go-azure-helpers/lang/pointer"
@@ -42,19 +43,12 @@ var expressRoutePortSchema = &pluginsdk.Schema{
4243
"macsec_cipher": {
4344
Type: pluginsdk.TypeString,
4445
Optional: true,
45-
46-
// TODO: The following hardcode can be replaced by SDK types once following is merged:
47-
// https://github.com/Azure/azure-rest-api-specs/pull/12329
48-
Default: "GcmAes128",
49-
// Default: string(expressrouteports.GcmAes128),
50-
51-
// TODO: The following hardcode can be replaced by SDK types once following is merged:
52-
// https://github.com/Azure/azure-rest-api-specs/pull/12329
46+
Default: string(expressrouteports.ExpressRouteLinkMacSecCipherGcmAesOneTwoEight),
5347
ValidateFunc: validation.StringInSlice([]string{
54-
"GcmAes128",
55-
"GcmAes256",
56-
// string(expressrouteports.GcmAes128),
57-
// string(expressrouteports.GcmAes256),
48+
string(expressrouteports.ExpressRouteLinkMacSecCipherGcmAesOneTwoEight),
49+
string(expressrouteports.ExpressRouteLinkMacSecCipherGcmAesTwoFiveSix),
50+
string(expressrouteports.ExpressRouteLinkMacSecCipherGcmAesXpnOneTwoEight),
51+
string(expressrouteports.ExpressRouteLinkMacSecCipherGcmAesXpnTwoFiveSix),
5852
}, false),
5953
},
6054
"macsec_ckn_keyvault_secret_id": {
@@ -274,6 +268,24 @@ func resourceArmExpressRoutePortUpdate(d *pluginsdk.ResourceData, meta interface
274268

275269
payload := existing.Model
276270

271+
// Normalize the identity type - Azure API returns lowercase (e.g. "userAssigned")
272+
// but the SDK MarshalJSON expects exact case match (e.g. "UserAssigned")
273+
// Without normalization, MarshalJSON defaults to "None" and removes the identity
274+
if payload.Identity != nil {
275+
// Use string comparison to normalize the type
276+
switch strings.ToLower(string(payload.Identity.Type)) {
277+
case "systemassigned":
278+
payload.Identity.Type = identity.TypeSystemAssigned
279+
case "userassigned":
280+
payload.Identity.Type = identity.TypeUserAssigned
281+
case "systemassigned, userassigned", "systemassigned,userassigned":
282+
payload.Identity.Type = identity.TypeSystemAssignedUserAssigned
283+
case "none":
284+
payload.Identity.Type = identity.TypeNone
285+
}
286+
log.Printf("[DEBUG] Normalized identity type from %q to %q", existing.Model.Identity.Type, payload.Identity.Type)
287+
}
288+
277289
if d.HasChange("identity") {
278290
expandedIdentity, err := identity.ExpandSystemAndUserAssignedMap(d.Get("identity").([]interface{}))
279291
if err != nil {
@@ -300,8 +312,6 @@ func resourceArmExpressRoutePortUpdate(d *pluginsdk.ResourceData, meta interface
300312
locks.ByID(id.ID())
301313
defer locks.UnlockByID(id.ID())
302314

303-
payload.Properties.Links = expandExpressRoutePortLinks(d.Get("link1").([]interface{}), d.Get("link2").([]interface{}))
304-
305315
if err := client.CreateOrUpdateThenPoll(ctx, *id, *payload); err != nil {
306316
return fmt.Errorf("updating %s: %+v", id, err)
307317
}

internal/services/network/express_route_port_resource_test.go

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,26 @@ func TestAccExpressRoutePort_userAssignedIdentity(t *testing.T) {
9494
Config: r.userAssignedIdentity(data),
9595
Check: acceptance.ComposeTestCheckFunc(
9696
check.That(data.ResourceName).ExistsInAzure(r),
97+
check.That(data.ResourceName).Key("identity.0.type").HasValue("UserAssigned"),
98+
check.That(data.ResourceName).Key("identity.0.identity_ids.#").HasValue("1"),
99+
),
100+
},
101+
{
102+
Config: r.userAssignedIdentityWithTags(data, "tag1"),
103+
Check: acceptance.ComposeTestCheckFunc(
104+
check.That(data.ResourceName).ExistsInAzure(r),
105+
check.That(data.ResourceName).Key("identity.0.type").HasValue("UserAssigned"),
106+
check.That(data.ResourceName).Key("identity.0.identity_ids.#").HasValue("1"),
107+
check.That(data.ResourceName).Key("tags.environment").HasValue("tag1"),
108+
),
109+
},
110+
{
111+
Config: r.userAssignedIdentityWithTags(data, "tag2"),
112+
Check: acceptance.ComposeTestCheckFunc(
113+
check.That(data.ResourceName).ExistsInAzure(r),
114+
check.That(data.ResourceName).Key("identity.0.type").HasValue("UserAssigned"),
115+
check.That(data.ResourceName).Key("identity.0.identity_ids.#").HasValue("1"),
116+
check.That(data.ResourceName).Key("tags.environment").HasValue("tag2"),
97117
),
98118
},
99119
data.ImportStep(),
@@ -115,6 +135,31 @@ func TestAccExpressRoutePort_linkCipher(t *testing.T) {
115135
})
116136
}
117137

138+
func TestAccExpressRoutePort_identityRemoval(t *testing.T) {
139+
data := acceptance.BuildTestData(t, "azurerm_express_route_port", "test")
140+
r := ExpressRoutePortResource{}
141+
142+
data.ResourceTest(t, r, []acceptance.TestStep{
143+
{
144+
Config: r.linkCipher(data),
145+
Check: acceptance.ComposeTestCheckFunc(
146+
check.That(data.ResourceName).ExistsInAzure(r),
147+
check.That(data.ResourceName).Key("identity.0.type").HasValue("UserAssigned"),
148+
check.That(data.ResourceName).Key("identity.0.identity_ids.#").HasValue("1"),
149+
check.That(data.ResourceName).Key("link1.0.macsec_cipher").HasValue("GcmAesXpn256"),
150+
),
151+
},
152+
{
153+
Config: r.basic(data),
154+
Check: acceptance.ComposeTestCheckFunc(
155+
check.That(data.ResourceName).ExistsInAzure(r),
156+
check.That(data.ResourceName).Key("identity.#").HasValue("0"),
157+
),
158+
},
159+
data.ImportStep(),
160+
})
161+
}
162+
118163
func (r ExpressRoutePortResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
119164
client := clients.Network.ExpressRoutePorts
120165

@@ -137,18 +182,18 @@ func (r ExpressRoutePortResource) basic(data acceptance.TestData) string {
137182
%s
138183
139184
resource "azurerm_express_route_port" "test" {
140-
name = "acctestERP-%d"
185+
name = "acctestERP-%[2]d"
141186
resource_group_name = azurerm_resource_group.test.name
142187
location = azurerm_resource_group.test.location
143-
peering_location = "Equinix-London-LDS"
188+
peering_location = "Airtel-Chennai2-CLS"
144189
bandwidth_in_gbps = 10
145190
encapsulation = "Dot1Q"
146191
billing_type = "MeteredData"
147192
tags = {
148193
ENV = "Test"
149194
}
150195
}
151-
`, template, data.RandomInteger)
196+
`, template, data.RandomIntOfLength(8))
152197
}
153198

154199
func (r ExpressRoutePortResource) adminState(data acceptance.TestData) string {
@@ -201,7 +246,7 @@ resource "azurerm_express_route_port" "test" {
201246
name = "acctestERP-%[2]d"
202247
resource_group_name = azurerm_resource_group.test.name
203248
location = azurerm_resource_group.test.location
204-
peering_location = "Area51-ERDirect"
249+
peering_location = "Airtel-Chennai2-CLS"
205250
bandwidth_in_gbps = 10
206251
encapsulation = "Dot1Q"
207252
identity {
@@ -276,7 +321,7 @@ resource "azurerm_express_route_port" "test" {
276321
identity_ids = [azurerm_user_assigned_identity.test.id]
277322
}
278323
link1 {
279-
macsec_cipher = "GcmAes256"
324+
macsec_cipher = "GcmAesXpn256"
280325
macsec_ckn_keyvault_secret_id = azurerm_key_vault_secret.ckn.id
281326
macsec_cak_keyvault_secret_id = azurerm_key_vault_secret.cak.id
282327
macsec_sci_enabled = true
@@ -290,6 +335,45 @@ resource "azurerm_express_route_port" "test" {
290335
`, template, data.RandomIntOfLength(8))
291336
}
292337

338+
func (r ExpressRoutePortResource) userAssignedIdentityWithTags(data acceptance.TestData, tagValue string) string {
339+
template := r.template(data)
340+
return fmt.Sprintf(`
341+
%s
342+
343+
resource "azurerm_user_assigned_identity" "test" {
344+
location = azurerm_resource_group.test.location
345+
name = "acctestUAI-%[2]d"
346+
resource_group_name = azurerm_resource_group.test.name
347+
}
348+
349+
resource "azurerm_express_route_port" "test" {
350+
name = "acctestERP-%[2]d"
351+
resource_group_name = azurerm_resource_group.test.name
352+
location = azurerm_resource_group.test.location
353+
peering_location = "Airtel-Chennai2-CLS"
354+
bandwidth_in_gbps = 10
355+
encapsulation = "Dot1Q"
356+
357+
identity {
358+
type = "UserAssigned"
359+
identity_ids = [azurerm_user_assigned_identity.test.id]
360+
}
361+
362+
link1 {
363+
admin_enabled = false
364+
}
365+
366+
link2 {
367+
admin_enabled = false
368+
}
369+
370+
tags = {
371+
environment = "%[3]s"
372+
}
373+
}
374+
`, template, data.RandomIntOfLength(8), tagValue)
375+
}
376+
293377
func (r ExpressRoutePortResource) template(data acceptance.TestData) string {
294378
return fmt.Sprintf(`
295379
provider "azurerm" {

website/docs/r/express_route_port.html.markdown

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ A `link` block supports the following:
7070

7171
* `admin_enabled` - (Optional) Whether enable administration state on the Express Route Port Link? Defaults to `false`.
7272

73-
* `macsec_cipher` - (Optional) The MACSec cipher used for this Express Route Port Link. Possible values are `GcmAes128` and `GcmAes256`. Defaults to `GcmAes128`.
73+
* `macsec_cipher` - (Optional) The MACSec cipher used for this Express Route Port Link. Possible values are `GcmAes128`, `GcmAes256`, `GcmAesXpn128` and `GcmAesXpn256`. Defaults to `GcmAes128`.
7474

7575
* `macsec_ckn_keyvault_secret_id` - (Optional) The ID of the Key Vault Secret that contains the MACSec CKN key for this Express Route Port Link.
7676

0 commit comments

Comments
 (0)