diff --git a/datadog-integration/data.tf b/datadog-integration/data.tf index a2104ab..e2d414f 100644 --- a/datadog-integration/data.tf +++ b/datadog-integration/data.tf @@ -38,3 +38,4 @@ data "oci_identity_domains_groups" "existing_group_in_domain" { data "oci_identity_domain" "domain" { domain_id = local.matching_domain_id } + diff --git a/datadog-integration/delete_stack.sh b/datadog-integration/delete_stack.sh index 7c8bca2..3841b5e 100755 --- a/datadog-integration/delete_stack.sh +++ b/datadog-integration/delete_stack.sh @@ -6,6 +6,7 @@ export OCI_CLI_SUPPRESS_FILE_PERMISSIONS_WARNING=True COMPARTMENT=$1 REGION=$2 DISPLAY_NAME=$3 +DEFINED_TAGS_JSON="${4:-}" STACK_IDS=($(oci --region "$REGION" resource-manager stack list --compartment-id $COMPARTMENT --display-name $DISPLAY_NAME --raw-output | jq -r '.data[]."id"')) @@ -14,12 +15,17 @@ if [[ -z "$STACK_IDS" ]]; then exit 0 fi +DEFINED_TAGS_ARG=() +if [[ -n "$DEFINED_TAGS_JSON" ]]; then + DEFINED_TAGS_ARG=(--defined-tags "$DEFINED_TAGS_JSON") +fi + echo "Found... stack... Ids: ${STACK_IDS[@]}" for STACK_ID in "${STACK_IDS[@]}"; do echo "Running...destroy...job...for...stack...: $STACK_ID" JOB_ID=$(oci --region "$REGION" resource-manager job create-destroy-job \ - --stack-id "$STACK_ID" --wait-for-state SUCCEEDED --wait-for-state FAILED \ + --stack-id "$STACK_ID" "${DEFINED_TAGS_ARG[@]}" --wait-for-state SUCCEEDED --wait-for-state FAILED \ --execution-plan-strategy AUTO_APPROVED \ --query "data.id" --raw-output) diff --git a/datadog-integration/locals.tf b/datadog-integration/locals.tf index 4d3c624..8c5a535 100644 --- a/datadog-integration/locals.tf +++ b/datadog-integration/locals.tf @@ -3,6 +3,28 @@ locals { ownedby = "datadog" } + # Defined tags: parsed from user input (multiline namespace.key:value per line) + defined_tags_raw = [ + for line in split("\n", var.defined_tags != null ? var.defined_tags : "") : + trimspace(line) if trimspace(line) != "" + ] + defined_tags = { + for line in local.defined_tags_raw : + # Format: namespace.key:value (split on first : so value can contain colons) + (split(":", line)[0]) => ( + length(split(":", line)) > 1 ? join(":", slice(split(":", line), 1, length(split(":", line)))) : "" + ) + } + + # Nested format for stack create --defined-tags (tags the Stack resource in the compartment) + compartment_defined_tags = length(local.defined_tags) > 0 ? { + for ns in distinct([for k in keys(local.defined_tags) : split(".", k)[0]]) : + ns => { for k, v in local.defined_tags : join(".", slice(split(".", k), 1, length(split(".", k)))) => v if split(".", k)[0] == ns } + } : {} + + # Prebuilt --defined-tags flag for oci resource-manager stack create (avoids heredoc quoting issues) + stack_create_defined_tags_flag = length(keys(local.compartment_defined_tags)) > 0 ? join("", ["--defined-tags '", jsonencode(local.compartment_defined_tags), "'"]) : "" + home_region_name = [ for region in data.oci_identity_region_subscriptions.subscribed_regions.region_subscriptions : region.region_name if region.is_home_region diff --git a/datadog-integration/main.tf b/datadog-integration/main.tf index a7d4dba..5b56dcb 100644 --- a/datadog-integration/main.tf +++ b/datadog-integration/main.tf @@ -201,6 +201,7 @@ module "compartment" { new_compartment_name = local.new_compartment_name parent_compartment_id = var.tenancy_ocid tags = local.tags + defined_tags = local.defined_tags } module "kms" { @@ -210,6 +211,7 @@ module "kms" { compartment_id = module.compartment.id datadog_api_key = var.datadog_api_key tags = local.tags + defined_tags = local.defined_tags } module "auth" { @@ -230,6 +232,7 @@ module "auth" { dg_sch_name = local.dg_sch_name dg_fn_name = local.dg_fn_name dg_policy_name = local.dg_policy_name + defined_tags = local.defined_tags } module "key" { @@ -260,7 +263,8 @@ module "integration" { user_ocid = module.auth[0].user_id subscribed_regions = tolist(local.final_regions_for_stacks) datadog_resource_compartment_id = module.compartment.id - logs_enabled = var.logs_enabled + logs_enabled = var.logs_enabled + defined_tags = local.defined_tags } diff --git a/datadog-integration/modules/auth/main.tf b/datadog-integration/modules/auth/main.tf index 566db5e..1306437 100644 --- a/datadog-integration/modules/auth/main.tf +++ b/datadog-integration/modules/auth/main.tf @@ -137,6 +137,14 @@ resource "oci_identity_domains_user" "dd_auth" { value = freeform_tags.value } } + dynamic "defined_tags" { + for_each = var.defined_tags + content { + namespace = split(".", defined_tags.key)[0] + key = join(".", slice(split(".", defined_tags.key), 1, length(split(".", defined_tags.key)))) + value = defined_tags.value + } + } } } @@ -161,6 +169,14 @@ resource "oci_identity_domains_group" "dd_auth" { value = freeform_tags.value } } + dynamic "defined_tags" { + for_each = var.defined_tags + content { + namespace = split(".", defined_tags.key)[0] + key = join(".", slice(split(".", defined_tags.key), 1, length(split(".", defined_tags.key)))) + value = defined_tags.value + } + } } } @@ -172,11 +188,13 @@ resource "oci_identity_policy" "dd_auth" { statements = [ "Define tenancy usage-report as ocid1.tenancy.oc1..aaaaaaaaned4fkpkisbwjlr56u7cj63lf3wffbilvqknstgtvzub7vhqkggq", "Allow group id ${var.existing_group_id != null && var.existing_group_id != "" ? var.existing_group_id : oci_identity_domains_group.dd_auth[0].ocid} to read all-resources in tenancy", + "Allow group id ${var.existing_group_id != null && var.existing_group_id != "" ? var.existing_group_id : oci_identity_domains_group.dd_auth[0].ocid} to use tag-namespaces in tenancy", "Allow group id ${var.existing_group_id != null && var.existing_group_id != "" ? var.existing_group_id : oci_identity_domains_group.dd_auth[0].ocid} to manage serviceconnectors in compartment id ${var.compartment_id}", "Allow group id ${var.existing_group_id != null && var.existing_group_id != "" ? var.existing_group_id : oci_identity_domains_group.dd_auth[0].ocid} to manage functions-family in compartment id ${var.compartment_id} where ANY {request.permission = 'FN_FUNCTION_UPDATE', request.permission = 'FN_FUNCTION_LIST', request.permission = 'FN_APP_LIST'}", "Endorse group id ${var.existing_group_id != null && var.existing_group_id != "" ? var.existing_group_id : oci_identity_domains_group.dd_auth[0].ocid} to read objects in tenancy usage-report" ] freeform_tags = var.tags + defined_tags = var.defined_tags } resource "oci_identity_domains_dynamic_resource_group" "service_connector" { @@ -210,4 +228,5 @@ resource "oci_identity_policy" "dynamic_group" { "Allow dynamic-group id ${oci_identity_domains_dynamic_resource_group.forwarding_function.ocid} to read secret-bundles in compartment id ${var.compartment_id}" ] freeform_tags = var.tags + defined_tags = var.defined_tags } diff --git a/datadog-integration/modules/auth/variables.tf b/datadog-integration/modules/auth/variables.tf index 675ef02..c64a131 100644 --- a/datadog-integration/modules/auth/variables.tf +++ b/datadog-integration/modules/auth/variables.tf @@ -11,7 +11,13 @@ variable "user_email" { } variable "tags" { - description = "A map of tags to assign to the resource" + description = "A map of freeform tags to assign to the resource" + type = map(string) + default = {} +} + +variable "defined_tags" { + description = "A map of defined tags to assign to the resource" type = map(string) default = {} } diff --git a/datadog-integration/modules/compartment/main.tf b/datadog-integration/modules/compartment/main.tf index 892701f..f7af030 100644 --- a/datadog-integration/modules/compartment/main.tf +++ b/datadog-integration/modules/compartment/main.tf @@ -20,5 +20,6 @@ resource "oci_identity_compartment" "new" { name = var.new_compartment_name description = "Compartment for Datadog generated resources" compartment_id = var.parent_compartment_id - freeform_tags = var.tags + freeform_tags = var.tags + defined_tags = var.defined_tags } diff --git a/datadog-integration/modules/compartment/variables.tf b/datadog-integration/modules/compartment/variables.tf index 59978f2..d5fc1f7 100644 --- a/datadog-integration/modules/compartment/variables.tf +++ b/datadog-integration/modules/compartment/variables.tf @@ -10,7 +10,13 @@ variable "parent_compartment_id" { } variable "tags" { - description = "A map of tags to assign to the compartment" + description = "A map of freeform tags to assign to the compartment" + type = map(string) + default = {} +} + +variable "defined_tags" { + description = "A map of defined tags to assign to the compartment" type = map(string) default = {} } diff --git a/datadog-integration/modules/integration/locals.tf b/datadog-integration/modules/integration/locals.tf index 21c24e2..2bf7b2d 100644 --- a/datadog-integration/modules/integration/locals.tf +++ b/datadog-integration/modules/integration/locals.tf @@ -18,8 +18,9 @@ locals { dd_compartment_id : var.datadog_resource_compartment_id dd_stack_id : try(data.external.stack_info.result.stack_id, "") logs_config : { - Enabled: var.logs_enabled + Enabled : var.logs_enabled } + defined_tags : [for k, v in var.defined_tags : "${k}:${v}"] } } } diff --git a/datadog-integration/modules/integration/variables.tf b/datadog-integration/modules/integration/variables.tf index 695eb7e..b3a2c1e 100644 --- a/datadog-integration/modules/integration/variables.tf +++ b/datadog-integration/modules/integration/variables.tf @@ -54,3 +54,9 @@ variable "logs_enabled" { description = "Indicates if logs should be enabled/disabled" default = false } + +variable "defined_tags" { + type = map(string) + description = "OCI defined tags applied to resources (namespace.key -> value). Sent to Datadog for integration config." + default = {} +} diff --git a/datadog-integration/modules/kms/main.tf b/datadog-integration/modules/kms/main.tf index 56a8aff..b3cb3a7 100644 --- a/datadog-integration/modules/kms/main.tf +++ b/datadog-integration/modules/kms/main.tf @@ -14,6 +14,7 @@ resource "oci_kms_vault" "datadog_vault" { display_name = "datadog-vault" vault_type = "DEFAULT" freeform_tags = var.tags + defined_tags = var.defined_tags } resource "oci_kms_key" "datadog_key" { @@ -25,6 +26,7 @@ resource "oci_kms_key" "datadog_key" { } management_endpoint = oci_kms_vault.datadog_vault.management_endpoint freeform_tags = var.tags + defined_tags = var.defined_tags } resource "oci_vault_secret" "api_key" { @@ -37,5 +39,6 @@ resource "oci_vault_secret" "api_key" { content = base64encode(var.datadog_api_key) } freeform_tags = var.tags + defined_tags = var.defined_tags } diff --git a/datadog-integration/modules/kms/variables.tf b/datadog-integration/modules/kms/variables.tf index 5a91bbb..878fa09 100644 --- a/datadog-integration/modules/kms/variables.tf +++ b/datadog-integration/modules/kms/variables.tf @@ -5,7 +5,13 @@ variable "compartment_id" { variable "tags" { type = map(string) - description = "A map of tags to assign to resources" + description = "A map of freeform tags to assign to resources" + default = {} +} + +variable "defined_tags" { + type = map(string) + description = "A map of defined tags to assign to resources" default = {} } diff --git a/datadog-integration/modules/regional-stacks/locals.tf b/datadog-integration/modules/regional-stacks/locals.tf index 3856f40..530c8f1 100644 --- a/datadog-integration/modules/regional-stacks/locals.tf +++ b/datadog-integration/modules/regional-stacks/locals.tf @@ -1,4 +1,5 @@ locals { + defined_tags_map = jsondecode(var.defined_tags) registry_host = lower("${var.region_key}.ocir.io/iddfxd5j9l2o") metrics_image_path = "${local.registry_host}/oci-datadog-forwarder/metrics:latest" logs_image_path = "${local.registry_host}/oci-datadog-forwarder/logs:latest" @@ -39,5 +40,5 @@ locals { ) # Simple subnet selection logic: use provided OCID or create new - subnet_id = var.subnet_ocid != "" ? var.subnet_ocid : module.vcn[0].subnet_id[local.subnet] + subnet_id = var.subnet_ocid != "" ? var.subnet_ocid : module.subnet[0].subnet_id[local.subnet] } diff --git a/datadog-integration/modules/regional-stacks/main.tf b/datadog-integration/modules/regional-stacks/main.tf index 41e2c79..6810e1c 100644 --- a/datadog-integration/modules/regional-stacks/main.tf +++ b/datadog-integration/modules/regional-stacks/main.tf @@ -17,6 +17,7 @@ resource "oci_functions_function" "logs_function" { display_name = "dd-logs-forwarder" memory_in_mbs = "1024" freeform_tags = var.tags + defined_tags = local.defined_tags_map image = local.logs_image_path image_digest = length(local.image_sha_logs) > 0 ? local.image_sha_logs : null @@ -27,6 +28,7 @@ resource "oci_functions_function" "metrics_function" { display_name = "dd-metrics-forwarder" memory_in_mbs = "512" freeform_tags = var.tags + defined_tags = local.defined_tags_map image = local.metrics_image_path image_digest = length(local.image_sha_metrics) > 0 ? local.image_sha_metrics : null } @@ -37,11 +39,28 @@ module "vcn" { version = "3.6.0" compartment_id = var.compartment_ocid freeform_tags = var.tags + defined_tags = local.defined_tags_map vcn_cidrs = ["10.0.0.0/16"] vcn_dns_label = "ddvcnmodule" vcn_name = local.vcn_name - lockdown_default_seclist = false + lockdown_default_seclist = false + subnets = {} + create_nat_gateway = true + nat_gateway_display_name = local.nat_gateway + create_service_gateway = true + service_gateway_display_name = local.service_gateway +} + +# Same VCN module's subnet submodule; we call it directly so we can pass defined_tags (parent VCN module doesn't). +module "subnet" { + count = var.subnet_ocid == "" ? 1 : 0 + source = "oracle-terraform-modules/vcn/oci//modules/subnet" + version = "3.6.0" + compartment_id = var.compartment_ocid + vcn_id = module.vcn[0].vcn_id + nat_route_id = module.vcn[0].nat_route_id + ig_route_id = module.vcn[0].ig_route_id subnets = { private = { cidr_block = "10.0.0.0/16" @@ -49,17 +68,15 @@ module "vcn" { name = local.subnet } } - - create_nat_gateway = true - nat_gateway_display_name = local.nat_gateway - create_service_gateway = true - service_gateway_display_name = local.service_gateway + freeform_tags = var.tags + defined_tags = local.defined_tags_map } resource "oci_functions_application" "dd_function_app" { compartment_id = var.compartment_ocid display_name = "dd-function-app" freeform_tags = var.tags + defined_tags = local.defined_tags_map shape = "GENERIC_X86_ARM" subnet_ids = [ local.subnet_id diff --git a/datadog-integration/modules/regional-stacks/variables.tf b/datadog-integration/modules/regional-stacks/variables.tf index c5ac420..28efdef 100644 --- a/datadog-integration/modules/regional-stacks/variables.tf +++ b/datadog-integration/modules/regional-stacks/variables.tf @@ -20,12 +20,18 @@ variable "datadog_site" { variable "tags" { type = map(string) - description = "A map of tags to assign to the resource" + description = "A map of freeform tags to assign to the resource" default = { ownedby = "datadog" } } +variable "defined_tags" { + type = string + description = "JSON-encoded map of defined tags (namespace.key = value), e.g. \"{\\\"Namespace.Key\\\":\\\"value\\\"}\". Passed from parent stack." + default = "{}" +} + variable "home_region" { type = string description = "The name of the home region" diff --git a/datadog-integration/regional_stack.tf b/datadog-integration/regional_stack.tf index 8e81826..ff78ec1 100644 --- a/datadog-integration/regional_stack.tf +++ b/datadog-integration/regional_stack.tf @@ -65,7 +65,8 @@ resource "null_resource" "regional_stacks_create_apply" { --config-source ${path.module}/modules/regional-stacks/dd_regional_stack.zip --variables '{"tenancy_ocid": "${var.tenancy_ocid}", "region": "${each.key}", \ "compartment_ocid": "${module.compartment.id}", "datadog_site": "${var.datadog_site}", "api_key_secret_id": "${module.kms[0].api_key_secret_id}", \ "home_region": "${local.home_region_name}", "region_key": "${local.subscribed_regions_map[each.key].region_key}", \ - "subnet_ocid": "${lookup(local.region_to_subnet_ocid_map, each.key, "")}"}' \ + "subnet_ocid": "${lookup(local.region_to_subnet_ocid_map, each.key, "")}", "defined_tags": ${jsonencode(jsonencode(local.defined_tags))}}' \ + ${local.stack_create_defined_tags_flag} \ --wait-for-state ACTIVE \ --max-wait-seconds 120 \ --wait-interval-seconds 5 \ @@ -85,7 +86,7 @@ resource "null_resource" "regional_stacks_create_apply" { JOB_ID="" for attempt in {1..5}; do echo "Attempting to create job (attempt $attempt/5)..." - if JOB_ID=$(oci resource-manager job create-apply-job --stack-id $STACK_ID $WAIT_COMMAND --execution-plan-strategy AUTO_APPROVED --region ${each.key} --query "data.id"); then + if JOB_ID=$(oci resource-manager job create-apply-job --stack-id $STACK_ID ${local.stack_create_defined_tags_flag} $WAIT_COMMAND --execution-plan-strategy AUTO_APPROVED --region ${each.key} --query "data.id"); then echo "Job created successfully: $JOB_ID for region ${each.key}" break else @@ -115,8 +116,9 @@ resource "terraform_data" "regional_stacks_destroy" { depends_on = [null_resource.precheck_marker, terraform_data.regional_stack_zip, terraform_data.stack_digest] for_each = local.target_regions_for_stacks input = { - compartment = module.compartment.id - stack_digest_id = terraform_data.stack_digest.id + compartment = module.compartment.id + stack_digest_id = terraform_data.stack_digest.id + defined_tags_json = length(keys(local.compartment_defined_tags)) > 0 ? jsonencode(local.compartment_defined_tags) : "" } provisioner "local-exec" { @@ -125,8 +127,8 @@ resource "terraform_data" "regional_stacks_destroy" { command = <namespace.key:value (e.g. CostCenter.Environment:prod). + Datadog recommends leaving this blank unless your tenancy has mandatory tag defaults that apply to the compartment where Datadog resources will be created. + required: false + default: "" + visible: ${show_advanced_options} diff --git a/datadog-integration/variables.tf b/datadog-integration/variables.tf index eb7634b..3be2f5d 100644 --- a/datadog-integration/variables.tf +++ b/datadog-integration/variables.tf @@ -88,3 +88,9 @@ variable "user_email" { description = "Email address where you want OCI to send you notifications about the created user." default = null } + +variable "defined_tags" { + type = string + description = "Defined tags to apply to all created resources. One entry per line in the format namespace.key:value (e.g. CostCenter.Environment:prod). Leave blank unless your tenancy has mandatory tag defaults." + default = "" +} \ No newline at end of file