From 522190fd6251d5d84fe03f83f3404a33933190ae Mon Sep 17 00:00:00 2001 From: Rafael Kassner Date: Thu, 12 Sep 2024 14:56:53 +0200 Subject: [PATCH] resource pullzone_edgerule: multi-actions support --- CHANGELOG.md | 4 + docs/resources/pullzone_edgerule.md | 13 +- internal/api/pullzone_edgerule.go | 25 +- .../provider/resource_pullzone_edgerule.go | 279 ++++++++++++++---- 4 files changed, 249 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcb25a7..dca1302 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). > [!NOTE] > While we strive to maintain backwards compatibility as much as possible, we can't guarantee semantic versioning will be strictly followed, as this provider depends on the underlying [bunny.net API](https://docs.bunny.net/reference/bunnynet-api-overview). +## [0.3.15] - 2024-09-12 +## Added +- resource pullzone_edgerule: multi-actions support + ## [0.3.14] - 2024-09-10 ## Changed - resource pullzone_hostname: deleting an internal hostname (`*.b-cdn.net`) will remove the resource from state without deleting it, as internal hostnames cannot be deleted; diff --git a/docs/resources/pullzone_edgerule.md b/docs/resources/pullzone_edgerule.md index cc10cd2..7678977 100644 --- a/docs/resources/pullzone_edgerule.md +++ b/docs/resources/pullzone_edgerule.md @@ -37,15 +37,16 @@ resource "bunnynet_pullzone_edgerule" "block_admin" { ### Required -- `action` (String) Options: `BlockRequest`, `BypassPermaCache`, `DisableOptimizer`, `DisableTokenAuthentication`, `EnableTokenAuthentication`, `ForceCompression`, `ForceDownload`, `ForceSSL`, `IgnoreQueryString`, `OriginStorage`, `OriginUrl`, `OverrideBrowserCacheTime`, `OverrideCacheTime`, `OverrideCacheTimePublic`, `Redirect`, `SetConnectionLimit`, `SetNetworkRateLimit`, `SetRequestHeader`, `SetRequestsPerSecondLimit`, `SetResponseHeader`, `SetStatusCode` - `enabled` (Boolean) Indicates whether the edge rule is enabled. - `pullzone` (Number) - `triggers` (List of Object) (see [below for nested schema](#nestedatt--triggers)) ### Optional +- `action` (String) Options: `BlockRequest`, `BypassPermaCache`, `DisableOptimizer`, `DisableTokenAuthentication`, `EnableTokenAuthentication`, `ForceCompression`, `ForceDownload`, `ForceSSL`, `IgnoreQueryString`, `OriginStorage`, `OriginUrl`, `OverrideBrowserCacheTime`, `OverrideCacheTime`, `OverrideCacheTimePublic`, `Redirect`, `SetConnectionLimit`, `SetNetworkRateLimit`, `SetRequestHeader`, `SetRequestsPerSecondLimit`, `SetResponseHeader`, `SetStatusCode` - `action_parameter1` (String) - `action_parameter2` (String) +- `actions` (List of Object) List of actions for the edge rule. (see [below for nested schema](#nestedatt--actions)) - `description` (String) The description of the edge rule. - `match_type` (String) Options: `MatchAll`, `MatchAny`, `MatchNone` @@ -64,6 +65,16 @@ Required: - `patterns` (List of String) - `type` (String) + + +### Nested Schema for `actions` + +Optional: + +- `parameter1` (String) +- `parameter2` (String) +- `type` (String) + ## Import Import is supported using the following syntax: diff --git a/internal/api/pullzone_edgerule.go b/internal/api/pullzone_edgerule.go index bb1de5c..f596c02 100644 --- a/internal/api/pullzone_edgerule.go +++ b/internal/api/pullzone_edgerule.go @@ -13,15 +13,22 @@ import ( ) type PullzoneEdgerule struct { - Id string `json:"Guid,omitempty"` - Enabled bool `json:"Enabled"` - Description string `json:"Description"` - Action uint8 `json:"ActionType"` - ActionParameter1 string `json:"ActionParameter1"` - ActionParameter2 string `json:"ActionParameter2"` - MatchType uint8 `json:"TriggerMatchingType"` - Triggers []PullzoneEdgeruleTrigger `json:"Triggers"` - PullzoneId int64 `json:"-"` + Id string `json:"Guid,omitempty"` + Enabled bool `json:"Enabled"` + Description string `json:"Description"` + Action uint8 `json:"ActionType"` + ActionParameter1 string `json:"ActionParameter1"` + ActionParameter2 string `json:"ActionParameter2"` + ExtraActions []PullzoneEdgeruleExtraAction `json:"ExtraActions"` + MatchType uint8 `json:"TriggerMatchingType"` + Triggers []PullzoneEdgeruleTrigger `json:"Triggers"` + PullzoneId int64 `json:"-"` +} + +type PullzoneEdgeruleExtraAction struct { + ActionType uint8 `json:"ActionType"` + ActionParameter1 string `json:"ActionParameter1"` + ActionParameter2 string `json:"ActionParameter2"` } type PullzoneEdgeruleTrigger struct { diff --git a/internal/provider/resource_pullzone_edgerule.go b/internal/provider/resource_pullzone_edgerule.go index 33ddf60..24817fb 100644 --- a/internal/provider/resource_pullzone_edgerule.go +++ b/internal/provider/resource_pullzone_edgerule.go @@ -7,9 +7,12 @@ import ( "context" "fmt" "github.com/bunnyway/terraform-provider-bunnynet/internal/api" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" "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/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" @@ -45,6 +48,7 @@ type PullzoneEdgeruleResourceModel struct { ActionParameter1 types.String `tfsdk:"action_parameter1"` ActionParameter2 types.String `tfsdk:"action_parameter2"` MatchType types.String `tfsdk:"match_type"` + Actions types.List `tfsdk:"actions"` Triggers types.List `tfsdk:"triggers"` } @@ -97,6 +101,14 @@ func (r *PullzoneEdgeruleResource) Metadata(ctx context.Context, req resource.Me resp.TypeName = req.ProviderTypeName + "_pullzone_edgerule" } +var pullzoneEdgeruleActionType = types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "type": types.StringType, + "parameter1": types.StringType, + "parameter2": types.StringType, + }, +} + var pullzoneEdgeruleTriggerType = types.ObjectType{ AttrTypes: map[string]attr.Type{ "type": types.StringType, @@ -135,27 +147,24 @@ func (r *PullzoneEdgeruleResource) Schema(ctx context.Context, req resource.Sche Description: "Indicates whether the edge rule is enabled.", }, "action": schema.StringAttribute{ - Required: true, + Optional: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, Validators: []validator.String{ stringvalidator.OneOf(maps.Values(pullzoneEdgeruleActionMap)...), + stringvalidator.ConflictsWith(path.MatchRoot("actions")), }, MarkdownDescription: generateMarkdownMapOptions(pullzoneEdgeruleActionMap), }, "action_parameter1": schema.StringAttribute{ - Computed: true, Optional: true, - Default: stringdefault.StaticString(""), PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, }, "action_parameter2": schema.StringAttribute{ - Computed: true, Optional: true, - Default: stringdefault.StaticString(""), PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, @@ -181,6 +190,20 @@ func (r *PullzoneEdgeruleResource) Schema(ctx context.Context, req resource.Sche }, MarkdownDescription: generateMarkdownMapOptions(pullzoneEdgeruleMatchTypeMap), }, + "actions": schema.ListAttribute{ + Optional: true, + Computed: true, + ElementType: pullzoneEdgeruleActionType, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ConflictsWith( + path.MatchRoot("action"), + path.MatchRoot("action_parameter1"), + path.MatchRoot("action_parameter2"), + ), + }, + Description: "List of actions for the edge rule.", + }, "triggers": schema.ListAttribute{ Required: true, ElementType: pullzoneEdgeruleTriggerType, @@ -189,6 +212,15 @@ func (r *PullzoneEdgeruleResource) Schema(ctx context.Context, req resource.Sche } } +func (r *PullzoneEdgeruleResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.AtLeastOneOf( + path.MatchRoot("action"), + path.MatchRoot("actions"), + ), + } +} + func (r *PullzoneEdgeruleResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { if req.ProviderData == nil { return @@ -225,7 +257,7 @@ func (r *PullzoneEdgeruleResource) Create(ctx context.Context, req resource.Crea } tflog.Trace(ctx, fmt.Sprintf("created edgerule for pullzone %d", dataApi.PullzoneId)) - dataTf, diags := r.convertApiToModel(dataApi) + dataTf, diags := r.convertApiToModel(dataApi, !dataTf.Action.IsNull()) if diags != nil { resp.Diagnostics.Append(diags...) return @@ -250,7 +282,7 @@ func (r *PullzoneEdgeruleResource) Read(ctx context.Context, req resource.ReadRe return } - dataTf, diags := r.convertApiToModel(dataApi) + dataTf, diags := r.convertApiToModel(dataApi, !data.Action.IsNull()) if diags != nil { resp.Diagnostics.Append(diags...) return @@ -276,7 +308,7 @@ func (r *PullzoneEdgeruleResource) Update(ctx context.Context, req resource.Upda return } - dataTf, diags := r.convertApiToModel(dataApi) + dataTf, diags := r.convertApiToModel(dataApi, !data.Action.IsNull()) if diags != nil { resp.Diagnostics.Append(diags...) return @@ -320,7 +352,7 @@ func (r *PullzoneEdgeruleResource) ImportState(ctx context.Context, req resource return } - dataTf, diags := r.convertApiToModel(edgerule) + dataTf, diags := r.convertApiToModel(edgerule, false) if diags != nil { resp.Diagnostics.Append(diags...) return @@ -334,92 +366,150 @@ func (r *PullzoneEdgeruleResource) convertModelToApi(ctx context.Context, dataTf dataApi.Id = dataTf.Id.ValueString() dataApi.PullzoneId = dataTf.PullzoneId.ValueInt64() dataApi.Enabled = dataTf.Enabled.ValueBool() - dataApi.Action = mapValueToKey(pullzoneEdgeruleActionMap, dataTf.Action.ValueString()) - dataApi.ActionParameter1 = dataTf.ActionParameter1.ValueString() - dataApi.ActionParameter2 = dataTf.ActionParameter2.ValueString() dataApi.Description = dataTf.Description.ValueString() dataApi.MatchType = mapValueToKey(pullzoneEdgeruleMatchTypeMap, dataTf.MatchType.ValueString()) - triggerElements := dataTf.Triggers.Elements() - triggers := make([]api.PullzoneEdgeruleTrigger, len(triggerElements)) - for i, el := range triggerElements { - trigger := el.(types.Object).Attributes() + // actions + { + actionsElements := dataTf.Actions.Elements() + + // action field + if len(actionsElements) == 0 { + dataApi.Action = mapValueToKey(pullzoneEdgeruleActionMap, dataTf.Action.ValueString()) + dataApi.ActionParameter1 = dataTf.ActionParameter1.ValueString() + dataApi.ActionParameter2 = dataTf.ActionParameter2.ValueString() + dataApi.ExtraActions = []api.PullzoneEdgeruleExtraAction{} + } else { + // actions list + actions := make([]api.PullzoneEdgeruleExtraAction, len(actionsElements)) + for i, el := range actionsElements { + action := el.(types.Object).Attributes() + parameter1 := "" + if t, ok := action["parameter1"]; ok && !t.(types.String).IsNull() { + parameter1 = t.(types.String).ValueString() + } + + parameter2 := "" + if t, ok := action["parameter2"]; ok && !t.(types.String).IsNull() { + parameter2 = t.(types.String).ValueString() + } + + actions[i] = api.PullzoneEdgeruleExtraAction{ + ActionType: mapValueToKey(pullzoneEdgeruleActionMap, action["type"].(types.String).ValueString()), + ActionParameter1: parameter1, + ActionParameter2: parameter2, + } + } - patternElements := trigger["patterns"].(types.List).Elements() - patterns := make([]string, len(patternElements)) - for j, pattern := range patternElements { - patterns[j] = pattern.(types.String).ValueString() - } + dataApi.Action = actions[0].ActionType + dataApi.ActionParameter1 = actions[0].ActionParameter1 + dataApi.ActionParameter2 = actions[0].ActionParameter2 - parameter1 := "" - if t, ok := trigger["parameter1"]; ok && !t.(types.String).IsNull() { - parameter1 = t.(types.String).ValueString() + if len(actions) > 1 { + dataApi.ExtraActions = actions[1:] + } } + } - parameter2 := "" - if t, ok := trigger["parameter2"]; ok && !t.(types.String).IsNull() { - parameter2 = t.(types.String).ValueString() - } + // triggers + { + triggerElements := dataTf.Triggers.Elements() + triggers := make([]api.PullzoneEdgeruleTrigger, len(triggerElements)) + for i, el := range triggerElements { + trigger := el.(types.Object).Attributes() + + patternElements := trigger["patterns"].(types.List).Elements() + patterns := make([]string, len(patternElements)) + for j, pattern := range patternElements { + patterns[j] = pattern.(types.String).ValueString() + } + + parameter1 := "" + if t, ok := trigger["parameter1"]; ok && !t.(types.String).IsNull() { + parameter1 = t.(types.String).ValueString() + } - triggers[i] = api.PullzoneEdgeruleTrigger{ - Type: mapValueToKey(pullzoneEdgeruleTriggerTypeMap, trigger["type"].(types.String).ValueString()), - MatchType: mapValueToKey(pullzoneEdgeruleMatchTypeMap, trigger["match_type"].(types.String).ValueString()), - Patterns: patterns, - Parameter1: parameter1, - Parameter2: parameter2, + parameter2 := "" + if t, ok := trigger["parameter2"]; ok && !t.(types.String).IsNull() { + parameter2 = t.(types.String).ValueString() + } + + triggers[i] = api.PullzoneEdgeruleTrigger{ + Type: mapValueToKey(pullzoneEdgeruleTriggerTypeMap, trigger["type"].(types.String).ValueString()), + MatchType: mapValueToKey(pullzoneEdgeruleMatchTypeMap, trigger["match_type"].(types.String).ValueString()), + Patterns: patterns, + Parameter1: parameter1, + Parameter2: parameter2, + } } - } - dataApi.Triggers = triggers + dataApi.Triggers = triggers + } return dataApi } -func (r *PullzoneEdgeruleResource) convertApiToModel(dataApi api.PullzoneEdgerule) (PullzoneEdgeruleResourceModel, diag.Diagnostics) { +func (r *PullzoneEdgeruleResource) convertApiToModel(dataApi api.PullzoneEdgerule, useSingleAction bool) (PullzoneEdgeruleResourceModel, diag.Diagnostics) { dataTf := PullzoneEdgeruleResourceModel{} dataTf.Id = types.StringValue(dataApi.Id) dataTf.PullzoneId = types.Int64Value(dataApi.PullzoneId) dataTf.Enabled = types.BoolValue(dataApi.Enabled) - dataTf.Action = types.StringValue(mapKeyToValue(pullzoneEdgeruleActionMap, dataApi.Action)) - dataTf.ActionParameter1 = types.StringValue(dataApi.ActionParameter1) - dataTf.ActionParameter2 = types.StringValue(dataApi.ActionParameter2) dataTf.Description = types.StringValue(dataApi.Description) dataTf.MatchType = types.StringValue(mapKeyToValue(pullzoneEdgeruleMatchTypeMap, dataApi.MatchType)) - if len(dataApi.Triggers) == 0 { - dataTf.Triggers = types.ListNull(types.ObjectType{}) - } else { - triggers := make([]attr.Value, len(dataApi.Triggers)) + // actions + { + i := 0 + actions := make([]attr.Value, len(dataApi.ExtraActions)+1) + + // main action + { + var parameter1 attr.Value + if dataApi.ActionParameter1 == "" { + parameter1 = types.StringNull() + } else { + parameter1 = types.StringValue(dataApi.ActionParameter1) + } - for i, tr := range dataApi.Triggers { - patterns := make([]attr.Value, len(tr.Patterns)) - for j, value := range tr.Patterns { - patterns[j] = types.StringValue(value) + var parameter2 attr.Value + if dataApi.ActionParameter2 == "" { + parameter2 = types.StringNull() + } else { + parameter2 = types.StringValue(dataApi.ActionParameter2) } - patternsList, diags := types.ListValue(types.StringType, patterns) + actionValue, diags := types.ObjectValue(pullzoneEdgeruleActionType.AttrTypes, map[string]attr.Value{ + "type": types.StringValue(mapKeyToValue(pullzoneEdgeruleActionMap, dataApi.Action)), + "parameter1": parameter1, + "parameter2": parameter2, + }) + if diags != nil { return PullzoneEdgeruleResourceModel{}, diags } + actions[i] = actionValue + i++ + } + + // extra actions + for _, extraAction := range dataApi.ExtraActions { var parameter1 attr.Value - if tr.Parameter1 == "" { + if extraAction.ActionParameter1 == "" { parameter1 = types.StringNull() } else { - parameter1 = types.StringValue(tr.Parameter1) + parameter1 = types.StringValue(extraAction.ActionParameter1) } var parameter2 attr.Value - if tr.Parameter2 == "" { + if extraAction.ActionParameter2 == "" { parameter2 = types.StringNull() } else { - parameter2 = types.StringValue(tr.Parameter2) + parameter2 = types.StringValue(extraAction.ActionParameter2) } - triggerValue, diags := types.ObjectValue(pullzoneEdgeruleTriggerType.AttrTypes, map[string]attr.Value{ - "type": types.StringValue(mapKeyToValue(pullzoneEdgeruleTriggerTypeMap, tr.Type)), - "match_type": types.StringValue(mapKeyToValue(pullzoneEdgeruleMatchTypeMap, tr.MatchType)), - "patterns": patternsList, + actionValue, diags := types.ObjectValue(pullzoneEdgeruleActionType.AttrTypes, map[string]attr.Value{ + "type": types.StringValue(mapKeyToValue(pullzoneEdgeruleActionMap, extraAction.ActionType)), "parameter1": parameter1, "parameter2": parameter2, }) @@ -428,15 +518,80 @@ func (r *PullzoneEdgeruleResource) convertApiToModel(dataApi api.PullzoneEdgerul return PullzoneEdgeruleResourceModel{}, diags } - triggers[i] = triggerValue + actions[i] = actionValue + i++ } - triggersList, diags := types.ListValue(pullzoneEdgeruleTriggerType, triggers) - if diags != nil { - return PullzoneEdgeruleResourceModel{}, diags + if len(actions) == 1 && useSingleAction { + actionAttr := actions[0].(types.Object).Attributes() + dataTf.Action = actionAttr["type"].(types.String) + dataTf.ActionParameter1 = actionAttr["parameter1"].(types.String) + dataTf.ActionParameter2 = actionAttr["parameter2"].(types.String) + dataTf.Actions = types.ListNull(pullzoneEdgeruleActionType) + } else { + actionsList, diags := types.ListValue(pullzoneEdgeruleActionType, actions) + if diags != nil { + return PullzoneEdgeruleResourceModel{}, diags + } + + dataTf.Actions = actionsList } + } - dataTf.Triggers = triggersList + // triggers + { + if len(dataApi.Triggers) == 0 { + dataTf.Triggers = types.ListNull(types.ObjectType{}) + } else { + triggers := make([]attr.Value, len(dataApi.Triggers)) + + for i, tr := range dataApi.Triggers { + patterns := make([]attr.Value, len(tr.Patterns)) + for j, value := range tr.Patterns { + patterns[j] = types.StringValue(value) + } + + patternsList, diags := types.ListValue(types.StringType, patterns) + if diags != nil { + return PullzoneEdgeruleResourceModel{}, diags + } + + var parameter1 attr.Value + if tr.Parameter1 == "" { + parameter1 = types.StringNull() + } else { + parameter1 = types.StringValue(tr.Parameter1) + } + + var parameter2 attr.Value + if tr.Parameter2 == "" { + parameter2 = types.StringNull() + } else { + parameter2 = types.StringValue(tr.Parameter2) + } + + triggerValue, diags := types.ObjectValue(pullzoneEdgeruleTriggerType.AttrTypes, map[string]attr.Value{ + "type": types.StringValue(mapKeyToValue(pullzoneEdgeruleTriggerTypeMap, tr.Type)), + "match_type": types.StringValue(mapKeyToValue(pullzoneEdgeruleMatchTypeMap, tr.MatchType)), + "patterns": patternsList, + "parameter1": parameter1, + "parameter2": parameter2, + }) + + if diags != nil { + return PullzoneEdgeruleResourceModel{}, diags + } + + triggers[i] = triggerValue + } + + triggersList, diags := types.ListValue(pullzoneEdgeruleTriggerType, triggers) + if diags != nil { + return PullzoneEdgeruleResourceModel{}, diags + } + + dataTf.Triggers = triggersList + } } return dataTf, nil