Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated Handling of DBCluster Update #1424

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 222 additions & 8 deletions pkg/controller/rds/dbcluster/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package dbcluster
import (
"context"
"strconv"
"strings"

svcsdk "github.com/aws/aws-sdk-go/service/rds"
svcsdkapi "github.com/aws/aws-sdk-go/service/rds/rdsiface"
Expand All @@ -12,6 +13,14 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

svcapitypes "github.com/crossplane-contrib/provider-aws/apis/rds/v1alpha1"

"github.com/crossplane-contrib/provider-aws/apis/v1alpha1"

aws "github.com/crossplane-contrib/provider-aws/pkg/clients"
"github.com/crossplane-contrib/provider-aws/pkg/clients/rds"
"github.com/crossplane-contrib/provider-aws/pkg/features"

xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/connection"
"github.com/crossplane/crossplane-runtime/pkg/controller"
Expand All @@ -20,19 +29,19 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/password"
"github.com/crossplane/crossplane-runtime/pkg/reconciler/managed"
"github.com/crossplane/crossplane-runtime/pkg/resource"

svcapitypes "github.com/crossplane-contrib/provider-aws/apis/rds/v1alpha1"
"github.com/crossplane-contrib/provider-aws/apis/v1alpha1"
aws "github.com/crossplane-contrib/provider-aws/pkg/clients"
"github.com/crossplane-contrib/provider-aws/pkg/clients/rds"
"github.com/crossplane-contrib/provider-aws/pkg/features"
cpresource "github.com/crossplane/crossplane-runtime/pkg/resource"
)

// error constants
const (
errSaveSecretFailed = "failed to save generated password to Kubernetes secret"
errUpdateTags = "cannot update tags"
)

type updater struct {
client svcsdkapi.RDSAPI
}

// SetupDBCluster adds a controller that reconciles DbCluster.
func SetupDBCluster(mgr ctrl.Manager, o controller.Options) error {
name := managed.ControllerName(svcapitypes.DBClusterGroupKind)
Expand All @@ -43,6 +52,8 @@ func SetupDBCluster(mgr ctrl.Manager, o controller.Options) error {
e.postObserve = c.postObserve
e.isUpToDate = isUpToDate
e.preUpdate = preUpdate
u := &updater{client: e.client}
e.postUpdate = u.postUpdate
e.preCreate = c.preCreate
e.preDelete = preDelete
e.filterList = filterList
Expand Down Expand Up @@ -131,7 +142,7 @@ func (e *custom) preCreate(ctx context.Context, cr *svcapitypes.DBCluster, obj *
return nil
}

func isUpToDate(cr *svcapitypes.DBCluster, out *svcsdk.DescribeDBClustersOutput) (bool, error) {
func isUpToDate(cr *svcapitypes.DBCluster, out *svcsdk.DescribeDBClustersOutput) (bool, error) { // nolint:gocyclo
status := aws.StringValue(out.DBClusters[0].Status)
if status == "modifying" || status == "upgrading" || status == "configuring-iam-database-auth" {
return true, nil
Expand All @@ -141,20 +152,168 @@ func isUpToDate(cr *svcapitypes.DBCluster, out *svcsdk.DescribeDBClustersOutput)
return false, nil
}

if !isPreferredMaintenanceWindowUpToDate(cr, out) {
return false, nil
}

if !isPreferredBackupWindowUpToDate(cr, out) {
return false, nil
}

if aws.Int64Value(cr.Spec.ForProvider.BacktrackWindow) != aws.Int64Value(out.DBClusters[0].BacktrackWindow) {
return false, nil
}

if !isBackupRetentionPeriodUpToDate(cr, out) {
return false, nil
}

if aws.BoolValue(cr.Spec.ForProvider.CopyTagsToSnapshot) != aws.BoolValue(out.DBClusters[0].CopyTagsToSnapshot) {
return false, nil
}

if aws.BoolValue(cr.Spec.ForProvider.DeletionProtection) != aws.BoolValue(out.DBClusters[0].DeletionProtection) {
return false, nil
}

if !isEngineVersionUpToDate(cr, out) {
return false, nil
}

if !isPortUpToDate(cr, out) {
return false, nil
}

isScalingConfigurationUpToDate, err := isScalingConfigurationUpToDate(cr.Spec.ForProvider.ScalingConfiguration, out.DBClusters[0].ScalingConfigurationInfo)
if !isScalingConfigurationUpToDate {
return false, err
}

add, remove := DiffTags(cr.Spec.ForProvider.Tags, out.DBClusters[0].TagList)
if len(add) > 0 || len(remove) > 0 {
return false, nil
}
return true, nil
}

func isPreferredMaintenanceWindowUpToDate(cr *svcapitypes.DBCluster, out *svcsdk.DescribeDBClustersOutput) bool {
// If PreferredMaintenanceWindow is not set, aws sets a random window
// so we do not try to update in this case
if cr.Spec.ForProvider.PreferredMaintenanceWindow != nil {

// AWS accepts uppercase weekdays, but returns lowercase values,
// therfore we compare usinf equalFold
if !strings.EqualFold(aws.StringValue(cr.Spec.ForProvider.PreferredMaintenanceWindow), aws.StringValue(out.DBClusters[0].PreferredMaintenanceWindow)) {
return false
}
}
return true
}

func isPreferredBackupWindowUpToDate(cr *svcapitypes.DBCluster, out *svcsdk.DescribeDBClustersOutput) bool {
// If PreferredBackupWindow is not set, aws sets a random window
// so we do not try to update in this case
if cr.Spec.ForProvider.PreferredBackupWindow != nil {
if aws.StringValue(cr.Spec.ForProvider.PreferredBackupWindow) != aws.StringValue(out.DBClusters[0].PreferredBackupWindow) {
return false
}
}
return true
}

func isBackupRetentionPeriodUpToDate(cr *svcapitypes.DBCluster, out *svcsdk.DescribeDBClustersOutput) bool {
// If BackupRetentionPeriod is not set, aws sets a default value
// so we do not try to update in this case
if cr.Spec.ForProvider.BackupRetentionPeriod != nil {
if aws.Int64Value(cr.Spec.ForProvider.BackupRetentionPeriod) != aws.Int64Value(out.DBClusters[0].BackupRetentionPeriod) {
return false
}
}
return true
}

func isScalingConfigurationUpToDate(sc *svcapitypes.ScalingConfiguration, obj *svcsdk.ScalingConfigurationInfo) (bool, error) {
jsonPatch, err := aws.CreateJSONPatch(sc, obj)
if err != nil {
return false, err
}
// if there is no difference, jsonPatch is {}
if len(jsonPatch) > 2 {
return false, nil
}
return true, nil
}

func isEngineVersionUpToDate(cr *svcapitypes.DBCluster, out *svcsdk.DescribeDBClustersOutput) bool {
// If EngineVersion is not set, aws sets a default value
// so we do not try to update in this case
if cr.Spec.ForProvider.EngineVersion != nil {
if aws.StringValue(cr.Spec.ForProvider.EngineVersion) != aws.StringValue(out.DBClusters[0].EngineVersion) {
return false
}
}
return true
}

func isPortUpToDate(cr *svcapitypes.DBCluster, out *svcsdk.DescribeDBClustersOutput) bool {
// If Port is not set, aws sets a default value
// so we do not try to update in this case
if cr.Spec.ForProvider.Port != nil {
if aws.Int64Value(cr.Spec.ForProvider.Port) != aws.Int64Value(out.DBClusters[0].Port) {
return false
}
}
return true
}

func preUpdate(_ context.Context, cr *svcapitypes.DBCluster, obj *svcsdk.ModifyDBClusterInput) error {
obj.DBClusterIdentifier = aws.String(meta.GetExternalName(cr))
obj.ApplyImmediately = cr.Spec.ForProvider.ApplyImmediately

return nil
}

func (u *updater) postUpdate(ctx context.Context, cr *svcapitypes.DBCluster, obj *svcsdk.ModifyDBClusterOutput, upd managed.ExternalUpdate, err error) (managed.ExternalUpdate, error) {
if err == nil {

input := GenerateDescribeDBClustersInput(cr)
resp, err := u.client.DescribeDBClustersWithContext(ctx, input)

tags := resp.DBClusters[0].TagList

add, remove := DiffTags(cr.Spec.ForProvider.Tags, tags)

if len(add) > 0 || len(remove) > 0 {
err := u.updateTags(ctx, cr, add, remove)
if err != nil {
return managed.ExternalUpdate{}, err
}
}
if err != nil {
if err != nil {
return managed.ExternalUpdate{}, aws.Wrap(cpresource.Ignore(IsNotFound, err), errDescribe)
}
}
if !isPreferredMaintenanceWindowUpToDate(cr, resp) {
return upd, errors.New("PreferredMaintenanceWindow not matching aws data")
}

if !isPreferredBackupWindowUpToDate(cr, resp) {
return upd, errors.New("PreferredBackupWindow not matching aws data")
}
}

return upd, err
}

func preDelete(_ context.Context, cr *svcapitypes.DBCluster, obj *svcsdk.DeleteDBClusterInput) (bool, error) {
obj.DBClusterIdentifier = aws.String(meta.GetExternalName(cr))
obj.FinalDBSnapshotIdentifier = aws.String(cr.Spec.ForProvider.FinalDBSnapshotIdentifier)

obj.SkipFinalSnapshot = aws.Bool(cr.Spec.ForProvider.SkipFinalSnapshot)

if !cr.Spec.ForProvider.SkipFinalSnapshot {
obj.FinalDBSnapshotIdentifier = aws.String(cr.Spec.ForProvider.FinalDBSnapshotIdentifier)
}
return false, nil
}

Expand Down Expand Up @@ -187,3 +346,58 @@ func (e *custom) savePasswordSecret(ctx context.Context, cr *svcapitypes.DBClust
}
return patcher.Apply(ctx, sc)
}

// DiffTags returns tags that should be added or removed.
func DiffTags(spec []*svcapitypes.Tag, current []*svcsdk.Tag) (addTags []*svcsdk.Tag, remove []*string) {
addMap := make(map[string]string, len(spec))
for _, t := range spec {
addMap[aws.StringValue(t.Key)] = aws.StringValue(t.Value)
}
removeMap := make(map[string]string, len(spec))
for _, t := range current {
if addMap[aws.StringValue(t.Key)] == aws.StringValue(t.Value) {
delete(addMap, aws.StringValue(t.Key))
continue
}
removeMap[aws.StringValue(t.Key)] = aws.StringValue(t.Value)
}
for k, v := range addMap {
addTags = append(addTags, &svcsdk.Tag{Key: aws.String(k), Value: aws.String(v)})
}
for k := range removeMap {
remove = append(remove, aws.String(k))
}
return
}

func (u *updater) updateTags(ctx context.Context, cr *svcapitypes.DBCluster, addTags []*svcsdk.Tag, removeTags []*string) error {

arn := cr.Status.AtProvider.DBClusterARN
if arn != nil {
if len(removeTags) > 0 {
inputR := &svcsdk.RemoveTagsFromResourceInput{
ResourceName: arn,
TagKeys: removeTags,
}

_, err := u.client.RemoveTagsFromResourceWithContext(ctx, inputR)
if err != nil {
return errors.New(errUpdateTags)
}
}
if len(addTags) > 0 {
inputC := &svcsdk.AddTagsToResourceInput{
ResourceName: arn,
Tags: addTags,
}

_, err := u.client.AddTagsToResourceWithContext(ctx, inputC)
if err != nil {
return errors.New(errUpdateTags)
}

}
}
return nil

}