diff --git a/docs/data-sources/alb_waf_configuration.md b/docs/data-sources/alb_waf_configuration.md
new file mode 100644
index 000000000..200a23525
--- /dev/null
+++ b/docs/data-sources/alb_waf_configuration.md
@@ -0,0 +1,39 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "stackit_alb_waf_configuration Data Source - stackit"
+subcategory: ""
+description: |-
+ albwaf resource schema.
+---
+
+# stackit_alb_waf_configuration (Data Source)
+
+albwaf resource schema.
+
+## Example Usage
+
+```terraform
+data "stackit_alb_waf_configuration" "example" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ name = "example-waf-configuration"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `name` (String) The name of the WAF Configuration.
+- `project_id` (String) STACKIT project ID to which the WAF Configuration is associated.
+
+### Optional
+
+- `region` (String) The resource region (e.g. eu01). If not defined, the provider region is used.
+
+### Read-Only
+
+- `custom_rule_group_name` (String) Name of the custom rule group for this WAF Configuration.
+- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`name`".
+- `labels` (Map of String) User-defined metadata as key-value pairs. Should not exceed 64 entries.
+- `managed_rule_set_name` (String) Name of the managed rule set configuration for this WAF Configuration.
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_configuration.md b/docs/resources/alb_waf_configuration.md
new file mode 100644
index 000000000..520841338
--- /dev/null
+++ b/docs/resources/alb_waf_configuration.md
@@ -0,0 +1,39 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "stackit_alb_waf_configuration Resource - stackit"
+subcategory: ""
+description: |-
+ albwaf resource schema.
+---
+
+# stackit_alb_waf_configuration (Resource)
+
+albwaf resource schema.
+
+## Example Usage
+
+```terraform
+resource "stackit_alb_waf_configuration" "example" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ name = "example-waf-configuration"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `name` (String) The name of the WAF Configuration.
+- `project_id` (String) STACKIT project ID to which the WAF Configuration is associated.
+
+### Optional
+
+- `custom_rule_group_name` (String) Name of the custom rule group for this WAF Configuration.
+- `labels` (Map of String) User-defined metadata as key-value pairs. Should not exceed 64 entries.
+- `managed_rule_set_name` (String) Name of the managed rule set configuration for this WAF Configuration.
+- `region` (String) The resource region (e.g. eu01). If not defined, the provider region is used.
+
+### Read-Only
+
+- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`name`".
diff --git a/docs/resources/alb_waf_managed_rule_set.md b/docs/resources/alb_waf_managed_rule_set.md
index eeb2c93f3..390e30ded 100644
--- a/docs/resources/alb_waf_managed_rule_set.md
+++ b/docs/resources/alb_waf_managed_rule_set.md
@@ -40,7 +40,6 @@ resource "stackit_alb_waf_managed_rule_set" "example" {
- `groups` (Attributes Map) Inventory of all available Managed Rule Set groups and their current configuration. (see [below for nested schema](#nestedatt--groups))
- `id` (String) Terraform's internal resource identifier. Structured as "`project_id`,`region`,`name`".
-- `usage` (Attributes) Managed Rule Set usage (see [below for nested schema](#nestedatt--usage))
- `version` (String) Managed Rule Set version.
@@ -60,13 +59,3 @@ Read-Only:
- `description` (String) A description of what this rule does.
- `mode` (String) The current mode of the rule.
- `severity` (String) Impact level.
-
-
-
-
-### Nested Schema for `usage`
-
-Read-Only:
-
-- `count` (Number) Number of WAFs using this Managed Rule Set.
-- `items` (List of String) List of WAFs that use this Managed Rule Set.
diff --git a/examples/data-sources/stackit_alb_waf_configuration/data-source.tf b/examples/data-sources/stackit_alb_waf_configuration/data-source.tf
new file mode 100644
index 000000000..c044ffa5e
--- /dev/null
+++ b/examples/data-sources/stackit_alb_waf_configuration/data-source.tf
@@ -0,0 +1,5 @@
+data "stackit_alb_waf_configuration" "example" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ name = "example-waf-configuration"
+}
+
diff --git a/examples/resources/stackit_alb_waf_configuration/resource.tf b/examples/resources/stackit_alb_waf_configuration/resource.tf
new file mode 100644
index 000000000..027c57e02
--- /dev/null
+++ b/examples/resources/stackit_alb_waf_configuration/resource.tf
@@ -0,0 +1,5 @@
+resource "stackit_alb_waf_configuration" "example" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ name = "example-waf-configuration"
+}
+
diff --git a/go.mod b/go.mod
index e78461d68..201178ce2 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 be2ed0801..f0b0d8887 100644
--- a/go.sum
+++ b/go.sum
@@ -672,8 +672,8 @@ github.com/stackitcloud/stackit-sdk-go/core v0.26.0 h1:jQEb9gkehfp6VCP6TcYk7BI10
github.com/stackitcloud/stackit-sdk-go/core v0.26.0/go.mod h1:WU1hhxnjXw2EV7CYa1nlEvNpMiRY6CvmIOaHuL3pOaA=
github.com/stackitcloud/stackit-sdk-go/services/alb v0.16.0 h1:WoWlHdzISGXPEaJOYt6HP5F9M5nbyCJL6VqRJZIaOQs=
github.com/stackitcloud/stackit-sdk-go/services/alb v0.16.0/go.mod h1:eK6oRB5Tmpt6KbXQ4UYBGg2LgW5bPtVoncL9E8JSRww=
-github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.10.0 h1:0WsTSSZ0LjNpM3E1d3MgkBXmzMQThVQ7IuXhL2w4EyM=
-github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.10.0/go.mod h1:4M9G1I64kZwlXO32ZoIpt0GAN4SpZ1SYerwCVVIBGoE=
+github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.11.0 h1:ejTZTnGKFUWs9Ch9U30Jd+tpDA/SnHuSF9DpfD6w+To=
+github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.11.0/go.mod h1:4M9G1I64kZwlXO32ZoIpt0GAN4SpZ1SYerwCVVIBGoE=
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.15.2 h1:b7WJ/vwxlVmNNX91kI3obqGcuoPAyaCbDL5aCMQ/sNg=
github.com/stackitcloud/stackit-sdk-go/services/authorization v0.15.2/go.mod h1:T/JF25XGJ3GqER/1L2N//DgY8x5tY7gA3N+/0nvmOWY=
github.com/stackitcloud/stackit-sdk-go/services/cdn v1.19.0 h1:k+KJ4gp9awhJMY5y55vDqRSr6G/S9+8haTNILGbgH9s=
diff --git a/stackit/internal/services/albwaf/albwaf_acc_test.go b/stackit/internal/services/albwaf/albwaf_acc_test.go
index b8e539850..d53fb8979 100644
--- a/stackit/internal/services/albwaf/albwaf_acc_test.go
+++ b/stackit/internal/services/albwaf/albwaf_acc_test.go
@@ -24,6 +24,12 @@ import (
var (
//go:embed testdata/managed-rule-set.tf
managedRuleSetConfig string
+
+ //go:embed testdata/resource-max.tf
+ wafMaxConfig string
+
+ //go:embed testdata/resource-min.tf
+ wafMinConfig string
)
var testManagedRuleSet = config.Variables{
@@ -39,6 +45,35 @@ var testManagedRuleSetUpdated = func() config.Variables {
return updatedConfig
}
+var testConfigVarsMax = config.Variables{
+ "project_id": config.StringVariable(testutil.ProjectId),
+ "waf_configuration_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)),
+ "rule_set_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)),
+ "type": config.StringVariable("TYPE_OWASP_CRS"),
+ "waf_configuration_label": config.StringVariable("some-label"),
+}
+
+var testConfigVarsMaxUpdated = func() config.Variables {
+ updatedConfig := config.Variables{}
+ maps.Copy(updatedConfig, testConfigVarsMax)
+ updatedConfig["waf_configuration_name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["waf_configuration_name"])))
+ updatedConfig["rule_set_name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["rule_set_name"])))
+ updatedConfig["waf_configuration_label"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["waf_configuration_label"])))
+ return updatedConfig
+}
+
+var testConfigVarsMin = config.Variables{
+ "project_id": config.StringVariable(testutil.ProjectId),
+ "waf_configuration_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)),
+}
+
+var testConfigVarsMinUpdated = func() config.Variables {
+ updatedConfig := config.Variables{}
+ maps.Copy(updatedConfig, testConfigVarsMin)
+ updatedConfig["waf_configuration_name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["waf_configuration_name"])))
+ return updatedConfig
+}
+
func TestAccManagedRuleSet(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
@@ -53,8 +88,6 @@ func TestAccManagedRuleSet(t *testing.T) {
resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "region", testutil.Region),
resource.TestCheckResourceAttrSet("stackit_alb_waf_managed_rule_set.managed_rule_set", "id"),
resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "name", testutil.ConvertConfigVariable(testManagedRuleSet["name"])),
-
- resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"),
),
},
// Data source
@@ -79,8 +112,6 @@ func TestAccManagedRuleSet(t *testing.T) {
"stackit_alb_waf_managed_rule_set.managed_rule_set", "id",
),
resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "name", testutil.ConvertConfigVariable(testManagedRuleSet["name"])),
-
- resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"),
),
},
// Import
@@ -115,8 +146,183 @@ 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"])),
+ ),
+ },
+ // Deletion is done by the framework implicitly
+ },
+ })
+}
+
+func TestAccWafConfigurationMin(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckDestroy,
+ Steps: []resource.TestStep{
+ // Creation
+ {
+ ConfigVariables: testConfigVarsMin,
+ Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), wafMinConfig),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("stackit_alb_waf_configuration.waf_instance", "project_id", testutil.ProjectId),
+ resource.TestCheckResourceAttr("stackit_alb_waf_configuration.waf_instance", "region", testutil.Region),
+ resource.TestCheckResourceAttrSet("stackit_alb_waf_configuration.waf_instance", "id"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_configuration.waf_instance", "name", testutil.ConvertConfigVariable(testConfigVarsMin["waf_configuration_name"])),
+ ),
+ },
+ // Data source
+ {
+ ConfigVariables: testConfigVarsMin,
+ Config: fmt.Sprintf(`
+ %s
+ %s
+ data "stackit_alb_waf_configuration" "waf" {
+ project_id = stackit_alb_waf_configuration.waf_instance.project_id
+ name = stackit_alb_waf_configuration.waf_instance.name
+ }
+ `,
+ testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), wafMinConfig,
+ ),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_configuration.waf", "project_id", testutil.ProjectId),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_configuration.waf", "region", testutil.Region),
+ resource.TestCheckResourceAttrPair(
+ "data.stackit_alb_waf_configuration.waf", "id",
+ "stackit_alb_waf_configuration.waf_instance", "id",
+ ),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_configuration.waf", "name", testutil.ConvertConfigVariable(testConfigVarsMin["waf_configuration_name"])),
+ ),
+ },
+ // Import
+ {
+ ConfigVariables: testConfigVarsMin,
+ ResourceName: "stackit_alb_waf_configuration.waf_instance",
+ ImportStateIdFunc: func(s *terraform.State) (string, error) {
+ r, ok := s.RootModule().Resources["stackit_alb_waf_configuration.waf_instance"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find resource stackit_alb_waf_configuration.waf_instance")
+ }
+ name, 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, name), nil
+ },
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ // Update
+ {
+ ConfigVariables: testConfigVarsMinUpdated(),
+ Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), wafMinConfig),
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectResourceAction("stackit_alb_waf_configuration.waf_instance", plancheck.ResourceActionReplace),
+ },
+ },
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("stackit_alb_waf_configuration.waf_instance", "project_id", testutil.ProjectId),
+ resource.TestCheckResourceAttr("stackit_alb_waf_configuration.waf_instance", "region", testutil.Region),
+ resource.TestCheckResourceAttrSet("stackit_alb_waf_configuration.waf_instance", "id"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_configuration.waf_instance", "name", testutil.ConvertConfigVariable(testConfigVarsMinUpdated()["waf_configuration_name"])),
+ ),
+ },
+ // Deletion is done by the framework implicitly
+ },
+ })
+}
+
+func TestAccWafConfigurationMax(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckDestroy,
+ Steps: []resource.TestStep{
+ // Creation
+ {
+ ConfigVariables: testConfigVarsMax,
+ Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), wafMaxConfig),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "name", testutil.ConvertConfigVariable(testConfigVarsMax["rule_set_name"])),
+
+ resource.TestCheckResourceAttr("stackit_alb_waf_configuration.waf_instance", "project_id", testutil.ProjectId),
+ resource.TestCheckResourceAttr("stackit_alb_waf_configuration.waf_instance", "region", testutil.Region),
+ resource.TestCheckResourceAttrSet("stackit_alb_waf_configuration.waf_instance", "id"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_configuration.waf_instance", "name", testutil.ConvertConfigVariable(testConfigVarsMax["waf_configuration_name"])),
+ resource.TestCheckResourceAttrPair("stackit_alb_waf_configuration.waf_instance", "managed_rule_set_name", "stackit_alb_waf_managed_rule_set.managed_rule_set", "name"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_configuration.waf_instance", "labels.label1", testutil.ConvertConfigVariable(testConfigVarsMax["waf_configuration_label"])),
+ ),
+ },
+ // Data source
+ {
+ ConfigVariables: testConfigVarsMax,
+ Config: fmt.Sprintf(`
+ %s
+ %s
+
+ data "stackit_alb_waf_managed_rule_set" "managed_rule_set" {
+ project_id = stackit_alb_waf_managed_rule_set.managed_rule_set.project_id
+ name = stackit_alb_waf_managed_rule_set.managed_rule_set.name
+ }
+
+ data "stackit_alb_waf_configuration" "waf" {
+ project_id = stackit_alb_waf_configuration.waf_instance.project_id
+ name = stackit_alb_waf_configuration.waf_instance.name
+ }
+ `,
+ testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), wafMaxConfig,
+ ),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttrPair(
+ "data.stackit_alb_waf_managed_rule_set.managed_rule_set", "id",
+ "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(testConfigVarsMax["rule_set_name"])),
+
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_configuration.waf", "project_id", testutil.ProjectId),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_configuration.waf", "region", testutil.Region),
+ resource.TestCheckResourceAttrPair(
+ "data.stackit_alb_waf_configuration.waf", "id",
+ "stackit_alb_waf_configuration.waf_instance", "id",
+ ),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_configuration.waf", "name", testutil.ConvertConfigVariable(testConfigVarsMax["waf_configuration_name"])),
+ resource.TestCheckResourceAttrPair("data.stackit_alb_waf_configuration.waf", "managed_rule_set_name", "data.stackit_alb_waf_managed_rule_set.managed_rule_set", "name"),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_configuration.waf", "labels.label1", testutil.ConvertConfigVariable(testConfigVarsMax["waf_configuration_label"])),
+ ),
+ },
+ // Import
+ {
+ ConfigVariables: testConfigVarsMax,
+ ResourceName: "stackit_alb_waf_configuration.waf_instance",
+ ImportStateIdFunc: func(s *terraform.State) (string, error) {
+ r, ok := s.RootModule().Resources["stackit_alb_waf_configuration.waf_instance"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find resource stackit_alb_waf_configuration.waf_instance")
+ }
+ name, 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, name), nil
+ },
+ ImportState: true,
+ ImportStateVerify: true,
+ },
+ // Update
+ {
+ ConfigVariables: testConfigVarsMaxUpdated(),
+ Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), wafMaxConfig),
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectResourceAction("stackit_alb_waf_configuration.waf_instance", plancheck.ResourceActionReplace),
+ },
+ },
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("stackit_alb_waf_configuration.waf_instance", "project_id", testutil.ProjectId),
+ resource.TestCheckResourceAttr("stackit_alb_waf_configuration.waf_instance", "region", testutil.Region),
+ resource.TestCheckResourceAttrSet("stackit_alb_waf_configuration.waf_instance", "id"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_configuration.waf_instance", "name", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["waf_configuration_name"])),
+ resource.TestCheckResourceAttr("stackit_alb_waf_configuration.waf_instance", "labels.label1", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["waf_configuration_label"])),
- resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"),
+ resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "name", testutil.ConvertConfigVariable(testConfigVarsMaxUpdated()["rule_set_name"])),
),
},
// Deletion is done by the framework implicitly
diff --git a/stackit/internal/services/albwaf/managed_rule_set/datasource.go b/stackit/internal/services/albwaf/managed_rule_set/datasource.go
index b802907a4..f53205ae2 100644
--- a/stackit/internal/services/albwaf/managed_rule_set/datasource.go
+++ b/stackit/internal/services/albwaf/managed_rule_set/datasource.go
@@ -11,7 +11,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
- "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
albWaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi"
@@ -100,21 +99,6 @@ func (d *managedRuleSetDataSource) Schema(_ context.Context, _ datasource.Schema
Description: descriptions["version"],
Computed: true,
},
- "usage": schema.SingleNestedAttribute{
- Description: descriptions["usage"],
- Computed: true,
- Attributes: map[string]schema.Attribute{
- "count": schema.Int32Attribute{
- Description: descriptions["usage_count"],
- Computed: true,
- },
- "items": schema.ListAttribute{
- Description: descriptions["usage_items"],
- Computed: true,
- ElementType: types.StringType,
- },
- },
- },
"groups": schema.MapNestedAttribute{
Description: descriptions["groups"],
Computed: true,
diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource.go b/stackit/internal/services/albwaf/managed_rule_set/resource.go
index 0409ec6e8..57cbb6664 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,
@@ -266,6 +237,27 @@ func (r *managedRuleSetResource) ModifyPlan(ctx context.Context, req resource.Mo
return
}
+ // Warn the user if the name is changing, as this triggers a replacement.
+ // Deletion of the old resource will fail if another resource (e.g. stackit_alb_waf_configuration)
+ // still references this managed rule set.
+ if !req.State.Raw.IsNull() {
+ var stateModel Model
+ resp.Diagnostics.Append(req.State.Get(ctx, &stateModel)...)
+ if !resp.Diagnostics.HasError() && !stateModel.Name.IsNull() && !stateModel.Name.IsUnknown() {
+ if !planModel.Name.Equal(stateModel.Name) {
+ resp.Diagnostics.AddWarning(
+ "Managed Rule Set name change requires resource replacement",
+ fmt.Sprintf(
+ "Changing the \"name\" attribute from %q to %q will destroy and recreate this resource. "+
+ "If another resource (e.g. \"stackit_alb_waf_configuration\") references this managed rule set "+
+ "by name, the replacement will fail. Remove or update that dependency before applying this change.",
+ stateModel.Name.ValueString(), planModel.Name.ValueString(),
+ ),
+ )
+ }
+ }
+ }
+
resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
if resp.Diagnostics.HasError() {
return
@@ -432,8 +424,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
@@ -498,23 +490,5 @@ func mapFields(ctx context.Context, managedRuleSet *albWaf.GetManagedRuleSetResp
return fmt.Errorf("mapping groups: %w", core.DiagsToError(diags))
}
- if usage, ok := managedRuleSet.GetUsageOk(); ok {
- usageModel := UsageModel{
- Count: types.Int32PointerValue(usage.Count),
- }
-
- usageModel.Items, diags = types.ListValueFrom(ctx, types.StringType, usage.GetItems())
- if diags.HasError() {
- return fmt.Errorf("creating usage object: %w", core.DiagsToError(diags))
- }
-
- model.Usage, diags = types.ObjectValueFrom(ctx, usageType, usageModel)
- if diags.HasError() {
- return fmt.Errorf("creating usage object: %w", core.DiagsToError(diags))
- }
- } else {
- model.Usage = types.ObjectNull(usageType)
- }
-
return nil
}
diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource_test.go b/stackit/internal/services/albwaf/managed_rule_set/resource_test.go
index 9b7bc9548..0158f48f6 100644
--- a/stackit/internal/services/albwaf/managed_rule_set/resource_test.go
+++ b/stackit/internal/services/albwaf/managed_rule_set/resource_test.go
@@ -36,8 +36,8 @@ func TestToCreatePayload(t *testing.T) {
Type: types.StringValue(string(albWaf.MRSTYPE_TYPE_OWASP_CRS)),
},
expected: &albWaf.CreateManagedRuleSetPayload{
- Name: testName.ValueStringPointer(),
- Type: new(albWaf.MRSTYPE_TYPE_OWASP_CRS),
+ Name: testName.ValueString(),
+ Type: albWaf.MRSTYPE_TYPE_OWASP_CRS,
},
isValid: true,
},
diff --git a/stackit/internal/services/albwaf/testdata/managed-rule-set.tf b/stackit/internal/services/albwaf/testdata/managed-rule-set.tf
index de5736aca..348e7bb4d 100644
--- a/stackit/internal/services/albwaf/testdata/managed-rule-set.tf
+++ b/stackit/internal/services/albwaf/testdata/managed-rule-set.tf
@@ -1,4 +1,3 @@
-
variable "project_id" {}
variable "type" {}
variable "name" {}
diff --git a/stackit/internal/services/albwaf/testdata/resource-max.tf b/stackit/internal/services/albwaf/testdata/resource-max.tf
new file mode 100644
index 000000000..e3ee71ae6
--- /dev/null
+++ b/stackit/internal/services/albwaf/testdata/resource-max.tf
@@ -0,0 +1,20 @@
+variable "project_id" {}
+variable "waf_configuration_name" {}
+variable "rule_set_name" {}
+variable "type" {}
+variable "waf_configuration_label" {}
+
+resource "stackit_alb_waf_managed_rule_set" "managed_rule_set" {
+ project_id = var.project_id
+ type = var.type
+ name = var.rule_set_name
+}
+resource "stackit_alb_waf_configuration" "waf_instance" {
+ project_id = var.project_id
+ name = var.waf_configuration_name
+ managed_rule_set_name = stackit_alb_waf_managed_rule_set.managed_rule_set.name
+ labels = {
+ label1 = var.waf_configuration_label
+ }
+}
+
diff --git a/stackit/internal/services/albwaf/testdata/resource-min.tf b/stackit/internal/services/albwaf/testdata/resource-min.tf
new file mode 100644
index 000000000..4d401ce5c
--- /dev/null
+++ b/stackit/internal/services/albwaf/testdata/resource-min.tf
@@ -0,0 +1,8 @@
+variable "project_id" {}
+variable "waf_configuration_name" {}
+
+resource "stackit_alb_waf_configuration" "waf_instance" {
+ project_id = var.project_id
+ name = var.waf_configuration_name
+}
+
diff --git a/stackit/internal/services/albwaf/waf_configuration/datasource.go b/stackit/internal/services/albwaf/waf_configuration/datasource.go
new file mode 100644
index 000000000..89859f77f
--- /dev/null
+++ b/stackit/internal/services/albwaf/waf_configuration/datasource.go
@@ -0,0 +1,148 @@
+package waf
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+
+ "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"
+ waf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi"
+
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/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"
+ albwafUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albwaf/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
+)
+
+var (
+ _ datasource.DataSource = &wafDatasource{}
+ _ datasource.DataSourceWithConfigure = &wafDatasource{}
+)
+
+type wafDatasource struct {
+ client *waf.APIClient
+ providerData core.ProviderData
+}
+
+func NewWafConfigurationDatasource() datasource.DataSource {
+ return &wafDatasource{}
+}
+
+func (d *wafDatasource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { // nolint:gocritic // function signature required by Terraform
+ var ok bool
+ d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
+ if !ok {
+ return
+ }
+
+ features.CheckBetaResourcesEnabled(ctx, &d.providerData, &resp.Diagnostics, "stackit_alb_waf_configuration", core.Datasource)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ d.client = albwafUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "ALB WAF client configured")
+}
+
+func (d *wafDatasource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: descriptions["main"],
+ 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(),
+ },
+ },
+ "name": schema.StringAttribute{
+ Description: descriptions["name"],
+ Required: true,
+ },
+ "region": schema.StringAttribute{
+ Description: descriptions["region"],
+ Optional: true,
+ // must be computed to allow for storing the override value from the provider
+ Computed: true,
+ },
+ "labels": schema.MapAttribute{
+ Description: descriptions["labels"],
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "managed_rule_set_name": schema.StringAttribute{
+ Description: descriptions["managed_rule_set_name"],
+ Computed: true,
+ },
+ "custom_rule_group_name": schema.StringAttribute{
+ Description: descriptions["custom_rule_group_name"],
+ Computed: true,
+ },
+ },
+ }
+}
+
+func (d *wafDatasource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_alb_waf_configuration"
+}
+
+func (d *wafDatasource) 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 := d.providerData.GetRegionWithOverride(model.Region)
+ ctx = tflog.SetField(ctx, "project_id", projectId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "name", name)
+
+ foundWAF, err := d.client.DefaultAPI.GetWAF(ctx, projectId, region, name).Execute()
+ if err != nil {
+ var oapiErr *oapierror.GenericOpenAPIError
+ if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
+ utils.LogError(ctx, &resp.Diagnostics, err, "Error reading ALB WAF Configuration:", fmt.Sprintf("Calling API: %v", err), map[int]string{http.StatusNotFound: fmt.Sprintf("ALB WAF Configuration %s not found", name)})
+ resp.State.RemoveResource(ctx)
+ return
+ }
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Configuration", err.Error())
+ return
+ }
+
+ ctx = core.LogResponse(ctx)
+
+ err = mapFields(ctx, foundWAF, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Configuration", 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 Configuration read")
+}
diff --git a/stackit/internal/services/albwaf/waf_configuration/resource.go b/stackit/internal/services/albwaf/waf_configuration/resource.go
new file mode 100644
index 000000000..e6dd622c2
--- /dev/null
+++ b/stackit/internal/services/albwaf/waf_configuration/resource.go
@@ -0,0 +1,486 @@
+package waf
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+ "regexp"
+ "strings"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "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-log/tflog"
+ "github.com/stackitcloud/stackit-sdk-go/core/oapierror"
+ waf "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"
+ albwafUtils "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"
+)
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ resource.Resource = &wafResource{}
+ _ resource.ResourceWithConfigure = &wafResource{}
+ _ resource.ResourceWithImportState = &wafResource{}
+ _ resource.ResourceWithModifyPlan = &wafResource{}
+)
+
+type Model struct {
+ Id types.String `tfsdk:"id"`
+ ProjectId types.String `tfsdk:"project_id"`
+ Region types.String `tfsdk:"region"`
+ Name types.String `tfsdk:"name"`
+ Labels types.Map `tfsdk:"labels"`
+ ManagedRuleSetName types.String `tfsdk:"managed_rule_set_name"`
+ CustomRuleGroupName types.String `tfsdk:"custom_rule_group_name"`
+}
+
+func NewWafConfigurationResource() resource.Resource {
+ return &wafResource{}
+}
+
+type ItemsModel struct {
+ ListenerNames types.Int32 `tfsdk:"listener_names"`
+ LoadBalancerName types.String `tfsdk:"load_balancer_name"`
+}
+
+type wafResource struct {
+ client *waf.APIClient
+ providerData core.ProviderData
+}
+
+func (r *wafResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_alb_waf_configuration"
+}
+
+// Use the modifier to set the effective region in the current plan.
+func (r *wafResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
+ var configModel Model
+ // skip initial empty configuration to avoid follow-up errors
+ 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 *wafResource) 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_configuration", core.Resource)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ apiClient := albwafUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ r.client = apiClient
+ tflog.Info(ctx, "albwaf client configured")
+}
+
+var descriptions = map[string]string{
+ "main": "albwaf resource schema.",
+ "id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`name`\".",
+ "project_id": "STACKIT project ID to which the WAF Configuration is associated.",
+ "region": "The resource region (e.g. eu01). If not defined, the provider region is used.",
+ "name": "The name of the WAF Configuration.",
+ "labels": "User-defined metadata as key-value pairs. Should not exceed 64 entries.",
+ "managed_rule_set_name": "Name of the managed rule set configuration for this WAF Configuration.",
+ "custom_rule_group_name": "Name of the custom rule group for this WAF Configuration.",
+ "count": "Number of listeners using this WAF Configuration.",
+ "items": "List of Application Load Balancers with their associated listeners that use this WAF Configuration.",
+ "listener_names": "List of listener names in this Application Load Balancer using this WAF Configuration.",
+ "load_balancer_name": "The display name of the Application Load Balancer.",
+}
+
+func (r *wafResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: descriptions["main"],
+ 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,
+ Validators: []validator.String{
+ validate.UUID(),
+ },
+ 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",
+ ),
+ },
+ },
+ "region": schema.StringAttribute{
+ Description: descriptions["region"],
+ Optional: true,
+ // must be computed to allow for storing the override value from the provider
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "labels": schema.MapAttribute{
+ Description: descriptions["labels"],
+ Optional: true,
+ ElementType: types.StringType,
+ Validators: []validator.Map{
+ mapvalidator.SizeAtMost(64),
+ },
+ },
+ "managed_rule_set_name": schema.StringAttribute{
+ Description: descriptions["managed_rule_set_name"],
+ Optional: 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",
+ ),
+ },
+ },
+ "custom_rule_group_name": schema.StringAttribute{
+ Description: descriptions["custom_rule_group_name"],
+ Optional: 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",
+ ),
+ },
+ },
+ },
+ }
+}
+
+func (r *wafResource) 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 Configuration", fmt.Sprint("Creating API payload: %w", err))
+ return
+ }
+ createResp, err := r.client.DefaultAPI.CreateWAF(ctx, projectId, region).CreateWAFPayload(*payload).Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Configuration", fmt.Sprint("Calling API: %w", err))
+ return
+ }
+ ctx = core.LogResponse(ctx)
+
+ ctx = tfutils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
+ "project_id": projectId,
+ "region": region,
+ "name": createResp.Name,
+ })
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ err = mapFields(ctx, createResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Configuration", 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 Configuration created")
+}
+
+func (r *wafResource) 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.DeleteWAF(ctx, projectId, region, name).Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting ALB WAF Configuration", fmt.Sprintf("Calling API: %v", err))
+ return
+ }
+
+ ctx = core.LogResponse(ctx)
+ tflog.Info(ctx, "ALB WAF Configuration deleted")
+}
+
+func (r *wafResource) 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)
+ if name == "" {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Configuration", "Name must be defined when reading ALB WAF Configuration")
+ return
+ }
+ ctx = tflog.SetField(ctx, "project_id", projectId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "name", name)
+
+ response, err := r.client.DefaultAPI.GetWAF(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 Configuration", err.Error())
+ return
+ }
+
+ ctx = core.LogResponse(ctx)
+
+ err = mapFields(ctx, response, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Configuration", 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 Configuration read")
+}
+
+func (r *wafResource) 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()
+ region := r.providerData.GetRegionWithOverride(model.Region)
+ name := model.Name.ValueString()
+
+ if name == "" {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating ALB WAF Configuration", "Name must be defined when updating ALB WAF Configuration")
+ return
+ }
+
+ ctx = tflog.SetField(ctx, "project_id", projectId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "name", name)
+
+ payload, err := toUpdatePayload(ctx, &model)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating ALB WAF Configuration", fmt.Sprint("Creating API payload: %w", err))
+ return
+ }
+ updateResp, err := r.client.DefaultAPI.UpdateWAF(ctx, projectId, region, name).UpdateWAFPayload(*payload).Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating ALB WAF Configuration", fmt.Sprint("Calling API: %w", err))
+ return
+ }
+ ctx = core.LogResponse(ctx)
+
+ ctx = tfutils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
+ "project_id": projectId,
+ "region": region,
+ "name": updateResp.Name,
+ })
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ err = mapFields(ctx, updateResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating ALB WAF Configuration", 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 Configuration created")
+}
+
+func (r *wafResource) 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 Configuration",
+ 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 Configuration state imported")
+}
+
+func toUpdatePayload(ctx context.Context, model *Model) (*waf.UpdateWAFPayload, error) {
+ if model == nil {
+ return nil, fmt.Errorf("nil model")
+ }
+
+ var labels *map[string]string
+ if !(model.Labels.IsNull() || model.Labels.IsUnknown()) {
+ diags := model.Labels.ElementsAs(ctx, &labels, false)
+ if diags.HasError() {
+ return nil, core.DiagsToError(diags)
+ }
+ }
+ return &waf.UpdateWAFPayload{
+ CustomRuleGroupName: model.CustomRuleGroupName.ValueStringPointer(),
+ ManagedRuleSetName: model.ManagedRuleSetName.ValueStringPointer(),
+ Labels: labels,
+ }, nil
+}
+
+func toCreatePayload(ctx context.Context, model *Model) (*waf.CreateWAFPayload, error) {
+ if model == nil {
+ return nil, fmt.Errorf("nil model")
+ }
+
+ var labels *map[string]string
+ if !(model.Labels.IsNull() || model.Labels.IsUnknown()) {
+ diags := model.Labels.ElementsAs(ctx, &labels, false)
+ if diags.HasError() {
+ return nil, core.DiagsToError(diags)
+ }
+ }
+ payload := &waf.CreateWAFPayload{
+ Name: model.Name.ValueString(),
+ CustomRuleGroupName: model.CustomRuleGroupName.ValueStringPointer(),
+ Labels: labels,
+ ManagedRuleSetName: model.ManagedRuleSetName.ValueStringPointer(),
+ }
+ return payload, nil
+}
+
+func mapFields(ctx context.Context, wafResponse *waf.GetWAFResponse, model *Model, region string) error {
+ if wafResponse == nil {
+ return fmt.Errorf("response input is nil")
+ }
+ if model == nil {
+ return fmt.Errorf("model input is nil")
+ }
+
+ labels, err := tfutils.MapLabels(ctx, wafResponse.Labels, model.Labels)
+ if err != nil {
+ return err
+ }
+
+ var name types.String
+ if wafResponse.Name != nil {
+ name = types.StringValue(*wafResponse.Name)
+ } else {
+ name = types.StringNull()
+ }
+
+ var customRuleGroupName types.String
+ if wafResponse.CustomRuleGroupName != nil {
+ customRuleGroupName = types.StringValue(*wafResponse.CustomRuleGroupName)
+ } else {
+ customRuleGroupName = types.StringNull()
+ }
+
+ var managedRuleSetName types.String
+ if wafResponse.ManagedRuleSetName != nil {
+ managedRuleSetName = types.StringValue(*wafResponse.ManagedRuleSetName)
+ } else {
+ managedRuleSetName = types.StringNull()
+ }
+
+ model.Id = tfutils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, model.Name.ValueString())
+ model.Name = name
+ model.Region = types.StringValue(region)
+ model.CustomRuleGroupName = customRuleGroupName
+ model.Labels = labels
+ model.ManagedRuleSetName = managedRuleSetName
+ return nil
+}
diff --git a/stackit/internal/services/albwaf/waf_configuration/resource_test.go b/stackit/internal/services/albwaf/waf_configuration/resource_test.go
new file mode 100644
index 000000000..613edf543
--- /dev/null
+++ b/stackit/internal/services/albwaf/waf_configuration/resource_test.go
@@ -0,0 +1,154 @@
+package waf
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ waf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi"
+)
+
+const (
+ testRegion = "eu01"
+)
+
+func Test_mapFieds(t *testing.T) {
+ fixtureModel := func(mods ...func(*Model)) *Model {
+ m := Model{
+ Id: types.StringValue(fmt.Sprintf("pid,%s,name", testRegion)),
+ ProjectId: types.StringValue("pid"),
+ Region: types.StringValue(testRegion),
+ Name: types.StringValue("name"),
+ Labels: types.MapNull(types.StringType),
+ ManagedRuleSetName: types.StringNull(),
+ CustomRuleGroupName: types.StringNull(),
+ }
+
+ for _, mod := range mods {
+ mod(&m)
+ }
+ return &m
+ }
+ tests := []struct {
+ name string
+ input *waf.GetWAFResponse
+ state *Model
+ region string
+ want *Model
+ wantErr bool
+ }{
+ {
+ name: "default_values",
+ input: &waf.GetWAFResponse{
+ Name: new("name"),
+ },
+ state: fixtureModel(),
+ region: testRegion,
+ want: fixtureModel(),
+ wantErr: false,
+ },
+ {
+ name: "simple values",
+ input: &waf.GetWAFResponse{
+ Name: new("name"),
+ Labels: &map[string]string{"label1": "value1"},
+ ManagedRuleSetName: new("managed_rule_set"),
+ CustomRuleGroupName: new("custom_rule_group"),
+ },
+ state: fixtureModel(),
+ region: testRegion,
+ want: fixtureModel(
+ func(m *Model) {
+ m.Labels = types.MapValueMust(types.StringType, map[string]attr.Value{
+ "label1": types.StringValue("value1"),
+ })
+ m.ManagedRuleSetName = types.StringValue("managed_rule_set")
+ m.CustomRuleGroupName = types.StringValue("custom_rule_group")
+ },
+ ),
+ wantErr: false,
+ },
+ {
+ name: "fails when model is nil",
+ state: nil,
+ wantErr: true,
+ },
+ {
+ name: "fails when input is nil",
+ input: nil,
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ gotErr := mapFields(t.Context(), tt.input, tt.state, tt.region)
+
+ if gotErr != nil {
+ if !tt.wantErr {
+ t.Errorf("mapFieds() failed: %v", gotErr)
+ }
+ return
+ }
+ if tt.wantErr {
+ t.Fatal("mapFieds() succeeded unexpectedly")
+ }
+ if !tt.wantErr {
+ diff := cmp.Diff(tt.state, tt.want)
+ if diff != "" {
+ t.Fatalf("Data does not match: %s", diff)
+ }
+ }
+ })
+ }
+}
+
+func Test_toCreatePayload(t *testing.T) {
+ tests := []struct {
+ name string
+ model *Model
+ want *waf.CreateWAFPayload
+ wantErr bool
+ }{
+ {
+ name: "basic values",
+ model: &Model{
+ ManagedRuleSetName: types.StringValue("example"),
+ CustomRuleGroupName: types.StringValue("example group name"),
+ Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
+ "label1": types.StringValue("value1"),
+ }),
+ },
+ want: &waf.CreateWAFPayload{
+ ManagedRuleSetName: new("example"),
+ CustomRuleGroupName: new("example group name"),
+ Labels: &map[string]string{"label1": "value1"},
+ },
+ wantErr: false,
+ },
+ {
+ name: "fails when model is nil",
+ model: nil,
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, gotErr := toCreatePayload(t.Context(), tt.model)
+ if gotErr != nil {
+ if !tt.wantErr {
+ t.Errorf("toCreatePayload() failed: %v", gotErr)
+ }
+ return
+ }
+ if tt.wantErr {
+ t.Fatal("toCreatePayload() succeeded unexpectedly")
+ }
+ diff := cmp.Diff(got, tt.want)
+ if diff != "" {
+ t.Errorf("Data does not match: %s", diff)
+ }
+ })
+ }
+}
diff --git a/stackit/provider.go b/stackit/provider.go
index 3a88d9dc4..1e0402cb4 100644
--- a/stackit/provider.go
+++ b/stackit/provider.go
@@ -25,6 +25,7 @@ import (
alb "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/alb/applicationloadbalancer"
cert "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albcertificates/certificate"
albWafManagedRuleSet "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albwaf/managed_rule_set"
+ albWaf "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albwaf/waf_configuration"
customRole "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/authorization/customrole"
roleAssignements "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/authorization/roleassignments"
cdnCustomDomain "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/cdn/customdomain"
@@ -681,6 +682,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,
+ albWaf.NewWafConfigurationDatasource,
albWafManagedRuleSet.NewManagedRuleSetDataSource,
alertGroup.NewAlertGroupDataSource,
cdn.NewDistributionDataSource,
@@ -795,6 +797,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,
+ albWaf.NewWafConfigurationResource,
albWafManagedRuleSet.NewManagedRuleSetResource,
alertGroup.NewAlertGroupResource,
cdn.NewDistributionResource,