From 0399eda99256348501905d37c1c19f3d61e9e119 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Thu, 16 Jul 2026 17:40:23 +0200 Subject: [PATCH 01/11] feat(albwaf): onboard managed rule set --- go.mod | 1 + go.sum | 2 + stackit/internal/core/core.go | 1 + .../albwaf/managed_rule_set/resource.go | 502 ++++++++++++++++++ .../albwaf/managed_rule_set/resource_test.go | 111 ++++ .../albwaf/testdata/managed-rule-set.tf | 10 + .../internal/services/albwaf/utils/util.go | 31 ++ .../services/albwaf/utils/util_test.go | 94 ++++ stackit/internal/testutil/testutil.go | 2 + stackit/provider.go | 9 + 10 files changed, 763 insertions(+) create mode 100644 stackit/internal/services/albwaf/managed_rule_set/resource.go create mode 100644 stackit/internal/services/albwaf/managed_rule_set/resource_test.go create mode 100644 stackit/internal/services/albwaf/testdata/managed-rule-set.tf create mode 100644 stackit/internal/services/albwaf/utils/util.go create mode 100644 stackit/internal/services/albwaf/utils/util_test.go diff --git a/go.mod b/go.mod index 879120105..db197c965 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +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.9.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 ad771eb9c..40ca7084b 100644 --- a/go.sum +++ b/go.sum @@ -672,6 +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.9.0 h1:R3ovD+OhhjhhiVYUkhK/Szo4pQVU0KKBJeCo3JB/aH0= +github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.9.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/core/core.go b/stackit/internal/core/core.go index d71a0cfed..a567a9d56 100644 --- a/stackit/internal/core/core.go +++ b/stackit/internal/core/core.go @@ -42,6 +42,7 @@ type ProviderData struct { DefaultRegion string ALBCertificatesCustomEndpoint string ALBCustomEndpoint string + ALBWAFCustomEndpoint string AuthorizationCustomEndpoint string CdnCustomEndpoint string DnsCustomEndpoint string diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource.go b/stackit/internal/services/albwaf/managed_rule_set/resource.go new file mode 100644 index 000000000..5ad82cd2a --- /dev/null +++ b/stackit/internal/services/albwaf/managed_rule_set/resource.go @@ -0,0 +1,502 @@ +package managed_rule_set + +import ( + "context" + "errors" + "fmt" + "net/http" + "regexp" + "strings" + + "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/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" + 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/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 = &managedRuleSetResource{} + _ resource.ResourceWithConfigure = &managedRuleSetResource{} + _ resource.ResourceWithImportState = &managedRuleSetResource{} + _ resource.ResourceWithModifyPlan = &managedRuleSetResource{} + + mrsTypeOptions = sdkUtils.EnumSliceToStringSlice(albwaf.AllowedMRSTypeEnumValues) +) + +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"` + Groups types.Map `tfsdk:"groups"` + Type types.String `tfsdk:"type"` + Usage types.Object `tfsdk:"usage"` + Version types.String `tfsdk:"version"` +} + +type RuleGroupModel struct { + Description types.String `tfsdk:"description"` + GroupName types.String `tfsdk:"group_name"` + Rules types.Map `tfsdk:"rules"` +} + +var ruleGroupType = map[string]attr.Type{ + "description": types.StringType, + "group_name": types.StringType, + "rules": types.MapType{ + ElemType: types.ObjectType{AttrTypes: ruleType}, + }, +} + +type RuleModel struct { + Description types.String `tfsdk:"description"` + Mode types.String `tfsdk:"mode"` + Severity types.String `tfsdk:"severity"` +} + +var ruleType = map[string]attr.Type{ + "description": types.StringType, + "mode": types.StringType, + "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 +} + +func NewManagedRuleSetResource() resource.Resource { + return &managedRuleSetResource{} +} + +func (r *managedRuleSetResource) 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 + } + + 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 *managedRuleSetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_albwaf_managed_rule_set" +} + +func (r *managedRuleSetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: fmt.Sprintf("ALB WAF Managed Rule Set resource schema. %s", core.ResourceRegionFallbackDocstring), + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Terraform's internal resource identifier. Structured as \"`project_id`,`region`,`name`\".", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "project_id": schema.StringAttribute{ + Description: "STACKIT project ID associated with the ALB WAF Managed Rule Set.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Description: "STACKIT region name the resource is located in. If not defined, the provider region is used.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "name": schema.StringAttribute{ + Description: "Managed Rule Set configuration 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", + ), + }, + }, + "type": schema.StringAttribute{ + Description: "Set the Managed Rule Set type.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.OneOf(mrsTypeOptions...), + }, + }, + "version": schema.StringAttribute{ + Description: "Managed Rule Set version.", + Computed: true, + }, + "usage": schema.SingleNestedAttribute{ + Description: "Managed Rule Set usage", + Computed: true, + Attributes: map[string]schema.Attribute{ + "count": schema.Int32Attribute{ + Description: "Number of WAFs using this Managed Rule Set.", + Computed: true, + }, + "items": schema.ListAttribute{ + Description: "List of WAFs that use this Managed Rule Set.", + Computed: true, + ElementType: types.StringType, + }, + }, + }, + "groups": schema.MapNestedAttribute{ + Description: "Inventory of all available Managed Rule Set groups and their current configuration.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + Description: "A description of what this group covers.", + Computed: true, + }, + "group_name": schema.StringAttribute{ + Description: "The name for the rule group.", + Computed: true, + }, + "rules": schema.MapNestedAttribute{ + Description: "Rules of the rule group.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + Description: "A description of what this rule does.", + Computed: true, + }, + "mode": schema.StringAttribute{ + Description: "The current mode of the rule.", + Computed: true, + }, + "severity": schema.StringAttribute{ + Description: "Impact level.", + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func (r *managedRuleSetResource) 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 *managedRuleSetResource) 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 Managed Rule Set", + 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 Managed Rule Set state imported") +} + +func (r *managedRuleSetResource) 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) + + payload, err := toCreatePayload(ctx, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Managed Rule Set", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + createResp, err := r.client.DefaultAPI.CreateManagedRuleSet(ctx, projectId, region).CreateManagedRuleSetPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Managed Rule Set", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + if createResp.Name == nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Managed Rule Set", "Got empty Managed Rule Set name") + return + } + managedRuleSetName := *createResp.Name + + ctx = tfutils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": projectId, + "region": region, + "name": managedRuleSetName, + }) + if resp.Diagnostics.HasError() { + return + } + + err = mapFields(ctx, createResp, &model, region) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Managed Rule Set", 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 Managed Rule Set created") +} + +func (r *managedRuleSetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform + core.LogAndAddError(ctx, &resp.Diagnostics, "Ressource not updatable", "alb Managed Rule Set is not updatable") +} + +func (r *managedRuleSetResource) 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, "name", name) + ctx = tflog.SetField(ctx, "region", region) + + managedRuleSetResp, err := r.client.DefaultAPI.GetManagedRuleSet(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 Managed Rule Set", err.Error()) + return + } + + ctx = core.LogResponse(ctx) + + err = mapFields(ctx, managedRuleSetResp, &model, region) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Managed Rule Set", 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 Managed Rule Set read") +} + +func (r *managedRuleSetResource) 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, "name", name) + ctx = tflog.SetField(ctx, "region", region) + + _, err := r.client.DefaultAPI.DeleteManagedRuleSet(ctx, projectId, region, name).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting ALB WAF Managed Rule Set", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + tflog.Info(ctx, "ALB WAF Managed Rule Set deleted") +} + +func toCreatePayload(ctx context.Context, model *Model) (*albwaf.CreateManagedRuleSetPayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + + payload := &albwaf.CreateManagedRuleSetPayload{ + Name: model.Name.ValueStringPointer(), + Type: new(albwaf.MRSType(model.Type.ValueString())), + } + + return payload, nil +} + +func mapFields(ctx context.Context, managedRuleSet *albwaf.GetManagedRuleSetResponse, model *Model, region string) error { + if managedRuleSet == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + var diags diag.Diagnostics + + model.Id = tfutils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, model.Name.ValueString()) + model.Name = types.StringValue(model.Name.ValueString()) + model.Region = types.StringValue(region) + + model.Type = types.StringPointerValue((*string)(managedRuleSet.Type)) + model.Version = types.StringPointerValue(managedRuleSet.Version) + + groupsMap := map[string]attr.Value{} + if groups, ok := managedRuleSet.GetGroupsOk(); ok { + for groupKey, group := range *groups { + + groupTF := RuleGroupModel{ + Description: types.StringPointerValue(group.Description), + GroupName: types.StringPointerValue(group.GroupName), + } + + ruleMap := map[string]attr.Value{} + if rules, ok := group.GetRulesOk(); ok { + for ruleKey, rule := range *rules { + + ruleTF := RuleModel{ + Description: types.StringPointerValue(rule.Description), + Mode: types.StringPointerValue((*string)(rule.Mode)), + Severity: types.StringPointerValue(rule.Severity), + } + + ruleMap[ruleKey], diags = types.ObjectValueFrom(ctx, ruleType, ruleTF) + if diags.HasError() { + return fmt.Errorf("mapping role: %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)) + } + + groupsMap[groupKey], diags = types.ObjectValueFrom(ctx, ruleGroupType, groupTF) + if diags.HasError() { + return fmt.Errorf("mapping group: %w", core.DiagsToError(diags)) + } + } + } + model.Groups, diags = types.MapValue( + types.ObjectType{AttrTypes: ruleGroupType}, + groupsMap, + ) + if diags.HasError() { + 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 new file mode 100644 index 000000000..6b0af4e70 --- /dev/null +++ b/stackit/internal/services/albwaf/managed_rule_set/resource_test.go @@ -0,0 +1,111 @@ +package managed_rule_set + +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-managed-rule-set") + testId = types.StringValue(testProjectId.ValueString() + "," + testRegion.ValueString() + "," + testName.ValueString()) +) + +func TestToCreatePayload(t *testing.T) { + tests := []struct { + name string + model *Model + expected *albwaf.CreateManagedRuleSetPayload + isValid bool + }{ + { + name: "default", + model: &Model{ + Name: testName, + Id: testId, + ProjectId: testProjectId, + Region: testRegion, + Type: types.StringValue(string(albwaf.MRSTYPE_TYPE_OWASP_CRS)), + }, + expected: &albwaf.CreateManagedRuleSetPayload{ + Name: testName.ValueStringPointer(), + Type: new(albwaf.MRSTYPE_TYPE_OWASP_CRS), + }, + 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.GetManagedRuleSetResponse + expected *Model + isValid bool + }{ + { + name: "default", + state: &Model{ + ProjectId: testProjectId, + Region: testRegion, + Name: testName, + Type: types.StringValue(string(albwaf.MRSTYPE_TYPE_OWASP_CRS)), + Id: testId, + Groups: types.MapValueMust(types.ObjectType{AttrTypes: ruleGroupType}, map[string]attr.Value{}), + }, + region: testRegion.ValueString(), + input: &albwaf.GetManagedRuleSetResponse{ + Groups: &map[string]albwaf.MRSRuleGroup{}, + Name: testName.ValueStringPointer(), + Type: new(albwaf.MRSTYPE2_TYPE_OWASP_CRS), + }, + expected: &Model{ + ProjectId: testProjectId, + Region: testRegion, + Name: testName, + Type: types.StringValue(string(albwaf.MRSTYPE_TYPE_OWASP_CRS)), + Id: testId, + Groups: types.MapValueMust(types.ObjectType{AttrTypes: ruleGroupType}, map[string]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/testdata/managed-rule-set.tf b/stackit/internal/services/albwaf/testdata/managed-rule-set.tf new file mode 100644 index 000000000..e279249b8 --- /dev/null +++ b/stackit/internal/services/albwaf/testdata/managed-rule-set.tf @@ -0,0 +1,10 @@ + +variable "project_id" {} +variable "type" {} +variable "name" {} + +resource "stackit_albwaf_managed_rule_set" "managed_rule_set" { + project_id = var.project_id + type = var.type + name = var.name +} diff --git a/stackit/internal/services/albwaf/utils/util.go b/stackit/internal/services/albwaf/utils/util.go new file mode 100644 index 000000000..d7808ee51 --- /dev/null +++ b/stackit/internal/services/albwaf/utils/util.go @@ -0,0 +1,31 @@ +package utils + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/stackitcloud/stackit-sdk-go/core/config" + 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/utils" +) + +func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *albwaf.APIClient { + apiClientConfigOptions := []config.ConfigurationOption{ + config.WithCustomAuth(providerData.RoundTripper), + utils.UserAgentConfigOption(providerData.Version), + } + if providerData.ALBWAFCustomEndpoint != "" { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.ALBWAFCustomEndpoint)) + } + + apiClient, err := albwaf.NewAPIClient(apiClientConfigOptions...) + if err != nil { + core.LogAndAddError(ctx, diags, "Error configuring API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err)) + return nil + } + + return apiClient +} diff --git a/stackit/internal/services/albwaf/utils/util_test.go b/stackit/internal/services/albwaf/utils/util_test.go new file mode 100644 index 000000000..ceb7cb2d6 --- /dev/null +++ b/stackit/internal/services/albwaf/utils/util_test.go @@ -0,0 +1,94 @@ +package utils + +import ( + "context" + "os" + "reflect" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/diag" + sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients" + "github.com/stackitcloud/stackit-sdk-go/core/config" + 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/utils" +) + +const ( + testVersion = "1.2.3" + testCustomEndpoint = "https://alb-waf-custom-endpoint.api.stackit.cloud" +) + +func TestConfigureClient(t *testing.T) { + /* mock authentication by setting service account token env variable */ + os.Clearenv() + err := os.Setenv(sdkClients.ServiceAccountToken, "mock-val") + if err != nil { + t.Errorf("error setting env variable: %v", err) + } + + type args struct { + providerData *core.ProviderData + } + tests := []struct { + name string + args args + wantErr bool + expected *albwaf.APIClient + }{ + { + name: "default endpoint", + args: args{ + providerData: &core.ProviderData{ + Version: testVersion, + }, + }, + expected: func() *albwaf.APIClient { + apiClient, err := albwaf.NewAPIClient( + utils.UserAgentConfigOption(testVersion), + ) + if err != nil { + t.Errorf("error configuring client: %v", err) + } + return apiClient + }(), + wantErr: false, + }, + { + name: "custom endpoint", + args: args{ + providerData: &core.ProviderData{ + Version: testVersion, + ALBWAFCustomEndpoint: testCustomEndpoint, + }, + }, + expected: func() *albwaf.APIClient { + apiClient, err := albwaf.NewAPIClient( + utils.UserAgentConfigOption(testVersion), + config.WithEndpoint(testCustomEndpoint), + ) + if err != nil { + t.Errorf("error configuring client: %v", err) + } + return apiClient + }(), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + diags := diag.Diagnostics{} + + actual := ConfigureClient(ctx, tt.args.providerData, &diags) + if diags.HasError() != tt.wantErr { + t.Errorf("ConfigureClient() error = %v, want %v", diags.HasError(), tt.wantErr) + } + + if !reflect.DeepEqual(actual, tt.expected) { + t.Errorf("ConfigureClient() = %v, want %v", actual, tt.expected) + } + }) + } +} diff --git a/stackit/internal/testutil/testutil.go b/stackit/internal/testutil/testutil.go index 9693516a7..2d7ce4083 100644 --- a/stackit/internal/testutil/testutil.go +++ b/stackit/internal/testutil/testutil.go @@ -69,6 +69,7 @@ var ( ALBCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_CUSTOM_ENDPOINT", providerName: "alb_custom_endpoint"} ALBCertCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_CERT_CUSTOM_ENDPOINT", providerName: "alb_certificates_custom_endpoint"} + ALBWAFCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_WAF_CUSTOM_ENDPOINT", providerName: "alb_waf_custom_endpoint"} CdnCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_CDN_CUSTOM_ENDPOINT", providerName: "cdn_custom_endpoint"} DnsCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_DNS_CUSTOM_ENDPOINT", providerName: "dns_custom_endpoint"} DremioCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_DREMIO_CUSTOM_ENDPOINT", providerName: "dremio_custom_endpoint"} @@ -107,6 +108,7 @@ var ( allCustomEndpoints = []customEndpointConfig{ ALBCustomEndpoint, ALBCertCustomEndpoint, + ALBWAFCustomEndpoint, CdnCustomEndpoint, DnsCustomEndpoint, EdgeCloudCustomEndpoint, diff --git a/stackit/provider.go b/stackit/provider.go index 99d425d73..145759f73 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" + 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" cdnCustomDomain "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/cdn/customdomain" @@ -186,6 +187,7 @@ type providerModel struct { AuthorizationCustomEndpoint types.String `tfsdk:"authorization_custom_endpoint"` CdnCustomEndpoint types.String `tfsdk:"cdn_custom_endpoint"` ALBCertificatesCustomEndpoint types.String `tfsdk:"alb_certificates_custom_endpoint"` + ALBWAFCustomEndpoint types.String `tfsdk:"alb_waf_custom_endpoint"` DnsCustomEndpoint types.String `tfsdk:"dns_custom_endpoint"` DremioCustomEndpoint types.String `tfsdk:"dremio_custom_endpoint"` EdgeCloudCustomEndpoint types.String `tfsdk:"edgecloud_custom_endpoint"` @@ -244,6 +246,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro "region": "Region will be used as the default location for regional services. Not all services require a region, some are global", "default_region": "Region will be used as the default location for regional services. Not all services require a region, some are global", "alb_certificates_custom_endpoint": "Custom endpoint for the Application Load Balancer TLS Certificate service", + "alb_waf_custom_endpoint": "Custom endpoint for the Application Load Balancer Web Application Firewall service", "alb_custom_endpoint": "Custom endpoint for the Application Load Balancer service", "cdn_custom_endpoint": "Custom endpoint for the CDN service", "dns_custom_endpoint": "Custom endpoint for the DNS service", @@ -374,6 +377,10 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro Optional: true, Description: descriptions["alb_certificates_custom_endpoint"], }, + "alb_waf_custom_endpoint": schema.StringAttribute{ + Optional: true, + Description: descriptions["alb_waf_custom_endpoint"], + }, "dns_custom_endpoint": schema.StringAttribute{ Optional: true, Description: descriptions["dns_custom_endpoint"], @@ -563,6 +570,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, setStringField(providerConfig.ALBCertificatesCustomEndpoint, func(v string) { providerData.ALBCertificatesCustomEndpoint = v }) setStringField(providerConfig.ALBCustomEndpoint, func(v string) { providerData.ALBCustomEndpoint = v }) + setStringField(providerConfig.ALBWAFCustomEndpoint, func(v string) { providerData.ALBWAFCustomEndpoint = v }) setStringField(providerConfig.AuthorizationCustomEndpoint, func(v string) { providerData.AuthorizationCustomEndpoint = v }) setStringField(providerConfig.CdnCustomEndpoint, func(v string) { providerData.CdnCustomEndpoint = v }) setStringField(providerConfig.DnsCustomEndpoint, func(v string) { providerData.DnsCustomEndpoint = v }) @@ -784,6 +792,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, + albWafManagedRuleSet.NewManagedRuleSetResource, alertGroup.NewAlertGroupResource, cdn.NewDistributionResource, cert.NewCertificatesResource, From fae6ee0f39959fb208d9fbdac0fc00542236c758 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Fri, 17 Jul 2026 10:25:17 +0200 Subject: [PATCH 02/11] add datasource and acc tests --- stackit/internal/core/core.go | 2 +- .../services/albwaf/albwaf_acc_test.go | 184 +++++++++++++++++ .../albwaf/managed_rule_set/datasource.go | 186 ++++++++++++++++++ .../albwaf/managed_rule_set/resource.go | 2 +- .../albwaf/testdata/managed-rule-set.tf | 2 +- .../internal/services/albwaf/utils/util.go | 4 +- .../services/albwaf/utils/util_test.go | 2 +- stackit/internal/testutil/testutil.go | 4 +- stackit/provider.go | 5 +- 9 files changed, 381 insertions(+), 10 deletions(-) create mode 100644 stackit/internal/services/albwaf/albwaf_acc_test.go create mode 100644 stackit/internal/services/albwaf/managed_rule_set/datasource.go diff --git a/stackit/internal/core/core.go b/stackit/internal/core/core.go index a567a9d56..478b92c71 100644 --- a/stackit/internal/core/core.go +++ b/stackit/internal/core/core.go @@ -42,7 +42,7 @@ type ProviderData struct { DefaultRegion string ALBCertificatesCustomEndpoint string ALBCustomEndpoint string - ALBWAFCustomEndpoint string + AlbWafCustomEndpoint string AuthorizationCustomEndpoint string CdnCustomEndpoint string DnsCustomEndpoint string diff --git a/stackit/internal/services/albwaf/albwaf_acc_test.go b/stackit/internal/services/albwaf/albwaf_acc_test.go new file mode 100644 index 000000000..b8e539850 --- /dev/null +++ b/stackit/internal/services/albwaf/albwaf_acc_test.go @@ -0,0 +1,184 @@ +package albwaf_test + +import ( + "context" + _ "embed" + "errors" + "fmt" + "maps" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "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" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" +) + +var ( + //go:embed testdata/managed-rule-set.tf + managedRuleSetConfig string +) + +var testManagedRuleSet = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)), + "type": config.StringVariable("TYPE_OWASP_CRS"), +} + +var testManagedRuleSetUpdated = func() config.Variables { + updatedConfig := config.Variables{} + maps.Copy(updatedConfig, testManagedRuleSet) + updatedConfig["name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["name"]))) + return updatedConfig +} + +func TestAccManagedRuleSet(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDestroy, + Steps: []resource.TestStep{ + // Creation + { + ConfigVariables: testManagedRuleSet, + Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), managedRuleSetConfig), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "project_id", testutil.ProjectId), + 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 + { + ConfigVariables: testManagedRuleSet, + 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 + } + `, + testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), managedRuleSetConfig, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "region", testutil.Region), + 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(testManagedRuleSet["name"])), + + resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"), + ), + }, + // Import + { + ConfigVariables: testManagedRuleSet, + ResourceName: "stackit_alb_waf_managed_rule_set.managed_rule_set", + ImportStateIdFunc: func(s *terraform.State) (string, error) { + r, ok := s.RootModule().Resources["stackit_alb_waf_managed_rule_set.managed_rule_set"] + if !ok { + return "", fmt.Errorf("couldn't find resource stackit_alb_waf_managed_rule_set.managed_rule_set") + } + 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: testManagedRuleSetUpdated(), + Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), managedRuleSetConfig), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("stackit_alb_waf_managed_rule_set.managed_rule_set", plancheck.ResourceActionReplace), + }, + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "region", testutil.Region), + resource.TestCheckResourceAttrSet("stackit_alb_waf_managed_rule_set.managed_rule_set", "id"), + resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "name", testutil.ConvertConfigVariable(testManagedRuleSetUpdated()["name"])), + + resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"), + ), + }, + // Deletion is done by the framework implicitly + }, + }) +} + +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) + } + + return client, nil +} + +func testAccCheckDestroy(s *terraform.State) error { + checkFunctions := []func(s *terraform.State) error{ + testAlbWafManagedRuleSetDestroy, + } + var errs []error + + for _, f := range checkFunctions { + func() { + err := f(s) + if err != nil { + errs = append(errs, err) + } + }() + } + return errors.Join(errs...) +} + +func testAlbWafManagedRuleSetDestroy(s *terraform.State) error { + ctx := context.Background() + client, err := createClient() + if err != nil { + return err + } + + managedRuleSetsToDestroy := []string{} + for _, rs := range s.RootModule().Resources { + if rs.Type != "stackit_alb_waf_managed_rule_set" { + continue + } + // managed rule set transform id: "[projectId],[region],[name]" + name := strings.Split(rs.Primary.ID, core.Separator)[2] + managedRuleSetsToDestroy = append(managedRuleSetsToDestroy, name) + } + + resp, err := client.DefaultAPI.ListManagedRuleSets(ctx, testutil.ProjectId, testutil.Region).Execute() + if err != nil { + return fmt.Errorf("getting resp: %w", err) + } + + for _, item := range resp.Items { + if utils.Contains(managedRuleSetsToDestroy, item.GetName()) { + _, err := client.DefaultAPI.DeleteManagedRuleSet(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 +} diff --git a/stackit/internal/services/albwaf/managed_rule_set/datasource.go b/stackit/internal/services/albwaf/managed_rule_set/datasource.go new file mode 100644 index 000000000..71c67aa46 --- /dev/null +++ b/stackit/internal/services/albwaf/managed_rule_set/datasource.go @@ -0,0 +1,186 @@ +package managed_rule_set + +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" + 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/services/albwaf/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ datasource.DataSource = &managedRuleSetDataSource{} + _ datasource.DataSourceWithConfigure = &managedRuleSetDataSource{} +) + +type managedRuleSetDataSource struct { + client *albWaf.APIClient + providerData core.ProviderData +} + +func NewManagedRuleSetDataSource() datasource.DataSource { + return &managedRuleSetDataSource{} +} + +func (d *managedRuleSetDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + var ok bool + d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + apiClient := utils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + d.client = apiClient + tflog.Info(ctx, "ALB WAF client configured") +} + +func (d *managedRuleSetDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_alb_waf_managed_rule_set" +} + +func (d *managedRuleSetDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: fmt.Sprintf("ALB WAF Managed Rule Set resource schema. %s", core.ResourceRegionFallbackDocstring), + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Terraform's internal resource identifier. Structured as \"`project_id`,`region`,`name`\".", + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: "STACKIT project ID associated with the ALB WAF Managed Rule Set.", + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Description: "STACKIT region name the resource is located in. If not defined, the provider region is used.", + Computed: true, + Optional: true, + }, + "name": schema.StringAttribute{ + Description: "Managed Rule Set configuration name.", + Required: true, + }, + "type": schema.StringAttribute{ + Description: "Set the Managed Rule Set type.", + Computed: true, + }, + "version": schema.StringAttribute{ + Description: "Managed Rule Set version.", + Computed: true, + }, + "usage": schema.SingleNestedAttribute{ + Description: "Managed Rule Set usage", + Computed: true, + Attributes: map[string]schema.Attribute{ + "count": schema.Int32Attribute{ + Description: "Number of WAFs using this Managed Rule Set.", + Computed: true, + }, + "items": schema.ListAttribute{ + Description: "List of WAFs that use this Managed Rule Set.", + Computed: true, + ElementType: types.StringType, + }, + }, + }, + "groups": schema.MapNestedAttribute{ + Description: "Inventory of all available Managed Rule Set groups and their current configuration.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + Description: "A description of what this group covers.", + Computed: true, + }, + "group_name": schema.StringAttribute{ + Description: "The name for the rule group.", + Computed: true, + }, + "rules": schema.MapNestedAttribute{ + Description: "Rules of the rule group.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + Description: "A description of what this rule does.", + Computed: true, + }, + "mode": schema.StringAttribute{ + Description: "The current mode of the rule.", + Computed: true, + }, + "severity": schema.StringAttribute{ + Description: "Impact level.", + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func (d *managedRuleSetDataSource) 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, "name", name) + ctx = tflog.SetField(ctx, "region", region) + + managedRuleSetResp, err := d.client.DefaultAPI.GetManagedRuleSet(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 Managed Rule Set", err.Error()) + return + } + + ctx = core.LogResponse(ctx) + + err = mapFields(ctx, managedRuleSetResp, &model, region) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Managed Rule Set", 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 Managed Rule Set read") +} diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource.go b/stackit/internal/services/albwaf/managed_rule_set/resource.go index 5ad82cd2a..5e3dc799b 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/resource.go +++ b/stackit/internal/services/albwaf/managed_rule_set/resource.go @@ -111,7 +111,7 @@ func (r *managedRuleSetResource) Configure(ctx context.Context, req resource.Con } func (r *managedRuleSetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_albwaf_managed_rule_set" + resp.TypeName = req.ProviderTypeName + "_alb_waf_managed_rule_set" } func (r *managedRuleSetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { diff --git a/stackit/internal/services/albwaf/testdata/managed-rule-set.tf b/stackit/internal/services/albwaf/testdata/managed-rule-set.tf index e279249b8..dac634f90 100644 --- a/stackit/internal/services/albwaf/testdata/managed-rule-set.tf +++ b/stackit/internal/services/albwaf/testdata/managed-rule-set.tf @@ -3,7 +3,7 @@ variable "project_id" {} variable "type" {} variable "name" {} -resource "stackit_albwaf_managed_rule_set" "managed_rule_set" { +resource "stackit_alb_waf_managed_rule_set" "managed_rule_set" { project_id = var.project_id type = var.type name = var.name diff --git a/stackit/internal/services/albwaf/utils/util.go b/stackit/internal/services/albwaf/utils/util.go index d7808ee51..d8e1e27fa 100644 --- a/stackit/internal/services/albwaf/utils/util.go +++ b/stackit/internal/services/albwaf/utils/util.go @@ -17,8 +17,8 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags config.WithCustomAuth(providerData.RoundTripper), utils.UserAgentConfigOption(providerData.Version), } - if providerData.ALBWAFCustomEndpoint != "" { - apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.ALBWAFCustomEndpoint)) + if providerData.AlbWafCustomEndpoint != "" { + apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.AlbWafCustomEndpoint)) } apiClient, err := albwaf.NewAPIClient(apiClientConfigOptions...) diff --git a/stackit/internal/services/albwaf/utils/util_test.go b/stackit/internal/services/albwaf/utils/util_test.go index ceb7cb2d6..57ddebbc3 100644 --- a/stackit/internal/services/albwaf/utils/util_test.go +++ b/stackit/internal/services/albwaf/utils/util_test.go @@ -60,7 +60,7 @@ func TestConfigureClient(t *testing.T) { args: args{ providerData: &core.ProviderData{ Version: testVersion, - ALBWAFCustomEndpoint: testCustomEndpoint, + AlbWafCustomEndpoint: testCustomEndpoint, }, }, expected: func() *albwaf.APIClient { diff --git a/stackit/internal/testutil/testutil.go b/stackit/internal/testutil/testutil.go index 2d7ce4083..3887570cb 100644 --- a/stackit/internal/testutil/testutil.go +++ b/stackit/internal/testutil/testutil.go @@ -69,7 +69,7 @@ var ( ALBCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_CUSTOM_ENDPOINT", providerName: "alb_custom_endpoint"} ALBCertCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_CERT_CUSTOM_ENDPOINT", providerName: "alb_certificates_custom_endpoint"} - ALBWAFCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_WAF_CUSTOM_ENDPOINT", providerName: "alb_waf_custom_endpoint"} + AlbWafCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_WAF_CUSTOM_ENDPOINT", providerName: "alb_waf_custom_endpoint"} CdnCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_CDN_CUSTOM_ENDPOINT", providerName: "cdn_custom_endpoint"} DnsCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_DNS_CUSTOM_ENDPOINT", providerName: "dns_custom_endpoint"} DremioCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_DREMIO_CUSTOM_ENDPOINT", providerName: "dremio_custom_endpoint"} @@ -108,7 +108,7 @@ var ( allCustomEndpoints = []customEndpointConfig{ ALBCustomEndpoint, ALBCertCustomEndpoint, - ALBWAFCustomEndpoint, + AlbWafCustomEndpoint, CdnCustomEndpoint, DnsCustomEndpoint, EdgeCloudCustomEndpoint, diff --git a/stackit/provider.go b/stackit/provider.go index 145759f73..f99c5eb3c 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -187,7 +187,7 @@ type providerModel struct { AuthorizationCustomEndpoint types.String `tfsdk:"authorization_custom_endpoint"` CdnCustomEndpoint types.String `tfsdk:"cdn_custom_endpoint"` ALBCertificatesCustomEndpoint types.String `tfsdk:"alb_certificates_custom_endpoint"` - ALBWAFCustomEndpoint types.String `tfsdk:"alb_waf_custom_endpoint"` + AlbWafCustomEndpoint types.String `tfsdk:"alb_waf_custom_endpoint"` DnsCustomEndpoint types.String `tfsdk:"dns_custom_endpoint"` DremioCustomEndpoint types.String `tfsdk:"dremio_custom_endpoint"` EdgeCloudCustomEndpoint types.String `tfsdk:"edgecloud_custom_endpoint"` @@ -570,7 +570,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, setStringField(providerConfig.ALBCertificatesCustomEndpoint, func(v string) { providerData.ALBCertificatesCustomEndpoint = v }) setStringField(providerConfig.ALBCustomEndpoint, func(v string) { providerData.ALBCustomEndpoint = v }) - setStringField(providerConfig.ALBWAFCustomEndpoint, func(v string) { providerData.ALBWAFCustomEndpoint = v }) + setStringField(providerConfig.AlbWafCustomEndpoint, func(v string) { providerData.AlbWafCustomEndpoint = v }) setStringField(providerConfig.AuthorizationCustomEndpoint, func(v string) { providerData.AuthorizationCustomEndpoint = v }) setStringField(providerConfig.CdnCustomEndpoint, func(v string) { providerData.CdnCustomEndpoint = v }) setStringField(providerConfig.DnsCustomEndpoint, func(v string) { providerData.DnsCustomEndpoint = v }) @@ -680,6 +680,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, + albWafManagedRuleSet.NewManagedRuleSetDataSource, alertGroup.NewAlertGroupDataSource, cdn.NewDistributionDataSource, cert.NewCertificatesDataSource, From 2d540876162491d0a5f9db937fe8abbc09a7b383 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Fri, 17 Jul 2026 10:27:40 +0200 Subject: [PATCH 03/11] fmt, lint and docs --- docs/data-sources/alb_waf_managed_rule_set.md | 61 +++++++++++++++++++ docs/index.md | 1 + docs/resources/alb_waf_managed_rule_set.md | 61 +++++++++++++++++++ .../albwaf/managed_rule_set/resource.go | 6 +- .../albwaf/testdata/managed-rule-set.tf | 4 +- 5 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 docs/data-sources/alb_waf_managed_rule_set.md create mode 100644 docs/resources/alb_waf_managed_rule_set.md diff --git a/docs/data-sources/alb_waf_managed_rule_set.md b/docs/data-sources/alb_waf_managed_rule_set.md new file mode 100644 index 000000000..1c7a27a0c --- /dev/null +++ b/docs/data-sources/alb_waf_managed_rule_set.md @@ -0,0 +1,61 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_alb_waf_managed_rule_set Data Source - stackit" +subcategory: "" +description: |- + ALB WAF Managed Rule Set resource schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on resource level. +--- + +# stackit_alb_waf_managed_rule_set (Data Source) + +ALB WAF Managed Rule Set resource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on resource level. + + + + +## Schema + +### Required + +- `name` (String) Managed Rule Set configuration name. +- `project_id` (String) STACKIT project ID associated with the ALB WAF Managed Rule Set. + +### Optional + +- `region` (String) STACKIT region name the resource is located in. If not defined, the provider region is used. + +### Read-Only + +- `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) Set the Managed Rule Set type. +- `usage` (Attributes) Managed Rule Set usage (see [below for nested schema](#nestedatt--usage)) +- `version` (String) Managed Rule Set version. + + +### Nested Schema for `groups` + +Read-Only: + +- `description` (String) A description of what this group covers. +- `group_name` (String) The name for the rule group. +- `rules` (Attributes Map) Rules of the rule group. (see [below for nested schema](#nestedatt--groups--rules)) + + +### Nested Schema for `groups.rules` + +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/index.md b/docs/index.md index 45110c0c4..c3ceba33e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -173,6 +173,7 @@ See this [example](https://professional-service.git.onstackit.cloud/professional - `alb_certificates_custom_endpoint` (String) Custom endpoint for the Application Load Balancer TLS Certificate service - `alb_custom_endpoint` (String) Custom endpoint for the Application Load Balancer service +- `alb_waf_custom_endpoint` (String) Custom endpoint for the Application Load Balancer Web Application Firewall service - `authorization_custom_endpoint` (String) Custom endpoint for the Membership service - `cdn_custom_endpoint` (String) Custom endpoint for the CDN service - `credentials_path` (String) Path of JSON from where the credentials are read. Takes precedence over the env var `STACKIT_CREDENTIALS_PATH`. Default value is `~/.stackit/credentials.json`. diff --git a/docs/resources/alb_waf_managed_rule_set.md b/docs/resources/alb_waf_managed_rule_set.md new file mode 100644 index 000000000..cd50eeda7 --- /dev/null +++ b/docs/resources/alb_waf_managed_rule_set.md @@ -0,0 +1,61 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_alb_waf_managed_rule_set Resource - stackit" +subcategory: "" +description: |- + ALB WAF Managed Rule Set resource schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on resource level. +--- + +# stackit_alb_waf_managed_rule_set (Resource) + +ALB WAF Managed Rule Set resource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on resource level. + + + + +## Schema + +### Required + +- `name` (String) Managed Rule Set configuration name. +- `project_id` (String) STACKIT project ID associated with the ALB WAF Managed Rule Set. +- `type` (String) Set the Managed Rule Set type. + +### Optional + +- `region` (String) STACKIT region name the resource is located in. If not defined, the provider region is used. + +### Read-Only + +- `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. + + +### Nested Schema for `groups` + +Read-Only: + +- `description` (String) A description of what this group covers. +- `group_name` (String) The name for the rule group. +- `rules` (Attributes Map) Rules of the rule group. (see [below for nested schema](#nestedatt--groups--rules)) + + +### Nested Schema for `groups.rules` + +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/managed_rule_set/resource.go b/stackit/internal/services/albwaf/managed_rule_set/resource.go index 5e3dc799b..fab8a2c71 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/resource.go +++ b/stackit/internal/services/albwaf/managed_rule_set/resource.go @@ -330,7 +330,7 @@ func (r *managedRuleSetResource) Create(ctx context.Context, req resource.Create tflog.Info(ctx, "ALB WAF Managed Rule Set created") } -func (r *managedRuleSetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform +func (r *managedRuleSetResource) 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 Managed Rule Set is not updatable") } @@ -406,7 +406,7 @@ func (r *managedRuleSetResource) Delete(ctx context.Context, req resource.Delete tflog.Info(ctx, "ALB WAF Managed Rule Set deleted") } -func toCreatePayload(ctx context.Context, model *Model) (*albwaf.CreateManagedRuleSetPayload, error) { +func toCreatePayload(_ context.Context, model *Model) (*albwaf.CreateManagedRuleSetPayload, error) { if model == nil { return nil, fmt.Errorf("nil model") } @@ -439,7 +439,6 @@ func mapFields(ctx context.Context, managedRuleSet *albwaf.GetManagedRuleSetResp groupsMap := map[string]attr.Value{} if groups, ok := managedRuleSet.GetGroupsOk(); ok { for groupKey, group := range *groups { - groupTF := RuleGroupModel{ Description: types.StringPointerValue(group.Description), GroupName: types.StringPointerValue(group.GroupName), @@ -448,7 +447,6 @@ func mapFields(ctx context.Context, managedRuleSet *albwaf.GetManagedRuleSetResp ruleMap := map[string]attr.Value{} if rules, ok := group.GetRulesOk(); ok { for ruleKey, rule := range *rules { - ruleTF := RuleModel{ Description: types.StringPointerValue(rule.Description), Mode: types.StringPointerValue((*string)(rule.Mode)), diff --git a/stackit/internal/services/albwaf/testdata/managed-rule-set.tf b/stackit/internal/services/albwaf/testdata/managed-rule-set.tf index dac634f90..de5736aca 100644 --- a/stackit/internal/services/albwaf/testdata/managed-rule-set.tf +++ b/stackit/internal/services/albwaf/testdata/managed-rule-set.tf @@ -5,6 +5,6 @@ variable "name" {} resource "stackit_alb_waf_managed_rule_set" "managed_rule_set" { project_id = var.project_id - type = var.type - name = var.name + type = var.type + name = var.name } From c493625640438bce76b059dc7d960417cee304a4 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Fri, 17 Jul 2026 10:32:59 +0200 Subject: [PATCH 04/11] add examples --- .../stackit_alb_waf_managed_rule_set/data-source.tf | 4 ++++ .../resources/stackit_alb_waf_managed_rule_set/resource.tf | 5 +++++ 2 files changed, 9 insertions(+) create mode 100644 examples/data-sources/stackit_alb_waf_managed_rule_set/data-source.tf create mode 100644 examples/resources/stackit_alb_waf_managed_rule_set/resource.tf diff --git a/examples/data-sources/stackit_alb_waf_managed_rule_set/data-source.tf b/examples/data-sources/stackit_alb_waf_managed_rule_set/data-source.tf new file mode 100644 index 000000000..2b5b6bf29 --- /dev/null +++ b/examples/data-sources/stackit_alb_waf_managed_rule_set/data-source.tf @@ -0,0 +1,4 @@ +data "stackit_alb_waf_managed_rule_set" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-managed-rule-set" +} diff --git a/examples/resources/stackit_alb_waf_managed_rule_set/resource.tf b/examples/resources/stackit_alb_waf_managed_rule_set/resource.tf new file mode 100644 index 000000000..f30363ed4 --- /dev/null +++ b/examples/resources/stackit_alb_waf_managed_rule_set/resource.tf @@ -0,0 +1,5 @@ +resource "stackit_alb_waf_managed_rule_set" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-managed-rule-set" + type = "TYPE_OWASP_CRS" +} From a0f0ad80e564a07b5f1b6ea7c25106dd6e7d2193 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Fri, 17 Jul 2026 10:43:29 +0200 Subject: [PATCH 05/11] rename albwaf -> albWaf --- .../albwaf/managed_rule_set/resource.go | 14 ++++++------ .../albwaf/managed_rule_set/resource_test.go | 22 +++++++++---------- .../internal/services/albwaf/utils/util.go | 6 ++--- .../services/albwaf/utils/util_test.go | 12 +++++----- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource.go b/stackit/internal/services/albwaf/managed_rule_set/resource.go index fab8a2c71..af1a14309 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/resource.go +++ b/stackit/internal/services/albwaf/managed_rule_set/resource.go @@ -19,7 +19,7 @@ import ( "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" + albWaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi" sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" @@ -36,7 +36,7 @@ var ( _ resource.ResourceWithImportState = &managedRuleSetResource{} _ resource.ResourceWithModifyPlan = &managedRuleSetResource{} - mrsTypeOptions = sdkUtils.EnumSliceToStringSlice(albwaf.AllowedMRSTypeEnumValues) + mrsTypeOptions = sdkUtils.EnumSliceToStringSlice(albWaf.AllowedMRSTypeEnumValues) ) type Model struct { @@ -87,7 +87,7 @@ var usageType = map[string]attr.Type{ } type managedRuleSetResource struct { - client *albwaf.APIClient + client *albWaf.APIClient providerData core.ProviderData } @@ -406,20 +406,20 @@ func (r *managedRuleSetResource) Delete(ctx context.Context, req resource.Delete tflog.Info(ctx, "ALB WAF Managed Rule Set deleted") } -func toCreatePayload(_ context.Context, model *Model) (*albwaf.CreateManagedRuleSetPayload, error) { +func toCreatePayload(_ context.Context, model *Model) (*albWaf.CreateManagedRuleSetPayload, error) { if model == nil { return nil, fmt.Errorf("nil model") } - payload := &albwaf.CreateManagedRuleSetPayload{ + payload := &albWaf.CreateManagedRuleSetPayload{ Name: model.Name.ValueStringPointer(), - Type: new(albwaf.MRSType(model.Type.ValueString())), + Type: new(albWaf.MRSType(model.Type.ValueString())), } return payload, nil } -func mapFields(ctx context.Context, managedRuleSet *albwaf.GetManagedRuleSetResponse, model *Model, region string) error { +func mapFields(ctx context.Context, managedRuleSet *albWaf.GetManagedRuleSetResponse, model *Model, region string) error { if managedRuleSet == nil { return fmt.Errorf("response input is 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 6b0af4e70..9b7bc9548 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/resource_test.go +++ b/stackit/internal/services/albwaf/managed_rule_set/resource_test.go @@ -9,7 +9,7 @@ import ( "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" + albWaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi" ) var ( @@ -23,7 +23,7 @@ func TestToCreatePayload(t *testing.T) { tests := []struct { name string model *Model - expected *albwaf.CreateManagedRuleSetPayload + expected *albWaf.CreateManagedRuleSetPayload isValid bool }{ { @@ -33,11 +33,11 @@ func TestToCreatePayload(t *testing.T) { Id: testId, ProjectId: testProjectId, Region: testRegion, - Type: types.StringValue(string(albwaf.MRSTYPE_TYPE_OWASP_CRS)), + Type: types.StringValue(string(albWaf.MRSTYPE_TYPE_OWASP_CRS)), }, - expected: &albwaf.CreateManagedRuleSetPayload{ + expected: &albWaf.CreateManagedRuleSetPayload{ Name: testName.ValueStringPointer(), - Type: new(albwaf.MRSTYPE_TYPE_OWASP_CRS), + Type: new(albWaf.MRSTYPE_TYPE_OWASP_CRS), }, isValid: true, }, @@ -64,7 +64,7 @@ func TestMapFields(t *testing.T) { name string state *Model region string - input *albwaf.GetManagedRuleSetResponse + input *albWaf.GetManagedRuleSetResponse expected *Model isValid bool }{ @@ -74,21 +74,21 @@ func TestMapFields(t *testing.T) { ProjectId: testProjectId, Region: testRegion, Name: testName, - Type: types.StringValue(string(albwaf.MRSTYPE_TYPE_OWASP_CRS)), + Type: types.StringValue(string(albWaf.MRSTYPE_TYPE_OWASP_CRS)), Id: testId, Groups: types.MapValueMust(types.ObjectType{AttrTypes: ruleGroupType}, map[string]attr.Value{}), }, region: testRegion.ValueString(), - input: &albwaf.GetManagedRuleSetResponse{ - Groups: &map[string]albwaf.MRSRuleGroup{}, + input: &albWaf.GetManagedRuleSetResponse{ + Groups: &map[string]albWaf.MRSRuleGroup{}, Name: testName.ValueStringPointer(), - Type: new(albwaf.MRSTYPE2_TYPE_OWASP_CRS), + Type: new(albWaf.MRSTYPE2_TYPE_OWASP_CRS), }, expected: &Model{ ProjectId: testProjectId, Region: testRegion, Name: testName, - Type: types.StringValue(string(albwaf.MRSTYPE_TYPE_OWASP_CRS)), + Type: types.StringValue(string(albWaf.MRSTYPE_TYPE_OWASP_CRS)), Id: testId, Groups: types.MapValueMust(types.ObjectType{AttrTypes: ruleGroupType}, map[string]attr.Value{}), }, diff --git a/stackit/internal/services/albwaf/utils/util.go b/stackit/internal/services/albwaf/utils/util.go index d8e1e27fa..9ed484a50 100644 --- a/stackit/internal/services/albwaf/utils/util.go +++ b/stackit/internal/services/albwaf/utils/util.go @@ -6,13 +6,13 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/stackitcloud/stackit-sdk-go/core/config" - 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/utils" ) -func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *albwaf.APIClient { +func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *albWaf.APIClient { apiClientConfigOptions := []config.ConfigurationOption{ config.WithCustomAuth(providerData.RoundTripper), utils.UserAgentConfigOption(providerData.Version), @@ -21,7 +21,7 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.AlbWafCustomEndpoint)) } - apiClient, err := albwaf.NewAPIClient(apiClientConfigOptions...) + apiClient, err := albWaf.NewAPIClient(apiClientConfigOptions...) if err != nil { core.LogAndAddError(ctx, diags, "Error configuring API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err)) return nil diff --git a/stackit/internal/services/albwaf/utils/util_test.go b/stackit/internal/services/albwaf/utils/util_test.go index 57ddebbc3..ccd9f606c 100644 --- a/stackit/internal/services/albwaf/utils/util_test.go +++ b/stackit/internal/services/albwaf/utils/util_test.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients" "github.com/stackitcloud/stackit-sdk-go/core/config" - 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/utils" @@ -35,7 +35,7 @@ func TestConfigureClient(t *testing.T) { name string args args wantErr bool - expected *albwaf.APIClient + expected *albWaf.APIClient }{ { name: "default endpoint", @@ -44,8 +44,8 @@ func TestConfigureClient(t *testing.T) { Version: testVersion, }, }, - expected: func() *albwaf.APIClient { - apiClient, err := albwaf.NewAPIClient( + expected: func() *albWaf.APIClient { + apiClient, err := albWaf.NewAPIClient( utils.UserAgentConfigOption(testVersion), ) if err != nil { @@ -63,8 +63,8 @@ func TestConfigureClient(t *testing.T) { AlbWafCustomEndpoint: testCustomEndpoint, }, }, - expected: func() *albwaf.APIClient { - apiClient, err := albwaf.NewAPIClient( + expected: func() *albWaf.APIClient { + apiClient, err := albWaf.NewAPIClient( utils.UserAgentConfigOption(testVersion), config.WithEndpoint(testCustomEndpoint), ) From f79dbc278bb1d7b0e75cd5a7f62fb190e7038918 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Fri, 17 Jul 2026 10:45:25 +0200 Subject: [PATCH 06/11] generate docs --- docs/data-sources/alb_waf_managed_rule_set.md | 9 ++++++++- docs/resources/alb_waf_managed_rule_set.md | 10 +++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/data-sources/alb_waf_managed_rule_set.md b/docs/data-sources/alb_waf_managed_rule_set.md index 1c7a27a0c..edab8a78d 100644 --- a/docs/data-sources/alb_waf_managed_rule_set.md +++ b/docs/data-sources/alb_waf_managed_rule_set.md @@ -10,7 +10,14 @@ description: |- ALB WAF Managed Rule Set resource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on resource level. - +## Example Usage + +```terraform +data "stackit_alb_waf_managed_rule_set" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-managed-rule-set" +} +``` ## Schema diff --git a/docs/resources/alb_waf_managed_rule_set.md b/docs/resources/alb_waf_managed_rule_set.md index cd50eeda7..5e25ec2c0 100644 --- a/docs/resources/alb_waf_managed_rule_set.md +++ b/docs/resources/alb_waf_managed_rule_set.md @@ -10,7 +10,15 @@ description: |- ALB WAF Managed Rule Set resource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on resource level. - +## Example Usage + +```terraform +resource "stackit_alb_waf_managed_rule_set" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-managed-rule-set" + type = "TYPE_OWASP_CRS" +} +``` ## Schema From d8cde3e7e8ad961d5760eac3e7a47847296baf08 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Mon, 20 Jul 2026 09:56:18 +0200 Subject: [PATCH 07/11] update version and introduce descriptions map --- docs/data-sources/alb_waf_managed_rule_set.md | 4 +- go.mod | 2 +- go.sum | 4 +- .../albwaf/managed_rule_set/datasource.go | 46 ++++++++------ .../albwaf/managed_rule_set/resource.go | 60 ++++++++++++------- 5 files changed, 71 insertions(+), 45 deletions(-) diff --git a/docs/data-sources/alb_waf_managed_rule_set.md b/docs/data-sources/alb_waf_managed_rule_set.md index edab8a78d..66bd5117c 100644 --- a/docs/data-sources/alb_waf_managed_rule_set.md +++ b/docs/data-sources/alb_waf_managed_rule_set.md @@ -3,12 +3,12 @@ page_title: "stackit_alb_waf_managed_rule_set Data Source - stackit" subcategory: "" description: |- - ALB WAF Managed Rule Set resource schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on resource level. + ALB WAF Managed Rule Set DataSource schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on datasource level. --- # stackit_alb_waf_managed_rule_set (Data Source) -ALB WAF Managed Rule Set resource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on resource level. +ALB WAF Managed Rule Set DataSource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on datasource level. ## Example Usage diff --git a/go.mod b/go.mod index db197c965..6e59f3a60 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.9.0 + github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.10.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 40ca7084b..4c4830217 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.9.0 h1:R3ovD+OhhjhhiVYUkhK/Szo4pQVU0KKBJeCo3JB/aH0= -github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.9.0/go.mod h1:4M9G1I64kZwlXO32ZoIpt0GAN4SpZ1SYerwCVVIBGoE= +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/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/managed_rule_set/datasource.go b/stackit/internal/services/albwaf/managed_rule_set/datasource.go index 71c67aa46..71c1758fe 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/datasource.go +++ b/stackit/internal/services/albwaf/managed_rule_set/datasource.go @@ -5,7 +5,9 @@ import ( "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" @@ -55,14 +57,14 @@ func (d *managedRuleSetDataSource) Metadata(_ context.Context, req datasource.Me func (d *managedRuleSetDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: fmt.Sprintf("ALB WAF Managed Rule Set resource schema. %s", core.ResourceRegionFallbackDocstring), + Description: fmt.Sprintf("ALB WAF Managed Rule Set DataSource schema. %s", core.DatasourceRegionFallbackDocstring), Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ - Description: "Terraform's internal resource identifier. Structured as \"`project_id`,`region`,`name`\".", + Description: descriptions["id"], Computed: true, }, "project_id": schema.StringAttribute{ - Description: "STACKIT project ID associated with the ALB WAF Managed Rule Set.", + Description: descriptions["project_id"], Required: true, Validators: []validator.String{ validate.UUID(), @@ -70,65 +72,71 @@ func (d *managedRuleSetDataSource) Schema(_ context.Context, _ datasource.Schema }, }, "region": schema.StringAttribute{ - Description: "STACKIT region name the resource is located in. If not defined, the provider region is used.", - Computed: true, + Description: descriptions["region"], Optional: true, + Computed: true, }, "name": schema.StringAttribute{ - Description: "Managed Rule Set configuration name.", + 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", + ), + }, }, "type": schema.StringAttribute{ - Description: "Set the Managed Rule Set type.", + Description: descriptions["type"], Computed: true, }, "version": schema.StringAttribute{ - Description: "Managed Rule Set version.", + Description: descriptions["version"], Computed: true, }, "usage": schema.SingleNestedAttribute{ - Description: "Managed Rule Set usage", + Description: descriptions["usage"], Computed: true, Attributes: map[string]schema.Attribute{ "count": schema.Int32Attribute{ - Description: "Number of WAFs using this Managed Rule Set.", + Description: descriptions["usage_count"], Computed: true, }, "items": schema.ListAttribute{ - Description: "List of WAFs that use this Managed Rule Set.", + Description: descriptions["usage_items"], Computed: true, ElementType: types.StringType, }, }, }, "groups": schema.MapNestedAttribute{ - Description: "Inventory of all available Managed Rule Set groups and their current configuration.", + Description: descriptions["groups"], Computed: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "description": schema.StringAttribute{ - Description: "A description of what this group covers.", + Description: descriptions["group_description"], Computed: true, }, "group_name": schema.StringAttribute{ - Description: "The name for the rule group.", + Description: descriptions["group_name"], Computed: true, }, "rules": schema.MapNestedAttribute{ - Description: "Rules of the rule group.", + Description: descriptions["group_rules"], Computed: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "description": schema.StringAttribute{ - Description: "A description of what this rule does.", + Description: descriptions["rule_description"], Computed: true, }, "mode": schema.StringAttribute{ - Description: "The current mode of the rule.", + Description: descriptions["rule_mode"], Computed: true, }, "severity": schema.StringAttribute{ - Description: "Impact level.", + Description: descriptions["rule_severity"], Computed: true, }, }, @@ -155,8 +163,8 @@ func (d *managedRuleSetDataSource) Read(ctx context.Context, req datasource.Read name := model.Name.ValueString() region := d.providerData.GetRegionWithOverride(model.Region) ctx = tflog.SetField(ctx, "project_id", projectId) - ctx = tflog.SetField(ctx, "name", name) ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "name", name) managedRuleSetResp, err := d.client.DefaultAPI.GetManagedRuleSet(ctx, projectId, region, name).Execute() if err != nil { diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource.go b/stackit/internal/services/albwaf/managed_rule_set/resource.go index af1a14309..c51258e9a 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/resource.go +++ b/stackit/internal/services/albwaf/managed_rule_set/resource.go @@ -114,19 +114,39 @@ func (r *managedRuleSetResource) Metadata(_ context.Context, req resource.Metada resp.TypeName = req.ProviderTypeName + "_alb_waf_managed_rule_set" } +// 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 Managed Rule Set.", + "region": "STACKIT region name the resource is located in. If not defined, the provider region is used.", + "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.", + "group_rules": "Rules of the rule group.", + "rule_description": "A description of what this rule does.", + "rule_mode": "The current mode of the rule.", + "rule_severity": "Impact level.", +} + func (r *managedRuleSetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Description: fmt.Sprintf("ALB WAF Managed Rule Set resource schema. %s", core.ResourceRegionFallbackDocstring), Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ - Description: "Terraform's internal resource identifier. Structured as \"`project_id`,`region`,`name`\".", + Description: descriptions["id"], Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, }, "project_id": schema.StringAttribute{ - Description: "STACKIT project ID associated with the ALB WAF Managed Rule Set.", + Description: descriptions["project_id"], Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), @@ -137,7 +157,7 @@ func (r *managedRuleSetResource) Schema(_ context.Context, _ resource.SchemaRequ }, }, "region": schema.StringAttribute{ - Description: "STACKIT region name the resource is located in. If not defined, the provider region is used.", + Description: descriptions["region"], Optional: true, Computed: true, PlanModifiers: []planmodifier.String{ @@ -145,7 +165,7 @@ func (r *managedRuleSetResource) Schema(_ context.Context, _ resource.SchemaRequ }, }, "name": schema.StringAttribute{ - Description: "Managed Rule Set configuration name.", + Description: descriptions["name"], Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), @@ -158,62 +178,59 @@ func (r *managedRuleSetResource) Schema(_ context.Context, _ resource.SchemaRequ }, }, "type": schema.StringAttribute{ - Description: "Set the Managed Rule Set type.", + Description: descriptions["type"], Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, - Validators: []validator.String{ - stringvalidator.OneOf(mrsTypeOptions...), - }, }, "version": schema.StringAttribute{ - Description: "Managed Rule Set version.", + Description: descriptions["version"], Computed: true, }, "usage": schema.SingleNestedAttribute{ - Description: "Managed Rule Set usage", + Description: descriptions["usage"], Computed: true, Attributes: map[string]schema.Attribute{ "count": schema.Int32Attribute{ - Description: "Number of WAFs using this Managed Rule Set.", + Description: descriptions["usage_count"], Computed: true, }, "items": schema.ListAttribute{ - Description: "List of WAFs that use this Managed Rule Set.", + Description: descriptions["usage_items"], Computed: true, ElementType: types.StringType, }, }, }, "groups": schema.MapNestedAttribute{ - Description: "Inventory of all available Managed Rule Set groups and their current configuration.", + Description: descriptions["groups"], Computed: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "description": schema.StringAttribute{ - Description: "A description of what this group covers.", + Description: descriptions["group_description"], Computed: true, }, "group_name": schema.StringAttribute{ - Description: "The name for the rule group.", + Description: descriptions["group_name"], Computed: true, }, "rules": schema.MapNestedAttribute{ - Description: "Rules of the rule group.", + Description: descriptions["group_rules"], Computed: true, NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "description": schema.StringAttribute{ - Description: "A description of what this rule does.", + Description: descriptions["rule_description"], Computed: true, }, "mode": schema.StringAttribute{ - Description: "The current mode of the rule.", + Description: descriptions["rule_mode"], Computed: true, }, "severity": schema.StringAttribute{ - Description: "Impact level.", + Description: descriptions["rule_severity"], Computed: true, }, }, @@ -286,6 +303,7 @@ func (r *managedRuleSetResource) Create(ctx context.Context, req resource.Create 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 { @@ -348,8 +366,8 @@ func (r *managedRuleSetResource) Read(ctx context.Context, req resource.ReadRequ name := model.Name.ValueString() region := r.providerData.GetRegionWithOverride(model.Region) ctx = tflog.SetField(ctx, "project_id", projectId) - ctx = tflog.SetField(ctx, "name", name) ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "name", name) managedRuleSetResp, err := r.client.DefaultAPI.GetManagedRuleSet(ctx, projectId, region, name).Execute() if err != nil { @@ -392,8 +410,8 @@ func (r *managedRuleSetResource) Delete(ctx context.Context, req resource.Delete name := model.Name.ValueString() region := r.providerData.GetRegionWithOverride(model.Region) ctx = tflog.SetField(ctx, "project_id", projectId) - ctx = tflog.SetField(ctx, "name", name) ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "name", name) _, err := r.client.DefaultAPI.DeleteManagedRuleSet(ctx, projectId, region, name).Execute() if err != nil { From 1ffd32d934475fe58f1340c07c34d8979c8df8eb Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Mon, 20 Jul 2026 10:23:05 +0200 Subject: [PATCH 08/11] add specific not found error message to datasource --- stackit/internal/services/albwaf/managed_rule_set/datasource.go | 1 + 1 file changed, 1 insertion(+) diff --git a/stackit/internal/services/albwaf/managed_rule_set/datasource.go b/stackit/internal/services/albwaf/managed_rule_set/datasource.go index 71c1758fe..067bf26f2 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/datasource.go +++ b/stackit/internal/services/albwaf/managed_rule_set/datasource.go @@ -170,6 +170,7 @@ func (d *managedRuleSetDataSource) Read(ctx context.Context, req datasource.Read 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 Managed Rule Set with name %q not found in project %q and region %q", name, projectId, region), err.Error()) resp.State.RemoveResource(ctx) return } From 6276a8b8589a1f311673ebb6a94246398b581007 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Mon, 20 Jul 2026 10:25:27 +0200 Subject: [PATCH 09/11] generate docs --- docs/data-sources/alb_waf_managed_rule_set.md | 2 +- docs/resources/alb_waf_managed_rule_set.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/data-sources/alb_waf_managed_rule_set.md b/docs/data-sources/alb_waf_managed_rule_set.md index 66bd5117c..12f142b12 100644 --- a/docs/data-sources/alb_waf_managed_rule_set.md +++ b/docs/data-sources/alb_waf_managed_rule_set.md @@ -35,7 +35,7 @@ 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) Set the Managed Rule Set type. +- `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. diff --git a/docs/resources/alb_waf_managed_rule_set.md b/docs/resources/alb_waf_managed_rule_set.md index 5e25ec2c0..c4b7e91f8 100644 --- a/docs/resources/alb_waf_managed_rule_set.md +++ b/docs/resources/alb_waf_managed_rule_set.md @@ -27,7 +27,7 @@ resource "stackit_alb_waf_managed_rule_set" "example" { - `name` (String) Managed Rule Set configuration name. - `project_id` (String) STACKIT project ID associated with the ALB WAF Managed Rule Set. -- `type` (String) Set the Managed Rule Set type. +- `type` (String) Type of the Managed Rule Set. ### Optional From 7e334cdf4f88598a28df009928c683d3d153781b Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Mon, 20 Jul 2026 10:34:58 +0200 Subject: [PATCH 10/11] removed unused variable --- stackit/internal/services/albwaf/managed_rule_set/resource.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource.go b/stackit/internal/services/albwaf/managed_rule_set/resource.go index c51258e9a..05de3141b 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/resource.go +++ b/stackit/internal/services/albwaf/managed_rule_set/resource.go @@ -21,8 +21,6 @@ import ( "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/services/albwaf/utils" @@ -35,8 +33,6 @@ var ( _ resource.ResourceWithConfigure = &managedRuleSetResource{} _ resource.ResourceWithImportState = &managedRuleSetResource{} _ resource.ResourceWithModifyPlan = &managedRuleSetResource{} - - mrsTypeOptions = sdkUtils.EnumSliceToStringSlice(albWaf.AllowedMRSTypeEnumValues) ) type Model struct { From 50ed8c5e594bdf525d0431b6d143f7e05abc38ac Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Mon, 20 Jul 2026 10:47:00 +0200 Subject: [PATCH 11/11] flag as beta --- docs/data-sources/alb_waf_managed_rule_set.md | 3 +++ docs/resources/alb_waf_managed_rule_set.md | 3 +++ .../services/albwaf/managed_rule_set/datasource.go | 8 +++++++- .../internal/services/albwaf/managed_rule_set/resource.go | 8 +++++++- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/data-sources/alb_waf_managed_rule_set.md b/docs/data-sources/alb_waf_managed_rule_set.md index 12f142b12..1f335dabf 100644 --- a/docs/data-sources/alb_waf_managed_rule_set.md +++ b/docs/data-sources/alb_waf_managed_rule_set.md @@ -4,12 +4,15 @@ page_title: "stackit_alb_waf_managed_rule_set Data Source - stackit" subcategory: "" description: |- ALB WAF Managed Rule Set DataSource schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on datasource level. + ~> This datasource 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_managed_rule_set (Data Source) ALB WAF Managed Rule Set DataSource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on datasource level. +~> This datasource 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 diff --git a/docs/resources/alb_waf_managed_rule_set.md b/docs/resources/alb_waf_managed_rule_set.md index c4b7e91f8..eeb2c93f3 100644 --- a/docs/resources/alb_waf_managed_rule_set.md +++ b/docs/resources/alb_waf_managed_rule_set.md @@ -4,12 +4,15 @@ page_title: "stackit_alb_waf_managed_rule_set Resource - stackit" subcategory: "" description: |- ALB WAF Managed Rule Set 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_managed_rule_set (Resource) ALB WAF Managed Rule Set resource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on resource level. +~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources. + ## Example Usage ```terraform diff --git a/stackit/internal/services/albwaf/managed_rule_set/datasource.go b/stackit/internal/services/albwaf/managed_rule_set/datasource.go index 067bf26f2..b802907a4 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/datasource.go +++ b/stackit/internal/services/albwaf/managed_rule_set/datasource.go @@ -18,6 +18,7 @@ import ( "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" ) @@ -43,6 +44,11 @@ func (d *managedRuleSetDataSource) Configure(ctx context.Context, req datasource return } + features.CheckBetaResourcesEnabled(ctx, &d.providerData, &resp.Diagnostics, "stackit_alb_waf_managed_rule_set", core.Datasource) + if resp.Diagnostics.HasError() { + return + } + apiClient := utils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics) if resp.Diagnostics.HasError() { return @@ -57,7 +63,7 @@ func (d *managedRuleSetDataSource) Metadata(_ context.Context, req datasource.Me func (d *managedRuleSetDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: fmt.Sprintf("ALB WAF Managed Rule Set DataSource schema. %s", core.DatasourceRegionFallbackDocstring), + Description: features.AddBetaDescription(fmt.Sprintf("ALB WAF Managed Rule Set DataSource schema. %s", core.DatasourceRegionFallbackDocstring), core.Datasource), Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: descriptions["id"], diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource.go b/stackit/internal/services/albwaf/managed_rule_set/resource.go index 05de3141b..0409ec6e8 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/resource.go +++ b/stackit/internal/services/albwaf/managed_rule_set/resource.go @@ -23,6 +23,7 @@ import ( "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" @@ -98,6 +99,11 @@ func (r *managedRuleSetResource) Configure(ctx context.Context, req resource.Con return } + features.CheckBetaResourcesEnabled(ctx, &r.providerData, &resp.Diagnostics, "stackit_alb_waf_managed_rule_set", core.Resource) + if resp.Diagnostics.HasError() { + return + } + apiClient := utils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics) if resp.Diagnostics.HasError() { return @@ -132,7 +138,7 @@ var descriptions = map[string]string{ func (r *managedRuleSetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: fmt.Sprintf("ALB WAF Managed Rule Set resource schema. %s", core.ResourceRegionFallbackDocstring), + Description: features.AddBetaDescription(fmt.Sprintf("ALB WAF Managed Rule Set resource schema. %s", core.ResourceRegionFallbackDocstring), core.Resource), Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: descriptions["id"],