Skip to content

Commit 9fbf8c4

Browse files
authored
Add OmitUnchangedField config for selective inclusion of changed CRD fields in update requests. (#406)
This patch adds a new generator configuration `update_operation.omit_unchanged_fields` that instructs the code generator to generate cvode that only include fields in update reuqests if their values have actually changed. This is accomplished by using `delta.DifferentAt` before setting any field in `newUpdateRequest` function. This change will improve the reliability of the generated code by preventing the controller from trying to update unecessary fields. Signed-off-by: Amine Hilaly <[email protected]>
1 parent 3c89a32 commit 9fbf8c4

File tree

6 files changed

+170
-2
lines changed

6 files changed

+170
-2
lines changed

pkg/config/resource.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,14 @@ type UpdateOperationConfig struct {
319319
// CustomMethodName is a string for the method name to replace the
320320
// sdkUpdate() method implementation for this resource
321321
CustomMethodName string `json:"custom_method_name"`
322+
// OmitUnchangedFields instructs the code generator on how to generate
323+
// `newUpdateRequestPayload` function. If the boolean is true, the code generator
324+
// will leverage `delta.DifferentAt` to check whether a field have changed or not
325+
// before including it in the update request.
326+
OmitUnchangedFields bool `json:"omit_unchanged_fields"`
322327
}
323328

324-
// AdditionalConfig can be used to specify additional printer columns to be included
329+
// AdditionalColumnConfig can be used to specify additional printer columns to be included
325330
// in a Resource's output from kubectl.
326331
type AdditionalColumnConfig struct {
327332
// Name is the thing to display in the column's output.

pkg/generate/code/set_sdk.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,19 @@ func SetSDK(
294294
// }
295295
// res.VpnMemberships = f0
296296
// }
297+
298+
omitUnchangedFieldsOnUpdate := op == r.Ops.Update && r.OmitUnchangedFieldsOnUpdate()
299+
if omitUnchangedFieldsOnUpdate {
300+
fieldJSONPath := fmt.Sprintf("%s.%s", cfg.PrefixConfig.SpecField[1:], f.Names.Camel)
301+
out += fmt.Sprintf(
302+
"%sif delta.DifferentAt(%q) {\n", indent, fieldJSONPath,
303+
)
304+
305+
// increase indentation level
306+
indentLevel++
307+
indent = "\t" + indent
308+
}
309+
297310
out += fmt.Sprintf(
298311
"%sif %s != nil {\n", indent, sourceAdaptedVarName,
299312
)
@@ -353,6 +366,16 @@ func SetSDK(
353366
out += fmt.Sprintf(
354367
"%s}\n", indent,
355368
)
369+
370+
if omitUnchangedFieldsOnUpdate {
371+
// decrease indentation level
372+
indentLevel--
373+
indent = indent[1:]
374+
375+
out += fmt.Sprintf(
376+
"%s}\n", indent,
377+
)
378+
}
356379
}
357380
return out
358381
}

pkg/generate/code/set_sdk_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,128 @@ func TestSetSDK_Elasticache_User_Create_Override_Values(t *testing.T) {
13671367
)
13681368
}
13691369

1370+
func TestSetSDK_MQ_Broker_newUpdateRequest_OmitUnchangedValues(t *testing.T) {
1371+
assert := assert.New(t)
1372+
require := require.New(t)
1373+
1374+
g := testutil.NewModelForService(t, "mq")
1375+
1376+
crd := testutil.GetCRDByName(t, g, "Broker")
1377+
require.NotNil(crd)
1378+
1379+
expected := `
1380+
if delta.DifferentAt("Spec.AuthenticationStrategy") {
1381+
if r.ko.Spec.AuthenticationStrategy != nil {
1382+
res.SetAuthenticationStrategy(*r.ko.Spec.AuthenticationStrategy)
1383+
}
1384+
}
1385+
if delta.DifferentAt("Spec.AutoMinorVersionUpgrade") {
1386+
if r.ko.Spec.AutoMinorVersionUpgrade != nil {
1387+
res.SetAutoMinorVersionUpgrade(*r.ko.Spec.AutoMinorVersionUpgrade)
1388+
}
1389+
}
1390+
if delta.DifferentAt("Spec.BrokerID") {
1391+
if r.ko.Status.BrokerID != nil {
1392+
res.SetBrokerId(*r.ko.Status.BrokerID)
1393+
}
1394+
}
1395+
if delta.DifferentAt("Spec.Configuration") {
1396+
if r.ko.Spec.Configuration != nil {
1397+
f3 := &svcsdk.ConfigurationId{}
1398+
if r.ko.Spec.Configuration.ID != nil {
1399+
f3.SetId(*r.ko.Spec.Configuration.ID)
1400+
}
1401+
if r.ko.Spec.Configuration.Revision != nil {
1402+
f3.SetRevision(*r.ko.Spec.Configuration.Revision)
1403+
}
1404+
res.SetConfiguration(f3)
1405+
}
1406+
}
1407+
if delta.DifferentAt("Spec.EngineVersion") {
1408+
if r.ko.Spec.EngineVersion != nil {
1409+
res.SetEngineVersion(*r.ko.Spec.EngineVersion)
1410+
}
1411+
}
1412+
if delta.DifferentAt("Spec.HostInstanceType") {
1413+
if r.ko.Spec.HostInstanceType != nil {
1414+
res.SetHostInstanceType(*r.ko.Spec.HostInstanceType)
1415+
}
1416+
}
1417+
if delta.DifferentAt("Spec.LDAPServerMetadata") {
1418+
if r.ko.Spec.LDAPServerMetadata != nil {
1419+
f6 := &svcsdk.LdapServerMetadataInput{}
1420+
if r.ko.Spec.LDAPServerMetadata.Hosts != nil {
1421+
f6f0 := []*string{}
1422+
for _, f6f0iter := range r.ko.Spec.LDAPServerMetadata.Hosts {
1423+
var f6f0elem string
1424+
f6f0elem = *f6f0iter
1425+
f6f0 = append(f6f0, &f6f0elem)
1426+
}
1427+
f6.SetHosts(f6f0)
1428+
}
1429+
if r.ko.Spec.LDAPServerMetadata.RoleBase != nil {
1430+
f6.SetRoleBase(*r.ko.Spec.LDAPServerMetadata.RoleBase)
1431+
}
1432+
if r.ko.Spec.LDAPServerMetadata.RoleName != nil {
1433+
f6.SetRoleName(*r.ko.Spec.LDAPServerMetadata.RoleName)
1434+
}
1435+
if r.ko.Spec.LDAPServerMetadata.RoleSearchMatching != nil {
1436+
f6.SetRoleSearchMatching(*r.ko.Spec.LDAPServerMetadata.RoleSearchMatching)
1437+
}
1438+
if r.ko.Spec.LDAPServerMetadata.RoleSearchSubtree != nil {
1439+
f6.SetRoleSearchSubtree(*r.ko.Spec.LDAPServerMetadata.RoleSearchSubtree)
1440+
}
1441+
if r.ko.Spec.LDAPServerMetadata.ServiceAccountPassword != nil {
1442+
f6.SetServiceAccountPassword(*r.ko.Spec.LDAPServerMetadata.ServiceAccountPassword)
1443+
}
1444+
if r.ko.Spec.LDAPServerMetadata.ServiceAccountUsername != nil {
1445+
f6.SetServiceAccountUsername(*r.ko.Spec.LDAPServerMetadata.ServiceAccountUsername)
1446+
}
1447+
if r.ko.Spec.LDAPServerMetadata.UserBase != nil {
1448+
f6.SetUserBase(*r.ko.Spec.LDAPServerMetadata.UserBase)
1449+
}
1450+
if r.ko.Spec.LDAPServerMetadata.UserRoleName != nil {
1451+
f6.SetUserRoleName(*r.ko.Spec.LDAPServerMetadata.UserRoleName)
1452+
}
1453+
if r.ko.Spec.LDAPServerMetadata.UserSearchMatching != nil {
1454+
f6.SetUserSearchMatching(*r.ko.Spec.LDAPServerMetadata.UserSearchMatching)
1455+
}
1456+
if r.ko.Spec.LDAPServerMetadata.UserSearchSubtree != nil {
1457+
f6.SetUserSearchSubtree(*r.ko.Spec.LDAPServerMetadata.UserSearchSubtree)
1458+
}
1459+
res.SetLdapServerMetadata(f6)
1460+
}
1461+
}
1462+
if delta.DifferentAt("Spec.Logs") {
1463+
if r.ko.Spec.Logs != nil {
1464+
f7 := &svcsdk.Logs{}
1465+
if r.ko.Spec.Logs.Audit != nil {
1466+
f7.SetAudit(*r.ko.Spec.Logs.Audit)
1467+
}
1468+
if r.ko.Spec.Logs.General != nil {
1469+
f7.SetGeneral(*r.ko.Spec.Logs.General)
1470+
}
1471+
res.SetLogs(f7)
1472+
}
1473+
}
1474+
if delta.DifferentAt("Spec.SecurityGroups") {
1475+
if r.ko.Spec.SecurityGroups != nil {
1476+
f8 := []*string{}
1477+
for _, f8iter := range r.ko.Spec.SecurityGroups {
1478+
var f8elem string
1479+
f8elem = *f8iter
1480+
f8 = append(f8, &f8elem)
1481+
}
1482+
res.SetSecurityGroups(f8)
1483+
}
1484+
}
1485+
`
1486+
assert.Equal(
1487+
expected,
1488+
code.SetSDK(crd.Config(), crd, model.OpTypeUpdate, "r.ko", "res", 1),
1489+
)
1490+
}
1491+
13701492
func TestSetSDK_RDS_DBInstance_Create(t *testing.T) {
13711493
assert := assert.New(t)
13721494
require := require.New(t)

pkg/model/crd.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,21 @@ func (r *CRD) HasImmutableFieldChanges() bool {
362362
return false
363363
}
364364

365+
// OmitUnchangedFieldsOnUpdate returns whether the controller needs to omit
366+
// unchanged fields from an update request or not.
367+
func (r *CRD) OmitUnchangedFieldsOnUpdate() bool {
368+
if r.Config() == nil {
369+
return false
370+
}
371+
rConfig, found := r.Config().Resources[r.Names.Original]
372+
if found {
373+
if rConfig.UpdateOperation != nil {
374+
return rConfig.UpdateOperation.OmitUnchangedFields
375+
}
376+
}
377+
return false
378+
}
379+
365380
// IsARNPrimaryKey returns true if the CRD uses its ARN as its primary key in
366381
// ReadOne calls.
367382
func (r *CRD) IsARNPrimaryKey() bool {

pkg/testdata/models/apis/mq/0000-00-00/generator.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ resources:
1212
fields:
1313
Users.Password:
1414
is_secret: true
15+
update_operation:
16+
omit_unchanged_fields: true

templates/pkg/resource/sdk_update.go.tpl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func (rm *resourceManager) sdkUpdate(
2525
return updated, err
2626
}
2727
{{- end }}
28-
input, err := rm.newUpdateRequestPayload(ctx, desired)
28+
input, err := rm.newUpdateRequestPayload(ctx, desired, delta)
2929
if err != nil {
3030
return nil, err
3131
}
@@ -68,6 +68,7 @@ func (rm *resourceManager) sdkUpdate(
6868
func (rm *resourceManager) newUpdateRequestPayload(
6969
ctx context.Context,
7070
r *resource,
71+
delta *ackcompare.Delta,
7172
) (*svcsdk.{{ .CRD.Ops.Update.InputRef.Shape.ShapeName }}, error) {
7273
res := &svcsdk.{{ .CRD.Ops.Update.InputRef.Shape.ShapeName }}{}
7374
{{ GoCodeSetUpdateInput .CRD "r.ko" "res" 1 }}

0 commit comments

Comments
 (0)