diff --git a/docs/data-sources/alb_waf_custom_rule_group.md b/docs/data-sources/alb_waf_custom_rule_group.md
new file mode 100644
index 000000000..4adfc985d
--- /dev/null
+++ b/docs/data-sources/alb_waf_custom_rule_group.md
@@ -0,0 +1,87 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "stackit_alb_waf_custom_rule_group Data Source - stackit"
+subcategory: ""
+description: |-
+ ALB WAF Custom Rule Group resource schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on resource level.
+ ~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
+---
+
+# stackit_alb_waf_custom_rule_group (Data Source)
+
+ALB WAF Custom Rule Group resource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on resource level.
+
+~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
+
+## Example Usage
+
+```terraform
+data "stackit_alb_waf_custom_rule_group" "example" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ name = "example-custom-rule-group"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `name` (String) Custom rule group configuration name.
+- `project_id` (String) STACKIT project ID associated with the ALB WAF Custom Rule Group.
+
+### Optional
+
+- `region` (String) STACKIT region name the resource is located in. If not defined, the provider region is used.
+
+### Read-Only
+
+- `id` (String) Terraform's internal resource identifier. Structured as "`project_id`,`region`,`name`".
+- `rules` (Attributes List) Enriched rules containing auto-generated IDs and computed severity values. (see [below for nested schema](#nestedatt--rules))
+
+
+### Nested Schema for `rules`
+
+Read-Only:
+
+- `behavior` (Attributes) (see [below for nested schema](#nestedatt--rules--behavior))
+- `conditions` (Attributes List) (see [below for nested schema](#nestedatt--rules--conditions))
+- `description` (String) A clear description explaining the threat vector or criteria addressed by this rule.
+- `id` (Number) Backend auto-allocated unique rule ID within the valid 1-99999 threshold.
+
+
+### Nested Schema for `rules.behavior`
+
+Read-Only:
+
+- `action` (String) The protective stance action. ACTION_DENY forces a 403 status response code.
+- `log` (Boolean) Determines whether an entry should be generated in the security ledger upon a rule hit.
+- `log_msg` (String) Custom notification message string mapped to underlying logdata contexts. Required if log is true.
+- `severity` (String) Severity classification metric used by internal analytics graphs.
+
+
+
+### Nested Schema for `rules.conditions`
+
+Read-Only:
+
+- `operator` (Attributes) The comparison logic executed against the transformed variable. (see [below for nested schema](#nestedatt--rules--conditions--operator))
+- `transformations` (List of String) Ordered normalization steps applied before the operator runs.
+- `variable` (Attributes) The part of the HTTP transaction to inspect. (see [below for nested schema](#nestedatt--rules--conditions--variable))
+
+
+### Nested Schema for `rules.conditions.operator`
+
+Read-Only:
+
+- `type` (String) The operational evaluation type definition macro.
+- `value` (String) The text or rule regex pattern arguments applied inside the operator execution loop.
+
+
+
+### Nested Schema for `rules.conditions.variable`
+
+Read-Only:
+
+- `type` (String) The targeted validation engine variable macro.
+- `value` (String) Optional key element context for map variables (e.g., matching a 'Host' header key).
diff --git a/docs/data-sources/alb_waf_managed_rule_set.md b/docs/data-sources/alb_waf_managed_rule_set.md
index 1f335dabf..c14a428bb 100644
--- a/docs/data-sources/alb_waf_managed_rule_set.md
+++ b/docs/data-sources/alb_waf_managed_rule_set.md
@@ -39,7 +39,6 @@ data "stackit_alb_waf_managed_rule_set" "example" {
- `groups` (Attributes Map) Inventory of all available Managed Rule Set groups and their current configuration. (see [below for nested schema](#nestedatt--groups))
- `id` (String) Terraform's internal resource identifier. Structured as "`project_id`,`region`,`name`".
- `type` (String) Type of the Managed Rule Set.
-- `usage` (Attributes) Managed Rule Set usage (see [below for nested schema](#nestedatt--usage))
- `version` (String) Managed Rule Set version.
@@ -59,13 +58,3 @@ Read-Only:
- `description` (String) A description of what this rule does.
- `mode` (String) The current mode of the rule.
- `severity` (String) Impact level.
-
-
-
-
-### Nested Schema for `usage`
-
-Read-Only:
-
-- `count` (Number) Number of WAFs using this Managed Rule Set.
-- `items` (List of String) List of WAFs that use this Managed Rule Set.
diff --git a/docs/resources/alb_waf_custom_rule_group.md b/docs/resources/alb_waf_custom_rule_group.md
new file mode 100644
index 000000000..e5bcee6a1
--- /dev/null
+++ b/docs/resources/alb_waf_custom_rule_group.md
@@ -0,0 +1,133 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "stackit_alb_waf_custom_rule_group Resource - stackit"
+subcategory: ""
+description: |-
+ ALB WAF Custom Rule Group resource schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on resource level.
+ ~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources.
+---
+
+# stackit_alb_waf_custom_rule_group (Resource)
+
+ALB WAF Custom Rule Group resource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on resource level.
+
+~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources.
+
+## Example Usage
+
+```terraform
+resource "stackit_alb_waf_custom_rule_group" "example" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ name = "example-custom-rule-group"
+ rules = [
+ {
+ description = "My custom rule group"
+ behavior = {
+ action = "ACTION_DENY"
+ log = true
+ logMsg = "Some custom notification message string"
+ }
+ conditions = [
+ {
+ operator = {
+ type = "OPERATOR_BEGINS_WITH"
+ value = "allowed objects"
+ }
+ transformations = [
+ "TRANSFORMATION_LOWERCASE"
+ ]
+ variable = {
+ type = "VARIABLE_REQUEST_HEADERS"
+ value = "Host"
+ }
+ }
+ ]
+ }
+ ]
+}
+```
+
+
+## Schema
+
+### Required
+
+- `name` (String) Custom rule group configuration name.
+- `project_id` (String) STACKIT project ID associated with the ALB WAF Custom Rule Group.
+- `rules` (Attributes List) Enriched rules containing auto-generated IDs and computed severity values. (see [below for nested schema](#nestedatt--rules))
+
+### Optional
+
+- `region` (String) STACKIT region name the resource is located in. If not defined, the provider region is used.
+
+### Read-Only
+
+- `id` (String) Terraform's internal resource identifier. Structured as "`project_id`,`region`,`name`".
+
+
+### Nested Schema for `rules`
+
+Required:
+
+- `behavior` (Attributes) (see [below for nested schema](#nestedatt--rules--behavior))
+- `conditions` (Attributes List) (see [below for nested schema](#nestedatt--rules--conditions))
+
+Optional:
+
+- `description` (String) A clear description explaining the threat vector or criteria addressed by this rule.
+
+Read-Only:
+
+- `id` (Number) Backend auto-allocated unique rule ID within the valid 1-99999 threshold.
+
+
+### Nested Schema for `rules.behavior`
+
+Required:
+
+- `action` (String) The protective stance action. ACTION_DENY forces a 403 status response code.
+
+Optional:
+
+- `log` (Boolean) Determines whether an entry should be generated in the security ledger upon a rule hit.
+- `log_msg` (String) Custom notification message string mapped to underlying logdata contexts. Required if log is true.
+
+Read-Only:
+
+- `severity` (String) Severity classification metric used by internal analytics graphs.
+
+
+
+### Nested Schema for `rules.conditions`
+
+Required:
+
+- `operator` (Attributes) The comparison logic executed against the transformed variable. (see [below for nested schema](#nestedatt--rules--conditions--operator))
+- `variable` (Attributes) The part of the HTTP transaction to inspect. (see [below for nested schema](#nestedatt--rules--conditions--variable))
+
+Optional:
+
+- `transformations` (List of String) Ordered normalization steps applied before the operator runs.
+
+
+### Nested Schema for `rules.conditions.operator`
+
+Required:
+
+- `type` (String) The operational evaluation type definition macro.
+
+Optional:
+
+- `value` (String) The text or rule regex pattern arguments applied inside the operator execution loop.
+
+
+
+### Nested Schema for `rules.conditions.variable`
+
+Required:
+
+- `type` (String) The targeted validation engine variable macro.
+
+Optional:
+
+- `value` (String) Optional key element context for map variables (e.g., matching a 'Host' header key).
diff --git a/docs/resources/alb_waf_managed_rule_set.md b/docs/resources/alb_waf_managed_rule_set.md
index eeb2c93f3..390e30ded 100644
--- a/docs/resources/alb_waf_managed_rule_set.md
+++ b/docs/resources/alb_waf_managed_rule_set.md
@@ -40,7 +40,6 @@ resource "stackit_alb_waf_managed_rule_set" "example" {
- `groups` (Attributes Map) Inventory of all available Managed Rule Set groups and their current configuration. (see [below for nested schema](#nestedatt--groups))
- `id` (String) Terraform's internal resource identifier. Structured as "`project_id`,`region`,`name`".
-- `usage` (Attributes) Managed Rule Set usage (see [below for nested schema](#nestedatt--usage))
- `version` (String) Managed Rule Set version.
@@ -60,13 +59,3 @@ Read-Only:
- `description` (String) A description of what this rule does.
- `mode` (String) The current mode of the rule.
- `severity` (String) Impact level.
-
-
-
-
-### Nested Schema for `usage`
-
-Read-Only:
-
-- `count` (Number) Number of WAFs using this Managed Rule Set.
-- `items` (List of String) List of WAFs that use this Managed Rule Set.
diff --git a/examples/data-sources/stackit_alb_waf_custom_rule_group/data-source.tf b/examples/data-sources/stackit_alb_waf_custom_rule_group/data-source.tf
new file mode 100644
index 000000000..1182aaac4
--- /dev/null
+++ b/examples/data-sources/stackit_alb_waf_custom_rule_group/data-source.tf
@@ -0,0 +1,4 @@
+data "stackit_alb_waf_custom_rule_group" "example" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ name = "example-custom-rule-group"
+}
diff --git a/examples/resources/stackit_alb_waf_custom_rule_group/resource.tf b/examples/resources/stackit_alb_waf_custom_rule_group/resource.tf
new file mode 100644
index 000000000..b34c43a61
--- /dev/null
+++ b/examples/resources/stackit_alb_waf_custom_rule_group/resource.tf
@@ -0,0 +1,29 @@
+resource "stackit_alb_waf_custom_rule_group" "example" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ name = "example-custom-rule-group"
+ rules = [
+ {
+ description = "My custom rule group"
+ behavior = {
+ action = "ACTION_DENY"
+ log = true
+ logMsg = "Some custom notification message string"
+ }
+ conditions = [
+ {
+ operator = {
+ type = "OPERATOR_BEGINS_WITH"
+ value = "allowed objects"
+ }
+ transformations = [
+ "TRANSFORMATION_LOWERCASE"
+ ]
+ variable = {
+ type = "VARIABLE_REQUEST_HEADERS"
+ value = "Host"
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/go.mod b/go.mod
index f530b0b66..c60ea4438 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,7 @@ require (
github.com/hashicorp/terraform-plugin-testing v1.16.0
github.com/stackitcloud/stackit-sdk-go/core v0.26.0
github.com/stackitcloud/stackit-sdk-go/services/alb v0.16.0
- github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.10.0
+ github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.11.0
github.com/stackitcloud/stackit-sdk-go/services/cdn v1.19.0
github.com/stackitcloud/stackit-sdk-go/services/certificates v1.9.0
github.com/stackitcloud/stackit-sdk-go/services/dns v0.21.0
diff --git a/go.sum b/go.sum
index 78bb4a31f..74c2781a8 100644
--- a/go.sum
+++ b/go.sum
@@ -672,8 +672,8 @@ github.com/stackitcloud/stackit-sdk-go/core v0.26.0 h1:jQEb9gkehfp6VCP6TcYk7BI10
github.com/stackitcloud/stackit-sdk-go/core v0.26.0/go.mod h1:WU1hhxnjXw2EV7CYa1nlEvNpMiRY6CvmIOaHuL3pOaA=
github.com/stackitcloud/stackit-sdk-go/services/alb v0.16.0 h1:WoWlHdzISGXPEaJOYt6HP5F9M5nbyCJL6VqRJZIaOQs=
github.com/stackitcloud/stackit-sdk-go/services/alb v0.16.0/go.mod h1:eK6oRB5Tmpt6KbXQ4UYBGg2LgW5bPtVoncL9E8JSRww=
-github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.10.0 h1:0WsTSSZ0LjNpM3E1d3MgkBXmzMQThVQ7IuXhL2w4EyM=
-github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.10.0/go.mod h1:4M9G1I64kZwlXO32ZoIpt0GAN4SpZ1SYerwCVVIBGoE=
+github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.11.0 h1:ejTZTnGKFUWs9Ch9U30Jd+tpDA/SnHuSF9DpfD6w+To=
+github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.11.0/go.mod h1:4M9G1I64kZwlXO32ZoIpt0GAN4SpZ1SYerwCVVIBGoE=
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.15.2 h1:b7WJ/vwxlVmNNX91kI3obqGcuoPAyaCbDL5aCMQ/sNg=
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.15.2/go.mod h1:T/JF25XGJ3GqER/1L2N//DgY8x5tY7gA3N+/0nvmOWY=
github.com/stackitcloud/stackit-sdk-go/services/cdn v1.19.0 h1:k+KJ4gp9awhJMY5y55vDqRSr6G/S9+8haTNILGbgH9s=
diff --git a/stackit/internal/services/albwaf/albwaf_acc_test.go b/stackit/internal/services/albwaf/albwaf_acc_test.go
index b8e539850..7b5455f8a 100644
--- a/stackit/internal/services/albwaf/albwaf_acc_test.go
+++ b/stackit/internal/services/albwaf/albwaf_acc_test.go
@@ -15,17 +15,62 @@ import (
"github.com/hashicorp/terraform-plugin-testing/plancheck"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
- albwaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi"
+ albWaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
)
var (
+ //go:embed testdata/custom-rule-group-min.tf
+ customRuleGroupMinConfig string
+
+ //go:embed testdata/custom-rule-group-max.tf
+ customRuleGroupMaxConfig string
+
//go:embed testdata/managed-rule-set.tf
managedRuleSetConfig string
)
+var testCustomRuleGroupMin = config.Variables{
+ "project_id": config.StringVariable(testutil.ProjectId),
+ "name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)),
+ "action": config.StringVariable("ACTION_DENY"),
+ "operator_type": config.StringVariable("OPERATOR_VALIDATE_UTF8_ENCODING"),
+ "operator_value": config.StringVariable("foo"),
+ "transformation": config.StringVariable("TRANSFORMATION_LOWERCASE"),
+ "variable_type": config.StringVariable("VARIABLE_RESPONSE_STATUS"),
+}
+
+var testCustomRuleGroupMinUpdated = func() config.Variables {
+ updatedConfig := config.Variables{}
+ maps.Copy(updatedConfig, testCustomRuleGroupMin)
+ updatedConfig["name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["name"])))
+ return updatedConfig
+}
+
+var testCustomRuleGroupMax = config.Variables{
+ "project_id": config.StringVariable(testutil.ProjectId),
+ "name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)),
+ "description": config.StringVariable("foo bar"),
+ "action": config.StringVariable("ACTION_DENY"),
+ "log": config.BoolVariable(true),
+ "log_msg": config.StringVariable("foo-bar"),
+ "operator_type": config.StringVariable("OPERATOR_CONTAINS"),
+ "operator_value": config.StringVariable("foo"),
+ "transformation": config.StringVariable("TRANSFORMATION_LOWERCASE"),
+ "variable_type": config.StringVariable("VARIABLE_REQUEST_HEADERS"),
+ "variable_value": config.StringVariable("bar"),
+}
+
+var testCustomRuleGroupMaxUpdated = func() config.Variables {
+ updatedConfig := config.Variables{}
+ maps.Copy(updatedConfig, testCustomRuleGroupMax)
+ updatedConfig["name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["name"])))
+ // updatedConfig["log"] = config.BoolVariable(false)
+ return updatedConfig
+}
+
var testManagedRuleSet = config.Variables{
"project_id": config.StringVariable(testutil.ProjectId),
"name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)),
@@ -39,6 +84,263 @@ var testManagedRuleSetUpdated = func() config.Variables {
return updatedConfig
}
+func TestAccCustomRuleGroupMin(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckDestroy,
+ Steps: []resource.TestStep{
+ // Creation
+ {
+ ConfigVariables: testCustomRuleGroupMin,
+ Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), customRuleGroupMinConfig),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "project_id", testutil.ProjectId),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "region", testutil.Region),
+ resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "id"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "name", testutil.ConvertConfigVariable(testCustomRuleGroupMin["name"])),
+
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.#", "1"),
+ // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.id"),
+
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.action", testutil.ConvertConfigVariable(testCustomRuleGroupMin["action"])),
+ // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log", "false"),
+ // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity"),
+
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.#", "1"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.type", testutil.ConvertConfigVariable(testCustomRuleGroupMin["operator_type"])),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.#", "0"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.type", testutil.ConvertConfigVariable(testCustomRuleGroupMin["variable_type"])),
+ ),
+ },
+ // Data source
+ {
+ ConfigVariables: testCustomRuleGroupMin,
+ Config: fmt.Sprintf(`
+ %s
+ %s
+
+ data "stackit_alb_waf_custom_rule_group" "custom_rule_group" {
+ project_id = stackit_alb_waf_custom_rule_group.custom_rule_group.project_id
+ name = stackit_alb_waf_custom_rule_group.custom_rule_group.name
+ }
+ `,
+ testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), customRuleGroupMinConfig,
+ ),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "project_id", testutil.ProjectId),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "region", testutil.Region),
+ resource.TestCheckResourceAttrPair(
+ "data.stackit_alb_waf_custom_rule_group.custom_rule_group", "id",
+ "stackit_alb_waf_custom_rule_group.custom_rule_group", "id",
+ ),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "name", testutil.ConvertConfigVariable(testCustomRuleGroupMin["name"])),
+
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.#", "1"),
+ resource.TestCheckResourceAttrPair(
+ "data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.id",
+ "stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.id",
+ ),
+
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.action", testutil.ConvertConfigVariable(testCustomRuleGroupMin["action"])),
+ // resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log", "false"),
+ resource.TestCheckResourceAttrPair(
+ "data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity",
+ "stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity",
+ ),
+
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.#", "1"),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.type", testutil.ConvertConfigVariable(testCustomRuleGroupMin["operator_type"])),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.#", "0"),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.type", testutil.ConvertConfigVariable(testCustomRuleGroupMin["variable_type"])),
+ ),
+ },
+ // Import
+ {
+ ConfigVariables: testCustomRuleGroupMin,
+ ResourceName: "stackit_alb_waf_custom_rule_group.custom_rule_group",
+ ImportStateIdFunc: func(s *terraform.State) (string, error) {
+ r, ok := s.RootModule().Resources["stackit_alb_waf_custom_rule_group.custom_rule_group"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find resource stackit_alb_waf_custom_rule_group.custom_rule_group")
+ }
+ policyId, ok := r.Primary.Attributes["name"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute name")
+ }
+ return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, policyId), nil
+ },
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ // Update
+ {
+ ConfigVariables: testCustomRuleGroupMinUpdated(),
+ Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), customRuleGroupMinConfig),
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectResourceAction("stackit_alb_waf_custom_rule_group.custom_rule_group", plancheck.ResourceActionReplace),
+ },
+ },
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "project_id", testutil.ProjectId),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "region", testutil.Region),
+ resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "id"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "name", testutil.ConvertConfigVariable(testCustomRuleGroupMinUpdated()["name"])),
+
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.#", "1"),
+ // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rule.0.id"),
+
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.action", testutil.ConvertConfigVariable(testCustomRuleGroupMinUpdated()["action"])),
+ // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log", "false"),
+ // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity"),
+
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.#", "1"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.type", testutil.ConvertConfigVariable(testCustomRuleGroupMinUpdated()["operator_type"])),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.#", "0"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.type", testutil.ConvertConfigVariable(testCustomRuleGroupMinUpdated()["variable_type"])),
+ ),
+ },
+ // Deletion is done by the framework implicitly
+ },
+ })
+}
+
+func TestAccCustomRuleGroupMax(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckDestroy,
+ Steps: []resource.TestStep{
+ // Creation
+ {
+ ConfigVariables: testCustomRuleGroupMax,
+ Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), customRuleGroupMaxConfig),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "project_id", testutil.ProjectId),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "region", testutil.Region),
+ resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "id"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "name", testutil.ConvertConfigVariable(testCustomRuleGroupMax["name"])),
+
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.#", "1"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.description", testutil.ConvertConfigVariable(testCustomRuleGroupMax["description"])),
+ // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.id"),
+
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.action", testutil.ConvertConfigVariable(testCustomRuleGroupMax["action"])),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log"])),
+ // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log_msg", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log_msg"])),
+ // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity"),
+
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.#", "1"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.type", testutil.ConvertConfigVariable(testCustomRuleGroupMax["operator_type"])),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.value", testutil.ConvertConfigVariable(testCustomRuleGroupMax["operator_value"])),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.#", "1"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.0", testutil.ConvertConfigVariable(testCustomRuleGroupMax["transformation"])),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.type", testutil.ConvertConfigVariable(testCustomRuleGroupMax["variable_type"])),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.value", testutil.ConvertConfigVariable(testCustomRuleGroupMax["variable_value"])),
+ ),
+ },
+ // Data source
+ {
+ ConfigVariables: testCustomRuleGroupMax,
+ Config: fmt.Sprintf(`
+ %s
+ %s
+
+ data "stackit_alb_waf_custom_rule_group" "custom_rule_group" {
+ project_id = stackit_alb_waf_custom_rule_group.custom_rule_group.project_id
+ name = stackit_alb_waf_custom_rule_group.custom_rule_group.name
+ }
+ `,
+ testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), customRuleGroupMaxConfig,
+ ),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "project_id", testutil.ProjectId),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "region", testutil.Region),
+ resource.TestCheckResourceAttrPair(
+ "data.stackit_alb_waf_custom_rule_group.custom_rule_group", "id",
+ "stackit_alb_waf_custom_rule_group.custom_rule_group", "id",
+ ),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "name", testutil.ConvertConfigVariable(testCustomRuleGroupMax["name"])),
+
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.#", "1"),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.description", testutil.ConvertConfigVariable(testCustomRuleGroupMax["description"])),
+ resource.TestCheckResourceAttrPair(
+ "data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.id",
+ "stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.id",
+ ),
+
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.action", testutil.ConvertConfigVariable(testCustomRuleGroupMax["action"])),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log"])),
+ // resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log_msg", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log_msg"])),
+ resource.TestCheckResourceAttrPair(
+ "data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity",
+ "stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity",
+ ),
+
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.#", "1"),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.type", testutil.ConvertConfigVariable(testCustomRuleGroupMax["operator_type"])),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.value", testutil.ConvertConfigVariable(testCustomRuleGroupMax["operator_value"])),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.#", "1"),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.0", testutil.ConvertConfigVariable(testCustomRuleGroupMax["transformation"])),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.type", testutil.ConvertConfigVariable(testCustomRuleGroupMax["variable_type"])),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.value", testutil.ConvertConfigVariable(testCustomRuleGroupMax["variable_value"])),
+ ),
+ },
+ // Import
+ {
+ ConfigVariables: testCustomRuleGroupMax,
+ ResourceName: "stackit_alb_waf_custom_rule_group.custom_rule_group",
+ ImportStateIdFunc: func(s *terraform.State) (string, error) {
+ r, ok := s.RootModule().Resources["stackit_alb_waf_custom_rule_group.custom_rule_group"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find resource stackit_alb_waf_custom_rule_group.custom_rule_group")
+ }
+ policyId, ok := r.Primary.Attributes["name"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find attribute name")
+ }
+ return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, policyId), nil
+ },
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ // Update
+ {
+ ConfigVariables: testCustomRuleGroupMaxUpdated(),
+ Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), customRuleGroupMaxConfig),
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectResourceAction("stackit_alb_waf_custom_rule_group.custom_rule_group", plancheck.ResourceActionReplace),
+ },
+ },
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "project_id", testutil.ProjectId),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "region", testutil.Region),
+ resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "id"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "name", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["name"])),
+
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.#", "1"),
+ // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rule.0.description", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["description"])),
+ // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rule.0.id"),
+
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.action", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["action"])),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["log"])),
+ // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log_msg", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["log_msg"])),
+ // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity"),
+
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.#", "1"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.type", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["operator_type"])),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.value", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["operator_value"])),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.#", "1"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.0", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["transformation"])),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.type", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["variable_type"])),
+ resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.value", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["variable_value"])),
+ ),
+ },
+ // Deletion is done by the framework implicitly
+ },
+ })
+}
+
func TestAccManagedRuleSet(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
@@ -53,8 +355,7 @@ func TestAccManagedRuleSet(t *testing.T) {
resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "region", testutil.Region),
resource.TestCheckResourceAttrSet("stackit_alb_waf_managed_rule_set.managed_rule_set", "id"),
resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "name", testutil.ConvertConfigVariable(testManagedRuleSet["name"])),
-
- resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "type", testutil.ConvertConfigVariable(testManagedRuleSet["type"])),
),
},
// Data source
@@ -79,8 +380,7 @@ func TestAccManagedRuleSet(t *testing.T) {
"stackit_alb_waf_managed_rule_set.managed_rule_set", "id",
),
resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "name", testutil.ConvertConfigVariable(testManagedRuleSet["name"])),
-
- resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "type", testutil.ConvertConfigVariable(testManagedRuleSet["type"])),
),
},
// Import
@@ -115,8 +415,7 @@ func TestAccManagedRuleSet(t *testing.T) {
resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "region", testutil.Region),
resource.TestCheckResourceAttrSet("stackit_alb_waf_managed_rule_set.managed_rule_set", "id"),
resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "name", testutil.ConvertConfigVariable(testManagedRuleSetUpdated()["name"])),
-
- resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "type", testutil.ConvertConfigVariable(testManagedRuleSetUpdated()["type"])),
),
},
// Deletion is done by the framework implicitly
@@ -124,8 +423,8 @@ func TestAccManagedRuleSet(t *testing.T) {
})
}
-func createClient() (*albwaf.APIClient, error) {
- client, err := albwaf.NewAPIClient(testutil.NewConfigBuilder().BuildClientOptions(testutil.AlbWafCustomEndpoint, false)...)
+func createClient() (*albWaf.APIClient, error) {
+ client, err := albWaf.NewAPIClient(testutil.NewConfigBuilder().BuildClientOptions(testutil.AlbWafCustomEndpoint, false)...)
if err != nil {
return nil, fmt.Errorf("creating client: %w", err)
}
@@ -135,6 +434,7 @@ func createClient() (*albwaf.APIClient, error) {
func testAccCheckDestroy(s *terraform.State) error {
checkFunctions := []func(s *terraform.State) error{
+ testAlbWafCustomRuleGroupDestroy,
testAlbWafManagedRuleSetDestroy,
}
var errs []error
@@ -150,6 +450,39 @@ func testAccCheckDestroy(s *terraform.State) error {
return errors.Join(errs...)
}
+func testAlbWafCustomRuleGroupDestroy(s *terraform.State) error {
+ ctx := context.Background()
+ client, err := createClient()
+ if err != nil {
+ return err
+ }
+
+ customRuleGroupsToDestroy := []string{}
+ for _, rs := range s.RootModule().Resources {
+ if rs.Type != "stackit_alb_waf_custom_rule_group" {
+ continue
+ }
+ // custom rule group transform id: "[projectId],[region],[name]"
+ name := strings.Split(rs.Primary.ID, core.Separator)[2]
+ customRuleGroupsToDestroy = append(customRuleGroupsToDestroy, name)
+ }
+
+ resp, err := client.DefaultAPI.ListCustomRuleGroup(ctx, testutil.ProjectId, testutil.Region).Execute()
+ if err != nil {
+ return fmt.Errorf("getting resp: %w", err)
+ }
+
+ for _, item := range resp.Items {
+ if utils.Contains(customRuleGroupsToDestroy, item.GetName()) {
+ _, err := client.DefaultAPI.DeleteCustomRuleGroup(ctx, testutil.ProjectId, testutil.Region, item.GetName()).Execute()
+ if err != nil {
+ return fmt.Errorf("deleting policy %s during CheckDestroy: %w", item.GetName(), err)
+ }
+ }
+ }
+ return nil
+}
+
func testAlbWafManagedRuleSetDestroy(s *terraform.State) error {
ctx := context.Background()
client, err := createClient()
diff --git a/stackit/internal/services/albwaf/custom_rule_group/datasource.go b/stackit/internal/services/albwaf/custom_rule_group/datasource.go
new file mode 100644
index 000000000..9ff842a37
--- /dev/null
+++ b/stackit/internal/services/albwaf/custom_rule_group/datasource.go
@@ -0,0 +1,221 @@
+package custom_rule_group
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+ "regexp"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/stackitcloud/stackit-sdk-go/core/oapierror"
+ albWaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi"
+
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albwaf/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
+)
+
+var (
+ _ datasource.DataSource = &customRuleGroupDataSource{}
+ _ datasource.DataSourceWithConfigure = &customRuleGroupDataSource{}
+)
+
+type customRuleGroupDataSource struct {
+ client *albWaf.APIClient
+ providerData core.ProviderData
+}
+
+func NewCustomRuleGroupDataSource() datasource.DataSource {
+ return &customRuleGroupDataSource{}
+}
+
+func (r *customRuleGroupDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ var ok bool
+ r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
+ if !ok {
+ return
+ }
+
+ features.CheckBetaResourcesEnabled(ctx, &r.providerData, &resp.Diagnostics, "stackit_alb_waf_custom_rule_group", core.Resource)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ apiClient := utils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ r.client = apiClient
+ tflog.Info(ctx, "ALB WAF client configured")
+}
+
+func (r *customRuleGroupDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_alb_waf_custom_rule_group"
+}
+
+func (r *customRuleGroupDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: features.AddBetaDescription(fmt.Sprintf("ALB WAF Custom Rule Group resource schema. %s", core.ResourceRegionFallbackDocstring), core.Resource),
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: descriptions["id"],
+ Computed: true,
+ },
+ "project_id": schema.StringAttribute{
+ Description: descriptions["project_id"],
+ Required: true,
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "region": schema.StringAttribute{
+ Description: descriptions["region"],
+ Optional: true,
+ Computed: true,
+ },
+ "name": schema.StringAttribute{
+ Description: descriptions["name"],
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.RegexMatches(
+ regexp.MustCompile(`^[0-9a-z](?:(?:[0-9a-z]|-){0,61}[0-9a-z])?$`),
+ "must start and end with an alphanumeric character, may contain hyphens, and be 1-63 characters long",
+ ),
+ },
+ },
+ "rules": schema.ListNestedAttribute{
+ Description: descriptions["rules"],
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "behavior": schema.SingleNestedAttribute{
+ Description: descriptions["behavior"],
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "action": schema.StringAttribute{
+ Description: descriptions["behavior_action"],
+ Computed: true,
+ },
+ "log": schema.BoolAttribute{
+ Description: descriptions["behavior_log"],
+ Computed: true,
+ },
+ "log_msg": schema.StringAttribute{
+ Description: descriptions["behavior_log_msg"],
+ Computed: true,
+ },
+ "severity": schema.StringAttribute{
+ Description: descriptions["behavior_severity"],
+ Computed: true,
+ },
+ },
+ },
+ "conditions": schema.ListNestedAttribute{
+ Description: descriptions["rule_conditions"],
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "operator": schema.SingleNestedAttribute{
+ Description: descriptions["operator"],
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "type": schema.StringAttribute{
+ Description: descriptions["operator_type"],
+ Computed: true,
+ },
+ "value": schema.StringAttribute{
+ Description: descriptions["operator_value"],
+ Computed: true,
+ },
+ },
+ },
+ "transformations": schema.ListAttribute{
+ Description: descriptions["transformations"],
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "variable": schema.SingleNestedAttribute{
+ Description: descriptions["variable"],
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "type": schema.StringAttribute{
+ Description: descriptions["variable_type"],
+ Computed: true,
+ },
+ "value": schema.StringAttribute{
+ Description: descriptions["variable_value"],
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ "description": schema.StringAttribute{
+ Description: descriptions["rule_description"],
+ Computed: true,
+ },
+ "id": schema.Int32Attribute{
+ Description: descriptions["rule_id"],
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func (r *customRuleGroupDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
+ var model Model
+ diags := req.Config.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ ctx = core.InitProviderContext(ctx)
+
+ projectId := model.ProjectId.ValueString()
+ name := model.Name.ValueString()
+ region := r.providerData.GetRegionWithOverride(model.Region)
+ ctx = tflog.SetField(ctx, "project_id", projectId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "name", name)
+
+ customRuleGroupResp, err := r.client.DefaultAPI.GetCustomRuleGroup(ctx, projectId, region, name).Execute()
+ if err != nil {
+ var oapiErr *oapierror.GenericOpenAPIError
+ if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
+ core.LogAndAddError(ctx, &resp.Diagnostics, fmt.Sprintf("ALB WAF Custom Rule Group with name %q not found in project %q and region %q", name, projectId, region), err.Error())
+ resp.State.RemoveResource(ctx)
+ return
+ }
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Custom Rule Group", err.Error())
+ return
+ }
+
+ ctx = core.LogResponse(ctx)
+
+ err = mapFields(ctx, customRuleGroupResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Custom Rule Group", fmt.Sprintf("Processing API payload: %v", err))
+ return
+ }
+
+ diags = resp.State.Set(ctx, model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "ALB WAF Custom Rule Group read")
+}
diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource.go b/stackit/internal/services/albwaf/custom_rule_group/resource.go
new file mode 100644
index 000000000..6c3f18beb
--- /dev/null
+++ b/stackit/internal/services/albwaf/custom_rule_group/resource.go
@@ -0,0 +1,752 @@
+package custom_rule_group
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+ "regexp"
+ "strings"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "github.com/stackitcloud/stackit-sdk-go/core/oapierror"
+ albWaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi"
+
+ sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils"
+
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albwaf/utils"
+ tfutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
+)
+
+var (
+ _ resource.Resource = &customRuleGroupResource{}
+ _ resource.ResourceWithConfigure = &customRuleGroupResource{}
+ _ resource.ResourceWithImportState = &customRuleGroupResource{}
+ _ resource.ResourceWithModifyPlan = &customRuleGroupResource{}
+
+ variableTypeOptions = sdkUtils.EnumSliceToStringSlice(albWaf.AllowedConditionVariableTypeEnumValues)
+ transformationOptions = sdkUtils.EnumSliceToStringSlice(albWaf.AllowedConditionTransformationsInnerEnumValues)
+ operatorTypeOptions = sdkUtils.EnumSliceToStringSlice(albWaf.AllowedConditionOperatorTypeEnumValues)
+ actionOptions = sdkUtils.EnumSliceToStringSlice(albWaf.AllowedBehaviourActionEnumValues)
+)
+
+type Model struct {
+ Id types.String `tfsdk:"id"` // needed by TF
+ ProjectId types.String `tfsdk:"project_id"`
+ Region types.String `tfsdk:"region"`
+ Name types.String `tfsdk:"name"`
+ Rules types.List `tfsdk:"rules"`
+}
+
+type RuleModel struct {
+ Behavior types.Object `tfsdk:"behavior"`
+ Conditions types.List `tfsdk:"conditions"`
+ Description types.String `tfsdk:"description"`
+ Id types.Int32 `tfsdk:"id"`
+}
+
+var ruleType = map[string]attr.Type{
+ "behavior": types.ObjectType{AttrTypes: behaviorType},
+ "conditions": types.ListType{
+ ElemType: types.ObjectType{AttrTypes: conditionType},
+ },
+ "description": types.StringType,
+ "id": types.Int32Type,
+}
+
+type BehaviorModel struct {
+ Action types.String `tfsdk:"action"`
+ Log types.Bool `tfsdk:"log"`
+ LogMsg types.String `tfsdk:"log_msg"`
+ Severity types.String `tfsdk:"severity"`
+}
+
+var behaviorType = map[string]attr.Type{
+ "action": types.StringType,
+ "log": types.BoolType,
+ "log_msg": types.StringType,
+ "severity": types.StringType,
+}
+
+type ConditionModel struct {
+ Operator types.Object `tfsdk:"operator"`
+ Transformations types.List `tfsdk:"transformations"`
+ Variable types.Object `tfsdk:"variable"`
+}
+
+var conditionType = map[string]attr.Type{
+ "operator": types.ObjectType{AttrTypes: operatorType},
+ "transformations": types.ListType{ElemType: types.StringType},
+ "variable": types.ObjectType{AttrTypes: variableType},
+}
+
+type OperatorModel struct {
+ Type types.String `tfsdk:"type"`
+ Value types.String `tfsdk:"value"`
+}
+
+var operatorType = map[string]attr.Type{
+ "type": types.StringType,
+ "value": types.StringType,
+}
+
+type VariableModel struct {
+ Type types.String `tfsdk:"type"`
+ Value types.String `tfsdk:"value"`
+}
+
+var variableType = map[string]attr.Type{
+ "type": types.StringType,
+ "value": types.StringType,
+}
+
+type customRuleGroupResource struct {
+ client *albWaf.APIClient
+ providerData core.ProviderData
+}
+
+func NewCustomRuleGroupResource() resource.Resource {
+ return &customRuleGroupResource{}
+}
+
+func (r *customRuleGroupResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ var ok bool
+ r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
+ if !ok {
+ return
+ }
+
+ features.CheckBetaResourcesEnabled(ctx, &r.providerData, &resp.Diagnostics, "stackit_alb_waf_custom_rule_group", core.Resource)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ apiClient := utils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ r.client = apiClient
+ tflog.Info(ctx, "ALB WAF client configured")
+}
+
+func (r *customRuleGroupResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_alb_waf_custom_rule_group"
+}
+
+// descriptions for the attributes in the Schema.
+var descriptions = map[string]string{
+ "id": "Terraform's internal resource identifier. Structured as \"`project_id`,`region`,`name`\".",
+ "project_id": "STACKIT project ID associated with the ALB WAF Custom Rule Group.",
+ "region": "STACKIT region name the resource is located in. If not defined, the provider region is used.",
+ "name": "Custom rule group configuration name.",
+ "rules": "Enriched rules containing auto-generated IDs and computed severity values.",
+ "rule_behavior": "Behavior of the rule.",
+ "rule_condition": "Conditions for this rule (order matters, first condition match triggers execution).",
+ "rule_description": "A clear description explaining the threat vector or criteria addressed by this rule.",
+ "rule_id": "Backend auto-allocated unique rule ID within the valid 1-99999 threshold.",
+ "behavior_action": "The protective stance action. ACTION_DENY forces a 403 status response code.",
+ "behavior_log": "Determines whether an entry should be generated in the security ledger upon a rule hit.",
+ "behavior_log_msg": "Custom notification message string mapped to underlying logdata contexts. Required if log is true.",
+ "behavior_severity": "Severity classification metric used by internal analytics graphs.",
+ "operator": "The comparison logic executed against the transformed variable.",
+ "operator_type": "The operational evaluation type definition macro.",
+ "operator_value": "The text or rule regex pattern arguments applied inside the operator execution loop.",
+ "transformations": "Ordered normalization steps applied before the operator runs.",
+ "variable": "The part of the HTTP transaction to inspect.",
+ "variable_type": "The targeted validation engine variable macro.",
+ "variable_value": "Optional key element context for map variables (e.g., matching a 'Host' header key).",
+}
+
+func (r *customRuleGroupResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: features.AddBetaDescription(fmt.Sprintf("ALB WAF Custom Rule Group resource schema. %s", core.ResourceRegionFallbackDocstring), core.Resource),
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: descriptions["id"],
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "project_id": schema.StringAttribute{
+ Description: descriptions["project_id"],
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ validate.UUID(),
+ validate.NoSeparator(),
+ },
+ },
+ "region": schema.StringAttribute{
+ Description: descriptions["region"],
+ Optional: true,
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "name": schema.StringAttribute{
+ Description: descriptions["name"],
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ stringvalidator.RegexMatches(
+ regexp.MustCompile(`^[0-9a-z](?:(?:[0-9a-z]|-){0,61}[0-9a-z])?$`),
+ "must start and end with an alphanumeric character, may contain hyphens, and be 1-63 characters long",
+ ),
+ },
+ },
+ "rules": schema.ListNestedAttribute{
+ Description: descriptions["rules"],
+ Required: true,
+ PlanModifiers: []planmodifier.List{
+ listplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.List{
+ listvalidator.SizeAtLeast(1),
+ },
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "behavior": schema.SingleNestedAttribute{
+ Description: descriptions["behavior"],
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "action": schema.StringAttribute{
+ Description: descriptions["behavior_action"],
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(actionOptions...),
+ },
+ },
+ "log": schema.BoolAttribute{
+ Description: descriptions["behavior_log"],
+ Optional: true,
+ },
+ "log_msg": schema.StringAttribute{
+ Description: descriptions["behavior_log_msg"],
+ Optional: true,
+ },
+ "severity": schema.StringAttribute{
+ Description: descriptions["behavior_severity"],
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ },
+ },
+ "conditions": schema.ListNestedAttribute{
+ Description: descriptions["rule_conditions"],
+ Required: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "operator": schema.SingleNestedAttribute{
+ Description: descriptions["operator"],
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "type": schema.StringAttribute{
+ Description: descriptions["operator_type"],
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(operatorTypeOptions...),
+ },
+ },
+ "value": schema.StringAttribute{
+ Description: descriptions["operator_value"],
+ Optional: true,
+ },
+ },
+ },
+ "transformations": schema.ListAttribute{
+ Description: descriptions["transformations"],
+ Optional: true,
+ ElementType: types.StringType,
+ Validators: []validator.List{
+ listvalidator.ValueStringsAre(
+ stringvalidator.OneOf(transformationOptions...),
+ ),
+ },
+ },
+ "variable": schema.SingleNestedAttribute{
+ Description: descriptions["variable"],
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "type": schema.StringAttribute{
+ Description: descriptions["variable_type"],
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(variableTypeOptions...),
+ },
+ },
+ "value": schema.StringAttribute{
+ Description: descriptions["variable_value"],
+ Optional: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ "description": schema.StringAttribute{
+ Description: descriptions["rule_description"],
+ Optional: true,
+ },
+ "id": schema.Int32Attribute{
+ Description: descriptions["rule_id"],
+ Computed: true,
+ PlanModifiers: []planmodifier.Int32{
+ int32planmodifier.UseStateForUnknown(),
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func (r *customRuleGroupResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
+ var configModel Model
+ if req.Config.Raw.IsNull() {
+ return
+ }
+ resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ var planModel Model
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ tfutils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+}
+
+func (r *customRuleGroupResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ idParts := strings.Split(req.ID, core.Separator)
+
+ if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
+ core.LogAndAddError(ctx, &resp.Diagnostics,
+ "Error importing ALB WAF Custom Rule Group",
+ fmt.Sprintf("Expected import identifier with format: [project_id],[region],[name] Got: %q", req.ID),
+ )
+ return
+ }
+
+ ctx = tfutils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
+ "project_id": idParts[0],
+ "region": idParts[1],
+ "name": idParts[2],
+ })
+ tflog.Info(ctx, "ALB WAF Custom Rule Group state imported")
+}
+
+func (r *customRuleGroupResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
+ var model Model
+ diags := req.Plan.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ ctx = core.InitProviderContext(ctx)
+
+ projectId := model.ProjectId.ValueString()
+ region := r.providerData.GetRegionWithOverride(model.Region)
+ ctx = tflog.SetField(ctx, "project_id", projectId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "name", model.Name)
+
+ payload, err := toCreatePayload(ctx, &model)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Custom Rule Group", fmt.Sprintf("Creating API payload: %v", err))
+ return
+ }
+
+ createResp, err := r.client.DefaultAPI.CreateCustomRuleGroup(ctx, projectId, region).CreateCustomRuleGroupPayload(*payload).Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Custom Rule Group", fmt.Sprintf("Calling API: %v", err))
+ return
+ }
+
+ ctx = core.LogResponse(ctx)
+
+ if createResp.Name == nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Custom Rule Group", "Got empty Custom Rule Group name")
+ return
+ }
+ customRuleGroupName := *createResp.Name
+
+ ctx = tfutils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
+ "project_id": projectId,
+ "region": region,
+ "name": customRuleGroupName,
+ })
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ err = mapFields(ctx, createResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Custom Rule Group", fmt.Sprintf("Processing API payload: %v", err))
+ return
+ }
+
+ diags = resp.State.Set(ctx, model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "ALB WAF Custom Rule Group created")
+}
+
+func (r *customRuleGroupResource) Update(ctx context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Ressource not updatable", "ALB WAF Custom Rule Group is not updatable")
+}
+
+func (r *customRuleGroupResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
+ var model Model
+ diags := req.State.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ ctx = core.InitProviderContext(ctx)
+
+ projectId := model.ProjectId.ValueString()
+ name := model.Name.ValueString()
+ region := r.providerData.GetRegionWithOverride(model.Region)
+ ctx = tflog.SetField(ctx, "project_id", projectId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "name", name)
+
+ customRuleGroupResp, err := r.client.DefaultAPI.GetCustomRuleGroup(ctx, projectId, region, name).Execute()
+ if err != nil {
+ var oapiErr *oapierror.GenericOpenAPIError
+ if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
+ resp.State.RemoveResource(ctx)
+ return
+ }
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Custom Rule Group", err.Error())
+ return
+ }
+
+ ctx = core.LogResponse(ctx)
+
+ err = mapFields(ctx, customRuleGroupResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Custom Rule Group", fmt.Sprintf("Processing API payload: %v", err))
+ return
+ }
+
+ diags = resp.State.Set(ctx, model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "ALB WAF Custom Rule Group read")
+}
+
+func (r *customRuleGroupResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
+ var model Model
+ diags := req.State.Get(ctx, &model)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ ctx = core.InitProviderContext(ctx)
+
+ projectId := model.ProjectId.ValueString()
+ name := model.Name.ValueString()
+ region := r.providerData.GetRegionWithOverride(model.Region)
+ ctx = tflog.SetField(ctx, "project_id", projectId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "name", name)
+
+ _, err := r.client.DefaultAPI.DeleteCustomRuleGroup(ctx, projectId, region, name).Execute()
+ if err != nil {
+ var oapiErr *oapierror.GenericOpenAPIError
+ if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
+ tflog.Info(ctx, "ALB WAF Custom Rule Group was already deleted")
+ return
+ }
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting ALB WAF Custom Rule Group", fmt.Sprintf("Calling API: %v", err))
+ return
+ }
+
+ ctx = core.LogResponse(ctx)
+
+ tflog.Info(ctx, "ALB WAF Custom Rule Group deleted")
+}
+
+func toCreatePayload(ctx context.Context, model *Model) (*albWaf.CreateCustomRuleGroupPayload, error) {
+ if model == nil {
+ return nil, fmt.Errorf("nil model")
+ }
+
+ payloadRules := []albWaf.CreateCustomRule{}
+ if !tfutils.IsUndefined(model.Rules) {
+ rules := []RuleModel{}
+ diags := model.Rules.ElementsAs(ctx, &rules, true)
+ if diags.HasError() {
+ return nil, fmt.Errorf("converting to rule map: %v", diags.Errors())
+ }
+
+ for _, rule := range rules {
+ behavior := BehaviorModel{}
+ if !tfutils.IsUndefined(rule.Behavior) {
+ diags := rule.Behavior.As(ctx, &behavior, basetypes.ObjectAsOptions{})
+ if diags.HasError() {
+ return nil, fmt.Errorf("converting to rule behavior: %v", diags.Errors())
+ }
+ }
+
+ conditions, err := toConditionsPayload(ctx, rule.Conditions)
+ if err != nil || conditions == nil {
+ return nil, fmt.Errorf("converting conditions: %w", err)
+ }
+
+ payloadRules = append(payloadRules, albWaf.CreateCustomRule{
+ Behaviour: albWaf.Behaviour{ // nolint:misspell // Generated from API spec
+ Action: albWaf.BehaviourAction(behavior.Action.ValueString()),
+ Log: behavior.Log.ValueBoolPointer(),
+ LogMsg: behavior.LogMsg.ValueStringPointer(),
+ },
+ Conditions: *conditions,
+ Description: rule.Description.ValueStringPointer(),
+ })
+ }
+ }
+
+ payload := &albWaf.CreateCustomRuleGroupPayload{
+ Name: model.Name.ValueString(),
+ Rules: payloadRules,
+ }
+
+ return payload, nil
+}
+
+func toConditionsPayload(ctx context.Context, conditions basetypes.ListValue) (*[]albWaf.Condition, error) {
+ result := []albWaf.Condition{}
+
+ if !tfutils.IsUndefined(conditions) {
+ conditionModels := []ConditionModel{}
+ diags := conditions.ElementsAs(ctx, &conditionModels, true)
+ if diags.HasError() {
+ return nil, fmt.Errorf("converting to rule map: %v", diags.Errors())
+ }
+
+ for _, condition := range conditionModels {
+ transformations := []albWaf.ConditionTransformationsInner{}
+ if !tfutils.IsUndefined(condition.Transformations) {
+ diags := condition.Transformations.ElementsAs(ctx, &transformations, true)
+ if diags.HasError() {
+ return nil, fmt.Errorf("converting transformations: %v", diags.Errors())
+ }
+ }
+
+ var operatorModel = OperatorModel{}
+ diags = condition.Operator.As(ctx, &operatorModel, basetypes.ObjectAsOptions{})
+ if diags.HasError() {
+ return nil, fmt.Errorf("converting operator: %v", diags.Errors())
+ }
+
+ var variableModel = VariableModel{}
+ diags = condition.Variable.As(ctx, &variableModel, basetypes.ObjectAsOptions{})
+ if diags.HasError() {
+ return nil, fmt.Errorf("converting variable: %v", diags.Errors())
+ }
+
+ result = append(result, albWaf.Condition{
+ Operator: albWaf.ConditionOperator{
+ Type: albWaf.ConditionOperatorType(operatorModel.Type.ValueString()),
+ Value: operatorModel.Value.ValueStringPointer(),
+ },
+ Transformations: transformations,
+ Variable: albWaf.ConditionVariable{
+ Type: albWaf.ConditionVariableType(variableModel.Type.ValueString()),
+ Value: variableModel.Value.ValueStringPointer(),
+ },
+ })
+ }
+ }
+
+ return &result, nil
+}
+
+func mapFields(ctx context.Context, customRuleGroup *albWaf.GetCustomRuleGroupResponse, model *Model, region string) error {
+ if customRuleGroup == nil {
+ return fmt.Errorf("response input is nil")
+ }
+ if model == nil {
+ return fmt.Errorf("model input is nil")
+ }
+
+ model.Id = tfutils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, model.Name.ValueString())
+ model.Name = types.StringValue(model.Name.ValueString())
+ model.Region = types.StringValue(region)
+
+ rules, err := mapRules(ctx, &customRuleGroup.Rules)
+ if err != nil || rules == nil {
+ return fmt.Errorf("map rules: %w", err)
+ }
+ model.Rules = *rules
+
+ return nil
+}
+
+func mapRules(ctx context.Context, rules *[]albWaf.GetCustomRule) (*basetypes.ListValue, error) {
+ var diags diag.Diagnostics
+ var result basetypes.ListValue
+
+ if rules != nil {
+ rulesList := []attr.Value{}
+ for _, rule := range *rules {
+ ruleTF := RuleModel{
+ Id: types.Int32PointerValue(rule.Id),
+ Description: types.StringPointerValue(rule.Description),
+ }
+
+ behavior, err := mapBehavior(ctx, rule.Behaviour) // nolint:misspell // Generated from API spec
+ if err != nil || behavior == nil {
+ return nil, fmt.Errorf("map behavior: %w", err)
+ }
+ ruleTF.Behavior = *behavior
+
+ conditions, err := mapConditions(ctx, rule)
+ if err != nil || conditions == nil {
+ return nil, fmt.Errorf("map conditions: %w", err)
+ }
+ ruleTF.Conditions = *conditions
+
+ rule, diags := types.ObjectValueFrom(ctx, ruleType, ruleTF)
+ if diags.HasError() {
+ return nil, fmt.Errorf("mapping rule: %w", core.DiagsToError(diags))
+ }
+ rulesList = append(rulesList, rule)
+ }
+ result, diags = types.ListValue(types.ObjectType{AttrTypes: ruleType}, rulesList)
+ if diags.HasError() {
+ return nil, fmt.Errorf("creating rule object: %w", core.DiagsToError(diags))
+ }
+ } else {
+ result = types.ListNull(types.ObjectType{AttrTypes: ruleType})
+ }
+
+ return &result, nil
+}
+
+func mapBehavior(ctx context.Context, behavior *albWaf.GetBehaviour) (*basetypes.ObjectValue, error) {
+ var diags diag.Diagnostics
+ var result basetypes.ObjectValue
+
+ if behavior != nil {
+ behaviorModel := BehaviorModel{
+ Action: types.StringPointerValue((*string)(behavior.Action)),
+ Log: types.BoolPointerValue(behavior.Log),
+ LogMsg: types.StringPointerValue(behavior.LogMsg),
+ Severity: types.StringPointerValue((*string)(behavior.Severity)),
+ }
+
+ result, diags = types.ObjectValueFrom(ctx, behaviorType, behaviorModel)
+ if diags.HasError() {
+ return nil, fmt.Errorf("creating behavior object: %w", core.DiagsToError(diags))
+ }
+ } else {
+ result = types.ObjectNull(behaviorType)
+ }
+
+ return &result, nil
+}
+
+func mapConditions(ctx context.Context, rule albWaf.GetCustomRule) (*basetypes.ListValue, error) {
+ var diags diag.Diagnostics
+ var result basetypes.ListValue
+
+ if conditions, ok := rule.GetConditionsOk(); ok {
+ conditionsList := []attr.Value{}
+ for _, condition := range conditions {
+ conditionTF := ConditionModel{}
+
+ if operator, ok := condition.GetOperatorOk(); ok {
+ operatorModel := OperatorModel{
+ Type: types.StringValue(string(operator.Type)),
+ Value: types.StringPointerValue(operator.Value),
+ }
+
+ conditionTF.Operator, diags = types.ObjectValueFrom(ctx, operatorType, operatorModel)
+ if diags.HasError() {
+ return nil, fmt.Errorf("creating operator object: %w", core.DiagsToError(diags))
+ }
+ } else {
+ conditionTF.Operator = types.ObjectNull(operatorType)
+ }
+
+ conditionTF.Transformations, diags = types.ListValueFrom(ctx, types.StringType, condition.Transformations)
+ if diags.HasError() {
+ return nil, fmt.Errorf("mapping transformations: %w", core.DiagsToError(diags))
+ }
+
+ if variable, ok := condition.GetVariableOk(); ok {
+ variableModel := VariableModel{
+ Type: types.StringValue(string(variable.Type)),
+ Value: types.StringPointerValue(variable.Value),
+ }
+
+ conditionTF.Variable, diags = types.ObjectValueFrom(ctx, variableType, variableModel)
+ if diags.HasError() {
+ return nil, fmt.Errorf("creating variable object: %w", core.DiagsToError(diags))
+ }
+ } else {
+ conditionTF.Variable = types.ObjectNull(variableType)
+ }
+
+ condition, diags := types.ObjectValueFrom(ctx, conditionType, conditionTF)
+ if diags.HasError() {
+ return nil, fmt.Errorf("mapping condition: %w", core.DiagsToError(diags))
+ }
+ conditionsList = append(conditionsList, condition)
+ }
+ result, diags = types.ListValue(types.ObjectType{AttrTypes: conditionType}, conditionsList)
+ if diags.HasError() {
+ return nil, fmt.Errorf("mapping conditions: %w", core.DiagsToError(diags))
+ }
+ } else {
+ result = types.ListNull(types.ObjectType{AttrTypes: conditionType})
+ }
+
+ return &result, nil
+}
diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource_test.go b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go
new file mode 100644
index 000000000..cb691f437
--- /dev/null
+++ b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go
@@ -0,0 +1,328 @@
+package custom_rule_group
+
+import (
+ "context"
+ _ "embed"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/uuid"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ albWaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi"
+)
+
+var (
+ testProjectId = types.StringValue(uuid.NewString())
+ testRegion = types.StringValue("eu01")
+ testName = types.StringValue("test-custom-rule-group")
+ testId = types.StringValue(testProjectId.ValueString() + "," + testRegion.ValueString() + "," + testName.ValueString())
+)
+
+func TestToCreatePayload(t *testing.T) {
+ tests := []struct {
+ name string
+ model *Model
+ expected *albWaf.CreateCustomRuleGroupPayload
+ isValid bool
+ }{
+ {
+ name: "default",
+ model: &Model{
+ Name: testName,
+ Id: testId,
+ ProjectId: testProjectId,
+ Region: testRegion,
+ Rules: types.ListValueMust(types.ObjectType{AttrTypes: ruleType}, []attr.Value{
+ types.ObjectValueMust(ruleType, map[string]attr.Value{
+ "behavior": types.ObjectValueMust(behaviorType, map[string]attr.Value{
+ "action": types.StringValue("some-action"),
+ "log": types.BoolValue(true),
+ "log_msg": types.StringValue("Log: something happened"),
+ "severity": types.StringNull(),
+ }),
+ "conditions": types.ListValueMust(types.ObjectType{AttrTypes: conditionType}, []attr.Value{
+ types.ObjectValueMust(conditionType, map[string]attr.Value{
+ "operator": types.ObjectValueMust(operatorType, map[string]attr.Value{
+ "type": types.StringValue("operator-type"),
+ "value": types.StringValue("operator-value"),
+ }),
+ "transformations": types.ListValueMust(types.StringType, []attr.Value{
+ types.StringValue("foo"),
+ types.StringValue("bar"),
+ }),
+ "variable": types.ObjectValueMust(variableType, map[string]attr.Value{
+ "type": types.StringValue("variable-type"),
+ "value": types.StringValue("variable-value"),
+ }),
+ }),
+ }),
+ "description": types.StringValue("foo-bar"),
+ "id": types.Int32Null(),
+ }),
+ }),
+ },
+ expected: &albWaf.CreateCustomRuleGroupPayload{
+ Name: testName.ValueString(),
+ Rules: []albWaf.CreateCustomRule{
+ {
+ Behaviour: albWaf.Behaviour{ // nolint:misspell // Generated from API spec
+ Action: albWaf.BehaviourAction("some-action"),
+ Log: new(true),
+ LogMsg: new("Log: something happened"),
+ },
+ Conditions: []albWaf.Condition{
+ {
+ Operator: albWaf.ConditionOperator{
+ Type: albWaf.ConditionOperatorType("operator-type"),
+ Value: new("operator-value"),
+ },
+ Transformations: []albWaf.ConditionTransformationsInner{
+ "foo",
+ "bar",
+ },
+ Variable: albWaf.ConditionVariable{
+ Type: albWaf.ConditionVariableType("variable-type"),
+ Value: new("variable-value"),
+ },
+ },
+ },
+ Description: new("foo-bar"),
+ },
+ },
+ },
+ isValid: true,
+ },
+ {
+ name: "null values",
+ model: &Model{
+ Name: testName,
+ Id: testId,
+ ProjectId: testProjectId,
+ Region: testRegion,
+ Rules: types.ListValueMust(types.ObjectType{AttrTypes: ruleType}, []attr.Value{
+ types.ObjectValueMust(ruleType, map[string]attr.Value{
+ "behavior": types.ObjectValueMust(behaviorType, map[string]attr.Value{
+ "action": types.StringNull(),
+ "log": types.BoolNull(),
+ "log_msg": types.StringNull(),
+ "severity": types.StringNull(),
+ }),
+ "conditions": types.ListValueMust(types.ObjectType{AttrTypes: conditionType}, []attr.Value{
+ types.ObjectValueMust(conditionType, map[string]attr.Value{
+ "operator": types.ObjectValueMust(operatorType, map[string]attr.Value{
+ "type": types.StringNull(),
+ "value": types.StringNull(),
+ }),
+ "transformations": types.ListValueMust(types.StringType, []attr.Value{}),
+ "variable": types.ObjectValueMust(variableType, map[string]attr.Value{
+ "type": types.StringNull(),
+ "value": types.StringNull(),
+ }),
+ }),
+ }),
+ "description": types.StringNull(),
+ "id": types.Int32Null(),
+ }),
+ }),
+ },
+ expected: &albWaf.CreateCustomRuleGroupPayload{
+ Name: testName.ValueString(),
+ Rules: []albWaf.CreateCustomRule{
+ {
+ Behaviour: albWaf.Behaviour{}, // nolint:misspell // Generated from API spec
+ Conditions: []albWaf.Condition{
+ {
+ Operator: albWaf.ConditionOperator{},
+ Transformations: []albWaf.ConditionTransformationsInner{},
+ Variable: albWaf.ConditionVariable{},
+ },
+ },
+ },
+ },
+ },
+ isValid: true,
+ },
+ {
+ name: "no rules",
+ model: &Model{
+ Name: testName,
+ Id: testId,
+ ProjectId: testProjectId,
+ Region: testRegion,
+ },
+ expected: &albWaf.CreateCustomRuleGroupPayload{
+ Name: testName.ValueString(),
+ Rules: []albWaf.CreateCustomRule{},
+ },
+ isValid: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := toCreatePayload(context.Background(), tt.model)
+ if (err != nil) == tt.isValid {
+ t.Errorf("toCreatePayload() error = %v, isValid %v", err, tt.isValid)
+ return
+ }
+
+ if tt.isValid {
+ if diff := cmp.Diff(got, tt.expected); diff != "" {
+ t.Errorf("Data does not match: %s", diff)
+ }
+ }
+ })
+ }
+}
+
+func TestMapFields(t *testing.T) {
+ tests := []struct {
+ name string
+ state *Model
+ region string
+ input *albWaf.GetCustomRuleGroupResponse
+ expected *Model
+ isValid bool
+ }{
+ {
+ name: "default",
+ state: &Model{
+ ProjectId: testProjectId,
+ Region: testRegion,
+ Name: testName,
+ Id: testId,
+ Rules: types.ListNull(types.ObjectType{AttrTypes: ruleType}),
+ },
+ region: testRegion.ValueString(),
+ input: &albWaf.GetCustomRuleGroupResponse{
+ Name: testName.ValueStringPointer(),
+ Rules: []albWaf.GetCustomRule{
+ {
+ Behaviour: &albWaf.GetBehaviour{ // nolint:misspell // Generated from API spec
+ Action: new(albWaf.GetBehaviourAction("some-action")),
+ Log: new(true),
+ LogMsg: new("Log: something happened"),
+ Severity: new(albWaf.GetBehaviourSeverity("critical")),
+ },
+ Conditions: []albWaf.Condition{
+ {
+ Operator: albWaf.ConditionOperator{
+ Type: albWaf.ConditionOperatorType("operator-type"),
+ Value: new("operator-value"),
+ },
+ Transformations: []albWaf.ConditionTransformationsInner{
+ "foo",
+ "bar",
+ },
+ Variable: albWaf.ConditionVariable{
+ Type: albWaf.ConditionVariableType("variable-type"),
+ Value: new("variable-value"),
+ },
+ },
+ },
+ Description: new("foo-bar"),
+ Id: new(int32(42)),
+ },
+ },
+ },
+ expected: &Model{
+ ProjectId: testProjectId,
+ Region: testRegion,
+ Name: testName,
+ Id: testId,
+ Rules: types.ListValueMust(types.ObjectType{AttrTypes: ruleType}, []attr.Value{
+ types.ObjectValueMust(ruleType, map[string]attr.Value{
+ "behavior": types.ObjectValueMust(behaviorType, map[string]attr.Value{
+ "action": types.StringValue("some-action"),
+ "log": types.BoolValue(true),
+ "log_msg": types.StringValue("Log: something happened"),
+ "severity": types.StringValue("critical"),
+ }),
+ "conditions": types.ListValueMust(types.ObjectType{AttrTypes: conditionType}, []attr.Value{
+ types.ObjectValueMust(conditionType, map[string]attr.Value{
+ "operator": types.ObjectValueMust(operatorType, map[string]attr.Value{
+ "type": types.StringValue("operator-type"),
+ "value": types.StringValue("operator-value"),
+ }),
+ "transformations": types.ListValueMust(types.StringType, []attr.Value{
+ types.StringValue("foo"),
+ types.StringValue("bar"),
+ }),
+ "variable": types.ObjectValueMust(variableType, map[string]attr.Value{
+ "type": types.StringValue("variable-type"),
+ "value": types.StringValue("variable-value"),
+ }),
+ }),
+ }),
+ "description": types.StringValue("foo-bar"),
+ "id": types.Int32Value(42),
+ }),
+ }),
+ },
+ isValid: true,
+ },
+ {
+ name: "empty rule",
+ state: &Model{
+ ProjectId: testProjectId,
+ Region: testRegion,
+ Name: testName,
+ Id: testId,
+ Rules: types.ListNull(types.ObjectType{AttrTypes: ruleType}),
+ },
+ region: testRegion.ValueString(),
+ input: &albWaf.GetCustomRuleGroupResponse{
+ Rules: []albWaf.GetCustomRule{
+ {},
+ },
+ },
+ expected: &Model{
+ Name: testName,
+ Id: testId,
+ ProjectId: testProjectId,
+ Region: testRegion,
+ Rules: types.ListValueMust(types.ObjectType{AttrTypes: ruleType}, []attr.Value{
+ types.ObjectValueMust(ruleType, map[string]attr.Value{
+ "behavior": types.ObjectNull(behaviorType),
+ "conditions": types.ListNull(types.ObjectType{AttrTypes: conditionType}),
+ "description": types.StringNull(),
+ "id": types.Int32Null(),
+ }),
+ }),
+ },
+ isValid: true,
+ },
+ {
+ name: "no rules",
+ state: &Model{
+ ProjectId: testProjectId,
+ Region: testRegion,
+ Name: testName,
+ Id: testId,
+ },
+ region: testRegion.ValueString(),
+ input: &albWaf.GetCustomRuleGroupResponse{},
+ expected: &Model{
+ Name: testName,
+ Id: testId,
+ ProjectId: testProjectId,
+ Region: testRegion,
+ Rules: types.ListValueMust(types.ObjectType{AttrTypes: ruleType}, []attr.Value{}),
+ },
+ isValid: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := context.Background()
+ if err := mapFields(ctx, tt.input, tt.state, tt.region); (err == nil) != tt.isValid {
+ t.Errorf("unexpected error")
+ }
+ if tt.isValid {
+ if diff := cmp.Diff(tt.state, tt.expected); diff != "" {
+ t.Fatalf("Data does not match: %s", diff)
+ }
+ }
+ })
+ }
+}
diff --git a/stackit/internal/services/albwaf/managed_rule_set/datasource.go b/stackit/internal/services/albwaf/managed_rule_set/datasource.go
index b802907a4..f53205ae2 100644
--- a/stackit/internal/services/albwaf/managed_rule_set/datasource.go
+++ b/stackit/internal/services/albwaf/managed_rule_set/datasource.go
@@ -11,7 +11,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
- "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
albWaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi"
@@ -100,21 +99,6 @@ func (d *managedRuleSetDataSource) Schema(_ context.Context, _ datasource.Schema
Description: descriptions["version"],
Computed: true,
},
- "usage": schema.SingleNestedAttribute{
- Description: descriptions["usage"],
- Computed: true,
- Attributes: map[string]schema.Attribute{
- "count": schema.Int32Attribute{
- Description: descriptions["usage_count"],
- Computed: true,
- },
- "items": schema.ListAttribute{
- Description: descriptions["usage_items"],
- Computed: true,
- ElementType: types.StringType,
- },
- },
- },
"groups": schema.MapNestedAttribute{
Description: descriptions["groups"],
Computed: true,
diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource.go b/stackit/internal/services/albwaf/managed_rule_set/resource.go
index 0409ec6e8..329e4192c 100644
--- a/stackit/internal/services/albwaf/managed_rule_set/resource.go
+++ b/stackit/internal/services/albwaf/managed_rule_set/resource.go
@@ -43,7 +43,6 @@ type Model struct {
Name types.String `tfsdk:"name"`
Groups types.Map `tfsdk:"groups"`
Type types.String `tfsdk:"type"`
- Usage types.Object `tfsdk:"usage"`
Version types.String `tfsdk:"version"`
}
@@ -73,16 +72,6 @@ var ruleType = map[string]attr.Type{
"severity": types.StringType,
}
-type UsageModel struct {
- Count types.Int32 `tfsdk:"count"`
- Items types.List `tfsdk:"items"`
-}
-
-var usageType = map[string]attr.Type{
- "count": types.Int32Type,
- "items": types.ListType{ElemType: types.StringType},
-}
-
type managedRuleSetResource struct {
client *albWaf.APIClient
providerData core.ProviderData
@@ -124,9 +113,6 @@ var descriptions = map[string]string{
"name": "Managed Rule Set configuration name.",
"type": "Type of the Managed Rule Set.",
"version": "Managed Rule Set version.",
- "usage": "Managed Rule Set usage",
- "usage_count": "Number of WAFs using this Managed Rule Set.",
- "usage_items": "List of WAFs that use this Managed Rule Set.",
"groups": "Inventory of all available Managed Rule Set groups and their current configuration.",
"group_description": "A description of what this group covers.",
"group_name": "The name for the rule group.",
@@ -190,21 +176,6 @@ func (r *managedRuleSetResource) Schema(_ context.Context, _ resource.SchemaRequ
Description: descriptions["version"],
Computed: true,
},
- "usage": schema.SingleNestedAttribute{
- Description: descriptions["usage"],
- Computed: true,
- Attributes: map[string]schema.Attribute{
- "count": schema.Int32Attribute{
- Description: descriptions["usage_count"],
- Computed: true,
- },
- "items": schema.ListAttribute{
- Description: descriptions["usage_items"],
- Computed: true,
- ElementType: types.StringType,
- },
- },
- },
"groups": schema.MapNestedAttribute{
Description: descriptions["groups"],
Computed: true,
@@ -432,8 +403,8 @@ func toCreatePayload(_ context.Context, model *Model) (*albWaf.CreateManagedRule
}
payload := &albWaf.CreateManagedRuleSetPayload{
- Name: model.Name.ValueStringPointer(),
- Type: new(albWaf.MRSType(model.Type.ValueString())),
+ Name: model.Name.ValueString(),
+ Type: albWaf.MRSType(model.Type.ValueString()),
}
return payload, nil
@@ -475,13 +446,13 @@ func mapFields(ctx context.Context, managedRuleSet *albWaf.GetManagedRuleSetResp
ruleMap[ruleKey], diags = types.ObjectValueFrom(ctx, ruleType, ruleTF)
if diags.HasError() {
- return fmt.Errorf("mapping role: %w", core.DiagsToError(diags))
+ return fmt.Errorf("mapping rule: %w", core.DiagsToError(diags))
}
}
}
groupTF.Rules, diags = types.MapValue(types.ObjectType{AttrTypes: ruleType}, ruleMap)
if diags.HasError() {
- return fmt.Errorf("mapping roles: %w", core.DiagsToError(diags))
+ return fmt.Errorf("mapping rules: %w", core.DiagsToError(diags))
}
groupsMap[groupKey], diags = types.ObjectValueFrom(ctx, ruleGroupType, groupTF)
@@ -498,23 +469,5 @@ func mapFields(ctx context.Context, managedRuleSet *albWaf.GetManagedRuleSetResp
return fmt.Errorf("mapping groups: %w", core.DiagsToError(diags))
}
- if usage, ok := managedRuleSet.GetUsageOk(); ok {
- usageModel := UsageModel{
- Count: types.Int32PointerValue(usage.Count),
- }
-
- usageModel.Items, diags = types.ListValueFrom(ctx, types.StringType, usage.GetItems())
- if diags.HasError() {
- return fmt.Errorf("creating usage object: %w", core.DiagsToError(diags))
- }
-
- model.Usage, diags = types.ObjectValueFrom(ctx, usageType, usageModel)
- if diags.HasError() {
- return fmt.Errorf("creating usage object: %w", core.DiagsToError(diags))
- }
- } else {
- model.Usage = types.ObjectNull(usageType)
- }
-
return nil
}
diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource_test.go b/stackit/internal/services/albwaf/managed_rule_set/resource_test.go
index 9b7bc9548..0158f48f6 100644
--- a/stackit/internal/services/albwaf/managed_rule_set/resource_test.go
+++ b/stackit/internal/services/albwaf/managed_rule_set/resource_test.go
@@ -36,8 +36,8 @@ func TestToCreatePayload(t *testing.T) {
Type: types.StringValue(string(albWaf.MRSTYPE_TYPE_OWASP_CRS)),
},
expected: &albWaf.CreateManagedRuleSetPayload{
- Name: testName.ValueStringPointer(),
- Type: new(albWaf.MRSTYPE_TYPE_OWASP_CRS),
+ Name: testName.ValueString(),
+ Type: albWaf.MRSTYPE_TYPE_OWASP_CRS,
},
isValid: true,
},
diff --git a/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf b/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf
new file mode 100644
index 000000000..74495fb3d
--- /dev/null
+++ b/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf
@@ -0,0 +1,42 @@
+
+variable "project_id" {}
+variable "name" {}
+variable "description" {}
+variable "action" {}
+variable "log" {}
+variable "log_msg" {}
+variable "operator_type" {}
+variable "operator_value" {}
+variable "transformation" {}
+variable "variable_type" {}
+variable "variable_value" {}
+
+resource "stackit_alb_waf_custom_rule_group" "custom_rule_group" {
+ project_id = var.project_id
+ name = var.name
+ rules = [
+ {
+ description = var.description
+ behavior = {
+ action = var.action
+ log = var.log
+ logMsg = var.log_msg
+ }
+ conditions = [
+ {
+ operator = {
+ type = var.operator_type
+ value = var.operator_value
+ }
+ transformations = [
+ var.transformation
+ ]
+ variable = {
+ type = var.variable_type
+ value = var.variable_value
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf b/stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf
new file mode 100644
index 000000000..cf1c92e6b
--- /dev/null
+++ b/stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf
@@ -0,0 +1,29 @@
+
+variable "project_id" {}
+variable "name" {}
+variable "action" {}
+variable "operator_type" {}
+variable "variable_type" {}
+
+resource "stackit_alb_waf_custom_rule_group" "custom_rule_group" {
+ project_id = var.project_id
+ name = var.name
+ rules = [
+ {
+ behavior = {
+ action = var.action
+ }
+ conditions = [
+ {
+ operator = {
+ type = var.operator_type
+ value = "dummy"
+ }
+ variable = {
+ type = var.variable_type
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/stackit/provider.go b/stackit/provider.go
index f99c5eb3c..7116bd89d 100644
--- a/stackit/provider.go
+++ b/stackit/provider.go
@@ -24,6 +24,7 @@ import (
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/access_token"
alb "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/alb/applicationloadbalancer"
cert "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albcertificates/certificate"
+ albWafCustomRuleGroup "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albwaf/custom_rule_group"
albWafManagedRuleSet "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albwaf/managed_rule_set"
customRole "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/authorization/customrole"
roleAssignements "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/authorization/roleassignments"
@@ -680,6 +681,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest,
func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource {
dataSources := []func() datasource.DataSource{
alb.NewApplicationLoadBalancerDataSource,
+ albWafCustomRuleGroup.NewCustomRuleGroupDataSource,
albWafManagedRuleSet.NewManagedRuleSetDataSource,
alertGroup.NewAlertGroupDataSource,
cdn.NewDistributionDataSource,
@@ -793,6 +795,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource
func (p *Provider) Resources(_ context.Context) []func() resource.Resource {
resources := []func() resource.Resource{
alb.NewApplicationLoadBalancerResource,
+ albWafCustomRuleGroup.NewCustomRuleGroupResource,
albWafManagedRuleSet.NewManagedRuleSetResource,
alertGroup.NewAlertGroupResource,
cdn.NewDistributionResource,