From 8c4728794020af671ee96a9c9b887ef25252b29a Mon Sep 17 00:00:00 2001
From: Yonatan Koren <me@yonatankoren.com>
Date: Mon, 7 Jun 2021 11:30:10 -0400
Subject: [PATCH] Feat: Add Support for Integration Actions (#20)

* Add support for the opsgenie_integration_action resource in both its own submodule and the config submodule.
* update context.tf in example configs
---
 README.md                                     |  25 ++-
 README.yaml                                   |   4 +
 docs/terraform.md                             |  21 +-
 examples/advanced_features/api_integration.tf |   9 +
 examples/advanced_features/context.tf         | 202 ++++++++++++++++++
 examples/advanced_features/fixtures.tfvars    |   4 +
 .../advanced_features/integration_action.tf   |  26 +++
 examples/advanced_features/main.tf            |   3 +
 examples/advanced_features/outputs.tf         |  24 +++
 examples/advanced_features/team.tf            |   9 +
 examples/advanced_features/variables.tf       |   5 +
 examples/advanced_features/versions.tf        |  10 +
 examples/alert_policy/context.tf              |  67 ++++--
 examples/api_integration/context.tf           |  67 ++++--
 examples/config/context.tf                    |  67 ++++--
 examples/config/outputs.tf                    |   5 +
 .../config/resources/integration_actions.yaml |  13 ++
 examples/escalation/context.tf                |  67 ++++--
 examples/integration_action/context.tf        | 202 ++++++++++++++++++
 examples/integration_action/fixtures.tfvars   |   4 +
 examples/integration_action/main.tf           |  48 +++++
 examples/integration_action/outputs.tf        |   9 +
 examples/integration_action/variables.tf      |   5 +
 examples/integration_action/versions.tf       |  10 +
 examples/notification_policy/context.tf       |  67 ++++--
 examples/team/context.tf                      |  67 ++++--
 examples/team_routing_rule/context.tf         |  67 ++++--
 examples/user/context.tf                      |  67 ++++--
 main.tf                                       |   6 +
 modules/config/integration_actions.tf         | 148 +++++++++++++
 modules/config/main.tf                        |   1 +
 modules/config/outputs.tf                     |   7 +
 modules/integration_action/README.md          |  57 +++++
 modules/integration_action/context.tf         | 202 ++++++++++++++++++
 modules/integration_action/main.tf            | 147 +++++++++++++
 modules/integration_action/outputs.tf         |   4 +
 modules/integration_action/variables.tf       |   4 +
 modules/integration_action/versions.tf        |  10 +
 outputs.tf                                    |   5 +
 test/src/examples_advanced_features.go        |  24 +++
 test/src/examples_integration_action.go       |  24 +++
 variables.tf                                  |   5 +
 42 files changed, 1672 insertions(+), 146 deletions(-)
 create mode 100644 examples/advanced_features/api_integration.tf
 create mode 100644 examples/advanced_features/context.tf
 create mode 100644 examples/advanced_features/fixtures.tfvars
 create mode 100644 examples/advanced_features/integration_action.tf
 create mode 100644 examples/advanced_features/main.tf
 create mode 100644 examples/advanced_features/outputs.tf
 create mode 100644 examples/advanced_features/team.tf
 create mode 100644 examples/advanced_features/variables.tf
 create mode 100644 examples/advanced_features/versions.tf
 create mode 100644 examples/config/resources/integration_actions.yaml
 create mode 100644 examples/integration_action/context.tf
 create mode 100644 examples/integration_action/fixtures.tfvars
 create mode 100644 examples/integration_action/main.tf
 create mode 100644 examples/integration_action/outputs.tf
 create mode 100644 examples/integration_action/variables.tf
 create mode 100644 examples/integration_action/versions.tf
 create mode 100644 modules/config/integration_actions.tf
 create mode 100644 modules/integration_action/README.md
 create mode 100644 modules/integration_action/context.tf
 create mode 100644 modules/integration_action/main.tf
 create mode 100644 modules/integration_action/outputs.tf
 create mode 100644 modules/integration_action/variables.tf
 create mode 100644 modules/integration_action/versions.tf
 create mode 100644 test/src/examples_advanced_features.go
 create mode 100644 test/src/examples_integration_action.go

diff --git a/README.md b/README.md
index 85806b6..eff04a1 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,7 @@ Available modules:
 - [API Integration](modules/api_integration)
 - [Config](modules/config)
 - [Escalation](modules/escalation)
+- [Integration Action](modules/integration_action) (advanced feature — not available to all OpsGenie plans)
 - [Notification Policy](modules/notification_policy)
 - [Team](modules/team)
 - [Team Routing Rule](modules/team_routing_rule)
@@ -78,6 +79,8 @@ Available modules:
 
 **Note:** Root module is just an example that uses all of submodules.
 
+**Note:** See the [Advanced Features Example](examples/advanced_features) for features only available to some OpsGenie plans.
+
 
 ## Security & Compliance [<img src="https://cloudposse.com/wp-content/uploads/2020/11/bridgecrew.svg" width="250" align="right" />](https://bridgecrew.io/)
 
@@ -140,6 +143,7 @@ Submodules examples:
 - [`alert_policy`](examples/alert_policy)
 - [`api_integration`](examples/api_integration)
 - [`escalation`](examples/escalation)
+- [`integration_action`](examples/integration_action) (advanced feature — not available to all OpsGenie plans)
 - [`notification_policy`](examples/notification_policy)
 - [`team`](examples/team)
 - [`team_routing_rule`](examples/team_routing_rule)
@@ -181,16 +185,17 @@ No providers.
 
 | Name | Source | Version |
 |------|--------|---------|
-| <a name="module_alert_policy"></a> [alert\_policy](#module\_alert\_policy) | ./modules/alert_policy |  |
-| <a name="module_api_integration"></a> [api\_integration](#module\_api\_integration) | ./modules/api_integration |  |
-| <a name="module_escalation"></a> [escalation](#module\_escalation) | ./modules/escalation |  |
-| <a name="module_notification_policy"></a> [notification\_policy](#module\_notification\_policy) | ./modules/notification_policy |  |
-| <a name="module_service"></a> [service](#module\_service) | ./modules/service |  |
-| <a name="module_service_incident_rule"></a> [service\_incident\_rule](#module\_service\_incident\_rule) | ./modules/service_incident_rule |  |
-| <a name="module_team"></a> [team](#module\_team) | ./modules/team |  |
-| <a name="module_team_routing_rule"></a> [team\_routing\_rule](#module\_team\_routing\_rule) | ./modules/team_routing_rule |  |
+| <a name="module_alert_policy"></a> [alert\_policy](#module\_alert\_policy) | ./modules/alert_policy | n/a |
+| <a name="module_api_integration"></a> [api\_integration](#module\_api\_integration) | ./modules/api_integration | n/a |
+| <a name="module_escalation"></a> [escalation](#module\_escalation) | ./modules/escalation | n/a |
+| <a name="module_integration_action"></a> [integration\_action](#module\_integration\_action) | ./modules/integration_action | n/a |
+| <a name="module_notification_policy"></a> [notification\_policy](#module\_notification\_policy) | ./modules/notification_policy | n/a |
+| <a name="module_service"></a> [service](#module\_service) | ./modules/service | n/a |
+| <a name="module_service_incident_rule"></a> [service\_incident\_rule](#module\_service\_incident\_rule) | ./modules/service_incident_rule | n/a |
+| <a name="module_team"></a> [team](#module\_team) | ./modules/team | n/a |
+| <a name="module_team_routing_rule"></a> [team\_routing\_rule](#module\_team\_routing\_rule) | ./modules/team_routing_rule | n/a |
 | <a name="module_this"></a> [this](#module\_this) | cloudposse/label/null | 0.24.1 |
-| <a name="module_user"></a> [user](#module\_user) | ./modules/user |  |
+| <a name="module_user"></a> [user](#module\_user) | ./modules/user | n/a |
 
 ## Resources
 
@@ -210,6 +215,7 @@ No resources.
 | <a name="input_environment"></a> [environment](#input\_environment) | Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no |
 | <a name="input_escalation"></a> [escalation](#input\_escalation) | Opsgenie Escalation configuration | `map` | `{}` | no |
 | <a name="input_id_length_limit"></a> [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).<br>Set to `0` for unlimited length.<br>Set to `null` for default, which is `0`.<br>Does not affect `id_full`. | `number` | `null` | no |
+| <a name="input_integration_action"></a> [integration\_action](#input\_integration\_action) | Opsgenie Integration Action configuration | `map` | `{}` | no |
 | <a name="input_label_key_case"></a> [label\_key\_case](#input\_label\_key\_case) | The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.<br>Possible values: `lower`, `title`, `upper`.<br>Default value: `title`. | `string` | `null` | no |
 | <a name="input_label_order"></a> [label\_order](#input\_label\_order) | The naming order of the id output and Name tag.<br>Defaults to ["namespace", "environment", "stage", "name", "attributes"].<br>You can omit any of the 5 elements, but at least one must be present. | `list(string)` | `null` | no |
 | <a name="input_label_value_case"></a> [label\_value\_case](#input\_label\_value\_case) | The letter case of output label values (also used in `tags` and `id`).<br>Possible values: `lower`, `title`, `upper` and `none` (no transformation).<br>Default value: `lower`. | `string` | `null` | no |
@@ -241,6 +247,7 @@ No resources.
 | <a name="output_api_integration_name"></a> [api\_integration\_name](#output\_api\_integration\_name) | The name of the Opsgenie API Integration |
 | <a name="output_escalation_id"></a> [escalation\_id](#output\_escalation\_id) | The ID of the Opsgenie Escalation |
 | <a name="output_escalation_name"></a> [escalation\_name](#output\_escalation\_name) | Name of the Opsgenie Escalation |
+| <a name="output_integration_action_id"></a> [integration\_action\_id](#output\_integration\_action\_id) | The ID of the Opsgenie Integration Action |
 | <a name="output_notification_policy_id"></a> [notification\_policy\_id](#output\_notification\_policy\_id) | The ID of the Opsgenie Notification Policy |
 | <a name="output_notification_policy_name"></a> [notification\_policy\_name](#output\_notification\_policy\_name) | The name of the Opsgenie Notification Policy |
 | <a name="output_service_id"></a> [service\_id](#output\_service\_id) | The ID of the Opsgenie Service |
diff --git a/README.yaml b/README.yaml
index f057fcf..43b92b4 100644
--- a/README.yaml
+++ b/README.yaml
@@ -64,6 +64,7 @@ introduction: |-
   - [API Integration](modules/api_integration)
   - [Config](modules/config)
   - [Escalation](modules/escalation)
+  - [Integration Action](modules/integration_action) (advanced feature — not available to all OpsGenie plans)
   - [Notification Policy](modules/notification_policy)
   - [Team](modules/team)
   - [Team Routing Rule](modules/team_routing_rule)
@@ -73,6 +74,8 @@ introduction: |-
 
   **Note:** Root module is just an example that uses all of submodules.
 
+  **Note:** See the [Advanced Features Example](examples/advanced_features) for features only available to some OpsGenie plans.
+
 usage: |-
   Here's how to invoke `team` module in your projects
 
@@ -99,6 +102,7 @@ examples: |-
   - [`alert_policy`](examples/alert_policy)
   - [`api_integration`](examples/api_integration)
   - [`escalation`](examples/escalation)
+  - [`integration_action`](examples/integration_action) (advanced feature — not available to all OpsGenie plans)
   - [`notification_policy`](examples/notification_policy)
   - [`team`](examples/team)
   - [`team_routing_rule`](examples/team_routing_rule)
diff --git a/docs/terraform.md b/docs/terraform.md
index b99b2f8..0262a3d 100644
--- a/docs/terraform.md
+++ b/docs/terraform.md
@@ -14,16 +14,17 @@ No providers.
 
 | Name | Source | Version |
 |------|--------|---------|
-| <a name="module_alert_policy"></a> [alert\_policy](#module\_alert\_policy) | ./modules/alert_policy |  |
-| <a name="module_api_integration"></a> [api\_integration](#module\_api\_integration) | ./modules/api_integration |  |
-| <a name="module_escalation"></a> [escalation](#module\_escalation) | ./modules/escalation |  |
-| <a name="module_notification_policy"></a> [notification\_policy](#module\_notification\_policy) | ./modules/notification_policy |  |
-| <a name="module_service"></a> [service](#module\_service) | ./modules/service |  |
-| <a name="module_service_incident_rule"></a> [service\_incident\_rule](#module\_service\_incident\_rule) | ./modules/service_incident_rule |  |
-| <a name="module_team"></a> [team](#module\_team) | ./modules/team |  |
-| <a name="module_team_routing_rule"></a> [team\_routing\_rule](#module\_team\_routing\_rule) | ./modules/team_routing_rule |  |
+| <a name="module_alert_policy"></a> [alert\_policy](#module\_alert\_policy) | ./modules/alert_policy | n/a |
+| <a name="module_api_integration"></a> [api\_integration](#module\_api\_integration) | ./modules/api_integration | n/a |
+| <a name="module_escalation"></a> [escalation](#module\_escalation) | ./modules/escalation | n/a |
+| <a name="module_integration_action"></a> [integration\_action](#module\_integration\_action) | ./modules/integration_action | n/a |
+| <a name="module_notification_policy"></a> [notification\_policy](#module\_notification\_policy) | ./modules/notification_policy | n/a |
+| <a name="module_service"></a> [service](#module\_service) | ./modules/service | n/a |
+| <a name="module_service_incident_rule"></a> [service\_incident\_rule](#module\_service\_incident\_rule) | ./modules/service_incident_rule | n/a |
+| <a name="module_team"></a> [team](#module\_team) | ./modules/team | n/a |
+| <a name="module_team_routing_rule"></a> [team\_routing\_rule](#module\_team\_routing\_rule) | ./modules/team_routing_rule | n/a |
 | <a name="module_this"></a> [this](#module\_this) | cloudposse/label/null | 0.24.1 |
-| <a name="module_user"></a> [user](#module\_user) | ./modules/user |  |
+| <a name="module_user"></a> [user](#module\_user) | ./modules/user | n/a |
 
 ## Resources
 
@@ -43,6 +44,7 @@ No resources.
 | <a name="input_environment"></a> [environment](#input\_environment) | Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no |
 | <a name="input_escalation"></a> [escalation](#input\_escalation) | Opsgenie Escalation configuration | `map` | `{}` | no |
 | <a name="input_id_length_limit"></a> [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).<br>Set to `0` for unlimited length.<br>Set to `null` for default, which is `0`.<br>Does not affect `id_full`. | `number` | `null` | no |
+| <a name="input_integration_action"></a> [integration\_action](#input\_integration\_action) | Opsgenie Integration Action configuration | `map` | `{}` | no |
 | <a name="input_label_key_case"></a> [label\_key\_case](#input\_label\_key\_case) | The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.<br>Possible values: `lower`, `title`, `upper`.<br>Default value: `title`. | `string` | `null` | no |
 | <a name="input_label_order"></a> [label\_order](#input\_label\_order) | The naming order of the id output and Name tag.<br>Defaults to ["namespace", "environment", "stage", "name", "attributes"].<br>You can omit any of the 5 elements, but at least one must be present. | `list(string)` | `null` | no |
 | <a name="input_label_value_case"></a> [label\_value\_case](#input\_label\_value\_case) | The letter case of output label values (also used in `tags` and `id`).<br>Possible values: `lower`, `title`, `upper` and `none` (no transformation).<br>Default value: `lower`. | `string` | `null` | no |
@@ -74,6 +76,7 @@ No resources.
 | <a name="output_api_integration_name"></a> [api\_integration\_name](#output\_api\_integration\_name) | The name of the Opsgenie API Integration |
 | <a name="output_escalation_id"></a> [escalation\_id](#output\_escalation\_id) | The ID of the Opsgenie Escalation |
 | <a name="output_escalation_name"></a> [escalation\_name](#output\_escalation\_name) | Name of the Opsgenie Escalation |
+| <a name="output_integration_action_id"></a> [integration\_action\_id](#output\_integration\_action\_id) | The ID of the Opsgenie Integration Action |
 | <a name="output_notification_policy_id"></a> [notification\_policy\_id](#output\_notification\_policy\_id) | The ID of the Opsgenie Notification Policy |
 | <a name="output_notification_policy_name"></a> [notification\_policy\_name](#output\_notification\_policy\_name) | The name of the Opsgenie Notification Policy |
 | <a name="output_service_id"></a> [service\_id](#output\_service\_id) | The ID of the Opsgenie Service |
diff --git a/examples/advanced_features/api_integration.tf b/examples/advanced_features/api_integration.tf
new file mode 100644
index 0000000..3a9b8e1
--- /dev/null
+++ b/examples/advanced_features/api_integration.tf
@@ -0,0 +1,9 @@
+module "api_integration" {
+  source = "../../modules/api_integration"
+
+  api_integration = {
+    name          = module.this.id
+    type          = "AmazonSns"
+    owner_team_id = module.team.team_id
+  }
+}
diff --git a/examples/advanced_features/context.tf b/examples/advanced_features/context.tf
new file mode 100644
index 0000000..81f99b4
--- /dev/null
+++ b/examples/advanced_features/context.tf
@@ -0,0 +1,202 @@
+#
+# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label
+# All other instances of this file should be a copy of that one
+#
+#
+# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf
+# and then place it in your Terraform module to automatically get
+# Cloud Posse's standard configuration inputs suitable for passing
+# to Cloud Posse modules.
+#
+# Modules should access the whole context as `module.this.context`
+# to get the input variables with nulls for defaults,
+# for example `context = module.this.context`,
+# and access individual variables as `module.this.<var>`,
+# with final values filled in.
+#
+# For example, when using defaults, `module.this.context.delimiter`
+# will be null, and `module.this.delimiter` will be `-` (hyphen).
+#
+
+module "this" {
+  source  = "cloudposse/label/null"
+  version = "0.24.1" # requires Terraform >= 0.13.0
+
+  enabled             = var.enabled
+  namespace           = var.namespace
+  environment         = var.environment
+  stage               = var.stage
+  name                = var.name
+  delimiter           = var.delimiter
+  attributes          = var.attributes
+  tags                = var.tags
+  additional_tag_map  = var.additional_tag_map
+  label_order         = var.label_order
+  regex_replace_chars = var.regex_replace_chars
+  id_length_limit     = var.id_length_limit
+  label_key_case      = var.label_key_case
+  label_value_case    = var.label_value_case
+
+  context = var.context
+}
+
+# Copy contents of cloudposse/terraform-null-label/variables.tf here
+
+variable "context" {
+  type = any
+  default = {
+    enabled             = true
+    namespace           = null
+    environment         = null
+    stage               = null
+    name                = null
+    delimiter           = null
+    attributes          = []
+    tags                = {}
+    additional_tag_map  = {}
+    regex_replace_chars = null
+    label_order         = []
+    id_length_limit     = null
+    label_key_case      = null
+    label_value_case    = null
+  }
+  description = <<-EOT
+    Single object for setting entire context at once.
+    See description of individual variables for details.
+    Leave string and numeric variables as `null` to use default value.
+    Individual variable settings (non-null) override settings in context object,
+    except for attributes, tags, and additional_tag_map, which are merged.
+  EOT
+
+  validation {
+    condition     = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
+
+  validation {
+    condition     = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
+}
+
+variable "enabled" {
+  type        = bool
+  default     = null
+  description = "Set to false to prevent the module from creating any resources"
+}
+
+variable "namespace" {
+  type        = string
+  default     = null
+  description = "Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp'"
+}
+
+variable "environment" {
+  type        = string
+  default     = null
+  description = "Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT'"
+}
+
+variable "stage" {
+  type        = string
+  default     = null
+  description = "Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release'"
+}
+
+variable "name" {
+  type        = string
+  default     = null
+  description = "Solution name, e.g. 'app' or 'jenkins'"
+}
+
+variable "delimiter" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.
+    Defaults to `-` (hyphen). Set to `""` to use no delimiter at all.
+  EOT
+}
+
+variable "attributes" {
+  type        = list(string)
+  default     = []
+  description = "Additional attributes (e.g. `1`)"
+}
+
+variable "tags" {
+  type        = map(string)
+  default     = {}
+  description = "Additional tags (e.g. `map('BusinessUnit','XYZ')`"
+}
+
+variable "additional_tag_map" {
+  type        = map(string)
+  default     = {}
+  description = "Additional tags for appending to tags_as_list_of_maps. Not added to `tags`."
+}
+
+variable "label_order" {
+  type        = list(string)
+  default     = null
+  description = <<-EOT
+    The naming order of the id output and Name tag.
+    Defaults to ["namespace", "environment", "stage", "name", "attributes"].
+    You can omit any of the 5 elements, but at least one must be present.
+  EOT
+}
+
+variable "regex_replace_chars" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.
+    If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits.
+  EOT
+}
+
+variable "id_length_limit" {
+  type        = number
+  default     = null
+  description = <<-EOT
+    Limit `id` to this many characters (minimum 6).
+    Set to `0` for unlimited length.
+    Set to `null` for default, which is `0`.
+    Does not affect `id_full`.
+  EOT
+  validation {
+    condition     = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0
+    error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length."
+  }
+}
+
+variable "label_key_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+    Possible values: `lower`, `title`, `upper`.
+    Default value: `title`.
+  EOT
+
+  validation {
+    condition     = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
+}
+
+variable "label_value_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of output label values (also used in `tags` and `id`).
+    Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+    Default value: `lower`.
+  EOT
+
+  validation {
+    condition     = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
+}
+#### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/examples/advanced_features/fixtures.tfvars b/examples/advanced_features/fixtures.tfvars
new file mode 100644
index 0000000..2660254
--- /dev/null
+++ b/examples/advanced_features/fixtures.tfvars
@@ -0,0 +1,4 @@
+enabled   = true
+namespace = "eg"
+name      = "incident-management-workflow"
+stage     = "test"
diff --git a/examples/advanced_features/integration_action.tf b/examples/advanced_features/integration_action.tf
new file mode 100644
index 0000000..6c72641
--- /dev/null
+++ b/examples/advanced_features/integration_action.tf
@@ -0,0 +1,26 @@
+module "integration_action" {
+  source = "../../modules/integration_action"
+
+  integration_action = {
+    integration_id = module.api_integration.api_integration_id
+    team_id        = module.team.team_id
+
+    create = [
+      {
+        name  = "Create Non-informational Alerts"
+        alias = "{{title}}"
+        filter = {
+          type = "match-all-conditions"
+          conditions = [
+            {
+              field          = "priority"
+              not            = true
+              operation      = "equals"
+              expected_value = "P5"
+            }
+          ]
+        }
+      }
+    ]
+  }
+}
diff --git a/examples/advanced_features/main.tf b/examples/advanced_features/main.tf
new file mode 100644
index 0000000..5ca8574
--- /dev/null
+++ b/examples/advanced_features/main.tf
@@ -0,0 +1,3 @@
+provider "opsgenie" {
+  api_key = var.opsgenie_provider_api_key
+}
diff --git a/examples/advanced_features/outputs.tf b/examples/advanced_features/outputs.tf
new file mode 100644
index 0000000..9e59880
--- /dev/null
+++ b/examples/advanced_features/outputs.tf
@@ -0,0 +1,24 @@
+output "integration_action_id" {
+  description = "The ID of the Opsgenie Integration Action"
+  value       = module.integration_action.integration_action_id
+}
+
+output "api_integration_id" {
+  description = "The ID of the Opsgenie integration"
+  value       = module.api_integration.api_integration_id
+}
+
+output "api_integration_name" {
+  description = "The name of the Opsgenie integration"
+  value       = module.api_integration.api_integration_name
+}
+
+output "team_id" {
+  description = "The ID of the Opsgenie team"
+  value       = module.team.team_id
+}
+
+output "team_name" {
+  description = "The name of the Opsgenie team"
+  value       = module.team.team_name
+}
diff --git a/examples/advanced_features/team.tf b/examples/advanced_features/team.tf
new file mode 100644
index 0000000..d160a38
--- /dev/null
+++ b/examples/advanced_features/team.tf
@@ -0,0 +1,9 @@
+module "team" {
+  source = "../../modules/team"
+
+  team = {
+    name           = "${module.this.id}.security"
+    description    = "${module.this.id} Security Team"
+    ignore_members = false
+  }
+}
diff --git a/examples/advanced_features/variables.tf b/examples/advanced_features/variables.tf
new file mode 100644
index 0000000..841a369
--- /dev/null
+++ b/examples/advanced_features/variables.tf
@@ -0,0 +1,5 @@
+variable "opsgenie_provider_api_key" {
+  type        = string
+  description = "The API Key for the Opsgenie Integration. If omitted, the OPSGENIE_API_KEY environment variable is used"
+  default     = null
+}
diff --git a/examples/advanced_features/versions.tf b/examples/advanced_features/versions.tf
new file mode 100644
index 0000000..240bbd5
--- /dev/null
+++ b/examples/advanced_features/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+  required_version = ">= 0.13.0"
+
+  required_providers {
+    opsgenie = {
+      source  = "opsgenie/opsgenie"
+      version = ">= 0.4"
+    }
+  }
+}
diff --git a/examples/alert_policy/context.tf b/examples/alert_policy/context.tf
index bae0cf1..81f99b4 100644
--- a/examples/alert_policy/context.tf
+++ b/examples/alert_policy/context.tf
@@ -19,7 +19,8 @@
 #
 
 module "this" {
-  source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.19.2"
+  source  = "cloudposse/label/null"
+  version = "0.24.1" # requires Terraform >= 0.13.0
 
   enabled             = var.enabled
   namespace           = var.namespace
@@ -33,6 +34,8 @@ module "this" {
   label_order         = var.label_order
   regex_replace_chars = var.regex_replace_chars
   id_length_limit     = var.id_length_limit
+  label_key_case      = var.label_key_case
+  label_value_case    = var.label_value_case
 
   context = var.context
 }
@@ -40,20 +43,7 @@ module "this" {
 # Copy contents of cloudposse/terraform-null-label/variables.tf here
 
 variable "context" {
-  type = object({
-    enabled             = bool
-    namespace           = string
-    environment         = string
-    stage               = string
-    name                = string
-    delimiter           = string
-    attributes          = list(string)
-    tags                = map(string)
-    additional_tag_map  = map(string)
-    regex_replace_chars = string
-    label_order         = list(string)
-    id_length_limit     = number
-  })
+  type = any
   default = {
     enabled             = true
     namespace           = null
@@ -67,6 +57,8 @@ variable "context" {
     regex_replace_chars = null
     label_order         = []
     id_length_limit     = null
+    label_key_case      = null
+    label_value_case    = null
   }
   description = <<-EOT
     Single object for setting entire context at once.
@@ -75,6 +67,16 @@ variable "context" {
     Individual variable settings (non-null) override settings in context object,
     except for attributes, tags, and additional_tag_map, which are merged.
   EOT
+
+  validation {
+    condition     = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
+
+  validation {
+    condition     = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
 }
 
 variable "enabled" {
@@ -157,11 +159,44 @@ variable "id_length_limit" {
   type        = number
   default     = null
   description = <<-EOT
-    Limit `id` to this many characters.
+    Limit `id` to this many characters (minimum 6).
     Set to `0` for unlimited length.
     Set to `null` for default, which is `0`.
     Does not affect `id_full`.
   EOT
+  validation {
+    condition     = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0
+    error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length."
+  }
+}
+
+variable "label_key_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+    Possible values: `lower`, `title`, `upper`.
+    Default value: `title`.
+  EOT
+
+  validation {
+    condition     = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
 }
 
+variable "label_value_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of output label values (also used in `tags` and `id`).
+    Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+    Default value: `lower`.
+  EOT
+
+  validation {
+    condition     = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
+}
 #### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/examples/api_integration/context.tf b/examples/api_integration/context.tf
index bae0cf1..81f99b4 100644
--- a/examples/api_integration/context.tf
+++ b/examples/api_integration/context.tf
@@ -19,7 +19,8 @@
 #
 
 module "this" {
-  source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.19.2"
+  source  = "cloudposse/label/null"
+  version = "0.24.1" # requires Terraform >= 0.13.0
 
   enabled             = var.enabled
   namespace           = var.namespace
@@ -33,6 +34,8 @@ module "this" {
   label_order         = var.label_order
   regex_replace_chars = var.regex_replace_chars
   id_length_limit     = var.id_length_limit
+  label_key_case      = var.label_key_case
+  label_value_case    = var.label_value_case
 
   context = var.context
 }
@@ -40,20 +43,7 @@ module "this" {
 # Copy contents of cloudposse/terraform-null-label/variables.tf here
 
 variable "context" {
-  type = object({
-    enabled             = bool
-    namespace           = string
-    environment         = string
-    stage               = string
-    name                = string
-    delimiter           = string
-    attributes          = list(string)
-    tags                = map(string)
-    additional_tag_map  = map(string)
-    regex_replace_chars = string
-    label_order         = list(string)
-    id_length_limit     = number
-  })
+  type = any
   default = {
     enabled             = true
     namespace           = null
@@ -67,6 +57,8 @@ variable "context" {
     regex_replace_chars = null
     label_order         = []
     id_length_limit     = null
+    label_key_case      = null
+    label_value_case    = null
   }
   description = <<-EOT
     Single object for setting entire context at once.
@@ -75,6 +67,16 @@ variable "context" {
     Individual variable settings (non-null) override settings in context object,
     except for attributes, tags, and additional_tag_map, which are merged.
   EOT
+
+  validation {
+    condition     = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
+
+  validation {
+    condition     = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
 }
 
 variable "enabled" {
@@ -157,11 +159,44 @@ variable "id_length_limit" {
   type        = number
   default     = null
   description = <<-EOT
-    Limit `id` to this many characters.
+    Limit `id` to this many characters (minimum 6).
     Set to `0` for unlimited length.
     Set to `null` for default, which is `0`.
     Does not affect `id_full`.
   EOT
+  validation {
+    condition     = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0
+    error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length."
+  }
+}
+
+variable "label_key_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+    Possible values: `lower`, `title`, `upper`.
+    Default value: `title`.
+  EOT
+
+  validation {
+    condition     = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
 }
 
+variable "label_value_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of output label values (also used in `tags` and `id`).
+    Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+    Default value: `lower`.
+  EOT
+
+  validation {
+    condition     = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
+}
 #### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/examples/config/context.tf b/examples/config/context.tf
index bae0cf1..81f99b4 100644
--- a/examples/config/context.tf
+++ b/examples/config/context.tf
@@ -19,7 +19,8 @@
 #
 
 module "this" {
-  source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.19.2"
+  source  = "cloudposse/label/null"
+  version = "0.24.1" # requires Terraform >= 0.13.0
 
   enabled             = var.enabled
   namespace           = var.namespace
@@ -33,6 +34,8 @@ module "this" {
   label_order         = var.label_order
   regex_replace_chars = var.regex_replace_chars
   id_length_limit     = var.id_length_limit
+  label_key_case      = var.label_key_case
+  label_value_case    = var.label_value_case
 
   context = var.context
 }
@@ -40,20 +43,7 @@ module "this" {
 # Copy contents of cloudposse/terraform-null-label/variables.tf here
 
 variable "context" {
-  type = object({
-    enabled             = bool
-    namespace           = string
-    environment         = string
-    stage               = string
-    name                = string
-    delimiter           = string
-    attributes          = list(string)
-    tags                = map(string)
-    additional_tag_map  = map(string)
-    regex_replace_chars = string
-    label_order         = list(string)
-    id_length_limit     = number
-  })
+  type = any
   default = {
     enabled             = true
     namespace           = null
@@ -67,6 +57,8 @@ variable "context" {
     regex_replace_chars = null
     label_order         = []
     id_length_limit     = null
+    label_key_case      = null
+    label_value_case    = null
   }
   description = <<-EOT
     Single object for setting entire context at once.
@@ -75,6 +67,16 @@ variable "context" {
     Individual variable settings (non-null) override settings in context object,
     except for attributes, tags, and additional_tag_map, which are merged.
   EOT
+
+  validation {
+    condition     = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
+
+  validation {
+    condition     = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
 }
 
 variable "enabled" {
@@ -157,11 +159,44 @@ variable "id_length_limit" {
   type        = number
   default     = null
   description = <<-EOT
-    Limit `id` to this many characters.
+    Limit `id` to this many characters (minimum 6).
     Set to `0` for unlimited length.
     Set to `null` for default, which is `0`.
     Does not affect `id_full`.
   EOT
+  validation {
+    condition     = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0
+    error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length."
+  }
+}
+
+variable "label_key_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+    Possible values: `lower`, `title`, `upper`.
+    Default value: `title`.
+  EOT
+
+  validation {
+    condition     = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
 }
 
+variable "label_value_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of output label values (also used in `tags` and `id`).
+    Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+    Default value: `lower`.
+  EOT
+
+  validation {
+    condition     = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
+}
 #### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/examples/config/outputs.tf b/examples/config/outputs.tf
index d34387c..e4efa8e 100644
--- a/examples/config/outputs.tf
+++ b/examples/config/outputs.tf
@@ -13,6 +13,11 @@ output "escalations" {
   description = "Escalations"
 }
 
+output "integration_actions" {
+  value       = module.opsgenie_config.integration_actions
+  description = "Integration Actions"
+}
+
 output "notification_policies" {
   value       = module.opsgenie_config.notification_policies
   description = "Notification policies"
diff --git a/examples/config/resources/integration_actions.yaml b/examples/config/resources/integration_actions.yaml
new file mode 100644
index 0000000..7f1896a
--- /dev/null
+++ b/examples/config/resources/integration_actions.yaml
@@ -0,0 +1,13 @@
+integration_actions:
+  - name: acme-dev-opsgenie-sns-close-low-priority
+    integration_name: acme-dev-opsgenie-sns-integration
+    create:
+      - name: Create Non-informational Alerts
+        alias: "{{title}}"
+        filter:
+          type: match-all-conditions
+          conditions:
+            - field: priority
+              not: true
+              operation: equals
+              expected_value: P5
diff --git a/examples/escalation/context.tf b/examples/escalation/context.tf
index bae0cf1..81f99b4 100644
--- a/examples/escalation/context.tf
+++ b/examples/escalation/context.tf
@@ -19,7 +19,8 @@
 #
 
 module "this" {
-  source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.19.2"
+  source  = "cloudposse/label/null"
+  version = "0.24.1" # requires Terraform >= 0.13.0
 
   enabled             = var.enabled
   namespace           = var.namespace
@@ -33,6 +34,8 @@ module "this" {
   label_order         = var.label_order
   regex_replace_chars = var.regex_replace_chars
   id_length_limit     = var.id_length_limit
+  label_key_case      = var.label_key_case
+  label_value_case    = var.label_value_case
 
   context = var.context
 }
@@ -40,20 +43,7 @@ module "this" {
 # Copy contents of cloudposse/terraform-null-label/variables.tf here
 
 variable "context" {
-  type = object({
-    enabled             = bool
-    namespace           = string
-    environment         = string
-    stage               = string
-    name                = string
-    delimiter           = string
-    attributes          = list(string)
-    tags                = map(string)
-    additional_tag_map  = map(string)
-    regex_replace_chars = string
-    label_order         = list(string)
-    id_length_limit     = number
-  })
+  type = any
   default = {
     enabled             = true
     namespace           = null
@@ -67,6 +57,8 @@ variable "context" {
     regex_replace_chars = null
     label_order         = []
     id_length_limit     = null
+    label_key_case      = null
+    label_value_case    = null
   }
   description = <<-EOT
     Single object for setting entire context at once.
@@ -75,6 +67,16 @@ variable "context" {
     Individual variable settings (non-null) override settings in context object,
     except for attributes, tags, and additional_tag_map, which are merged.
   EOT
+
+  validation {
+    condition     = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
+
+  validation {
+    condition     = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
 }
 
 variable "enabled" {
@@ -157,11 +159,44 @@ variable "id_length_limit" {
   type        = number
   default     = null
   description = <<-EOT
-    Limit `id` to this many characters.
+    Limit `id` to this many characters (minimum 6).
     Set to `0` for unlimited length.
     Set to `null` for default, which is `0`.
     Does not affect `id_full`.
   EOT
+  validation {
+    condition     = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0
+    error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length."
+  }
+}
+
+variable "label_key_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+    Possible values: `lower`, `title`, `upper`.
+    Default value: `title`.
+  EOT
+
+  validation {
+    condition     = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
 }
 
+variable "label_value_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of output label values (also used in `tags` and `id`).
+    Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+    Default value: `lower`.
+  EOT
+
+  validation {
+    condition     = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
+}
 #### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/examples/integration_action/context.tf b/examples/integration_action/context.tf
new file mode 100644
index 0000000..81f99b4
--- /dev/null
+++ b/examples/integration_action/context.tf
@@ -0,0 +1,202 @@
+#
+# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label
+# All other instances of this file should be a copy of that one
+#
+#
+# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf
+# and then place it in your Terraform module to automatically get
+# Cloud Posse's standard configuration inputs suitable for passing
+# to Cloud Posse modules.
+#
+# Modules should access the whole context as `module.this.context`
+# to get the input variables with nulls for defaults,
+# for example `context = module.this.context`,
+# and access individual variables as `module.this.<var>`,
+# with final values filled in.
+#
+# For example, when using defaults, `module.this.context.delimiter`
+# will be null, and `module.this.delimiter` will be `-` (hyphen).
+#
+
+module "this" {
+  source  = "cloudposse/label/null"
+  version = "0.24.1" # requires Terraform >= 0.13.0
+
+  enabled             = var.enabled
+  namespace           = var.namespace
+  environment         = var.environment
+  stage               = var.stage
+  name                = var.name
+  delimiter           = var.delimiter
+  attributes          = var.attributes
+  tags                = var.tags
+  additional_tag_map  = var.additional_tag_map
+  label_order         = var.label_order
+  regex_replace_chars = var.regex_replace_chars
+  id_length_limit     = var.id_length_limit
+  label_key_case      = var.label_key_case
+  label_value_case    = var.label_value_case
+
+  context = var.context
+}
+
+# Copy contents of cloudposse/terraform-null-label/variables.tf here
+
+variable "context" {
+  type = any
+  default = {
+    enabled             = true
+    namespace           = null
+    environment         = null
+    stage               = null
+    name                = null
+    delimiter           = null
+    attributes          = []
+    tags                = {}
+    additional_tag_map  = {}
+    regex_replace_chars = null
+    label_order         = []
+    id_length_limit     = null
+    label_key_case      = null
+    label_value_case    = null
+  }
+  description = <<-EOT
+    Single object for setting entire context at once.
+    See description of individual variables for details.
+    Leave string and numeric variables as `null` to use default value.
+    Individual variable settings (non-null) override settings in context object,
+    except for attributes, tags, and additional_tag_map, which are merged.
+  EOT
+
+  validation {
+    condition     = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
+
+  validation {
+    condition     = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
+}
+
+variable "enabled" {
+  type        = bool
+  default     = null
+  description = "Set to false to prevent the module from creating any resources"
+}
+
+variable "namespace" {
+  type        = string
+  default     = null
+  description = "Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp'"
+}
+
+variable "environment" {
+  type        = string
+  default     = null
+  description = "Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT'"
+}
+
+variable "stage" {
+  type        = string
+  default     = null
+  description = "Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release'"
+}
+
+variable "name" {
+  type        = string
+  default     = null
+  description = "Solution name, e.g. 'app' or 'jenkins'"
+}
+
+variable "delimiter" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.
+    Defaults to `-` (hyphen). Set to `""` to use no delimiter at all.
+  EOT
+}
+
+variable "attributes" {
+  type        = list(string)
+  default     = []
+  description = "Additional attributes (e.g. `1`)"
+}
+
+variable "tags" {
+  type        = map(string)
+  default     = {}
+  description = "Additional tags (e.g. `map('BusinessUnit','XYZ')`"
+}
+
+variable "additional_tag_map" {
+  type        = map(string)
+  default     = {}
+  description = "Additional tags for appending to tags_as_list_of_maps. Not added to `tags`."
+}
+
+variable "label_order" {
+  type        = list(string)
+  default     = null
+  description = <<-EOT
+    The naming order of the id output and Name tag.
+    Defaults to ["namespace", "environment", "stage", "name", "attributes"].
+    You can omit any of the 5 elements, but at least one must be present.
+  EOT
+}
+
+variable "regex_replace_chars" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.
+    If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits.
+  EOT
+}
+
+variable "id_length_limit" {
+  type        = number
+  default     = null
+  description = <<-EOT
+    Limit `id` to this many characters (minimum 6).
+    Set to `0` for unlimited length.
+    Set to `null` for default, which is `0`.
+    Does not affect `id_full`.
+  EOT
+  validation {
+    condition     = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0
+    error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length."
+  }
+}
+
+variable "label_key_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+    Possible values: `lower`, `title`, `upper`.
+    Default value: `title`.
+  EOT
+
+  validation {
+    condition     = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
+}
+
+variable "label_value_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of output label values (also used in `tags` and `id`).
+    Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+    Default value: `lower`.
+  EOT
+
+  validation {
+    condition     = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
+}
+#### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/examples/integration_action/fixtures.tfvars b/examples/integration_action/fixtures.tfvars
new file mode 100644
index 0000000..83719e9
--- /dev/null
+++ b/examples/integration_action/fixtures.tfvars
@@ -0,0 +1,4 @@
+enabled   = true
+namespace = "eg"
+name      = "integration"
+stage     = "test"
diff --git a/examples/integration_action/main.tf b/examples/integration_action/main.tf
new file mode 100644
index 0000000..bf94fb1
--- /dev/null
+++ b/examples/integration_action/main.tf
@@ -0,0 +1,48 @@
+provider "opsgenie" {
+  api_key = var.opsgenie_provider_api_key
+}
+
+module "team" {
+  source = "../../modules/team"
+
+  team = {
+    name = module.this.id
+  }
+}
+
+module "api_integration" {
+  source = "../../modules/api_integration"
+
+  api_integration = {
+    name          = module.this.id
+    type          = "AmazonSns"
+    owner_team_id = module.team.team_id
+  }
+}
+
+module "integration_action" {
+  source = "../../modules/integration_action"
+
+  integration_action = {
+    integration_id = module.api_integration.api_integration_id
+    team_id        = module.team.team_id
+
+    create = [
+      {
+        name  = "Create Non-informational Alerts"
+        alias = "{{title}}"
+        filter = {
+          type = "match-all-conditions"
+          conditions = [
+            {
+              field          = "priority"
+              not            = true
+              operation      = "equals"
+              expected_value = "P5"
+            }
+          ]
+        }
+      }
+    ]
+  }
+}
diff --git a/examples/integration_action/outputs.tf b/examples/integration_action/outputs.tf
new file mode 100644
index 0000000..e4e6b5c
--- /dev/null
+++ b/examples/integration_action/outputs.tf
@@ -0,0 +1,9 @@
+output "integration_action" {
+  description = "Opsgenie Integration Action"
+  value       = module.integration_action
+}
+
+output "integration_action_id" {
+  description = "The ID of the Opsgenie Integration Action"
+  value       = module.integration_action.integration_action_id
+}
diff --git a/examples/integration_action/variables.tf b/examples/integration_action/variables.tf
new file mode 100644
index 0000000..841a369
--- /dev/null
+++ b/examples/integration_action/variables.tf
@@ -0,0 +1,5 @@
+variable "opsgenie_provider_api_key" {
+  type        = string
+  description = "The API Key for the Opsgenie Integration. If omitted, the OPSGENIE_API_KEY environment variable is used"
+  default     = null
+}
diff --git a/examples/integration_action/versions.tf b/examples/integration_action/versions.tf
new file mode 100644
index 0000000..240bbd5
--- /dev/null
+++ b/examples/integration_action/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+  required_version = ">= 0.13.0"
+
+  required_providers {
+    opsgenie = {
+      source  = "opsgenie/opsgenie"
+      version = ">= 0.4"
+    }
+  }
+}
diff --git a/examples/notification_policy/context.tf b/examples/notification_policy/context.tf
index bae0cf1..81f99b4 100644
--- a/examples/notification_policy/context.tf
+++ b/examples/notification_policy/context.tf
@@ -19,7 +19,8 @@
 #
 
 module "this" {
-  source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.19.2"
+  source  = "cloudposse/label/null"
+  version = "0.24.1" # requires Terraform >= 0.13.0
 
   enabled             = var.enabled
   namespace           = var.namespace
@@ -33,6 +34,8 @@ module "this" {
   label_order         = var.label_order
   regex_replace_chars = var.regex_replace_chars
   id_length_limit     = var.id_length_limit
+  label_key_case      = var.label_key_case
+  label_value_case    = var.label_value_case
 
   context = var.context
 }
@@ -40,20 +43,7 @@ module "this" {
 # Copy contents of cloudposse/terraform-null-label/variables.tf here
 
 variable "context" {
-  type = object({
-    enabled             = bool
-    namespace           = string
-    environment         = string
-    stage               = string
-    name                = string
-    delimiter           = string
-    attributes          = list(string)
-    tags                = map(string)
-    additional_tag_map  = map(string)
-    regex_replace_chars = string
-    label_order         = list(string)
-    id_length_limit     = number
-  })
+  type = any
   default = {
     enabled             = true
     namespace           = null
@@ -67,6 +57,8 @@ variable "context" {
     regex_replace_chars = null
     label_order         = []
     id_length_limit     = null
+    label_key_case      = null
+    label_value_case    = null
   }
   description = <<-EOT
     Single object for setting entire context at once.
@@ -75,6 +67,16 @@ variable "context" {
     Individual variable settings (non-null) override settings in context object,
     except for attributes, tags, and additional_tag_map, which are merged.
   EOT
+
+  validation {
+    condition     = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
+
+  validation {
+    condition     = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
 }
 
 variable "enabled" {
@@ -157,11 +159,44 @@ variable "id_length_limit" {
   type        = number
   default     = null
   description = <<-EOT
-    Limit `id` to this many characters.
+    Limit `id` to this many characters (minimum 6).
     Set to `0` for unlimited length.
     Set to `null` for default, which is `0`.
     Does not affect `id_full`.
   EOT
+  validation {
+    condition     = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0
+    error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length."
+  }
+}
+
+variable "label_key_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+    Possible values: `lower`, `title`, `upper`.
+    Default value: `title`.
+  EOT
+
+  validation {
+    condition     = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
 }
 
+variable "label_value_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of output label values (also used in `tags` and `id`).
+    Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+    Default value: `lower`.
+  EOT
+
+  validation {
+    condition     = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
+}
 #### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/examples/team/context.tf b/examples/team/context.tf
index bae0cf1..81f99b4 100644
--- a/examples/team/context.tf
+++ b/examples/team/context.tf
@@ -19,7 +19,8 @@
 #
 
 module "this" {
-  source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.19.2"
+  source  = "cloudposse/label/null"
+  version = "0.24.1" # requires Terraform >= 0.13.0
 
   enabled             = var.enabled
   namespace           = var.namespace
@@ -33,6 +34,8 @@ module "this" {
   label_order         = var.label_order
   regex_replace_chars = var.regex_replace_chars
   id_length_limit     = var.id_length_limit
+  label_key_case      = var.label_key_case
+  label_value_case    = var.label_value_case
 
   context = var.context
 }
@@ -40,20 +43,7 @@ module "this" {
 # Copy contents of cloudposse/terraform-null-label/variables.tf here
 
 variable "context" {
-  type = object({
-    enabled             = bool
-    namespace           = string
-    environment         = string
-    stage               = string
-    name                = string
-    delimiter           = string
-    attributes          = list(string)
-    tags                = map(string)
-    additional_tag_map  = map(string)
-    regex_replace_chars = string
-    label_order         = list(string)
-    id_length_limit     = number
-  })
+  type = any
   default = {
     enabled             = true
     namespace           = null
@@ -67,6 +57,8 @@ variable "context" {
     regex_replace_chars = null
     label_order         = []
     id_length_limit     = null
+    label_key_case      = null
+    label_value_case    = null
   }
   description = <<-EOT
     Single object for setting entire context at once.
@@ -75,6 +67,16 @@ variable "context" {
     Individual variable settings (non-null) override settings in context object,
     except for attributes, tags, and additional_tag_map, which are merged.
   EOT
+
+  validation {
+    condition     = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
+
+  validation {
+    condition     = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
 }
 
 variable "enabled" {
@@ -157,11 +159,44 @@ variable "id_length_limit" {
   type        = number
   default     = null
   description = <<-EOT
-    Limit `id` to this many characters.
+    Limit `id` to this many characters (minimum 6).
     Set to `0` for unlimited length.
     Set to `null` for default, which is `0`.
     Does not affect `id_full`.
   EOT
+  validation {
+    condition     = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0
+    error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length."
+  }
+}
+
+variable "label_key_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+    Possible values: `lower`, `title`, `upper`.
+    Default value: `title`.
+  EOT
+
+  validation {
+    condition     = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
 }
 
+variable "label_value_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of output label values (also used in `tags` and `id`).
+    Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+    Default value: `lower`.
+  EOT
+
+  validation {
+    condition     = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
+}
 #### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/examples/team_routing_rule/context.tf b/examples/team_routing_rule/context.tf
index bae0cf1..81f99b4 100644
--- a/examples/team_routing_rule/context.tf
+++ b/examples/team_routing_rule/context.tf
@@ -19,7 +19,8 @@
 #
 
 module "this" {
-  source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.19.2"
+  source  = "cloudposse/label/null"
+  version = "0.24.1" # requires Terraform >= 0.13.0
 
   enabled             = var.enabled
   namespace           = var.namespace
@@ -33,6 +34,8 @@ module "this" {
   label_order         = var.label_order
   regex_replace_chars = var.regex_replace_chars
   id_length_limit     = var.id_length_limit
+  label_key_case      = var.label_key_case
+  label_value_case    = var.label_value_case
 
   context = var.context
 }
@@ -40,20 +43,7 @@ module "this" {
 # Copy contents of cloudposse/terraform-null-label/variables.tf here
 
 variable "context" {
-  type = object({
-    enabled             = bool
-    namespace           = string
-    environment         = string
-    stage               = string
-    name                = string
-    delimiter           = string
-    attributes          = list(string)
-    tags                = map(string)
-    additional_tag_map  = map(string)
-    regex_replace_chars = string
-    label_order         = list(string)
-    id_length_limit     = number
-  })
+  type = any
   default = {
     enabled             = true
     namespace           = null
@@ -67,6 +57,8 @@ variable "context" {
     regex_replace_chars = null
     label_order         = []
     id_length_limit     = null
+    label_key_case      = null
+    label_value_case    = null
   }
   description = <<-EOT
     Single object for setting entire context at once.
@@ -75,6 +67,16 @@ variable "context" {
     Individual variable settings (non-null) override settings in context object,
     except for attributes, tags, and additional_tag_map, which are merged.
   EOT
+
+  validation {
+    condition     = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
+
+  validation {
+    condition     = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
 }
 
 variable "enabled" {
@@ -157,11 +159,44 @@ variable "id_length_limit" {
   type        = number
   default     = null
   description = <<-EOT
-    Limit `id` to this many characters.
+    Limit `id` to this many characters (minimum 6).
     Set to `0` for unlimited length.
     Set to `null` for default, which is `0`.
     Does not affect `id_full`.
   EOT
+  validation {
+    condition     = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0
+    error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length."
+  }
+}
+
+variable "label_key_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+    Possible values: `lower`, `title`, `upper`.
+    Default value: `title`.
+  EOT
+
+  validation {
+    condition     = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
 }
 
+variable "label_value_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of output label values (also used in `tags` and `id`).
+    Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+    Default value: `lower`.
+  EOT
+
+  validation {
+    condition     = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
+}
 #### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/examples/user/context.tf b/examples/user/context.tf
index bae0cf1..81f99b4 100644
--- a/examples/user/context.tf
+++ b/examples/user/context.tf
@@ -19,7 +19,8 @@
 #
 
 module "this" {
-  source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.19.2"
+  source  = "cloudposse/label/null"
+  version = "0.24.1" # requires Terraform >= 0.13.0
 
   enabled             = var.enabled
   namespace           = var.namespace
@@ -33,6 +34,8 @@ module "this" {
   label_order         = var.label_order
   regex_replace_chars = var.regex_replace_chars
   id_length_limit     = var.id_length_limit
+  label_key_case      = var.label_key_case
+  label_value_case    = var.label_value_case
 
   context = var.context
 }
@@ -40,20 +43,7 @@ module "this" {
 # Copy contents of cloudposse/terraform-null-label/variables.tf here
 
 variable "context" {
-  type = object({
-    enabled             = bool
-    namespace           = string
-    environment         = string
-    stage               = string
-    name                = string
-    delimiter           = string
-    attributes          = list(string)
-    tags                = map(string)
-    additional_tag_map  = map(string)
-    regex_replace_chars = string
-    label_order         = list(string)
-    id_length_limit     = number
-  })
+  type = any
   default = {
     enabled             = true
     namespace           = null
@@ -67,6 +57,8 @@ variable "context" {
     regex_replace_chars = null
     label_order         = []
     id_length_limit     = null
+    label_key_case      = null
+    label_value_case    = null
   }
   description = <<-EOT
     Single object for setting entire context at once.
@@ -75,6 +67,16 @@ variable "context" {
     Individual variable settings (non-null) override settings in context object,
     except for attributes, tags, and additional_tag_map, which are merged.
   EOT
+
+  validation {
+    condition     = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
+
+  validation {
+    condition     = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
 }
 
 variable "enabled" {
@@ -157,11 +159,44 @@ variable "id_length_limit" {
   type        = number
   default     = null
   description = <<-EOT
-    Limit `id` to this many characters.
+    Limit `id` to this many characters (minimum 6).
     Set to `0` for unlimited length.
     Set to `null` for default, which is `0`.
     Does not affect `id_full`.
   EOT
+  validation {
+    condition     = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0
+    error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length."
+  }
+}
+
+variable "label_key_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+    Possible values: `lower`, `title`, `upper`.
+    Default value: `title`.
+  EOT
+
+  validation {
+    condition     = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
 }
 
+variable "label_value_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of output label values (also used in `tags` and `id`).
+    Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+    Default value: `lower`.
+  EOT
+
+  validation {
+    condition     = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
+}
 #### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/main.tf b/main.tf
index 030e551..c766d3c 100644
--- a/main.tf
+++ b/main.tf
@@ -20,6 +20,12 @@ module "escalation" {
   escalation = var.escalation
 }
 
+module "integration_action" {
+  source = "./modules/integration_action"
+
+  integration_action = var.integration_action
+}
+
 module "notification_policy" {
   source = "./modules/notification_policy"
 
diff --git a/modules/config/integration_actions.tf b/modules/config/integration_actions.tf
new file mode 100644
index 0000000..2624f14
--- /dev/null
+++ b/modules/config/integration_actions.tf
@@ -0,0 +1,148 @@
+resource "opsgenie_integration_action" "this" {
+  for_each = module.this.enabled ? { for integration_action in local.integration_actions : integration_action.name => integration_action } : {}
+
+  # Look up our integration id by name
+  integration_id = try(opsgenie_api_integration.this[each.value.integration_name].id, null)
+
+  dynamic "create" {
+    for_each = try(each.value.create, [])
+
+    content {
+      name                           = try(create.value.name, null)
+      order                          = try(create.value.order, null)
+      tags                           = try(create.value.tags, [])
+      user                           = try(create.value.user, null)
+      note                           = try(create.value.note, null)
+      alias                          = try(create.value.alias, null)
+      source                         = try(create.value.source, null)
+      message                        = try(create.value.message, null)
+      description                    = try(create.value.description, null)
+      entity                         = try(create.value.entity, null)
+      alert_actions                  = try(create.value.alert_actions, [])
+      extra_properties               = try(create.value.extra_properties, {})
+      custom_priority                = try(create.value.custom_priority, null)
+      ignore_responders_from_payload = try(create.value.ignore_responders_from_payload, false)
+      ignore_teams_from_payload      = try(create.value.ignore_teams_from_payload, false)
+
+      responders {
+        id   = try(opsgenie_api_integration.this[each.value.integration_name].owner_team_id, null)
+        type = "team"
+      }
+
+      filter {
+        type = try(create.value.filter.type, null)
+
+        dynamic "conditions" {
+          for_each = try(create.value.filter.conditions, [])
+
+          content {
+            field          = try(conditions.value.field, null)
+            not            = try(conditions.value.not, null)
+            operation      = try(conditions.value.operation, null)
+            expected_value = try(conditions.value.expected_value, null)
+          }
+        }
+      }
+    }
+  }
+
+  dynamic "close" {
+    for_each = try(each.value.close, [])
+
+    content {
+      name  = try(close.value.name, null)
+      order = try(close.value.order, null)
+      alias = try(close.value.alias, null)
+
+      filter {
+        type = try(close.value.filter.type, null)
+
+        dynamic "conditions" {
+          for_each = try(close.value.filter.conditions, [])
+
+          content {
+            field          = try(conditions.value.field, null)
+            not            = try(conditions.value.not, null)
+            operation      = try(conditions.value.operation, null)
+            expected_value = try(conditions.value.expected_value, null)
+          }
+        }
+      }
+    }
+  }
+
+  dynamic "acknowledge" {
+    for_each = try(each.value.acknowledge, [])
+
+    content {
+      name  = try(acknowledge.value.name, null)
+      order = try(acknowledge.value.order, null)
+      alias = try(acknowledge.value.alias, null)
+
+      filter {
+        type = try(acknowledge.value.filter.type, null)
+
+        dynamic "conditions" {
+          for_each = try(acknowledge.value.filter.conditions, [])
+
+          content {
+            field          = try(conditions.value.field, null)
+            not            = try(conditions.value.not, null)
+            operation      = try(conditions.value.operation, null)
+            expected_value = try(conditions.value.expected_value, null)
+          }
+        }
+      }
+    }
+  }
+
+  dynamic "add_note" {
+    for_each = try(each.value.add_note, [])
+
+    content {
+      name  = try(add_note.value.name, null)
+      order = try(add_note.value.order, null)
+      alias = try(add_note.value.alias, null)
+      note  = try(add_note.value.name, null)
+
+      filter {
+        type = try(add_note.value.filter.type, null)
+
+        dynamic "conditions" {
+          for_each = try(add_note.value.filter.conditions, [])
+
+          content {
+            field          = try(conditions.value.field, null)
+            not            = try(conditions.value.not, null)
+            operation      = try(conditions.value.operation, null)
+            expected_value = try(conditions.value.expected_value, null)
+          }
+        }
+      }
+    }
+  }
+
+  dynamic "ignore" {
+    for_each = try(each.value.ignore, [])
+
+    content {
+      name  = try(ignore.value.name, null)
+      order = try(ignore.value.order, null)
+
+      filter {
+        type = try(ignore.value.filter.type, null)
+
+        dynamic "conditions" {
+          for_each = try(ignore.value.filter.conditions, [])
+
+          content {
+            field          = try(conditions.value.field, null)
+            not            = try(conditions.value.not, null)
+            operation      = try(conditions.value.operation, null)
+            expected_value = try(conditions.value.expected_value, null)
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/modules/config/main.tf b/modules/config/main.tf
index 1b368f2..4795126 100644
--- a/modules/config/main.tf
+++ b/modules/config/main.tf
@@ -2,6 +2,7 @@ locals {
   alert_policies         = lookup(var.opsgenie_resources, "alert_policies", [])
   api_integrations       = lookup(var.opsgenie_resources, "api_integrations", [])
   escalations            = lookup(var.opsgenie_resources, "escalations", [])
+  integration_actions    = lookup(var.opsgenie_resources, "integration_actions", [])
   notification_policies  = lookup(var.opsgenie_resources, "notification_policies", [])
   team_routing_rules     = lookup(var.opsgenie_resources, "team_routing_rules", [])
   teams                  = lookup(var.opsgenie_resources, "teams", [])
diff --git a/modules/config/outputs.tf b/modules/config/outputs.tf
index 4cc8927..4b9e726 100644
--- a/modules/config/outputs.tf
+++ b/modules/config/outputs.tf
@@ -27,6 +27,13 @@ output "escalations" {
   description = "Escalations"
 }
 
+output "integration_actions" {
+  value = [
+    for integration_action in opsgenie_integration_action.this : integration_action.id
+  ]
+  description = "Integration Actions"
+}
+
 output "notification_policies" {
   value = {
     for policy in opsgenie_notification_policy.this : policy.id => policy.name
diff --git a/modules/integration_action/README.md b/modules/integration_action/README.md
new file mode 100644
index 0000000..9746afb
--- /dev/null
+++ b/modules/integration_action/README.md
@@ -0,0 +1,57 @@
+## Integration Action
+
+Terraform module to configure [Opsgenie Integration Action](https://registry.terraform.io/providers/opsgenie/opsgenie/latest/docs/resources/integration_action)
+
+NOTE: your OpsGenie plan must support advanced integrations. Otherwise, you will get the following error back from the API: `Your plan does not allow saving advanced integrations.`.
+
+
+## Usage
+
+[Create Opsgenie Integration Action example](../../examples/integration_action)
+
+```hcl
+module "integration_action" {
+  source  = "cloudposse/incident-management/opsgenie//modules/integration_action"
+  # Cloud Posse recommends pinning every module to a specific version
+  # version     = "x.x.x"
+
+  integration_action = {
+    integration_id = module.api_integration.api_integration_id
+
+    create = [
+      {
+        name  = "Create Non-informational Alerts"
+        alias = "{{title}}"
+        filter = {
+          type = "match-all-conditions"
+          conditions = [
+            {
+              field          = "priority"
+              not            = true
+              operation      = "equals"
+              expected_value = "P5"
+            }
+          ]
+        }
+      }
+    ]
+  }
+}
+```
+
+## Inputs
+
+**Note:** `integration_action` is a map for two reasons: 
+- to be able to put whole configuration in yaml file
+- variables defined with type set are not robust enough (can't set default values)
+
+|  Name                          |  Default                          |  Description                                                                                                                    | Required |
+|:-------------------------------|:---------------------------------:|:--------------------------------------------------------------------------------------------------------------------------------|:--------:|
+| `integration_action`          | `{}`                              | This variable is used to configure Opsgenie Integration Action.                                                                | Yes      |
+
+
+## Outputs
+
+| Name                        | Description                                  |
+|:----------------------------|:---------------------------------------------|
+| `integration_action_id`    | The ID of the Opsgenie Integration Action.  |
diff --git a/modules/integration_action/context.tf b/modules/integration_action/context.tf
new file mode 100644
index 0000000..81f99b4
--- /dev/null
+++ b/modules/integration_action/context.tf
@@ -0,0 +1,202 @@
+#
+# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label
+# All other instances of this file should be a copy of that one
+#
+#
+# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf
+# and then place it in your Terraform module to automatically get
+# Cloud Posse's standard configuration inputs suitable for passing
+# to Cloud Posse modules.
+#
+# Modules should access the whole context as `module.this.context`
+# to get the input variables with nulls for defaults,
+# for example `context = module.this.context`,
+# and access individual variables as `module.this.<var>`,
+# with final values filled in.
+#
+# For example, when using defaults, `module.this.context.delimiter`
+# will be null, and `module.this.delimiter` will be `-` (hyphen).
+#
+
+module "this" {
+  source  = "cloudposse/label/null"
+  version = "0.24.1" # requires Terraform >= 0.13.0
+
+  enabled             = var.enabled
+  namespace           = var.namespace
+  environment         = var.environment
+  stage               = var.stage
+  name                = var.name
+  delimiter           = var.delimiter
+  attributes          = var.attributes
+  tags                = var.tags
+  additional_tag_map  = var.additional_tag_map
+  label_order         = var.label_order
+  regex_replace_chars = var.regex_replace_chars
+  id_length_limit     = var.id_length_limit
+  label_key_case      = var.label_key_case
+  label_value_case    = var.label_value_case
+
+  context = var.context
+}
+
+# Copy contents of cloudposse/terraform-null-label/variables.tf here
+
+variable "context" {
+  type = any
+  default = {
+    enabled             = true
+    namespace           = null
+    environment         = null
+    stage               = null
+    name                = null
+    delimiter           = null
+    attributes          = []
+    tags                = {}
+    additional_tag_map  = {}
+    regex_replace_chars = null
+    label_order         = []
+    id_length_limit     = null
+    label_key_case      = null
+    label_value_case    = null
+  }
+  description = <<-EOT
+    Single object for setting entire context at once.
+    See description of individual variables for details.
+    Leave string and numeric variables as `null` to use default value.
+    Individual variable settings (non-null) override settings in context object,
+    except for attributes, tags, and additional_tag_map, which are merged.
+  EOT
+
+  validation {
+    condition     = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
+
+  validation {
+    condition     = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
+}
+
+variable "enabled" {
+  type        = bool
+  default     = null
+  description = "Set to false to prevent the module from creating any resources"
+}
+
+variable "namespace" {
+  type        = string
+  default     = null
+  description = "Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp'"
+}
+
+variable "environment" {
+  type        = string
+  default     = null
+  description = "Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT'"
+}
+
+variable "stage" {
+  type        = string
+  default     = null
+  description = "Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release'"
+}
+
+variable "name" {
+  type        = string
+  default     = null
+  description = "Solution name, e.g. 'app' or 'jenkins'"
+}
+
+variable "delimiter" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.
+    Defaults to `-` (hyphen). Set to `""` to use no delimiter at all.
+  EOT
+}
+
+variable "attributes" {
+  type        = list(string)
+  default     = []
+  description = "Additional attributes (e.g. `1`)"
+}
+
+variable "tags" {
+  type        = map(string)
+  default     = {}
+  description = "Additional tags (e.g. `map('BusinessUnit','XYZ')`"
+}
+
+variable "additional_tag_map" {
+  type        = map(string)
+  default     = {}
+  description = "Additional tags for appending to tags_as_list_of_maps. Not added to `tags`."
+}
+
+variable "label_order" {
+  type        = list(string)
+  default     = null
+  description = <<-EOT
+    The naming order of the id output and Name tag.
+    Defaults to ["namespace", "environment", "stage", "name", "attributes"].
+    You can omit any of the 5 elements, but at least one must be present.
+  EOT
+}
+
+variable "regex_replace_chars" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.
+    If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits.
+  EOT
+}
+
+variable "id_length_limit" {
+  type        = number
+  default     = null
+  description = <<-EOT
+    Limit `id` to this many characters (minimum 6).
+    Set to `0` for unlimited length.
+    Set to `null` for default, which is `0`.
+    Does not affect `id_full`.
+  EOT
+  validation {
+    condition     = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0
+    error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length."
+  }
+}
+
+variable "label_key_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
+    Possible values: `lower`, `title`, `upper`.
+    Default value: `title`.
+  EOT
+
+  validation {
+    condition     = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`."
+  }
+}
+
+variable "label_value_case" {
+  type        = string
+  default     = null
+  description = <<-EOT
+    The letter case of output label values (also used in `tags` and `id`).
+    Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+    Default value: `lower`.
+  EOT
+
+  validation {
+    condition     = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+    error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+  }
+}
+#### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/modules/integration_action/main.tf b/modules/integration_action/main.tf
new file mode 100644
index 0000000..5d3fb93
--- /dev/null
+++ b/modules/integration_action/main.tf
@@ -0,0 +1,147 @@
+resource "opsgenie_integration_action" "this" {
+  count = module.this.enabled ? 1 : 0
+
+  integration_id = var.integration_action.integration_id
+
+  dynamic "create" {
+    for_each = try(var.integration_action.create, [])
+
+    content {
+      name                           = try(create.value.name, null)
+      order                          = try(create.value.order, null)
+      tags                           = try(create.value.tags, [])
+      user                           = try(create.value.user, null)
+      note                           = try(create.value.note, null)
+      alias                          = try(create.value.alias, null)
+      source                         = try(create.value.source, null)
+      message                        = try(create.value.message, null)
+      description                    = try(create.value.description, null)
+      entity                         = try(create.value.entity, null)
+      alert_actions                  = try(create.value.alert_actions, [])
+      extra_properties               = try(create.value.extra_properties, {})
+      custom_priority                = try(create.value.custom_priority, null)
+      ignore_responders_from_payload = try(create.value.ignore_responders_from_payload, false)
+      ignore_teams_from_payload      = try(create.value.ignore_teams_from_payload, false)
+
+      responders {
+        id   = var.integration_action.team_id
+        type = "team"
+      }
+
+      filter {
+        type = try(create.value.filter.type, null)
+
+        dynamic "conditions" {
+          for_each = try(create.value.filter.conditions, [])
+
+          content {
+            field          = try(conditions.value.field, null)
+            not            = try(conditions.value.not, null)
+            operation      = try(conditions.value.operation, null)
+            expected_value = try(conditions.value.expected_value, null)
+          }
+        }
+      }
+    }
+  }
+
+  dynamic "close" {
+    for_each = try(var.integration_action.close, [])
+
+    content {
+      name  = try(close.value.name, null)
+      order = try(close.value.order, null)
+      alias = try(close.value.alias, null)
+
+      filter {
+        type = try(close.value.filter.type, null)
+
+        dynamic "conditions" {
+          for_each = try(close.value.filter.conditions, [])
+
+          content {
+            field          = try(conditions.value.field, null)
+            not            = try(conditions.value.not, null)
+            operation      = try(conditions.value.operation, null)
+            expected_value = try(conditions.value.expected_value, null)
+          }
+        }
+      }
+    }
+  }
+
+  dynamic "acknowledge" {
+    for_each = try(var.integration_action.acknowledge, [])
+
+    content {
+      name  = try(acknowledge.value.name, null)
+      order = try(acknowledge.value.order, null)
+      alias = try(acknowledge.value.alias, null)
+
+      filter {
+        type = try(acknowledge.value.filter.type, null)
+
+        dynamic "conditions" {
+          for_each = try(acknowledge.value.filter.conditions, [])
+
+          content {
+            field          = try(conditions.value.field, null)
+            not            = try(conditions.value.not, null)
+            operation      = try(conditions.value.operation, null)
+            expected_value = try(conditions.value.expected_value, null)
+          }
+        }
+      }
+    }
+  }
+
+  dynamic "add_note" {
+    for_each = try(var.integration_action.add_note, [])
+
+    content {
+      name  = try(add_note.value.name, null)
+      order = try(add_note.value.order, null)
+      alias = try(add_note.value.alias, null)
+      note  = try(add_note.value.name, null)
+
+      filter {
+        type = try(add_note.value.filter.type, null)
+
+        dynamic "conditions" {
+          for_each = try(add_note.value.filter.conditions, [])
+
+          content {
+            field          = try(conditions.value.field, null)
+            not            = try(conditions.value.not, null)
+            operation      = try(conditions.value.operation, null)
+            expected_value = try(conditions.value.expected_value, null)
+          }
+        }
+      }
+    }
+  }
+
+  dynamic "ignore" {
+    for_each = try(var.integration_action.ignore, [])
+
+    content {
+      name  = try(ignore.value.name, null)
+      order = try(ignore.value.order, null)
+
+      filter {
+        type = try(ignore.value.filter.type, null)
+
+        dynamic "conditions" {
+          for_each = try(ignore.value.filter.conditions, [])
+
+          content {
+            field          = try(conditions.value.field, null)
+            not            = try(conditions.value.not, null)
+            operation      = try(conditions.value.operation, null)
+            expected_value = try(conditions.value.expected_value, null)
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/modules/integration_action/outputs.tf b/modules/integration_action/outputs.tf
new file mode 100644
index 0000000..c510179
--- /dev/null
+++ b/modules/integration_action/outputs.tf
@@ -0,0 +1,4 @@
+output "integration_action_id" {
+  description = "The ID of the Opsgenie Integration Action"
+  value       = try(opsgenie_integration_action.this[0].id, null)
+}
diff --git a/modules/integration_action/variables.tf b/modules/integration_action/variables.tf
new file mode 100644
index 0000000..1eee418
--- /dev/null
+++ b/modules/integration_action/variables.tf
@@ -0,0 +1,4 @@
+variable "integration_action" {
+  default     = {}
+  description = "Opsgenie Integration Action configuration"
+}
diff --git a/modules/integration_action/versions.tf b/modules/integration_action/versions.tf
new file mode 100644
index 0000000..240bbd5
--- /dev/null
+++ b/modules/integration_action/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+  required_version = ">= 0.13.0"
+
+  required_providers {
+    opsgenie = {
+      source  = "opsgenie/opsgenie"
+      version = ">= 0.4"
+    }
+  }
+}
diff --git a/outputs.tf b/outputs.tf
index 876eeb2..dacecf8 100644
--- a/outputs.tf
+++ b/outputs.tf
@@ -54,6 +54,11 @@ output "escalation_name" {
   value       = module.escalation.escalation_name
 }
 
+output "integration_action_id" {
+  description = "The ID of the Opsgenie Integration Action"
+  value       = module.integration_action.integration_action_id
+}
+
 output "notification_policy_id" {
   description = "The ID of the Opsgenie Notification Policy"
   value       = module.notification_policy.notification_policy_id
diff --git a/test/src/examples_advanced_features.go b/test/src/examples_advanced_features.go
new file mode 100644
index 0000000..23267eb
--- /dev/null
+++ b/test/src/examples_advanced_features.go
@@ -0,0 +1,24 @@
+package test
+
+import (
+	"github.com/gruntwork-io/terratest/modules/terraform"
+	"testing"
+)
+
+
+// Test the Terraform module in examples/advanced_features using Terratest.
+func TestExamplesAdvancedFeatures(t *testing.T) {
+	terraformOptions := &terraform.Options{
+		// The path to where our Terraform code is located
+		TerraformDir: "../../examples/advanced_features",
+		Upgrade:      true,
+		// Variables to pass to our Terraform code using -var-file options
+		VarFiles: []string{"fixtures.tfvars"},
+	}
+
+	// At the end of the test, run `terraform destroy` to clean up any resources that were created
+	defer terraform.Destroy(t, terraformOptions)
+
+	// This will run `terraform init` and `terraform apply` and fail the test if there are any errors
+	terraform.InitAndApply(t, terraformOptions)
+}
diff --git a/test/src/examples_integration_action.go b/test/src/examples_integration_action.go
new file mode 100644
index 0000000..2b88e89
--- /dev/null
+++ b/test/src/examples_integration_action.go
@@ -0,0 +1,24 @@
+package test
+
+import (
+	"github.com/gruntwork-io/terratest/modules/terraform"
+	"testing"
+)
+
+
+// Test the Terraform module in examples/integration_action using Terratest.
+func TestExamplesIntegrationAction(t *testing.T) {
+	terraformOptions := &terraform.Options{
+		// The path to where our Terraform code is located
+		TerraformDir: "../../examples/integration_action",
+		Upgrade:      true,
+		// Variables to pass to our Terraform code using -var-file options
+		VarFiles: []string{"fixtures.tfvars"},
+	}
+
+  // At the end of the test, run `terraform destroy` to clean up any resources that were created
+	defer terraform.Destroy(t, terraformOptions)
+
+	// This will run `terraform init` and `terraform apply` and fail the test if there are any errors
+	terraform.InitAndApply(t, terraformOptions)
+}
diff --git a/variables.tf b/variables.tf
index d818dde..9ad736a 100644
--- a/variables.tf
+++ b/variables.tf
@@ -19,6 +19,11 @@ variable "escalation" {
   description = "Opsgenie Escalation configuration"
 }
 
+variable "integration_action" {
+  default     = {}
+  description = "Opsgenie Integration Action configuration"
+}
+
 variable "notification_policy" {
   default     = {}
   description = "Opsgenie Notification Policy configuration"