From 4a7f0ccce88988ec25df227c4f28dca8fbe41da4 Mon Sep 17 00:00:00 2001 From: Bhargav Mogra Date: Tue, 16 Jan 2024 10:34:18 +0700 Subject: [PATCH] feat: `pagerduty_service_integration_email` to trigger alerts on recieving email. (#36) This feature solves the requirement to automatically trigger alerts upon receiving specific emails to specific email addresses. Also has below changes: * ci: Remove shiftleft scan from workflow and update pre-commit config * feat: Create module for pagerduty-service-integration-email * feat: Add example for pagerduty-service-integration-email usage * test: Add test for pagerduty service integration email * test: Use random suffixes in all examples to avoid resource conflicts during testing * docs: update readme to account for random generator resource * test: Update all tests to account for the random suffixes in their assertions AND fix the 'destroy' part of these tests by moving the defer block upto the top --- .github/workflows/shiftleft-terraform.yaml | 28 ---- .pre-commit-config.yaml | 60 +++++++-- Makefile | 1 + examples/honest-two-level-schedule/README.md | 8 +- examples/honest-two-level-schedule/inputs.tf | 1 + examples/honest-two-level-schedule/main.tf | 20 ++- examples/pagerduty-business-service/main.tf | 12 +- .../pagerduty-escalation-policy/README.md | 8 +- .../pagerduty-escalation-policy/inputs.tf | 1 + examples/pagerduty-escalation-policy/main.tf | 42 +++--- examples/pagerduty-schedule/README.md | 10 +- examples/pagerduty-schedule/inputs.tf | 1 + examples/pagerduty-schedule/main.tf | 18 ++- .../README.md | 52 ++++++++ .../inputs.tf | 34 +++++ .../main.tf | 69 ++++++++++ .../outputs.tf | 19 +++ .../providers.tf | 12 ++ examples/pagerduty-service/README.md | 12 +- examples/pagerduty-service/inputs.tf | 1 + examples/pagerduty-service/main.tf | 6 +- examples/pagerduty-service/providers.tf | 12 ++ examples/pagerduty-stakeholder/inputs.tf | 3 +- examples/pagerduty-stakeholder/main.tf | 10 +- examples/pagerduty-team/main.tf | 10 +- examples/pagerduty-user/README.md | 9 +- examples/pagerduty-user/inputs.tf | 1 + examples/pagerduty-user/main.tf | 10 +- examples/pagerduty-user/outputs.tf | 5 + inputs.tf | 1 + modules/honest-two-level-schedule/inputs.tf | 3 + .../README.md | 45 +++++++ .../generic-email-inbound-integration.tf | 26 ++++ .../inputs.tf | 43 ++++++ .../outputs.tf | 9 ++ .../providers.tf | 10 ++ modules/pagerduty-team/README.md | 2 +- modules/pagerduty-user/inputs.tf | 2 + test/honest_two_level_schedule_test.go | 7 +- test/pagerduty_business_service_test.go | 24 ++-- test/pagerduty_escalation_policy_test.go | 14 +- test/pagerduty_schedule_test.go | 10 +- ...agerduty_service_email_integration_test.go | 123 ++++++++++++++++++ test/pagerduty_service_test.go | 13 +- test/pagerduty_team_test.go | 18 +-- test/pagerduty_user_test.go | 31 +++-- 46 files changed, 718 insertions(+), 138 deletions(-) delete mode 100644 .github/workflows/shiftleft-terraform.yaml create mode 100644 examples/pagerduty-service-integration-email/README.md create mode 100644 examples/pagerduty-service-integration-email/inputs.tf create mode 100644 examples/pagerduty-service-integration-email/main.tf create mode 100644 examples/pagerduty-service-integration-email/outputs.tf create mode 100644 examples/pagerduty-service-integration-email/providers.tf create mode 100644 examples/pagerduty-service/providers.tf create mode 100644 modules/pagerduty-service-integration-email/README.md create mode 100644 modules/pagerduty-service-integration-email/generic-email-inbound-integration.tf create mode 100644 modules/pagerduty-service-integration-email/inputs.tf create mode 100644 modules/pagerduty-service-integration-email/outputs.tf create mode 100644 modules/pagerduty-service-integration-email/providers.tf create mode 100644 test/pagerduty_service_email_integration_test.go diff --git a/.github/workflows/shiftleft-terraform.yaml b/.github/workflows/shiftleft-terraform.yaml deleted file mode 100644 index c2dd729..0000000 --- a/.github/workflows/shiftleft-terraform.yaml +++ /dev/null @@ -1,28 +0,0 @@ ---- -# This workflow integrates ShiftLeft NG SAST with GitHub -# Visit https://docs.shiftleft.io for help -name: shiftleft-terraform - -on: - pull_request: - workflow_dispatch: - -permissions: read-all - -jobs: - shiftleft-terraform: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - run: echo "REPO_NAME=${GITHUB_REPOSITORY#*/}" >> $GITHUB_ENV - - name: Download ShiftLeft CLI - run: | - curl https://cdn.shiftleft.io/download/sl > ${GITHUB_WORKSPACE}/sl && chmod a+rx ${GITHUB_WORKSPACE}/sl - - name: Extract branch name - shell: bash - run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" - id: extract_branch - - name: NextGen Static Analysis - run: ${GITHUB_WORKSPACE}/sl analyze --strict --wait --app ${{ env.REPO_NAME}} --tag branch=${{ github.head_ref || steps.extract_branch.outputs.branch }} --terraform $(pwd) - env: - SHIFTLEFT_ACCESS_TOKEN: ${{ secrets.SHIFTLEFT_ACCESS_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8492d20..27a2a54 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,26 +1,64 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks + repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 # Get the latest version from: https://github.com/pre-commit/pre-commit-hooks/releases + rev: v4.5.0 # Get the latest from: https://github.com/pre-commit/pre-commit-hooks/releases hooks: - - id: end-of-file-fixer - - id: trailing-whitespace - id: check-yaml + args: ['--allow-multiple-documents'] + - id: check-added-large-files - id: detect-aws-credentials - args: ["--allow-missing-credentials"] + args: ['--allow-missing-credentials'] + - repo: local + hooks: + - id: create-checkov-baseline + name: Create Checkov Baseline + entry: bash -c 'if [ ! -f .checkov.baseline ]; then echo "{}" > .checkov.baseline && touch baseline-created; fi' + language: system + stages: [commit] + pass_filenames: false - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.77.1 # Get the latest version from: https://github.com/antonbabenko/pre-commit-terraform/releases + rev: v1.83.6 # Get the latest from: https://github.com/antonbabenko/pre-commit-terraform/releases hooks: - id: terraform_fmt - - id: terraform_docs - id: terraform_validate + args: + - --hook-config=--retry-once-with-cleanup=true + - --tf-init-args=-upgrade - id: terraform_tfsec + exclude: "test/" - id: terraform_checkov - - repo: https://github.com/gitguardian/ggshield - rev: v1.14.4 + exclude: "test/" + args: + - --args=--baseline __GIT_WORKING_DIR__/.checkov.baseline + - repo: local hooks: - - id: ggshield - language: python + - id: delete-checkov-baseline + name: Delete Checkov Baseline + entry: bash -c 'if [ -f baseline-created ]; then rm .checkov.baseline && rm baseline-created; fi' + language: system stages: [commit] - args: [ 'secret', 'scan', 'pre-commit' ] + pass_filenames: false + - repo: https://github.com/gitguardian/ggshield + rev: v1.21.0 + hooks: + - id: ggshield + language: python + stages: [commit] + args: [ 'secret', 'scan', 'pre-commit' ] + - repo: local + hooks: + - id: docs + name: docs + entry: make + args: [ 'docs' ] + language: system + # Run this at the end so that we don't end up in infinite loop + # where the end of line fixer runs first and then the docs and fmt + # and other hooks that modify files will break it again. + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 # Get the latest from: https://github.com/pre-commit/pre-commit-hooks/releases + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer diff --git a/Makefile b/Makefile index e962d8e..998975f 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ docs: cd examples/pagerduty-escalation-policy/; terraform-docs markdown . --output-file README.md --output-mode inject cd examples/honest-two-level-schedule/; terraform-docs markdown . --output-file README.md --output-mode inject cd examples/pagerduty-service/; terraform-docs markdown . --output-file README.md --output-mode inject + cd examples/pagerduty-service-integrations-email/; terraform-docs markdown . --output-file README.md --output-mode inject clean: rm -rf examples/*/terraform.tfstate examples/*/terraform.tfstate.backup examples/*/.test-data diff --git a/examples/honest-two-level-schedule/README.md b/examples/honest-two-level-schedule/README.md index 33245ca..2c13f54 100644 --- a/examples/honest-two-level-schedule/README.md +++ b/examples/honest-two-level-schedule/README.md @@ -15,7 +15,9 @@ ## Providers -No providers. +| Name | Version | +|------|---------| +| [random](#provider\_random) | n/a | ## Modules @@ -27,7 +29,9 @@ No providers. ## Resources -No resources. +| Name | Type | +|------|------| +| [random_id.random_suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | ## Inputs diff --git a/examples/honest-two-level-schedule/inputs.tf b/examples/honest-two-level-schedule/inputs.tf index 1e225dc..214de19 100644 --- a/examples/honest-two-level-schedule/inputs.tf +++ b/examples/honest-two-level-schedule/inputs.tf @@ -11,6 +11,7 @@ variable "team_name" { variable "pagerduty_token" { type = string description = "PagerDuty API token." + sensitive = true } variable "dummy_user_count" { diff --git a/examples/honest-two-level-schedule/main.tf b/examples/honest-two-level-schedule/main.tf index 2b0e08b..0e3e0fa 100644 --- a/examples/honest-two-level-schedule/main.tf +++ b/examples/honest-two-level-schedule/main.tf @@ -1,23 +1,31 @@ +resource "random_id" "random_suffix" { + byte_length = 4 +} + +locals { + random_suffix = random_id.random_suffix.b64_url +} + module "dummy_users" { count = var.dummy_user_count source = "../../modules/pagerduty-user" - name = "pagerduty-schedule-example-user-${count.index}" - email_address = "pagerduty-schedule-example-user-${count.index}@honestbank.com" + name = "pagerduty-schedule-example-user-${count.index}-${local.random_suffix}" + email_address = "pagerduty-schedule-example-user-${count.index}-${local.random_suffix}@honestbank.com" } module "dummy_team" { source = "../../modules/pagerduty-team" - name = var.team_name - description = "${var.name} - this is an example description" + name = "${var.team_name} - ${local.random_suffix}" + description = "${var.name} - ${local.random_suffix} - this is an example description" } module "schedule" { source = "../../modules/honest-two-level-schedule" - name = "Example - ${var.name}" - description = "${var.name} - this is an example description" + name = "Example-${var.name}-${local.random_suffix}" + description = "${var.name} - ${local.random_suffix} - this is an example description" # 604,800 seconds = 1 week (7 days) # 86,400 seconds = 1 day diff --git a/examples/pagerduty-business-service/main.tf b/examples/pagerduty-business-service/main.tf index 840b268..a915342 100644 --- a/examples/pagerduty-business-service/main.tf +++ b/examples/pagerduty-business-service/main.tf @@ -1,7 +1,15 @@ +resource "random_id" "random_suffix" { + byte_length = 4 +} + +locals { + random_suffix = random_id.random_suffix.b64_url +} + module "mock_team" { source = "../../modules/pagerduty-team" - name = "team - ${var.name}" + name = "Team-${var.name}-${local.random_suffix}" description = "Created by terratest" } @@ -9,7 +17,7 @@ module "mock_team" { module "pagerduty_business_service" { source = "../../modules/pagerduty-business-service" - name = var.name + name = "${var.name}-${local.random_suffix}" description = var.description point_of_contact = var.point_of_contact owner_team_id = module.mock_team.id diff --git a/examples/pagerduty-escalation-policy/README.md b/examples/pagerduty-escalation-policy/README.md index a2bf72a..d2c4a84 100644 --- a/examples/pagerduty-escalation-policy/README.md +++ b/examples/pagerduty-escalation-policy/README.md @@ -13,7 +13,9 @@ This example creates a 3-level escalation policy - an example screenshot is prov ## Providers -No providers. +| Name | Version | +|------|---------| +| [random](#provider\_random) | n/a | ## Modules @@ -34,7 +36,9 @@ No providers. ## Resources -No resources. +| Name | Type | +|------|------| +| [random_id.random_suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | ## Inputs diff --git a/examples/pagerduty-escalation-policy/inputs.tf b/examples/pagerduty-escalation-policy/inputs.tf index a7e2bcc..969daf6 100644 --- a/examples/pagerduty-escalation-policy/inputs.tf +++ b/examples/pagerduty-escalation-policy/inputs.tf @@ -28,4 +28,5 @@ variable "escalation_levels" { variable "pagerduty_token" { type = string description = "PagerDuty API token." + sensitive = true } diff --git a/examples/pagerduty-escalation-policy/main.tf b/examples/pagerduty-escalation-policy/main.tf index 4691cd3..b02042b 100644 --- a/examples/pagerduty-escalation-policy/main.tf +++ b/examples/pagerduty-escalation-policy/main.tf @@ -1,38 +1,46 @@ +resource "random_id" "random_suffix" { + byte_length = 4 +} + +locals { + random_suffix = random_id.random_suffix.b64_url +} + module "engineering_user_one" { source = "../../modules/pagerduty-user" - name = "pagerduty-escalation-policy-example-engineering-user-one" - email_address = "pagerduty-escalation-policy-example-engineering-user-one@honestbank.com" + name = "engineering-user-one-${local.random_suffix}" + email_address = "engineering-user-one-${local.random_suffix}@honestbank.com" } module "engineering_user_two" { source = "../../modules/pagerduty-user" - name = "pagerduty-escalation-policy-example-engineering-user-two" - email_address = "pagerduty-escalation-policy-example-engineering-user-two@honestbank.com" + name = "example-engineering-user-two-${local.random_suffix}" + email_address = "example-engineering-user-two-${local.random_suffix}@honestbank.com" } module "engineering_lead" { source = "../../modules/pagerduty-user" - name = "pagerduty-escalation-policy-example-engineering-lead" - email_address = "pagerduty-escalation-policy-example-engineering-lead@honestbank.com" + name = "example-engineering-lead-${local.random_suffix}" + email_address = "example-engineering-lead-${local.random_suffix}@honestbank.com" } module "product_manager" { source = "../../modules/pagerduty-user" - name = "pagerduty-escalation-policy-example-product-manager" - email_address = "pagerduty-escalation-policy-example-product-manager@honestbank.com" + name = "example-product-manager-${local.random_suffix}" + email_address = "example-product-manager-${local.random_suffix}@honestbank.com" } module "product_lead" { source = "../../modules/pagerduty-user" - name = "pagerduty-escalation-policy-example-product-lead" - email_address = "pagerduty-escalation-policy-example-product-lead@honestbank.com" + name = "example-product-lead-${local.random_suffix}" + email_address = "example-product-lead-${local.random_suffix}@honestbank.com" } module "level_one_engineering_schedule" { source = "../../modules/pagerduty-schedule" description = "level one engineering schedule" - name = "level one engineering schedule - ${var.schedule_suffix}" + name = "level one engineering schedule-${var.schedule_suffix}-${local.random_suffix}" # 604,800 seconds = 1 week (7 days) rotation_turn_length_seconds = 604800 @@ -51,7 +59,7 @@ module "level_two_engineering_schedule" { source = "../../modules/pagerduty-schedule" description = "level two engineering schedule" - name = "level two engineering schedule - ${var.schedule_suffix}" + name = "level two engineering schedule-${var.schedule_suffix}-${local.random_suffix}" # 604,800 seconds = 1 week (7 days) rotation_turn_length_seconds = 604800 @@ -70,7 +78,7 @@ module "level_two_product_schedule" { source = "../../modules/pagerduty-schedule" description = "level two product schedule" - name = "level two product schedule - ${var.schedule_suffix}" + name = "level two product schedule-${var.schedule_suffix}-${local.random_suffix}" # 604,800 seconds = 1 week (7 days) rotation_turn_length_seconds = 604800 @@ -86,7 +94,7 @@ module "level_three_engineering_schedule" { source = "../../modules/pagerduty-schedule" description = "level three engineering schedule" - name = "level three engineering schedule - ${var.schedule_suffix}" + name = "level three engineering schedule-${var.schedule_suffix}-${local.random_suffix}" # 604,800 seconds = 1 week (7 days) rotation_turn_length_seconds = 604800 @@ -102,7 +110,7 @@ module "level_three_product_schedule" { source = "../../modules/pagerduty-schedule" description = "level three product schedule" - name = "level three product schedule - ${var.schedule_suffix}" + name = "level three product schedule-${var.schedule_suffix}-${local.random_suffix}" # 604,800 seconds = 1 week (7 days) rotation_turn_length_seconds = 604800 @@ -116,14 +124,14 @@ module "level_three_product_schedule" { module "mock_team" { source = "../../modules/pagerduty-team" - name = "${var.name} team" + name = "Team-${var.name}-${local.random_suffix}" description = "Created by terratest" } module "escalation_policy" { source = "../../modules/pagerduty-escalation-policy" - name = var.name + name = "${var.name}-${local.random_suffix}" description = var.description escalation_delay_in_minutes = 60 diff --git a/examples/pagerduty-schedule/README.md b/examples/pagerduty-schedule/README.md index 3c2ffa8..aeb6f01 100644 --- a/examples/pagerduty-schedule/README.md +++ b/examples/pagerduty-schedule/README.md @@ -11,7 +11,9 @@ This example creates a basic PagerDuty Schedule. ## Providers -No providers. +| Name | Version | +|------|---------| +| [random](#provider\_random) | n/a | ## Modules @@ -23,7 +25,9 @@ No providers. ## Resources -No resources. +| Name | Type | +|------|------| +| [random_id.random_suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | ## Inputs @@ -39,4 +43,4 @@ No resources. | [schedule\_id](#output\_schedule\_id) | The `id` attribute of the schedule. | | [user\_one\_id](#output\_user\_one\_id) | Dummy user created for inserting into the schedule. | | [user\_two\_id](#output\_user\_two\_id) | Dummy user created for inserting into the schedule. | - + \ No newline at end of file diff --git a/examples/pagerduty-schedule/inputs.tf b/examples/pagerduty-schedule/inputs.tf index 67bea49..235e9c5 100644 --- a/examples/pagerduty-schedule/inputs.tf +++ b/examples/pagerduty-schedule/inputs.tf @@ -6,4 +6,5 @@ variable "name" { variable "pagerduty_token" { type = string description = "PagerDuty API token." + sensitive = true } diff --git a/examples/pagerduty-schedule/main.tf b/examples/pagerduty-schedule/main.tf index c23d920..2d9e703 100644 --- a/examples/pagerduty-schedule/main.tf +++ b/examples/pagerduty-schedule/main.tf @@ -1,19 +1,27 @@ +resource "random_id" "random_suffix" { + byte_length = 4 +} + +locals { + random_suffix = random_id.random_suffix.b64_url +} + module "user_one" { source = "../../modules/pagerduty-user" - name = "pagerduty-schedule-example-user-one" - email_address = "pagerduty-schedule-example-user-one@honestbank.com" + name = "pagerduty-schedule-example-user-one-${local.random_suffix}" + email_address = "pagerduty-schedule-example-user-one-${local.random_suffix}@honestbank.com" } module "user_two" { source = "../../modules/pagerduty-user" - name = "pagerduty-schedule-example-user-two" - email_address = "pagerduty-schedule-example-user-two@honestbank.com" + name = "pagerduty-schedule-example-user-two-${local.random_suffix}" + email_address = "pagerduty-schedule-example-user-two-${local.random_suffix}@honestbank.com" } module "schedule" { source = "../../modules/pagerduty-schedule" - name = var.name + name = "${var.name}-${local.random_suffix}" description = "Example schedule" # 604,800 seconds = 1 week (7 days) diff --git a/examples/pagerduty-service-integration-email/README.md b/examples/pagerduty-service-integration-email/README.md new file mode 100644 index 0000000..70add50 --- /dev/null +++ b/examples/pagerduty-service-integration-email/README.md @@ -0,0 +1,52 @@ +# PagerDuty Service Integrations - Email Example + +This example creates a basic PagerDuty service integration of type `generic_email_inbound_integration`. + + +## Requirements + +| Name | Version | +|------|---------| +| [pagerduty](#requirement\_pagerduty) | >= 2.7 | + +## Providers + +| Name | Version | +|------|---------| +| [random](#provider\_random) | n/a | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [engineering\_user\_one](#module\_engineering\_user\_one) | ../../modules/pagerduty-user | n/a | +| [escalation\_policy](#module\_escalation\_policy) | ../../modules/pagerduty-escalation-policy | n/a | +| [level\_one\_engineering\_schedule](#module\_level\_one\_engineering\_schedule) | ../../modules/pagerduty-schedule | n/a | +| [mock\_team](#module\_mock\_team) | ../../modules/pagerduty-team | n/a | +| [service](#module\_service) | ../../modules/pagerduty-service | n/a | +| [service\_email\_integration](#module\_service\_email\_integration) | ../../modules/pagerduty-service-integrations-email | n/a | + +## Resources + +| Name | Type | +|------|------| +| [random_id.random_suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [email\_filter](#input\_email\_filter) | email\_filter = {
from\_email\_regex : "The regex used to match the 'from' field in the inbound email. Should be a valid regex or null"
subject\_regex : "The regex used to match the 'subject' field in the inbound email. Should be a valid regex or null"
} |
object({
from_email_regex = string
subject_regex = string
})
|
{
"from_email_regex": null,
"subject_regex": null
}
| no | +| [integration\_email](#input\_integration\_email) | This is the unique fully-qualified email address used for routing emails to this integration for processing. | `string` | n/a | yes | +| [name](#input\_name) | The name of the service integration. | `string` | n/a | yes | +| [pagerduty\_token](#input\_pagerduty\_token) | PagerDuty API token. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [id](#output\_id) | The ID of the service integration. | +| [integration\_email](#output\_integration\_email) | This is the unique fully-qualified email address used for routing emails to this integration for processing. | +| [integration\_id](#output\_integration\_id) | This is the unique key used to route events to this integration when received via the PagerDuty Events API. | +| [service\_id](#output\_service\_id) | n/a | + diff --git a/examples/pagerduty-service-integration-email/inputs.tf b/examples/pagerduty-service-integration-email/inputs.tf new file mode 100644 index 0000000..c2e7b1b --- /dev/null +++ b/examples/pagerduty-service-integration-email/inputs.tf @@ -0,0 +1,34 @@ +variable "name" { + description = "The name of the service integration." + type = string +} + +variable "integration_email" { + description = "This is the unique fully-qualified email address used for routing emails to this integration for processing." + type = string +} + +variable "email_filter" { + type = object({ + from_email_regex = string + subject_regex = string + }) + + default = { + from_email_regex = null + subject_regex = null + } + + description = <<-EOT + email_filter = { + from_email_regex : "The regex used to match the 'from' field in the inbound email. Should be a valid regex or null" + subject_regex : "The regex used to match the 'subject' field in the inbound email. Should be a valid regex or null" + } + EOT +} + +variable "pagerduty_token" { + type = string + description = "PagerDuty API token." + sensitive = true +} diff --git a/examples/pagerduty-service-integration-email/main.tf b/examples/pagerduty-service-integration-email/main.tf new file mode 100644 index 0000000..958719b --- /dev/null +++ b/examples/pagerduty-service-integration-email/main.tf @@ -0,0 +1,69 @@ +resource "random_id" "random_suffix" { + byte_length = 4 +} + +locals { + schedule_suffix = "schedule-suffix-${random_id.random_suffix.b64_url}" + random_suffix = random_id.random_suffix.b64_url +} + +module "engineering_user_one" { + source = "../../modules/pagerduty-user" + name = "user-${local.random_suffix}" + email_address = "user-${local.random_suffix}@honestbank.com" +} + +module "mock_team" { + source = "../../modules/pagerduty-team" + name = "Team-${var.name}-${local.random_suffix}" + description = "Created by terratest" +} + +module "level_one_engineering_schedule" { + source = "../../modules/pagerduty-schedule" + + description = "level one engineering schedule" + name = "level one engineering schedule-${local.schedule_suffix}" + + # 604,800 seconds = 1 week (7 days) + rotation_turn_length_seconds = 604800 + + # Wednesday 5pm GMT+7 rotation handover + start_datetime = "2022-07-27T17:00:00+07:00" + time_zone = "Asia/Bangkok" + + user_ids = [module.engineering_user_one.id] +} + +module "escalation_policy" { + source = "../../modules/pagerduty-escalation-policy" + + name = "${var.name}-${local.random_suffix}" + description = "example escalation policy" + + escalation_delay_in_minutes = 60 + + escalation_levels = [[module.level_one_engineering_schedule.id]] + + teams_id = [ + module.mock_team.id, + ] +} + +module "service" { + source = "../../modules/pagerduty-service" + name = "example-service-${random_id.random_suffix.b64_url}" + description = "example service" + + acknowledgement_timeout = 0 + escalation_policy_id = module.escalation_policy.id + incident_urgency_rule_constant_urgency_value = "high" +} + +module "service_email_integration" { + source = "../../modules/pagerduty-service-integration-email" + integration_email = var.integration_email + name = var.name + service_id = module.service.id + email_filter = var.email_filter +} diff --git a/examples/pagerduty-service-integration-email/outputs.tf b/examples/pagerduty-service-integration-email/outputs.tf new file mode 100644 index 0000000..2834af3 --- /dev/null +++ b/examples/pagerduty-service-integration-email/outputs.tf @@ -0,0 +1,19 @@ +output "id" { + description = "The ID of the service integration." + value = module.service_email_integration.id +} + +output "integration_id" { + description = "This is the unique key used to route events to this integration when received via the PagerDuty Events API." + value = module.service_email_integration.id +} + +output "service_id" { + description = "This is the ID of the service to which this email integration routes alerts to." + value = module.service.id +} + +output "integration_email" { + description = "This is the unique fully-qualified email address used for routing emails to this integration for processing." + value = module.service_email_integration.integration_email +} diff --git a/examples/pagerduty-service-integration-email/providers.tf b/examples/pagerduty-service-integration-email/providers.tf new file mode 100644 index 0000000..8e9cfe1 --- /dev/null +++ b/examples/pagerduty-service-integration-email/providers.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + pagerduty = { + source = "pagerduty/pagerduty" + version = ">= 2.7" + } + } +} + +provider "pagerduty" { + token = var.pagerduty_token +} diff --git a/examples/pagerduty-service/README.md b/examples/pagerduty-service/README.md index 6faf57f..558be5b 100644 --- a/examples/pagerduty-service/README.md +++ b/examples/pagerduty-service/README.md @@ -5,11 +5,15 @@ This example creates a PagerDuty Service (along with the required escalation pol ## Requirements -No requirements. +| Name | Version | +|------|---------| +| [pagerduty](#requirement\_pagerduty) | >= 2.7 | ## Providers -No providers. +| Name | Version | +|------|---------| +| [random](#provider\_random) | n/a | ## Modules @@ -20,7 +24,9 @@ No providers. ## Resources -No resources. +| Name | Type | +|------|------| +| [random_id.random_suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | ## Inputs diff --git a/examples/pagerduty-service/inputs.tf b/examples/pagerduty-service/inputs.tf index 7c851e8..d168d05 100644 --- a/examples/pagerduty-service/inputs.tf +++ b/examples/pagerduty-service/inputs.tf @@ -13,6 +13,7 @@ variable "description" { variable "pagerduty_token" { type = string description = "PagerDuty API token." + sensitive = true } variable "schedule_suffix" { diff --git a/examples/pagerduty-service/main.tf b/examples/pagerduty-service/main.tf index 9b8844c..b22d7c0 100644 --- a/examples/pagerduty-service/main.tf +++ b/examples/pagerduty-service/main.tf @@ -1,3 +1,7 @@ +resource "random_id" "random_suffix" { + byte_length = 4 +} + module "escalation_policy" { source = "../pagerduty-escalation-policy" @@ -7,7 +11,7 @@ module "escalation_policy" { module "service" { source = "../../modules/pagerduty-service" - name = var.name + name = "${var.name}-${random_id.random_suffix.b64_url}" description = var.description acknowledgement_timeout = 0 diff --git a/examples/pagerduty-service/providers.tf b/examples/pagerduty-service/providers.tf new file mode 100644 index 0000000..8e9cfe1 --- /dev/null +++ b/examples/pagerduty-service/providers.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + pagerduty = { + source = "pagerduty/pagerduty" + version = ">= 2.7" + } + } +} + +provider "pagerduty" { + token = var.pagerduty_token +} diff --git a/examples/pagerduty-stakeholder/inputs.tf b/examples/pagerduty-stakeholder/inputs.tf index deaf20f..948d53d 100644 --- a/examples/pagerduty-stakeholder/inputs.tf +++ b/examples/pagerduty-stakeholder/inputs.tf @@ -1,5 +1,5 @@ variable "email_address" { - description = "The email adddress of the user." + description = "The email address of the user." type = string } @@ -11,6 +11,7 @@ variable "name" { variable "pagerduty_token" { type = string description = "PagerDuty API token." + sensitive = true } variable "role" { diff --git a/examples/pagerduty-stakeholder/main.tf b/examples/pagerduty-stakeholder/main.tf index 8ae4336..cbcb7ec 100644 --- a/examples/pagerduty-stakeholder/main.tf +++ b/examples/pagerduty-stakeholder/main.tf @@ -1,7 +1,15 @@ +resource "random_id" "random_suffix" { + byte_length = 4 +} + +locals { + random_suffix = random_id.random_suffix.b64_url +} + module "stakeholder" { source = "../../modules/pagerduty-user" - name = var.name + name = "${var.name}-${local.random_suffix}" email_address = var.email_address role = var.role } diff --git a/examples/pagerduty-team/main.tf b/examples/pagerduty-team/main.tf index 30763c7..d3f3448 100644 --- a/examples/pagerduty-team/main.tf +++ b/examples/pagerduty-team/main.tf @@ -1,6 +1,14 @@ +resource "random_id" "random_suffix" { + byte_length = 4 +} + +locals { + random_suffix = random_id.random_suffix.b64_url +} + module "team" { source = "../../modules/pagerduty-team" - name = var.name + name = "${var.name}-${local.random_suffix}" description = var.description responder_user_ids = toset([for user_id, role in var.team_members : user_id if role == "responder"]) diff --git a/examples/pagerduty-user/README.md b/examples/pagerduty-user/README.md index 2dad8c8..34f458f 100644 --- a/examples/pagerduty-user/README.md +++ b/examples/pagerduty-user/README.md @@ -11,7 +11,9 @@ This example creates a basic PagerDuty User. ## Providers -No providers. +| Name | Version | +|------|---------| +| [random](#provider\_random) | n/a | ## Modules @@ -21,7 +23,9 @@ No providers. ## Resources -No resources. +| Name | Type | +|------|------| +| [random_id.random_suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | ## Inputs @@ -36,6 +40,7 @@ No resources. | Name | Description | |------|-------------| +| [generated\_user\_name\_suffix](#output\_generated\_user\_name\_suffix) | The generated suffix for the user's name to avoid conflicting resource creation during testing. | | [html\_url](#output\_html\_url) | URL at which the entity is uniquely displayed in the Web app. | | [id](#output\_id) | The ID of the user. | | [invitation\_sent](#output\_invitation\_sent) | If true, the user has an outstanding invitation. | diff --git a/examples/pagerduty-user/inputs.tf b/examples/pagerduty-user/inputs.tf index 52f2c7e..d75ff28 100644 --- a/examples/pagerduty-user/inputs.tf +++ b/examples/pagerduty-user/inputs.tf @@ -11,6 +11,7 @@ variable "name" { variable "pagerduty_token" { type = string description = "PagerDuty API token." + sensitive = true } variable "role" { diff --git a/examples/pagerduty-user/main.tf b/examples/pagerduty-user/main.tf index 151d94c..78aae98 100644 --- a/examples/pagerduty-user/main.tf +++ b/examples/pagerduty-user/main.tf @@ -1,7 +1,15 @@ +resource "random_id" "random_suffix" { + byte_length = 4 +} + +locals { + random_suffix = random_id.random_suffix.b64_url +} + module "user" { source = "../../modules/pagerduty-user" - name = var.name + name = "${var.name}-${local.random_suffix}" email_address = var.email_address role = var.role } diff --git a/examples/pagerduty-user/outputs.tf b/examples/pagerduty-user/outputs.tf index fba8807..882e022 100644 --- a/examples/pagerduty-user/outputs.tf +++ b/examples/pagerduty-user/outputs.tf @@ -12,3 +12,8 @@ output "invitation_sent" { description = "If true, the user has an outstanding invitation." value = module.user.invitation_sent } + +output "generated_user_name_suffix" { + value = local.random_suffix + description = "The generated suffix for the user's name to avoid conflicting resource creation during testing." +} diff --git a/inputs.tf b/inputs.tf index 5debb24..ca3295f 100644 --- a/inputs.tf +++ b/inputs.tf @@ -1,4 +1,5 @@ variable "pagerduty_token" { type = string description = "PagerDuty API token." + sensitive = true } diff --git a/modules/honest-two-level-schedule/inputs.tf b/modules/honest-two-level-schedule/inputs.tf index fcb5651..8983a5c 100644 --- a/modules/honest-two-level-schedule/inputs.tf +++ b/modules/honest-two-level-schedule/inputs.tf @@ -11,6 +11,7 @@ variable "name" { variable "rotation_turn_length_seconds" { description = "The time in seconds each individual is on-call for." type = number + validation { condition = var.rotation_turn_length_seconds > 0 error_message = "Rotation turn length must be greater than 0." @@ -32,6 +33,7 @@ variable "time_zone" { variable "user_ids" { description = "An ordered list of PagerDuty User IDs to add to the schedule. The individual's order in the schedule depends on the order of this list." type = list(string) + validation { # This validation does not catch repeating user IDs with different cases (uppercase/lowercase, etc) # So duplicate values of "A" and "a" will be allowed. @@ -40,6 +42,7 @@ variable "user_ids" { length(distinct(var.user_ids)) == length(var.user_ids), length(distinct([for u in var.user_ids : lower(u)])) == length(var.user_ids), ]) + error_message = "At least two unique responders are required to build a two-level schedule. Repeated values are not allowed." } } diff --git a/modules/pagerduty-service-integration-email/README.md b/modules/pagerduty-service-integration-email/README.md new file mode 100644 index 0000000..958e75f --- /dev/null +++ b/modules/pagerduty-service-integration-email/README.md @@ -0,0 +1,45 @@ +## PagerDuty Email Service Integration + +A `pagerduty-service-integration-email` is a [`pagerduty_service_integration`](https://registry.terraform.io/providers/PagerDuty/pagerduty/latest/docs/resources/service_integration) of the type `generic_email_inbound_integration`. Use this module to trigger incidents via email messages. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.3.0 | +| [pagerduty](#requirement\_pagerduty) | >= 2.7 | + +## Providers + +| Name | Version | +|------|---------| +| [pagerduty](#provider\_pagerduty) | >= 2.7 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [pagerduty_service_integration.generic_email_inbound_integration](https://registry.terraform.io/providers/pagerduty/pagerduty/latest/docs/resources/service_integration) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [email\_filter](#input\_email\_filter) | email\_filter = {
from\_email\_regex : "The regex used to match the 'from' field in the inbound email. Should be a valid regex or null"
subject\_regex : "The regex used to match the 'subject' field in the inbound email. Should be a valid regex or null"
} |
object({
from_email_regex = string
subject_regex = string
})
|
{
"from_email_regex": null,
"subject_regex": null
}
| no | +| [email\_incident\_creation](#input\_email\_incident\_creation) | Behaviour of Email Management feature (explained in PD docs)[https://support.pagerduty.com/docs/email-management-filters-and-rules#control-when-a-new-incident-or-alert-is-triggered]. Can be on\_new\_email, on\_new\_email\_subject, only\_if\_no\_open\_incidents or use\_rules. | `string` | `"use_rules"` | no | +| [integration\_email](#input\_integration\_email) | This is the unique fully-qualified email address used for routing emails to this integration for processing. | `string` | n/a | yes | +| [name](#input\_name) | The name of the service integration. | `string` | n/a | yes | +| [service\_id](#input\_service\_id) | The ID of the service the integration should belong to. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [id](#output\_id) | The ID of the service integration. | +| [integration\_email](#output\_integration\_email) | This is the unique fully-qualified email address used for routing emails to this integration for processing. | + diff --git a/modules/pagerduty-service-integration-email/generic-email-inbound-integration.tf b/modules/pagerduty-service-integration-email/generic-email-inbound-integration.tf new file mode 100644 index 0000000..934a531 --- /dev/null +++ b/modules/pagerduty-service-integration-email/generic-email-inbound-integration.tf @@ -0,0 +1,26 @@ +resource "pagerduty_service_integration" "generic_email_inbound_integration" { + name = var.name + service = var.service_id + integration_email = var.integration_email + # In the initial implementation of this module, we hardcode this value because + # we expose configuration for only 1 email-filter object, + # Further Enhancement: Expose this as a variable when we start exposing email filters + # as a collection of "email_filter" objects. + email_filter_mode = "and-rules-email" + # This module will only encapsulate integrations of the type `generic_email_inbound_integration` + type = "generic_email_inbound_integration" + email_incident_creation = var.email_incident_creation + + email_filter { + # We chose not to expose the body filter functionality in the initial implementation of this module, and instead hardcode + # the best fit default here. + # Further enhancement: Expose this attribute as a variable. + body_mode = "always" + + from_email_mode = var.email_filter.from_email_regex == null ? "always" : "match" + from_email_regex = var.email_filter.from_email_regex + + subject_mode = var.email_filter.subject_regex == null ? "always" : "match" + subject_regex = var.email_filter.subject_regex + } +} diff --git a/modules/pagerduty-service-integration-email/inputs.tf b/modules/pagerduty-service-integration-email/inputs.tf new file mode 100644 index 0000000..6fccca8 --- /dev/null +++ b/modules/pagerduty-service-integration-email/inputs.tf @@ -0,0 +1,43 @@ +variable "name" { + description = "The name of the service integration." + type = string +} + +variable "service_id" { + description = "The ID of the service the integration should belong to." + type = string +} + +variable "integration_email" { + description = "This is the unique fully-qualified email address used for routing emails to this integration for processing." + type = string +} + +variable "email_incident_creation" { + default = "use_rules" + description = "Behaviour of Email Management feature (explained in PD docs)[https://support.pagerduty.com/docs/email-management-filters-and-rules#control-when-a-new-incident-or-alert-is-triggered]. Can be on_new_email, on_new_email_subject, only_if_no_open_incidents or use_rules." + + validation { + condition = contains(["on_new_email", "on_new_email_subject", "only_if_no_open_incidents", "use_rules"], var.email_incident_creation) + error_message = "Invalid value passed to email_incident_creation. Must be one of on_new_email, on_new_email_subject, only_if_no_open_incidents or use_rules." + } +} + +variable "email_filter" { + type = object({ + from_email_regex = string + subject_regex = string + }) + + default = { + from_email_regex = null + subject_regex = null + } + + description = <<-EOT + email_filter = { + from_email_regex : "The regex used to match the 'from' field in the inbound email. Should be a valid regex or null" + subject_regex : "The regex used to match the 'subject' field in the inbound email. Should be a valid regex or null" + } + EOT +} diff --git a/modules/pagerduty-service-integration-email/outputs.tf b/modules/pagerduty-service-integration-email/outputs.tf new file mode 100644 index 0000000..3f867c6 --- /dev/null +++ b/modules/pagerduty-service-integration-email/outputs.tf @@ -0,0 +1,9 @@ +output "id" { + description = "The ID of the service integration." + value = pagerduty_service_integration.generic_email_inbound_integration.id +} + +output "integration_email" { + description = "This is the unique fully-qualified email address used for routing emails to this integration for processing." + value = pagerduty_service_integration.generic_email_inbound_integration.integration_email +} diff --git a/modules/pagerduty-service-integration-email/providers.tf b/modules/pagerduty-service-integration-email/providers.tf new file mode 100644 index 0000000..97fb805 --- /dev/null +++ b/modules/pagerduty-service-integration-email/providers.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.3.0" + + required_providers { + pagerduty = { + source = "pagerduty/pagerduty" + version = ">= 2.7" + } + } +} diff --git a/modules/pagerduty-team/README.md b/modules/pagerduty-team/README.md index dfe02de..8fb50c2 100644 --- a/modules/pagerduty-team/README.md +++ b/modules/pagerduty-team/README.md @@ -46,4 +46,4 @@ No modules. |------|-------------| | [html\_url](#output\_html\_url) | URL at which the entity is uniquely displayed in the PagerDuty web UI. | | [id](#output\_id) | The ID of the team. | - + \ No newline at end of file diff --git a/modules/pagerduty-user/inputs.tf b/modules/pagerduty-user/inputs.tf index 0a77a98..12564f3 100644 --- a/modules/pagerduty-user/inputs.tf +++ b/modules/pagerduty-user/inputs.tf @@ -12,6 +12,7 @@ variable "role" { default = "user" description = "The user's role in PagerDuty. Can be `admin`, `limited_user`, `read_only_user` (Full Stakeholder), or `user`." type = string + validation { condition = anytrue([ var.role == "admin", @@ -19,6 +20,7 @@ variable "role" { var.role == "read_only_user", var.role == "user", ]) + error_message = "role must be one of `admin`, `limited_user`, `read_only_user`, or `user`." } } diff --git a/test/honest_two_level_schedule_test.go b/test/honest_two_level_schedule_test.go index 2297fac..504cc55 100644 --- a/test/honest_two_level_schedule_test.go +++ b/test/honest_two_level_schedule_test.go @@ -15,6 +15,9 @@ import ( func TestHonestTwoLevelSchedule(t *testing.T) { workingDir := test_structure.CopyTerraformFolderToTemp(t, "..", "examples/honest-two-level-schedule") + defer test_structure.RunTestStage(t, "destroy_two_level_schedule", func() { + destroyTwoLevelSchedule(t, workingDir) + }) levelOneScheduleId := "" levelTwoScheduleId := "" @@ -28,10 +31,6 @@ func TestHonestTwoLevelSchedule(t *testing.T) { levelOneScheduleId, levelTwoScheduleId, teamID = createTwoLevelScheduleWithUserCount(t, workingDir, 2, teamName) }) - defer test_structure.RunTestStage(t, "destroy_two_level_schedule", func() { - destroyTwoLevelSchedule(t, workingDir) - }) - test_structure.RunTestStage(t, "verify_two_level_schedule", func() { verifyTwoLevelScheduleWithUserCount(t, levelOneScheduleId, levelTwoScheduleId, 2) verifyTeamSetInSchedule(t, levelOneScheduleId, levelTwoScheduleId, teamID) diff --git a/test/pagerduty_business_service_test.go b/test/pagerduty_business_service_test.go index a067777..da45502 100644 --- a/test/pagerduty_business_service_test.go +++ b/test/pagerduty_business_service_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "strings" "testing" "github.com/PagerDuty/go-pagerduty" @@ -12,12 +13,15 @@ import ( "github.com/stretchr/testify/assert" ) - const businessServiceExampleDir = "./examples/pagerduty-business-service" const businessServiceDescriptionMock = "Created by Terratest" const businessServicePointOfContactMock = "Terratest - Contact the engineers" + func TestPagerdutyBusinessService(t *testing.T) { workingDir := test_structure.CopyTerraformFolderToTemp(t, "..", businessServiceExampleDir) + defer test_structure.RunTestStage(t, "destroy_business_service", func() { + destroyPagerdutyBusinessService(t, workingDir) + }) businessServiceID := "" runID := generateRunId() @@ -26,9 +30,6 @@ func TestPagerdutyBusinessService(t *testing.T) { test_structure.RunTestStage(t, "create_business_service", func() { businessServiceID = createPagerdutyBusinessService(t, workingDir, businessServiceName) }) - defer test_structure.RunTestStage(t, "destroy_business_service", func() { - destroyPagerdutyBusinessService(t, workingDir) - }) test_structure.RunTestStage(t, "verify_business_service", func() { verifyPagerdutyBusinessService(t, businessServiceID, businessServiceName) @@ -39,8 +40,8 @@ func createPagerdutyBusinessService(t *testing.T, workingDir string, businessSer options := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ TerraformDir: workingDir, Vars: map[string]interface{}{ - "name": businessServiceName, - "description": businessServiceDescriptionMock, + "name": businessServiceName, + "description": businessServiceDescriptionMock, "point_of_contact": businessServicePointOfContactMock, }, }) @@ -50,7 +51,12 @@ func createPagerdutyBusinessService(t *testing.T, workingDir string, businessSer } func destroyPagerdutyBusinessService(t *testing.T, workingDir string) { - terraform.Destroy(t, test_structure.LoadTerraformOptions(t, workingDir)) + _, err := terraform.DestroyE(t, test_structure.LoadTerraformOptions(t, workingDir)) + // have to re-do destroy sometimes because of race conditions (i.e. try to delete team while it still has associations) + // In the retry the team will get deleted properly because the associations have been deleted in previous run + if err != nil { + terraform.Destroy(t, test_structure.LoadTerraformOptions(t, workingDir)) + } } func verifyPagerdutyBusinessService(t *testing.T, serviceID string, expectedBussinessServiceName string) { @@ -59,6 +65,6 @@ func verifyPagerdutyBusinessService(t *testing.T, serviceID string, expectedBuss if serviceErr != nil { log.Println("error getting service: ", serviceErr) } - - assert.Equal(t, expectedBussinessServiceName, businessService.Name) + fmt.Printf("comparing strings %s with %s", expectedBussinessServiceName, businessService.Name) + assert.True(t, strings.HasPrefix(businessService.Name, expectedBussinessServiceName)) } diff --git a/test/pagerduty_escalation_policy_test.go b/test/pagerduty_escalation_policy_test.go index 4ca167d..a875f30 100644 --- a/test/pagerduty_escalation_policy_test.go +++ b/test/pagerduty_escalation_policy_test.go @@ -18,6 +18,9 @@ func TestPagerdutyEscalationPolicy(t *testing.T) { workingDir := test_structure.CopyTerraformFolderToTemp(t, "..", "examples/pagerduty-escalation-policy") workingDir = "../examples/pagerduty-escalation-policy" + defer test_structure.RunTestStage(t, "destroy_escalation_policy", func() { + destroyEscalationPolicy(t, workingDir) + }) log.Println("working dir for this test is: ", workingDir) @@ -28,10 +31,6 @@ func TestPagerdutyEscalationPolicy(t *testing.T) { log.Println("created escalation policy ID: ", escalationPolicyId) }) - defer test_structure.RunTestStage(t, "destroy_escalation_policy", func() { - destroyEscalationPolicy(t, workingDir) - }) - test_structure.RunTestStage(t, "verify_escalation_policy", func() { verifyEscalationPolicy(t, escalationPolicyId) }) @@ -54,7 +53,12 @@ func createEscalationPolicy(t *testing.T, workingDir string, runID string) strin } func destroyEscalationPolicy(t *testing.T, workingDir string) { - terraform.Destroy(t, test_structure.LoadTerraformOptions(t, workingDir)) + _, err := terraform.DestroyE(t, test_structure.LoadTerraformOptions(t, workingDir)) + // have to re-do destroy sometimes because of race conditions (i.e. try to delete team while it still has associations) + // In the retry the team will get deleted properly because the associations have been deleted in previous run + if err != nil { + terraform.Destroy(t, test_structure.LoadTerraformOptions(t, workingDir)) + } } func verifyEscalationPolicy(t *testing.T, escalationPolicyId string) { diff --git a/test/pagerduty_schedule_test.go b/test/pagerduty_schedule_test.go index d48c46e..6d2ae5e 100644 --- a/test/pagerduty_schedule_test.go +++ b/test/pagerduty_schedule_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "log" + "strings" "testing" "time" @@ -27,16 +28,15 @@ func TestPagerdutySchedule(t *testing.T) { createdScheduleId := "" userOneId := "" userTwoId := "" + defer test_structure.RunTestStage(t, "destroy_schedule", func() { + destroySchedule(t, scheduleWorkingDir) + }) test_structure.RunTestStage(t, "create_schedule", func() { createdScheduleId, userOneId, userTwoId = createSchedule(t, scheduleWorkingDir, scheduleName) assert.NotNilf(t, createdScheduleId, "created schedule ID cannot be nil") }) - defer test_structure.RunTestStage(t, "destroy_schedule", func() { - destroySchedule(t, scheduleWorkingDir) - }) - test_structure.RunTestStage(t, "verify_schedule", func() { log.Println("🔎🔎🔎 About to verify schedule ID: ", createdScheduleId) verifySchedule(t, createdScheduleId, userOneId, userTwoId, scheduleName) @@ -88,7 +88,7 @@ func verifySchedule(t *testing.T, scheduleId string, userOneId string, userTwoId } log.Println("Retrieved schedule from PagerDuty SDK: ", schedule) - assert.Equal(t, expectedScheduleName, schedule.Name) + assert.True(t, strings.HasPrefix(schedule.Name, expectedScheduleName)) assert.Equal(t, "Asia/Bangkok", schedule.TimeZone) assert.Equalf(t, 1, len(schedule.ScheduleLayers), "there must be one schedule layer created") diff --git a/test/pagerduty_service_email_integration_test.go b/test/pagerduty_service_email_integration_test.go new file mode 100644 index 0000000..a980043 --- /dev/null +++ b/test/pagerduty_service_email_integration_test.go @@ -0,0 +1,123 @@ +package test + +import ( + "context" + "fmt" + "github.com/stretchr/testify/assert" + "log" + "testing" + + "github.com/PagerDuty/go-pagerduty" + "github.com/gruntwork-io/terratest/modules/terraform" + test_structure "github.com/gruntwork-io/terratest/modules/test-structure" +) + +func TestPagerdutyServiceEmailIntegration(t *testing.T) { + workingDir := test_structure.CopyTerraformFolderToTemp(t, "..", "examples/pagerduty-service-integration-email") + + // Temporary override for local development and testing + workingDir = "../examples/pagerduty-service-integration-email" + serviceId := "" + integrationId := "" + emailFilter := EmailFilter{} + runID := generateRunId() + integrationEmail := fmt.Sprintf("example%s@honest-test.pagerduty.com", runID) + integrationName := fmt.Sprintf("example-pagerduty-service-integrations-email-%s", runID) + defer test_structure.RunTestStage(t, "destroy_service_integration_email", func() { + destroyPagerdutyServiceEmailIntegration(t, workingDir) + }) + + test_structure.RunTestStage(t, "create_service_integration_email", func() { + var err error + serviceId, integrationId, emailFilter, err = createPagerdutyServiceIntegration(t, workingDir, integrationEmail, integrationName) + if err != nil { + destroyPagerdutyBusinessService(t, workingDir) + t.FailNow() + } + }) + + test_structure.RunTestStage(t, "verify_service_email_integration", func() { + verifyPagerdutyServiceEmailIntegration(t, serviceId, integrationId, integrationName, integrationEmail, emailFilter) + }) +} + +func createPagerdutyServiceIntegration(t *testing.T, workingDir string, integrationEmail string, integrationName string) (string, string, EmailFilter, error) { + subjectRegex := "(CRITICAL*)" + fromEmailRegex := "(@foo.test*)" + options := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ + TerraformDir: workingDir, + Vars: map[string]interface{}{ + "name": integrationName, + "integration_email": integrationEmail, + "email_filter": map[string]interface{}{ + "subject_mode": "match", + "subject_regex": subjectRegex, + "body_mode": "always", + "body_regex": nil, + "from_email_mode": "match", + "from_email_regex": fromEmailRegex, + }, + }, + }) + test_structure.SaveTerraformOptions(t, workingDir, options) + _, err := terraform.InitAndApplyE(t, options) + if err != nil { + return "", "", EmailFilter{}, err + } + serviceId, err := terraform.OutputE(t, options, "service_id") + integrationId, err := terraform.OutputE(t, options, "integration_id") + emailFilter := EmailFilter{ + SubjectMode: pagerduty.EmailFilterRuleModeMatch, + SubjectRegex: subjectRegex, + BodyMode: pagerduty.EmailFilterRuleModeAlways, + FromEmailMode: pagerduty.EmailFilterRuleModeMatch, + FromEmailRegex: fromEmailRegex, + } + return serviceId, integrationId, emailFilter, err +} + +type EmailFilter struct { + SubjectMode pagerduty.IntegrationEmailFilterRuleMode + SubjectRegex string + BodyMode pagerduty.IntegrationEmailFilterRuleMode + FromEmailMode pagerduty.IntegrationEmailFilterRuleMode + FromEmailRegex string +} + +func destroyPagerdutyServiceEmailIntegration(t *testing.T, workingDir string) { + _, err := terraform.DestroyE(t, test_structure.LoadTerraformOptions(t, workingDir)) + // have to re-do destroy sometimes because of race conditions (i.e. try to delete team while it still has associations) + // In the retry the team will get deleted properly because the associations have been deleted in previous run + if err != nil { + terraform.Destroy(t, test_structure.LoadTerraformOptions(t, workingDir)) + } +} + +func verifyPagerdutyServiceEmailIntegration( + t *testing.T, + serviceId string, + integrationId string, + expectedIntegrationName string, + expectedIntegrationEmail string, + expectedEmailFilter EmailFilter, +) { + client := pagerduty.NewClient(loadPagerdutyToken(t)) + options := pagerduty.GetIntegrationOptions{} + integration, serviceErr := client.GetIntegrationWithContext(context.Background(), serviceId, integrationId, options) + if serviceErr != nil { + log.Println("error getting service integration: ", serviceErr) + } + assert.Equal(t, integrationId, integration.ID) + assert.Equal(t, expectedIntegrationEmail, integration.IntegrationEmail) + assert.Equal(t, serviceId, integration.Service.ID) + emailFilter := integration.EmailFilters[0] + realEmailFilter := EmailFilter{ + SubjectMode: emailFilter.SubjectMode, + SubjectRegex: *emailFilter.SubjectRegex, + BodyMode: emailFilter.BodyMode, + FromEmailMode: emailFilter.FromEmailMode, + FromEmailRegex: *emailFilter.FromEmailRegex, + } + assert.Equal(t, expectedEmailFilter, realEmailFilter) + assert.Equal(t, expectedIntegrationName, integration.Name) +} diff --git a/test/pagerduty_service_test.go b/test/pagerduty_service_test.go index 66165b5..bd57871 100644 --- a/test/pagerduty_service_test.go +++ b/test/pagerduty_service_test.go @@ -16,6 +16,9 @@ func TestPagerdutyService(t *testing.T) { // Temporary override for local development and testing workingDir = "../examples/pagerduty-service" + defer test_structure.RunTestStage(t, "destroy_service", func() { + destroyPagerdutyService(t, workingDir) + }) serviceId := "" serviceName := "" @@ -23,9 +26,6 @@ func TestPagerdutyService(t *testing.T) { test_structure.RunTestStage(t, "create_service", func() { serviceId, serviceName = createPagerdutyService(t, workingDir, runID) }) - defer test_structure.RunTestStage(t, "destroy_service", func() { - destroyPagerdutyService(t, workingDir) - }) test_structure.RunTestStage(t, "verify_service", func() { verifyPagerdutyService(t, serviceId, serviceName) @@ -45,7 +45,12 @@ func createPagerdutyService(t *testing.T, workingDir string, runID string) (stri } func destroyPagerdutyService(t *testing.T, workingDir string) { - terraform.Destroy(t, test_structure.LoadTerraformOptions(t, workingDir)) + _, err := terraform.DestroyE(t, test_structure.LoadTerraformOptions(t, workingDir)) + // have to re-do destroy sometimes because of race conditions (i.e. try to delete team while it still has associations) + // In the retry the team will get deleted properly because the associations have been deleted in previous run + if err != nil { + terraform.Destroy(t, test_structure.LoadTerraformOptions(t, workingDir)) + } } func verifyPagerdutyService(t *testing.T, serviceId string, serviceName string) { diff --git a/test/pagerduty_team_test.go b/test/pagerduty_team_test.go index 1831bff..f5abb13 100644 --- a/test/pagerduty_team_test.go +++ b/test/pagerduty_team_test.go @@ -11,27 +11,26 @@ import ( "github.com/stretchr/testify/assert" ) - -const roleManager = "manager" -const roleEngineer = "engineer" - func TestPagerdutyTeam(t *testing.T) { // This will verify the presence of the token as well as return it _ = loadPagerdutyToken(t) workingDir := "../examples/pagerduty-team" + defer test_structure.RunTestStage(t, "destroy_team", func() { + terraform.Destroy(t, test_structure.LoadTerraformOptions(t, workingDir)) + }) // Value will be assigned once team is created, for verification createdTeamId := "" runId := generateRunId() teamName := "terratest team " + runId teamDescription := "This team was created by Terratest from the terraform-pagerduty repo with run ID: " + runId - teamMembers := map[string]string{} + teamMembers := map[string]string{} test_structure.RunTestStage(t, "create_team", func() { options := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ TerraformDir: workingDir, Vars: map[string]interface{}{ - "name": teamName, - "description": teamDescription, + "name": teamName, + "description": teamDescription, "team_members": teamMembers, }, }) @@ -58,10 +57,5 @@ func TestPagerdutyTeam(t *testing.T) { } } assert.NotNil(t, targetTeam) - - }) - - test_structure.RunTestStage(t, "destroy_team", func() { - terraform.Destroy(t, test_structure.LoadTerraformOptions(t, workingDir)) }) } diff --git a/test/pagerduty_user_test.go b/test/pagerduty_user_test.go index ef54324..c737022 100644 --- a/test/pagerduty_user_test.go +++ b/test/pagerduty_user_test.go @@ -2,6 +2,7 @@ package test import ( "context" + "fmt" "github.com/PagerDuty/go-pagerduty" http_helper "github.com/gruntwork-io/terratest/modules/http-helper" "github.com/gruntwork-io/terratest/modules/terraform" @@ -17,24 +18,25 @@ func TestPagerdutyUser(t *testing.T) { // Working dirs userWorkingDir := "" + generatedUserNameSuffix := "" + userEmail := "" // For assignment later createdUserId := "" + defer test_structure.RunTestStage(t, "destroy_user", func() { + destroyUser(t, userWorkingDir) + }) test_structure.RunTestStage(t, "create_user", func() { // NOTE: We use `read_only_user` (Stakeholder) because those licenses are available. // We don't always have standard `user` licenses available so the test will fail // when trying to create a user with no licenses available. - userWorkingDir, createdUserId = createUser(t, "read_only_user", pagerdutyApiToken) - }) - - defer test_structure.RunTestStage(t, "destroy_user", func() { - destroyUser(t, pagerdutyApiToken, userWorkingDir) + userWorkingDir, createdUserId, generatedUserNameSuffix, userEmail = createUser(t, "read_only_user", pagerdutyApiToken) }) test_structure.RunTestStage(t, "verify_user", func() { - verifyUser(t, createdUserId, "read_only_user", pagerdutyApiBaseUrl, pagerdutyApiToken) + verifyUser(t, createdUserId, "read_only_user", pagerdutyApiBaseUrl, pagerdutyApiToken, fmt.Sprintf("example-%s", generatedUserNameSuffix), userEmail) }) } @@ -64,8 +66,10 @@ func TestPagerdutyUser(t *testing.T) { // }) //} -func createUser(t *testing.T, role string, pagerdutyApiToken string) (string, string) { +func createUser(t *testing.T, role string, pagerdutyApiToken string) (string, string, string, string) { //workingDir := test_structure.CopyTerraformFolderToTemp(t, "..", "modules/pagerduty-user") + runID := generateRunId() + userEmail := fmt.Sprintf("pagerduty-user-example%s@honestbank.com", runID) workingDir := "../examples/pagerduty-user" log.Println("createUser - workingDir is: ", workingDir) @@ -77,6 +81,7 @@ func createUser(t *testing.T, role string, pagerdutyApiToken string) (string, st TerraformDir: "../examples/pagerduty-user", Vars: map[string]interface{}{ + "email_address": userEmail, "pagerduty_token": pagerdutyApiToken, "role": role, }, @@ -86,16 +91,17 @@ func createUser(t *testing.T, role string, pagerdutyApiToken string) (string, st terraform.InitAndApply(t, createUserTerraformOptions) pagerdutyUserId := terraform.Output(t, createUserTerraformOptions, "id") log.Println("🔢 returning PagerDuty User ID: ", pagerdutyUserId) + generatedUserNameSuffix := terraform.Output(t, createUserTerraformOptions, "generated_user_name_suffix") - return workingDir, pagerdutyUserId + return workingDir, pagerdutyUserId, generatedUserNameSuffix, userEmail } -func destroyUser(t *testing.T, pagerdutyApiToken string, workingDir string) { +func destroyUser(t *testing.T, workingDir string) { terraformOptions := test_structure.LoadTerraformOptions(t, workingDir) terraform.Destroy(t, terraformOptions) } -func verifyUser(t *testing.T, pagerdutyUserId string, role string, pagerdutyApiBaseUrl string, pagerdutyApiToken string) { +func verifyUser(t *testing.T, pagerdutyUserId string, role string, pagerdutyApiBaseUrl string, pagerdutyApiToken string, expectedUserName string, expectedUserEmail string) { pagerdutyApiOptions := http_helper.HttpDoOptions{ Method: "GET", Url: pagerdutyApiBaseUrl + "/users/" + pagerdutyUserId, @@ -111,8 +117,9 @@ func verifyUser(t *testing.T, pagerdutyUserId string, role string, pagerdutyApiB // Should return 200 assert.Equalf(t, 200, status, "incorrect response code, expected 200") - assert.Containsf(t, response, "\"name\":\"example\"", "correct name not found") - assert.Containsf(t, response, "\"email\":\"pagerduty-user-example@honestbank.com\"", "correct email not found") + fmt.Printf("json = %s", response) + assert.Containsf(t, response, fmt.Sprintf("\"name\":\"%s\"", expectedUserName), "correct name not found") + assert.Containsf(t, response, fmt.Sprintf("\"email\":\"%s\"", expectedUserEmail), "correct email not found") client := pagerduty.NewClient(loadPagerdutyToken(t)) getUserOptions := pagerduty.GetUserOptions{}