Skip to content

Commit e809ce5

Browse files
authored
Merge pull request #926 from crossplane/backport-924-to-release-0.20
[Backport release-0.20] Tweak DynamoDb table update logic and connection secret keys
2 parents c6a23c8 + f469889 commit e809ce5

1 file changed

Lines changed: 74 additions & 62 deletions

File tree

pkg/controller/dynamodb/table/hooks.go

Lines changed: 74 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ import (
2020
"context"
2121
"encoding/json"
2222
"sort"
23-
"strings"
2423
"time"
2524

26-
awsgo "github.com/aws/aws-sdk-go/aws"
2725
svcsdk "github.com/aws/aws-sdk-go/service/dynamodb"
2826
svcsdkapi "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface"
2927
"github.com/google/go-cmp/cmp"
@@ -59,7 +57,6 @@ func SetupTable(mgr ctrl.Manager, l logging.Logger, rl workqueue.RateLimiter, po
5957
e.isUpToDate = isUpToDate
6058
u := &updateClient{client: e.client}
6159
e.preUpdate = u.preUpdate
62-
e.postUpdate = postUpdate
6360
},
6461
}
6562
return ctrl.NewControllerManagedBy(mgr).
@@ -109,10 +106,10 @@ func postObserve(_ context.Context, cr *svcapitypes.Table, resp *svcsdk.Describe
109106
}
110107

111108
obs.ConnectionDetails = managed.ConnectionDetails{
112-
"TableName": []byte(meta.GetExternalName(cr)),
113-
"TableArn": []byte(aws.StringValue(resp.Table.TableArn)),
114-
"LatestStreamArn": []byte(aws.StringValue(resp.Table.LatestStreamArn)),
115-
"LatestStreamLabel": []byte(aws.StringValue(resp.Table.LatestStreamLabel)),
109+
"tableName": []byte(meta.GetExternalName(cr)),
110+
"tableArn": []byte(aws.StringValue(resp.Table.TableArn)),
111+
"latestStreamArn": []byte(aws.StringValue(resp.Table.LatestStreamArn)),
112+
"latestStreamLabel": []byte(aws.StringValue(resp.Table.LatestStreamLabel)),
116113
}
117114

118115
return obs, nil
@@ -177,18 +174,18 @@ func lateInitialize(in *svcapitypes.TableParameters, t *svcsdk.DescribeTableOutp
177174
WriteCapacityUnits: t.Table.ProvisionedThroughput.WriteCapacityUnits,
178175
}
179176
}
180-
if in.SSESpecification == nil && t.Table.SSEDescription != nil {
181-
in.SSESpecification = &svcapitypes.SSESpecification{
182-
SSEType: t.Table.SSEDescription.SSEType,
177+
if t.Table.SSEDescription != nil {
178+
if in.SSESpecification == nil {
179+
in.SSESpecification = &svcapitypes.SSESpecification{}
180+
}
181+
if in.SSESpecification.Enabled == nil && t.Table.SSEDescription.Status != nil {
182+
in.SSESpecification.Enabled = aws.Bool(*t.Table.SSEDescription.Status == string(svcapitypes.SSEStatus_ENABLED))
183183
}
184-
}
185-
if in.SSESpecification != nil {
186184
if in.SSESpecification.KMSMasterKeyID == nil && t.Table.SSEDescription.KMSMasterKeyArn != nil {
187-
in.SSESpecification = &svcapitypes.SSESpecification{
188-
KMSMasterKeyID: t.Table.SSEDescription.KMSMasterKeyArn,
189-
SSEType: t.Table.SSEDescription.SSEType,
190-
Enabled: in.SSESpecification.Enabled,
191-
}
185+
in.SSESpecification.KMSMasterKeyID = t.Table.SSEDescription.KMSMasterKeyArn
186+
}
187+
if in.SSESpecification.SSEType == nil && t.Table.SSEDescription.SSEType != nil {
188+
in.SSESpecification.SSEType = t.Table.SSEDescription.SSEType
192189
}
193190
}
194191
if in.StreamSpecification == nil && t.Table.StreamSpecification != nil {
@@ -197,6 +194,11 @@ func lateInitialize(in *svcapitypes.TableParameters, t *svcsdk.DescribeTableOutp
197194
StreamViewType: t.Table.StreamSpecification.StreamViewType,
198195
}
199196
}
197+
198+
if in.BillingMode == nil && t.Table.BillingModeSummary != nil {
199+
in.BillingMode = t.Table.BillingModeSummary.BillingMode
200+
}
201+
200202
return nil
201203
}
202204

@@ -289,10 +291,24 @@ func createPatch(in *svcsdk.DescribeTableOutput, target *svcapitypes.TableParame
289291
}
290292

291293
func isUpToDate(cr *svcapitypes.Table, resp *svcsdk.DescribeTableOutput) (bool, error) {
294+
// A table that's currently updating or creating can't be updated, so we
295+
// temporarily consider it to be up-to-date no matter what.
296+
switch aws.StringValue(cr.Status.AtProvider.TableStatus) {
297+
case string(svcapitypes.TableStatus_SDK_UPDATING), string(svcapitypes.TableStatus_SDK_CREATING):
298+
return true, nil
299+
}
300+
301+
// Similarly, a table that's currently updating its SSE status can't be
302+
// updated, so we temporarily consider it to be up-to-date.
303+
if cr.Status.AtProvider.SSEDescription != nil && aws.StringValue(cr.Status.AtProvider.SSEDescription.Status) == string(svcapitypes.SSEStatus_UPDATING) {
304+
return true, nil
305+
}
306+
292307
patch, err := createPatch(resp, &cr.Spec.ForProvider)
293308
if err != nil {
294309
return false, err
295310
}
311+
296312
return cmp.Equal(&svcapitypes.TableParameters{}, patch,
297313
cmpopts.IgnoreTypes(&xpv1.Reference{}, &xpv1.Selector{}, []xpv1.Reference{}),
298314
cmpopts.IgnoreFields(svcapitypes.TableParameters{}, "Region", "Tags", "GlobalSecondaryIndexes", "KeySchema", "LocalSecondaryIndexes", "CustomTableParameters")), nil
@@ -302,59 +318,55 @@ type updateClient struct {
302318
client svcsdkapi.DynamoDBAPI
303319
}
304320

305-
// nolint: gocyclo
306-
func (e *updateClient) preUpdate(_ context.Context, cr *svcapitypes.Table, u *svcsdk.UpdateTableInput) error {
307-
switch aws.StringValue(cr.Status.AtProvider.TableStatus) {
308-
case string(svcapitypes.TableStatus_SDK_UPDATING), string(svcapitypes.TableStatus_SDK_CREATING):
309-
return nil
310-
}
311-
t, err := e.client.DescribeTable(&svcsdk.DescribeTableInput{TableName: aws.String(meta.GetExternalName(cr))})
321+
func (e *updateClient) preUpdate(ctx context.Context, cr *svcapitypes.Table, u *svcsdk.UpdateTableInput) error {
322+
323+
filtered := &svcsdk.UpdateTableInput{TableName: aws.String(meta.GetExternalName(cr))}
324+
325+
// The AWS API requires us to do one kind of update at a time per
326+
// https://github.com/aws/aws-sdk-go/blob/v1.34.32/service/dynamodb/api.go#L5605
327+
// This means that we need to return a filtered UpdateTableInput that
328+
// contains at most one thing that needs updating. In order to be
329+
// eventually consistent (i.e. to eventually update all the things, one
330+
// on each reconcile pass) we need to determine the 'next' thing to
331+
// update on each pass. This means we need to diff actual vs desired
332+
// state here inside preUpdate. Unfortunately we read the actual state
333+
// during Observe, but don't typically pass it to update. We could stash
334+
// the observed state in a cache during postObserve then read it here,
335+
// but we typically prefer to be as stateless as possible even if it
336+
// means redundant API calls.
337+
out, err := e.client.DescribeTableWithContext(ctx, &svcsdk.DescribeTableInput{TableName: aws.String(meta.GetExternalName(cr))})
312338
if err != nil {
313339
return aws.Wrap(err, errDescribe)
314340
}
315341

316-
newUpdateObj := &svcsdk.UpdateTableInput{
317-
TableName: aws.String(meta.GetExternalName(cr)),
342+
p, err := createPatch(out, &cr.Spec.ForProvider)
343+
if err != nil {
344+
return err
318345
}
319-
// NOTE(muvaf): AWS API prohibits doing those calls in the same call.
320-
// See https://github.com/aws/aws-sdk-go/blob/v1.34.32/service/dynamodb/api.go#L5605
321-
switch {
322-
case cr.Spec.ForProvider.ProvisionedThroughput != nil &&
323-
(aws.Int64Value(t.Table.ProvisionedThroughput.ReadCapacityUnits) != aws.Int64Value(cr.Spec.ForProvider.ProvisionedThroughput.ReadCapacityUnits) ||
324-
aws.Int64Value(t.Table.ProvisionedThroughput.WriteCapacityUnits) != aws.Int64Value(cr.Spec.ForProvider.ProvisionedThroughput.WriteCapacityUnits)):
325-
newUpdateObj.ProvisionedThroughput = u.ProvisionedThroughput
326-
// NOTE(muvaf): Unless StreamEnabled is changed, updating stream specification
327-
// won't work.
328-
case cr.Spec.ForProvider.StreamSpecification != nil &&
329-
(awsgo.BoolValue(t.Table.StreamSpecification.StreamEnabled) != awsgo.BoolValue(cr.Spec.ForProvider.StreamSpecification.StreamEnabled)):
330-
newUpdateObj.StreamSpecification = u.StreamSpecification
331-
332-
case cr.Spec.ForProvider.SSESpecification != nil &&
333-
(aws.StringValue(t.Table.SSEDescription.KMSMasterKeyArn) != aws.StringValue(cr.Spec.ForProvider.SSESpecification.KMSMasterKeyID)) &&
334-
(aws.StringValue(t.Table.SSEDescription.SSEType) == aws.StringValue(cr.Spec.ForProvider.SSESpecification.SSEType)):
335-
newUpdateObj.SSESpecification = u.SSESpecification
336346

337-
case cr.Spec.ForProvider.BillingMode != nil &&
338-
cr.Spec.ForProvider.BillingMode != t.Table.BillingModeSummary.BillingMode:
339-
newUpdateObj.BillingMode = u.BillingMode
340-
341-
// TODO(muvaf): ReplicationGroupUpdate and GlobalSecondaryIndexUpdate features
342-
// are not implemented yet.
347+
// TODO(muvaf): Implement ReplicaUpdates and GlobalSecondaryIndexUpdates.
348+
switch {
349+
case p.ProvisionedThroughput != nil:
350+
filtered.ProvisionedThroughput = u.ProvisionedThroughput
351+
case p.StreamSpecification != nil:
352+
// NOTE(muvaf): Unless StreamEnabled is changed, updating stream
353+
// specification won't work.
354+
filtered.StreamSpecification = u.StreamSpecification
355+
case p.SSESpecification != nil:
356+
// NOTE(negz): Attempting to update the KMSMasterKeyId to its
357+
// current value returns an error
358+
filtered.SSESpecification = &svcsdk.SSESpecification{
359+
Enabled: u.SSESpecification.Enabled,
360+
SSEType: u.SSESpecification.SSEType,
361+
}
362+
if p.SSESpecification.KMSMasterKeyID != nil {
363+
filtered.SSESpecification.KMSMasterKeyId = u.SSESpecification.KMSMasterKeyId
364+
}
365+
case p.BillingMode != nil:
366+
filtered.BillingMode = u.BillingMode
343367
}
344368

345-
*u = *newUpdateObj
369+
*u = *filtered
346370

347371
return nil
348372
}
349-
350-
func postUpdate(_ context.Context, cr *svcapitypes.Table, t *svcsdk.UpdateTableOutput, upd managed.ExternalUpdate, err error) (managed.ExternalUpdate, error) {
351-
if err != nil {
352-
// no update required - newUpdateObj was empty
353-
// no enum available for this skip
354-
if strings.Contains(err.Error(), "ProvisionedThroughput, BillingMode, UpdateStreamEnabled, GlobalSecondaryIndexUpdates or SSESpecification or ReplicaUpdates is required") {
355-
return upd, nil
356-
}
357-
return managed.ExternalUpdate{}, err
358-
}
359-
return upd, nil
360-
}

0 commit comments

Comments
 (0)