@@ -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
291293func 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