Skip to content

Commit 5af7991

Browse files
Netapp all squash (#15436) (#11023)
[upstream:af16540527e583d40ffdefa798fc739376021c3b] Signed-off-by: Modular Magician <[email protected]>
1 parent af9c15a commit 5af7991

File tree

4 files changed

+844
-876
lines changed

4 files changed

+844
-876
lines changed

.changelog/15436.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
netapp: Modified `squashMode` param for netapp_volumes resources. Added custom logic to handle the API behaviour
3+
```

google-beta/services/netapp/resource_netapp_volume.go

Lines changed: 148 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,47 @@ func ProjectIDDiffSuppress(_, old, new string, _ *schema.ResourceData) bool {
7777
return suffix1 == suffix2
7878
}
7979

80+
func suppressSquashModeDiff(k, old, new string, d *schema.ResourceData) bool {
81+
// k: The key of the field, e.g., "export_policy.0.rules.1.squash_mode"
82+
// old: The value in the state (what the API returned on last read)
83+
// new: The value in the configuration (what the user set)
84+
// d: The ResourceData for the entire resource
85+
86+
// 1. Only suppress if the user did NOT set squash_mode in the config.
87+
// According to the requirements, a "classic rule" is identified by the ABSENCE of squash_mode.
88+
if new == "ALL_SQUASH" {
89+
// If 'new' is not an empty string, the user has explicitly provided a value
90+
// for squash_mode in the Terraform configuration. In this scenario, any
91+
// difference between the API's value ('old') and the configured value ('new')
92+
// is a real change and should NOT be suppressed.
93+
return false
94+
}
95+
96+
if new == "" && old != "ALL_SQUASH" {
97+
return true
98+
}
99+
100+
// 2. The user did not specify squash_mode in the configuration ('new' is empty).
101+
// Now, we suppress the diff if the API/state value ('old') is one of the
102+
// specific values that should be treated as equivalent to an unset field.
103+
// These values are "NO_ROOT_SQUASH", "ROOT_SQUASH".
104+
switch old {
105+
case "NO_ROOT_SQUASH", "ROOT_SQUASH", "":
106+
// The API returned one of the values that we consider equivalent to the field
107+
// being unconfigured by the user. Since the user also didn't configure it,
108+
// we should suppress this diff.
109+
return true
110+
default:
111+
// If 'old' is not one of the values to be suppressed (and 'new' is empty),
112+
// we do not suppress the diff. This could happen if, for instance, the API
113+
// returned an unexpected value or if 'old' is also empty.
114+
return false
115+
}
116+
// Note: The previous logic involving parsing 'k' and checking 'has_root_access'
117+
// has been removed because squash_mode is independent of has_root_access,
118+
// and a "classic rule" is defined by the absence of squash_mode itself.
119+
}
120+
80121
var (
81122
_ = bytes.Clone
82123
_ = context.WithCancel
@@ -361,13 +402,14 @@ the parent Volume's 'capacity_gib'.`,
361402
"anon_uid": {
362403
Type: schema.TypeInt,
363404
Optional: true,
364-
Description: `An integer representing the anonymous user ID. Range is 0 to 4294967295. Required when 'squash_mode' is 'ROOT_SQUASH' or 'ALL_SQUASH'.`,
405+
Description: `An integer representing the anonymous user ID. Range is 0 to 4294967295. Required when 'squash_mode' is 'ALL_SQUASH'.`,
365406
},
366407
"has_root_access": {
367-
Type: schema.TypeString,
368-
Computed: true,
369-
Optional: true,
370-
Description: `If enabled, the root user (UID = 0) of the specified clients doesn't get mapped to nobody (UID = 65534). This is also known as no_root_squash.`,
408+
Type: schema.TypeString,
409+
Computed: true,
410+
Optional: true,
411+
Description: `If enabled, the root user (UID = 0) of the specified clients doesn't get mapped to nobody (UID = 65534). This is also known as no_root_squash.
412+
It's overwritten by the squash_mode parameter. Use either squash_mode or has_root_access.`,
371413
},
372414
"kerberos5_read_only": {
373415
Type: schema.TypeBool,
@@ -410,11 +452,12 @@ the parent Volume's 'capacity_gib'.`,
410452
Description: `Enable to apply the export rule to NFSV4.1 clients.`,
411453
},
412454
"squash_mode": {
413-
Type: schema.TypeString,
414-
Computed: true,
415-
Optional: true,
416-
ValidateFunc: verify.ValidateEnum([]string{"NO_ROOT_SQUASH", "ROOT_SQUASH", "ALL_SQUASH", ""}),
417-
Description: `SquashMode defines how remote user privileges are restricted when accessing an NFS export. It controls how the user identities (like root) are mapped to anonymous users to limit access and enforce security. Possible values: ["NO_ROOT_SQUASH", "ROOT_SQUASH", "ALL_SQUASH"]`,
455+
Type: schema.TypeString,
456+
Optional: true,
457+
ValidateFunc: verify.ValidateEnum([]string{"SQUASH_MODE_UNSPECIFIED", "NO_ROOT_SQUASH", "ROOT_SQUASH", "ALL_SQUASH", ""}),
458+
DiffSuppressFunc: suppressSquashModeDiff,
459+
Description: `SquashMode defines how remote user privileges are restricted when accessing an NFS export. It controls how the user identities (like root) are mapped to anonymous users to limit access and enforce security.
460+
It overwrites the has_root_access parameter. Use either squash_mode or has_root_access. For ALL_SQUASH, access_type needs to be set to READ_WRITE. Possible values: ["SQUASH_MODE_UNSPECIFIED", "NO_ROOT_SQUASH", "ROOT_SQUASH", "ALL_SQUASH"]`,
418461
},
419462
},
420463
},
@@ -1525,12 +1568,106 @@ func resourceNetappVolumeUpdate(d *schema.ResourceData, meta interface{}) error
15251568
newBlockDevices = append(newBlockDevices, newblockDevice)
15261569
}
15271570

1528-
log.Printf("newBlockDevices %v", newBlockDevices)
15291571
if len(newBlockDevices) > 0 {
15301572
obj["blockDevices"] = newBlockDevices
15311573
}
15321574
}
15331575

1576+
// detect export_policy presence in TF config of volume
1577+
1578+
if v, ok := d.GetOk("export_policy"); ok {
1579+
1580+
l := v.([]interface{})
1581+
newExportPolicy := make([]interface{}, 0, len(l))
1582+
1583+
for _, item := range v.([]interface{}) {
1584+
if item == nil {
1585+
continue
1586+
}
1587+
ruleSet := item.(map[string]interface{})
1588+
1589+
if ruleMap, ruleMapExists := ruleSet["rules"]; ruleMapExists {
1590+
1591+
l := ruleMap.([]interface{})
1592+
newRuleMap := make([]interface{}, 0, len(l))
1593+
for _, ruleMapItem := range ruleMap.([]interface{}) {
1594+
if ruleMapItem == nil {
1595+
continue
1596+
}
1597+
ruleMapItemSet := ruleMapItem.(map[string]interface{})
1598+
newRuleMapItemSet := make(map[string]interface{})
1599+
1600+
if val, exists := ruleMapItemSet["access_type"]; exists {
1601+
newRuleMapItemSet["accessType"] = val
1602+
}
1603+
if val, exists := ruleMapItemSet["allowed_clients"]; exists {
1604+
newRuleMapItemSet["allowedClients"] = val
1605+
}
1606+
if val, exists := ruleMapItemSet["has_root_access"]; exists {
1607+
newRuleMapItemSet["hasRootAccess"] = val
1608+
}
1609+
if val, exists := ruleMapItemSet["nfsv3"]; exists {
1610+
newRuleMapItemSet["nfsv3"] = val
1611+
}
1612+
if val, exists := ruleMapItemSet["kerberos5_read_only"]; exists {
1613+
newRuleMapItemSet["kerberos5ReadOnly"] = val
1614+
}
1615+
if val, exists := ruleMapItemSet["kerberos5_read_write"]; exists {
1616+
newRuleMapItemSet["kerberos5ReadWrite"] = val
1617+
}
1618+
if val, exists := ruleMapItemSet["kerberos5i_read_only"]; exists {
1619+
newRuleMapItemSet["kerberos5iReadOnly"] = val
1620+
}
1621+
if val, exists := ruleMapItemSet["kerberos5i_read_write"]; exists {
1622+
newRuleMapItemSet["kerberos5iReadWrite"] = val
1623+
}
1624+
if val, exists := ruleMapItemSet["kerberos5p_read_only"]; exists {
1625+
newRuleMapItemSet["kerberos5pReadOnly"] = val
1626+
}
1627+
if val, exists := ruleMapItemSet["kerberos5p_read_write"]; exists {
1628+
newRuleMapItemSet["kerberos5pReadWrite"] = val
1629+
}
1630+
1631+
// Handle "squash_mode":
1632+
squashModeVal, squashModeExists := ruleMapItemSet["squash_mode"]
1633+
1634+
// Only send if the user explicitly added it.
1635+
// If not added, send as null.
1636+
if squashModeExists && squashModeVal == "ALL_SQUASH" {
1637+
// User provided the field, send their value
1638+
newRuleMapItemSet["squashMode"] = squashModeVal
1639+
} else {
1640+
// User did NOT provide the field, or provided an empty value.
1641+
// Explicitly send null to the API.
1642+
newRuleMapItemSet["squashMode"] = nil
1643+
}
1644+
1645+
// Handle "anon_uid"
1646+
anonUidVal, anonUidExists := ruleMapItemSet["anon_uid"]
1647+
1648+
// Only send if the user explicitly added it.
1649+
// If not added, send as null.
1650+
if anonUidExists && anonUidVal != nil && anonUidVal != 0 {
1651+
// User provided the field, send their value
1652+
newRuleMapItemSet["anonUid"] = anonUidVal
1653+
} else {
1654+
// User did NOT provide the field, or provided an empty value.
1655+
// Explicitly send null to the API.
1656+
newRuleMapItemSet["anonUid"] = nil
1657+
}
1658+
1659+
newRuleMap = append(newRuleMap, newRuleMapItemSet)
1660+
1661+
}
1662+
ruleSet["rules"] = newRuleMap
1663+
newExportPolicy = append(newExportPolicy, ruleSet)
1664+
}
1665+
}
1666+
if len(newExportPolicy) > 0 {
1667+
obj["exportPolicy"] = newExportPolicy[0]
1668+
}
1669+
}
1670+
15341671
// err == nil indicates that the billing_project value was found
15351672
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
15361673
billingProject = bp

0 commit comments

Comments
 (0)