From 454f691f09a29ce705610783cc7d5c52834ae279 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Mon, 27 Jul 2026 18:08:26 +0200 Subject: [PATCH 01/11] feat(albwaf): Onboard custom rule groups relates to STACKITTPR-748 --- .../services/albwaf/albwaf_acc_test.go | 351 ++++++++ .../albwaf/custom_rule_group/datasource.go | 236 +++++ .../albwaf/custom_rule_group/resource.go | 822 ++++++++++++++++++ .../albwaf/custom_rule_group/resource_test.go | 344 ++++++++ .../albwaf/managed_rule_set/resource.go | 4 +- .../albwaf/testdata/custom-rule-group-max.tf | 42 + .../albwaf/testdata/custom-rule-group-min.tf | 29 + stackit/provider.go | 3 + 8 files changed, 1829 insertions(+), 2 deletions(-) create mode 100644 stackit/internal/services/albwaf/custom_rule_group/datasource.go create mode 100644 stackit/internal/services/albwaf/custom_rule_group/resource.go create mode 100644 stackit/internal/services/albwaf/custom_rule_group/resource_test.go create mode 100644 stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf create mode 100644 stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf diff --git a/stackit/internal/services/albwaf/albwaf_acc_test.go b/stackit/internal/services/albwaf/albwaf_acc_test.go index b8e539850..3de990be9 100644 --- a/stackit/internal/services/albwaf/albwaf_acc_test.go +++ b/stackit/internal/services/albwaf/albwaf_acc_test.go @@ -22,10 +22,55 @@ import ( ) 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,275 @@ 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.behaviour.action", testutil.ConvertConfigVariable(testCustomRuleGroupMin["action"])), + // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log", "false"), + // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.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"])), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "usage.count", "0"), + ), + }, + // 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.behaviour.action", testutil.ConvertConfigVariable(testCustomRuleGroupMin["action"])), + // resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log", "false"), + resource.TestCheckResourceAttrPair( + "data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.severity", + "stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.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"])), + + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "usage.count", "0"), + ), + }, + // 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.behaviour.action", testutil.ConvertConfigVariable(testCustomRuleGroupMinUpdated()["action"])), + // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log", "false"), + // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.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"])), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "usage.count", "0"), + ), + }, + // 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.behaviour.action", testutil.ConvertConfigVariable(testCustomRuleGroupMax["action"])), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log"])), + // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log_msg", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log_msg"])), + // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.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"])), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "usage.count", "0"), + ), + }, + // 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.behaviour.action", testutil.ConvertConfigVariable(testCustomRuleGroupMax["action"])), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log"])), + // resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log_msg", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log_msg"])), + resource.TestCheckResourceAttrPair( + "data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.severity", + "stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.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"])), + + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "usage.count", "0"), + ), + }, + // 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.behaviour.action", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["action"])), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["log"])), + // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log_msg", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["log_msg"])), + // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.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"])), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "usage.count", "0"), + ), + }, + // Deletion is done by the framework implicitly + }, + }) +} + func TestAccManagedRuleSet(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, @@ -53,6 +367,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", "type", testutil.ConvertConfigVariable(testManagedRuleSet["type"])), resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"), ), @@ -79,6 +394,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", "type", testutil.ConvertConfigVariable(testManagedRuleSet["type"])), resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"), ), @@ -115,6 +431,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", "type", testutil.ConvertConfigVariable(testManagedRuleSetUpdated()["type"])), resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"), ), @@ -135,6 +452,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 +468,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..b10c9ec94 --- /dev/null +++ b/stackit/internal/services/albwaf/custom_rule_group/datasource.go @@ -0,0 +1,236 @@ +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{ + "behaviour": schema.SingleNestedAttribute{ + Description: descriptions["behaviour"], + Computed: true, + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + Description: descriptions["behaviour_action"], + Computed: true, + }, + "log": schema.BoolAttribute{ + Description: descriptions["behaviour_log"], + Computed: true, + }, + "log_msg": schema.StringAttribute{ + Description: descriptions["behaviour_log_msg"], + Computed: true, + }, + "severity": schema.StringAttribute{ + Description: descriptions["behaviour_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, + }, + }, + }, + }, + "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, + }, + }, + }, + }, + } +} + +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..7479b63bd --- /dev/null +++ b/stackit/internal/services/albwaf/custom_rule_group/resource.go @@ -0,0 +1,822 @@ +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"` + Usage types.Object `tfsdk:"usage"` +} + +type RuleModel struct { + Behaviour types.Object `tfsdk:"behaviour"` + Conditions types.List `tfsdk:"conditions"` + Description types.String `tfsdk:"description"` + Id types.Int32 `tfsdk:"id"` +} + +var ruleType = map[string]attr.Type{ + "behaviour": types.ObjectType{AttrTypes: behaviourType}, + "conditions": types.ListType{ + ElemType: types.ObjectType{AttrTypes: conditionType}, + }, + "description": types.StringType, + "id": types.Int32Type, +} + +type BehaviourModel struct { + Action types.String `tfsdk:"action"` + Log types.Bool `tfsdk:"log"` + LogMsg types.String `tfsdk:"log_msg"` + Severity types.String `tfsdk:"severity"` +} + +var behaviourType = 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 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 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_behaviour": "Behaviour 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.", + "behaviour_action": "The protective stance action. ACTION_DENY forces a 403 status response code.", + "behaviour_log": "Determines whether an entry should be generated in the security ledger upon a rule hit.", + "behaviour_log_msg": "Custom notification message string mapped to underlying logdata contexts. Required if log is true.", + "behaviour_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).", + "usage": "Tracking metrics for CRG resource utilization.", + "usage_count": "Number of WAF configurations actively using this rule group.", + "usage_items": "List of individual WAF configuration names that bind this rule group.", +} + +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{ + "behaviour": schema.SingleNestedAttribute{ + Description: descriptions["behaviour"], + Required: true, + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + Description: descriptions["behaviour_action"], + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf(actionOptions...), + }, + }, + "log": schema.BoolAttribute{ + Description: descriptions["behaviour_log"], + Optional: true, + }, + "log_msg": schema.StringAttribute{ + Description: descriptions["behaviour_log_msg"], + Optional: true, + }, + "severity": schema.StringAttribute{ + Description: descriptions["behaviour_severity"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + "conditions": schema.ListNestedAttribute{ + Description: descriptions["rule_conditions"], + Optional: 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(), + }, + }, + }, + }, + }, + "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, + }, + }, + }, + }, + } +} + +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 { + behaviour := BehaviourModel{} + if !tfutils.IsUndefined(rule.Behaviour) { + diags := rule.Behaviour.As(ctx, &behaviour, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, fmt.Errorf("converting to rule behaviour: %v", diags.Errors()) + } + } + + conditions, err := toConditionsPayload(ctx, rule.Conditions) + if err != nil || conditions == nil { + return nil, fmt.Errorf("converting conditions: %v", err) + } + + payloadRules = append(payloadRules, albWaf.CreateCustomRule{ + Behaviour: &albWaf.Behaviour{ + Action: (*albWaf.BehaviourAction)(behaviour.Action.ValueStringPointer()), + Log: behaviour.Log.ValueBoolPointer(), + LogMsg: behaviour.LogMsg.ValueStringPointer(), + }, + Conditions: *conditions, + Description: rule.Description.ValueStringPointer(), + }) + } + } + + payload := &albWaf.CreateCustomRuleGroupPayload{ + Name: model.Name.ValueStringPointer(), + 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 operator *albWaf.ConditionOperator + var operatorModel = OperatorModel{} + if !tfutils.IsUndefined(condition.Operator) { + diags = condition.Operator.As(ctx, &operatorModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, fmt.Errorf("converting operator: %v", diags.Errors()) + } + + operator = &albWaf.ConditionOperator{ + Type: (*albWaf.ConditionOperatorType)(operatorModel.Type.ValueStringPointer()), + Value: operatorModel.Value.ValueStringPointer(), + } + } + + var variable *albWaf.ConditionVariable + var variableModel = VariableModel{} + if !tfutils.IsUndefined(condition.Variable) { + diags = condition.Variable.As(ctx, &variableModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, fmt.Errorf("converting variable: %v", diags.Errors()) + } + + variable = &albWaf.ConditionVariable{ + Type: (*albWaf.ConditionVariableType)(variableModel.Type.ValueStringPointer()), + Value: variableModel.Value.ValueStringPointer(), + } + } + + result = append(result, albWaf.Condition{ + Operator: operator, + Transformations: transformations, + Variable: variable, + }) + } + } + + 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 + + usage, err := mapUsage(ctx, customRuleGroup.Usage) + if err != nil || usage == nil { + return fmt.Errorf("map usage: %w", err) + } + model.Usage = *usage + + 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), + } + + behaviour, err := mapBehaviour(ctx, rule.Behaviour) + if err != nil || behaviour == nil { + return nil, fmt.Errorf("map behaviour: %w", err) + } + ruleTF.Behaviour = *behaviour + + 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 mapBehaviour(ctx context.Context, behaviour *albWaf.GetBehaviour) (*basetypes.ObjectValue, error) { + var diags diag.Diagnostics + var result basetypes.ObjectValue + + if behaviour != nil { + behaviourModel := BehaviourModel{ + Action: types.StringPointerValue((*string)(behaviour.Action)), + Log: types.BoolPointerValue(behaviour.Log), + LogMsg: types.StringPointerValue(behaviour.LogMsg), + Severity: types.StringPointerValue((*string)(behaviour.Severity)), + } + + result, diags = types.ObjectValueFrom(ctx, behaviourType, behaviourModel) + if diags.HasError() { + return nil, fmt.Errorf("creating behaviour object: %w", core.DiagsToError(diags)) + } + } else { + result = types.ObjectNull(behaviourType) + } + + 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.StringPointerValue((*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.StringPointerValue((*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 +} + +func mapUsage(ctx context.Context, usage *albWaf.CRGUsage) (*basetypes.ObjectValue, error) { + var diags diag.Diagnostics + var result basetypes.ObjectValue + + if usage != nil { + usageModel := UsageModel{ + Count: types.Int32PointerValue(usage.Count), + } + + usageModel.Items, diags = types.ListValueFrom(ctx, types.StringType, usage.GetItems()) + if diags.HasError() { + return nil, fmt.Errorf("creating usage object: %w", core.DiagsToError(diags)) + } + + result, diags = types.ObjectValueFrom(ctx, usageType, usageModel) + if diags.HasError() { + return nil, fmt.Errorf("creating usage object: %w", core.DiagsToError(diags)) + } + } else { + result = types.ObjectNull(usageType) + } + + 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..4179cb981 --- /dev/null +++ b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go @@ -0,0 +1,344 @@ +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{ + "behaviour": types.ObjectValueMust(behaviourType, 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.ValueStringPointer(), + Rules: []albWaf.CreateCustomRule{ + albWaf.CreateCustomRule{ + Behaviour: &albWaf.Behaviour{ + Action: new(albWaf.BehaviourAction("some-action")), + Log: new(true), + LogMsg: new("Log: something happened"), + }, + Conditions: []albWaf.Condition{ + albWaf.Condition{ + Operator: &albWaf.ConditionOperator{ + Type: new(albWaf.ConditionOperatorType("operator-type")), + Value: new("operator-value"), + }, + Transformations: []albWaf.ConditionTransformationsInner{ + "foo", + "bar", + }, + Variable: &albWaf.ConditionVariable{ + Type: new(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{ + "behaviour": types.ObjectValueMust(behaviourType, 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.ValueStringPointer(), + Rules: []albWaf.CreateCustomRule{ + albWaf.CreateCustomRule{ + Behaviour: &albWaf.Behaviour{}, + Conditions: []albWaf.Condition{ + 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.ValueStringPointer(), + 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{ + albWaf.GetCustomRule{ + Behaviour: &albWaf.GetBehaviour{ + Action: new(albWaf.GetBehaviourAction("some-action")), + Log: new(true), + LogMsg: new("Log: something happened"), + Severity: new(albWaf.GetBehaviourSeverity("critical")), + }, + Conditions: []albWaf.Condition{ + albWaf.Condition{ + Operator: &albWaf.ConditionOperator{ + Type: new(albWaf.ConditionOperatorType("operator-type")), + Value: new("operator-value"), + }, + Transformations: []albWaf.ConditionTransformationsInner{ + "foo", + "bar", + }, + Variable: &albWaf.ConditionVariable{ + Type: new(albWaf.ConditionVariableType("variable-type")), + Value: new("variable-value"), + }, + }, + }, + Description: new("foo-bar"), + Id: new(int32(42)), + }, + }, + Usage: &albWaf.CRGUsage{ + Count: new(int32(42)), + Items: []string{ + "one", + "two", + "three", + }, + }, + }, + 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{ + "behaviour": types.ObjectValueMust(behaviourType, 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), + }), + }), + Usage: types.ObjectValueMust(usageType, map[string]attr.Value{ + "count": types.Int32Value(42), + "items": types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("one"), + types.StringValue("two"), + types.StringValue("three"), + }), + }), + }, + 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{ + 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{ + "behaviour": types.ObjectNull(behaviourType), + "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/resource.go b/stackit/internal/services/albwaf/managed_rule_set/resource.go index 0409ec6e8..a63718da0 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/resource.go +++ b/stackit/internal/services/albwaf/managed_rule_set/resource.go @@ -475,13 +475,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) 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..bf81b4d9d --- /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 + behaviour = { + 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..10dd562be --- /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 = [ + { + behaviour = { + 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, From e8cb0d090a2dea663a6038bb509b11cbeeae1c6e Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Mon, 27 Jul 2026 18:21:38 +0200 Subject: [PATCH 02/11] generate-docu and format --- .../data-sources/alb_waf_custom_rule_group.md | 92 ++++++++++++++ docs/resources/alb_waf_custom_rule_group.md | 113 ++++++++++++++++++ .../albwaf/custom_rule_group/resource.go | 2 +- .../albwaf/custom_rule_group/resource_test.go | 14 +-- .../albwaf/testdata/custom-rule-group-max.tf | 4 +- .../albwaf/testdata/custom-rule-group-min.tf | 2 +- 6 files changed, 216 insertions(+), 11 deletions(-) create mode 100644 docs/data-sources/alb_waf_custom_rule_group.md create mode 100644 docs/resources/alb_waf_custom_rule_group.md 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..0ed80c49e --- /dev/null +++ b/docs/data-sources/alb_waf_custom_rule_group.md @@ -0,0 +1,92 @@ +--- +# 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. + + + + +## 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)) +- `usage` (Attributes) Tracking metrics for CRG resource utilization. (see [below for nested schema](#nestedatt--usage)) + + +### Nested Schema for `rules` + +Read-Only: + +- `behaviour` (Attributes) (see [below for nested schema](#nestedatt--rules--behaviour)) +- `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.behaviour` + +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). + + + + + +### Nested Schema for `usage` + +Read-Only: + +- `count` (Number) Number of WAF configurations actively using this rule group. +- `items` (List of String) List of individual WAF configuration names that bind this rule group. 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..b265241ad --- /dev/null +++ b/docs/resources/alb_waf_custom_rule_group.md @@ -0,0 +1,113 @@ +--- +# 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. + + + + +## 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`". +- `usage` (Attributes) Tracking metrics for CRG resource utilization. (see [below for nested schema](#nestedatt--usage)) + + +### Nested Schema for `rules` + +Required: + +- `behaviour` (Attributes) (see [below for nested schema](#nestedatt--rules--behaviour)) + +Optional: + +- `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. + +Read-Only: + +- `id` (Number) Backend auto-allocated unique rule ID within the valid 1-99999 threshold. + + +### Nested Schema for `rules.behaviour` + +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). + + + + + +### Nested Schema for `usage` + +Read-Only: + +- `count` (Number) Number of WAF configurations actively using this rule group. +- `items` (List of String) List of individual WAF configuration names that bind this rule group. diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource.go b/stackit/internal/services/albwaf/custom_rule_group/resource.go index 7479b63bd..7841808fe 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource.go @@ -565,7 +565,7 @@ func toCreatePayload(ctx context.Context, model *Model) (*albWaf.CreateCustomRul conditions, err := toConditionsPayload(ctx, rule.Conditions) if err != nil || conditions == nil { - return nil, fmt.Errorf("converting conditions: %v", err) + return nil, fmt.Errorf("converting conditions: %w", err) } payloadRules = append(payloadRules, albWaf.CreateCustomRule{ diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource_test.go b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go index 4179cb981..20474765b 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource_test.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go @@ -65,14 +65,14 @@ func TestToCreatePayload(t *testing.T) { expected: &albWaf.CreateCustomRuleGroupPayload{ Name: testName.ValueStringPointer(), Rules: []albWaf.CreateCustomRule{ - albWaf.CreateCustomRule{ + { Behaviour: &albWaf.Behaviour{ Action: new(albWaf.BehaviourAction("some-action")), Log: new(true), LogMsg: new("Log: something happened"), }, Conditions: []albWaf.Condition{ - albWaf.Condition{ + { Operator: &albWaf.ConditionOperator{ Type: new(albWaf.ConditionOperatorType("operator-type")), Value: new("operator-value"), @@ -129,10 +129,10 @@ func TestToCreatePayload(t *testing.T) { expected: &albWaf.CreateCustomRuleGroupPayload{ Name: testName.ValueStringPointer(), Rules: []albWaf.CreateCustomRule{ - albWaf.CreateCustomRule{ + { Behaviour: &albWaf.Behaviour{}, Conditions: []albWaf.Condition{ - albWaf.Condition{ + { Operator: &albWaf.ConditionOperator{}, Transformations: []albWaf.ConditionTransformationsInner{}, Variable: &albWaf.ConditionVariable{}, @@ -197,7 +197,7 @@ func TestMapFields(t *testing.T) { input: &albWaf.GetCustomRuleGroupResponse{ Name: testName.ValueStringPointer(), Rules: []albWaf.GetCustomRule{ - albWaf.GetCustomRule{ + { Behaviour: &albWaf.GetBehaviour{ Action: new(albWaf.GetBehaviourAction("some-action")), Log: new(true), @@ -205,7 +205,7 @@ func TestMapFields(t *testing.T) { Severity: new(albWaf.GetBehaviourSeverity("critical")), }, Conditions: []albWaf.Condition{ - albWaf.Condition{ + { Operator: &albWaf.ConditionOperator{ Type: new(albWaf.ConditionOperatorType("operator-type")), Value: new("operator-value"), @@ -289,7 +289,7 @@ func TestMapFields(t *testing.T) { region: testRegion.ValueString(), input: &albWaf.GetCustomRuleGroupResponse{ Rules: []albWaf.GetCustomRule{ - albWaf.GetCustomRule{}, + {}, }, }, expected: &Model{ diff --git a/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf b/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf index bf81b4d9d..6f6093981 100644 --- a/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf +++ b/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf @@ -12,8 +12,8 @@ variable "variable_type" {} variable "variable_value" {} resource "stackit_alb_waf_custom_rule_group" "custom_rule_group" { - project_id = var.project_id - name = var.name + project_id = var.project_id + name = var.name rules = [ { description = var.description diff --git a/stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf b/stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf index 10dd562be..29e7a71db 100644 --- a/stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf +++ b/stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf @@ -20,7 +20,7 @@ resource "stackit_alb_waf_custom_rule_group" "custom_rule_group" { value = "dummy" } variable = { - type = var.variable_type + type = var.variable_type } } ] From 1d66dc8dbdc09c3b24d9924ad87f693233501990 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Mon, 27 Jul 2026 19:51:33 +0200 Subject: [PATCH 03/11] upgrade to albwaf sdk v0.11.0 --- docs/resources/alb_waf_custom_rule_group.md | 2 +- go.mod | 2 +- go.sum | 4 +- .../services/albwaf/albwaf_acc_test.go | 6 +-- .../albwaf/custom_rule_group/resource.go | 50 ++++++++----------- .../albwaf/custom_rule_group/resource_test.go | 32 ++++++------ .../albwaf/managed_rule_set/resource.go | 4 +- .../albwaf/managed_rule_set/resource_test.go | 4 +- 8 files changed, 47 insertions(+), 57 deletions(-) diff --git a/docs/resources/alb_waf_custom_rule_group.md b/docs/resources/alb_waf_custom_rule_group.md index b265241ad..41e778dc8 100644 --- a/docs/resources/alb_waf_custom_rule_group.md +++ b/docs/resources/alb_waf_custom_rule_group.md @@ -39,10 +39,10 @@ ALB WAF Custom Rule Group resource schema. Uses the `default_region` specified i Required: - `behaviour` (Attributes) (see [below for nested schema](#nestedatt--rules--behaviour)) +- `conditions` (Attributes List) (see [below for nested schema](#nestedatt--rules--conditions)) Optional: -- `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. Read-Only: 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 3de990be9..8544e5f0f 100644 --- a/stackit/internal/services/albwaf/albwaf_acc_test.go +++ b/stackit/internal/services/albwaf/albwaf_acc_test.go @@ -15,7 +15,7 @@ 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" @@ -441,8 +441,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) } diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource.go b/stackit/internal/services/albwaf/custom_rule_group/resource.go index 7841808fe..d49b422cd 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource.go @@ -272,7 +272,7 @@ func (r *customRuleGroupResource) Schema(_ context.Context, _ resource.SchemaReq }, "conditions": schema.ListNestedAttribute{ Description: descriptions["rule_conditions"], - Optional: true, + Required: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "operator": schema.SingleNestedAttribute{ @@ -569,8 +569,8 @@ func toCreatePayload(ctx context.Context, model *Model) (*albWaf.CreateCustomRul } payloadRules = append(payloadRules, albWaf.CreateCustomRule{ - Behaviour: &albWaf.Behaviour{ - Action: (*albWaf.BehaviourAction)(behaviour.Action.ValueStringPointer()), + Behaviour: albWaf.Behaviour{ + Action: albWaf.BehaviourAction(behaviour.Action.ValueString()), Log: behaviour.Log.ValueBoolPointer(), LogMsg: behaviour.LogMsg.ValueStringPointer(), }, @@ -581,7 +581,7 @@ func toCreatePayload(ctx context.Context, model *Model) (*albWaf.CreateCustomRul } payload := &albWaf.CreateCustomRuleGroupPayload{ - Name: model.Name.ValueStringPointer(), + Name: model.Name.ValueString(), Rules: payloadRules, } @@ -607,38 +607,28 @@ func toConditionsPayload(ctx context.Context, conditions basetypes.ListValue) (* } } - var operator *albWaf.ConditionOperator var operatorModel = OperatorModel{} - if !tfutils.IsUndefined(condition.Operator) { - diags = condition.Operator.As(ctx, &operatorModel, basetypes.ObjectAsOptions{}) - if diags.HasError() { - return nil, fmt.Errorf("converting operator: %v", diags.Errors()) - } - - operator = &albWaf.ConditionOperator{ - Type: (*albWaf.ConditionOperatorType)(operatorModel.Type.ValueStringPointer()), - Value: operatorModel.Value.ValueStringPointer(), - } + diags = condition.Operator.As(ctx, &operatorModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, fmt.Errorf("converting operator: %v", diags.Errors()) } - var variable *albWaf.ConditionVariable var variableModel = VariableModel{} - if !tfutils.IsUndefined(condition.Variable) { - diags = condition.Variable.As(ctx, &variableModel, basetypes.ObjectAsOptions{}) - if diags.HasError() { - return nil, fmt.Errorf("converting variable: %v", diags.Errors()) - } - - variable = &albWaf.ConditionVariable{ - Type: (*albWaf.ConditionVariableType)(variableModel.Type.ValueStringPointer()), - Value: variableModel.Value.ValueStringPointer(), - } + 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: operator, + Operator: albWaf.ConditionOperator{ + Type: albWaf.ConditionOperatorType(operatorModel.Type.ValueString()), + Value: operatorModel.Value.ValueStringPointer(), + }, Transformations: transformations, - Variable: variable, + Variable: albWaf.ConditionVariable{ + Type: albWaf.ConditionVariableType(variableModel.Type.ValueString()), + Value: variableModel.Value.ValueStringPointer(), + }, }) } } @@ -748,7 +738,7 @@ func mapConditions(ctx context.Context, rule albWaf.GetCustomRule) (*basetypes.L if operator, ok := condition.GetOperatorOk(); ok { operatorModel := OperatorModel{ - Type: types.StringPointerValue((*string)(operator.Type)), + Type: types.StringValue(string(operator.Type)), Value: types.StringPointerValue(operator.Value), } @@ -767,7 +757,7 @@ func mapConditions(ctx context.Context, rule albWaf.GetCustomRule) (*basetypes.L if variable, ok := condition.GetVariableOk(); ok { variableModel := VariableModel{ - Type: types.StringPointerValue((*string)(variable.Type)), + Type: types.StringValue(string(variable.Type)), Value: types.StringPointerValue(variable.Value), } diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource_test.go b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go index 20474765b..5c9273d20 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource_test.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go @@ -63,26 +63,26 @@ func TestToCreatePayload(t *testing.T) { }), }, expected: &albWaf.CreateCustomRuleGroupPayload{ - Name: testName.ValueStringPointer(), + Name: testName.ValueString(), Rules: []albWaf.CreateCustomRule{ { - Behaviour: &albWaf.Behaviour{ - Action: new(albWaf.BehaviourAction("some-action")), + Behaviour: albWaf.Behaviour{ + Action: albWaf.BehaviourAction("some-action"), Log: new(true), LogMsg: new("Log: something happened"), }, Conditions: []albWaf.Condition{ { - Operator: &albWaf.ConditionOperator{ - Type: new(albWaf.ConditionOperatorType("operator-type")), + Operator: albWaf.ConditionOperator{ + Type: albWaf.ConditionOperatorType("operator-type"), Value: new("operator-value"), }, Transformations: []albWaf.ConditionTransformationsInner{ "foo", "bar", }, - Variable: &albWaf.ConditionVariable{ - Type: new(albWaf.ConditionVariableType("variable-type")), + Variable: albWaf.ConditionVariable{ + Type: albWaf.ConditionVariableType("variable-type"), Value: new("variable-value"), }, }, @@ -127,15 +127,15 @@ func TestToCreatePayload(t *testing.T) { }), }, expected: &albWaf.CreateCustomRuleGroupPayload{ - Name: testName.ValueStringPointer(), + Name: testName.ValueString(), Rules: []albWaf.CreateCustomRule{ { - Behaviour: &albWaf.Behaviour{}, + Behaviour: albWaf.Behaviour{}, Conditions: []albWaf.Condition{ { - Operator: &albWaf.ConditionOperator{}, + Operator: albWaf.ConditionOperator{}, Transformations: []albWaf.ConditionTransformationsInner{}, - Variable: &albWaf.ConditionVariable{}, + Variable: albWaf.ConditionVariable{}, }, }, }, @@ -152,7 +152,7 @@ func TestToCreatePayload(t *testing.T) { Region: testRegion, }, expected: &albWaf.CreateCustomRuleGroupPayload{ - Name: testName.ValueStringPointer(), + Name: testName.ValueString(), Rules: []albWaf.CreateCustomRule{}, }, isValid: true, @@ -206,16 +206,16 @@ func TestMapFields(t *testing.T) { }, Conditions: []albWaf.Condition{ { - Operator: &albWaf.ConditionOperator{ - Type: new(albWaf.ConditionOperatorType("operator-type")), + Operator: albWaf.ConditionOperator{ + Type: albWaf.ConditionOperatorType("operator-type"), Value: new("operator-value"), }, Transformations: []albWaf.ConditionTransformationsInner{ "foo", "bar", }, - Variable: &albWaf.ConditionVariable{ - Type: new(albWaf.ConditionVariableType("variable-type")), + Variable: albWaf.ConditionVariable{ + Type: albWaf.ConditionVariableType("variable-type"), Value: new("variable-value"), }, }, diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource.go b/stackit/internal/services/albwaf/managed_rule_set/resource.go index a63718da0..37f4bb9d5 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/resource.go +++ b/stackit/internal/services/albwaf/managed_rule_set/resource.go @@ -432,8 +432,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 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, }, From 221c0da7df2b5caf0e6acebe4a877a810fbb6498 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Tue, 28 Jul 2026 16:48:03 +0200 Subject: [PATCH 04/11] add examples --- .../data-source.tf | 4 +++ .../resource.tf | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 examples/data-sources/stackit_alb_waf_custom_rule_group/data-source.tf create mode 100644 examples/resources/stackit_alb_waf_custom_rule_group/resource.tf 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..3cc262086 --- /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" + behaviour = { + 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" + } + } + ] + } + ] +} From eb0482becdc2cf0ce709128073edab7673a36782 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Tue, 28 Jul 2026 16:50:41 +0200 Subject: [PATCH 05/11] generate docs --- .../data-sources/alb_waf_custom_rule_group.md | 9 ++++- docs/resources/alb_waf_custom_rule_group.md | 34 ++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/docs/data-sources/alb_waf_custom_rule_group.md b/docs/data-sources/alb_waf_custom_rule_group.md index 0ed80c49e..8d9f70290 100644 --- a/docs/data-sources/alb_waf_custom_rule_group.md +++ b/docs/data-sources/alb_waf_custom_rule_group.md @@ -13,7 +13,14 @@ ALB WAF Custom Rule Group resource schema. Uses the `default_region` specified i ~> 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 diff --git a/docs/resources/alb_waf_custom_rule_group.md b/docs/resources/alb_waf_custom_rule_group.md index 41e778dc8..7ace5c6aa 100644 --- a/docs/resources/alb_waf_custom_rule_group.md +++ b/docs/resources/alb_waf_custom_rule_group.md @@ -13,7 +13,39 @@ ALB WAF Custom Rule Group resource schema. Uses the `default_region` specified i ~> 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" + behaviour = { + 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 From e58fab4ef36ba1372b06472b045c78e92b4f6627 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Wed, 29 Jul 2026 12:19:25 +0200 Subject: [PATCH 06/11] start renaming behaviour to behavior --- .../data-sources/alb_waf_custom_rule_group.md | 6 +- docs/resources/alb_waf_custom_rule_group.md | 8 +- .../resource.tf | 2 +- .../services/albwaf/albwaf_acc_test.go | 46 ++++---- .../albwaf/custom_rule_group/datasource.go | 12 +- .../albwaf/custom_rule_group/resource.go | 108 +++++++++--------- .../albwaf/custom_rule_group/resource_test.go | 8 +- .../albwaf/testdata/custom-rule-group-max.tf | 2 +- .../albwaf/testdata/custom-rule-group-min.tf | 2 +- 9 files changed, 97 insertions(+), 97 deletions(-) diff --git a/docs/data-sources/alb_waf_custom_rule_group.md b/docs/data-sources/alb_waf_custom_rule_group.md index 8d9f70290..c9155af67 100644 --- a/docs/data-sources/alb_waf_custom_rule_group.md +++ b/docs/data-sources/alb_waf_custom_rule_group.md @@ -45,13 +45,13 @@ data "stackit_alb_waf_custom_rule_group" "example" { Read-Only: -- `behaviour` (Attributes) (see [below for nested schema](#nestedatt--rules--behaviour)) +- `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.behaviour` + +### Nested Schema for `rules.behavior` Read-Only: diff --git a/docs/resources/alb_waf_custom_rule_group.md b/docs/resources/alb_waf_custom_rule_group.md index 7ace5c6aa..c88421b56 100644 --- a/docs/resources/alb_waf_custom_rule_group.md +++ b/docs/resources/alb_waf_custom_rule_group.md @@ -22,7 +22,7 @@ resource "stackit_alb_waf_custom_rule_group" "example" { rules = [ { description = "My custom rule group" - behaviour = { + behavior = { action = "ACTION_DENY" log = true logMsg = "Some custom notification message string" @@ -70,7 +70,7 @@ resource "stackit_alb_waf_custom_rule_group" "example" { Required: -- `behaviour` (Attributes) (see [below for nested schema](#nestedatt--rules--behaviour)) +- `behavior` (Attributes) (see [below for nested schema](#nestedatt--rules--behavior)) - `conditions` (Attributes List) (see [below for nested schema](#nestedatt--rules--conditions)) Optional: @@ -81,8 +81,8 @@ Read-Only: - `id` (Number) Backend auto-allocated unique rule ID within the valid 1-99999 threshold. - -### Nested Schema for `rules.behaviour` + +### Nested Schema for `rules.behavior` Required: diff --git a/examples/resources/stackit_alb_waf_custom_rule_group/resource.tf b/examples/resources/stackit_alb_waf_custom_rule_group/resource.tf index 3cc262086..b34c43a61 100644 --- a/examples/resources/stackit_alb_waf_custom_rule_group/resource.tf +++ b/examples/resources/stackit_alb_waf_custom_rule_group/resource.tf @@ -4,7 +4,7 @@ resource "stackit_alb_waf_custom_rule_group" "example" { rules = [ { description = "My custom rule group" - behaviour = { + behavior = { action = "ACTION_DENY" log = true logMsg = "Some custom notification message string" diff --git a/stackit/internal/services/albwaf/albwaf_acc_test.go b/stackit/internal/services/albwaf/albwaf_acc_test.go index 8544e5f0f..38733bce9 100644 --- a/stackit/internal/services/albwaf/albwaf_acc_test.go +++ b/stackit/internal/services/albwaf/albwaf_acc_test.go @@ -102,9 +102,9 @@ func TestAccCustomRuleGroupMin(t *testing.T) { 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.behaviour.action", testutil.ConvertConfigVariable(testCustomRuleGroupMin["action"])), - // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log", "false"), - // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.severity"), + 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"])), @@ -143,11 +143,11 @@ func TestAccCustomRuleGroupMin(t *testing.T) { "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.behaviour.action", testutil.ConvertConfigVariable(testCustomRuleGroupMin["action"])), - // resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log", "false"), + 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.behaviour.severity", - "stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.severity", + "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"), @@ -194,9 +194,9 @@ func TestAccCustomRuleGroupMin(t *testing.T) { 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.behaviour.action", testutil.ConvertConfigVariable(testCustomRuleGroupMinUpdated()["action"])), - // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log", "false"), - // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.severity"), + 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"])), @@ -230,10 +230,10 @@ func TestAccCustomRuleGroupMax(t *testing.T) { 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.behaviour.action", testutil.ConvertConfigVariable(testCustomRuleGroupMax["action"])), - resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log"])), - // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log_msg", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log_msg"])), - // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.severity"), + 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"])), @@ -276,12 +276,12 @@ func TestAccCustomRuleGroupMax(t *testing.T) { "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.behaviour.action", testutil.ConvertConfigVariable(testCustomRuleGroupMax["action"])), - resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log"])), - // resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log_msg", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log_msg"])), + 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.behaviour.severity", - "stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.severity", + "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"), @@ -332,10 +332,10 @@ func TestAccCustomRuleGroupMax(t *testing.T) { // 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.behaviour.action", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["action"])), - resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["log"])), - // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.log_msg", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["log_msg"])), - // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behaviour.severity"), + 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"])), diff --git a/stackit/internal/services/albwaf/custom_rule_group/datasource.go b/stackit/internal/services/albwaf/custom_rule_group/datasource.go index b10c9ec94..128fa3d67 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/datasource.go +++ b/stackit/internal/services/albwaf/custom_rule_group/datasource.go @@ -97,24 +97,24 @@ func (r *customRuleGroupDataSource) Schema(_ context.Context, _ datasource.Schem Computed: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ - "behaviour": schema.SingleNestedAttribute{ - Description: descriptions["behaviour"], + "behavior": schema.SingleNestedAttribute{ + Description: descriptions["behavior"], Computed: true, Attributes: map[string]schema.Attribute{ "action": schema.StringAttribute{ - Description: descriptions["behaviour_action"], + Description: descriptions["behavior_action"], Computed: true, }, "log": schema.BoolAttribute{ - Description: descriptions["behaviour_log"], + Description: descriptions["behavior_log"], Computed: true, }, "log_msg": schema.StringAttribute{ - Description: descriptions["behaviour_log_msg"], + Description: descriptions["behavior_log_msg"], Computed: true, }, "severity": schema.StringAttribute{ - Description: descriptions["behaviour_severity"], + Description: descriptions["behavior_severity"], Computed: true, }, }, diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource.go b/stackit/internal/services/albwaf/custom_rule_group/resource.go index d49b422cd..d7721638b 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource.go @@ -57,14 +57,14 @@ type Model struct { } type RuleModel struct { - Behaviour types.Object `tfsdk:"behaviour"` + 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{ - "behaviour": types.ObjectType{AttrTypes: behaviourType}, + "behavior": types.ObjectType{AttrTypes: behaviorType}, "conditions": types.ListType{ ElemType: types.ObjectType{AttrTypes: conditionType}, }, @@ -72,14 +72,14 @@ var ruleType = map[string]attr.Type{ "id": types.Int32Type, } -type BehaviourModel struct { +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 behaviourType = map[string]attr.Type{ +var behaviorType = map[string]attr.Type{ "action": types.StringType, "log": types.BoolType, "log_msg": types.StringType, @@ -163,29 +163,29 @@ func (r *customRuleGroupResource) Metadata(_ context.Context, req resource.Metad // 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_behaviour": "Behaviour 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.", - "behaviour_action": "The protective stance action. ACTION_DENY forces a 403 status response code.", - "behaviour_log": "Determines whether an entry should be generated in the security ledger upon a rule hit.", - "behaviour_log_msg": "Custom notification message string mapped to underlying logdata contexts. Required if log is true.", - "behaviour_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).", - "usage": "Tracking metrics for CRG resource utilization.", - "usage_count": "Number of WAF configurations actively using this rule group.", - "usage_items": "List of individual WAF configuration names that bind this rule group.", + "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).", + "usage": "Tracking metrics for CRG resource utilization.", + "usage_count": "Number of WAF configurations actively using this rule group.", + "usage_items": "List of individual WAF configuration names that bind this rule group.", } func (r *customRuleGroupResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { @@ -242,27 +242,27 @@ func (r *customRuleGroupResource) Schema(_ context.Context, _ resource.SchemaReq }, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ - "behaviour": schema.SingleNestedAttribute{ - Description: descriptions["behaviour"], + "behavior": schema.SingleNestedAttribute{ + Description: descriptions["behavior"], Required: true, Attributes: map[string]schema.Attribute{ "action": schema.StringAttribute{ - Description: descriptions["behaviour_action"], + Description: descriptions["behavior_action"], Required: true, Validators: []validator.String{ stringvalidator.OneOf(actionOptions...), }, }, "log": schema.BoolAttribute{ - Description: descriptions["behaviour_log"], + Description: descriptions["behavior_log"], Optional: true, }, "log_msg": schema.StringAttribute{ - Description: descriptions["behaviour_log_msg"], + Description: descriptions["behavior_log_msg"], Optional: true, }, "severity": schema.StringAttribute{ - Description: descriptions["behaviour_severity"], + Description: descriptions["behavior_severity"], Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), @@ -555,11 +555,11 @@ func toCreatePayload(ctx context.Context, model *Model) (*albWaf.CreateCustomRul } for _, rule := range rules { - behaviour := BehaviourModel{} - if !tfutils.IsUndefined(rule.Behaviour) { - diags := rule.Behaviour.As(ctx, &behaviour, basetypes.ObjectAsOptions{}) + 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 behaviour: %v", diags.Errors()) + return nil, fmt.Errorf("converting to rule behavior: %v", diags.Errors()) } } @@ -570,9 +570,9 @@ func toCreatePayload(ctx context.Context, model *Model) (*albWaf.CreateCustomRul payloadRules = append(payloadRules, albWaf.CreateCustomRule{ Behaviour: albWaf.Behaviour{ - Action: albWaf.BehaviourAction(behaviour.Action.ValueString()), - Log: behaviour.Log.ValueBoolPointer(), - LogMsg: behaviour.LogMsg.ValueStringPointer(), + Action: albWaf.BehaviourAction(behavior.Action.ValueString()), + Log: behavior.Log.ValueBoolPointer(), + LogMsg: behavior.LogMsg.ValueStringPointer(), }, Conditions: *conditions, Description: rule.Description.ValueStringPointer(), @@ -675,11 +675,11 @@ func mapRules(ctx context.Context, rules *[]albWaf.GetCustomRule) (*basetypes.Li Description: types.StringPointerValue(rule.Description), } - behaviour, err := mapBehaviour(ctx, rule.Behaviour) - if err != nil || behaviour == nil { - return nil, fmt.Errorf("map behaviour: %w", err) + behavior, err := mapBehavior(ctx, rule.Behaviour) + if err != nil || behavior == nil { + return nil, fmt.Errorf("map behavior: %w", err) } - ruleTF.Behaviour = *behaviour + ruleTF.Behavior = *behavior conditions, err := mapConditions(ctx, rule) if err != nil || conditions == nil { @@ -704,24 +704,24 @@ func mapRules(ctx context.Context, rules *[]albWaf.GetCustomRule) (*basetypes.Li return &result, nil } -func mapBehaviour(ctx context.Context, behaviour *albWaf.GetBehaviour) (*basetypes.ObjectValue, error) { +func mapBehavior(ctx context.Context, behavior *albWaf.GetBehaviour) (*basetypes.ObjectValue, error) { var diags diag.Diagnostics var result basetypes.ObjectValue - if behaviour != nil { - behaviourModel := BehaviourModel{ - Action: types.StringPointerValue((*string)(behaviour.Action)), - Log: types.BoolPointerValue(behaviour.Log), - LogMsg: types.StringPointerValue(behaviour.LogMsg), - Severity: types.StringPointerValue((*string)(behaviour.Severity)), + 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, behaviourType, behaviourModel) + result, diags = types.ObjectValueFrom(ctx, behaviorType, behaviorModel) if diags.HasError() { - return nil, fmt.Errorf("creating behaviour object: %w", core.DiagsToError(diags)) + return nil, fmt.Errorf("creating behavior object: %w", core.DiagsToError(diags)) } } else { - result = types.ObjectNull(behaviourType) + result = types.ObjectNull(behaviorType) } 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 index 5c9273d20..9b6de9a52 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource_test.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go @@ -35,7 +35,7 @@ func TestToCreatePayload(t *testing.T) { Region: testRegion, Rules: types.ListValueMust(types.ObjectType{AttrTypes: ruleType}, []attr.Value{ types.ObjectValueMust(ruleType, map[string]attr.Value{ - "behaviour": types.ObjectValueMust(behaviourType, 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"), @@ -102,7 +102,7 @@ func TestToCreatePayload(t *testing.T) { Region: testRegion, Rules: types.ListValueMust(types.ObjectType{AttrTypes: ruleType}, []attr.Value{ types.ObjectValueMust(ruleType, map[string]attr.Value{ - "behaviour": types.ObjectValueMust(behaviourType, map[string]attr.Value{ + "behavior": types.ObjectValueMust(behaviorType, map[string]attr.Value{ "action": types.StringNull(), "log": types.BoolNull(), "log_msg": types.StringNull(), @@ -240,7 +240,7 @@ func TestMapFields(t *testing.T) { Id: testId, Rules: types.ListValueMust(types.ObjectType{AttrTypes: ruleType}, []attr.Value{ types.ObjectValueMust(ruleType, map[string]attr.Value{ - "behaviour": types.ObjectValueMust(behaviourType, 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"), @@ -299,7 +299,7 @@ func TestMapFields(t *testing.T) { Region: testRegion, Rules: types.ListValueMust(types.ObjectType{AttrTypes: ruleType}, []attr.Value{ types.ObjectValueMust(ruleType, map[string]attr.Value{ - "behaviour": types.ObjectNull(behaviourType), + "behavior": types.ObjectNull(behaviorType), "conditions": types.ListNull(types.ObjectType{AttrTypes: conditionType}), "description": types.StringNull(), "id": types.Int32Null(), diff --git a/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf b/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf index 6f6093981..74495fb3d 100644 --- a/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf +++ b/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf @@ -17,7 +17,7 @@ resource "stackit_alb_waf_custom_rule_group" "custom_rule_group" { rules = [ { description = var.description - behaviour = { + behavior = { action = var.action log = var.log logMsg = var.log_msg diff --git a/stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf b/stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf index 29e7a71db..cf1c92e6b 100644 --- a/stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf +++ b/stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf @@ -10,7 +10,7 @@ resource "stackit_alb_waf_custom_rule_group" "custom_rule_group" { name = var.name rules = [ { - behaviour = { + behavior = { action = var.action } conditions = [ From 2bcf5adab694d2234511942cdfdd7048f2ac9376 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Fri, 31 Jul 2026 10:29:18 +0200 Subject: [PATCH 07/11] ignored linter warnings for generated structs --- .../internal/services/albwaf/custom_rule_group/resource.go | 4 ++-- .../services/albwaf/custom_rule_group/resource_test.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource.go b/stackit/internal/services/albwaf/custom_rule_group/resource.go index d7721638b..89ad9ab82 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource.go @@ -569,7 +569,7 @@ func toCreatePayload(ctx context.Context, model *Model) (*albWaf.CreateCustomRul } payloadRules = append(payloadRules, albWaf.CreateCustomRule{ - Behaviour: albWaf.Behaviour{ + Behaviour: albWaf.Behaviour{ // nolint:misspell // Generated from API spec Action: albWaf.BehaviourAction(behavior.Action.ValueString()), Log: behavior.Log.ValueBoolPointer(), LogMsg: behavior.LogMsg.ValueStringPointer(), @@ -675,7 +675,7 @@ func mapRules(ctx context.Context, rules *[]albWaf.GetCustomRule) (*basetypes.Li Description: types.StringPointerValue(rule.Description), } - behavior, err := mapBehavior(ctx, rule.Behaviour) + 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) } diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource_test.go b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go index 9b6de9a52..7c8ad6d54 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource_test.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go @@ -66,7 +66,7 @@ func TestToCreatePayload(t *testing.T) { Name: testName.ValueString(), Rules: []albWaf.CreateCustomRule{ { - Behaviour: albWaf.Behaviour{ + Behaviour: albWaf.Behaviour{ // nolint:misspell // Generated from API spec Action: albWaf.BehaviourAction("some-action"), Log: new(true), LogMsg: new("Log: something happened"), @@ -130,7 +130,7 @@ func TestToCreatePayload(t *testing.T) { Name: testName.ValueString(), Rules: []albWaf.CreateCustomRule{ { - Behaviour: albWaf.Behaviour{}, + Behaviour: albWaf.Behaviour{}, // nolint:misspell // Generated from API spec Conditions: []albWaf.Condition{ { Operator: albWaf.ConditionOperator{}, @@ -198,7 +198,7 @@ func TestMapFields(t *testing.T) { Name: testName.ValueStringPointer(), Rules: []albWaf.GetCustomRule{ { - Behaviour: &albWaf.GetBehaviour{ + Behaviour: &albWaf.GetBehaviour{ // nolint:misspell // Generated from API spec Action: new(albWaf.GetBehaviourAction("some-action")), Log: new(true), LogMsg: new("Log: something happened"), From 692a40c094cafb600f9ddc0056d7516f0a2e3331 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Fri, 31 Jul 2026 10:33:14 +0200 Subject: [PATCH 08/11] remove usage --- .../data-sources/alb_waf_custom_rule_group.md | 12 ---- docs/data-sources/alb_waf_managed_rule_set.md | 11 ---- docs/resources/alb_waf_custom_rule_group.md | 12 ---- docs/resources/alb_waf_managed_rule_set.md | 11 ---- .../services/albwaf/albwaf_acc_test.go | 18 ------ .../albwaf/custom_rule_group/datasource.go | 15 ----- .../albwaf/custom_rule_group/resource.go | 60 ------------------- .../albwaf/custom_rule_group/resource_test.go | 16 ----- .../albwaf/managed_rule_set/datasource.go | 16 ----- .../albwaf/managed_rule_set/resource.go | 47 --------------- 10 files changed, 218 deletions(-) diff --git a/docs/data-sources/alb_waf_custom_rule_group.md b/docs/data-sources/alb_waf_custom_rule_group.md index c9155af67..4adfc985d 100644 --- a/docs/data-sources/alb_waf_custom_rule_group.md +++ b/docs/data-sources/alb_waf_custom_rule_group.md @@ -38,7 +38,6 @@ data "stackit_alb_waf_custom_rule_group" "example" { - `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)) -- `usage` (Attributes) Tracking metrics for CRG resource utilization. (see [below for nested schema](#nestedatt--usage)) ### Nested Schema for `rules` @@ -86,14 +85,3 @@ 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). - - - - - -### Nested Schema for `usage` - -Read-Only: - -- `count` (Number) Number of WAF configurations actively using this rule group. -- `items` (List of String) List of individual WAF configuration names that bind this rule group. 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 index c88421b56..e5bcee6a1 100644 --- a/docs/resources/alb_waf_custom_rule_group.md +++ b/docs/resources/alb_waf_custom_rule_group.md @@ -63,7 +63,6 @@ resource "stackit_alb_waf_custom_rule_group" "example" { ### Read-Only - `id` (String) Terraform's internal resource identifier. Structured as "`project_id`,`region`,`name`". -- `usage` (Attributes) Tracking metrics for CRG resource utilization. (see [below for nested schema](#nestedatt--usage)) ### Nested Schema for `rules` @@ -132,14 +131,3 @@ Required: Optional: - `value` (String) Optional key element context for map variables (e.g., matching a 'Host' header key). - - - - - -### Nested Schema for `usage` - -Read-Only: - -- `count` (Number) Number of WAF configurations actively using this rule group. -- `items` (List of String) List of individual WAF configuration names that bind this rule group. 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/stackit/internal/services/albwaf/albwaf_acc_test.go b/stackit/internal/services/albwaf/albwaf_acc_test.go index 38733bce9..7b5455f8a 100644 --- a/stackit/internal/services/albwaf/albwaf_acc_test.go +++ b/stackit/internal/services/albwaf/albwaf_acc_test.go @@ -110,8 +110,6 @@ func TestAccCustomRuleGroupMin(t *testing.T) { 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"])), - - resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "usage.count", "0"), ), }, // Data source @@ -154,8 +152,6 @@ func TestAccCustomRuleGroupMin(t *testing.T) { 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"])), - - resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "usage.count", "0"), ), }, // Import @@ -202,8 +198,6 @@ func TestAccCustomRuleGroupMin(t *testing.T) { 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"])), - - resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "usage.count", "0"), ), }, // Deletion is done by the framework implicitly @@ -242,8 +236,6 @@ func TestAccCustomRuleGroupMax(t *testing.T) { 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"])), - - resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "usage.count", "0"), ), }, // Data source @@ -291,8 +283,6 @@ func TestAccCustomRuleGroupMax(t *testing.T) { 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"])), - - resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "usage.count", "0"), ), }, // Import @@ -344,8 +334,6 @@ func TestAccCustomRuleGroupMax(t *testing.T) { 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"])), - - resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "usage.count", "0"), ), }, // Deletion is done by the framework implicitly @@ -368,8 +356,6 @@ func TestAccManagedRuleSet(t *testing.T) { 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", "type", testutil.ConvertConfigVariable(testManagedRuleSet["type"])), - - resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"), ), }, // Data source @@ -395,8 +381,6 @@ func TestAccManagedRuleSet(t *testing.T) { ), 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", "type", testutil.ConvertConfigVariable(testManagedRuleSet["type"])), - - resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"), ), }, // Import @@ -432,8 +416,6 @@ func TestAccManagedRuleSet(t *testing.T) { 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", "type", testutil.ConvertConfigVariable(testManagedRuleSetUpdated()["type"])), - - resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"), ), }, // Deletion is done by the framework implicitly diff --git a/stackit/internal/services/albwaf/custom_rule_group/datasource.go b/stackit/internal/services/albwaf/custom_rule_group/datasource.go index 128fa3d67..9ff842a37 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/datasource.go +++ b/stackit/internal/services/albwaf/custom_rule_group/datasource.go @@ -171,21 +171,6 @@ func (r *customRuleGroupDataSource) Schema(_ context.Context, _ datasource.Schem }, }, }, - "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, - }, - }, - }, }, } } diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource.go b/stackit/internal/services/albwaf/custom_rule_group/resource.go index 89ad9ab82..6c3f18beb 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource.go @@ -53,7 +53,6 @@ type Model struct { Region types.String `tfsdk:"region"` Name types.String `tfsdk:"name"` Rules types.List `tfsdk:"rules"` - Usage types.Object `tfsdk:"usage"` } type RuleModel struct { @@ -118,16 +117,6 @@ var variableType = map[string]attr.Type{ "value": 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 customRuleGroupResource struct { client *albWaf.APIClient providerData core.ProviderData @@ -183,9 +172,6 @@ var descriptions = map[string]string{ "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).", - "usage": "Tracking metrics for CRG resource utilization.", - "usage_count": "Number of WAF configurations actively using this rule group.", - "usage_items": "List of individual WAF configuration names that bind this rule group.", } func (r *customRuleGroupResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { @@ -336,21 +322,6 @@ func (r *customRuleGroupResource) Schema(_ context.Context, _ resource.SchemaReq }, }, }, - "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, - }, - }, - }, }, } } @@ -654,12 +625,6 @@ func mapFields(ctx context.Context, customRuleGroup *albWaf.GetCustomRuleGroupRe } model.Rules = *rules - usage, err := mapUsage(ctx, customRuleGroup.Usage) - if err != nil || usage == nil { - return fmt.Errorf("map usage: %w", err) - } - model.Usage = *usage - return nil } @@ -785,28 +750,3 @@ func mapConditions(ctx context.Context, rule albWaf.GetCustomRule) (*basetypes.L return &result, nil } - -func mapUsage(ctx context.Context, usage *albWaf.CRGUsage) (*basetypes.ObjectValue, error) { - var diags diag.Diagnostics - var result basetypes.ObjectValue - - if usage != nil { - usageModel := UsageModel{ - Count: types.Int32PointerValue(usage.Count), - } - - usageModel.Items, diags = types.ListValueFrom(ctx, types.StringType, usage.GetItems()) - if diags.HasError() { - return nil, fmt.Errorf("creating usage object: %w", core.DiagsToError(diags)) - } - - result, diags = types.ObjectValueFrom(ctx, usageType, usageModel) - if diags.HasError() { - return nil, fmt.Errorf("creating usage object: %w", core.DiagsToError(diags)) - } - } else { - result = types.ObjectNull(usageType) - } - - 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 index 7c8ad6d54..cb691f437 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource_test.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go @@ -224,14 +224,6 @@ func TestMapFields(t *testing.T) { Id: new(int32(42)), }, }, - Usage: &albWaf.CRGUsage{ - Count: new(int32(42)), - Items: []string{ - "one", - "two", - "three", - }, - }, }, expected: &Model{ ProjectId: testProjectId, @@ -266,14 +258,6 @@ func TestMapFields(t *testing.T) { "id": types.Int32Value(42), }), }), - Usage: types.ObjectValueMust(usageType, map[string]attr.Value{ - "count": types.Int32Value(42), - "items": types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("one"), - types.StringValue("two"), - types.StringValue("three"), - }), - }), }, isValid: true, }, 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 37f4bb9d5..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, @@ -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 } From 2334a585d2f4641e0decaf0c51671a4c4a3bbd63 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Mon, 3 Aug 2026 11:21:30 +0200 Subject: [PATCH 09/11] fix comments --- .../albwaf/custom_rule_group/resource.go | 30 ++++++++++++------- .../albwaf/custom_rule_group/resource_test.go | 5 +++- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource.go b/stackit/internal/services/albwaf/custom_rule_group/resource.go index 6c3f18beb..09fb1230e 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource.go @@ -522,7 +522,7 @@ func toCreatePayload(ctx context.Context, model *Model) (*albWaf.CreateCustomRul rules := []RuleModel{} diags := model.Rules.ElementsAs(ctx, &rules, true) if diags.HasError() { - return nil, fmt.Errorf("converting to rule map: %v", diags.Errors()) + return nil, fmt.Errorf("converting to rule map: %w", core.DiagsToError(diags)) } for _, rule := range rules { @@ -530,13 +530,15 @@ func toCreatePayload(ctx context.Context, model *Model) (*albWaf.CreateCustomRul 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()) + return nil, fmt.Errorf("converting to rule behavior: %w", core.DiagsToError(diags)) } } conditions, err := toConditionsPayload(ctx, rule.Conditions) - if err != nil || conditions == nil { + if err != nil { return nil, fmt.Errorf("converting conditions: %w", err) + } else if conditions == nil { + return nil, fmt.Errorf("conditions can not be empty") } payloadRules = append(payloadRules, albWaf.CreateCustomRule{ @@ -566,7 +568,7 @@ func toConditionsPayload(ctx context.Context, conditions basetypes.ListValue) (* conditionModels := []ConditionModel{} diags := conditions.ElementsAs(ctx, &conditionModels, true) if diags.HasError() { - return nil, fmt.Errorf("converting to rule map: %v", diags.Errors()) + return nil, fmt.Errorf("converting to rule map: %w", core.DiagsToError(diags)) } for _, condition := range conditionModels { @@ -574,20 +576,20 @@ func toConditionsPayload(ctx context.Context, conditions basetypes.ListValue) (* if !tfutils.IsUndefined(condition.Transformations) { diags := condition.Transformations.ElementsAs(ctx, &transformations, true) if diags.HasError() { - return nil, fmt.Errorf("converting transformations: %v", diags.Errors()) + return nil, fmt.Errorf("converting transformations: %w", core.DiagsToError(diags)) } } var operatorModel = OperatorModel{} diags = condition.Operator.As(ctx, &operatorModel, basetypes.ObjectAsOptions{}) if diags.HasError() { - return nil, fmt.Errorf("converting operator: %v", diags.Errors()) + return nil, fmt.Errorf("converting operator: %w", core.DiagsToError(diags)) } var variableModel = VariableModel{} diags = condition.Variable.As(ctx, &variableModel, basetypes.ObjectAsOptions{}) if diags.HasError() { - return nil, fmt.Errorf("converting variable: %v", diags.Errors()) + return nil, fmt.Errorf("converting variable: %w", core.DiagsToError(diags)) } result = append(result, albWaf.Condition{ @@ -616,12 +618,14 @@ func mapFields(ctx context.Context, customRuleGroup *albWaf.GetCustomRuleGroupRe } model.Id = tfutils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, model.Name.ValueString()) - model.Name = types.StringValue(model.Name.ValueString()) + model.Name = types.StringValue(customRuleGroup.GetName()) model.Region = types.StringValue(region) rules, err := mapRules(ctx, &customRuleGroup.Rules) - if err != nil || rules == nil { + if err != nil { return fmt.Errorf("map rules: %w", err) + } else if rules == nil { + return fmt.Errorf("rules can not be empty") } model.Rules = *rules @@ -641,14 +645,18 @@ func mapRules(ctx context.Context, rules *[]albWaf.GetCustomRule) (*basetypes.Li } behavior, err := mapBehavior(ctx, rule.Behaviour) // nolint:misspell // Generated from API spec - if err != nil || behavior == nil { + if err != nil { return nil, fmt.Errorf("map behavior: %w", err) + } else if behavior == nil { + return nil, fmt.Errorf("behavior can not be empty") } ruleTF.Behavior = *behavior conditions, err := mapConditions(ctx, rule) - if err != nil || conditions == nil { + if err != nil { return nil, fmt.Errorf("map conditions: %w", err) + } else if conditions == nil { + return nil, fmt.Errorf("conditions can not be empty") } ruleTF.Conditions = *conditions diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource_test.go b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go index cb691f437..0c941f082 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource_test.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go @@ -272,6 +272,7 @@ func TestMapFields(t *testing.T) { }, region: testRegion.ValueString(), input: &albWaf.GetCustomRuleGroupResponse{ + Name: testName.ValueStringPointer(), Rules: []albWaf.GetCustomRule{ {}, }, @@ -301,7 +302,9 @@ func TestMapFields(t *testing.T) { Id: testId, }, region: testRegion.ValueString(), - input: &albWaf.GetCustomRuleGroupResponse{}, + input: &albWaf.GetCustomRuleGroupResponse{ + Name: testName.ValueStringPointer(), + }, expected: &Model{ Name: testName, Id: testId, From 2faac03c700e853e832bbf12e7d4e6e21f7e9d73 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Mon, 3 Aug 2026 16:07:36 +0200 Subject: [PATCH 10/11] add update endpoint --- .../resource.tf | 2 +- go.mod | 2 +- go.sum | 4 +- .../services/albwaf/albwaf_acc_test.go | 5 +- .../albwaf/custom_rule_group/resource.go | 99 ++++++++++++++++--- .../albwaf/testdata/custom-rule-group-max.tf | 2 +- 6 files changed, 93 insertions(+), 21 deletions(-) diff --git a/examples/resources/stackit_alb_waf_custom_rule_group/resource.tf b/examples/resources/stackit_alb_waf_custom_rule_group/resource.tf index b34c43a61..c91a492dc 100644 --- a/examples/resources/stackit_alb_waf_custom_rule_group/resource.tf +++ b/examples/resources/stackit_alb_waf_custom_rule_group/resource.tf @@ -7,7 +7,7 @@ resource "stackit_alb_waf_custom_rule_group" "example" { behavior = { action = "ACTION_DENY" log = true - logMsg = "Some custom notification message string" + log_msg = "Some custom notification message string" } conditions = [ { diff --git a/go.mod b/go.mod index c60ea4438..9331ea030 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.11.0 + github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.12.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 74c2781a8..1778de5ae 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.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/albwaf v0.12.0 h1:H1Cv5GBJvUU9bRrMVobc8no+cDDsqcpcDA+EjdzmhIw= +github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.12.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 7b5455f8a..3dbf5ffa1 100644 --- a/stackit/internal/services/albwaf/albwaf_acc_test.go +++ b/stackit/internal/services/albwaf/albwaf_acc_test.go @@ -66,7 +66,8 @@ var testCustomRuleGroupMax = config.Variables{ var testCustomRuleGroupMaxUpdated = func() config.Variables { updatedConfig := config.Variables{} maps.Copy(updatedConfig, testCustomRuleGroupMax) - updatedConfig["name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["name"]))) + // Name should not be updated, test if the update works in place + updatedConfig["log_msg"] = config.StringVariable("foo-bar:") // updatedConfig["log"] = config.BoolVariable(false) return updatedConfig } @@ -309,7 +310,7 @@ func TestAccCustomRuleGroupMax(t *testing.T) { 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), + plancheck.ExpectResourceAction("stackit_alb_waf_custom_rule_group.custom_rule_group", plancheck.ResourceActionUpdate), }, }, Check: resource.ComposeAggregateTestCheckFunc( diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource.go b/stackit/internal/services/albwaf/custom_rule_group/resource.go index 09fb1230e..b868e8bd9 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource.go @@ -15,7 +15,6 @@ import ( "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" @@ -220,9 +219,6 @@ func (r *customRuleGroupResource) Schema(_ context.Context, _ resource.SchemaReq "rules": schema.ListNestedAttribute{ Description: descriptions["rules"], Required: true, - PlanModifiers: []planmodifier.List{ - listplanmodifier.RequiresReplace(), - }, Validators: []validator.List{ listvalidator.SizeAtLeast(1), }, @@ -431,8 +427,52 @@ func (r *customRuleGroupResource) Create(ctx context.Context, req resource.Creat 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) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // 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() + customRuleGroupName := model.Name.ValueString() + region := model.Region.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "name", customRuleGroupName) + ctx = tflog.SetField(ctx, "region", region) + + payload, err := toUpdatePayload(ctx, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating export policy", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + updateResp, err := r.client.DefaultAPI.UpdateCustomRuleGroup(ctx, projectId, region, customRuleGroupName).UpdateCustomRuleGroupPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating export policy", fmt.Sprintf("Calling API to update export policy: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + // map export policy + err = mapFields(ctx, updateResp, &model, region) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating export policy", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + // Set state to fully populated data + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Info(ctx, "ALB WAF Custom Rule Group update") } func (r *customRuleGroupResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform @@ -517,10 +557,46 @@ func toCreatePayload(ctx context.Context, model *Model) (*albWaf.CreateCustomRul return nil, fmt.Errorf("nil model") } + payloadRules, err := toRulesPayload(ctx, model.Rules) + if err != nil { + return nil, fmt.Errorf("generating rules payload: %w", err) + } else if payloadRules == nil { + return nil, fmt.Errorf("rules can not be empty") + } + + payload := &albWaf.CreateCustomRuleGroupPayload{ + Name: model.Name.ValueString(), + Rules: *payloadRules, + } + + return payload, nil +} + +func toUpdatePayload(ctx context.Context, model *Model) (*albWaf.UpdateCustomRuleGroupPayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + + payloadRules, err := toRulesPayload(ctx, model.Rules) + if err != nil { + return nil, fmt.Errorf("generating rules payload: %w", err) + } else if payloadRules == nil { + return nil, fmt.Errorf("rules can not be empty") + } + + payload := &albWaf.UpdateCustomRuleGroupPayload{ + Name: model.Name.ValueString(), + Rules: *payloadRules, + } + + return payload, nil +} + +func toRulesPayload(ctx context.Context, modelRules basetypes.ListValue) (*[]albWaf.CreateCustomRule, error) { payloadRules := []albWaf.CreateCustomRule{} - if !tfutils.IsUndefined(model.Rules) { + if !tfutils.IsUndefined(modelRules) { rules := []RuleModel{} - diags := model.Rules.ElementsAs(ctx, &rules, true) + diags := modelRules.ElementsAs(ctx, &rules, true) if diags.HasError() { return nil, fmt.Errorf("converting to rule map: %w", core.DiagsToError(diags)) } @@ -553,12 +629,7 @@ func toCreatePayload(ctx context.Context, model *Model) (*albWaf.CreateCustomRul } } - payload := &albWaf.CreateCustomRuleGroupPayload{ - Name: model.Name.ValueString(), - Rules: payloadRules, - } - - return payload, nil + return &payloadRules, nil } func toConditionsPayload(ctx context.Context, conditions basetypes.ListValue) (*[]albWaf.Condition, error) { diff --git a/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf b/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf index 74495fb3d..738dee387 100644 --- a/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf +++ b/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf @@ -20,7 +20,7 @@ resource "stackit_alb_waf_custom_rule_group" "custom_rule_group" { behavior = { action = var.action log = var.log - logMsg = var.log_msg + log_msg = var.log_msg } conditions = [ { From abf915c3a0d48ccda02d62d1b057aa507f14ebaa Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Mon, 3 Aug 2026 16:34:21 +0200 Subject: [PATCH 11/11] generate docs --- docs/resources/alb_waf_custom_rule_group.md | 2 +- .../internal/services/albwaf/custom_rule_group/resource.go | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/resources/alb_waf_custom_rule_group.md b/docs/resources/alb_waf_custom_rule_group.md index e5bcee6a1..baf9ccf36 100644 --- a/docs/resources/alb_waf_custom_rule_group.md +++ b/docs/resources/alb_waf_custom_rule_group.md @@ -25,7 +25,7 @@ resource "stackit_alb_waf_custom_rule_group" "example" { behavior = { action = "ACTION_DENY" log = true - logMsg = "Some custom notification message string" + log_msg = "Some custom notification message string" } conditions = [ { diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource.go b/stackit/internal/services/albwaf/custom_rule_group/resource.go index b868e8bd9..1d8657cd3 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource.go @@ -446,22 +446,21 @@ func (r *customRuleGroupResource) Update(ctx context.Context, req resource.Updat payload, err := toUpdatePayload(ctx, &model) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating export policy", fmt.Sprintf("Creating API payload: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating ALB WAF Custom Rule Group", fmt.Sprintf("Creating API payload: %v", err)) return } updateResp, err := r.client.DefaultAPI.UpdateCustomRuleGroup(ctx, projectId, region, customRuleGroupName).UpdateCustomRuleGroupPayload(*payload).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating export policy", fmt.Sprintf("Calling API to update export policy: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating ALB WAF Custom Rule Group", fmt.Sprintf("Calling API update endpoint: %v", err)) return } ctx = core.LogResponse(ctx) - // map export policy err = mapFields(ctx, updateResp, &model, region) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating export policy", fmt.Sprintf("Processing API payload: %v", err)) + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating ALB WAF Custom Rule Group", fmt.Sprintf("Processing API payload: %v", err)) return }