diff --git a/pkg/config/resource.go b/pkg/config/resource.go index aa050b95..ba946179 100644 --- a/pkg/config/resource.go +++ b/pkg/config/resource.go @@ -339,6 +339,12 @@ type UpdateOperationConfig struct { // will leverage `delta.DifferentAt` to check whether a field have changed or not // before including it in the update request. OmitUnchangedFields bool `json:"omit_unchanged_fields"` + // OnlySetChangedFields instructs the code generator on how to generate logic for setting + // the value of Spec fields after a successful Update operation in the `sdkUpdate` function. + // If the boolean is true, the code generator uses `delta.DifferentAt` to check if a + // field had changed or not before setting it's value to that returned by the Update AWS API's + // response. Defaults to false. + OnlySetChangedFields bool `json:"only_set_unchanged_fields"` } // ReadOperationsConfig contains instructions for the code generator to handle diff --git a/pkg/generate/code/set_resource.go b/pkg/generate/code/set_resource.go index d6da5a83..6ed6566f 100644 --- a/pkg/generate/code/set_resource.go +++ b/pkg/generate/code/set_resource.go @@ -235,6 +235,18 @@ func SetResource( continue } + onlySetChangedFieldsOnUpdate := op == r.Ops.Update && r.OnlySetChangedFieldsOnUpdate() + if onlySetChangedFieldsOnUpdate && inSpec { + fieldJSONPath := fmt.Sprintf("%s.%s", cfg.PrefixConfig.SpecField[1:], f.Names.Camel) + out += fmt.Sprintf( + "%sif delta.DifferentAt(%q) {\n", indent, fieldJSONPath, + ) + + // increase indentation level + indentLevel++ + indent = "\t" + indent + } + sourceMemberShapeRef := outputShape.MemberRefs[memberName] if sourceMemberShapeRef.Shape == nil { // We may have some instructions to specially handle this field by @@ -413,6 +425,16 @@ func SetResource( } else { indentLevel += 1 } + + if onlySetChangedFieldsOnUpdate && inSpec { + // decrease indentation level + indentLevel-- + indent = indent[1:] + + out += fmt.Sprintf( + "%s}\n", indent, + ) + } } return out } diff --git a/pkg/generate/code/set_resource_test.go b/pkg/generate/code/set_resource_test.go index cbfa6813..25a47b26 100644 --- a/pkg/generate/code/set_resource_test.go +++ b/pkg/generate/code/set_resource_test.go @@ -14,6 +14,7 @@ package code_test import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -5050,3 +5051,150 @@ func TestSetResource_WAFv2_RuleGroup_ReadOne(t *testing.T) { code.SetResource(crd.Config(), crd, op, "resp", "ko", 1), ) } + +func TestSetResource_MQ_OnlySetUnchangedFields_Update(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewModelForServiceWithOptions(t, "mq", &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-only-set-unchanged-fields.yaml", + }) + op := model.OpTypeUpdate + + crd := testutil.GetCRDByName(t, g, "Broker") + require.NotNil(crd) + + expected := ` + if delta.DifferentAt("Spec.AuthenticationStrategy") { + if resp.AuthenticationStrategy != "" { + ko.Spec.AuthenticationStrategy = aws.String(string(resp.AuthenticationStrategy)) + } else { + ko.Spec.AuthenticationStrategy = nil + } + } + if delta.DifferentAt("Spec.AutoMinorVersionUpgrade") { + if resp.AutoMinorVersionUpgrade != nil { + ko.Spec.AutoMinorVersionUpgrade = resp.AutoMinorVersionUpgrade + } else { + ko.Spec.AutoMinorVersionUpgrade = nil + } + } + if resp.BrokerId != nil { + ko.Status.BrokerID = resp.BrokerId + } else { + ko.Status.BrokerID = nil + } + if delta.DifferentAt("Spec.Configuration") { + if resp.Configuration != nil { + f3 := &svcapitypes.ConfigurationID{} + if resp.Configuration.Id != nil { + f3.ID = resp.Configuration.Id + } + if resp.Configuration.Revision != nil { + revisionCopy := int64(*resp.Configuration.Revision) + f3.Revision = &revisionCopy + } + ko.Spec.Configuration = f3 + } else { + ko.Spec.Configuration = nil + } + } + if delta.DifferentAt("Spec.EngineVersion") { + if resp.EngineVersion != nil { + ko.Spec.EngineVersion = resp.EngineVersion + } else { + ko.Spec.EngineVersion = nil + } + } + if delta.DifferentAt("Spec.HostInstanceType") { + if resp.HostInstanceType != nil { + ko.Spec.HostInstanceType = resp.HostInstanceType + } else { + ko.Spec.HostInstanceType = nil + } + } + if delta.DifferentAt("Spec.LDAPServerMetadata") { + if resp.LdapServerMetadata != nil { + f8 := &svcapitypes.LDAPServerMetadataInput{} + if resp.LdapServerMetadata.Hosts != nil { + f8.Hosts = aws.StringSlice(resp.LdapServerMetadata.Hosts) + } + if resp.LdapServerMetadata.RoleBase != nil { + f8.RoleBase = resp.LdapServerMetadata.RoleBase + } + if resp.LdapServerMetadata.RoleName != nil { + f8.RoleName = resp.LdapServerMetadata.RoleName + } + if resp.LdapServerMetadata.RoleSearchMatching != nil { + f8.RoleSearchMatching = resp.LdapServerMetadata.RoleSearchMatching + } + if resp.LdapServerMetadata.RoleSearchSubtree != nil { + f8.RoleSearchSubtree = resp.LdapServerMetadata.RoleSearchSubtree + } + if resp.LdapServerMetadata.ServiceAccountUsername != nil { + f8.ServiceAccountUsername = resp.LdapServerMetadata.ServiceAccountUsername + } + if resp.LdapServerMetadata.UserBase != nil { + f8.UserBase = resp.LdapServerMetadata.UserBase + } + if resp.LdapServerMetadata.UserRoleName != nil { + f8.UserRoleName = resp.LdapServerMetadata.UserRoleName + } + if resp.LdapServerMetadata.UserSearchMatching != nil { + f8.UserSearchMatching = resp.LdapServerMetadata.UserSearchMatching + } + if resp.LdapServerMetadata.UserSearchSubtree != nil { + f8.UserSearchSubtree = resp.LdapServerMetadata.UserSearchSubtree + } + ko.Spec.LDAPServerMetadata = f8 + } else { + ko.Spec.LDAPServerMetadata = nil + } + } + if delta.DifferentAt("Spec.Logs") { + if resp.Logs != nil { + f9 := &svcapitypes.Logs{} + if resp.Logs.Audit != nil { + f9.Audit = resp.Logs.Audit + } + if resp.Logs.General != nil { + f9.General = resp.Logs.General + } + ko.Spec.Logs = f9 + } else { + ko.Spec.Logs = nil + } + } + if delta.DifferentAt("Spec.MaintenanceWindowStartTime") { + if resp.MaintenanceWindowStartTime != nil { + f10 := &svcapitypes.WeeklyStartTime{} + if resp.MaintenanceWindowStartTime.DayOfWeek != "" { + f10.DayOfWeek = aws.String(string(resp.MaintenanceWindowStartTime.DayOfWeek)) + } + if resp.MaintenanceWindowStartTime.TimeOfDay != nil { + f10.TimeOfDay = resp.MaintenanceWindowStartTime.TimeOfDay + } + if resp.MaintenanceWindowStartTime.TimeZone != nil { + f10.TimeZone = resp.MaintenanceWindowStartTime.TimeZone + } + ko.Spec.MaintenanceWindowStartTime = f10 + } else { + ko.Spec.MaintenanceWindowStartTime = nil + } + } + if delta.DifferentAt("Spec.SecurityGroups") { + if resp.SecurityGroups != nil { + ko.Spec.SecurityGroups = aws.StringSlice(resp.SecurityGroups) + } else { + ko.Spec.SecurityGroups = nil + } + } +` + actual := code.SetResource(crd.Config(), crd, op, "resp", "ko", 1) + fmt.Print(actual) + + assert.Equal( + expected, + actual, + ) +} diff --git a/pkg/model/crd.go b/pkg/model/crd.go index de9510f1..13094045 100644 --- a/pkg/model/crd.go +++ b/pkg/model/crd.go @@ -351,6 +351,21 @@ func (r *CRD) OmitUnchangedFieldsOnUpdate() bool { return false } +// OnlySetChangedFieldsOnUpdate returns whether the controller needs to ensure that +// only values included in the delta are updated based on the Update operation's response. +func (r *CRD) OnlySetChangedFieldsOnUpdate() bool { + if r.Config() == nil { + return false + } + rConfig, found := r.Config().Resources[r.Names.Original] + if found { + if rConfig.UpdateOperation != nil { + return rConfig.UpdateOperation.OnlySetChangedFields + } + } + return false +} + // IsARNPrimaryKey returns true if the CRD uses its ARN as its primary key in // ReadOne calls. func (r *CRD) IsARNPrimaryKey() bool { diff --git a/pkg/testdata/models/apis/mq/0000-00-00/generator-only-set-unchanged-fields.yaml b/pkg/testdata/models/apis/mq/0000-00-00/generator-only-set-unchanged-fields.yaml new file mode 100644 index 00000000..d288e7d0 --- /dev/null +++ b/pkg/testdata/models/apis/mq/0000-00-00/generator-only-set-unchanged-fields.yaml @@ -0,0 +1,16 @@ +ignore: + resources: + - Configuration + - User + field_paths: + - CreateBrokerInput.DataReplicationMode + - CreateBrokerInput.DataReplicationPrimaryBrokerArn + - User.ReplicationUser +resources: + Broker: + fields: + Users.Password: + is_secret: true + update_operation: + omit_unchanged_fields: true + only_set_unchanged_fields: true \ No newline at end of file