Skip to content

Commit 7c989aa

Browse files
author
Matheus Politano
committed
feat: add redirects in convertConfig
1 parent 2adf8f0 commit 7c989aa

File tree

2 files changed

+286
-30
lines changed

2 files changed

+286
-30
lines changed

stackit/internal/services/cdn/distribution/resource.go

Lines changed: 207 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"time"
1010

1111
"github.com/google/uuid"
12+
"github.com/hashicorp/terraform-plugin-framework-validators/int32validator"
13+
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
1214
"github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator"
1315
"github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator"
1416
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
@@ -17,8 +19,10 @@ import (
1719
"github.com/hashicorp/terraform-plugin-framework/path"
1820
"github.com/hashicorp/terraform-plugin-framework/resource"
1921
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
22+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
2023
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault"
2124
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
25+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
2226
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
2327
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
2428
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -43,29 +47,38 @@ var (
4347
)
4448

4549
var schemaDescriptions = map[string]string{
46-
"id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`distribution_id`\".",
47-
"distribution_id": "CDN distribution ID",
48-
"project_id": "STACKIT project ID associated with the distribution",
49-
"status": "Status of the distribution",
50-
"created_at": "Time when the distribution was created",
51-
"updated_at": "Time when the distribution was last updated",
52-
"errors": "List of distribution errors",
53-
"domains": "List of configured domains for the distribution",
54-
"config": "The distribution configuration",
55-
"config_backend": "The configured backend for the distribution",
56-
"config_regions": "The configured regions where content will be hosted",
57-
"config_backend_type": "The configured backend type. ",
58-
"config_optimizer": "Configuration for the Image Optimizer. This is a paid feature that automatically optimizes images to reduce their file size for faster delivery, leading to improved website performance and a better user experience.",
59-
"config_backend_origin_url": "The configured backend type http for the distribution",
60-
"config_backend_origin_request_headers": "The configured type http origin request headers for the backend",
61-
"config_backend_geofencing": "The configured type http to configure countries where content is allowed. A map of URLs to a list of countries",
62-
"config_blocked_countries": "The configured countries where distribution of content is blocked",
63-
"domain_name": "The name of the domain",
64-
"domain_status": "The status of the domain",
65-
"domain_type": "The type of the domain. Each distribution has one domain of type \"managed\", and domains of type \"custom\" may be additionally created by the user",
66-
"domain_errors": "List of domain errors",
67-
"config_backend_bucket_url": "The URL of the bucket (e.g. https://s3.example.com). Required if type is 'bucket'.",
68-
"config_backend_region": "The region where the bucket is hosted. Required if type is 'bucket'.",
50+
"id": "Terraform's internal resource identifier. It is structured as \"`project_id`,`distribution_id`\".",
51+
"distribution_id": "CDN distribution ID",
52+
"project_id": "STACKIT project ID associated with the distribution",
53+
"status": "Status of the distribution",
54+
"created_at": "Time when the distribution was created",
55+
"updated_at": "Time when the distribution was last updated",
56+
"errors": "List of distribution errors",
57+
"domains": "List of configured domains for the distribution",
58+
"config": "The distribution configuration",
59+
"config_backend": "The configured backend for the distribution",
60+
"config_regions": "The configured regions where content will be hosted",
61+
"config_backend_type": "The configured backend type. ",
62+
"config_optimizer": "Configuration for the Image Optimizer. This is a paid feature that automatically optimizes images to reduce their file size for faster delivery, leading to improved website performance and a better user experience.",
63+
"config_backend_origin_url": "The configured backend type http for the distribution",
64+
"config_backend_origin_request_headers": "The configured type http origin request headers for the backend",
65+
"config_backend_geofencing": "The configured type http to configure countries where content is allowed. A map of URLs to a list of countries",
66+
"config_blocked_countries": "The configured countries where distribution of content is blocked",
67+
"config_redirects": "A wrapper for a list of redirect rules that allows for redirect settings on a distribution",
68+
"config_redirects_rules": "A list of redirect rules. The order of rules matters for evaluation",
69+
"config_redirects_rule_description": "An optional description for the redirect rule",
70+
"config_redirects_rule_enabled": "A toggle to enable or disable the redirect rule. Default to true",
71+
"config_redirects_rule_target_url": "The target URL to redirect to. Must be a valid URI",
72+
"config_redirects_rule_status_code": "The HTTP status code for the redirect. Must be one of 301, 302, 303, 307, or 308.",
73+
"config_redirects_rule_matchers": "A list of matchers that define when this rule should apply. At least one matcher is required",
74+
"config_redirects_rule_matcher_values": "A list of glob patterns to match against the request path. At least one value is required. Examples: \"/shop/*\" or \"*/img/*\"",
75+
"config_redirects_rule_match_condition": "Defines how multiple matchers within this rule are combined (ALL, ANY, NONE). Defaults to ANY.",
76+
"domain_name": "The name of the domain",
77+
"domain_status": "The status of the domain",
78+
"domain_type": "The type of the domain. Each distribution has one domain of type \"managed\", and domains of type \"custom\" may be additionally created by the user",
79+
"domain_errors": "List of domain errors",
80+
"config_backend_bucket_url": "The URL of the bucket (e.g. https://s3.example.com). Required if type is 'bucket'.",
81+
"config_backend_region": "The region where the bucket is hosted. Required if type is 'bucket'.",
6982
"config_backend_credentials_access_key_id": "The access key for the bucket. Required if type is 'bucket'.",
7083
"config_backend_credentials_secret_access_key": "The secret key for the bucket. Required if type is 'bucket'.",
7184
"config_backend_credentials": "The credentials for the bucket. Required if type is 'bucket'.",
@@ -83,19 +96,38 @@ type Model struct {
8396
Config types.Object `tfsdk:"config"` // the configuration of the distribution
8497
}
8598

99+
type matcher struct {
100+
Values []string `tfsdk:"values"`
101+
ValueMatchCondition *string `tfsdk:"value_match_condition"`
102+
}
103+
104+
type redirectRule struct {
105+
Description *string `tfsdk:"description"`
106+
Enabled *bool `tfsdk:"enabled"`
107+
TargetUrl string `tfsdk:"target_url"`
108+
StatusCode int32 `tfsdk:"status_code"`
109+
Matchers []matcher `tfsdk:"matchers"`
110+
RuleMatchCondition *string `tfsdk:"rule_match_condition"`
111+
}
112+
113+
type redirectConfig struct {
114+
Rules []redirectRule `tfsdk:"rules"`
115+
}
116+
86117
type distributionConfig struct {
87-
Backend backend `tfsdk:"backend"` // The backend associated with the distribution
88-
Regions *[]string `tfsdk:"regions"` // The regions in which data will be cached
89-
BlockedCountries *[]string `tfsdk:"blocked_countries"` // The countries for which content will be blocked
90-
Optimizer types.Object `tfsdk:"optimizer"` // The optimizer configuration
118+
Backend backend `tfsdk:"backend"` // The backend associated with the distribution
119+
Redirects *redirectConfig `tfsdk:"redirects"` // A wrapper for a list of redirect rules that allows for redirect settings on a distribution
120+
Regions *[]string `tfsdk:"regions"` // The regions in which data will be cached
121+
BlockedCountries *[]string `tfsdk:"blocked_countries"` // The countries for which content will be blocked
122+
Optimizer types.Object `tfsdk:"optimizer"` // The optimizer configuration
91123
}
92124

93125
type optimizerConfig struct {
94126
Enabled types.Bool `tfsdk:"enabled"`
95127
}
96128

97129
type backend struct {
98-
Type string `tfsdk:"type"` // The type of the backend. Currently, only "http" backend is supported
130+
Type string `tfsdk:"type"` // The type of the backend. Currently, only "http" and "bucket" backend is supported
99131
OriginURL *string `tfsdk:"origin_url"` // The origin URL of the backend
100132
OriginRequestHeaders *map[string]string `tfsdk:"origin_request_headers"` // Request headers that should be added by the CDN distribution to incoming requests
101133
Geofencing *map[string][]*string `tfsdk:"geofencing"` // The geofencing is an object mapping multiple alternative origins to country codes.
@@ -116,6 +148,9 @@ var configTypes = map[string]attr.Type{
116148
"optimizer": types.ObjectType{
117149
AttrTypes: optimizerTypes,
118150
},
151+
"redirects": types.ObjectType{
152+
AttrTypes: redirectsTypes,
153+
},
119154
}
120155

121156
var optimizerTypes = map[string]attr.Type{
@@ -126,6 +161,32 @@ var geofencingTypes = types.MapType{ElemType: types.ListType{
126161
ElemType: types.StringType,
127162
}}
128163

164+
var matcherTypes = map[string]attr.Type{
165+
"values": types.ListType{ElemType: types.StringType},
166+
"value_match_condition": types.StringType,
167+
}
168+
169+
var redirectRuleTypes = map[string]attr.Type{
170+
"description": types.StringType,
171+
"enabled": types.BoolType,
172+
"target_url": types.StringType,
173+
"status_code": types.Int32Type,
174+
"rule_match_condition": types.StringType,
175+
"matchers": types.ListType{
176+
ElemType: types.ObjectType{
177+
AttrTypes: matcherTypes,
178+
},
179+
},
180+
}
181+
182+
var redirectsTypes = map[string]attr.Type{
183+
"rules": types.ListType{
184+
ElemType: types.ObjectType{
185+
AttrTypes: redirectRuleTypes,
186+
},
187+
},
188+
}
189+
129190
var backendTypes = map[string]attr.Type{
130191
"type": types.StringType,
131192
"origin_url": types.StringType,
@@ -183,6 +244,8 @@ func (r *distributionResource) Metadata(_ context.Context, req resource.Metadata
183244

184245
func (r *distributionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
185246
backendOptions := []string{"http", "bucket"}
247+
matchCondition := []string{"ANY", "ALL", "NONE"}
248+
statusCode := []int32{301, 302, 303, 307, 308}
186249
resp.Schema = schema.Schema{
187250
MarkdownDescription: features.AddBetaDescription("CDN distribution data source schema.", core.Resource),
188251
Description: "CDN distribution data source schema.",
@@ -267,6 +330,73 @@ func (r *distributionResource) Schema(_ context.Context, _ resource.SchemaReques
267330
objectvalidator.AlsoRequires(path.MatchRelative().AtName("enabled")),
268331
},
269332
},
333+
"redirects": schema.SingleNestedAttribute{
334+
Required: true,
335+
Description: schemaDescriptions["config_redirects"],
336+
Attributes: map[string]schema.Attribute{
337+
"rules": schema.ListNestedAttribute{
338+
Description: schemaDescriptions["config_redirects_rules"],
339+
Required: true,
340+
Validators: []validator.List{
341+
listvalidator.SizeAtLeast(1),
342+
},
343+
NestedObject: schema.NestedAttributeObject{
344+
Attributes: map[string]schema.Attribute{
345+
"description": schema.StringAttribute{
346+
Description: schemaDescriptions["config_redirects_rule_description"],
347+
Optional: true,
348+
},
349+
"enabled": schema.BoolAttribute{
350+
Optional: true,
351+
Computed: true,
352+
Description: schemaDescriptions["config_redirects_rule_enabled"],
353+
Default: booldefault.StaticBool(true),
354+
},
355+
"targetUrl": schema.StringAttribute{
356+
Required: true,
357+
Description: schemaDescriptions["config_redirects_rule_target_url"],
358+
},
359+
"statusCode": schema.Int32Attribute{
360+
Required: true,
361+
Description: schemaDescriptions["config_redirects_rule_status_code"],
362+
Validators: []validator.Int32{int32validator.OneOf(statusCode...)},
363+
},
364+
"ruleMatchCondition": schema.StringAttribute{
365+
Optional: true,
366+
Computed: true,
367+
Description: schemaDescriptions["config_redirects_rule_match_condition"],
368+
Default: stringdefault.StaticString("ANY"),
369+
Validators: []validator.String{stringvalidator.OneOfCaseInsensitive(matchCondition...)},
370+
},
371+
"matchers": schema.ListNestedAttribute{
372+
Description: schemaDescriptions["config_redirects_rule_matchers"],
373+
Required: true,
374+
Validators: []validator.List{
375+
listvalidator.SizeAtLeast(1),
376+
},
377+
NestedObject: schema.NestedAttributeObject{
378+
Attributes: map[string]schema.Attribute{
379+
"values": schema.ListAttribute{
380+
Description: schemaDescriptions["config_redirects_rule_matcher_values"],
381+
Required: true,
382+
Validators: []validator.List{
383+
listvalidator.SizeAtLeast(1),
384+
},
385+
},
386+
"ruleMatchCondition": schema.StringAttribute{
387+
Optional: true,
388+
Description: schemaDescriptions["config_redirects_rule_match_condition"],
389+
Default: stringdefault.StaticString("ANY"),
390+
Computed: true,
391+
Validators: []validator.String{stringvalidator.OneOfCaseInsensitive(matchCondition...)},
392+
},
393+
},
394+
},
395+
}},
396+
},
397+
},
398+
},
399+
},
270400
"backend": schema.SingleNestedAttribute{
271401
Required: true,
272402
Description: schemaDescriptions["config_backend"],
@@ -1023,6 +1153,7 @@ func convertConfig(ctx context.Context, model *Model) (*cdn.Config, error) {
10231153
if model == nil {
10241154
return nil, errors.New("model cannot be nil")
10251155
}
1156+
10261157
if model.Config.IsNull() || model.Config.IsUnknown() {
10271158
return nil, errors.New("config cannot be nil or unknown")
10281159
}
@@ -1057,6 +1188,53 @@ func convertConfig(ctx context.Context, model *Model) (*cdn.Config, error) {
10571188
}
10581189
}
10591190

1191+
// redirects
1192+
var redirectsConfig *cdn.RedirectConfig
1193+
1194+
if configModel.Redirects != nil {
1195+
sdkRules := []cdn.RedirectRule{}
1196+
1197+
if len(configModel.Redirects.Rules) > 0 {
1198+
for _, rule := range configModel.Redirects.Rules {
1199+
matchers := []cdn.Matcher{}
1200+
for _, matcher := range rule.Matchers {
1201+
var matchCond *cdn.MatchCondition
1202+
if matcher.ValueMatchCondition != nil {
1203+
cond := cdn.MatchCondition(*matcher.ValueMatchCondition)
1204+
matchCond = &cond
1205+
}
1206+
1207+
matchers = append(matchers, cdn.Matcher{
1208+
Values: &matcher.Values,
1209+
ValueMatchCondition: matchCond,
1210+
})
1211+
}
1212+
1213+
var ruleMatchCond *cdn.MatchCondition
1214+
if rule.RuleMatchCondition != nil {
1215+
cond := cdn.MatchCondition(*rule.RuleMatchCondition)
1216+
ruleMatchCond = &cond
1217+
}
1218+
1219+
statusCode := cdn.RedirectRuleStatusCode(rule.StatusCode)
1220+
targerUrl := rule.TargetUrl
1221+
1222+
sdkConfigRule := cdn.RedirectRule{
1223+
Description: rule.Description,
1224+
Enabled: rule.Enabled,
1225+
Matchers: &matchers,
1226+
RuleMatchCondition: ruleMatchCond,
1227+
StatusCode: &statusCode,
1228+
TargetUrl: &targerUrl,
1229+
}
1230+
sdkRules = append(sdkRules, sdkConfigRule)
1231+
}
1232+
}
1233+
redirectsConfig = &cdn.RedirectConfig{
1234+
Rules: &sdkRules,
1235+
}
1236+
}
1237+
10601238
// geofencing
10611239
geofencing := map[string][]string{}
10621240
if configModel.Backend.Geofencing != nil {
@@ -1080,6 +1258,7 @@ func convertConfig(ctx context.Context, model *Model) (*cdn.Config, error) {
10801258
Backend: &cdn.ConfigBackend{},
10811259
Regions: &regions,
10821260
BlockedCountries: &blockedCountries,
1261+
Redirects: redirectsConfig,
10831262
}
10841263

10851264
if configModel.Backend.Type == "http" {

0 commit comments

Comments
 (0)