Skip to content

Commit

Permalink
Merge pull request #1514 from ravilr/rds_prevent_drift_due_to_automan…
Browse files Browse the repository at this point in the history
…aged_upgrades

ignore drift in rds.Cluster and rds.Instance due to engine_version diff arising out of automanaged upgrades
  • Loading branch information
turkenf authored Oct 10, 2024
2 parents 5c94053 + c3547fb commit 9cd7c62
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 1 deletion.
32 changes: 31 additions & 1 deletion config/rds/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ import (
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"

"github.com/crossplane/upjet/pkg/config"
"github.com/crossplane/upjet/pkg/types/comments"

"github.com/upbound/provider-aws/config/common"
"github.com/upbound/provider-aws/config/rds/utils"
)

// Configure adds configurations for the rds group.
func Configure(p *config.Provider) {
func Configure(p *config.Provider) { //nolint:gocyclo
p.AddResourceConfigurator("aws_rds_cluster", func(r *config.Resource) {
// Mutually exclusive with aws_rds_cluster_role_association
config.MoveToStatus(r.TerraformResource, "iam_roles")
Expand Down Expand Up @@ -80,6 +82,20 @@ func Configure(p *config.Provider) {
"master DB user. If you set autoGeneratePassword to true, the Secret" +
" referenced here will be created or updated with generated password" +
" if it does not already contain one."
r.TerraformCustomDiff = func(diff *terraform.InstanceDiff, _ *terraform.InstanceState, _ *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
if diff == nil || diff.Destroy {
return diff, nil
}
// Ignore the engine version diff, if the desired spec version is lower than the external's actual version.
// Downgrades are not allowed by AWS RDS.
if evDiff, ok := diff.Attributes["engine_version"]; ok && evDiff.Old != "" && evDiff.New != "" {
c := utils.CompareEngineVersions(evDiff.New, evDiff.Old)
if c <= 0 {
delete(diff.Attributes, "engine_version")
}
}
return diff, nil
}
})

p.AddResourceConfigurator("aws_rds_cluster_instance", func(r *config.Resource) {
Expand Down Expand Up @@ -167,6 +183,20 @@ func Configure(p *config.Provider) {
" if it does not already contain one."
r.MetaResource.ArgumentDocs["engine"] = "- (Required unless a `snapshotIdentifier` or `replicateSourceDb` is provided) The database engine to use. For supported values, see the Engine parameter in [API action CreateDBInstance](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_CreateDBInstance.html). Note that for Amazon Aurora instances the engine must match the [DB Cluster](https://marketplace.upbound.io/providers/upbound/provider-aws/latest/resources/rds.aws.upbound.io/Cluster/v1beta1)'s engine'. For information on the difference between the available Aurora MySQL engines see Comparison in the [Amazon RDS Release Notes](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraMySQLReleaseNotes/Welcome.html)."
r.MetaResource.ArgumentDocs["engine_version"] = "- (Optional) The engine version to use. If `autoMinorVersionUpgrade` is enabled, you can provide a prefix of the version such as 5.7 (for 5.7.10). The actual engine version used is returned in the attribute `status.atProvider.engineVersionActual`. For supported values, see the EngineVersion parameter in [API action CreateDBInstance](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_CreateDBInstance.html). Note that for Amazon Aurora instances the engine version must match the [DB Cluster](https://marketplace.upbound.io/providers/upbound/provider-aws/latest/resources/rds.aws.upbound.io/Cluster/v1beta1)'s engine version'."
r.TerraformCustomDiff = func(diff *terraform.InstanceDiff, _ *terraform.InstanceState, _ *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
if diff == nil || diff.Destroy {
return diff, nil
}
// Ignore the engine version diff, if the desired spec version is lower than the external's actual version.
// Downgrades are not allowed by AWS RDS.
if evDiff, ok := diff.Attributes["engine_version"]; ok && evDiff.Old != "" && evDiff.New != "" {
c := utils.CompareEngineVersions(evDiff.New, evDiff.Old)
if c <= 0 {
delete(diff.Attributes, "engine_version")
}
}
return diff, nil
}
})

p.AddResourceConfigurator("aws_db_proxy", func(r *config.Resource) {
Expand Down
89 changes: 89 additions & 0 deletions config/rds/utils/engine_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-FileCopyrightText: 2024 The Crossplane Authors <https://crossplane.io>
//
// SPDX-License-Identifier: CC0-1.0

package utils

import (
"strconv"
"strings"
)

// EngineVersion represents an AWS RDS engine version.
type EngineVersion []any

// ParseEngineVersion from a raw string.
func ParseEngineVersion(raw string) EngineVersion {
split := strings.Split(raw, ".")

v := make(EngineVersion, len(split))
for i, s := range split {
d, err := strconv.Atoi(s)
if err != nil {
v[i] = s
} else {
v[i] = d
}
}
return v
}

const (
compareIsHigher = 1
compareIsEqual = 0
compareIsLower = -1
)

// Compare returns a positive value if v is represents a higher version number
// than other. A negative value is returned if other is higher than v.
// It returns 0 if both are considered equal.
func (v EngineVersion) Compare(other EngineVersion) int {
if other == nil {
return compareIsHigher
}

for i := 0; i < len(v); i++ {
a := v.get(i)
b := other.get(i)
c := compareVersionComponents(a, b)
if c != 0 {
return c
}
}
return compareIsEqual
}

func compareVersionComponents(a, b any) int {
if a == b {
return compareIsEqual
}
if b == nil {
return compareIsHigher
}
aI, aIsInt := a.(int)
bI, bIsInt := b.(int)
if aIsInt {
if bIsInt {
return aI - bI
}
return compareIsHigher
}
if bIsInt {
return compareIsLower
}
return compareIsEqual // We cannot decide if both are strings.
}

func (v EngineVersion) get(i int) any {
if i >= 0 && i < len(v) {
return v[i]
}
return nil
}

// CompareEngineVersions is a shortcut to compare two engine versions.
func CompareEngineVersions(a, b string) int {
av := ParseEngineVersion(a)
bv := ParseEngineVersion(b)
return av.Compare(bv)
}
186 changes: 186 additions & 0 deletions config/rds/utils/engine_version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// SPDX-FileCopyrightText: 2024 The Crossplane Authors <https://crossplane.io>
//
// SPDX-License-Identifier: Apache-2.0

package utils

import (
"testing"

"github.com/google/go-cmp/cmp"
)

func TestEngineVersionIsHigherOrEqual(t *testing.T) {
type args struct {
spec string
current string
}

type want struct {
result int
}

cases := map[string]struct {
args
want
}{
"AuroraMySQLIsEqual": {
args: args{
spec: "5",
current: "5.7.mysql_aurora.2.07.0",
},
want: want{
result: 0,
},
},
"AuroraMySQLIsEqual2": {
args: args{
spec: "5.7",
current: "5.7.mysql_aurora.2.07.0",
},
want: want{
result: 0,
},
},
"AuroraMySQLIsHigher": {
args: args{
spec: "8",
current: "5.7.mysql_aurora.2.07.0",
},
want: want{
result: 1,
},
},
"AuroraPostgresIsEqual": {
args: args{
spec: "14",
current: "14.4",
},
want: want{
result: 0,
},
},
"AuroraPostgresIsLower": {
args: args{
spec: "14",
current: "15.2",
},
want: want{
result: -1,
},
},
"MariaDBIsEqual": {
args: args{
spec: "10",
current: "10.3.35",
},
want: want{
result: 0,
},
},
"MariaDBNotIsLower": {
args: args{
spec: "10",
current: "11.0",
},
want: want{
result: -1,
},
},
"MySQLIsEqual": {
args: args{
spec: "5.7",
current: "5.7.38",
},
want: want{
result: 0,
},
},
"MySQLIsHigher": {
args: args{
spec: "8",
current: "5.7.38",
},
want: want{
result: 1,
},
},
"OracleIsEqual": {
args: args{
spec: "19",
current: "19.0.0.0.ru-2023-01.rur-2023-01.r1",
},
want: want{
result: 0,
},
},
"OracleIsHigher": {
args: args{
spec: "20",
current: "19.0.0.0.ru-2023-01.rur-2023-01.r1",
},
want: want{
result: 1,
},
},
"PostgresIsEqual": {
args: args{
spec: "10",
current: "10.17",
},
want: want{
result: 0,
},
},
"PostgresIsHigher": {
args: args{
spec: "14",
current: "10.17",
},
want: want{
result: 1,
},
},
"SQLServerIsEqual": {
args: args{
spec: "12",
current: "12.00.6293.0.v1",
},
want: want{
result: 0,
},
},
"SQLServerIsHigher": {
args: args{
spec: "14",
current: "12.00.6293.0.v1",
},
want: want{
result: 1,
},
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
specV := ParseEngineVersion(tc.args.spec)
curV := ParseEngineVersion(tc.args.current)

res := specV.Compare(curV)
resSign := sign(res)
if diff := cmp.Diff(tc.want.result, resSign); diff != "" {
t.Errorf("r: -want, +got:\n%q\n%q\n%s", tc.args.spec, tc.args.current, diff)
}
})
}
}

func sign(x int) int {
if x < 0 {
return -1
}
if x > 0 {
return 1
}
return 0
}

0 comments on commit 9cd7c62

Please sign in to comment.