From fd12586497e5bfa42d37def5e545f07cc28f7740 Mon Sep 17 00:00:00 2001 From: Agustin Bettati Date: Tue, 20 Jan 2026 17:23:31 +0100 Subject: [PATCH 1/6] TODO revert - triggering CI run --- cfn-resources/organization/cmd/resource/resource.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cfn-resources/organization/cmd/resource/resource.go b/cfn-resources/organization/cmd/resource/resource.go index 4325d6670..d2fd46d9d 100644 --- a/cfn-resources/organization/cmd/resource/resource.go +++ b/cfn-resources/organization/cmd/resource/resource.go @@ -223,6 +223,8 @@ func Delete(req handler.Request, prevModel *Model, currentModel *Model) (handler deleteRequest := conn.OrganizationsApi.DeleteOrg(ctx, *currentModel.OrgId) + // TODO delete + // Since the Delete API is synchronous and takes more than 1 minute most of the time, // we need to make the call in a goroutine and return a progress event // after 10 Seconds. Reason for wait is that the Delete API From 754df3842d80ed0a66f4d462ab73554272923d99 Mon Sep 17 00:00:00 2001 From: Agustin Bettati Date: Tue, 20 Jan 2026 17:55:30 +0100 Subject: [PATCH 2/6] retry delete request after 20 seconds if 500 Internal Server Error is encountered --- .../organization/cmd/resource/resource.go | 77 +++++++++++-------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/cfn-resources/organization/cmd/resource/resource.go b/cfn-resources/organization/cmd/resource/resource.go index d2fd46d9d..371aec774 100644 --- a/cfn-resources/organization/cmd/resource/resource.go +++ b/cfn-resources/organization/cmd/resource/resource.go @@ -221,40 +221,55 @@ func Delete(req handler.Request, prevModel *Model, currentModel *Model) (handler return handleError(response, constants.DELETE, err) } - deleteRequest := conn.OrganizationsApi.DeleteOrg(ctx, *currentModel.OrgId) - - // TODO delete - - // Since the Delete API is synchronous and takes more than 1 minute most of the time, - // we need to make the call in a goroutine and return a progress event - // after 10 Seconds. Reason for wait is that the Delete API - // may throw error immediately if the resource is not found. - - responseChan := make(chan DeleteResponse, 1) - go func() { - response, err := deleteRequest.Execute() - responseChan <- DeleteResponse{Error: err, Response: response} - }() - currentModel.IsDeleted = util.Pointer(false) - select { - case responseMsg := <-responseChan: - if responseMsg.Error != nil { - return handleError(responseMsg.Response, constants.DELETE, responseMsg.Error) + + // Encapsulate the delete+wait logic so the same flow can be used on retry. + runDelete := func() (*DeleteResponse, *handler.ProgressEvent) { + deleteRequest := conn.OrganizationsApi.DeleteOrg(ctx, *currentModel.OrgId) + + // Since the Delete API is synchronous and takes more than 1 minute most of the time, + // we need to make the call in a goroutine and return a progress event + // after 10 Seconds. Reason for wait is that the Delete API + // may throw error immediately if the resource is not found. + + responseChan := make(chan DeleteResponse, 1) + go func() { + response, err := deleteRequest.Execute() + responseChan <- DeleteResponse{Error: err, Response: response} + }() + + select { + case responseMsg := <-responseChan: + return &responseMsg, nil + case <-time.After(30 * time.Second): + // If the Delete is not completed in the above time, + // we return a progress event with inProgress status and callback context + return nil, &handler.ProgressEvent{ + OperationStatus: handler.InProgress, + Message: DeleteInProgress, + ResourceModel: currentModel, + CallbackDelaySeconds: CallBackSeconds, + CallbackContext: map[string]interface{}{ + constants.StateName: DeletingState, + }, + } } + } - case <-time.After(30 * time.Second): - // If the Delete is not completed in the above time, - // we return a progress event with inProgress status and callback context - return handler.ProgressEvent{ - OperationStatus: handler.InProgress, - Message: DeleteInProgress, - ResourceModel: currentModel, - CallbackDelaySeconds: CallBackSeconds, - CallbackContext: map[string]interface{}{ - constants.StateName: DeletingState, - }, - }, nil + responseMsg, progressEvent := runDelete() + if responseMsg.Error != nil { + // Retry once on transient server error, waiting 20 seconds before retrying request + // This covers case of contract tests which create and delete an org within seconds, encountering an error while deleting the org + if responseMsg.Response != nil && responseMsg.Response.StatusCode == http.StatusInternalServerError { + time.Sleep(20 * time.Second) + responseMsg, progressEvent = runDelete() + } + } + if progressEvent != nil { + return *progressEvent, nil + } + if responseMsg.Error != nil { + return handleError(responseMsg.Response, constants.DELETE, responseMsg.Error) } return handler.ProgressEvent{ From 1528c5aab6d002ad1a2e9765d6e1a5edb586aa32 Mon Sep 17 00:00:00 2001 From: Agustin Bettati Date: Tue, 20 Jan 2026 21:55:49 +0100 Subject: [PATCH 3/6] extract into a function --- .../organization/cmd/resource/resource.go | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/cfn-resources/organization/cmd/resource/resource.go b/cfn-resources/organization/cmd/resource/resource.go index 371aec774..16bc9ca05 100644 --- a/cfn-resources/organization/cmd/resource/resource.go +++ b/cfn-resources/organization/cmd/resource/resource.go @@ -223,46 +223,13 @@ func Delete(req handler.Request, prevModel *Model, currentModel *Model) (handler currentModel.IsDeleted = util.Pointer(false) - // Encapsulate the delete+wait logic so the same flow can be used on retry. - runDelete := func() (*DeleteResponse, *handler.ProgressEvent) { - deleteRequest := conn.OrganizationsApi.DeleteOrg(ctx, *currentModel.OrgId) - - // Since the Delete API is synchronous and takes more than 1 minute most of the time, - // we need to make the call in a goroutine and return a progress event - // after 10 Seconds. Reason for wait is that the Delete API - // may throw error immediately if the resource is not found. - - responseChan := make(chan DeleteResponse, 1) - go func() { - response, err := deleteRequest.Execute() - responseChan <- DeleteResponse{Error: err, Response: response} - }() - - select { - case responseMsg := <-responseChan: - return &responseMsg, nil - case <-time.After(30 * time.Second): - // If the Delete is not completed in the above time, - // we return a progress event with inProgress status and callback context - return nil, &handler.ProgressEvent{ - OperationStatus: handler.InProgress, - Message: DeleteInProgress, - ResourceModel: currentModel, - CallbackDelaySeconds: CallBackSeconds, - CallbackContext: map[string]interface{}{ - constants.StateName: DeletingState, - }, - } - } - } - - responseMsg, progressEvent := runDelete() + responseMsg, progressEvent := runDelete(ctx, conn, currentModel) if responseMsg.Error != nil { // Retry once on transient server error, waiting 20 seconds before retrying request // This covers case of contract tests which create and delete an org within seconds, encountering an error while deleting the org if responseMsg.Response != nil && responseMsg.Response.StatusCode == http.StatusInternalServerError { time.Sleep(20 * time.Second) - responseMsg, progressEvent = runDelete() + responseMsg, progressEvent = runDelete(ctx, conn, currentModel) } } if progressEvent != nil { @@ -278,6 +245,39 @@ func Delete(req handler.Request, prevModel *Model, currentModel *Model) (handler ResourceModel: nil}, nil } +// Encapsulate the delete+wait logic so the same flow can be used on retry. +func runDelete(ctx context.Context, conn *admin.APIClient, currentModel *Model) (*DeleteResponse, *handler.ProgressEvent) { + deleteRequest := conn.OrganizationsApi.DeleteOrg(ctx, *currentModel.OrgId) + + // Since the Delete API is synchronous and takes more than 1 minute most of the time, + // we need to make the call in a goroutine and return a progress event + // after 10 Seconds. Reason for wait is that the Delete API + // may throw error immediately if the resource is not found. + + responseChan := make(chan DeleteResponse, 1) + go func() { + response, err := deleteRequest.Execute() + responseChan <- DeleteResponse{Error: err, Response: response} + }() + + select { + case responseMsg := <-responseChan: + return &responseMsg, nil + case <-time.After(30 * time.Second): + // If the Delete is not completed in the above time, + // we return a progress event with inProgress status and callback context + return nil, &handler.ProgressEvent{ + OperationStatus: handler.InProgress, + Message: DeleteInProgress, + ResourceModel: currentModel, + CallbackDelaySeconds: CallBackSeconds, + CallbackContext: map[string]interface{}{ + constants.StateName: DeletingState, + }, + } + } +} + func deleteCallback(ctx context.Context, conn *admin.APIClient, currentModel *Model) (handler.ProgressEvent, error) { // Read before delete org, response, err := currentModel.getOrgDetails(ctx, conn, currentModel) From 518c582ec4ad01547b4816048947401f22955bac Mon Sep 17 00:00:00 2001 From: Agustin Bettati Date: Wed, 21 Jan 2026 11:56:34 +0100 Subject: [PATCH 4/6] add file from make build --- .../organization/cmd/resource/config.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 cfn-resources/organization/cmd/resource/config.go diff --git a/cfn-resources/organization/cmd/resource/config.go b/cfn-resources/organization/cmd/resource/config.go new file mode 100644 index 000000000..4d9eb7831 --- /dev/null +++ b/cfn-resources/organization/cmd/resource/config.go @@ -0,0 +1,19 @@ +// Code generated by 'cfn generate', changes will be undone by the next invocation. DO NOT EDIT. +// Updates to this type are made my editing the schema file and executing the 'generate' command. +package resource + +import "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler" + +// TypeConfiguration is autogenerated from the json schema +type TypeConfiguration struct { +} + +// Configuration returns a resource's configuration. +func Configuration(req handler.Request) (*TypeConfiguration, error) { + // Populate the type configuration + typeConfig := &TypeConfiguration{} + if err := req.UnmarshalTypeConfig(typeConfig); err != nil { + return typeConfig, err + } + return typeConfig, nil +} From faa0bc25616861723d1968a7cd01365bb63d0082 Mon Sep 17 00:00:00 2001 From: Agustin Bettati Date: Wed, 21 Jan 2026 15:49:45 +0100 Subject: [PATCH 5/6] adding log statement in case of retry --- cfn-resources/organization/cmd/resource/resource.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cfn-resources/organization/cmd/resource/resource.go b/cfn-resources/organization/cmd/resource/resource.go index 16bc9ca05..1607194db 100644 --- a/cfn-resources/organization/cmd/resource/resource.go +++ b/cfn-resources/organization/cmd/resource/resource.go @@ -228,6 +228,7 @@ func Delete(req handler.Request, prevModel *Model, currentModel *Model) (handler // Retry once on transient server error, waiting 20 seconds before retrying request // This covers case of contract tests which create and delete an org within seconds, encountering an error while deleting the org if responseMsg.Response != nil && responseMsg.Response.StatusCode == http.StatusInternalServerError { + logger.Warnf("Transient server error while deleting organization, retrying in 20 seconds") time.Sleep(20 * time.Second) responseMsg, progressEvent = runDelete(ctx, conn, currentModel) } From 3f3d9af5f953a84ebe9c8339575a54d589c04dce Mon Sep 17 00:00:00 2001 From: Agustin Bettati Date: Wed, 21 Jan 2026 15:55:01 +0100 Subject: [PATCH 6/6] fixing linting --- cfn-resources/organization/cmd/resource/resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfn-resources/organization/cmd/resource/resource.go b/cfn-resources/organization/cmd/resource/resource.go index 1607194db..a017f8e52 100644 --- a/cfn-resources/organization/cmd/resource/resource.go +++ b/cfn-resources/organization/cmd/resource/resource.go @@ -228,7 +228,7 @@ func Delete(req handler.Request, prevModel *Model, currentModel *Model) (handler // Retry once on transient server error, waiting 20 seconds before retrying request // This covers case of contract tests which create and delete an org within seconds, encountering an error while deleting the org if responseMsg.Response != nil && responseMsg.Response.StatusCode == http.StatusInternalServerError { - logger.Warnf("Transient server error while deleting organization, retrying in 20 seconds") + _, _ = logger.Warnf("Transient server error while deleting organization, retrying in 20 seconds") time.Sleep(20 * time.Second) responseMsg, progressEvent = runDelete(ctx, conn, currentModel) }