Skip to content
Open
Show file tree
Hide file tree
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
4 changes: 4 additions & 0 deletions aws_organizations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ Before getting started, ensure you have the following prerequisites:
10. Move to the Review page and Click Submit. This launches the creation process for the Datadog StackSet. This could take a while depending on how many accounts need to be integrated. Ensure that the StackSet successfully creates all resources before proceeding.
11. After the stack is created, go back to the AWS integration tile in Datadog and click Ready!

## Pre-existing Integrations

If you deploy this StackSet to an AWS Organization or OU that includes accounts which already have a Datadog integration configured (e.g., via a standalone stack), the template will detect the existing integration and update it rather than attempting to create a duplicate. This prevents a 409 conflict error that would otherwise trigger a rollback and delete the pre-existing integration. Additionally, if the stack fails for any other reason (e.g., IAM role name conflict) and rolls back, the rollback will preserve the pre-existing integration rather than deleting it.

## Datadog::Integrations::AWS

This CloudFormation StackSet only manages *AWS* resources required by the Datadog AWS integration. The actual integration configuration within Datadog platform can also be managed in CloudFormation using the custom resource [Datadog::Integrations::AWS](https://github.com/DataDog/datadog-cloudformation-resources/tree/master/datadog-integrations-aws-handler) if you like.
Expand Down
50 changes: 40 additions & 10 deletions aws_organizations/main_organizations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -272,13 +272,42 @@ Resources:
try:
# Call Datadog API and report response back to CloudFormation
uuid = ""
if event["RequestType"] != "Create":
preexisting = False
if event["RequestType"] == "Create":
# Check if account already exists to avoid 409 conflict.
# A failed POST triggers rollback which deletes the pre-existing integration,
# so we use PATCH instead of POST if the account is already registered.
datadog_account_response = get_datadog_account(event)
code = datadog_account_response.getcode()
data = datadog_account_response.read()
if code == 200 and data:
json_response = json.loads(data)
if len(json_response["data"]) > 0:
LOGGER.info("Account already exists in Datadog. Using PATCH to update instead of POST to avoid conflict.")
uuid = json_response["data"][0]["id"]
method = "PATCH"
preexisting = True
elif event["RequestType"] == "Delete":
# If this resource was created by patching a pre-existing integration,
# do not delete it on stack rollback/deletion.
physical_resource_id = event.get("PhysicalResourceId", "")
if physical_resource_id == "PREEXISTING":
LOGGER.info("Integration was pre-existing. Skipping delete to preserve existing integration.")
cfResponse = {"Message": "Skipped delete for pre-existing integration."}
cfnresponse.send(event, context, responseStatus="SUCCESS", responseData=cfResponse, reason=None)
return
datadog_account_response = get_datadog_account(event)
uuid = extract_uuid_from_account_response(event, context, datadog_account_response)
if uuid is None:
return
else:
datadog_account_response = get_datadog_account(event)
uuid = extract_uuid_from_account_response(event, context, datadog_account_response)
if uuid is None:
return
response = call_datadog_api(uuid, event, method)
cfn_response_send_api_result(event, context, method, response)
physical_id = "PREEXISTING" if preexisting else None
cfn_response_send_api_result(event, context, method, response, physical_id)

except Exception as e:
LOGGER.info("Failed - exception thrown during processing.")
Expand Down Expand Up @@ -315,7 +344,7 @@ Resources:
return None


def cfn_response_send_api_result(event, context, method, response):
def cfn_response_send_api_result(event, context, method, response, physical_id=None):
reason = None
json_response = ""
code = response.getcode()
Expand All @@ -331,13 +360,14 @@ Resources:
if method == "POST" or method == "PATCH":
external_id = json_response["data"]["attributes"]["auth_config"]["external_id"]
cfResponse["ExternalId"] = external_id
cfnresponse.send(
event,
context,
responseStatus=response_status,
responseData=cfResponse,
reason=reason,
)
send_kwargs = {
"responseStatus": response_status,
"responseData": cfResponse,
"reason": reason,
}
if physical_id:
send_kwargs["physicalResourceId"] = physical_id
cfnresponse.send(event, context, **send_kwargs)
return
cfn_response_send_failure(event, context, "Datadog API returned error: {}".format(json_response))

Expand Down
2 changes: 1 addition & 1 deletion aws_organizations/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v4.1.0
v4.1.1
4 changes: 4 additions & 0 deletions aws_quickstart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ This template creates the following AWS resources required by the Datadog AWS in
- The Datadog Forwarder only deploys to the AWS region where the AWS integration CloudFormation stack is launched. If you operate in multiple AWS regions, you can deploy the Forwarder stack (without the rest of the AWS integration stack) directly to other regions as needed.
- The Datadog Forwarder is installed with default settings as a nested stack, edit the nested stack directly to update the forwarder specific settings.

## Pre-existing Integrations

If you deploy this CloudFormation stack to an AWS account that already has a Datadog integration configured, the stack will detect the existing integration and update it rather than attempting to create a duplicate. This prevents a 409 conflict error that would otherwise trigger a rollback and delete the pre-existing integration. Additionally, if the stack fails for any other reason (e.g., IAM role name conflict) and rolls back, the rollback will preserve the pre-existing integration rather than deleting it.

## Updating your CloudFormation Stack

As of v2.0.0 of the aws_quickstart template updates to the stack parameters are supported. Updates should generally be made to the root CloudFormation Stack (entitled DatadogIntegration by default). We do not support updating the API Key or APP Key fields to point to a different Datadog Organization - if these are updated they must point to the original Organization. You can also update the version of the template used by selecting "Replace existing template" while updating your CloudFormation Stack. You must select a version number with the same major version as your current template.
Expand Down
50 changes: 40 additions & 10 deletions aws_quickstart/datadog_integration_api_call_v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -237,13 +237,42 @@ Resources:
try:
# Call Datadog API and report response back to CloudFormation
uuid = ""
if event["RequestType"] != "Create":
preexisting = False
if event["RequestType"] == "Create":
# Check if account already exists to avoid 409 conflict.
# A failed POST triggers rollback which deletes the pre-existing integration,
# so we use PATCH instead of POST if the account is already registered.
datadog_account_response = get_datadog_account(event)
code = datadog_account_response.getcode()
data = datadog_account_response.read()
if code == 200 and data:
json_response = json.loads(data)
if len(json_response["data"]) > 0:
LOGGER.info("Account already exists in Datadog. Using PATCH to update instead of POST to avoid conflict.")
uuid = json_response["data"][0]["id"]
method = "PATCH"
preexisting = True
elif event["RequestType"] == "Delete":
# If this resource was created by patching a pre-existing integration,
# do not delete it on stack rollback/deletion.
physical_resource_id = event.get("PhysicalResourceId", "")
if physical_resource_id == "PREEXISTING":
LOGGER.info("Integration was pre-existing. Skipping delete to preserve existing integration.")
cfResponse = {"Message": "Skipped delete for pre-existing integration."}
cfnresponse.send(event, context, responseStatus="SUCCESS", responseData=cfResponse, reason=None)
return
datadog_account_response = get_datadog_account(event)
uuid = extract_uuid_from_account_response(event, context, datadog_account_response)
if uuid is None:
return
else:
datadog_account_response = get_datadog_account(event)
uuid = extract_uuid_from_account_response(event, context, datadog_account_response)
if uuid is None:
return
response = call_datadog_api(uuid, event, method)
cfn_response_send_api_result(event, context, method, response)
physical_id = "PREEXISTING" if preexisting else None
cfn_response_send_api_result(event, context, method, response, physical_id)

except Exception as e:
LOGGER.info("Failed - exception thrown during processing.")
Expand Down Expand Up @@ -280,7 +309,7 @@ Resources:
return None


def cfn_response_send_api_result(event, context, method, response):
def cfn_response_send_api_result(event, context, method, response, physical_id=None):
reason = None
json_response = ""
code = response.getcode()
Expand All @@ -296,13 +325,14 @@ Resources:
if method == "POST" or method == "PATCH":
external_id = json_response["data"]["attributes"]["auth_config"]["external_id"]
cfResponse["ExternalId"] = external_id
cfnresponse.send(
event,
context,
responseStatus=response_status,
responseData=cfResponse,
reason=reason,
)
send_kwargs = {
"responseStatus": response_status,
"responseData": cfResponse,
"reason": reason,
}
if physical_id:
send_kwargs["physicalResourceId"] = physical_id
cfnresponse.send(event, context, **send_kwargs)
return
cfn_response_send_failure(event, context, "Datadog API returned error: {}".format(json_response))

Expand Down
2 changes: 1 addition & 1 deletion aws_quickstart/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v4.5.2
v4.5.3
Loading