Skip to content

Commit 153c232

Browse files
committed
add waf and route stages
1 parent b8dca3e commit 153c232

File tree

10 files changed

+3303
-0
lines changed

10 files changed

+3303
-0
lines changed

internal/provider/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,9 @@ func Provider(config *Config) plugin.ProviderFunc {
148148
"scaleway_edge_services_head_stage": edgeservices.ResourceHeadStage(),
149149
"scaleway_edge_services_pipeline": edgeservices.ResourcePipeline(),
150150
"scaleway_edge_services_plan": edgeservices.ResourcePlan(),
151+
"scaleway_edge_services_route_stage": edgeservices.ResourceRouteStage(),
151152
"scaleway_edge_services_tls_stage": edgeservices.ResourceTLSStage(),
153+
"scaleway_edge_services_waf_stage": edgeservices.ResourceWAFStage(),
152154
"scaleway_flexible_ip": flexibleip.ResourceIP(),
153155
"scaleway_flexible_ip_mac_address": flexibleip.ResourceMACAddress(),
154156
"scaleway_function": function.ResourceFunction(),
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
package edgeservices
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8+
edgeservices "github.com/scaleway/scaleway-sdk-go/api/edge_services/v1beta1"
9+
"github.com/scaleway/scaleway-sdk-go/scw"
10+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/httperrors"
11+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/account"
12+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/types"
13+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/verify"
14+
)
15+
16+
func ResourceRouteStage() *schema.Resource {
17+
return &schema.Resource{
18+
CreateContext: ResourceRouteStageCreate,
19+
ReadContext: ResourceRouteStageRead,
20+
UpdateContext: ResourceRouteStageUpdate,
21+
DeleteContext: ResourceRouteStageDelete,
22+
Importer: &schema.ResourceImporter{
23+
StateContext: schema.ImportStatePassthroughContext,
24+
},
25+
SchemaVersion: 0,
26+
Schema: map[string]*schema.Schema{
27+
"pipeline_id": {
28+
Type: schema.TypeString,
29+
Required: true,
30+
Description: "The ID of the pipeline",
31+
},
32+
"waf_stage_id": {
33+
Type: schema.TypeString,
34+
Optional: true,
35+
Description: "The ID of the WAF stage HTTP requests should be forwarded to when no rules are matched",
36+
},
37+
"after_position": {
38+
Type: schema.TypeInt,
39+
Optional: true,
40+
Description: "Add rules after the given position",
41+
ConflictsWith: []string{"before_position"},
42+
},
43+
"before_position": {
44+
Type: schema.TypeInt,
45+
Optional: true,
46+
Description: "Add rules before the given position",
47+
ConflictsWith: []string{"after_position"},
48+
},
49+
"rules": {
50+
Type: schema.TypeList,
51+
Optional: true,
52+
Description: "List of rules to be checked against every HTTP request. The first matching rule will forward the request to its specified backend stage. If no rules are matched, the request is forwarded to the WAF stage defined by `waf_stage_id`",
53+
Elem: &schema.Resource{
54+
Schema: map[string]*schema.Schema{
55+
"backend_stage_id": {
56+
Type: schema.TypeString,
57+
Required: true,
58+
Description: "ID of the backend stage that requests matching the rule should be forwarded to",
59+
},
60+
"rule_http_match": {
61+
Type: schema.TypeList,
62+
Optional: true,
63+
Description: "Rule condition to be matched. Requests matching the condition defined here will be directly forwarded to the backend specified by the `backend_stage_id` field. Requests that do not match will be checked by the next rule's condition",
64+
MaxItems: 1,
65+
Elem: &schema.Resource{
66+
Schema: map[string]*schema.Schema{
67+
"method_filters": {
68+
Type: schema.TypeList,
69+
Optional: true,
70+
Computed: true,
71+
Elem: &schema.Schema{
72+
Type: schema.TypeString,
73+
ValidateDiagFunc: verify.ValidateEnum[edgeservices.RuleHTTPMatchMethodFilter](),
74+
},
75+
Description: "HTTP methods to filter for. A request using any of these methods will be considered to match the rule. Possible values are `get`, `post`, `put`, `patch`, `delete`, `head`, `options`. All methods will match if none is provided",
76+
},
77+
"path_filter": {
78+
Type: schema.TypeList,
79+
Optional: true,
80+
MaxItems: 1,
81+
Description: "HTTP URL path to filter for. A request whose path matches the given filter will be considered to match the rule. All paths will match if none is provided",
82+
Elem: &schema.Resource{
83+
Schema: map[string]*schema.Schema{
84+
"path_filter_type": {
85+
Type: schema.TypeString,
86+
Required: true,
87+
ValidateDiagFunc: verify.ValidateEnum[edgeservices.RuleHTTPMatchPathFilterPathFilterType](),
88+
Description: "The type of filter to match for the HTTP URL path. For now, all path filters must be written in regex and use the `regex` type",
89+
},
90+
"value": {
91+
Type: schema.TypeString,
92+
Required: true,
93+
Description: "The value to be matched for the HTTP URL path",
94+
},
95+
},
96+
},
97+
},
98+
},
99+
},
100+
},
101+
},
102+
},
103+
},
104+
"created_at": {
105+
Type: schema.TypeString,
106+
Computed: true,
107+
Description: "The date and time of the creation of the route stage",
108+
},
109+
"updated_at": {
110+
Type: schema.TypeString,
111+
Computed: true,
112+
Description: "The date and time of the last update of the route stage",
113+
},
114+
"project_id": account.ProjectIDSchema(),
115+
},
116+
}
117+
}
118+
119+
func ResourceRouteStageCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
120+
api := NewEdgeServicesAPI(m)
121+
122+
routeStage, err := api.CreateRouteStage(&edgeservices.CreateRouteStageRequest{
123+
PipelineID: d.Get("pipeline_id").(string),
124+
WafStageID: types.ExpandStringPtr(d.Get("waf_stage_id").(string)),
125+
}, scw.WithContext(ctx))
126+
if err != nil {
127+
return diag.FromErr(err)
128+
}
129+
130+
var afterPosition *uint64
131+
if v, ok := d.GetOk("after_position"); ok {
132+
afterPosition = types.ExpandUint64Ptr(v)
133+
} else {
134+
afterPosition = nil
135+
}
136+
137+
var beforePosition *uint64
138+
if v, ok := d.GetOk("before_position"); ok {
139+
beforePosition = types.ExpandUint64Ptr(v)
140+
} else {
141+
beforePosition = nil
142+
}
143+
144+
_, err = api.AddRouteRules(&edgeservices.AddRouteRulesRequest{
145+
RouteStageID: routeStage.ID,
146+
AfterPosition: afterPosition,
147+
BeforePosition: beforePosition,
148+
RouteRules: expandRouteRules(d.Get("rules")),
149+
}, scw.WithContext(ctx))
150+
if err != nil {
151+
return diag.FromErr(err)
152+
}
153+
154+
d.SetId(routeStage.ID)
155+
156+
return ResourceRouteStageRead(ctx, d, m)
157+
}
158+
159+
func ResourceRouteStageRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
160+
api := NewEdgeServicesAPI(m)
161+
162+
routeStage, err := api.GetRouteStage(&edgeservices.GetRouteStageRequest{
163+
RouteStageID: d.Id(),
164+
}, scw.WithContext(ctx))
165+
if err != nil {
166+
if httperrors.Is404(err) {
167+
d.SetId("")
168+
169+
return nil
170+
}
171+
172+
return diag.FromErr(err)
173+
}
174+
175+
_ = d.Set("pipeline_id", routeStage.PipelineID)
176+
_ = d.Set("waf_stage_id", types.FlattenStringPtr(routeStage.WafStageID))
177+
_ = d.Set("created_at", types.FlattenTime(routeStage.CreatedAt))
178+
_ = d.Set("updated_at", types.FlattenTime(routeStage.UpdatedAt))
179+
180+
routeRules, err := api.ListRouteRules(&edgeservices.ListRouteRulesRequest{
181+
RouteStageID: routeStage.ID,
182+
}, scw.WithContext(ctx))
183+
if err != nil {
184+
return diag.FromErr(err)
185+
}
186+
187+
_ = d.Set("rules", flattenRouteRules(routeRules.RouteRules))
188+
189+
return nil
190+
}
191+
192+
func ResourceRouteStageUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
193+
api := NewEdgeServicesAPI(m)
194+
195+
hasChanged := false
196+
197+
updateRequest := &edgeservices.UpdateRouteStageRequest{
198+
RouteStageID: d.Id(),
199+
}
200+
201+
if d.HasChange("waf_stage_id") {
202+
updateRequest.WafStageID = types.ExpandStringPtr(d.Get("waf_stage_id").(string))
203+
hasChanged = true
204+
}
205+
206+
if hasChanged {
207+
_, err := api.UpdateRouteStage(updateRequest, scw.WithContext(ctx))
208+
if err != nil {
209+
return diag.FromErr(err)
210+
}
211+
}
212+
213+
if d.HasChange("rules") {
214+
_, err := api.SetRouteRules(&edgeservices.SetRouteRulesRequest{
215+
RouteStageID: d.Id(),
216+
RouteRules: expandRouteRules(d.Get("rules")),
217+
}, scw.WithContext(ctx))
218+
if err != nil {
219+
return diag.FromErr(err)
220+
}
221+
}
222+
223+
return ResourceRouteStageRead(ctx, d, m)
224+
}
225+
226+
func ResourceRouteStageDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
227+
api := NewEdgeServicesAPI(m)
228+
229+
err := api.DeleteRouteStage(&edgeservices.DeleteRouteStageRequest{
230+
RouteStageID: d.Id(),
231+
}, scw.WithContext(ctx))
232+
if err != nil && !httperrors.Is404(err) {
233+
return diag.FromErr(err)
234+
}
235+
236+
return nil
237+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package edgeservices_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
7+
"github.com/scaleway/terraform-provider-scaleway/v2/internal/acctest"
8+
edgeservicestestfuncs "github.com/scaleway/terraform-provider-scaleway/v2/internal/services/edgeservices/testfuncs"
9+
)
10+
11+
func TestAccEdgeServicesRoute_Basic(t *testing.T) {
12+
tt := acctest.NewTestTools(t)
13+
defer tt.Cleanup()
14+
resource.ParallelTest(t, resource.TestCase{
15+
PreCheck: func() { acctest.PreCheck(t) },
16+
ProviderFactories: tt.ProviderFactories,
17+
CheckDestroy: edgeservicestestfuncs.CheckEdgeServicesRouteDestroy(tt),
18+
Steps: []resource.TestStep{
19+
{
20+
Config: `
21+
resource "scaleway_edge_services_pipeline" "main" {
22+
name = "my-edge-services-pipeline"
23+
description = "pipeline description"
24+
}
25+
26+
resource "scaleway_edge_services_waf_stage" "waf" {
27+
pipeline_id = scaleway_edge_services_pipeline.main.id
28+
mode = "enable"
29+
paranoia_level = 3
30+
}
31+
32+
resource "scaleway_object_bucket" "main" {
33+
name = "test-acc-scaleway-object-bucket-basic-route"
34+
tags = {
35+
foo = "bar"
36+
}
37+
}
38+
39+
resource "scaleway_edge_services_backend_stage" "backend" {
40+
pipeline_id = scaleway_edge_services_pipeline.main.id
41+
s3_backend_config {
42+
bucket_name = scaleway_object_bucket.main.name
43+
bucket_region = "fr-par"
44+
}
45+
}
46+
47+
resource "scaleway_edge_services_route_stage" "main" {
48+
pipeline_id = scaleway_edge_services_pipeline.main.id
49+
waf_stage_id = scaleway_edge_services_waf_stage.waf.id
50+
after_position = 0
51+
52+
rules {
53+
backend_stage_id = scaleway_edge_services_backend_stage.backend.id
54+
55+
rule_http_match {
56+
method_filters = ["get", "post"]
57+
58+
path_filter {
59+
path_filter_type = "regex"
60+
value = ".*"
61+
}
62+
}
63+
}
64+
}
65+
`,
66+
Check: resource.ComposeTestCheckFunc(
67+
edgeservicestestfuncs.CheckEdgeServicesRouteExists(tt, "scaleway_edge_services_route_stage.main"),
68+
resource.TestCheckResourceAttrPair(
69+
"scaleway_edge_services_backend_stage.backend", "id",
70+
"scaleway_edge_services_route_stage.main", "rules.0.backend_stage_id"),
71+
resource.TestCheckResourceAttrPair(
72+
"scaleway_edge_services_waf_stage.waf", "id",
73+
"scaleway_edge_services_route_stage.main", "waf_stage_id"),
74+
resource.TestCheckResourceAttrPair(
75+
"scaleway_edge_services_waf_stage.waf", "id",
76+
"scaleway_edge_services_route_stage.main", "waf_stage_id"),
77+
resource.TestCheckResourceAttr("scaleway_edge_services_route_stage.main", "rules.0.rule_http_match.0.method_filters.0", "get"),
78+
resource.TestCheckResourceAttr("scaleway_edge_services_route_stage.main", "rules.0.rule_http_match.0.method_filters.1", "post"),
79+
resource.TestCheckResourceAttr("scaleway_edge_services_route_stage.main", "rules.0.rule_http_match.0.path_filter.0.path_filter_type", "regex"),
80+
resource.TestCheckResourceAttr("scaleway_edge_services_route_stage.main", "rules.0.rule_http_match.0.path_filter.0.value", ".*"),
81+
resource.TestCheckResourceAttrSet("scaleway_edge_services_route_stage.main", "created_at"),
82+
resource.TestCheckResourceAttrSet("scaleway_edge_services_route_stage.main", "updated_at"),
83+
),
84+
},
85+
},
86+
})
87+
}

0 commit comments

Comments
 (0)