@@ -20,6 +20,7 @@ import (
2020 "context"
2121 "encoding/json"
2222 "sort"
23+ "strings"
2324 "time"
2425
2526 svcsdk "github.com/aws/aws-sdk-go/service/dynamodb"
@@ -52,6 +53,7 @@ func SetupTable(mgr ctrl.Manager, l logging.Logger, rl workqueue.RateLimiter, po
5253 e .postObserve = postObserve
5354 e .preCreate = preCreate
5455 e .preDelete = preDelete
56+ e .postDelete = postDelete
5557 e .lateInitialize = lateInitialize
5658 e .isUpToDate = isUpToDate
5759 u := & updateClient {client : e .client }
@@ -89,6 +91,20 @@ func preDelete(_ context.Context, cr *svcapitypes.Table, obj *svcsdk.DeleteTable
8991 return false , nil
9092}
9193
94+ func postDelete (_ context.Context , _ * svcapitypes.Table , _ * svcsdk.DeleteTableOutput , err error ) error {
95+ if err == nil {
96+ return nil
97+ }
98+ // The DynamoDB API returns this error when you try to delete a table
99+ // that is already being deleted. Unfortunately we're passed a v1 SDK
100+ // error that has been munged by aws.Wrap, so we can't use errors.As to
101+ // identify it and must fall back to string matching.
102+ if strings .Contains (err .Error (), "ResourceInUseException" ) {
103+ return nil
104+ }
105+ return err
106+ }
107+
92108func postObserve (_ context.Context , cr * svcapitypes.Table , resp * svcsdk.DescribeTableOutput , obs managed.ExternalObservation , err error ) (managed.ExternalObservation , error ) {
93109 if err != nil {
94110 return managed.ExternalObservation {}, err
@@ -144,8 +160,6 @@ func (e *tagger) Initialize(ctx context.Context, mg resource.Managed) error {
144160 return errors .Wrap (e .kube .Update (ctx , cr ), "cannot update Table Spec" )
145161}
146162
147- // NOTE(muvaf): The rest is taken from manually written controller.
148-
149163func lateInitialize (in * svcapitypes.TableParameters , t * svcsdk.DescribeTableOutput ) error { // nolint:gocyclo,unparam
150164 if t == nil {
151165 return nil
@@ -166,7 +180,20 @@ func lateInitialize(in *svcapitypes.TableParameters, t *svcsdk.DescribeTableOutp
166180 if in .KeySchema == nil && len (t .Table .KeySchema ) != 0 {
167181 in .KeySchema = buildAlphaKeyElements (t .Table .KeySchema )
168182 }
169-
183+ if in .BillingMode == nil {
184+ // NOTE(negz): As far as I can tell DescribeTableOutput only
185+ // includes a BillingModeSummary when the billing mode is set to
186+ // PAY_PER_REQUEST. PROVISIONED seems to be he implied default.
187+ // Late initializing a value that the API only implies is
188+ // perhaps a bit of a violation, but it avoids a false positive
189+ // in our IsUpToDate logic which would otherwise detect a diff
190+ // between our desired state (PROVISIONED) and the actual state
191+ // (unspecified).
192+ in .BillingMode = aws .String (svcsdk .BillingModeProvisioned )
193+ if t .Table .BillingModeSummary != nil {
194+ in .BillingMode = t .Table .BillingModeSummary .BillingMode
195+ }
196+ }
170197 if in .ProvisionedThroughput == nil && t .Table .ProvisionedThroughput != nil {
171198 in .ProvisionedThroughput = & svcapitypes.ProvisionedThroughput {
172199 ReadCapacityUnits : t .Table .ProvisionedThroughput .ReadCapacityUnits ,
@@ -187,17 +214,20 @@ func lateInitialize(in *svcapitypes.TableParameters, t *svcsdk.DescribeTableOutp
187214 in .SSESpecification .SSEType = t .Table .SSEDescription .SSEType
188215 }
189216 }
190- if in .StreamSpecification == nil && t .Table .StreamSpecification != nil {
191- in .StreamSpecification = & svcapitypes.StreamSpecification {
192- StreamEnabled : t .Table .StreamSpecification .StreamEnabled ,
193- StreamViewType : t .Table .StreamSpecification .StreamViewType ,
217+ if in .StreamSpecification == nil {
218+ // NOTE(negz): We late initialize StreamEnabled to false to
219+ // avoid IsUpToDate thinking it needs to explicitly make an
220+ // update to set StreamEnabled to false. DescribeTableOutput
221+ // omits StreamSpecification entirely when it's not enabled.
222+ in .StreamSpecification = & svcapitypes.StreamSpecification {StreamEnabled : aws .Bool (false , aws .FieldRequired )}
223+ if t .Table .StreamSpecification != nil {
224+ in .StreamSpecification = & svcapitypes.StreamSpecification {
225+ StreamEnabled : t .Table .StreamSpecification .StreamEnabled ,
226+ StreamViewType : t .Table .StreamSpecification .StreamViewType ,
227+ }
194228 }
195229 }
196230
197- if in .BillingMode == nil && t .Table .BillingModeSummary != nil {
198- in .BillingMode = t .Table .BillingModeSummary .BillingMode
199- }
200-
201231 return nil
202232}
203233
@@ -290,10 +320,11 @@ func createPatch(in *svcsdk.DescribeTableOutput, target *svcapitypes.TableParame
290320}
291321
292322func isUpToDate (cr * svcapitypes.Table , resp * svcsdk.DescribeTableOutput ) (bool , error ) {
293- // A table that's currently updating or creating can't be updated, so we
294- // temporarily consider it to be up-to-date no matter what.
323+ // A table that's currently creating, deleting, or updating can't be
324+ // updated, so we temporarily consider it to be up-to-date no matter
325+ // what.
295326 switch aws .StringValue (cr .Status .AtProvider .TableStatus ) {
296- case string (svcapitypes .TableStatus_SDK_UPDATING ), string (svcapitypes .TableStatus_SDK_CREATING ):
327+ case string (svcapitypes .TableStatus_SDK_UPDATING ), string (svcapitypes .TableStatus_SDK_CREATING ), string ( svcapitypes . TableStatus_SDK_DELETING ) :
297328 return true , nil
298329 }
299330
@@ -308,14 +339,25 @@ func isUpToDate(cr *svcapitypes.Table, resp *svcsdk.DescribeTableOutput) (bool,
308339 return false , err
309340 }
310341
342+ // TODO(negz): Support updating tags if possible.
343+ // https://github.com/crossplane/provider-aws/issues/945
344+
311345 // At least one of ProvisionedThroughput, BillingMode, UpdateStreamEnabled,
312346 // GlobalSecondaryIndexUpdates or SSESpecification or ReplicaUpdates is
313347 // required.
314348 switch {
315- case patch .ProvisionedThroughput != nil :
316- return false , nil
317349 case patch .BillingMode != nil :
318350 return false , nil
351+ case patch .ProvisionedThroughput != nil :
352+ // TODO(negz): DescribeTableOutput appears to report that
353+ // ProvisionedThroughput is 0 when the billing mode is set to
354+ // PAY_PER_REQUEST. This means that if the billing mode is
355+ // changed from PROVISIONED to PAY_PER_REQUEST the provisioned
356+ // throughput must be set to 0 read and write or Crossplane will
357+ // think an update is needed and the update will fail because
358+ // you can't set provisioned throughput when the billing mode is
359+ // set to PAY_PER_REQUEST.
360+ return false , nil
319361 case patch .StreamSpecification != nil :
320362 return false , nil
321363 case len (diffGlobalSecondaryIndexes (GenerateGlobalSecondaryIndexDescriptions (cr .Spec .ForProvider .GlobalSecondaryIndexes ), resp .Table .GlobalSecondaryIndexes )) != 0 :
@@ -329,7 +371,6 @@ type updateClient struct {
329371}
330372
331373func (e * updateClient ) preUpdate (ctx context.Context , cr * svcapitypes.Table , u * svcsdk.UpdateTableInput ) error {
332-
333374 filtered := & svcsdk.UpdateTableInput {
334375 TableName : aws .String (meta .GetExternalName (cr )),
335376 AttributeDefinitions : u .AttributeDefinitions ,
@@ -358,7 +399,17 @@ func (e *updateClient) preUpdate(ctx context.Context, cr *svcapitypes.Table, u *
358399 }
359400 gsiUpdates := diffGlobalSecondaryIndexes (GenerateGlobalSecondaryIndexDescriptions (cr .Spec .ForProvider .GlobalSecondaryIndexes ), out .Table .GlobalSecondaryIndexes )
360401 switch {
402+ case p .BillingMode != nil :
403+ filtered .BillingMode = u .BillingMode
404+
405+ // NOTE(negz): You must include provisioned throughput when
406+ // updating the billing mode to PROVISIONED.
407+ if aws .StringValue (u .BillingMode ) == string (svcapitypes .BillingMode_PROVISIONED ) {
408+ filtered .ProvisionedThroughput = u .ProvisionedThroughput
409+ }
361410 case p .ProvisionedThroughput != nil :
411+ // NOTE(negz): You may only included provisioned throughput when
412+ // the billing mode is PROVISIONED.
362413 filtered .ProvisionedThroughput = u .ProvisionedThroughput
363414 case p .StreamSpecification != nil :
364415 // NOTE(muvaf): Unless StreamEnabled is changed, updating stream
@@ -374,8 +425,6 @@ func (e *updateClient) preUpdate(ctx context.Context, cr *svcapitypes.Table, u *
374425 if p .SSESpecification .KMSMasterKeyID != nil {
375426 filtered .SSESpecification .KMSMasterKeyId = u .SSESpecification .KMSMasterKeyId
376427 }
377- case p .BillingMode != nil :
378- filtered .BillingMode = u .BillingMode
379428 case len (gsiUpdates ) != 0 :
380429 filtered .SetGlobalSecondaryIndexUpdates (gsiUpdates )
381430 }
0 commit comments