├── .editorconfig ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── bug_report.yml │ ├── config.yml │ ├── feature_request.md │ ├── feature_request.yml │ └── question.md ├── PULL_REQUEST_TEMPLATE.md ├── banner.png ├── dependabot.yml ├── mergify.yml ├── renovate.json ├── settings.yml └── workflows │ ├── branch.yml │ ├── chatops.yml │ ├── release.yml │ └── scheduled.yml ├── .gitignore ├── LICENSE ├── README.md ├── README.yaml ├── atmos.yaml ├── catalog ├── monitors │ ├── amq.yaml │ ├── aurora.yaml │ ├── ec2.yaml │ ├── host.yaml │ ├── k8s.yaml │ ├── rabbitmq.yaml │ ├── rds.yaml │ └── redshift.yaml └── roles │ ├── admin.yaml │ ├── api.yaml │ ├── apm.yaml │ ├── billing.yaml │ ├── dashboards.yaml │ ├── data.yaml │ ├── incident.yaml │ ├── integrations.yaml │ ├── logs.yaml │ ├── metric.yaml │ ├── monitors.yaml │ ├── org.yaml │ ├── rum.yaml │ ├── security.yaml │ ├── standard.yaml │ ├── synthetics.yaml │ ├── usage.yaml │ └── user.yaml ├── context.tf ├── examples ├── child_organization │ ├── context.tf │ ├── fixtures.us-east-2.tfvars │ ├── main.tf │ ├── outputs.tf │ ├── providers.tf │ ├── variables.tf │ └── versions.tf ├── complete │ ├── context.tf │ ├── fixtures.us-east-2.tfvars │ ├── main.tf │ ├── monitors-test │ │ ├── anomaly-recovery-legacy-test.yaml │ │ ├── anomaly-recovery-test.json │ │ ├── monitor-variables-test.json │ │ ├── schedule-legacy-test.yaml │ │ └── schedule-test.json │ ├── outputs.tf │ ├── providers.tf │ ├── variables.tf │ └── versions.tf ├── organization_settings │ ├── context.tf │ ├── fixtures.us-east-2.tfvars │ ├── main.tf │ ├── outputs.tf │ ├── providers.tf │ ├── variables.tf │ └── versions.tf ├── rbac │ ├── context.tf │ ├── fixtures.us-east-2.tfvars │ ├── main.tf │ ├── outputs.tf │ ├── providers.tf │ ├── variables.tf │ └── versions.tf ├── slo │ ├── catalog │ │ ├── metric_slo.yaml │ │ └── monitor_slo.yaml │ ├── context.tf │ ├── fixtures.us-east-2.tfvars │ ├── main.tf │ ├── outputs.tf │ ├── providers.tf │ ├── variables.tf │ └── versions.tf └── synthetics │ ├── catalog │ ├── api-schema │ │ ├── api-multistep-test.yaml │ │ └── browser-multistep-test.yaml │ └── terraform-schema │ │ ├── http.yaml │ │ └── ssl.yaml │ ├── context.tf │ ├── fixtures.us-east-2.tfvars │ ├── main.tf │ ├── outputs.tf │ ├── providers.tf │ ├── variables.tf │ └── versions.tf ├── main.tf ├── modules ├── child_organization │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── monitors │ ├── README.md │ ├── context.tf │ ├── main.tf │ ├── normalize-legacy.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── organization_settings │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── permissions │ ├── context.tf │ ├── datadog-permissions.md │ ├── main.tf │ ├── outputs.tf │ └── versions.tf ├── roles │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── slo │ ├── README.md │ ├── context.tf │ ├── main.tf │ ├── metric_slo.tf │ ├── monitor_slo.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf └── synthetics │ ├── README.md │ ├── context.tf │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── outputs.tf ├── test ├── .gitignore ├── Makefile ├── Makefile.alpine └── src │ ├── .gitignore │ ├── Makefile │ ├── examples_child_organization_test.go │ ├── examples_complete_test.go │ ├── examples_synthetics_test.go │ ├── go.mod │ └── go.sum ├── variables.tf └── versions.tf /.editorconfig: -------------------------------------------------------------------------------- 1 | # Unix-style newlines with a newline ending every file 2 | [*] 3 | charset = utf-8 4 | end_of_line = lf 5 | indent_size = 2 6 | indent_style = space 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.{tf,tfvars}] 11 | indent_size = 2 12 | indent_style = space 13 | 14 | [*.md] 15 | max_line_length = 0 16 | trim_trailing_whitespace = false 17 | 18 | # Override for Makefile 19 | [{Makefile, makefile, GNUmakefile, Makefile.*}] 20 | tab_width = 4 21 | indent_style = tab 22 | indent_size = tab 23 | 24 | # Enforce `go` formatting rules 25 | [*.go] 26 | indent_size = unset 27 | indent_style = tab 28 | 29 | [COMMIT_EDITMSG] 30 | max_line_length = 0 31 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Use this file to define individuals or teams that are responsible for code in a repository. 2 | # Read more: 3 | # 4 | # Order is important: the last matching pattern has the highest precedence 5 | 6 | # These owners will be the default owners for everything 7 | * @cloudposse/engineering @cloudposse/contributors 8 | 9 | # Cloud Posse must review any changes to Makefiles 10 | **/Makefile @cloudposse/engineering 11 | **/Makefile.* @cloudposse/engineering 12 | 13 | # Cloud Posse must review any changes to GitHub actions 14 | .github/* @cloudposse/engineering 15 | 16 | # Cloud Posse must review any changes to standard context definition, 17 | # but some changes can be rubber-stamped. 18 | **/*.tf @cloudposse/engineering @cloudposse/contributors @cloudposse/approvers 19 | README.yaml @cloudposse/engineering @cloudposse/contributors @cloudposse/approvers 20 | README.md @cloudposse/engineering @cloudposse/contributors @cloudposse/approvers 21 | docs/*.md @cloudposse/engineering @cloudposse/contributors @cloudposse/approvers 22 | 23 | # Cloud Posse Admins must review all changes to CODEOWNERS or the mergify configuration 24 | .github/mergify.yml @cloudposse/admins 25 | .github/CODEOWNERS @cloudposse/admins 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Found a bug? Maybe our [Slack Community](https://slack.cloudposse.com) can help. 11 | 12 | [![Slack Community](https://slack.cloudposse.com/badge.svg)](https://slack.cloudposse.com) 13 | 14 | ## Describe the Bug 15 | A clear and concise description of what the bug is. 16 | 17 | ## Expected Behavior 18 | A clear and concise description of what you expected to happen. 19 | 20 | ## Steps to Reproduce 21 | Steps to reproduce the behavior: 22 | 1. Go to '...' 23 | 2. Run '....' 24 | 3. Enter '....' 25 | 4. See error 26 | 27 | ## Screenshots 28 | If applicable, add screenshots or logs to help explain your problem. 29 | 30 | ## Environment (please complete the following information): 31 | 32 | Anything that will help us triage the bug will help. Here are some ideas: 33 | - OS: [e.g. Linux, OSX, WSL, etc] 34 | - Version [e.g. 10.15] 35 | 36 | ## Additional Context 37 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | description: Create a report to help us improve 4 | labels: ["bug"] 5 | assignees: [""] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Found a bug? 11 | 12 | Please checkout our [Slack Community](https://slack.cloudposse.com) 13 | or visit our [Slack Archive](https://archive.sweetops.com/). 14 | 15 | [![Slack Community](https://slack.cloudposse.com/badge.svg)](https://slack.cloudposse.com) 16 | 17 | - type: textarea 18 | id: concise-description 19 | attributes: 20 | label: Describe the Bug 21 | description: A clear and concise description of what the bug is. 22 | placeholder: What is the bug about? 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | id: expected 28 | attributes: 29 | label: Expected Behavior 30 | description: A clear and concise description of what you expected. 31 | placeholder: What happened? 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | id: reproduction-steps 37 | attributes: 38 | label: Steps to Reproduce 39 | description: Steps to reproduce the behavior. 40 | placeholder: How do we reproduce it? 41 | validations: 42 | required: true 43 | 44 | - type: textarea 45 | id: screenshots 46 | attributes: 47 | label: Screenshots 48 | description: If applicable, add screenshots or logs to help explain. 49 | validations: 50 | required: false 51 | 52 | - type: textarea 53 | id: environment 54 | attributes: 55 | label: Environment 56 | description: Anything that will help us triage the bug. 57 | placeholder: | 58 | - OS: [e.g. Linux, OSX, WSL, etc] 59 | - Version [e.g. 10.15] 60 | - Module version 61 | - Terraform version 62 | validations: 63 | required: false 64 | 65 | - type: textarea 66 | id: additional 67 | attributes: 68 | label: Additional Context 69 | description: | 70 | Add any other context about the problem here. 71 | validations: 72 | required: false 73 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | 5 | - name: Community Slack Team 6 | url: https://cloudposse.com/slack/ 7 | about: |- 8 | Please ask and answer questions here. 9 | 10 | - name: Office Hours 11 | url: https://cloudposse.com/office-hours/ 12 | about: |- 13 | Join us every Wednesday for FREE Office Hours (lunch & learn). 14 | 15 | - name: DevOps Accelerator Program 16 | url: https://cloudposse.com/accelerate/ 17 | about: |- 18 | Own your infrastructure in record time. We build it. You drive it. 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'feature request' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Have a question? Please checkout our [Slack Community](https://slack.cloudposse.com) or visit our [Slack Archive](https://archive.sweetops.com/). 11 | 12 | [![Slack Community](https://slack.cloudposse.com/badge.svg)](https://slack.cloudposse.com) 13 | 14 | ## Describe the Feature 15 | 16 | A clear and concise description of what the bug is. 17 | 18 | ## Expected Behavior 19 | 20 | A clear and concise description of what you expected to happen. 21 | 22 | ## Use Case 23 | 24 | Is your feature request related to a problem/challenge you are trying to solve? Please provide some additional context of why this feature or capability will be valuable. 25 | 26 | ## Describe Ideal Solution 27 | 28 | A clear and concise description of what you want to happen. If you don't know, that's okay. 29 | 30 | ## Alternatives Considered 31 | 32 | Explain what alternative solutions or features you've considered. 33 | 34 | ## Additional Context 35 | 36 | Add any other context or screenshots about the feature request here. 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | description: Suggest an idea for this project 4 | labels: ["feature request"] 5 | assignees: [""] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Have a question? 11 | 12 | Please checkout our [Slack Community](https://slack.cloudposse.com) 13 | or visit our [Slack Archive](https://archive.sweetops.com/). 14 | 15 | [![Slack Community](https://slack.cloudposse.com/badge.svg)](https://slack.cloudposse.com) 16 | 17 | - type: textarea 18 | id: concise-description 19 | attributes: 20 | label: Describe the Feature 21 | description: A clear and concise description of what the feature is. 22 | placeholder: What is the feature about? 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | id: expected 28 | attributes: 29 | label: Expected Behavior 30 | description: A clear and concise description of what you expected. 31 | placeholder: What happened? 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | id: use-case 37 | attributes: 38 | label: Use Case 39 | description: | 40 | Is your feature request related to a problem/challenge you are trying 41 | to solve? 42 | 43 | Please provide some additional context of why this feature or 44 | capability will be valuable. 45 | validations: 46 | required: true 47 | 48 | - type: textarea 49 | id: ideal-solution 50 | attributes: 51 | label: Describe Ideal Solution 52 | description: A clear and concise description of what you want to happen. 53 | validations: 54 | required: true 55 | 56 | - type: textarea 57 | id: alternatives-considered 58 | attributes: 59 | label: Alternatives Considered 60 | description: Explain alternative solutions or features considered. 61 | validations: 62 | required: false 63 | 64 | - type: textarea 65 | id: additional 66 | attributes: 67 | label: Additional Context 68 | description: | 69 | Add any other context about the problem here. 70 | validations: 71 | required: false 72 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudposse/terraform-datadog-platform/0af3588dc7748101bc432866a5819dcf011461b1/.github/ISSUE_TEMPLATE/question.md -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## what 2 | 3 | 7 | 8 | ## why 9 | 10 | 15 | 16 | ## references 17 | 18 | 22 | -------------------------------------------------------------------------------- /.github/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudposse/terraform-datadog-platform/0af3588dc7748101bc432866a5819dcf011461b1/.github/banner.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: gomod 7 | directory: / 8 | labels: 9 | - dependencies 10 | - go 11 | - no-release 12 | schedule: 13 | interval: weekly 14 | day: sunday 15 | ignore: 16 | - dependency-name: "*" 17 | update-types: ["version-update:semver-major"] 18 | 19 | -------------------------------------------------------------------------------- /.github/mergify.yml: -------------------------------------------------------------------------------- 1 | extends: .github 2 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":preserveSemverRanges" 5 | ], 6 | "baseBranches": ["main", "master", "/^release\\/v\\d{1,2}$/"], 7 | "labels": ["auto-update"], 8 | "dependencyDashboardAutoclose": true, 9 | "enabledManagers": ["terraform"], 10 | "terraform": { 11 | "ignorePaths": ["**/context.tf", "examples/**"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # Upstream changes from _extends are only recognized when modifications are made to this file in the default branch. 2 | _extends: .github 3 | repository: 4 | name: terraform-datadog-platform 5 | description: Terraform module to configure and provision Datadog monitors, custom RBAC roles with permissions, Datadog synthetic tests, Datadog child organizations, and other Datadog resources from a YAML configuration, complete with automated tests. 6 | homepage: https://cloudposse.com/accelerate 7 | topics: terraform, terraform-modules, datadog, monitoring, terratest, synthetics 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/branch.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Branch 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - release/** 8 | types: [opened, synchronize, reopened, labeled, unlabeled] 9 | push: 10 | branches: 11 | - main 12 | - release/v* 13 | paths-ignore: 14 | - '.github/**' 15 | - 'docs/**' 16 | - 'examples/**' 17 | - 'test/**' 18 | - 'README.md' 19 | 20 | permissions: {} 21 | 22 | jobs: 23 | terraform-module: 24 | uses: cloudposse/.github/.github/workflows/shared-terraform-module.yml@main 25 | secrets: inherit 26 | -------------------------------------------------------------------------------- /.github/workflows/chatops.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: chatops 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | permissions: 8 | pull-requests: write 9 | id-token: write 10 | contents: write 11 | statuses: write 12 | 13 | jobs: 14 | test: 15 | uses: cloudposse/.github/.github/workflows/shared-terraform-chatops.yml@main 16 | if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/terratest') }} 17 | secrets: inherit 18 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: release 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | permissions: 9 | id-token: write 10 | contents: write 11 | pull-requests: write 12 | 13 | jobs: 14 | terraform-module: 15 | uses: cloudposse/.github/.github/workflows/shared-release-branches.yml@main 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /.github/workflows/scheduled.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: scheduled 3 | on: 4 | workflow_dispatch: { } # Allows manually trigger this workflow 5 | schedule: 6 | - cron: "0 3 * * *" 7 | 8 | permissions: 9 | pull-requests: write 10 | id-token: write 11 | contents: write 12 | 13 | jobs: 14 | scheduled: 15 | uses: cloudposse/.github/.github/workflows/shared-terraform-scheduled.yml@main 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.tfstate 3 | *.tfstate.backup 4 | 5 | # Module directory 6 | .terraform/ 7 | .idea 8 | *.iml 9 | 10 | .build-harness 11 | build-harness 12 | **/.terraform.lock.hcl 13 | -------------------------------------------------------------------------------- /atmos.yaml: -------------------------------------------------------------------------------- 1 | # Atmos Configuration — powered by https://atmos.tools 2 | # 3 | # This configuration enables centralized, DRY, and consistent project scaffolding using Atmos. 4 | # 5 | # Included features: 6 | # - Organizational custom commands: https://atmos.tools/core-concepts/custom-commands 7 | # - Automated README generation: https://atmos.tools/cli/commands/docs/generate 8 | # 9 | 10 | # Import shared configuration used by all modules 11 | import: 12 | - https://raw.githubusercontent.com/cloudposse/.github/refs/heads/main/.github/atmos/terraform-module.yaml 13 | -------------------------------------------------------------------------------- /catalog/monitors/amq.yaml: -------------------------------------------------------------------------------- 1 | # The official Datadog API documentation with available query parameters & alert types: 2 | # https://docs.datadoghq.com/api/v1/monitors/#create-a-monitor 3 | 4 | amq-cpu-utilization: 5 | name: "(AMQ) CPU Utilization above 90%" 6 | type: metric alert 7 | query: | 8 | avg(last_15m):avg:aws.amazonmq.cpu_utilization{*} by {broker} > 90 9 | message: | 10 | {{#is_warning}} 11 | ({broker}) - CPU Utilization above 85% 12 | {{/is_warning}} 13 | {{#is_alert}} 14 | ({broker}) - CPU Utilization above 90% 15 | {{/is_alert}} 16 | escalation_message: "" 17 | tags: 18 | ManagedBy: Terraform 19 | priority: 3 20 | notify_no_data: false 21 | notify_audit: true 22 | require_full_window: true 23 | enable_logs_sample: false 24 | force_delete: true 25 | include_tags: true 26 | locked: false 27 | renotify_interval: 60 28 | timeout_h: 0 29 | evaluation_delay: 60 30 | new_host_delay: 300 31 | new_group_delay: 0 32 | groupby_simple_monitor: false 33 | renotify_occurrences: 0 34 | renotify_statuses: [] 35 | validate: true 36 | no_data_timeframe: 10 37 | threshold_windows: { } 38 | thresholds: 39 | critical: 90 40 | warning: 85 41 | #unknown: 42 | #ok: 43 | #critical_recovery: 44 | #warning_recovery: 45 | 46 | amq-heap-usage: 47 | name: "(AMQ) JVM heap usage above 95%" 48 | type: metric alert 49 | query: | 50 | avg(last_15m):avg:aws.amazonmq.heap_usage{*} by {broker} > 95 51 | message: | 52 | {{#is_warning}} 53 | ({broker}) - JVM heap usage above 90% 54 | {{/is_warning}} 55 | {{#is_alert}} 56 | ({broker}) - JVM heap usage above 95% 57 | {{/is_alert}} 58 | escalation_message: "" 59 | tags: 60 | ManagedBy: Terraform 61 | priority: 3 62 | notify_no_data: false 63 | notify_audit: true 64 | require_full_window: true 65 | enable_logs_sample: false 66 | force_delete: true 67 | include_tags: true 68 | locked: false 69 | renotify_interval: 60 70 | timeout_h: 0 71 | evaluation_delay: 60 72 | new_host_delay: 300 73 | new_group_delay: 0 74 | groupby_simple_monitor: false 75 | renotify_occurrences: 0 76 | renotify_statuses: [] 77 | validate: true 78 | no_data_timeframe: 10 79 | threshold_windows: { } 80 | thresholds: 81 | critical: 95 82 | warning: 90 83 | #unknown: 84 | #ok: 85 | #critical_recovery: 86 | #warning_recovery: 87 | 88 | amq-network-in: 89 | name: "(AMQ) Anomaly of a large variance in network-in bytes" 90 | type: metric alert 91 | query: | 92 | avg(last_4h):anomalies(avg:aws.amazonmq.network_in{*} by {broker}, 'agile', 2, direction='both', alert_window='last_15m', interval=60, count_default_zero='true', seasonality='hourly') >= 1 93 | message: | 94 | {{#is_alert}} 95 | ({broker}) - Anomaly of a large variance in network-in bytes 96 | {{/is_alert}} 97 | escalation_message: "" 98 | tags: 99 | ManagedBy: Terraform 100 | priority: 3 101 | notify_no_data: false 102 | notify_audit: true 103 | require_full_window: true 104 | enable_logs_sample: false 105 | force_delete: true 106 | include_tags: true 107 | locked: false 108 | renotify_interval: 60 109 | timeout_h: 0 110 | evaluation_delay: 900 111 | new_host_delay: 300 112 | new_group_delay: 0 113 | groupby_simple_monitor: false 114 | renotify_occurrences: 0 115 | renotify_statuses: [] 116 | validate: true 117 | no_data_timeframe: 10 118 | threshold_windows: 119 | trigger_window: "last_15m" 120 | recovery_window: "last_15m" 121 | thresholds: 122 | critical: 1 123 | #warning: 124 | #unknown: 125 | #ok: 126 | critical_recovery: 0 127 | #warning_recovery: 128 | 129 | amq-network-out: 130 | name: "(AMQ) Anomaly of a large variance in network-out bytes" 131 | type: metric alert 132 | query: | 133 | avg(last_4h):anomalies(avg:aws.amazonmq.network_out{*} by {broker}, 'agile', 2, direction='both', alert_window='last_15m', interval=60, count_default_zero='true', seasonality='hourly') >= 1 134 | message: | 135 | {{#is_alert}} 136 | ({broker}) - Anomaly of a large variance in network-out bytes 137 | {{/is_alert}} 138 | escalation_message: "" 139 | tags: 140 | ManagedBy: Terraform 141 | priority: 3 142 | notify_no_data: false 143 | notify_audit: true 144 | require_full_window: true 145 | enable_logs_sample: false 146 | force_delete: true 147 | include_tags: true 148 | locked: false 149 | renotify_interval: 60 150 | timeout_h: 0 151 | evaluation_delay: 900 152 | new_host_delay: 300 153 | new_group_delay: 0 154 | groupby_simple_monitor: false 155 | renotify_occurrences: 0 156 | renotify_statuses: [] 157 | validate: true 158 | no_data_timeframe: 10 159 | threshold_windows: 160 | trigger_window: "last_15m" 161 | recovery_window: "last_15m" 162 | thresholds: 163 | critical: 1 164 | #warning: 165 | #unknown: 166 | #ok: 167 | critical_recovery: 0 168 | #warning_recovery: 169 | 170 | amq-current-connections-count: 171 | name: "(AMQ) Anomaly of a large variance in broker connections" 172 | type: metric alert 173 | query: | 174 | avg(last_4h):anomalies(avg:aws.amazonmq.current_connections_count{*}.as_count(), 'basic', 2, direction='both', alert_window='last_15m', interval=60, count_default_zero='true') >= 1 175 | message: | 176 | {{#is_alert}} 177 | ({broker}) - Anomaly of a large variance in broker connections 178 | {{/is_alert}} 179 | escalation_message: "" 180 | tags: 181 | ManagedBy: Terraform 182 | priority: 3 183 | notify_no_data: false 184 | notify_audit: true 185 | require_full_window: true 186 | enable_logs_sample: false 187 | force_delete: true 188 | include_tags: true 189 | locked: false 190 | renotify_interval: 60 191 | timeout_h: 0 192 | evaluation_delay: 900 193 | new_host_delay: 300 194 | new_group_delay: 0 195 | groupby_simple_monitor: false 196 | renotify_occurrences: 0 197 | renotify_statuses: [] 198 | validate: true 199 | no_data_timeframe: 10 200 | threshold_windows: 201 | trigger_window: "last_15m" 202 | recovery_window: "last_15m" 203 | thresholds: 204 | critical: 1 205 | #warning: 206 | #unknown: 207 | #ok: 208 | critical_recovery: 0 209 | #warning_recovery: 210 | -------------------------------------------------------------------------------- /catalog/monitors/aurora.yaml: -------------------------------------------------------------------------------- 1 | # The official Datadog API documentation with available query parameters & alert types: 2 | # https://docs.datadoghq.com/api/v1/monitors/#create-a-monitor 3 | 4 | aurora-replica-lag: 5 | name: "(RDS) Aurora Replica Lag Detected" 6 | type: metric alert 7 | query: | 8 | min(last_15m):min:aws.rds.aurora_replica_lag{*} by {dbinstanceidentifier} > 1000 9 | message: | 10 | {{#is_warning}} 11 | ({dbinstanceidentifier}) Replica lag has been greater than half a second for more than 15 minutes 12 | {{/is_warning}} 13 | {{#is_alert}} 14 | ({dbinstanceidentifier}) Replica lag has been greater than 1s for more than 15 minutes 15 | {{/is_alert}} 16 | escalation_message: "" 17 | tags: 18 | ManagedBy: Terraform 19 | priority: 3 20 | notify_no_data: false 21 | notify_audit: true 22 | require_full_window: false 23 | enable_logs_sample: false 24 | force_delete: true 25 | include_tags: true 26 | locked: false 27 | renotify_interval: 60 28 | timeout_h: 0 29 | evaluation_delay: 60 30 | new_host_delay: 300 31 | new_group_delay: 0 32 | groupby_simple_monitor: false 33 | renotify_occurrences: 0 34 | renotify_statuses: [] 35 | validate: true 36 | no_data_timeframe: 10 37 | threshold_windows: { } 38 | thresholds: 39 | critical: 1000 40 | warning: 500 41 | #unknown: 42 | #ok: 43 | #critical_recovery: 44 | #warning_recovery: 45 | -------------------------------------------------------------------------------- /catalog/monitors/ec2.yaml: -------------------------------------------------------------------------------- 1 | # The official Datadog API documentation with available query parameters & alert types: 2 | # https://docs.datadoghq.com/api/v1/monitors/#create-a-monitor 3 | 4 | ec2-failed-status-check: 5 | name: "(EC2) Failed Status Check" 6 | type: metric alert 7 | query: | 8 | avg(last_10m):avg:aws.ec2.status_check_failed{*} by {instance_id} > 0 9 | message: | 10 | ({stage} {region}) {instance_id} failed a status check 11 | escalation_message: "" 12 | tags: 13 | ManagedBy: Terraform 14 | priority: 3 15 | notify_no_data: false 16 | notify_audit: true 17 | require_full_window: true 18 | enable_logs_sample: false 19 | force_delete: true 20 | include_tags: true 21 | locked: false 22 | renotify_interval: 60 23 | timeout_h: 0 24 | evaluation_delay: 60 25 | new_host_delay: 300 26 | new_group_delay: 0 27 | groupby_simple_monitor: false 28 | renotify_occurrences: 0 29 | renotify_statuses: [] 30 | validate: true 31 | no_data_timeframe: 10 32 | threshold_windows: { } 33 | thresholds: 34 | critical: 0 35 | #warning: 36 | #unknown: 37 | #ok: 38 | #critical_recovery: 39 | #warning_recovery: 40 | -------------------------------------------------------------------------------- /catalog/monitors/host.yaml: -------------------------------------------------------------------------------- 1 | # The official Datadog API documentation with available query parameters & alert types: 2 | # https://docs.datadoghq.com/api/v1/monitors/#create-a-monitor 3 | 4 | host-io-wait-times: 5 | name: "(Host) I/O Wait Times" 6 | type: metric alert 7 | query: "avg(last_10m):avg:system.cpu.iowait{*} by {host} > 50" 8 | message: |- 9 | The I/O wait time for ({{host.name}} {{host.ip}}) is very high 10 | escalation_message: "" 11 | tags: 12 | ManagedBy: Terraform 13 | priority: 3 14 | notify_no_data: false 15 | notify_audit: true 16 | require_full_window: true 17 | enable_logs_sample: false 18 | force_delete: true 19 | include_tags: true 20 | locked: false 21 | renotify_interval: 60 22 | timeout_h: 0 23 | evaluation_delay: 60 24 | new_host_delay: 300 25 | new_group_delay: 0 26 | groupby_simple_monitor: false 27 | renotify_occurrences: 0 28 | renotify_statuses: [] 29 | validate: true 30 | no_data_timeframe: 10 31 | threshold_windows: { } 32 | thresholds: 33 | critical: 50 34 | warning: 30 35 | #unknown: 36 | #ok: 37 | #critical_recovery: 38 | #warning_recovery: 39 | 40 | host-disk-use: 41 | name: "(Host) Host Disk Usage" 42 | type: metric alert 43 | query: "avg(last_30m):(avg:system.disk.total{*} by {host} - avg:system.disk.free{*} by {host}) / avg:system.disk.total{*} by {host} * 100 > 90" 44 | message: |- 45 | Disk Usage has been above threshold over 30 minutes on ({{host.name}} {{host.ip}}) 46 | escalation_message: "" 47 | tags: 48 | ManagedBy: Terraform 49 | priority: 3 50 | notify_no_data: false 51 | notify_audit: true 52 | require_full_window: true 53 | enable_logs_sample: false 54 | force_delete: true 55 | include_tags: true 56 | locked: false 57 | renotify_interval: 60 58 | timeout_h: 0 59 | evaluation_delay: 60 60 | new_host_delay: 300 61 | new_group_delay: 0 62 | groupby_simple_monitor: false 63 | renotify_occurrences: 0 64 | renotify_statuses: [] 65 | validate: true 66 | no_data_timeframe: 10 67 | threshold_windows: { } 68 | thresholds: 69 | critical: 90 70 | warning: 80 71 | #unknown: 72 | #ok: 73 | critical_recovery: 85 74 | warning_recovery: 75 75 | 76 | host-high-mem-use: 77 | name: "(Host) Memory Utilization" 78 | type: query alert 79 | query: "avg(last_15m):avg:system.mem.pct_usable{*} by {host} < 0.1" 80 | message: |- 81 | Running out of free memory on ({{host.name}} {{host.ip}}) 82 | escalation_message: "" 83 | tags: 84 | ManagedBy: Terraform 85 | priority: 3 86 | notify_no_data: false 87 | notify_audit: true 88 | require_full_window: true 89 | enable_logs_sample: false 90 | force_delete: true 91 | include_tags: true 92 | locked: false 93 | renotify_interval: 60 94 | timeout_h: 0 95 | evaluation_delay: 60 96 | new_host_delay: 300 97 | new_group_delay: 0 98 | groupby_simple_monitor: false 99 | renotify_occurrences: 0 100 | renotify_statuses: [] 101 | validate: true 102 | no_data_timeframe: 10 103 | threshold_windows: { } 104 | thresholds: 105 | critical: 0.1 106 | warning: 0.15 107 | #unknown: 108 | #ok: 109 | #critical_recovery: 110 | #warning_recovery: 111 | 112 | host-high-load-avg: 113 | name: "(Host) High System Load Average" 114 | type: metric alert 115 | query: "avg(last_30m):avg:system.load.norm.5{*} by {host} > 2" 116 | message: |- 117 | Load average is high on ({{host.name}} {{host.ip}}) 118 | escalation_message: "" 119 | tags: 120 | ManagedBy: Terraform 121 | priority: 3 122 | notify_no_data: false 123 | notify_audit: true 124 | require_full_window: true 125 | enable_logs_sample: false 126 | force_delete: true 127 | include_tags: true 128 | locked: false 129 | renotify_interval: 60 130 | timeout_h: 0 131 | evaluation_delay: 60 132 | new_host_delay: 300 133 | new_group_delay: 0 134 | groupby_simple_monitor: false 135 | renotify_occurrences: 0 136 | renotify_statuses: [] 137 | validate: true 138 | no_data_timeframe: 10 139 | threshold_windows: { } 140 | thresholds: 141 | critical: 2 142 | #warning: 143 | #unknown: 144 | #ok: 145 | #critical_recovery: 146 | #warning_recovery: 147 | -------------------------------------------------------------------------------- /catalog/monitors/rabbitmq.yaml: -------------------------------------------------------------------------------- 1 | rabbitmq-messages-unacknowledged-rate-too-high: 2 | name: "[RabbitMQ] - Messages unacknowledged rate is higher than usual on: {{host.name}}" 3 | type: "query alert" 4 | query: | 5 | avg(last_4h):anomalies(avg:rabbitmq.queue.messages_unacknowledged.rate{*} by {rabbitmq_queue,host}, 'agile', 2, direction='above', alert_window='last_15m', interval=60, count_default_zero='true', seasonality='hourly') >= 1 6 | message: | 7 | The rate at which messages are being delivered without receiving acknowledgement is higher than usual. 8 | There may be errors or performance issues downstream.\n 9 | Host: {{host.name}}\n 10 | RabbitMQ Queue: {{rabbitmq_queue.name}} 11 | escalation_message: "" 12 | tags: 13 | ManagedBy: Terraform 14 | Integration: RabbitMQ 15 | priority: 3 16 | notify_no_data: false 17 | notify_audit: true 18 | require_full_window: true 19 | enable_logs_sample: false 20 | force_delete: true 21 | include_tags: true 22 | locked: false 23 | renotify_interval: 0 24 | timeout_h: 0 25 | evaluation_delay: 60 26 | new_host_delay: 300 27 | new_group_delay: 0 28 | groupby_simple_monitor: false 29 | renotify_occurrences: 0 30 | renotify_statuses: [] 31 | validate: true 32 | no_data_timeframe: null 33 | threshold_windows: { } 34 | thresholds: 35 | critical: 1 36 | critical_recovery: 0 37 | #warning: 38 | #unknown: 39 | #ok: 40 | #warning_recovery: 41 | 42 | rabbitmq-disk-usage-too-high: 43 | name: "[RabbitMQ] Level of disk usage is too high for host: {{host.name}}" 44 | type: "query alert" 45 | query: | 46 | avg(last_5m):avg:rabbitmq.node.mem_used{*} by {host} / avg:system.mem.total{*} by {host} * 100 > 35 47 | message: | 48 | RabbitMQ is using too many resources on host: {{host.name}}. 49 | It may block connections and won't be able to perform many internal operations. 50 | escalation_message: "" 51 | tags: 52 | ManagedBy: Terraform 53 | Integration: RabbitMQ 54 | priority: 3 55 | notify_no_data: false 56 | notify_audit: true 57 | require_full_window: true 58 | enable_logs_sample: false 59 | force_delete: true 60 | include_tags: true 61 | locked: false 62 | renotify_interval: 0 63 | timeout_h: 0 64 | evaluation_delay: 60 65 | new_host_delay: 300 66 | new_group_delay: 0 67 | groupby_simple_monitor: false 68 | renotify_occurrences: 0 69 | renotify_statuses: [] 70 | validate: true 71 | no_data_timeframe: null 72 | threshold_windows: { } 73 | thresholds: 74 | critical: 35 75 | warning: 30 76 | #unknown: 77 | #ok: 78 | #critical_recovery: 79 | #warning_recovery: 80 | -------------------------------------------------------------------------------- /catalog/monitors/rds.yaml: -------------------------------------------------------------------------------- 1 | # The official Datadog API documentation with available query parameters & alert types: 2 | # https://docs.datadoghq.com/api/v1/monitors/#create-a-monitor 3 | 4 | rds-cpuutilization: 5 | name: "(RDS) CPU Utilization above 90%" 6 | type: metric alert 7 | query: | 8 | avg(last_15m):avg:aws.rds.cpuutilization{*} by {dbinstanceidentifier} > 90 9 | message: | 10 | {{#is_warning}} 11 | ({dbinstanceidentifier}) CPU Utilization above 85% 12 | {{/is_warning}} 13 | {{#is_alert}} 14 | ({dbinstanceidentifier}) CPU Utilization above 90% 15 | {{/is_alert}} 16 | escalation_message: "" 17 | tags: 18 | ManagedBy: Terraform 19 | priority: 3 20 | notify_no_data: false 21 | notify_audit: true 22 | require_full_window: true 23 | enable_logs_sample: false 24 | force_delete: true 25 | include_tags: true 26 | locked: false 27 | renotify_interval: 60 28 | timeout_h: 0 29 | evaluation_delay: 60 30 | new_host_delay: 300 31 | new_group_delay: 0 32 | groupby_simple_monitor: false 33 | renotify_occurrences: 0 34 | renotify_statuses: [] 35 | validate: true 36 | no_data_timeframe: 10 37 | threshold_windows: { } 38 | thresholds: 39 | critical: 90 40 | warning: 85 41 | #unknown: 42 | #ok: 43 | #critical_recovery: 44 | #warning_recovery: 45 | 46 | rds-disk-queue-depth: 47 | name: "(RDS) Disk queue depth above 64" 48 | type: metric alert 49 | query: | 50 | avg(last_15m):avg:aws.rds.disk_queue_depth{*} by {dbinstanceidentifier} > 64 51 | message: | 52 | {{#is_warning}} 53 | ({dbinstanceidentifier}) Disk queue depth above 48 54 | {{/is_warning}} 55 | {{#is_alert}} 56 | ({dbinstanceidentifier}) Disk queue depth above 64 57 | {{/is_alert}} 58 | escalation_message: "" 59 | tags: 60 | ManagedBy: Terraform 61 | priority: 3 62 | notify_no_data: false 63 | notify_audit: true 64 | require_full_window: true 65 | enable_logs_sample: false 66 | force_delete: true 67 | include_tags: true 68 | locked: false 69 | renotify_interval: 60 70 | timeout_h: 0 71 | evaluation_delay: 60 72 | new_host_delay: 300 73 | new_group_delay: 0 74 | groupby_simple_monitor: false 75 | renotify_occurrences: 0 76 | renotify_statuses: [] 77 | validate: true 78 | no_data_timeframe: 10 79 | threshold_windows: { } 80 | thresholds: 81 | critical: 64 82 | warning: 48 83 | #unknown: 84 | #ok: 85 | #critical_recovery: 86 | #warning_recovery: 87 | 88 | rds-freeable-memory: 89 | name: "(RDS) Freeable memory below 256 MB" 90 | type: metric alert 91 | query: | 92 | avg(last_5m):avg:aws.rds.freeable_memory{*} < 256000000 93 | message: | 94 | {{#is_warning}} 95 | ({dbinstanceidentifier}) Freeable memory below 512 MB 96 | {{/is_warning}} 97 | {{#is_alert}} 98 | ({dbinstanceidentifier}) Freeable memory below 256 MB 99 | {{/is_alert}} 100 | escalation_message: "" 101 | tags: 102 | ManagedBy: Terraform 103 | priority: 3 104 | notify_no_data: false 105 | notify_audit: true 106 | require_full_window: true 107 | enable_logs_sample: false 108 | force_delete: true 109 | include_tags: true 110 | locked: false 111 | renotify_interval: 60 112 | timeout_h: 0 113 | evaluation_delay: 60 114 | new_host_delay: 300 115 | new_group_delay: 0 116 | groupby_simple_monitor: false 117 | renotify_occurrences: 0 118 | renotify_statuses: [] 119 | validate: true 120 | no_data_timeframe: 10 121 | threshold_windows: { } 122 | thresholds: 123 | critical: 256000000 124 | warning: 512000000 125 | #unknown: 126 | #ok: 127 | #critical_recovery: 128 | #warning_recovery: 129 | 130 | rds-swap-usage: 131 | name: "(RDS) Swap usage above 256 MB" 132 | type: metric alert 133 | query: | 134 | avg(last_15m):avg:aws.rds.swap_usage{*} by {dbinstanceidentifier} > 256000000 135 | message: | 136 | {{#is_warning}} 137 | ({dbinstanceidentifier}) Swap usage above 128 MB 138 | {{/is_warning}} 139 | {{#is_alert}} 140 | ({dbinstanceidentifier}) Swap usage above 256 MB 141 | {{/is_alert}} 142 | escalation_message: "" 143 | tags: 144 | ManagedBy: Terraform 145 | priority: 3 146 | notify_no_data: false 147 | notify_audit: true 148 | require_full_window: true 149 | enable_logs_sample: false 150 | force_delete: true 151 | include_tags: true 152 | locked: false 153 | renotify_interval: 60 154 | timeout_h: 0 155 | evaluation_delay: 60 156 | new_host_delay: 300 157 | new_group_delay: 0 158 | groupby_simple_monitor: false 159 | renotify_occurrences: 0 160 | renotify_statuses: [] 161 | validate: true 162 | no_data_timeframe: 10 163 | threshold_windows: { } 164 | thresholds: 165 | critical: 256000000 166 | warning: 128000000 167 | #unknown: 168 | #ok: 169 | #critical_recovery: 170 | #warning_recovery: 171 | 172 | rds-database-connections: 173 | name: "(RDS) Anomaly of a large variance in RDS connection count" 174 | type: metric alert 175 | query: | 176 | avg(last_4h):anomalies(avg:aws.rds.database_connections{*}, 'basic', 2, direction='both', alert_window='last_15m', interval=60, count_default_zero='true') >= 1 177 | message: | 178 | {{#is_warning}} 179 | ({dbinstanceidentifier}) Anomaly of a large variance in RDS connection count 180 | {{/is_warning}} 181 | {{#is_alert}} 182 | ({dbinstanceidentifier}) Anomaly of a large variance in RDS connection count 183 | {{/is_alert}} 184 | escalation_message: "" 185 | tags: 186 | ManagedBy: Terraform 187 | priority: 3 188 | notify_no_data: false 189 | notify_audit: true 190 | require_full_window: true 191 | enable_logs_sample: false 192 | force_delete: true 193 | include_tags: true 194 | locked: false 195 | renotify_interval: 60 196 | timeout_h: 0 197 | evaluation_delay: 60 198 | new_host_delay: 300 199 | new_group_delay: 0 200 | groupby_simple_monitor: false 201 | renotify_occurrences: 0 202 | renotify_statuses: [] 203 | validate: true 204 | no_data_timeframe: 10 205 | threshold_windows: 206 | trigger_window: "last_15m" 207 | recovery_window: "last_15m" 208 | thresholds: 209 | critical: 1 210 | #warning: 211 | #unknown: 212 | #ok: 213 | critical_recovery: 0 214 | #warning_recovery: 215 | -------------------------------------------------------------------------------- /catalog/roles/admin.yaml: -------------------------------------------------------------------------------- 1 | admin: 2 | name: "Admin" 3 | permissions: 4 | - "admin" 5 | -------------------------------------------------------------------------------- /catalog/roles/api.yaml: -------------------------------------------------------------------------------- 1 | api-keys-read: 2 | name: "API keys read" 3 | permissions: 4 | - "api_keys_read" 5 | 6 | api-keys-write: 7 | name: "API keys write" 8 | permissions: 9 | - "api_keys_write" 10 | -------------------------------------------------------------------------------- /catalog/roles/apm.yaml: -------------------------------------------------------------------------------- 1 | apm-apdex-manage-write: 2 | name: "APM apdex manage write" 3 | permissions: 4 | - "apm_apdex_manage_write" 5 | 6 | apm-primary-operation-write: 7 | name: "APM primary operation write" 8 | permissions: 9 | - "apm_primary_operation_write" 10 | 11 | apm-retention-filter-read: 12 | name: "APM retention filter read" 13 | permissions: 14 | - "apm_retention_filter_read" 15 | 16 | apm-retention-filter-write: 17 | name: "APM retention filter write" 18 | permissions: 19 | - "apm_retention_filter_write" 20 | 21 | apm-service-ingest-read: 22 | name: "APM service ingest read" 23 | permissions: 24 | - "apm_service_ingest_read" 25 | 26 | apm-service-ingest-write: 27 | name: "APM service ingest write" 28 | permissions: 29 | - "apm_service_ingest_write" 30 | 31 | apm-tag-management-write: 32 | name: "APM tag management write" 33 | permissions: 34 | - "apm_tag_management_write" 35 | -------------------------------------------------------------------------------- /catalog/roles/billing.yaml: -------------------------------------------------------------------------------- 1 | billing-edit: 2 | name: "Billing edit" 3 | permissions: 4 | - "billing_edit" 5 | 6 | billing-read: 7 | name: "Billing read" 8 | permissions: 9 | - "billing_read" 10 | -------------------------------------------------------------------------------- /catalog/roles/dashboards.yaml: -------------------------------------------------------------------------------- 1 | dashboards-public-share: 2 | name: "Dashboards public share" 3 | permissions: 4 | - "dashboards_public_share" 5 | 6 | dashboards-write: 7 | name: "Dashboards write" 8 | permissions: 9 | - "dashboards_write" 10 | -------------------------------------------------------------------------------- /catalog/roles/data.yaml: -------------------------------------------------------------------------------- 1 | data-scanner-read: 2 | name: "Data scanner read" 3 | permissions: 4 | - "data_scanner_read" 5 | 6 | data-scanner-write: 7 | name: "Data scanner write" 8 | permissions: 9 | - "data_scanner_write" 10 | -------------------------------------------------------------------------------- /catalog/roles/incident.yaml: -------------------------------------------------------------------------------- 1 | incident-settings-read: 2 | name: "Incident settings read" 3 | permissions: 4 | - "incident_settings_read" 5 | 6 | incident-settings-write: 7 | name: "Incident settings write" 8 | permissions: 9 | - "incident_settings_write" 10 | 11 | incident-write: 12 | name: "Incident write" 13 | permissions: 14 | - "incident_write" 15 | -------------------------------------------------------------------------------- /catalog/roles/integrations.yaml: -------------------------------------------------------------------------------- 1 | integrations-api: 2 | name: "Integrations API" 3 | permissions: 4 | - "integrations_api" 5 | -------------------------------------------------------------------------------- /catalog/roles/logs.yaml: -------------------------------------------------------------------------------- 1 | logs-generate-metrics: 2 | name: "Logs generate metrics" 3 | permissions: 4 | - "logs_generate_metrics" 5 | 6 | logs-live-tail: 7 | name: "Logs live tail" 8 | permissions: 9 | - "logs_live_tail" 10 | 11 | logs-modify-indexes: 12 | name: "Logs modify indexes" 13 | permissions: 14 | - "logs_modify_indexes" 15 | 16 | logs-public-config-api: 17 | name: "Logs public config api" 18 | permissions: 19 | - "logs_public_config_api" 20 | 21 | logs-read-archives: 22 | name: "Logs read archives" 23 | permissions: 24 | - "logs_read_archives" 25 | 26 | logs-read-data: 27 | name: "Logs read data" 28 | permissions: 29 | - "logs_read_data" 30 | 31 | logs-read-index-data: 32 | name: "Logs read index data" 33 | permissions: 34 | - "logs_read_index_data" 35 | 36 | logs-write-archives: 37 | name: "Logs write archives" 38 | permissions: 39 | - "logs_write_archives" 40 | 41 | logs-write-exclusion-filters: 42 | name: "Logs write exclusion filters" 43 | permissions: 44 | - "logs_write_exclusion_filters" 45 | 46 | logs-write-facets: 47 | name: "Logs write facets" 48 | permissions: 49 | - "logs_write_facets" 50 | 51 | logs-write-historical-view: 52 | name: "Logs write historical view" 53 | permissions: 54 | - "logs_write_historical_view" 55 | 56 | logs-write-pipelines: 57 | name: "Logs write pipelines" 58 | permissions: 59 | - "logs_write_pipelines" 60 | 61 | logs-write-processors: 62 | name: "Logs write processors" 63 | permissions: 64 | - "logs_write_processors" 65 | -------------------------------------------------------------------------------- /catalog/roles/metric.yaml: -------------------------------------------------------------------------------- 1 | metric-tags-write: 2 | name: "Metric tags write" 3 | permissions: 4 | - "metric_tags_write" 5 | -------------------------------------------------------------------------------- /catalog/roles/monitors.yaml: -------------------------------------------------------------------------------- 1 | monitors-write: 2 | name: "Monitors write" 3 | permissions: 4 | - "monitors_write" 5 | 6 | monitors-downtime: 7 | name: "Monitors downtime" 8 | permissions: 9 | - "monitors_downtime" 10 | -------------------------------------------------------------------------------- /catalog/roles/org.yaml: -------------------------------------------------------------------------------- 1 | org-app-keys-read: 2 | name: "Org app keys read" 3 | permissions: 4 | - "org_app_keys_read" 5 | 6 | org-app-keys-write: 7 | name: "Org app keys write" 8 | permissions: 9 | - "org_app_keys_write" 10 | -------------------------------------------------------------------------------- /catalog/roles/rum.yaml: -------------------------------------------------------------------------------- 1 | rum-apps-write: 2 | name: "Rum apps write" 3 | permissions: 4 | - "rum_apps_write" 5 | -------------------------------------------------------------------------------- /catalog/roles/security.yaml: -------------------------------------------------------------------------------- 1 | security-monitoring-filters-read: 2 | name: "Security monitoring filters read" 3 | permissions: 4 | - "security_monitoring_filters_read" 5 | 6 | security-monitoring-filters-write: 7 | name: "Security monitoring filters write" 8 | permissions: 9 | - "security_monitoring_filters_write" 10 | 11 | security-monitoring-rules-read: 12 | name: "Security monitoring rules read" 13 | permissions: 14 | - "security_monitoring_rules_read" 15 | 16 | security-monitoring-rules-write: 17 | name: "Security monitoring rules write" 18 | permissions: 19 | - "security_monitoring_rules_write" 20 | 21 | security-monitoring-signals-read: 22 | name: "Security monitoring signals read" 23 | permissions: 24 | - "security_monitoring_signals_read" 25 | -------------------------------------------------------------------------------- /catalog/roles/standard.yaml: -------------------------------------------------------------------------------- 1 | standard: 2 | name: "Standard" 3 | permissions: 4 | - "standard" 5 | -------------------------------------------------------------------------------- /catalog/roles/synthetics.yaml: -------------------------------------------------------------------------------- 1 | synthetics-default-settings-read: 2 | name: "Synthetics default settings read" 3 | permissions: 4 | - "synthetics_default_settings_read" 5 | 6 | synthetics-default-settings-write: 7 | name: "Synthetics default settings write" 8 | permissions: 9 | - "synthetics_default_settings_write" 10 | 11 | synthetics-global-variable-read: 12 | name: "Synthetics global variable read" 13 | permissions: 14 | - "synthetics_global_variable_read" 15 | 16 | synthetics-global-variable-write: 17 | name: "Synthetics global variable write" 18 | permissions: 19 | - "synthetics_global_variable_write" 20 | 21 | synthetics-private-location-read: 22 | name: "Synthetics private location read" 23 | permissions: 24 | - "synthetics_private_location_read" 25 | 26 | synthetics-private-location-write: 27 | name: "Synthetics private location write" 28 | permissions: 29 | - "synthetics_private_location_write" 30 | 31 | synthetics-read: 32 | name: "Synthetics read" 33 | permissions: 34 | - "synthetics_read" 35 | 36 | synthetics-write: 37 | name: "Synthetics write" 38 | permissions: 39 | - "synthetics_write" 40 | -------------------------------------------------------------------------------- /catalog/roles/usage.yaml: -------------------------------------------------------------------------------- 1 | usage-edit: 2 | name: "Usage edit" 3 | permissions: 4 | - "usage_edit" 5 | 6 | usage-read: 7 | name: "Usage read" 8 | permissions: 9 | - "usage_read" 10 | -------------------------------------------------------------------------------- /catalog/roles/user.yaml: -------------------------------------------------------------------------------- 1 | user-access-invite: 2 | name: "User access invite" 3 | permissions: 4 | - "user_access_invite" 5 | 6 | user-access-manage: 7 | name: "User access manage" 8 | permissions: 9 | - "user_access_manage" 10 | 11 | user-app-keys: 12 | name: "User app keys" 13 | permissions: 14 | - "user_app_keys" 15 | -------------------------------------------------------------------------------- /context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf 12 | # 13 | # Modules should access the whole context as `module.this.context` 14 | # to get the input variables with nulls for defaults, 15 | # for example `context = module.this.context`, 16 | # and access individual variables as `module.this.`, 17 | # with final values filled in. 18 | # 19 | # For example, when using defaults, `module.this.context.delimiter` 20 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 21 | # 22 | 23 | module "this" { 24 | source = "cloudposse/label/null" 25 | version = "0.25.0" # requires Terraform >= 0.13.0 26 | 27 | enabled = var.enabled 28 | namespace = var.namespace 29 | tenant = var.tenant 30 | environment = var.environment 31 | stage = var.stage 32 | name = var.name 33 | delimiter = var.delimiter 34 | attributes = var.attributes 35 | tags = var.tags 36 | additional_tag_map = var.additional_tag_map 37 | label_order = var.label_order 38 | regex_replace_chars = var.regex_replace_chars 39 | id_length_limit = var.id_length_limit 40 | label_key_case = var.label_key_case 41 | label_value_case = var.label_value_case 42 | descriptor_formats = var.descriptor_formats 43 | labels_as_tags = var.labels_as_tags 44 | 45 | context = var.context 46 | } 47 | 48 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 49 | 50 | variable "context" { 51 | type = any 52 | default = { 53 | enabled = true 54 | namespace = null 55 | tenant = null 56 | environment = null 57 | stage = null 58 | name = null 59 | delimiter = null 60 | attributes = [] 61 | tags = {} 62 | additional_tag_map = {} 63 | regex_replace_chars = null 64 | label_order = [] 65 | id_length_limit = null 66 | label_key_case = null 67 | label_value_case = null 68 | descriptor_formats = {} 69 | # Note: we have to use [] instead of null for unset lists due to 70 | # https://github.com/hashicorp/terraform/issues/28137 71 | # which was not fixed until Terraform 1.0.0, 72 | # but we want the default to be all the labels in `label_order` 73 | # and we want users to be able to prevent all tag generation 74 | # by setting `labels_as_tags` to `[]`, so we need 75 | # a different sentinel to indicate "default" 76 | labels_as_tags = ["unset"] 77 | } 78 | description = <<-EOT 79 | Single object for setting entire context at once. 80 | See description of individual variables for details. 81 | Leave string and numeric variables as `null` to use default value. 82 | Individual variable settings (non-null) override settings in context object, 83 | except for attributes, tags, and additional_tag_map, which are merged. 84 | EOT 85 | 86 | validation { 87 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 88 | error_message = "Allowed values: `lower`, `title`, `upper`." 89 | } 90 | 91 | validation { 92 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 93 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 94 | } 95 | } 96 | 97 | variable "enabled" { 98 | type = bool 99 | default = null 100 | description = "Set to false to prevent the module from creating any resources" 101 | } 102 | 103 | variable "namespace" { 104 | type = string 105 | default = null 106 | description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" 107 | } 108 | 109 | variable "tenant" { 110 | type = string 111 | default = null 112 | description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" 113 | } 114 | 115 | variable "environment" { 116 | type = string 117 | default = null 118 | description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" 119 | } 120 | 121 | variable "stage" { 122 | type = string 123 | default = null 124 | description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" 125 | } 126 | 127 | variable "name" { 128 | type = string 129 | default = null 130 | description = <<-EOT 131 | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. 132 | This is the only ID element not also included as a `tag`. 133 | The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. 134 | EOT 135 | } 136 | 137 | variable "delimiter" { 138 | type = string 139 | default = null 140 | description = <<-EOT 141 | Delimiter to be used between ID elements. 142 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 143 | EOT 144 | } 145 | 146 | variable "attributes" { 147 | type = list(string) 148 | default = [] 149 | description = <<-EOT 150 | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, 151 | in the order they appear in the list. New attributes are appended to the 152 | end of the list. The elements of the list are joined by the `delimiter` 153 | and treated as a single ID element. 154 | EOT 155 | } 156 | 157 | variable "labels_as_tags" { 158 | type = set(string) 159 | default = ["default"] 160 | description = <<-EOT 161 | Set of labels (ID elements) to include as tags in the `tags` output. 162 | Default is to include all labels. 163 | Tags with empty values will not be included in the `tags` output. 164 | Set to `[]` to suppress all generated tags. 165 | **Notes:** 166 | The value of the `name` tag, if included, will be the `id`, not the `name`. 167 | Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be 168 | changed in later chained modules. Attempts to change it will be silently ignored. 169 | EOT 170 | } 171 | 172 | variable "tags" { 173 | type = map(string) 174 | default = {} 175 | description = <<-EOT 176 | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). 177 | Neither the tag keys nor the tag values will be modified by this module. 178 | EOT 179 | } 180 | 181 | variable "additional_tag_map" { 182 | type = map(string) 183 | default = {} 184 | description = <<-EOT 185 | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. 186 | This is for some rare cases where resources want additional configuration of tags 187 | and therefore take a list of maps with tag key, value, and additional configuration. 188 | EOT 189 | } 190 | 191 | variable "label_order" { 192 | type = list(string) 193 | default = null 194 | description = <<-EOT 195 | The order in which the labels (ID elements) appear in the `id`. 196 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 197 | You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. 198 | EOT 199 | } 200 | 201 | variable "regex_replace_chars" { 202 | type = string 203 | default = null 204 | description = <<-EOT 205 | Terraform regular expression (regex) string. 206 | Characters matching the regex will be removed from the ID elements. 207 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 208 | EOT 209 | } 210 | 211 | variable "id_length_limit" { 212 | type = number 213 | default = null 214 | description = <<-EOT 215 | Limit `id` to this many characters (minimum 6). 216 | Set to `0` for unlimited length. 217 | Set to `null` for keep the existing setting, which defaults to `0`. 218 | Does not affect `id_full`. 219 | EOT 220 | validation { 221 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 222 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 223 | } 224 | } 225 | 226 | variable "label_key_case" { 227 | type = string 228 | default = null 229 | description = <<-EOT 230 | Controls the letter case of the `tags` keys (label names) for tags generated by this module. 231 | Does not affect keys of tags passed in via the `tags` input. 232 | Possible values: `lower`, `title`, `upper`. 233 | Default value: `title`. 234 | EOT 235 | 236 | validation { 237 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 238 | error_message = "Allowed values: `lower`, `title`, `upper`." 239 | } 240 | } 241 | 242 | variable "label_value_case" { 243 | type = string 244 | default = null 245 | description = <<-EOT 246 | Controls the letter case of ID elements (labels) as included in `id`, 247 | set as tag values, and output by this module individually. 248 | Does not affect values of tags passed in via the `tags` input. 249 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 250 | Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. 251 | Default value: `lower`. 252 | EOT 253 | 254 | validation { 255 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 256 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 257 | } 258 | } 259 | 260 | variable "descriptor_formats" { 261 | type = any 262 | default = {} 263 | description = <<-EOT 264 | Describe additional descriptors to be output in the `descriptors` output map. 265 | Map of maps. Keys are names of descriptors. Values are maps of the form 266 | `{ 267 | format = string 268 | labels = list(string) 269 | }` 270 | (Type is `any` so the map values can later be enhanced to provide additional options.) 271 | `format` is a Terraform format string to be passed to the `format()` function. 272 | `labels` is a list of labels, in order, to pass to `format()` function. 273 | Label values will be normalized before being passed to `format()` so they will be 274 | identical to how they appear in `id`. 275 | Default is `{}` (`descriptors` output will be empty). 276 | EOT 277 | } 278 | 279 | #### End of copy of cloudposse/terraform-null-label/variables.tf 280 | -------------------------------------------------------------------------------- /examples/child_organization/fixtures.us-east-2.tfvars: -------------------------------------------------------------------------------- 1 | namespace = "eg" 2 | 3 | stage = "test" 4 | 5 | name = "datadog-child-org" 6 | 7 | organization_name = "test" 8 | -------------------------------------------------------------------------------- /examples/child_organization/main.tf: -------------------------------------------------------------------------------- 1 | module "datadog_child_organization" { 2 | source = "../../modules/child_organization" 3 | 4 | organization_name = var.organization_name 5 | 6 | context = module.this.context 7 | } 8 | -------------------------------------------------------------------------------- /examples/child_organization/outputs.tf: -------------------------------------------------------------------------------- 1 | output "id" { 2 | value = module.datadog_child_organization.id 3 | description = "Organization ID" 4 | } 5 | 6 | output "description" { 7 | value = module.datadog_child_organization.description 8 | description = "Description of the organization" 9 | } 10 | 11 | output "public_id" { 12 | value = module.datadog_child_organization.public_id 13 | description = "Public ID of the organization" 14 | } 15 | 16 | output "user" { 17 | value = module.datadog_child_organization.user 18 | description = "Information about organization users" 19 | } 20 | 21 | output "settings" { 22 | value = module.datadog_child_organization.settings 23 | description = "Organization settings" 24 | } 25 | -------------------------------------------------------------------------------- /examples/child_organization/providers.tf: -------------------------------------------------------------------------------- 1 | # For the example, we use ENV variables `DD_API_KEY` and `DD_APP_KEY` in place of the `api_key` and `app_key` parameters to the provider 2 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs#argument-reference 3 | provider "datadog" { 4 | validate = false 5 | } 6 | -------------------------------------------------------------------------------- /examples/child_organization/variables.tf: -------------------------------------------------------------------------------- 1 | # https://docs.datadoghq.com/account_management/org_settings/#overview 2 | 3 | variable "organization_name" { 4 | type = string 5 | description = "Datadog organization name" 6 | } 7 | -------------------------------------------------------------------------------- /examples/child_organization/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | datadog = { 6 | source = "datadog/datadog" 7 | version = ">= 3.0.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/complete/fixtures.us-east-2.tfvars: -------------------------------------------------------------------------------- 1 | namespace = "eg" 2 | 3 | stage = "test" 4 | 5 | name = "datadog-monitor" 6 | 7 | alert_tags = ["@opsgenie"] 8 | 9 | alert_tags_separator = "\n" 10 | 11 | legacy_monitor_paths = ["./monitors-test/*.yaml"] 12 | 13 | json_monitor_paths = ["./monitors-test/*.json"] 14 | -------------------------------------------------------------------------------- /examples/complete/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | # Note that we do not follow the usual pattern of using the `attributes` in the resource ID. 3 | # This is because we do not have automatic cleanup of the monitors, and we want to avoid 4 | # leaving orphaned monitors around. This way, tests will fail if they find an orphaned 5 | # monitor already exists, and we will know to clean it up manually. 6 | legacy_monitor_files = flatten([for p in var.legacy_monitor_paths : fileset(path.module, p)]) 7 | legacy_monitor_list = [for f in local.legacy_monitor_files : yamldecode(file(f))] 8 | legacy_monitor_map = merge(local.legacy_monitor_list...) 9 | 10 | json_monitor_files = flatten([for p in var.json_monitor_paths : fileset(path.module, p)]) 11 | json_monitor_list = [for f in local.json_monitor_files : jsondecode(file(f))] 12 | json_monitor_map = { for v in local.json_monitor_list : v.name => v } 13 | 14 | monitor_map = merge(local.legacy_monitor_map, local.json_monitor_map) 15 | } 16 | 17 | module "datadog_monitors" { 18 | source = "../../modules/monitors" 19 | 20 | datadog_monitors = local.monitor_map 21 | alert_tags = var.alert_tags 22 | alert_tags_separator = var.alert_tags_separator 23 | 24 | context = module.this.context 25 | } 26 | -------------------------------------------------------------------------------- /examples/complete/monitors-test/anomaly-recovery-legacy-test.yaml: -------------------------------------------------------------------------------- 1 | anomaly-recovery-legacy-test: 2 | name: anomaly recovery legacy test 3 | type: query alert 4 | query: avg(last_1d):anomalies(avg:system.load.1{*}, 'agile', 2, direction='both', interval=300, alert_window='last_37m', count_default_zero='true', seasonality='weekly') >= 1 5 | message: recovery window test 6 | tags: 7 | ManagedBy: Terraform 8 | Terratest: 9 | thresholds: 10 | critical: 1 11 | critical_recovery: 0 12 | warning: 0.8 13 | notify_audit: false 14 | require_full_window: false 15 | notify_no_data: false 16 | renotify_interval: 0 17 | threshold_windows: 18 | trigger_window: last_37m 19 | recovery_window: last_1h 20 | include_tags: false 21 | timeout_h: 1 22 | evaluation_delay: 88 23 | new_host_delay: 300 24 | priority: 5 25 | restricted_roles: null 26 | -------------------------------------------------------------------------------- /examples/complete/monitors-test/anomaly-recovery-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anomaly-recovery-test", 3 | "type": "query alert", 4 | "query": "avg(last_1d):anomalies(avg:system.load.1{*}, 'agile', 2, direction='both', interval=300, alert_window='last_37m', count_default_zero='true', seasonality='weekly') >= 1", 5 | "message": "recovery window test", 6 | "tags": [], 7 | "options": { 8 | "thresholds": { 9 | "critical": 1, 10 | "critical_recovery": 0, 11 | "warning": 0.8 12 | }, 13 | "notify_audit": false, 14 | "require_full_window": false, 15 | "notify_no_data": false, 16 | "renotify_interval": 0, 17 | "threshold_windows": { 18 | "trigger_window": "last_37m", 19 | "recovery_window": "last_1h" 20 | }, 21 | "include_tags": false, 22 | "timeout_h": 1, 23 | "evaluation_delay": 88, 24 | "new_host_delay": 300, 25 | "silenced": {} 26 | }, 27 | "priority": 5, 28 | "restricted_roles": null 29 | } 30 | -------------------------------------------------------------------------------- /examples/complete/monitors-test/monitor-variables-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monitor-variables-test", 3 | "type": "event-v2 alert", 4 | "query": "formula(\"query2 / query1 * 100\").last(\"15m\") > 0.8", 5 | "message": "Test of monitors with variables", 6 | "tags": [ 7 | "test:examplemonitor", 8 | "env:ci" 9 | ], 10 | "options": { 11 | "thresholds": { 12 | "critical": 0.8 13 | }, 14 | "enable_logs_sample": false, 15 | "notify_audit": false, 16 | "on_missing_data": "default", 17 | "include_tags": false, 18 | "variables": [ 19 | { 20 | "data_source": "events", 21 | "name": "query2", 22 | "indexes": [ 23 | "*" 24 | ], 25 | "compute": { 26 | "aggregation": "count" 27 | }, 28 | "group_by": [], 29 | "search": { 30 | "query": "status:error" 31 | }, 32 | "storage": "hot" 33 | }, 34 | { 35 | "data_source": "events", 36 | "name": "query1", 37 | "indexes": [ 38 | "*" 39 | ], 40 | "compute": { 41 | "aggregation": "count" 42 | }, 43 | "group_by": [], 44 | "search": { 45 | "query": "" 46 | }, 47 | "storage": "hot" 48 | } 49 | ], 50 | "new_host_delay": 300, 51 | "groupby_simple_monitor": false, 52 | "silenced": {} 53 | }, 54 | "priority": 5, 55 | "restricted_roles": null 56 | } 57 | -------------------------------------------------------------------------------- /examples/complete/monitors-test/schedule-legacy-test.yaml: -------------------------------------------------------------------------------- 1 | schedule-legacy-test: 2 | name: schedule-legacy-test 3 | type: event-v2 alert 4 | query: events("service:datadog-agent").rollup("cardinality", "@evt.id").current("1h") > 2345 5 | message: No message 6 | threshold_windows: {} 7 | thresholds: 8 | critical: 2345 9 | warning: 987 10 | enable_logs_sample: false 11 | notify_audit: false 12 | on_missing_data: default 13 | include_tags: false 14 | scheduling_options: 15 | - evaluation_window: 16 | - hour_starts: 7 17 | priority: 5 18 | -------------------------------------------------------------------------------- /examples/complete/monitors-test/schedule-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "schedule-test", 3 | "type": "event-v2 alert", 4 | "query": "events(\"service:datadog-agent\").rollup(\"cardinality\", \"@evt.id\").current(\"1h\") > 2345", 5 | "message": "No message", 6 | "tags": [ 7 | "test:examplemonitor", 8 | "Terratest" 9 | ], 10 | "options": { 11 | "thresholds": { 12 | "critical": 2345, 13 | "warning": 987 14 | }, 15 | "enable_logs_sample": false, 16 | "notify_audit": false, 17 | "on_missing_data": "default", 18 | "include_tags": false, 19 | "scheduling_options": { 20 | "custom_schedule": { 21 | "recurrences": [ 22 | { 23 | "rrule": "FREQ=DAILY;INTERVAL=1;BYHOUR=17;BYMINUTE=54", 24 | "timezone": "America/Los_Angeles" 25 | } 26 | ] 27 | }, 28 | "evaluation_window": { 29 | "hour_starts": 7 30 | } 31 | } 32 | }, 33 | "priority": 5 34 | } 35 | -------------------------------------------------------------------------------- /examples/complete/outputs.tf: -------------------------------------------------------------------------------- 1 | output "datadog_monitor_names" { 2 | value = module.datadog_monitors.datadog_monitor_names 3 | description = "Names of the created Datadog monitors" 4 | } 5 | 6 | output "datadog_monitors" { 7 | value = module.datadog_monitors.datadog_monitors 8 | description = "Datadog monitor outputs" 9 | } 10 | -------------------------------------------------------------------------------- /examples/complete/providers.tf: -------------------------------------------------------------------------------- 1 | # For the example, we use ENV variables `DD_API_KEY` and `DD_APP_KEY` in place of the `api_key` and `app_key` parameters to the provider 2 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs#argument-reference 3 | provider "datadog" { 4 | } 5 | -------------------------------------------------------------------------------- /examples/complete/variables.tf: -------------------------------------------------------------------------------- 1 | variable "alert_tags" { 2 | type = list(string) 3 | description = "List of alert tags to add to all alert messages, e.g. `[\"@opsgenie\"]` or `[\"@devops\", \"@opsgenie\"]`" 4 | } 5 | 6 | variable "alert_tags_separator" { 7 | type = string 8 | description = "Separator for the alert tags. All strings from the `alert_tags` variable will be joined into one string using the separator and then added to the alert message" 9 | } 10 | 11 | variable "legacy_monitor_paths" { 12 | type = list(string) 13 | description = "List of paths to legacy Datadog monitor configurations" 14 | } 15 | 16 | variable "json_monitor_paths" { 17 | type = list(string) 18 | description = "List of paths to JSON Datadog monitor configurations" 19 | } 20 | -------------------------------------------------------------------------------- /examples/complete/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | datadog = { 6 | source = "datadog/datadog" 7 | version = ">= 3.0.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/organization_settings/fixtures.us-east-2.tfvars: -------------------------------------------------------------------------------- 1 | namespace = "eg" 2 | 3 | stage = "test" 4 | 5 | name = "datadog-org-settings" 6 | 7 | organization_name = "test" 8 | 9 | # Free and Trial organizations cannot enable SAML 10 | saml_enabled = false 11 | 12 | saml_autocreate_users_domains = [] 13 | 14 | saml_autocreate_users_enabled = false 15 | 16 | saml_idp_initiated_login_enabled = false 17 | 18 | saml_strict_mode_enabled = false 19 | 20 | private_widget_share = false 21 | 22 | saml_autocreate_access_role = "ro" 23 | -------------------------------------------------------------------------------- /examples/organization_settings/main.tf: -------------------------------------------------------------------------------- 1 | module "datadog_organization_settings" { 2 | source = "../../modules/organization_settings" 3 | 4 | organization_name = var.organization_name 5 | saml_enabled = var.saml_enabled 6 | saml_autocreate_users_domains = var.saml_autocreate_users_domains 7 | saml_autocreate_users_enabled = var.saml_autocreate_users_enabled 8 | saml_idp_initiated_login_enabled = var.saml_idp_initiated_login_enabled 9 | saml_strict_mode_enabled = var.saml_strict_mode_enabled 10 | private_widget_share = var.private_widget_share 11 | saml_autocreate_access_role = var.saml_autocreate_access_role 12 | 13 | context = module.this.context 14 | } 15 | -------------------------------------------------------------------------------- /examples/organization_settings/outputs.tf: -------------------------------------------------------------------------------- 1 | output "id" { 2 | value = module.datadog_organization_settings.id 3 | description = "Organization ID" 4 | } 5 | 6 | output "description" { 7 | value = module.datadog_organization_settings.description 8 | description = "Description of the organization" 9 | } 10 | 11 | output "public_id" { 12 | value = module.datadog_organization_settings.public_id 13 | description = "Public ID of the organization" 14 | } 15 | 16 | output "settings" { 17 | value = module.datadog_organization_settings.settings 18 | description = "Organization settings" 19 | } 20 | -------------------------------------------------------------------------------- /examples/organization_settings/providers.tf: -------------------------------------------------------------------------------- 1 | # For the example, we use ENV variables `DD_API_KEY` and `DD_APP_KEY` in place of the `api_key` and `app_key` parameters to the provider 2 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs#argument-reference 3 | provider "datadog" { 4 | validate = false 5 | } 6 | -------------------------------------------------------------------------------- /examples/organization_settings/variables.tf: -------------------------------------------------------------------------------- 1 | # https://docs.datadoghq.com/account_management/org_settings/#overview 2 | 3 | variable "organization_name" { 4 | type = string 5 | description = "Datadog organization name" 6 | } 7 | 8 | variable "saml_enabled" { 9 | type = bool 10 | default = true 11 | description = "Whether or not SAML is enabled for the child organization. Note that Free and Trial organizations cannot enable SAML" 12 | } 13 | 14 | variable "saml_autocreate_users_domains" { 15 | type = list(string) 16 | default = [] 17 | description = "List of domains where the SAML automated user creation is enabled" 18 | } 19 | 20 | variable "saml_autocreate_users_enabled" { 21 | type = bool 22 | default = false 23 | description = "Whether or not the automated user creation based on SAML domain is enabled" 24 | } 25 | 26 | variable "saml_idp_initiated_login_enabled" { 27 | type = bool 28 | default = true 29 | description = "Whether or not IdP initiated login is enabled for the Datadog organization" 30 | } 31 | 32 | variable "saml_strict_mode_enabled" { 33 | type = bool 34 | default = false 35 | description = "Whether or not the SAML strict mode is enabled. If true, all users must log in with SAML" 36 | } 37 | 38 | variable "private_widget_share" { 39 | type = bool 40 | default = false 41 | description = "Whether or not the organization users can share widgets outside of Datadog" 42 | } 43 | 44 | variable "saml_autocreate_access_role" { 45 | type = string 46 | default = "ro" 47 | description = "The access role of an autocreated user. Options are `st` (standard user), `adm` (admin user), or `ro` (read-only user). Allowed enum values: `st`, `adm` , `ro`, `ERROR`" 48 | } 49 | -------------------------------------------------------------------------------- /examples/organization_settings/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | datadog = { 6 | source = "datadog/datadog" 7 | version = ">= 3.0.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/rbac/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf 12 | # 13 | # Modules should access the whole context as `module.this.context` 14 | # to get the input variables with nulls for defaults, 15 | # for example `context = module.this.context`, 16 | # and access individual variables as `module.this.`, 17 | # with final values filled in. 18 | # 19 | # For example, when using defaults, `module.this.context.delimiter` 20 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 21 | # 22 | 23 | module "this" { 24 | source = "cloudposse/label/null" 25 | version = "0.25.0" # requires Terraform >= 0.13.0 26 | 27 | enabled = var.enabled 28 | namespace = var.namespace 29 | tenant = var.tenant 30 | environment = var.environment 31 | stage = var.stage 32 | name = var.name 33 | delimiter = var.delimiter 34 | attributes = var.attributes 35 | tags = var.tags 36 | additional_tag_map = var.additional_tag_map 37 | label_order = var.label_order 38 | regex_replace_chars = var.regex_replace_chars 39 | id_length_limit = var.id_length_limit 40 | label_key_case = var.label_key_case 41 | label_value_case = var.label_value_case 42 | descriptor_formats = var.descriptor_formats 43 | labels_as_tags = var.labels_as_tags 44 | 45 | context = var.context 46 | } 47 | 48 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 49 | 50 | variable "context" { 51 | type = any 52 | default = { 53 | enabled = true 54 | namespace = null 55 | tenant = null 56 | environment = null 57 | stage = null 58 | name = null 59 | delimiter = null 60 | attributes = [] 61 | tags = {} 62 | additional_tag_map = {} 63 | regex_replace_chars = null 64 | label_order = [] 65 | id_length_limit = null 66 | label_key_case = null 67 | label_value_case = null 68 | descriptor_formats = {} 69 | # Note: we have to use [] instead of null for unset lists due to 70 | # https://github.com/hashicorp/terraform/issues/28137 71 | # which was not fixed until Terraform 1.0.0, 72 | # but we want the default to be all the labels in `label_order` 73 | # and we want users to be able to prevent all tag generation 74 | # by setting `labels_as_tags` to `[]`, so we need 75 | # a different sentinel to indicate "default" 76 | labels_as_tags = ["unset"] 77 | } 78 | description = <<-EOT 79 | Single object for setting entire context at once. 80 | See description of individual variables for details. 81 | Leave string and numeric variables as `null` to use default value. 82 | Individual variable settings (non-null) override settings in context object, 83 | except for attributes, tags, and additional_tag_map, which are merged. 84 | EOT 85 | 86 | validation { 87 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 88 | error_message = "Allowed values: `lower`, `title`, `upper`." 89 | } 90 | 91 | validation { 92 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 93 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 94 | } 95 | } 96 | 97 | variable "enabled" { 98 | type = bool 99 | default = null 100 | description = "Set to false to prevent the module from creating any resources" 101 | } 102 | 103 | variable "namespace" { 104 | type = string 105 | default = null 106 | description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" 107 | } 108 | 109 | variable "tenant" { 110 | type = string 111 | default = null 112 | description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" 113 | } 114 | 115 | variable "environment" { 116 | type = string 117 | default = null 118 | description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" 119 | } 120 | 121 | variable "stage" { 122 | type = string 123 | default = null 124 | description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" 125 | } 126 | 127 | variable "name" { 128 | type = string 129 | default = null 130 | description = <<-EOT 131 | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. 132 | This is the only ID element not also included as a `tag`. 133 | The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. 134 | EOT 135 | } 136 | 137 | variable "delimiter" { 138 | type = string 139 | default = null 140 | description = <<-EOT 141 | Delimiter to be used between ID elements. 142 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 143 | EOT 144 | } 145 | 146 | variable "attributes" { 147 | type = list(string) 148 | default = [] 149 | description = <<-EOT 150 | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, 151 | in the order they appear in the list. New attributes are appended to the 152 | end of the list. The elements of the list are joined by the `delimiter` 153 | and treated as a single ID element. 154 | EOT 155 | } 156 | 157 | variable "labels_as_tags" { 158 | type = set(string) 159 | default = ["default"] 160 | description = <<-EOT 161 | Set of labels (ID elements) to include as tags in the `tags` output. 162 | Default is to include all labels. 163 | Tags with empty values will not be included in the `tags` output. 164 | Set to `[]` to suppress all generated tags. 165 | **Notes:** 166 | The value of the `name` tag, if included, will be the `id`, not the `name`. 167 | Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be 168 | changed in later chained modules. Attempts to change it will be silently ignored. 169 | EOT 170 | } 171 | 172 | variable "tags" { 173 | type = map(string) 174 | default = {} 175 | description = <<-EOT 176 | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). 177 | Neither the tag keys nor the tag values will be modified by this module. 178 | EOT 179 | } 180 | 181 | variable "additional_tag_map" { 182 | type = map(string) 183 | default = {} 184 | description = <<-EOT 185 | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. 186 | This is for some rare cases where resources want additional configuration of tags 187 | and therefore take a list of maps with tag key, value, and additional configuration. 188 | EOT 189 | } 190 | 191 | variable "label_order" { 192 | type = list(string) 193 | default = null 194 | description = <<-EOT 195 | The order in which the labels (ID elements) appear in the `id`. 196 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 197 | You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. 198 | EOT 199 | } 200 | 201 | variable "regex_replace_chars" { 202 | type = string 203 | default = null 204 | description = <<-EOT 205 | Terraform regular expression (regex) string. 206 | Characters matching the regex will be removed from the ID elements. 207 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 208 | EOT 209 | } 210 | 211 | variable "id_length_limit" { 212 | type = number 213 | default = null 214 | description = <<-EOT 215 | Limit `id` to this many characters (minimum 6). 216 | Set to `0` for unlimited length. 217 | Set to `null` for keep the existing setting, which defaults to `0`. 218 | Does not affect `id_full`. 219 | EOT 220 | validation { 221 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 222 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 223 | } 224 | } 225 | 226 | variable "label_key_case" { 227 | type = string 228 | default = null 229 | description = <<-EOT 230 | Controls the letter case of the `tags` keys (label names) for tags generated by this module. 231 | Does not affect keys of tags passed in via the `tags` input. 232 | Possible values: `lower`, `title`, `upper`. 233 | Default value: `title`. 234 | EOT 235 | 236 | validation { 237 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 238 | error_message = "Allowed values: `lower`, `title`, `upper`." 239 | } 240 | } 241 | 242 | variable "label_value_case" { 243 | type = string 244 | default = null 245 | description = <<-EOT 246 | Controls the letter case of ID elements (labels) as included in `id`, 247 | set as tag values, and output by this module individually. 248 | Does not affect values of tags passed in via the `tags` input. 249 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 250 | Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. 251 | Default value: `lower`. 252 | EOT 253 | 254 | validation { 255 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 256 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 257 | } 258 | } 259 | 260 | variable "descriptor_formats" { 261 | type = any 262 | default = {} 263 | description = <<-EOT 264 | Describe additional descriptors to be output in the `descriptors` output map. 265 | Map of maps. Keys are names of descriptors. Values are maps of the form 266 | `{ 267 | format = string 268 | labels = list(string) 269 | }` 270 | (Type is `any` so the map values can later be enhanced to provide additional options.) 271 | `format` is a Terraform format string to be passed to the `format()` function. 272 | `labels` is a list of labels, in order, to pass to `format()` function. 273 | Label values will be normalized before being passed to `format()` so they will be 274 | identical to how they appear in `id`. 275 | Default is `{}` (`descriptors` output will be empty). 276 | EOT 277 | } 278 | 279 | #### End of copy of cloudposse/terraform-null-label/variables.tf 280 | -------------------------------------------------------------------------------- /examples/rbac/fixtures.us-east-2.tfvars: -------------------------------------------------------------------------------- 1 | namespace = "eg" 2 | 3 | stage = "test" 4 | 5 | name = "datadog-monitor" 6 | 7 | alert_tags = ["@opsgenie"] 8 | 9 | alert_tags_separator = "\n" 10 | 11 | monitor_paths = ["../../catalog/monitors/*.yaml"] 12 | 13 | role_paths = ["../../catalog/roles/monitors.yaml"] 14 | -------------------------------------------------------------------------------- /examples/rbac/main.tf: -------------------------------------------------------------------------------- 1 | module "monitor_configs" { 2 | source = "cloudposse/config/yaml" 3 | version = "1.0.2" 4 | 5 | map_config_local_base_path = path.module 6 | map_config_paths = var.monitor_paths 7 | 8 | context = module.this.context 9 | } 10 | 11 | module "role_configs" { 12 | source = "cloudposse/config/yaml" 13 | version = "1.0.2" 14 | 15 | map_config_local_base_path = path.module 16 | map_config_paths = var.role_paths 17 | 18 | context = module.this.context 19 | } 20 | 21 | locals { 22 | # Example of assigning restricted roles with permissions to monitors. 23 | # See `catalog/monitors` for the available monitors. 24 | # See `catalog/roles` for the available roles. 25 | # Only these roles will have access to the monitors. 26 | # The Datadog users that are associated with the roles will have the corresponding monitor permissions 27 | 28 | monitors_write_role_name = module.datadog_roles.datadog_roles["monitors-write"].name 29 | monitors_downtime_role_name = module.datadog_roles.datadog_roles["monitors-downtime"].name 30 | 31 | monitors_roles_map = { 32 | aurora-replica-lag = [local.monitors_write_role_name, local.monitors_downtime_role_name] 33 | ec2-failed-status-check = [local.monitors_write_role_name, local.monitors_downtime_role_name] 34 | redshift-health-status = [local.monitors_downtime_role_name] 35 | k8s-deployment-replica-pod-down = [local.monitors_write_role_name] 36 | } 37 | } 38 | 39 | module "datadog_roles" { 40 | source = "../../modules/roles" 41 | 42 | datadog_roles = module.role_configs.map_configs 43 | 44 | context = module.this.context 45 | } 46 | 47 | module "datadog_monitors" { 48 | source = "../../modules/monitors" 49 | 50 | datadog_monitors = module.monitor_configs.map_configs 51 | alert_tags = var.alert_tags 52 | alert_tags_separator = var.alert_tags_separator 53 | restricted_roles_map = local.monitors_roles_map 54 | 55 | context = module.this.context 56 | } 57 | -------------------------------------------------------------------------------- /examples/rbac/outputs.tf: -------------------------------------------------------------------------------- 1 | output "datadog_monitor_names" { 2 | value = module.datadog_monitors.datadog_monitor_names 3 | description = "Names of the created Datadog monitors" 4 | } 5 | 6 | output "datadog_monitors" { 7 | value = module.datadog_monitors.datadog_monitors 8 | description = "The created Datadog monitors" 9 | } 10 | 11 | output "datadog_available_permissions" { 12 | value = local.available_permissions 13 | description = "Map of available permission names to permission IDs" 14 | } 15 | -------------------------------------------------------------------------------- /examples/rbac/providers.tf: -------------------------------------------------------------------------------- 1 | # For the example, we use ENV variables `DD_API_KEY` and `DD_APP_KEY` in place of the `api_key` and `app_key` parameters to the provider 2 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs#argument-reference 3 | provider "datadog" { 4 | } 5 | -------------------------------------------------------------------------------- /examples/rbac/variables.tf: -------------------------------------------------------------------------------- 1 | variable "alert_tags" { 2 | type = list(string) 3 | description = "List of alert tags to add to all alert messages, e.g. `[\"@opsgenie\"]` or `[\"@devops\", \"@opsgenie\"]`" 4 | } 5 | 6 | variable "alert_tags_separator" { 7 | type = string 8 | description = "Separator for the alert tags. All strings from the `alert_tags` variable will be joined into one string using the separator and then added to the alert message" 9 | } 10 | 11 | variable "monitor_paths" { 12 | type = list(string) 13 | description = "List of paths to Datadog monitor configurations" 14 | } 15 | 16 | variable "role_paths" { 17 | type = list(string) 18 | description = "List of paths to Datadog role configurations" 19 | } 20 | -------------------------------------------------------------------------------- /examples/rbac/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | datadog = { 6 | source = "datadog/datadog" 7 | version = ">= 3.0.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/slo/catalog/metric_slo.yaml: -------------------------------------------------------------------------------- 1 | metric-slo: 2 | name: "(SLO) Synthetic Checks" 3 | type: metric 4 | query: 5 | numerator: sum:synthetics.test_runs{status:success}.as_count() 6 | denominator: sum:synthetics.test_runs{*}.as_count() 7 | description: | 8 | Number of Successful Synthetic Checks. 9 | message: | 10 | ({stage} {region}) {instance_id} failed a SLO check 11 | force_delete: true 12 | validate: true 13 | thresholds: 14 | - target: "99.5" 15 | timeframe: "7d" 16 | warning: "99.9" 17 | - target: "99" 18 | timeframe: "30d" 19 | warning: "99.5" 20 | tags: 21 | ManagedBy: terraform 22 | test: true 23 | api_version: null 24 | -------------------------------------------------------------------------------- /examples/slo/catalog/monitor_slo.yaml: -------------------------------------------------------------------------------- 1 | monitor-slo: 2 | name: "(SLO) EC2 Availability" 3 | type: monitor 4 | description: | 5 | Number of EC2 failed status checks. 6 | message: | 7 | ({stage} {region}) {instance_id} failed a SLO check 8 | force_delete: true 9 | validate: true 10 | thresholds: 11 | - target: "99.5" 12 | timeframe: "7d" 13 | warning: "99.9" 14 | - target: "99" 15 | timeframe: "30d" 16 | warning: "99.5" 17 | # Either `monitor_ids` or `monitors` should be provided 18 | # `monitor_ids` is a list of externally created monitors to use for this monitor-based SLO 19 | # If `monitors` map is provided, the monitors will be created by the module and assigned to the SLO 20 | monitor_ids: null 21 | monitors: 22 | ec2-failed-status-check: 23 | name: "(EC2) Status Check" 24 | type: metric alert 25 | query: | 26 | avg(last_10m):avg:aws.ec2.status_check_failed{*} by {instance_id} > 0 27 | message: | 28 | ({stage} {region}) {instance_id} failed a status check 29 | escalation_message: "" 30 | tags: 31 | ManagedBy: Terraform 32 | priority: 3 33 | notify_no_data: false 34 | notify_audit: true 35 | require_full_window: true 36 | enable_logs_sample: false 37 | force_delete: true 38 | include_tags: true 39 | locked: false 40 | renotify_interval: 60 41 | timeout_h: 0 42 | evaluation_delay: 60 43 | new_host_delay: 300 44 | new_group_delay: 0 45 | groupby_simple_monitor: false 46 | renotify_occurrences: 0 47 | renotify_statuses: [] 48 | validate: true 49 | no_data_timeframe: 10 50 | threshold_windows: {} 51 | thresholds: 52 | critical: 0 53 | tags: 54 | ManagedBy: terraform 55 | test: true 56 | api_version: null 57 | -------------------------------------------------------------------------------- /examples/slo/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf 12 | # 13 | # Modules should access the whole context as `module.this.context` 14 | # to get the input variables with nulls for defaults, 15 | # for example `context = module.this.context`, 16 | # and access individual variables as `module.this.`, 17 | # with final values filled in. 18 | # 19 | # For example, when using defaults, `module.this.context.delimiter` 20 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 21 | # 22 | 23 | module "this" { 24 | source = "cloudposse/label/null" 25 | version = "0.25.0" # requires Terraform >= 0.13.0 26 | 27 | enabled = var.enabled 28 | namespace = var.namespace 29 | tenant = var.tenant 30 | environment = var.environment 31 | stage = var.stage 32 | name = var.name 33 | delimiter = var.delimiter 34 | attributes = var.attributes 35 | tags = var.tags 36 | additional_tag_map = var.additional_tag_map 37 | label_order = var.label_order 38 | regex_replace_chars = var.regex_replace_chars 39 | id_length_limit = var.id_length_limit 40 | label_key_case = var.label_key_case 41 | label_value_case = var.label_value_case 42 | descriptor_formats = var.descriptor_formats 43 | labels_as_tags = var.labels_as_tags 44 | 45 | context = var.context 46 | } 47 | 48 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 49 | 50 | variable "context" { 51 | type = any 52 | default = { 53 | enabled = true 54 | namespace = null 55 | tenant = null 56 | environment = null 57 | stage = null 58 | name = null 59 | delimiter = null 60 | attributes = [] 61 | tags = {} 62 | additional_tag_map = {} 63 | regex_replace_chars = null 64 | label_order = [] 65 | id_length_limit = null 66 | label_key_case = null 67 | label_value_case = null 68 | descriptor_formats = {} 69 | # Note: we have to use [] instead of null for unset lists due to 70 | # https://github.com/hashicorp/terraform/issues/28137 71 | # which was not fixed until Terraform 1.0.0, 72 | # but we want the default to be all the labels in `label_order` 73 | # and we want users to be able to prevent all tag generation 74 | # by setting `labels_as_tags` to `[]`, so we need 75 | # a different sentinel to indicate "default" 76 | labels_as_tags = ["unset"] 77 | } 78 | description = <<-EOT 79 | Single object for setting entire context at once. 80 | See description of individual variables for details. 81 | Leave string and numeric variables as `null` to use default value. 82 | Individual variable settings (non-null) override settings in context object, 83 | except for attributes, tags, and additional_tag_map, which are merged. 84 | EOT 85 | 86 | validation { 87 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 88 | error_message = "Allowed values: `lower`, `title`, `upper`." 89 | } 90 | 91 | validation { 92 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 93 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 94 | } 95 | } 96 | 97 | variable "enabled" { 98 | type = bool 99 | default = null 100 | description = "Set to false to prevent the module from creating any resources" 101 | } 102 | 103 | variable "namespace" { 104 | type = string 105 | default = null 106 | description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" 107 | } 108 | 109 | variable "tenant" { 110 | type = string 111 | default = null 112 | description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" 113 | } 114 | 115 | variable "environment" { 116 | type = string 117 | default = null 118 | description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" 119 | } 120 | 121 | variable "stage" { 122 | type = string 123 | default = null 124 | description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" 125 | } 126 | 127 | variable "name" { 128 | type = string 129 | default = null 130 | description = <<-EOT 131 | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. 132 | This is the only ID element not also included as a `tag`. 133 | The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. 134 | EOT 135 | } 136 | 137 | variable "delimiter" { 138 | type = string 139 | default = null 140 | description = <<-EOT 141 | Delimiter to be used between ID elements. 142 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 143 | EOT 144 | } 145 | 146 | variable "attributes" { 147 | type = list(string) 148 | default = [] 149 | description = <<-EOT 150 | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, 151 | in the order they appear in the list. New attributes are appended to the 152 | end of the list. The elements of the list are joined by the `delimiter` 153 | and treated as a single ID element. 154 | EOT 155 | } 156 | 157 | variable "labels_as_tags" { 158 | type = set(string) 159 | default = ["default"] 160 | description = <<-EOT 161 | Set of labels (ID elements) to include as tags in the `tags` output. 162 | Default is to include all labels. 163 | Tags with empty values will not be included in the `tags` output. 164 | Set to `[]` to suppress all generated tags. 165 | **Notes:** 166 | The value of the `name` tag, if included, will be the `id`, not the `name`. 167 | Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be 168 | changed in later chained modules. Attempts to change it will be silently ignored. 169 | EOT 170 | } 171 | 172 | variable "tags" { 173 | type = map(string) 174 | default = {} 175 | description = <<-EOT 176 | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). 177 | Neither the tag keys nor the tag values will be modified by this module. 178 | EOT 179 | } 180 | 181 | variable "additional_tag_map" { 182 | type = map(string) 183 | default = {} 184 | description = <<-EOT 185 | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. 186 | This is for some rare cases where resources want additional configuration of tags 187 | and therefore take a list of maps with tag key, value, and additional configuration. 188 | EOT 189 | } 190 | 191 | variable "label_order" { 192 | type = list(string) 193 | default = null 194 | description = <<-EOT 195 | The order in which the labels (ID elements) appear in the `id`. 196 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 197 | You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. 198 | EOT 199 | } 200 | 201 | variable "regex_replace_chars" { 202 | type = string 203 | default = null 204 | description = <<-EOT 205 | Terraform regular expression (regex) string. 206 | Characters matching the regex will be removed from the ID elements. 207 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 208 | EOT 209 | } 210 | 211 | variable "id_length_limit" { 212 | type = number 213 | default = null 214 | description = <<-EOT 215 | Limit `id` to this many characters (minimum 6). 216 | Set to `0` for unlimited length. 217 | Set to `null` for keep the existing setting, which defaults to `0`. 218 | Does not affect `id_full`. 219 | EOT 220 | validation { 221 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 222 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 223 | } 224 | } 225 | 226 | variable "label_key_case" { 227 | type = string 228 | default = null 229 | description = <<-EOT 230 | Controls the letter case of the `tags` keys (label names) for tags generated by this module. 231 | Does not affect keys of tags passed in via the `tags` input. 232 | Possible values: `lower`, `title`, `upper`. 233 | Default value: `title`. 234 | EOT 235 | 236 | validation { 237 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 238 | error_message = "Allowed values: `lower`, `title`, `upper`." 239 | } 240 | } 241 | 242 | variable "label_value_case" { 243 | type = string 244 | default = null 245 | description = <<-EOT 246 | Controls the letter case of ID elements (labels) as included in `id`, 247 | set as tag values, and output by this module individually. 248 | Does not affect values of tags passed in via the `tags` input. 249 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 250 | Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. 251 | Default value: `lower`. 252 | EOT 253 | 254 | validation { 255 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 256 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 257 | } 258 | } 259 | 260 | variable "descriptor_formats" { 261 | type = any 262 | default = {} 263 | description = <<-EOT 264 | Describe additional descriptors to be output in the `descriptors` output map. 265 | Map of maps. Keys are names of descriptors. Values are maps of the form 266 | `{ 267 | format = string 268 | labels = list(string) 269 | }` 270 | (Type is `any` so the map values can later be enhanced to provide additional options.) 271 | `format` is a Terraform format string to be passed to the `format()` function. 272 | `labels` is a list of labels, in order, to pass to `format()` function. 273 | Label values will be normalized before being passed to `format()` so they will be 274 | identical to how they appear in `id`. 275 | Default is `{}` (`descriptors` output will be empty). 276 | EOT 277 | } 278 | 279 | #### End of copy of cloudposse/terraform-null-label/variables.tf 280 | -------------------------------------------------------------------------------- /examples/slo/fixtures.us-east-2.tfvars: -------------------------------------------------------------------------------- 1 | namespace = "eg" 2 | 3 | stage = "test" 4 | 5 | name = "datadog-slo" 6 | 7 | slo_paths = ["catalog/*.yaml"] 8 | -------------------------------------------------------------------------------- /examples/slo/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | slo_files = flatten([for p in var.slo_paths : fileset(path.module, p)]) 3 | slo_list = [for f in local.slo_files : yamldecode(file(f))] 4 | slo_map = merge(local.slo_list...) 5 | } 6 | 7 | module "datadog_slo" { 8 | source = "../../modules/slo" 9 | 10 | datadog_slos = local.slo_map 11 | 12 | alert_tags = var.alert_tags 13 | alert_tags_separator = var.alert_tags_separator 14 | 15 | context = module.this.context 16 | } 17 | -------------------------------------------------------------------------------- /examples/slo/outputs.tf: -------------------------------------------------------------------------------- 1 | output "datadog_monitor_slos" { 2 | value = module.datadog_slo.datadog_monitor_slos 3 | description = "Map of created monitor-based SLOs" 4 | } 5 | 6 | output "datadog_monitor_slo_monitors" { 7 | value = module.datadog_slo.datadog_monitor_slo_monitors 8 | description = "Created monitors for the monitor-based SLOs" 9 | } 10 | 11 | output "datadog_metric_slos" { 12 | value = module.datadog_slo.datadog_metric_slos 13 | description = "Map of created metric-based SLOs" 14 | } 15 | 16 | output "datadog_metric_slo_alerts" { 17 | value = module.datadog_slo.datadog_metric_slo_alerts 18 | description = "Map of created metric-based SLO alerts" 19 | } 20 | -------------------------------------------------------------------------------- /examples/slo/providers.tf: -------------------------------------------------------------------------------- 1 | # For the example, we use ENV variables `DD_API_KEY` and `DD_APP_KEY` in place of the `api_key` and `app_key` parameters to the provider 2 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs#argument-reference 3 | provider "datadog" { 4 | validate = false 5 | } 6 | 7 | -------------------------------------------------------------------------------- /examples/slo/variables.tf: -------------------------------------------------------------------------------- 1 | variable "slo_paths" { 2 | type = list(string) 3 | description = "List of paths to Datadog SLO configurations" 4 | } 5 | 6 | variable "alert_tags" { 7 | type = list(string) 8 | description = "List of alert tags to add to all alert messages, e.g. `[\"@opsgenie\"]` or `[\"@devops\", \"@opsgenie\"]`" 9 | default = null 10 | } 11 | 12 | variable "alert_tags_separator" { 13 | type = string 14 | description = "Separator for the alert tags. All strings from the `alert_tags` variable will be joined into one string using the separator and then added to the alert message" 15 | default = "\n" 16 | } 17 | -------------------------------------------------------------------------------- /examples/slo/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | datadog = { 6 | source = "datadog/datadog" 7 | version = ">= 3.0.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/synthetics/catalog/api-schema/api-multistep-test.yaml: -------------------------------------------------------------------------------- 1 | "Multistep API test": 2 | name: Multistep API test 3 | status: paused 4 | type: api 5 | tags: [] 6 | config: 7 | configVariables: [] 8 | steps: 9 | - id: 47q-xx9-2ng 10 | name: Top Level echo server 11 | subtype: http 12 | extractedValues: 13 | - type: http_body 14 | parser: 15 | type: json_path 16 | value: $.headers.host 17 | name: SERVER_HOST 18 | secure: false 19 | allowFailure: true 20 | isCritical: true 21 | retry: 22 | count: 1 23 | interval: 3000 24 | assertions: 25 | - operator: lessThan 26 | type: responseTime 27 | target: 1000 28 | - operator: is 29 | type: statusCode 30 | target: 200 31 | - operator: is 32 | property: content-type 33 | type: header 34 | target: application/json 35 | - type: body 36 | operator: validatesJSONPath 37 | target: 38 | jsonPath: $.headers.cookie 39 | operator: contains 40 | targetValue: DatadogTest=yes 41 | request: 42 | method: GET 43 | url: https://echo.free.beeceptor.com 44 | headers: 45 | cookie: DatadogTest=yes 46 | httpVersion: any 47 | - id: e5i-w66-4ed 48 | name: No file list 49 | subtype: http 50 | extractedValues: [] 51 | allowFailure: false 52 | isCritical: true 53 | retry: 54 | count: 0 55 | interval: 300 56 | assertions: 57 | - operator: lessThan 58 | type: responseTime 59 | target: 1000 60 | - operator: is 61 | type: statusCode 62 | target: 200 63 | - operator: is 64 | property: content-type 65 | type: header 66 | target: application/json 67 | - type: body 68 | operator: doesNotContain 69 | target: tmp 70 | request: 71 | method: GET 72 | url: https://echo.free.beeceptor.com/?echo_file=/ 73 | httpVersion: any 74 | message: "" 75 | options: 76 | tick_every: 86400 77 | min_failure_duration: 0 78 | min_location_failed: 1 79 | retry: 80 | count: 0 81 | interval: 300 82 | monitor_options: 83 | renotify_interval: 0 84 | locations: 85 | - aws:us-west-2 86 | subtype: multi 87 | -------------------------------------------------------------------------------- /examples/synthetics/catalog/api-schema/browser-multistep-test.yaml: -------------------------------------------------------------------------------- 1 | # Example multistep browser test using API schema 2 | 3 | "Example multistep test": 4 | name: Example multistep test 5 | status: paused 6 | type: browser 7 | config: 8 | assertions: [] 9 | configVariables: [] 10 | request: 11 | method: GET 12 | headers: {} 13 | url: https://archive.sweetops.com/ 14 | setCookie: "" 15 | variables: 16 | - example: Oct 17 | name: DELAYED_MONTH 18 | pattern: '{{ date(-3d, MMM) }}' 19 | secure: false 20 | type: text 21 | - example: Oct 22 | name: THIS_MONTH 23 | pattern: '{{ date(0d, MMM) }}' 24 | secure: false 25 | type: text 26 | message: |- 27 | Slack Archive outdated 28 | options: 29 | device_ids: 30 | - edge.laptop_large 31 | - chrome.mobile_small 32 | ignoreServerCertificateError: false 33 | disableCors: false 34 | disableCsp: false 35 | noScreenshot: false 36 | tick_every: 43200 37 | min_location_failed: 1 38 | retry: 39 | count: 0 40 | interval: 300 41 | monitor_options: 42 | renotify_interval: 0 43 | rumSettings: 44 | isEnabled: false 45 | enableProfiling: false 46 | enableSecurityTesting: false 47 | locations: 48 | - aws:us-west-2 49 | steps: 50 | - name: Click on button "leadinModal-close" 51 | params: 52 | element: 53 | multiLocator: 54 | ab: /*[local-name()="html"][1]/*[local-name()="body"][1]/*[local-name()="div"][7]/*[local-name()="div"][2]/*[local-name()="button"][1] 55 | at: /descendant::*[@role="dialog"]/descendant::*[@*[local-name()="aria-label"]="Close"] 56 | cl: /descendant::*[contains(concat(' ', normalize-space(@class), ' '), " leadinModal-close ")] 57 | clt: /descendant::*[contains(concat(' ', normalize-space(@class), ' '), " leadinModal-close ")] 58 | co: '[{"tagName":"BUTTON","text":"close","textType":"aria-label"}]' 59 | ro: //*[contains(concat(' ', normalize-space(@class), ' '), " leadinModal-close ")] 60 | targetOuterHTML: 61 | url: https://archive.sweetops.com/ 62 | timeout: 11 63 | type: click 64 | allowFailure: true 65 | isCritical: false 66 | noScreenshot: true 67 | - name: Click on i "menu" 68 | params: 69 | element: 70 | multiLocator: 71 | ab: /*[local-name()="html"][1]/*[local-name()="body"][1]/*[local-name()="header"][1]/*[local-name()="nav"][1]/*[local-name()="div"][1]/*[local-name()="a"][2]/*[local-name()="i"][1] 72 | at: /descendant::*[@role="banner"]/descendant::*[@href="#"]/*[local-name()="i"][1] 73 | cl: /descendant::*[contains(concat(' ', normalize-space(@class), ' '), " sidenav-trigger ")]/descendant::*[contains(concat(' ', normalize-space(@class), ' '), " material-icons ")] 74 | clt: /descendant::*[contains(concat(' ', normalize-space(@class), ' '), " sidenav-trigger ")]/descendant::*[contains(concat(' ', normalize-space(@class), ' '), " material-icons ")] 75 | co: '[{"text":"menu","textType":"directText"}]' 76 | ro: //*[text()[normalize-space(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ', 'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ')) = "menu"]] 77 | targetOuterHTML: menu 78 | url: https://archive.sweetops.com/ 79 | type: click 80 | allowFailure: false 81 | isCritical: true 82 | noScreenshot: false 83 | - name: Click on link "#terraform" 84 | params: 85 | element: 86 | multiLocator: 87 | ab: /*[local-name()="html"][1]/*[local-name()="body"][1]/*[local-name()="ul"][1]/*[local-name()="li"][19]/*[local-name()="a"][1] 88 | at: "" 89 | cl: /descendant::*[contains(concat(' ', normalize-space(@class), ' '), " sidenav ")]/*[local-name()="li"][19]/descendant::*[contains(concat(' ', normalize-space(@class), ' '), " page-link ")] 90 | clt: /descendant::*[contains(concat(' ', normalize-space(@class), ' '), " sidenav ")]/*[local-name()="li"][19]/descendant::*[contains(concat(' ', normalize-space(@class), ' '), " page-link ")] 91 | co: '[{"text":"#terraform","textType":"directText"},{"relation":"AFTER","tagName":"LI","text":" #refarch ","textType":"innerText"}]' 92 | ro: //*[@href="/terraform/"] 93 | targetOuterHTML: #terraform 94 | url: https://archive.sweetops.com/ 95 | timeout: 0 96 | type: click 97 | allowFailure: false 98 | isCritical: true 99 | noScreenshot: false 100 | - name: Test link "current month" content 101 | params: 102 | check: matchRegex 103 | element: 104 | multiLocator: 105 | ab: /*[local-name()="html"][1]/*[local-name()="body"][1]/*[local-name()="main"][1]/*[local-name()="div"][1]/*[local-name()="div"][1]/*[local-name()="div"][1]/*[local-name()="div"][2]/*[local-name()="div"][1]/*[local-name()="ul"][2]/*[local-name()="li"][10]/*[local-name()="a"][1] 106 | at: "" 107 | cl: /descendant::*[contains(concat(' ', normalize-space(@class), ' '), " post ")]/descendant::*[contains(concat(' ', normalize-space(@class), ' '), " curr-month ")][1]/*[local-name()="a"][1] 108 | clt: /descendant::*[contains(concat(' ', normalize-space(@class), ' '), " post ")]/descendant::*[contains(concat(' ', normalize-space(@class), ' '), " curr-month ")][1]/descendant::*[text()[normalize-space(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ', 'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ')) = "oct"]] 109 | co: '[{"text":"oct","textType":"directText"},{"relation":"BEFORE","tagName":"DIV","text":" #terraform (2023-10) discussions related to terraform or terraform modules","textType":"innerText"}]' 110 | ro: //*[local-name()="div"][1]/*[local-name()="ul"][2]/*[10]/* 111 | targetOuterHTML: Oct 112 | url: https://archive.sweetops.com/terraform/2023/10/ 113 | userLocator: 114 | failTestOnCannotLocate: true 115 | values: 116 | - type: css 117 | value: div.pagination-wrapper:has(+ header) li.curr-month > a 118 | value: '{{ DELAYED_MONTH }}|{{ THIS_MONTH }}' 119 | timeout: 5 120 | type: assertElementContent 121 | allowFailure: false 122 | isCritical: true 123 | noScreenshot: false 124 | -------------------------------------------------------------------------------- /examples/synthetics/catalog/terraform-schema/http.yaml: -------------------------------------------------------------------------------- 1 | simple: 2 | name: "cloudposse.com website test" 3 | message: "Cloud Posse website is not reachable" 4 | type: api 5 | subtype: http 6 | tags: 7 | ManagedBy: Terraform 8 | locations: 9 | - "all" 10 | status: "paused" 11 | request_definition: 12 | url: "https://cloudposse.com" 13 | method: GET 14 | request_headers: {} 15 | request_query: {} 16 | set_cookie: "" 17 | options_list: 18 | tick_every: 1800 19 | assertion: 20 | - type: statusCode 21 | operator: is 22 | target: "200" 23 | 24 | complex: 25 | name: "cloudposse.com complex test" 26 | message: "Cloud Posse website is not reachable (advanced test)" 27 | type: api 28 | subtype: http 29 | tags: 30 | ManagedBy: Terraform 31 | locations: 32 | - "aws:us-west-2" 33 | status: "paused" 34 | request_definition: 35 | url: "https://cloudposse.com" 36 | method: GET 37 | request_headers: 38 | Accept-Charset: "utf-8, iso-8859-1;q=0.5" 39 | Accept: "text/html" 40 | request_query: 41 | hello: world 42 | set_cookie: "" 43 | options_list: 44 | tick_every: 1800 45 | follow_redirects: true 46 | retry: 47 | count: 3 48 | interval: 5 49 | monitor_options: 50 | renotify_interval: 1440 51 | assertion: 52 | - type: statusCode 53 | operator: is 54 | target: "200" 55 | - type: body 56 | operator: validatesJSONPath 57 | targetjsonpath: 58 | operator: is 59 | targetvalue: true 60 | jsonpath: status 61 | -------------------------------------------------------------------------------- /examples/synthetics/catalog/terraform-schema/ssl.yaml: -------------------------------------------------------------------------------- 1 | ssl-test: 2 | name: "SSL Test" 3 | message: "The certificate has expired!" 4 | type: api 5 | subtype: ssl 6 | tags: 7 | ManagedBy: Terraform 8 | locations: 9 | - "aws:us-west-2" 10 | status: "paused" 11 | request_definition: 12 | host: cloudposse.com 13 | port: 443 14 | request_headers: {} 15 | request_query: {} 16 | set_cookie: "" 17 | options_list: 18 | tick_every: 1800 19 | assertion: 20 | - type: certificate 21 | operator: isInMoreThan 22 | target: 30 23 | -------------------------------------------------------------------------------- /examples/synthetics/fixtures.us-east-2.tfvars: -------------------------------------------------------------------------------- 1 | namespace = "eg" 2 | 3 | stage = "test" 4 | 5 | name = "datadog-synthetics" 6 | 7 | alert_tags = ["@opsgenie"] 8 | 9 | alert_tags_separator = "\n" 10 | 11 | synthetic_paths = ["catalog/terraform-schema/*.yaml", "catalog/api-schema/*.yaml"] 12 | -------------------------------------------------------------------------------- /examples/synthetics/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | synthetics_files = flatten([for p in var.synthetic_paths : fileset(path.module, p)]) 3 | synthetics_list = [for f in local.synthetics_files : yamldecode(file(f))] 4 | synthetics_map = merge(local.synthetics_list...) 5 | } 6 | 7 | module "datadog_synthetics" { 8 | source = "../../modules/synthetics" 9 | 10 | datadog_synthetics = local.synthetics_map 11 | alert_tags = var.alert_tags 12 | alert_tags_separator = var.alert_tags_separator 13 | 14 | locations = ["aws:us-east-2"] 15 | context = module.this.context 16 | } 17 | -------------------------------------------------------------------------------- /examples/synthetics/outputs.tf: -------------------------------------------------------------------------------- 1 | output "datadog_synthetics_test_names" { 2 | value = module.datadog_synthetics.datadog_synthetics_test_names 3 | description = "Names of the created Datadog synthetic tests" 4 | } 5 | 6 | output "datadog_synthetics_test_ids" { 7 | value = module.datadog_synthetics.datadog_synthetics_test_ids 8 | description = "IDs of the created Datadog synthetic tests" 9 | } 10 | 11 | output "datadog_synthetics_test_monitor_ids" { 12 | value = module.datadog_synthetics.datadog_synthetics_test_monitor_ids 13 | description = "IDs of the monitors associated with the Datadog synthetics tests" 14 | } 15 | 16 | output "datadog_synthetic_tests" { 17 | value = module.datadog_synthetics.datadog_synthetic_tests 18 | description = "Synthetic tests created in DataDog" 19 | sensitive = true 20 | } 21 | -------------------------------------------------------------------------------- /examples/synthetics/providers.tf: -------------------------------------------------------------------------------- 1 | # For the example, we use ENV variables `DD_API_KEY` and `DD_APP_KEY` in place of the `api_key` and `app_key` parameters to the provider 2 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs#argument-reference 3 | provider "datadog" { 4 | validate = false 5 | } 6 | -------------------------------------------------------------------------------- /examples/synthetics/variables.tf: -------------------------------------------------------------------------------- 1 | variable "alert_tags" { 2 | type = list(string) 3 | description = "List of alert tags to add to all alert messages, e.g. `[\"@opsgenie\"]` or `[\"@devops\", \"@opsgenie\"]`" 4 | } 5 | 6 | variable "alert_tags_separator" { 7 | type = string 8 | description = "Separator for the alert tags. All strings from the `alert_tags` variable will be joined into one string using the separator and then added to the alert message" 9 | } 10 | 11 | variable "synthetic_paths" { 12 | type = list(string) 13 | description = "List of paths to Datadog synthetic test configurations to be deployed" 14 | } 15 | -------------------------------------------------------------------------------- /examples/synthetics/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | datadog = { 6 | source = "datadog/datadog" 7 | version = ">=3.43.1" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | enabled = module.this.enabled 3 | alert_tags = local.enabled && var.alert_tags != null ? format("%s%s", var.alert_tags_separator, join(var.alert_tags_separator, var.alert_tags)) : "" 4 | } 5 | 6 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/monitor 7 | resource "datadog_monitor" "default" { 8 | for_each = local.enabled ? var.datadog_monitors : {} 9 | 10 | name = each.value.name 11 | type = each.value.type 12 | query = each.value.query 13 | message = format("%s%s", each.value.message, local.alert_tags) 14 | escalation_message = lookup(each.value, "escalation_message", null) 15 | require_full_window = lookup(each.value, "require_full_window", null) 16 | notify_no_data = lookup(each.value, "notify_no_data", null) 17 | new_host_delay = lookup(each.value, "new_host_delay", null) 18 | evaluation_delay = lookup(each.value, "evaluation_delay", null) 19 | no_data_timeframe = lookup(each.value, "no_data_timeframe", null) 20 | renotify_interval = lookup(each.value, "renotify_interval", null) 21 | notify_audit = lookup(each.value, "notify_audit", null) 22 | timeout_h = lookup(each.value, "timeout_h", null) 23 | include_tags = lookup(each.value, "include_tags", null) 24 | enable_logs_sample = lookup(each.value, "enable_logs_sample", null) 25 | force_delete = lookup(each.value, "force_delete", null) 26 | 27 | monitor_thresholds { 28 | warning = lookup(each.value.thresholds, "warning", null) 29 | warning_recovery = lookup(each.value.thresholds, "warning_recovery", null) 30 | critical = lookup(each.value.thresholds, "critical", null) 31 | critical_recovery = lookup(each.value.thresholds, "critical_recovery", null) 32 | ok = lookup(each.value.thresholds, "ok", null) 33 | unknown = lookup(each.value.thresholds, "unknown", null) 34 | } 35 | 36 | monitor_threshold_windows { 37 | recovery_window = lookup(each.value.threshold_windows, "recovery_window", null) 38 | trigger_window = lookup(each.value.threshold_windows, "trigger_window", null) 39 | } 40 | 41 | # Assign restricted roles 42 | # Only these roles will have access to the monitor according to their permissions 43 | restricted_roles = try(var.restricted_roles_map[each.key], null) 44 | 45 | # `restricted_roles` conflicts with `locked` 46 | # Use `locked`` only if `restricted_roles` is not provided 47 | locked = try(var.restricted_roles_map[each.key], null) == null ? lookup(each.value, "locked", null) : null 48 | 49 | tags = lookup(each.value, "tags", module.this.tags) 50 | } 51 | -------------------------------------------------------------------------------- /modules/child_organization/main.tf: -------------------------------------------------------------------------------- 1 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/child_organization 2 | # https://github.com/hashicorp/cdktf-provider-datadog/blob/main/API.md 3 | 4 | locals { 5 | enabled = module.this.enabled 6 | } 7 | 8 | resource "datadog_child_organization" "default" { 9 | count = local.enabled ? 1 : 0 10 | name = var.organization_name 11 | } 12 | -------------------------------------------------------------------------------- /modules/child_organization/outputs.tf: -------------------------------------------------------------------------------- 1 | output "id" { 2 | value = try(datadog_child_organization.default[0].id, "") 3 | description = "Organization ID" 4 | } 5 | 6 | output "description" { 7 | value = try(datadog_child_organization.default[0].description, "") 8 | description = "Description of the organization" 9 | } 10 | 11 | output "public_id" { 12 | value = try(datadog_child_organization.default[0].public_id, "") 13 | description = "Public ID of the organization" 14 | } 15 | 16 | output "user" { 17 | value = try(datadog_child_organization.default[0].user[0], {}) 18 | description = "Information about organization users" 19 | } 20 | 21 | output "settings" { 22 | value = try(datadog_child_organization.default[0].settings[0], {}) 23 | description = "Organization settings" 24 | } 25 | 26 | output "api_key" { 27 | value = try(datadog_child_organization.default[0].api_key[0], {}) 28 | description = "Information about Datadog API key" 29 | sensitive = true 30 | } 31 | 32 | output "application_key" { 33 | value = try(datadog_child_organization.default[0].application_key[0], {}) 34 | description = "Datadog application key with its associated metadata" 35 | sensitive = true 36 | } 37 | -------------------------------------------------------------------------------- /modules/child_organization/variables.tf: -------------------------------------------------------------------------------- 1 | variable "organization_name" { 2 | type = string 3 | description = "Datadog organization name" 4 | } 5 | -------------------------------------------------------------------------------- /modules/child_organization/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | datadog = { 6 | source = "datadog/datadog" 7 | version = ">= 3.0.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /modules/monitors/README.md: -------------------------------------------------------------------------------- 1 | ## monitors 2 | 3 | This module creates Datadog [monitors](https://docs.datadoghq.com/api/latest/monitors/). 4 | It accepts all the configuration parameters supported by the Datadog Terraform 5 | resource. 6 | 7 | ## Usage 8 | 9 | The `datadog_monitors` input takes a map of test definitions. You must supply 10 | the keys for the map, but the values can follow either of two schemas described 11 | below. 12 | 13 | There are some optional additions to the monitor definition that are not 14 | part of the Datadog API schema. These are: 15 | - `force_delete` (boolean): If true, when deleting the monitor, the monitor 16 | will be deleted even if it is referenced by other resources. 17 | - `validate` (boolean): If false, the monitor will not be validated during 18 | the Terraform plan phase. 19 | - `enabled` (boolean): If false, the monitor will not be created. This is to 20 | allow you to suppress a monitor created through merging configuration snippets. 21 | 22 | ### Tags 23 | 24 | This module provides special handling for tags. The `tags` attribute of a monitor 25 | definition in the Datadog API and the Terraform resource is a list of strings. 26 | However, the Cloud Posse (as well as AWS) default is to use a map of strings 27 | for tags. This module allows you to provide either a list or a map for the tags. 28 | 29 | If you provide a list, it will be used as is. If you provide a map, it will be 30 | converted to a list of strings in the format `key:value` (or `key` if `value` is 31 | `null`). If you provide no tag settings at all, not even an empty list or map, 32 | the module will use the default tags from the [null-label](https://github.com/cloudposse/terraform-null-label/) module. 33 | 34 | ### API Schema (preferred) 35 | 36 | Datadog provides a REST API for managing monitors. We refer to the responses 37 | to the API requests for monitor definitions (`GET https://api.datadoghq.com/api/v1/monitor/{monitor_id}`) 38 | as the "API Schema" for the tests. This should correspond to the documented 39 | API "Model" for Monitors plus additional information such as creation date, 40 | but if the documentation and the API response differ, we use the API response 41 | as the source of truth. 42 | 43 | You can retrieve Monitor definitions from Datadog via the Datadog Web Console. 44 | Navigate to the monitor you want to retrieve, click the gear icon in the upper 45 | right (representing "settings"), and select "Export". This will display a JSON 46 | representation of the monitor definition. You can then click the "Copy" button 47 | to copy the JSON to the clipboard. 48 | 49 |
Example of JSON for monitors 50 | 51 | Note that many elements of the monitor definition are optional, and the JSON 52 | representation will only include the elements that are set. This is an example 53 | of a monitor, not a comprehensive list of all possible elements. 54 | 55 | ```json 56 | { 57 | "name": "schedule-test", 58 | "type": "event-v2 alert", 59 | "query": "events(\"service:datadog-agent\").rollup(\"cardinality\", \"@evt.id\").current(\"1h\") > 2345", 60 | "message": "No message", 61 | "tags": [ 62 | "test:examplemonitor", 63 | "Terratest" 64 | ], 65 | "options": { 66 | "thresholds": { 67 | "critical": 2345, 68 | "warning": 987 69 | }, 70 | "enable_logs_sample": false, 71 | "notify_audit": false, 72 | "on_missing_data": "default", 73 | "include_tags": false, 74 | "scheduling_options": { 75 | "custom_schedule": { 76 | "recurrences": [ 77 | { 78 | "rrule": "FREQ=DAILY;INTERVAL=1;BYHOUR=17;BYMINUTE=54", 79 | "timezone": "America/Los_Angeles" 80 | } 81 | ] 82 | }, 83 | "evaluation_window": { 84 | "hour_starts": 7 85 | } 86 | } 87 | }, 88 | "priority": 5 89 | } 90 | ``` 91 | 92 |
93 | 94 | You can find other examples in the [examples/complete/monitors-test/](../../examples/complete/monitors-test) directory. 95 | 96 | You can then use the `jsondecode()` function in Terraform to convert the JSON 97 | to a Terraform object, and use that object as the value for the monitor definition. 98 | You can also transform the JSON to HCL other ways, however you prefer. The 99 | relevant point is that this module will accept the monitor definition in this 100 | schema. Any field in the API schema that does not have a counterpart in the 101 | Terraform schema will be ignored. 102 | 103 | #### Special Notes 104 | 105 | The `alert_tags` input is provided for convenience. It is used to add notification 106 | tags to the monitor message. However, it does not check to see if the tags are 107 | already present. If the tags are already present, they will still be added again. 108 | 109 | > [!IMPORTANT] 110 | > 111 | > If you define a monitor via JSON, and then you use `alert_tags` when creating 112 | > it, and then export the JSON representation of the created monitor definition, 113 | > it will not match because of the added tags. 114 | 115 | Note that `restricted_roles_map` provides a convenient way to specify the 116 | `restricted_roles` attribute of the monitor. This is a map of monitors to 117 | sets of Datadog unique role identifiers. If provided, this will override the 118 | `restricted_roles` attribute of the monitor definition. If not provided, the 119 | `restricted_roles` attribute of the monitor definition will be used, if present. 120 | 121 | > [!IMPORTANT] 122 | > 123 | > ### Legacy schema (deprecated) 124 | > 125 | > Historically, and preserved for backward compatibility, you can configure tests 126 | > using the schema used in v1.3.0 and earlier. This schema flattens the monitor 127 | > definition, pulling up the `options` attributes to the top level. 128 | > 129 | > Note that not all fields are supported in this schema, and it is only preserved for 130 | > backward compatibility. We recommend that you use the API schema going forward. 131 | 132 | -------------------------------------------------------------------------------- /modules/monitors/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | enabled = module.this.enabled 3 | 4 | alert_tags = local.enabled && var.alert_tags != null ? format("%s%s", var.alert_tags_separator, join(var.alert_tags_separator, var.alert_tags)) : "" 5 | } 6 | 7 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/monitor 8 | resource "datadog_monitor" "default" { 9 | for_each = { for k, v in local.datadog_monitors : k => v if local.enabled && lookup(v, "enabled", true) } 10 | 11 | name = each.value.name 12 | type = each.value.type 13 | query = each.value.query 14 | message = format("%s%s", each.value.message, local.alert_tags) 15 | priority = try(each.value.priority, null) 16 | 17 | # Assign restricted roles 18 | # Only these roles will have access to the monitor according to their permissions. 19 | # If `restricted_roles` is present var map, it takes precedence over the setting in the monitor definition. 20 | restricted_roles = try(var.restricted_roles_map[each.key], try(each.value.restricted_roles, null)) 21 | 22 | # `restricted_roles` conflicts with `locked`, which is deprecated. 23 | # Use `locked`` only if present and `restricted_roles` is not provided. 24 | locked = try(var.restricted_roles_map[each.key], try(each.value.restricted_roles, null)) == null ? try(each.value.options.locked, null) : null 25 | 26 | # Setting tags is complicated and moved to the bottom of this resource. 27 | 28 | ##### Attributes under `options` in the API model #### 29 | enable_logs_sample = try(each.value.options.enable_logs_sample, null) 30 | # enable_samples is read-only in provider v3.36.0 31 | # enable_samples = try(each.value.options.enable_samples, null) 32 | escalation_message = try(each.value.options.escalation_message, null) 33 | evaluation_delay = try(each.value.options.evaluation_delay, null) 34 | group_retention_duration = try(each.value.options.group_retention_duration, null) 35 | groupby_simple_monitor = try(each.value.options.groupby_simple_monitor, null) 36 | include_tags = try(each.value.options.include_tags, null) 37 | # min_failure_duration is not supported in provider v3.36.0 38 | # min_failure_duration = try(each.value.options.min_failure_duration, null) 39 | # min_location_failed is not supported in provider v3.36.0 40 | # min_location_failed = try(each.value.options.min_location_failed, null) 41 | new_group_delay = try(each.value.options.new_group_delay, null) 42 | no_data_timeframe = try(each.value.options.no_data_timeframe, null) 43 | notification_preset_name = try(each.value.options.notification_preset_name, null) 44 | notify_audit = try(each.value.options.notify_audit, null) 45 | notify_by = try(each.value.options.notify_by, null) 46 | notify_no_data = try(each.value.options.notify_no_data, null) 47 | on_missing_data = try(each.value.options.on_missing_data, null) 48 | renotify_interval = try(each.value.options.renotify_interval, null) 49 | renotify_occurrences = try(each.value.options.renotify_occurrences, null) 50 | renotify_statuses = try(each.value.options.renotify_statuses, null) 51 | require_full_window = try(each.value.options.require_full_window, null) 52 | timeout_h = try(each.value.options.timeout_h, null) 53 | 54 | # DEPRECATED: `new_host_delay` is deprecated, but still needed when set to zero to override default. 55 | # So it is ignored unless set to zero. 56 | new_host_delay = try(each.value.options.new_host_delay, null) == 0 ? 0 : null 57 | 58 | dynamic "monitor_thresholds" { 59 | for_each = try([each.value.options.thresholds], []) 60 | iterator = thresholds 61 | content { 62 | critical = lookup(thresholds.value, "critical", null) 63 | critical_recovery = lookup(thresholds.value, "critical_recovery", null) 64 | ok = lookup(thresholds.value, "ok", null) 65 | unknown = lookup(thresholds.value, "unknown", null) 66 | warning = lookup(thresholds.value, "warning", null) 67 | warning_recovery = lookup(thresholds.value, "warning_recovery", null) 68 | } 69 | } 70 | 71 | dynamic "monitor_threshold_windows" { 72 | for_each = try([each.value.options.threshold_windows], []) 73 | iterator = threshold_windows 74 | content { 75 | recovery_window = lookup(threshold_windows.value, "recovery_window", null) 76 | trigger_window = lookup(threshold_windows.value, "trigger_window", null) 77 | } 78 | } 79 | 80 | dynamic "scheduling_options" { 81 | # Legacy support. In the API, `scheduling_options` is a map of objects, but legacy it was a list of objects. 82 | for_each = try(tolist(each.value.options.scheduling_options), try([each.value.options.scheduling_options], [])) 83 | content { 84 | dynamic "custom_schedule" { 85 | for_each = try([scheduling_options.value.custom_schedule], []) 86 | content { 87 | dynamic "recurrence" { 88 | for_each = try(custom_schedule.value.recurrences, []) 89 | content { 90 | rrule = recurrence.value.rrule 91 | timezone = recurrence.value.timezone 92 | start = try(recurrence.value.start, null) 93 | } 94 | } # recurrence 95 | } 96 | } # custom_schedule 97 | 98 | dynamic "evaluation_window" { 99 | # Legacy support. In the API, `evaluation_window` is a map of objects, but legacy it was a list of objects. 100 | for_each = try(tolist(scheduling_options.value.evaluation_window), try([scheduling_options.value.evaluation_window], [])) 101 | content { 102 | day_starts = lookup(evaluation_window.value, "day_starts", null) 103 | hour_starts = lookup(evaluation_window.value, "hour_starts", null) 104 | month_starts = lookup(evaluation_window.value, "month_starts", null) 105 | } 106 | } # evaluation_window 107 | } 108 | } # scheduling_options 109 | 110 | dynamic "variables" { 111 | # There is only one `variables` block in the Terraform model. 112 | for_each = try(length(tolist(each.value.options.variables)), 0) > 0 ? ["variables"] : [] 113 | content { 114 | # The JSON `variables` attribute is a list of objects, 115 | # but in Terraform the list is expressed as repeated `event_query` blocks under `variables`. 116 | dynamic "event_query" { 117 | for_each = each.value.options.variables 118 | iterator = query 119 | content { 120 | compute { 121 | aggregation = query.value.compute.aggregation 122 | interval = lookup(query.value.compute, "interval", null) 123 | metric = lookup(query.value.compute, "metric", null) 124 | } 125 | 126 | data_source = query.value.data_source 127 | 128 | dynamic "group_by" { 129 | for_each = try(query.value.group_by, []) 130 | content { 131 | facet = group_by.value.facet 132 | limit = try(group_by.value.limit, null) 133 | 134 | dynamic "sort" { 135 | for_each = try([group_by.value.sort], []) 136 | content { 137 | aggregation = sort.value.aggregation 138 | metric = try(sort.value.metric, null) 139 | order = try(sort.value.order, null) 140 | } 141 | } # sort 142 | } 143 | } # group_by 144 | 145 | indexes = try(query.value.indexes, null) 146 | name = query.value.name 147 | 148 | search { 149 | # Cannot be null or empty string, use "*" which is equivalent to "" and matches everything 150 | # See https://github.com/DataDog/terraform-provider-datadog/pull/2275 151 | query = coalesce(try(query.value.search.query, null), "*") 152 | } 153 | } 154 | } # event_query 155 | } 156 | } # variables 157 | 158 | ### End of `options` attributes ### 159 | 160 | # `force_delete` and `validate` are not part of the Datadog API monitor model. 161 | # They are features provided by the Datadog Terraform provider to help manage the monitors. 162 | force_delete = lookup(each.value, "force_delete", null) 163 | # If validate is set to false, Terraform will skip the validation call done during plan. 164 | validate = lookup(each.value, "validate", null) 165 | 166 | ############################ 167 | # TAGS 168 | ############################ 169 | # Datadog's tags are a list of strings, but Terraform's tags are a map of strings. 170 | # Convert Terraform tags map to Datadog tags list 171 | # If a key is supplied with a value: 172 | # tags: 173 | # key: value 174 | # it will render the tag as "key:value" 175 | # If a key is supplied without a value (null) 176 | # tags: 177 | # key: null 178 | # it will render the tag as simply "key" 179 | # However, the API schema takes a list of tags 180 | # tags: 181 | # - key:value 182 | # so if the tags are provided as a list, we just pass that through. 183 | # If the monitor has no setting for tags, we add the default tags, if enabled. 184 | tags = try(tolist(each.value.tags), null) != null ? try(tolist(each.value.tags), null) : [ 185 | # If the user has supplied a map of tags, use it. Otherwise, use the default tags if enabled. 186 | for tagk, tagv in try(tomap(each.value.tags), var.default_tags_enabled ? module.this.tags : {}) : (tagv != null ? format("%s:%s", tagk, tagv) : tagk) 187 | ] 188 | } 189 | -------------------------------------------------------------------------------- /modules/monitors/normalize-legacy.tf: -------------------------------------------------------------------------------- 1 | # Legacy support 2 | # 3 | # The preferred way to use this module is to supply configuration matching the 4 | # model of the Datadog API. However, the module also supports a legacy mode 5 | # where the configuration that is in the model under `options` can be provided 6 | # at the top level of the map. This mode is deprecated and will not be enhanced 7 | # to include new features, e.g. `group_retention_duration` or 8 | # `scheduling_options.custom_schedule`. 9 | # 10 | 11 | locals { 12 | # Step one, list all the keys that are in the legacy configuration 13 | # that should be under options. Only the keys that were in the legacy 14 | # module are listed here. New keys should not be added to this list. 15 | # Instead, users should convert their configuration to the new model. 16 | legacy_attributes = [ 17 | "enable_logs_sample", 18 | "escalation_message", 19 | "evaluation_delay", 20 | "groupby_simple_monitor", 21 | "include_tags", 22 | "locked", 23 | "new_group_delay", 24 | "new_host_delay", 25 | "no_data_timeframe", 26 | "notify_audit", 27 | "notify_no_data", 28 | "priority", 29 | "renotify_interval", 30 | "renotify_occurrences", 31 | "renotify_statuses", 32 | "require_full_window", 33 | "scheduling_options", 34 | "thresholds", 35 | "threshold_windows", 36 | "timeout_h", 37 | "validate" 38 | ] 39 | 40 | # Step two, create a map containing the legacy options that should be moved. 41 | # We do not need to remove the keys from the original map, as they will simply be ignored. 42 | legacy_option_attributes_maps = { for k, v in var.datadog_monitors : k => { for attr in local.legacy_attributes : attr => v[attr] if try(v[attr], null) != null } } 43 | 44 | # Step three, add the legacy options to the maps under the `options` key, 45 | # but only if the `options` key is not already present. 46 | datadog_monitors = { for k, v in var.datadog_monitors : k => merge( 47 | { options = local.legacy_option_attributes_maps[k] }, v) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /modules/monitors/outputs.tf: -------------------------------------------------------------------------------- 1 | output "datadog_monitor_names" { 2 | value = values(datadog_monitor.default)[*].name 3 | description = "Names of the created Datadog monitors" 4 | } 5 | 6 | output "datadog_monitor_ids" { 7 | value = values(datadog_monitor.default)[*].id 8 | description = "IDs of the created Datadog monitors" 9 | } 10 | 11 | output "datadog_monitors" { 12 | value = datadog_monitor.default 13 | description = "Datadog monitor outputs" 14 | } 15 | -------------------------------------------------------------------------------- /modules/monitors/variables.tf: -------------------------------------------------------------------------------- 1 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/monitor 2 | variable "datadog_monitors" { 3 | # We use `any` type here because the `datadog_monitors` map can have multiple configurations 4 | # and it is too complex to model all possible configurations in the type system. 5 | type = any 6 | description = <<-EOT 7 | Map of Datadog monitor configurations. See the README for details on the expected structure. 8 | Keys will be used as monitor names if not provided in the `name` attribute. 9 | Attributes match [Datadog "Create a monitor" API](https://docs.datadoghq.com/api/v1/monitors/#create-a-monitor). 10 | For backward compatibility, attributes under `options` in the API schema that were 11 | previously allowed at the top level remain available at the top level if no `options` are provided. 12 | Because `new_host_delay` is deprecated, it is ignored unless set to zero. 13 | EOT 14 | } 15 | 16 | variable "alert_tags" { 17 | type = list(string) 18 | description = "List of alert tags to add to all alert messages, e.g. `[\"@opsgenie\"]` or `[\"@devops\", \"@opsgenie\"]`" 19 | default = null 20 | } 21 | 22 | variable "alert_tags_separator" { 23 | type = string 24 | description = "Separator for the alert tags. All strings from the `alert_tags` variable will be joined into one string using the separator and then added to the alert message" 25 | default = "\n" 26 | } 27 | 28 | variable "restricted_roles_map" { 29 | type = map(set(string)) 30 | description = <<-EOT 31 | Map of `datadog_monitors` map keys to sets of Datadog unique role IDs 32 | to restrict access to each monitor. If provided, it will override 33 | the `restricted_roles` attribute in the monitor definition. 34 | EOT 35 | default = {} 36 | } 37 | 38 | variable "default_tags_enabled" { 39 | type = bool 40 | description = <<-EOT 41 | If true, monitors without `tags` in their definitions will have tags 42 | from `null-label` added to them. Note that even an empty `list` or `map` of tags in 43 | the monitor definition will keep the default tags from being added. 44 | EOT 45 | default = true 46 | } 47 | -------------------------------------------------------------------------------- /modules/monitors/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | datadog = { 6 | source = "datadog/datadog" 7 | # >= 3.36.0 is required, so that bugfix for "start" is included. 8 | # See https://github.com/DataDog/terraform-provider-datadog/issues/2255 9 | version = ">= 3.36.0" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /modules/organization_settings/main.tf: -------------------------------------------------------------------------------- 1 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/child_organization 2 | # https://github.com/hashicorp/cdktf-provider-datadog/blob/main/API.md 3 | 4 | locals { 5 | enabled = module.this.enabled 6 | } 7 | 8 | resource "datadog_organization_settings" "default" { 9 | count = local.enabled ? 1 : 0 10 | 11 | name = var.organization_name 12 | 13 | settings { 14 | saml { 15 | # Free and Trial organizations cannot enable SAML 16 | enabled = var.saml_enabled 17 | } 18 | 19 | saml_autocreate_users_domains { 20 | domains = var.saml_autocreate_users_domains 21 | enabled = var.saml_autocreate_users_enabled 22 | } 23 | 24 | saml_strict_mode { 25 | enabled = var.saml_strict_mode_enabled 26 | } 27 | 28 | saml_idp_initiated_login { 29 | enabled = var.saml_idp_initiated_login_enabled 30 | } 31 | 32 | private_widget_share = var.private_widget_share 33 | 34 | saml_autocreate_access_role = var.saml_autocreate_access_role 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /modules/organization_settings/outputs.tf: -------------------------------------------------------------------------------- 1 | output "id" { 2 | value = try(datadog_organization_settings.default[0].id, "") 3 | description = "Organization settings ID" 4 | } 5 | 6 | output "description" { 7 | value = try(datadog_organization_settings.default[0].description, "") 8 | description = "Description of the organization" 9 | } 10 | 11 | output "public_id" { 12 | value = try(datadog_organization_settings.default[0].public_id, "") 13 | description = "Public ID of the organization" 14 | } 15 | 16 | output "settings" { 17 | value = try(datadog_organization_settings.default[0].settings, {}) 18 | description = "Organization settings" 19 | } 20 | -------------------------------------------------------------------------------- /modules/organization_settings/variables.tf: -------------------------------------------------------------------------------- 1 | # https://docs.datadoghq.com/account_management/org_settings/#overview 2 | 3 | variable "organization_name" { 4 | type = string 5 | description = "Datadog organization name" 6 | } 7 | 8 | variable "saml_enabled" { 9 | type = bool 10 | default = false 11 | description = "Whether or not SAML is enabled for the child organization. Note that Free and Trial organizations cannot enable SAML" 12 | } 13 | 14 | variable "saml_autocreate_users_domains" { 15 | type = list(string) 16 | default = [] 17 | description = "List of domains where the SAML automated user creation is enabled" 18 | } 19 | 20 | variable "saml_autocreate_users_enabled" { 21 | type = bool 22 | default = false 23 | description = "Whether or not the automated user creation based on SAML domain is enabled" 24 | } 25 | 26 | variable "saml_idp_initiated_login_enabled" { 27 | type = bool 28 | default = true 29 | description = "Whether or not IdP initiated login is enabled for the Datadog organization" 30 | } 31 | 32 | variable "saml_strict_mode_enabled" { 33 | type = bool 34 | default = false 35 | description = "Whether or not the SAML strict mode is enabled. If true, all users must log in with SAML" 36 | } 37 | 38 | variable "private_widget_share" { 39 | type = bool 40 | default = false 41 | description = "Whether or not the organization users can share widgets outside of Datadog" 42 | } 43 | 44 | variable "saml_autocreate_access_role" { 45 | type = string 46 | default = "ro" 47 | description = "The access role of an autocreated user. Options are `st` (standard user), `adm` (admin user), or `ro` (read-only user). Allowed enum values: `st`, `adm` , `ro`, `ERROR`" 48 | } 49 | -------------------------------------------------------------------------------- /modules/organization_settings/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | datadog = { 6 | source = "datadog/datadog" 7 | version = ">= 3.0.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /modules/permissions/datadog-permissions.md: -------------------------------------------------------------------------------- 1 | # Datadog Permissions 2 | 3 | ## Available Permissions 4 | 5 | ```hcl 6 | permissions = { 7 | "admin" = "984a2bd4-d3b4-11e8-a1ff-a7f660d43029" 8 | "api_keys_read" = "372896c4-f923-11ea-adbc-4fecd107156d" 9 | "api_keys_write" = "3e4d4d28-f923-11ea-adbc-e3565938c12e" 10 | "apm_apdex_manage_write" = "53950c54-2dce-11eb-84c0-a79ae108f6f8" 11 | "apm_primary_operation_write" = "61765026-2dce-11eb-84c0-833e230d1b8f" 12 | "apm_retention_filter_read" = "43fa188e-2dce-11eb-84c0-835ad1fd6287" 13 | "apm_retention_filter_write" = "465cfe66-2dce-11eb-84c0-6baa888239fa" 14 | "apm_service_ingest_read" = "4916eebe-2dce-11eb-84c0-271cb2c672e8" 15 | "apm_service_ingest_write" = "4e3f02b4-2dce-11eb-84c0-2fca946a6efc" 16 | "apm_tag_management_write" = "5cbe5f9c-2dce-11eb-84c0-872d3e9f1076" 17 | "billing_edit" = "46a301e0-ec5c-11ea-aa9f-6ba6cc675d8c" 18 | "billing_read" = "46a301df-ec5c-11ea-aa9f-970a9ae645e5" 19 | "dashboards_public_share" = "d90f6832-d3d8-11e9-a77a-bf8a2607f864" 20 | "dashboards_write" = "d90f6831-d3d8-11e9-a77a-4fd230ddbc6a" 21 | "data_scanner_read" = "bf0dcf7c-90af-11eb-9b82-da7ad0900002" 22 | "data_scanner_write" = "bf0dcf7d-90af-11eb-9b82-da7ad0900002" 23 | "incident_settings_read" = "12efc20f-d36c-11eb-a9b8-da7ad0900002" 24 | "incident_settings_write" = "12efc210-d36c-11eb-a9b8-da7ad0900002" 25 | "incident_write" = "12efc211-d36c-11eb-a9b8-da7ad0900002" 26 | "integrations_api" = "fcac2ad8-2843-11eb-8315-0fe47949d625" 27 | "logs_generate_metrics" = "979df720-aed7-11e9-99c6-a7eb8373165a" 28 | "logs_live_tail" = "6f66600e-dd12-11e8-9e55-7f30fbb45e73" 29 | "logs_modify_indexes" = "62cc036c-dd12-11e8-9e54-db9995643092" 30 | "logs_public_config_api" = "1a92ede2-6cb2-11e9-99c6-2b3a4a0cdf0a" 31 | "logs_read_archives" = "b382b982-8535-11ea-93de-2bf1bdf20798" 32 | "logs_read_data" = "1af86ce4-7823-11ea-93dc-d7cad1b1c6cb" 33 | "logs_read_index_data" = "5e605652-dd12-11e8-9e53-375565b8970e" 34 | "logs_write_archives" = "87b00304-dd12-11e8-9e59-cbeb5f71f72f" 35 | "logs_write_exclusion_filters" = "7d7c98ac-dd12-11e8-9e56-93700598622d" 36 | "logs_write_facets" = "6ba32d22-0e1a-11eb-ba44-bf9a5aafaa39" 37 | "logs_write_historical_view" = "07c3c146-f7f8-11ea-acf6-0bd62b9ae60e" 38 | "logs_write_pipelines" = "811ac4ca-dd12-11e8-9e57-676a7f0beef9" 39 | "logs_write_processors" = "84aa3ae4-dd12-11e8-9e58-a373a514ccd0" 40 | "metric_tags_write" = "46a301e4-ec5c-11ea-aa9f-87282b3a50cc" 41 | "monitors_downtime" = "4d87d5f8-d8b1-11e9-a77a-eb9c8350d04f" 42 | "monitors_write" = "48ef71ea-d8b1-11e9-a77a-93f408470ad0" 43 | "org_app_keys_read" = "46a301db-ec5c-11ea-aa9f-2fe72193d60e" 44 | "org_app_keys_write" = "46a301dc-ec5c-11ea-aa9f-13b33f8f46ea" 45 | "rum_apps_write" = "8106300a-54f7-11eb-8cbc-7781a434a67b" 46 | "security_monitoring_filters_read" = "98b984f4-b16d-11eb-a2c6-da7ad0900002" 47 | "security_monitoring_filters_write" = "98b984f5-b16d-11eb-a2c6-da7ad0900002" 48 | "security_monitoring_rules_read" = "7314eb20-aa58-11ea-95e2-6fb6e4a451d5" 49 | "security_monitoring_rules_write" = "7b516476-aa58-11ea-95e2-93718cd56369" 50 | "security_monitoring_signals_read" = "80de1ec0-aa58-11ea-95e2-aff381626d5d" 51 | "standard" = "984d2f00-d3b4-11e8-a200-bb47109e9987" 52 | "synthetics_default_settings_read" = "5c6b88e2-f923-11ea-adbc-abf57d079420" 53 | "synthetics_default_settings_write" = "642eebe6-f923-11ea-adbc-eb617674ea04" 54 | "synthetics_global_variable_read" = "4628ca54-f923-11ea-adbc-4b2b7f88c5e9" 55 | "synthetics_global_variable_write" = "4ada6e36-f923-11ea-adbc-0788e5c5e3cf" 56 | "synthetics_private_location_read" = "46a301dd-ec5c-11ea-aa9f-97edfb345bc9" 57 | "synthetics_private_location_write" = "46a301de-ec5c-11ea-aa9f-a73252c24806" 58 | "synthetics_read" = "5025ee24-f923-11ea-adbc-576ea241df8d" 59 | "synthetics_write" = "55f4b5ec-f923-11ea-adbc-1bfa2334a755" 60 | "usage_edit" = "46a301e2-ec5c-11ea-aa9f-1f511b7305fd" 61 | "usage_read" = "46a301e1-ec5c-11ea-aa9f-afa39f6f3e36" 62 | "user_access_invite" = "9ac1d8cc-e707-11ea-aa2d-73d37e989a9d" 63 | "user_access_manage" = "9de604d8-e707-11ea-aa2d-93f1a783b3a3" 64 | "user_app_keys" = "46a301da-ec5c-11ea-aa9f-73bedeab67ee" 65 | } 66 | ``` 67 | -------------------------------------------------------------------------------- /modules/permissions/main.tf: -------------------------------------------------------------------------------- 1 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/role 2 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs/data-sources/permissions 3 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/monitor 4 | # https://docs.datadoghq.com/api/latest/monitors/#create-a-monitor 5 | # https://docs.datadoghq.com/account_management/rbac/?tab=datadogapplication 6 | # https://docs.datadoghq.com/account_management/rbac/permissions/ 7 | # https://docs.datadoghq.com/api/latest/monitors/ 8 | # https://github.com/hashicorp/cdktf-provider-datadog/blob/main/API.md 9 | 10 | # Get all available Datadog permissions 11 | data "datadog_permissions" "available_permissions" { 12 | count = local.enabled ? 1 : 0 13 | } 14 | 15 | locals { 16 | enabled = module.this.enabled 17 | permissions = local.enabled ? data.datadog_permissions.available_permissions[0].permissions : null 18 | } 19 | -------------------------------------------------------------------------------- /modules/permissions/outputs.tf: -------------------------------------------------------------------------------- 1 | output "permissions" { 2 | value = local.permissions 3 | description = "Available Datadog permissions. It's a map of permission names to permissions IDs" 4 | } 5 | -------------------------------------------------------------------------------- /modules/permissions/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | datadog = { 6 | source = "datadog/datadog" 7 | version = ">= 3.0.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /modules/roles/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf 12 | # 13 | # Modules should access the whole context as `module.this.context` 14 | # to get the input variables with nulls for defaults, 15 | # for example `context = module.this.context`, 16 | # and access individual variables as `module.this.`, 17 | # with final values filled in. 18 | # 19 | # For example, when using defaults, `module.this.context.delimiter` 20 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 21 | # 22 | 23 | module "this" { 24 | source = "cloudposse/label/null" 25 | version = "0.25.0" # requires Terraform >= 0.13.0 26 | 27 | enabled = var.enabled 28 | namespace = var.namespace 29 | tenant = var.tenant 30 | environment = var.environment 31 | stage = var.stage 32 | name = var.name 33 | delimiter = var.delimiter 34 | attributes = var.attributes 35 | tags = var.tags 36 | additional_tag_map = var.additional_tag_map 37 | label_order = var.label_order 38 | regex_replace_chars = var.regex_replace_chars 39 | id_length_limit = var.id_length_limit 40 | label_key_case = var.label_key_case 41 | label_value_case = var.label_value_case 42 | descriptor_formats = var.descriptor_formats 43 | labels_as_tags = var.labels_as_tags 44 | 45 | context = var.context 46 | } 47 | 48 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 49 | 50 | variable "context" { 51 | type = any 52 | default = { 53 | enabled = true 54 | namespace = null 55 | tenant = null 56 | environment = null 57 | stage = null 58 | name = null 59 | delimiter = null 60 | attributes = [] 61 | tags = {} 62 | additional_tag_map = {} 63 | regex_replace_chars = null 64 | label_order = [] 65 | id_length_limit = null 66 | label_key_case = null 67 | label_value_case = null 68 | descriptor_formats = {} 69 | # Note: we have to use [] instead of null for unset lists due to 70 | # https://github.com/hashicorp/terraform/issues/28137 71 | # which was not fixed until Terraform 1.0.0, 72 | # but we want the default to be all the labels in `label_order` 73 | # and we want users to be able to prevent all tag generation 74 | # by setting `labels_as_tags` to `[]`, so we need 75 | # a different sentinel to indicate "default" 76 | labels_as_tags = ["unset"] 77 | } 78 | description = <<-EOT 79 | Single object for setting entire context at once. 80 | See description of individual variables for details. 81 | Leave string and numeric variables as `null` to use default value. 82 | Individual variable settings (non-null) override settings in context object, 83 | except for attributes, tags, and additional_tag_map, which are merged. 84 | EOT 85 | 86 | validation { 87 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 88 | error_message = "Allowed values: `lower`, `title`, `upper`." 89 | } 90 | 91 | validation { 92 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 93 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 94 | } 95 | } 96 | 97 | variable "enabled" { 98 | type = bool 99 | default = null 100 | description = "Set to false to prevent the module from creating any resources" 101 | } 102 | 103 | variable "namespace" { 104 | type = string 105 | default = null 106 | description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" 107 | } 108 | 109 | variable "tenant" { 110 | type = string 111 | default = null 112 | description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" 113 | } 114 | 115 | variable "environment" { 116 | type = string 117 | default = null 118 | description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" 119 | } 120 | 121 | variable "stage" { 122 | type = string 123 | default = null 124 | description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" 125 | } 126 | 127 | variable "name" { 128 | type = string 129 | default = null 130 | description = <<-EOT 131 | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. 132 | This is the only ID element not also included as a `tag`. 133 | The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. 134 | EOT 135 | } 136 | 137 | variable "delimiter" { 138 | type = string 139 | default = null 140 | description = <<-EOT 141 | Delimiter to be used between ID elements. 142 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 143 | EOT 144 | } 145 | 146 | variable "attributes" { 147 | type = list(string) 148 | default = [] 149 | description = <<-EOT 150 | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, 151 | in the order they appear in the list. New attributes are appended to the 152 | end of the list. The elements of the list are joined by the `delimiter` 153 | and treated as a single ID element. 154 | EOT 155 | } 156 | 157 | variable "labels_as_tags" { 158 | type = set(string) 159 | default = ["default"] 160 | description = <<-EOT 161 | Set of labels (ID elements) to include as tags in the `tags` output. 162 | Default is to include all labels. 163 | Tags with empty values will not be included in the `tags` output. 164 | Set to `[]` to suppress all generated tags. 165 | **Notes:** 166 | The value of the `name` tag, if included, will be the `id`, not the `name`. 167 | Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be 168 | changed in later chained modules. Attempts to change it will be silently ignored. 169 | EOT 170 | } 171 | 172 | variable "tags" { 173 | type = map(string) 174 | default = {} 175 | description = <<-EOT 176 | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). 177 | Neither the tag keys nor the tag values will be modified by this module. 178 | EOT 179 | } 180 | 181 | variable "additional_tag_map" { 182 | type = map(string) 183 | default = {} 184 | description = <<-EOT 185 | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. 186 | This is for some rare cases where resources want additional configuration of tags 187 | and therefore take a list of maps with tag key, value, and additional configuration. 188 | EOT 189 | } 190 | 191 | variable "label_order" { 192 | type = list(string) 193 | default = null 194 | description = <<-EOT 195 | The order in which the labels (ID elements) appear in the `id`. 196 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 197 | You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. 198 | EOT 199 | } 200 | 201 | variable "regex_replace_chars" { 202 | type = string 203 | default = null 204 | description = <<-EOT 205 | Terraform regular expression (regex) string. 206 | Characters matching the regex will be removed from the ID elements. 207 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 208 | EOT 209 | } 210 | 211 | variable "id_length_limit" { 212 | type = number 213 | default = null 214 | description = <<-EOT 215 | Limit `id` to this many characters (minimum 6). 216 | Set to `0` for unlimited length. 217 | Set to `null` for keep the existing setting, which defaults to `0`. 218 | Does not affect `id_full`. 219 | EOT 220 | validation { 221 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 222 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 223 | } 224 | } 225 | 226 | variable "label_key_case" { 227 | type = string 228 | default = null 229 | description = <<-EOT 230 | Controls the letter case of the `tags` keys (label names) for tags generated by this module. 231 | Does not affect keys of tags passed in via the `tags` input. 232 | Possible values: `lower`, `title`, `upper`. 233 | Default value: `title`. 234 | EOT 235 | 236 | validation { 237 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 238 | error_message = "Allowed values: `lower`, `title`, `upper`." 239 | } 240 | } 241 | 242 | variable "label_value_case" { 243 | type = string 244 | default = null 245 | description = <<-EOT 246 | Controls the letter case of ID elements (labels) as included in `id`, 247 | set as tag values, and output by this module individually. 248 | Does not affect values of tags passed in via the `tags` input. 249 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 250 | Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. 251 | Default value: `lower`. 252 | EOT 253 | 254 | validation { 255 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 256 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 257 | } 258 | } 259 | 260 | variable "descriptor_formats" { 261 | type = any 262 | default = {} 263 | description = <<-EOT 264 | Describe additional descriptors to be output in the `descriptors` output map. 265 | Map of maps. Keys are names of descriptors. Values are maps of the form 266 | `{ 267 | format = string 268 | labels = list(string) 269 | }` 270 | (Type is `any` so the map values can later be enhanced to provide additional options.) 271 | `format` is a Terraform format string to be passed to the `format()` function. 272 | `labels` is a list of labels, in order, to pass to `format()` function. 273 | Label values will be normalized before being passed to `format()` so they will be 274 | identical to how they appear in `id`. 275 | Default is `{}` (`descriptors` output will be empty). 276 | EOT 277 | } 278 | 279 | #### End of copy of cloudposse/terraform-null-label/variables.tf 280 | -------------------------------------------------------------------------------- /modules/roles/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | enabled = module.this.enabled 3 | permissions = module.permissions.permissions 4 | } 5 | 6 | module "permissions" { 7 | source = "../permissions" 8 | context = module.this.context 9 | } 10 | 11 | resource "datadog_role" "default" { 12 | for_each = local.enabled ? var.datadog_roles : {} 13 | 14 | name = each.value.name 15 | 16 | dynamic "permission" { 17 | for_each = each.value.permissions 18 | content { 19 | id = local.permissions != null ? lookup(local.permissions, permission.value, null) : null 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /modules/roles/outputs.tf: -------------------------------------------------------------------------------- 1 | output "datadog_role_names" { 2 | value = values(datadog_role.default)[*].name 3 | description = "Names of the created Datadog roles" 4 | } 5 | 6 | output "datadog_role_ids" { 7 | value = values(datadog_role.default)[*].id 8 | description = "IDs of the created Datadog roles" 9 | } 10 | 11 | output "datadog_roles" { 12 | value = datadog_role.default 13 | description = "Created Datadog roles" 14 | } 15 | -------------------------------------------------------------------------------- /modules/roles/variables.tf: -------------------------------------------------------------------------------- 1 | # https://docs.datadoghq.com/account_management/rbac/?tab=datadogapplication 2 | variable "datadog_roles" { 3 | type = map(object({ 4 | name = string 5 | permissions = set(string) 6 | })) 7 | description = "Map of Datadog role configurations. See catalog for examples" 8 | } 9 | -------------------------------------------------------------------------------- /modules/roles/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | datadog = { 6 | source = "datadog/datadog" 7 | version = ">= 3.0.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /modules/slo/README.md: -------------------------------------------------------------------------------- 1 | # Datadog SLO 2 | 3 | This module is responsible for creating Datadog [Service Level Objectives](https://docs.datadoghq.com/monitors/service_level_objectives/) and their related monitors and alerts. 4 | 5 | The module can create metric-based SLOs (and the corresponding alerts) and monitor-based SLOs (and the corresponding monitors). 6 | 7 | ## Alerts 8 | 9 | Datadog alerts for SLOs are terraformed through the monitor object. 10 | 11 | An SLO can have many thresholds set, but a monitor can only have one. In order to get around this, the module creates Datadog monitors for each threshold within an SLO. 12 | 13 | ## Usage 14 | 15 | Example of metric-based SLO: 16 | 17 | ```yaml 18 | metric-slo: 19 | name: "(SLO) Synthetic Checks" 20 | type: metric 21 | query: 22 | numerator: sum:synthetics.test_runs{status:success}.as_count() 23 | denominator: sum:synthetics.test_runs{*}.as_count() 24 | description: | 25 | Number of Successful Synthetic Checks. 26 | message: | 27 | ({stage} {region}) {instance_id} failed a SLO check 28 | force_delete: true 29 | validate: true 30 | thresholds: 31 | - target: "99.5" 32 | timeframe: "7d" 33 | warning: "99.9" 34 | - target: "99" 35 | timeframe: "30d" 36 | warning: "99.5" 37 | tags: 38 | ManagedBy: terraform 39 | test: true 40 | api_version: null 41 | ``` 42 | 43 | Example of monitor-based SLO: 44 | 45 | ```yaml 46 | monitor-slo: 47 | name: "(SLO) EC2 Availability" 48 | type: monitor 49 | description: | 50 | Number of EC2 failed status checks. 51 | message: | 52 | ({stage} {region}) {instance_id} failed a SLO check 53 | force_delete: true 54 | validate: true 55 | thresholds: 56 | - target: "99.5" 57 | timeframe: "7d" 58 | warning: "99.9" 59 | - target: "99" 60 | timeframe: "30d" 61 | warning: "99.5" 62 | # Either `monitor_ids` or `monitors` should be provided 63 | # `monitor_ids` is a list of externally created monitors to use for this monitor-based SLO 64 | # If `monitors` map is provided, the monitors will be created by the module and assigned to the SLO 65 | monitor_ids: null 66 | monitors: 67 | ec2-failed-status-check: 68 | name: "(EC2) Status Check" 69 | type: metric alert 70 | query: | 71 | avg(last_10m):avg:aws.ec2.status_check_failed{*} by {instance_id} > 0 72 | message: | 73 | ({stage} {region}) {instance_id} failed a status check 74 | escalation_message: "" 75 | tags: 76 | ManagedBy: Terraform 77 | priority: 3 78 | notify_no_data: false 79 | notify_audit: true 80 | require_full_window: true 81 | enable_logs_sample: false 82 | force_delete: true 83 | include_tags: true 84 | locked: false 85 | renotify_interval: 60 86 | timeout_h: 0 87 | evaluation_delay: 60 88 | new_host_delay: 300 89 | new_group_delay: 0 90 | groupby_simple_monitor: false 91 | renotify_occurrences: 0 92 | renotify_statuses: [] 93 | validate: true 94 | no_data_timeframe: 10 95 | threshold_windows: {} 96 | thresholds: 97 | critical: 0 98 | tags: 99 | ManagedBy: terraform 100 | test: true 101 | api_version: null 102 | ``` 103 | 104 | ## References 105 | - [Service Level Objectives](https://docs.datadoghq.com/monitors/service_level_objectives/) 106 | - [Monitor-based SLOs](https://docs.datadoghq.com/monitors/service_level_objectives/monitor/) 107 | - [Datadog Error Budget](https://docs.datadoghq.com/monitors/service_level_objectives/error_budget/) 108 | - [Monitor-based SLO example](https://github.com/DataDog/terraform-provider-datadog/issues/667) 109 | -------------------------------------------------------------------------------- /modules/slo/context.tf: -------------------------------------------------------------------------------- 1 | # 2 | # ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label 3 | # All other instances of this file should be a copy of that one 4 | # 5 | # 6 | # Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf 7 | # and then place it in your Terraform module to automatically get 8 | # Cloud Posse's standard configuration inputs suitable for passing 9 | # to Cloud Posse modules. 10 | # 11 | # curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf 12 | # 13 | # Modules should access the whole context as `module.this.context` 14 | # to get the input variables with nulls for defaults, 15 | # for example `context = module.this.context`, 16 | # and access individual variables as `module.this.`, 17 | # with final values filled in. 18 | # 19 | # For example, when using defaults, `module.this.context.delimiter` 20 | # will be null, and `module.this.delimiter` will be `-` (hyphen). 21 | # 22 | 23 | module "this" { 24 | source = "cloudposse/label/null" 25 | version = "0.25.0" # requires Terraform >= 0.13.0 26 | 27 | enabled = var.enabled 28 | namespace = var.namespace 29 | tenant = var.tenant 30 | environment = var.environment 31 | stage = var.stage 32 | name = var.name 33 | delimiter = var.delimiter 34 | attributes = var.attributes 35 | tags = var.tags 36 | additional_tag_map = var.additional_tag_map 37 | label_order = var.label_order 38 | regex_replace_chars = var.regex_replace_chars 39 | id_length_limit = var.id_length_limit 40 | label_key_case = var.label_key_case 41 | label_value_case = var.label_value_case 42 | descriptor_formats = var.descriptor_formats 43 | labels_as_tags = var.labels_as_tags 44 | 45 | context = var.context 46 | } 47 | 48 | # Copy contents of cloudposse/terraform-null-label/variables.tf here 49 | 50 | variable "context" { 51 | type = any 52 | default = { 53 | enabled = true 54 | namespace = null 55 | tenant = null 56 | environment = null 57 | stage = null 58 | name = null 59 | delimiter = null 60 | attributes = [] 61 | tags = {} 62 | additional_tag_map = {} 63 | regex_replace_chars = null 64 | label_order = [] 65 | id_length_limit = null 66 | label_key_case = null 67 | label_value_case = null 68 | descriptor_formats = {} 69 | # Note: we have to use [] instead of null for unset lists due to 70 | # https://github.com/hashicorp/terraform/issues/28137 71 | # which was not fixed until Terraform 1.0.0, 72 | # but we want the default to be all the labels in `label_order` 73 | # and we want users to be able to prevent all tag generation 74 | # by setting `labels_as_tags` to `[]`, so we need 75 | # a different sentinel to indicate "default" 76 | labels_as_tags = ["unset"] 77 | } 78 | description = <<-EOT 79 | Single object for setting entire context at once. 80 | See description of individual variables for details. 81 | Leave string and numeric variables as `null` to use default value. 82 | Individual variable settings (non-null) override settings in context object, 83 | except for attributes, tags, and additional_tag_map, which are merged. 84 | EOT 85 | 86 | validation { 87 | condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) 88 | error_message = "Allowed values: `lower`, `title`, `upper`." 89 | } 90 | 91 | validation { 92 | condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) 93 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 94 | } 95 | } 96 | 97 | variable "enabled" { 98 | type = bool 99 | default = null 100 | description = "Set to false to prevent the module from creating any resources" 101 | } 102 | 103 | variable "namespace" { 104 | type = string 105 | default = null 106 | description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" 107 | } 108 | 109 | variable "tenant" { 110 | type = string 111 | default = null 112 | description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" 113 | } 114 | 115 | variable "environment" { 116 | type = string 117 | default = null 118 | description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" 119 | } 120 | 121 | variable "stage" { 122 | type = string 123 | default = null 124 | description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" 125 | } 126 | 127 | variable "name" { 128 | type = string 129 | default = null 130 | description = <<-EOT 131 | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. 132 | This is the only ID element not also included as a `tag`. 133 | The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. 134 | EOT 135 | } 136 | 137 | variable "delimiter" { 138 | type = string 139 | default = null 140 | description = <<-EOT 141 | Delimiter to be used between ID elements. 142 | Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. 143 | EOT 144 | } 145 | 146 | variable "attributes" { 147 | type = list(string) 148 | default = [] 149 | description = <<-EOT 150 | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, 151 | in the order they appear in the list. New attributes are appended to the 152 | end of the list. The elements of the list are joined by the `delimiter` 153 | and treated as a single ID element. 154 | EOT 155 | } 156 | 157 | variable "labels_as_tags" { 158 | type = set(string) 159 | default = ["default"] 160 | description = <<-EOT 161 | Set of labels (ID elements) to include as tags in the `tags` output. 162 | Default is to include all labels. 163 | Tags with empty values will not be included in the `tags` output. 164 | Set to `[]` to suppress all generated tags. 165 | **Notes:** 166 | The value of the `name` tag, if included, will be the `id`, not the `name`. 167 | Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be 168 | changed in later chained modules. Attempts to change it will be silently ignored. 169 | EOT 170 | } 171 | 172 | variable "tags" { 173 | type = map(string) 174 | default = {} 175 | description = <<-EOT 176 | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). 177 | Neither the tag keys nor the tag values will be modified by this module. 178 | EOT 179 | } 180 | 181 | variable "additional_tag_map" { 182 | type = map(string) 183 | default = {} 184 | description = <<-EOT 185 | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. 186 | This is for some rare cases where resources want additional configuration of tags 187 | and therefore take a list of maps with tag key, value, and additional configuration. 188 | EOT 189 | } 190 | 191 | variable "label_order" { 192 | type = list(string) 193 | default = null 194 | description = <<-EOT 195 | The order in which the labels (ID elements) appear in the `id`. 196 | Defaults to ["namespace", "environment", "stage", "name", "attributes"]. 197 | You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. 198 | EOT 199 | } 200 | 201 | variable "regex_replace_chars" { 202 | type = string 203 | default = null 204 | description = <<-EOT 205 | Terraform regular expression (regex) string. 206 | Characters matching the regex will be removed from the ID elements. 207 | If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. 208 | EOT 209 | } 210 | 211 | variable "id_length_limit" { 212 | type = number 213 | default = null 214 | description = <<-EOT 215 | Limit `id` to this many characters (minimum 6). 216 | Set to `0` for unlimited length. 217 | Set to `null` for keep the existing setting, which defaults to `0`. 218 | Does not affect `id_full`. 219 | EOT 220 | validation { 221 | condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 222 | error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." 223 | } 224 | } 225 | 226 | variable "label_key_case" { 227 | type = string 228 | default = null 229 | description = <<-EOT 230 | Controls the letter case of the `tags` keys (label names) for tags generated by this module. 231 | Does not affect keys of tags passed in via the `tags` input. 232 | Possible values: `lower`, `title`, `upper`. 233 | Default value: `title`. 234 | EOT 235 | 236 | validation { 237 | condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) 238 | error_message = "Allowed values: `lower`, `title`, `upper`." 239 | } 240 | } 241 | 242 | variable "label_value_case" { 243 | type = string 244 | default = null 245 | description = <<-EOT 246 | Controls the letter case of ID elements (labels) as included in `id`, 247 | set as tag values, and output by this module individually. 248 | Does not affect values of tags passed in via the `tags` input. 249 | Possible values: `lower`, `title`, `upper` and `none` (no transformation). 250 | Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. 251 | Default value: `lower`. 252 | EOT 253 | 254 | validation { 255 | condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) 256 | error_message = "Allowed values: `lower`, `title`, `upper`, `none`." 257 | } 258 | } 259 | 260 | variable "descriptor_formats" { 261 | type = any 262 | default = {} 263 | description = <<-EOT 264 | Describe additional descriptors to be output in the `descriptors` output map. 265 | Map of maps. Keys are names of descriptors. Values are maps of the form 266 | `{ 267 | format = string 268 | labels = list(string) 269 | }` 270 | (Type is `any` so the map values can later be enhanced to provide additional options.) 271 | `format` is a Terraform format string to be passed to the `format()` function. 272 | `labels` is a list of labels, in order, to pass to `format()` function. 273 | Label values will be normalized before being passed to `format()` so they will be 274 | identical to how they appear in `id`. 275 | Default is `{}` (`descriptors` output will be empty). 276 | EOT 277 | } 278 | 279 | #### End of copy of cloudposse/terraform-null-label/variables.tf 280 | -------------------------------------------------------------------------------- /modules/slo/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | enabled = module.this.enabled 3 | 4 | alert_tags = local.enabled && var.alert_tags != null ? format("%s%s", var.alert_tags_separator, join(var.alert_tags_separator, var.alert_tags)) : "" 5 | } 6 | -------------------------------------------------------------------------------- /modules/slo/metric_slo.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | datadog_metric_slos = { for slo in var.datadog_slos : slo.name => slo if slo.type == "metric" && lookup(slo, "enabled", true) && local.enabled } 3 | 4 | temp_datadog_metric_slo_alerts = flatten([ 5 | for name, slo in var.datadog_slos : [ 6 | for i, threshold in slo.thresholds : { 7 | slo = slo, 8 | slo_name = format("%s_threshold%s", name, i) 9 | threshold = threshold 10 | } 11 | if slo.type == "metric" && local.enabled && lookup(slo, "enabled", true) 12 | ] 13 | ]) 14 | 15 | datadog_metric_slo_alerts = { for monitor in local.temp_datadog_metric_slo_alerts : monitor.slo_name => monitor } 16 | } 17 | 18 | resource "datadog_service_level_objective" "metric_slo" { 19 | for_each = local.datadog_metric_slos 20 | 21 | # Required 22 | name = each.value.name 23 | type = each.value.type 24 | 25 | # Optional 26 | description = lookup(each.value, "description", null) 27 | force_delete = lookup(each.value, "force_delete", true) 28 | validate = lookup(each.value, "validate", false) 29 | 30 | query { 31 | denominator = each.value.query.denominator 32 | numerator = each.value.query.numerator 33 | } 34 | 35 | dynamic "thresholds" { 36 | for_each = each.value.thresholds 37 | content { 38 | target = lookup(thresholds.value, "target", null) 39 | timeframe = lookup(thresholds.value, "timeframe", null) 40 | warning = lookup(thresholds.value, "warning", null) 41 | } 42 | } 43 | 44 | # Convert terraform tags map to Datadog tags map 45 | # If a key is supplied with a value, it will render "key:value" as a tag 46 | # tags: 47 | # key: value 48 | # If a key is supplied without a value (null), it will render "key" as a tag 49 | # tags: 50 | # key: null 51 | tags = [ 52 | for tagk, tagv in lookup(each.value, "tags", module.this.tags) : (tagv != null ? format("%s:%s", tagk, tagv) : tagk) 53 | ] 54 | } 55 | 56 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/monitor 57 | resource "datadog_monitor" "metric_slo_alert" { 58 | for_each = local.datadog_metric_slo_alerts 59 | 60 | type = "slo alert" 61 | name = format("(SLO Error Budget Alert) %s", each.value.slo.name) 62 | message = format("%s %s", each.value.slo.message, local.alert_tags) 63 | 64 | query = < ${lookup(each.value.threshold, "target", "99.00")} 66 | EOF 67 | 68 | monitor_thresholds { 69 | critical = lookup(each.value.threshold, "target", null) 70 | } 71 | 72 | # Convert terraform tags map to Datadog tags map 73 | # If a key is supplied with a value, it will render "key:value" as a tag 74 | # tags: 75 | # key: value 76 | # If a key is supplied without a value (null), it will render "key" as a tag 77 | # tags: 78 | # key: null 79 | tags = [ 80 | for tagk, tagv in lookup(each.value.slo, "tags", module.this.tags) : (tagv != null ? format("%s:%s", tagk, tagv) : tagk) 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /modules/slo/monitor_slo.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | datadog_monitor_slos = { for slo in var.datadog_slos : slo.name => slo if slo.type == "monitor" && lookup(slo, "enabled", true) && local.enabled } 3 | } 4 | 5 | module "datadog_monitors" { 6 | source = "../monitors" 7 | 8 | for_each = local.datadog_monitor_slos 9 | 10 | datadog_monitors = lookup(each.value, "monitors", {}) 11 | alert_tags = var.alert_tags 12 | alert_tags_separator = var.alert_tags_separator 13 | 14 | context = module.this.context 15 | } 16 | 17 | resource "datadog_service_level_objective" "monitor_slo" { 18 | for_each = local.datadog_monitor_slos 19 | 20 | # Required 21 | name = each.value.name 22 | type = each.value.type 23 | 24 | dynamic "thresholds" { 25 | for_each = each.value.thresholds 26 | content { 27 | target = lookup(thresholds.value, "target", "99.00") 28 | timeframe = lookup(thresholds.value, "timeframe", "7d") 29 | warning = lookup(thresholds.value, "warning", "99.95") 30 | } 31 | } 32 | 33 | groups = lookup(each.value, "groups", []) 34 | 35 | # Either `monitor_ids` or `monitors` should be provided 36 | # If `monitors` map is provided, the monitors are created in the `datadog_monitors` module 37 | monitor_ids = try(each.value.monitor_ids, null) != null ? each.value.monitor_ids : module.datadog_monitors[each.key].datadog_monitor_ids 38 | 39 | # Optional 40 | description = lookup(each.value, "description", null) 41 | force_delete = lookup(each.value, "force_delete", true) 42 | validate = lookup(each.value, "validate", false) 43 | 44 | # Convert terraform tags map to Datadog tags map 45 | # If a key is supplied with a value, it will render "key:value" as a tag 46 | # tags: 47 | # key: value 48 | # If a key is supplied without a value (null), it will render "key" as a tag 49 | # tags: 50 | # key: null 51 | tags = [ 52 | for tagk, tagv in lookup(each.value, "tags", module.this.tags) : (tagv != null ? format("%s:%s", tagk, tagv) : tagk) 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /modules/slo/outputs.tf: -------------------------------------------------------------------------------- 1 | output "datadog_monitor_slos" { 2 | value = datadog_service_level_objective.monitor_slo 3 | description = "Map of created monitor-based SLOs" 4 | } 5 | 6 | output "datadog_monitor_slo_monitors" { 7 | value = module.datadog_monitors 8 | description = "Created monitors for the monitor-based SLOs" 9 | } 10 | 11 | output "datadog_metric_slos" { 12 | value = datadog_service_level_objective.metric_slo 13 | description = "Map of created metric-based SLOs" 14 | } 15 | 16 | output "datadog_metric_slo_alerts" { 17 | value = datadog_monitor.metric_slo_alert 18 | description = "Map of created metric-based SLO alerts" 19 | } 20 | -------------------------------------------------------------------------------- /modules/slo/variables.tf: -------------------------------------------------------------------------------- 1 | variable "datadog_slos" { 2 | type = any 3 | description = "Map of Datadog SLO configurations" 4 | } 5 | 6 | variable "alert_tags" { 7 | type = list(string) 8 | description = "List of alert tags to add to all alert messages, e.g. `[\"@opsgenie\"]` or `[\"@devops\", \"@opsgenie\"]`" 9 | default = null 10 | } 11 | 12 | variable "alert_tags_separator" { 13 | type = string 14 | description = "Separator for the alert tags. All strings from the `alert_tags` variable will be joined into one string using the separator and then added to the alert message" 15 | default = "\n" 16 | } 17 | -------------------------------------------------------------------------------- /modules/slo/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | datadog = { 6 | source = "datadog/datadog" 7 | version = ">= 3.0.0" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /modules/synthetics/README.md: -------------------------------------------------------------------------------- 1 | ## synthetics 2 | 3 | This module creates Datadog [synthetic tests](https://docs.datadoghq.com/api/latest/synthetics/). 4 | It accepts all the configuration parameters supported by the Datadog Terraform 5 | resource except `BasicAuth` authentication wherever it occurs. 6 | 7 | ## Usage 8 | 9 | The `datadog_synthetics` input takes a map of test definitions. You must supply 10 | the keys for the map, but the values can follow either of two schemas described 11 | below. 12 | 13 | ### API Schema 14 | 15 | Datadog provides a REST API for managing synthetic tests. We refer to the responses 16 | to the API requests for test definitions as the "API Schema" for the tests. 17 | (Note that some items in the response are read-only, and are ignored if included 18 | in the definition of a test.) There are errors and omissions in the documentation 19 | of the API output, and where we have found API results that differ from the 20 | documentation, we have used the API results as the source of truth. 21 | 22 | You can retrieve test definitions from the Datadog API in 3 ways: 23 | 24 | 1. You can [retrieve an individual API test](https://docs.datadoghq.com/api/latest/synthetics/#get-an-api-test) 25 | 2. You can [retrieve an individual browser test](https://docs.datadoghq.com/api/latest/synthetics/#get-a-browser-test) 26 | 3. You can [retrieve a list of all tests](https://docs.datadoghq.com/api/latest/synthetics/#get-the-list-of-all-synthetic-tests) 27 | 28 | NOTE: As of this writing (2023-10-20), the list of all tests fails to include the steps in a multistep browser test. 29 | You must use the individual browser test API to retrieve the test definition including the steps. 30 | This is a known issue with Datadog, and hopefully will be fixed soon, but verify that it is fixed before 31 | relying on the list of all tests if you are using multistep browser tests. 32 | 33 | The `datadog_synthetics` input takes a map of test definitions. You must supply 34 | the keys for the map, but the values can simply be the output of 35 | applying Terraform's `jsondecode()` to the output of the API. In the case 36 | of the list of all tests, you must iterate over the list and supply 37 | a key for each test. We recommend that you use the test's `name` as the key. 38 | 39 | For any test, you can optionally add `enabled = false` to disable/delete the test. 40 | 41 | NOTE: Since this module is implemented in Terraform, any field in the API 42 | schema that does not have a counterpart in the Terraform schema will be ignored. 43 | As of this writing (Datadog Terraform provider version 3.30.0), that includes 44 | the `metatdata` field in the steps of a multistep API test. 45 | 46 | See https://github.com/DataDog/terraform-provider-datadog/issues/2155 47 | 48 | 49 | ### Terraform schema 50 | 51 | Historically, and preserved for backward compatibility, you can configure tests 52 | using the `datadog_synthetics` input, which takes an object that is a map of 53 | synthetic tests, each test matching the schema of the `datadog_sythetics_test` 54 | [Terraform resource](https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/synthetics_test). See examples with suffix `-terraform.yaml` in 55 | `examples/synthetics/terraform-schema/catalog`. 56 | 57 | One distinction of this schema is that there is no `config` member. Instead, 58 | elements of `config`, such as `assertions`, are pulled up to the top level 59 | and rendered in the singular, e.g. `assertion`. Another change is that `options` is 60 | renamed `options_list`. When in doubt, refer to the Terraform code to see how 61 | the input is mapped to the Terraform resource. 62 | 63 | Note that the Terraform resource does not support the details of the `element` 64 | field for the steps of a multistep browser test. This module allows you to use an object 65 | following the API schema, or you can use a string that is the JSON encoding 66 | of the object. However, if you use the JSON string encoding, and you are 67 | using a "user locator", you must supply the `element_user_locator` attribute 68 | even though it is already included in the JSON encoding, or else the 69 | Terraform provider will show perpetual drift. 70 | 71 | As with the API schema, you can optionally add `enabled = false` to disable/delete the test. 72 | 73 | #### Unsupported inputs 74 | 75 | Any of the "BasicAuth" inputs are not supported due to their complexity and 76 | rare usage. Usually you can use the `headers` input instead. BasicAuth support 77 | could be added if there is a need. 78 | 79 | ### Locations 80 | 81 | The `locations` input takes a list of locations used to run the test. It 82 | defaults to the special value "all" which is used to indicate all _public_ 83 | locations. Any locations included in the `locations` list will be _added_ to the 84 | list of locations specified in the individual test; they will not replace the 85 | list of locations specified in the test. 86 | -------------------------------------------------------------------------------- /modules/synthetics/outputs.tf: -------------------------------------------------------------------------------- 1 | output "datadog_synthetics_test_names" { 2 | value = values(datadog_synthetics_test.default)[*].name 3 | description = "Names of the created Datadog Synthetic tests" 4 | } 5 | 6 | output "datadog_synthetics_test_ids" { 7 | value = values(datadog_synthetics_test.default)[*].id 8 | description = "IDs of the created Datadog synthetic tests" 9 | } 10 | 11 | output "datadog_synthetics_test_monitor_ids" { 12 | value = values(datadog_synthetics_test.default)[*].monitor_id 13 | description = "IDs of the monitors associated with the Datadog synthetics tests" 14 | } 15 | 16 | output "datadog_synthetic_tests" { 17 | value = datadog_synthetics_test.default 18 | description = "The synthetic tests created in DataDog" 19 | } 20 | -------------------------------------------------------------------------------- /modules/synthetics/variables.tf: -------------------------------------------------------------------------------- 1 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/synthetics_test 2 | variable "datadog_synthetics" { 3 | type = any 4 | description = "Map of Datadog synthetic test configurations using Terraform or API schema. See README for details." 5 | } 6 | 7 | variable "default_tags_enabled" { 8 | type = bool 9 | description = "If true, all tests will have tags from `null-label` added to them" 10 | default = true 11 | } 12 | 13 | variable "alert_tags" { 14 | type = list(string) 15 | description = "List of alert tags to add to all alert messages, e.g. `[\"@opsgenie\"]` or `[\"@devops\", \"@opsgenie\"]`" 16 | default = [] 17 | } 18 | 19 | variable "alert_tags_separator" { 20 | type = string 21 | description = "Separator for the alert tags. All strings from the `alert_tags` variable will be joined into one string using the separator and then added to the alert message" 22 | default = "\n" 23 | } 24 | 25 | variable "locations" { 26 | type = list(string) 27 | description = <<-EOT 28 | Array of locations used to run synthetic tests, or `"all"` to use all public locations. 29 | These locations will be added to any locations specified in the `locations` attribute of a test. 30 | EOT 31 | default = ["all"] 32 | } 33 | 34 | -------------------------------------------------------------------------------- /modules/synthetics/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | datadog = { 6 | source = "datadog/datadog" 7 | # Must have >= 3.43.1 to have fix for https://github.com/DataDog/terraform-provider-datadog/issues/2531 8 | version = ">= 3.43.1" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "datadog_monitor_names" { 2 | value = values(datadog_monitor.default)[*].name 3 | description = "Names of the created Datadog monitors" 4 | } 5 | 6 | output "datadog_monitor_ids" { 7 | value = values(datadog_monitor.default)[*].id 8 | description = "IDs of the created Datadog monitors" 9 | } 10 | 11 | output "datadog_monitors" { 12 | value = datadog_monitor.default 13 | description = "Datadog monitor outputs" 14 | } 15 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | .test-harness 2 | .envrc 3 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | TEST_HARNESS ?= https://github.com/cloudposse/test-harness.git 2 | TEST_HARNESS_BRANCH ?= master 3 | TEST_HARNESS_PATH = $(realpath .test-harness) 4 | BATS_ARGS ?= --tap 5 | BATS_LOG ?= test.log 6 | 7 | # Define a macro to run the tests 8 | define RUN_TESTS 9 | @echo "Running tests in $(1)" 10 | @cd $(1) && bats $(BATS_ARGS) $(addsuffix .bats,$(addprefix $(TEST_HARNESS_PATH)/test/terraform/,$(TESTS))) 11 | endef 12 | 13 | default: all 14 | 15 | -include Makefile.* 16 | 17 | ## Provision the test-harnesss 18 | .test-harness: 19 | [ -d $@ ] || git clone --depth=1 -b $(TEST_HARNESS_BRANCH) $(TEST_HARNESS) $@ 20 | 21 | ## Initialize the tests 22 | init: .test-harness 23 | 24 | ## Install all dependencies (OS specific) 25 | deps:: 26 | @exit 0 27 | 28 | ## Clean up the test harness 29 | clean: 30 | [ "$(TEST_HARNESS_PATH)" == "/" ] || rm -rf $(TEST_HARNESS_PATH) 31 | 32 | ## Run all tests 33 | all: module examples/complete 34 | 35 | ## Run basic sanity checks against the module itself 36 | module: export TESTS ?= installed lint module-pinning provider-pinning validate terraform-docs input-descriptions output-descriptions 37 | module: deps 38 | $(call RUN_TESTS, ../) 39 | 40 | ## Run tests against example 41 | examples/complete: export TESTS ?= installed lint validate 42 | examples/complete: deps 43 | $(call RUN_TESTS, ../$@) 44 | -------------------------------------------------------------------------------- /test/Makefile.alpine: -------------------------------------------------------------------------------- 1 | ifneq (,$(wildcard /sbin/apk)) 2 | ## Install all dependencies for alpine 3 | deps:: init 4 | @apk add --update terraform-docs@cloudposse json2hcl@cloudposse 5 | endif 6 | -------------------------------------------------------------------------------- /test/src/.gitignore: -------------------------------------------------------------------------------- 1 | .gopath 2 | vendor/ 3 | -------------------------------------------------------------------------------- /test/src/Makefile: -------------------------------------------------------------------------------- 1 | export TERRAFORM_VERSION ?= $(shell curl -s https://checkpoint-api.hashicorp.com/v1/check/terraform | jq -r -M '.current_version' | cut -d. -f1) 2 | 3 | .DEFAULT_GOAL : all 4 | 5 | .PHONY: all 6 | ## Default target 7 | all: test 8 | 9 | .PHONY : init 10 | ## Initialize tests 11 | init: 12 | @exit 0 13 | 14 | .PHONY : test 15 | ## Run tests 16 | test: init 17 | go mod download 18 | go test -v -timeout 5m -run TestExamplesComplete 19 | go test -v -timeout 5m -run TestExamplesSynthetics 20 | # We can only create Datadog child organizations with terraform, but cannot destroy them 21 | # Warning: Cannot delete organization. Remove organization by contacting support (https://docs.datadoghq.com/help/). 22 | # go test -v -timeout 5m -run TestExamplesChildOrganization 23 | 24 | ## Run tests in docker container 25 | docker/test: 26 | docker run --name terratest --rm -it -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -e GITHUB_TOKEN \ 27 | -e PATH="/usr/local/terraform/$(TERRAFORM_VERSION)/bin:/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \ 28 | -v $(CURDIR)/../../:/module/ cloudposse/test-harness:latest -C /module/test/src test 29 | 30 | .PHONY : clean 31 | ## Clean up files 32 | clean: 33 | rm -rf ../../examples/complete/*.tfstate* 34 | -------------------------------------------------------------------------------- /test/src/examples_child_organization_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // Test the Terraform module in examples/child_organization using Terratest. 8 | // We can only create Datadog child organizations with terraform, but cannot destroy them 9 | // Warning: Cannot delete organization. Remove organization by contacting support (https://docs.datadoghq.com/help/). 10 | func DisabledTestExamplesChildOrganization(t *testing.T) { 11 | t.Parallel() 12 | /* 13 | randID := strings.ToLower(random.UniqueId()) 14 | attributes := []string{randID} 15 | 16 | rootFolder := "../../" 17 | terraformFolderRelativeToRoot := "examples/child_organization" 18 | varFiles := []string{"fixtures.us-east-2.tfvars"} 19 | 20 | tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot) 21 | 22 | terraformOptions := &terraform.Options{ 23 | // The path to where our Terraform code is located 24 | TerraformDir: tempTestFolder, 25 | Upgrade: true, 26 | // Variables to pass to our Terraform code using -var-file options 27 | VarFiles: varFiles, 28 | Vars: map[string]interface{}{ 29 | "attributes": attributes, 30 | }, 31 | } 32 | 33 | // At the end of the test, run `terraform destroy` to clean up any resources that were created 34 | defer cleanup(t, terraformOptions, tempTestFolder) 35 | 36 | // This will run `terraform init` and `terraform apply` and fail the test if there are any errors 37 | terraform.InitAndApply(t, terraformOptions) 38 | 39 | // Run `terraform output` to get the value of an output variable 40 | datadogChildOrgId := terraform.OutputList(t, terraformOptions, "id") 41 | // Verify we're getting back the outputs we expect 42 | assert.Greater(t, len(datadogChildOrgId), 0) 43 | 44 | */ 45 | } 46 | -------------------------------------------------------------------------------- /test/src/examples_synthetics_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "github.com/gruntwork-io/terratest/modules/logger" 5 | "github.com/gruntwork-io/terratest/modules/random" 6 | "github.com/gruntwork-io/terratest/modules/terraform" 7 | test_structure "github.com/gruntwork-io/terratest/modules/test-structure" 8 | "github.com/stretchr/testify/assert" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | // Test the Terraform module in examples/synthetics using Terratest. 14 | func TestExamplesSynthetics(t *testing.T) { 15 | t.Parallel() 16 | randID := strings.ToLower(random.UniqueId()) 17 | attributes := []string{randID} 18 | 19 | rootFolder := "../../" 20 | terraformFolderRelativeToRoot := "examples/synthetics" 21 | varFiles := []string{"fixtures.us-east-2.tfvars"} 22 | 23 | tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot) 24 | 25 | terraformOptions := &terraform.Options{ 26 | // The path to where our Terraform code is located 27 | TerraformDir: tempTestFolder, 28 | Upgrade: true, 29 | // Variables to pass to our Terraform code using -var-file options 30 | VarFiles: varFiles, 31 | Vars: map[string]interface{}{ 32 | "attributes": attributes, 33 | }, 34 | } 35 | // Keep the output quiet 36 | if !testing.Verbose() { 37 | terraformOptions.Logger = logger.Discard 38 | } 39 | 40 | // At the end of the test, run `terraform destroy` to clean up any resources that were created 41 | defer cleanup(t, terraformOptions, tempTestFolder) 42 | 43 | // This will run `terraform init` and `terraform apply` and fail the test if there are any errors 44 | terraform.InitAndApply(t, terraformOptions) 45 | 46 | // Run `terraform output` to get the value of an output variable 47 | datadogSyntheticsTestNames := terraform.OutputList(t, terraformOptions, "datadog_synthetics_test_names") 48 | // Verify we're getting back the outputs we expect 49 | assert.Greater(t, len(datadogSyntheticsTestNames), 0) 50 | } 51 | -------------------------------------------------------------------------------- /test/src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cloudposse/terraform-datadog-platform 2 | 3 | go 1.21 4 | 5 | require ( 6 | // Known security flaws in terratest dependencies prior to v0.40.15 7 | github.com/gruntwork-io/terratest v0.46.11 8 | github.com/stretchr/testify v1.8.4 9 | ) 10 | 11 | require ( 12 | cloud.google.com/go v0.110.0 // indirect 13 | cloud.google.com/go/compute v1.19.1 // indirect 14 | cloud.google.com/go/compute/metadata v0.2.3 // indirect 15 | cloud.google.com/go/iam v0.13.0 // indirect 16 | cloud.google.com/go/storage v1.28.1 // indirect 17 | github.com/agext/levenshtein v1.2.3 // indirect 18 | github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect 19 | github.com/aws/aws-sdk-go v1.44.122 // indirect 20 | github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect 21 | github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect 22 | github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect 23 | github.com/davecgh/go-spew v1.1.1 // indirect 24 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 25 | github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 // indirect 26 | github.com/go-logr/logr v1.2.4 // indirect 27 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 28 | github.com/go-openapi/jsonreference v0.20.2 // indirect 29 | github.com/go-openapi/swag v0.22.3 // indirect 30 | github.com/go-sql-driver/mysql v1.4.1 // indirect 31 | github.com/gogo/protobuf v1.3.2 // indirect 32 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 33 | github.com/golang/protobuf v1.5.3 // indirect 34 | github.com/google/gnostic-models v0.6.8 // indirect 35 | github.com/google/go-cmp v0.5.9 // indirect 36 | github.com/google/gofuzz v1.2.0 // indirect 37 | github.com/google/uuid v1.3.0 // indirect 38 | github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect 39 | github.com/googleapis/gax-go/v2 v2.7.1 // indirect 40 | github.com/gruntwork-io/go-commons v0.8.0 // indirect 41 | github.com/hashicorp/errwrap v1.0.0 // indirect 42 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 43 | github.com/hashicorp/go-getter v1.7.5 // indirect 44 | github.com/hashicorp/go-multierror v1.1.0 // indirect 45 | github.com/hashicorp/go-safetemp v1.0.0 // indirect 46 | github.com/hashicorp/go-version v1.6.0 // indirect 47 | github.com/hashicorp/hcl/v2 v2.9.1 // indirect 48 | github.com/hashicorp/terraform-json v0.13.0 // indirect 49 | github.com/imdario/mergo v0.3.11 // indirect 50 | github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a // indirect 51 | github.com/jmespath/go-jmespath v0.4.0 // indirect 52 | github.com/josharian/intern v1.0.0 // indirect 53 | github.com/json-iterator/go v1.1.12 // indirect 54 | github.com/klauspost/compress v1.15.11 // indirect 55 | github.com/mailru/easyjson v0.7.7 // indirect 56 | github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 // indirect 57 | github.com/mitchellh/go-homedir v1.1.0 // indirect 58 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 59 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 60 | github.com/moby/spdystream v0.2.0 // indirect 61 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 62 | github.com/modern-go/reflect2 v1.0.2 // indirect 63 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 64 | github.com/pmezard/go-difflib v1.0.0 // indirect 65 | github.com/pquerna/otp v1.2.0 // indirect 66 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 67 | github.com/spf13/pflag v1.0.5 // indirect 68 | github.com/tmccombs/hcl2json v0.3.3 // indirect 69 | github.com/ulikunitz/xz v0.5.11 // indirect 70 | github.com/urfave/cli v1.22.2 // indirect 71 | github.com/zclconf/go-cty v1.9.1 // indirect 72 | go.opencensus.io v0.24.0 // indirect 73 | golang.org/x/crypto v0.21.0 // indirect 74 | golang.org/x/net v0.23.0 // indirect 75 | golang.org/x/oauth2 v0.8.0 // indirect 76 | golang.org/x/sys v0.18.0 // indirect 77 | golang.org/x/term v0.18.0 // indirect 78 | golang.org/x/text v0.14.0 // indirect 79 | golang.org/x/time v0.3.0 // indirect 80 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect 81 | google.golang.org/api v0.114.0 // indirect 82 | google.golang.org/appengine v1.6.7 // indirect 83 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect 84 | google.golang.org/grpc v1.56.3 // indirect 85 | google.golang.org/protobuf v1.33.0 // indirect 86 | gopkg.in/inf.v0 v0.9.1 // indirect 87 | gopkg.in/yaml.v2 v2.4.0 // indirect 88 | gopkg.in/yaml.v3 v3.0.1 // indirect 89 | k8s.io/api v0.28.4 // indirect 90 | k8s.io/apimachinery v0.28.4 // indirect 91 | k8s.io/client-go v0.28.4 // indirect 92 | k8s.io/klog/v2 v2.100.1 // indirect 93 | k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect 94 | k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect 95 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 96 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 97 | sigs.k8s.io/yaml v1.3.0 // indirect 98 | ) 99 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | # https://registry.terraform.io/providers/DataDog/datadog/latest/docs/resources/monitor 2 | variable "datadog_monitors" { 3 | type = map(object({ 4 | name = string 5 | type = string 6 | message = string 7 | escalation_message = string 8 | query = string 9 | tags = list(string) 10 | notify_no_data = bool 11 | new_host_delay = number 12 | evaluation_delay = number 13 | no_data_timeframe = number 14 | renotify_interval = number 15 | notify_audit = bool 16 | timeout_h = number 17 | enable_logs_sample = bool 18 | include_tags = bool 19 | require_full_window = bool 20 | locked = bool 21 | force_delete = bool 22 | threshold_windows = map(any) 23 | thresholds = map(any) 24 | })) 25 | description = "Map of Datadog monitor configurations. See catalog for examples" 26 | } 27 | 28 | variable "alert_tags" { 29 | type = list(string) 30 | description = "List of alert tags to add to all alert messages, e.g. `[\"@opsgenie\"]` or `[\"@devops\", \"@opsgenie\"]`" 31 | default = null 32 | } 33 | 34 | variable "alert_tags_separator" { 35 | type = string 36 | description = "Separator for the alert tags. All strings from the `alert_tags` variable will be joined into one string using the separator and then added to the alert message" 37 | default = "\n" 38 | } 39 | 40 | variable "restricted_roles_map" { 41 | type = map(set(string)) 42 | description = "Map of monitors names to sets of Datadog roles to restrict access to each monitor" 43 | default = {} 44 | } 45 | -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0.0" 3 | 4 | required_providers { 5 | datadog = { 6 | source = "datadog/datadog" 7 | version = ">= 3.0.0" 8 | } 9 | } 10 | } 11 | --------------------------------------------------------------------------------