Skip to content

Commit f5d4aa6

Browse files
yfodilremyleone
andauthored
feat(edge_services): add waf and route stages (#2999)
* add waf and route stages * add docs * add ExpandUint64Ptr * lint * remove position fields --------- Co-authored-by: Rémy Léone <[email protected]>
1 parent 3b3dca5 commit f5d4aa6

13 files changed

+3384
-0
lines changed

Diff for: docs/resources/edge_services_route_stage.md

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
subcategory: "Edge Services"
3+
page_title: "Scaleway: scaleway_edge_services_route_stage"
4+
---
5+
6+
# Resource: scaleway_edge_services_route_stage
7+
8+
Creates and manages Scaleway Edge Services Route Stages.
9+
10+
## Example Usage
11+
12+
### Basic
13+
14+
```terraform
15+
resource "scaleway_edge_services_route_stage" "main" {
16+
pipeline_id = scaleway_edge_services_pipeline.main.id
17+
waf_stage_id = scaleway_edge_services_waf_stage.waf.id
18+
19+
rule {
20+
backend_stage_id = scaleway_edge_services_backend_stage.backend.id
21+
rule_http_match {
22+
method_filters = ["get", "post"]
23+
path_filter {
24+
path_filter_type = "regex"
25+
value = ".*"
26+
}
27+
}
28+
}
29+
}
30+
```
31+
32+
## Argument Reference
33+
34+
- `pipeline_id` - (Required) The ID of the pipeline.
35+
- `waf_stage_id` - (Optional) The ID of the WAF stage HTTP requests should be forwarded to when no rules are matched.
36+
- `rule` - (Optional) The 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`.
37+
- `backend_stage_id` (Required) The ID of the backend stage that requests matching the rule should be forwarded to.
38+
- `rule_http_match` (Optional) The 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.
39+
- `method_filters` (Optional) 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.
40+
- `path_filter` (Optional) 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.
41+
- `path_filter_type` (Required) 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.
42+
- `value` (Required) The value to be matched for the HTTP URL path.
43+
- `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the project the route stage is associated with.
44+
45+
## Attributes Reference
46+
47+
In addition to all arguments above, the following attributes are exported:
48+
49+
- `id` - The ID of the route stage (UUID format).
50+
- `created_at` - The date and time of the creation of the route stage.
51+
- `updated_at` - The date and time of the last update of the route stage.
52+
53+
## Import
54+
55+
Route stages can be imported using the `{id}`, e.g.
56+
57+
```bash
58+
$ terraform import scaleway_edge_services_route_stage.basic 11111111-1111-1111-1111-111111111111
59+
```

Diff for: docs/resources/edge_services_waf_stage.md

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
subcategory: "Edge Services"
3+
page_title: "Scaleway: scaleway_edge_services_waf_stage"
4+
---
5+
6+
# Resource: scaleway_edge_services_waf_stage
7+
8+
Creates and manages Scaleway Edge Services WAF Stages.
9+
10+
## Example Usage
11+
12+
### Basic
13+
14+
```terraform
15+
resource "scaleway_edge_services_waf_stage" "main" {
16+
pipeline_id = scaleway_edge_services_pipeline.main.id
17+
mode = "enable"
18+
paranoia_level = 3
19+
}
20+
```
21+
22+
## Argument Reference
23+
24+
- `pipeline_id` - (Required) The ID of the pipeline.
25+
- `paranoia_level` - (Required) The sensitivity level (`1`,`2`,`3`,`4`) to use when classifying requests as malicious. With a high level, requests are more likely to be classed as malicious, and false positives are expected. With a lower level, requests are more likely to be classed as benign.
26+
- `backend_stage_id` - (Optional) The ID of the backend stage to forward requests to after the WAF stage.
27+
- `mode` - (Optional) The mode defining WAF behavior (`disable`/`log_only`/`enable`).
28+
- `project_id` - (Defaults to [provider](../index.md#project_id) `project_id`) The ID of the project the WAF stage is associated with.
29+
30+
## Attributes Reference
31+
32+
In addition to all arguments above, the following attributes are exported:
33+
34+
- `id` - The ID of the WAF stage (UUID format).
35+
- `created_at` - The date and time of the creation of the WAF stage.
36+
- `updated_at` - The date and time of the last update of the WAF stage.
37+
38+
## Import
39+
40+
WAF stages can be imported using the `{id}`, e.g.
41+
42+
```bash
43+
$ terraform import scaleway_edge_services_waf_stage.basic 11111111-1111-1111-1111-111111111111
44+
```

Diff for: internal/provider/provider.go

+2
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(),

Diff for: internal/services/edgeservices/route_stage.go

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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+
"rule": {
38+
Type: schema.TypeList,
39+
Optional: true,
40+
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`",
41+
Elem: &schema.Resource{
42+
Schema: map[string]*schema.Schema{
43+
"backend_stage_id": {
44+
Type: schema.TypeString,
45+
Required: true,
46+
Description: "ID of the backend stage that requests matching the rule should be forwarded to",
47+
},
48+
"rule_http_match": {
49+
Type: schema.TypeList,
50+
Optional: true,
51+
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",
52+
MaxItems: 1,
53+
Elem: &schema.Resource{
54+
Schema: map[string]*schema.Schema{
55+
"method_filters": {
56+
Type: schema.TypeList,
57+
Optional: true,
58+
Computed: true,
59+
Elem: &schema.Schema{
60+
Type: schema.TypeString,
61+
ValidateDiagFunc: verify.ValidateEnum[edgeservices.RuleHTTPMatchMethodFilter](),
62+
},
63+
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",
64+
},
65+
"path_filter": {
66+
Type: schema.TypeList,
67+
Optional: true,
68+
MaxItems: 1,
69+
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",
70+
Elem: &schema.Resource{
71+
Schema: map[string]*schema.Schema{
72+
"path_filter_type": {
73+
Type: schema.TypeString,
74+
Required: true,
75+
ValidateDiagFunc: verify.ValidateEnum[edgeservices.RuleHTTPMatchPathFilterPathFilterType](),
76+
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",
77+
},
78+
"value": {
79+
Type: schema.TypeString,
80+
Required: true,
81+
Description: "The value to be matched for the HTTP URL path",
82+
},
83+
},
84+
},
85+
},
86+
},
87+
},
88+
},
89+
},
90+
},
91+
},
92+
"created_at": {
93+
Type: schema.TypeString,
94+
Computed: true,
95+
Description: "The date and time of the creation of the route stage",
96+
},
97+
"updated_at": {
98+
Type: schema.TypeString,
99+
Computed: true,
100+
Description: "The date and time of the last update of the route stage",
101+
},
102+
"project_id": account.ProjectIDSchema(),
103+
},
104+
}
105+
}
106+
107+
func ResourceRouteStageCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
108+
api := NewEdgeServicesAPI(m)
109+
110+
routeStage, err := api.CreateRouteStage(&edgeservices.CreateRouteStageRequest{
111+
PipelineID: d.Get("pipeline_id").(string),
112+
WafStageID: types.ExpandStringPtr(d.Get("waf_stage_id").(string)),
113+
}, scw.WithContext(ctx))
114+
if err != nil {
115+
return diag.FromErr(err)
116+
}
117+
118+
_, err = api.SetRouteRules(&edgeservices.SetRouteRulesRequest{
119+
RouteStageID: routeStage.ID,
120+
RouteRules: expandRouteRules(d.Get("rule")),
121+
}, scw.WithContext(ctx))
122+
if err != nil {
123+
return diag.FromErr(err)
124+
}
125+
126+
d.SetId(routeStage.ID)
127+
128+
return ResourceRouteStageRead(ctx, d, m)
129+
}
130+
131+
func ResourceRouteStageRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
132+
api := NewEdgeServicesAPI(m)
133+
134+
routeStage, err := api.GetRouteStage(&edgeservices.GetRouteStageRequest{
135+
RouteStageID: d.Id(),
136+
}, scw.WithContext(ctx))
137+
if err != nil {
138+
if httperrors.Is404(err) {
139+
d.SetId("")
140+
141+
return nil
142+
}
143+
144+
return diag.FromErr(err)
145+
}
146+
147+
_ = d.Set("pipeline_id", routeStage.PipelineID)
148+
_ = d.Set("waf_stage_id", types.FlattenStringPtr(routeStage.WafStageID))
149+
_ = d.Set("created_at", types.FlattenTime(routeStage.CreatedAt))
150+
_ = d.Set("updated_at", types.FlattenTime(routeStage.UpdatedAt))
151+
152+
routeRules, err := api.ListRouteRules(&edgeservices.ListRouteRulesRequest{
153+
RouteStageID: routeStage.ID,
154+
}, scw.WithContext(ctx))
155+
if err != nil {
156+
return diag.FromErr(err)
157+
}
158+
159+
_ = d.Set("rule", flattenRouteRules(routeRules.RouteRules))
160+
161+
return nil
162+
}
163+
164+
func ResourceRouteStageUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
165+
api := NewEdgeServicesAPI(m)
166+
167+
hasChanged := false
168+
169+
updateRequest := &edgeservices.UpdateRouteStageRequest{
170+
RouteStageID: d.Id(),
171+
}
172+
173+
if d.HasChange("waf_stage_id") {
174+
updateRequest.WafStageID = types.ExpandStringPtr(d.Get("waf_stage_id").(string))
175+
hasChanged = true
176+
}
177+
178+
if hasChanged {
179+
_, err := api.UpdateRouteStage(updateRequest, scw.WithContext(ctx))
180+
if err != nil {
181+
return diag.FromErr(err)
182+
}
183+
}
184+
185+
if d.HasChange("rule") {
186+
_, err := api.SetRouteRules(&edgeservices.SetRouteRulesRequest{
187+
RouteStageID: d.Id(),
188+
RouteRules: expandRouteRules(d.Get("rule")),
189+
}, scw.WithContext(ctx))
190+
if err != nil {
191+
return diag.FromErr(err)
192+
}
193+
}
194+
195+
return ResourceRouteStageRead(ctx, d, m)
196+
}
197+
198+
func ResourceRouteStageDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
199+
api := NewEdgeServicesAPI(m)
200+
201+
err := api.DeleteRouteStage(&edgeservices.DeleteRouteStageRequest{
202+
RouteStageID: d.Id(),
203+
}, scw.WithContext(ctx))
204+
if err != nil && !httperrors.Is404(err) {
205+
return diag.FromErr(err)
206+
}
207+
208+
return nil
209+
}

0 commit comments

Comments
 (0)