diff --git a/docs/data-sources/cdn_distribution.md b/docs/data-sources/cdn_distribution.md
index a4f28cc7..84791109 100644
--- a/docs/data-sources/cdn_distribution.md
+++ b/docs/data-sources/cdn_distribution.md
@@ -43,6 +43,10 @@ data "stackit_cdn_distribution" "example" {
### Nested Schema for `config`
+Optional:
+
+- `blocked_countries` (List of String) The configured countries where distribution of content is blocked
+
Read-Only:
- `backend` (Attributes) The configured backend for the distribution (see [below for nested schema](#nestedatt--config--backend))
diff --git a/docs/guides/stackit_cdn_with_custom_domain.md b/docs/guides/stackit_cdn_with_custom_domain.md
index aa915136..0c90a88d 100644
--- a/docs/guides/stackit_cdn_with_custom_domain.md
+++ b/docs/guides/stackit_cdn_with_custom_domain.md
@@ -21,7 +21,8 @@ This guide outlines the process of creating a STACKIT CDN distribution and confi
type = "http"
origin_url = "mybackend.onstackit.cloud"
}
- regions = ["EU", "US", "ASIA", "AF", "SA"]
+ regions = ["EU", "US", "ASIA", "AF", "SA"]
+ blocked_countries = ["DE", "AT", "CH"]
}
}
diff --git a/docs/resources/cdn_distribution.md b/docs/resources/cdn_distribution.md
index 5a52fd20..c9ad2688 100644
--- a/docs/resources/cdn_distribution.md
+++ b/docs/resources/cdn_distribution.md
@@ -23,7 +23,9 @@ resource "stackit_cdn_distribution" "example_distribution" {
type = "http"
origin_url = "mybackend.onstackit.cloud"
}
- regions = ["EU", "US", "ASIA", "AF", "SA"]
+ regions = ["EU", "US", "ASIA", "AF", "SA"]
+ blocked_countries = ["DE", "AT", "CH"]
+
optimizer = {
enabled = true
}
@@ -59,6 +61,7 @@ Required:
Optional:
+- `blocked_countries` (List of String) The configured countries where distribution of content is blocked
- `optimizer` (Attributes) 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. (see [below for nested schema](#nestedatt--config--optimizer))
diff --git a/examples/resources/stackit_cdn_distribution/resource.tf b/examples/resources/stackit_cdn_distribution/resource.tf
index 39565b22..ebdae25e 100644
--- a/examples/resources/stackit_cdn_distribution/resource.tf
+++ b/examples/resources/stackit_cdn_distribution/resource.tf
@@ -5,7 +5,9 @@ resource "stackit_cdn_distribution" "example_distribution" {
type = "http"
origin_url = "mybackend.onstackit.cloud"
}
- regions = ["EU", "US", "ASIA", "AF", "SA"]
+ regions = ["EU", "US", "ASIA", "AF", "SA"]
+ blocked_countries = ["DE", "AT", "CH"]
+
optimizer = {
enabled = true
}
diff --git a/stackit/internal/services/cdn/cdn_acc_test.go b/stackit/internal/services/cdn/cdn_acc_test.go
index 43d24520..b723f5d8 100644
--- a/stackit/internal/services/cdn/cdn_acc_test.go
+++ b/stackit/internal/services/cdn/cdn_acc_test.go
@@ -24,6 +24,7 @@ var instanceResource = map[string]string{
"config_backend_origin_url": "https://test-backend-1.cdn-dev.runs.onstackit.cloud",
"config_regions": "\"EU\", \"US\"",
"config_regions_updated": "\"EU\", \"US\", \"ASIA\"",
+ "blocked_countries": "\"CU\", \"AQ\"", // Do NOT use DE or AT here, because the request might be blocked by bunny at the time of creation - don't lock yourself out
"custom_domain_prefix": uuid.NewString(), // we use a different domain prefix each test run due to inconsistent upstream release of domains, which might impair consecutive test runs
}
@@ -38,7 +39,9 @@ func configResources(regions string) string {
type = "http"
origin_url = "%s"
}
- regions = [%s]
+ regions = [%s]
+ blocked_countries = [%s]
+
optimizer = {
enabled = true
}
@@ -60,7 +63,9 @@ func configResources(regions string) string {
type = "CNAME"
records = ["${stackit_cdn_distribution.distribution.domains[0].name}."]
}
- `, testutil.CdnProviderConfig(), testutil.ProjectId, instanceResource["config_backend_origin_url"], regions, testutil.ProjectId, testutil.ProjectId, instanceResource["custom_domain_prefix"])
+ `, testutil.CdnProviderConfig(), testutil.ProjectId, instanceResource["config_backend_origin_url"],
+ regions, instanceResource["blocked_countries"], testutil.ProjectId,
+ testutil.ProjectId, instanceResource["custom_domain_prefix"])
}
func configCustomDomainResources(regions string) string {
@@ -111,6 +116,9 @@ func TestAccCDNDistributionResource(t *testing.T) {
resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.regions.#", "2"),
resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.regions.0", "EU"),
resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.regions.1", "US"),
+ resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_countries.#", "2"),
+ resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_countries.0", "CU"),
+ resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_countries.1", "AQ"),
resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.optimizer.enabled", "true"),
resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "project_id", testutil.ProjectId),
resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "status", "ACTIVE"),
@@ -191,6 +199,9 @@ func TestAccCDNDistributionResource(t *testing.T) {
resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.regions.#", "2"),
resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.regions.0", "EU"),
resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.regions.1", "US"),
+ resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_countries.#", "2"),
+ resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_countries.0", "CU"),
+ resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_countries.1", "AQ"),
resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.optimizer.enabled", "true"),
resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "project_id", testutil.ProjectId),
resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "status", "ACTIVE"),
@@ -217,6 +228,9 @@ func TestAccCDNDistributionResource(t *testing.T) {
resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.regions.0", "EU"),
resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.regions.1", "US"),
resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.regions.2", "ASIA"),
+ resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_countries.#", "2"),
+ resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_countries.0", "CU"),
+ resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_countries.1", "AQ"),
resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.optimizer.enabled", "true"),
resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "project_id", testutil.ProjectId),
resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "status", "ACTIVE"),
diff --git a/stackit/internal/services/cdn/distribution/datasource.go b/stackit/internal/services/cdn/distribution/datasource.go
index 09afc4ff..590af144 100644
--- a/stackit/internal/services/cdn/distribution/datasource.go
+++ b/stackit/internal/services/cdn/distribution/datasource.go
@@ -149,6 +149,11 @@ func (r *distributionDataSource) Schema(_ context.Context, _ datasource.SchemaRe
Description: schemaDescriptions["config_regions"],
ElementType: types.StringType,
},
+ "blocked_countries": schema.ListAttribute{
+ Optional: true,
+ Description: schemaDescriptions["config_blocked_countries"],
+ ElementType: types.StringType,
+ },
"optimizer": schema.SingleNestedAttribute{
Description: schemaDescriptions["config_optimizer"],
Computed: true,
diff --git a/stackit/internal/services/cdn/distribution/resource.go b/stackit/internal/services/cdn/distribution/resource.go
index d1cc4e33..8c4125f7 100644
--- a/stackit/internal/services/cdn/distribution/resource.go
+++ b/stackit/internal/services/cdn/distribution/resource.go
@@ -57,6 +57,7 @@ var schemaDescriptions = map[string]string{
"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.",
"config_backend_origin_url": "The configured backend type for the distribution",
"config_backend_origin_request_headers": "The configured origin request headers for the backend",
+ "config_blocked_countries": "The configured countries where distribution of content is blocked",
"domain_name": "The name of the domain",
"domain_status": "The status of the domain",
"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",
@@ -76,13 +77,16 @@ type Model struct {
}
type distributionConfig struct {
- Backend backend `tfsdk:"backend"` // The backend associated with the distribution
- Regions *[]string `tfsdk:"regions"` // The regions in which data will be cached
- Optimizer types.Object `tfsdk:"optimizer"` // The optimizer configuration
+ Backend backend `tfsdk:"backend"` // The backend associated with the distribution
+ Regions *[]string `tfsdk:"regions"` // The regions in which data will be cached
+ BlockedCountries *[]string `tfsdk:"blocked_countries"` // The countries for which content will be blocked
+ Optimizer types.Object `tfsdk:"optimizer"` // The optimizer configuration
}
+
type optimizerConfig struct {
Enabled types.Bool `tfsdk:"enabled"`
}
+
type backend struct {
Type string `tfsdk:"type"` // The type of the backend. Currently, only "http" backend is supported
OriginURL string `tfsdk:"origin_url"` // The origin URL of the backend
@@ -90,8 +94,9 @@ type backend struct {
}
var configTypes = map[string]attr.Type{
- "backend": types.ObjectType{AttrTypes: backendTypes},
- "regions": types.ListType{ElemType: types.StringType},
+ "backend": types.ObjectType{AttrTypes: backendTypes},
+ "regions": types.ListType{ElemType: types.StringType},
+ "blocked_countries": types.ListType{ElemType: types.StringType},
"optimizer": types.ObjectType{
AttrTypes: optimizerTypes,
},
@@ -258,6 +263,11 @@ func (r *distributionResource) Schema(_ context.Context, _ resource.SchemaReques
Description: schemaDescriptions["config_regions"],
ElementType: types.StringType,
},
+ "blocked_countries": schema.ListAttribute{
+ Optional: true,
+ Description: schemaDescriptions["config_blocked_countries"],
+ ElementType: types.StringType,
+ },
},
},
},
@@ -378,6 +388,26 @@ func (r *distributionResource) Update(ctx context.Context, req resource.UpdateRe
regions = append(regions, *regionEnum)
}
+ // blockedCountries
+ // Use a pointer to a slice to distinguish between an empty list (unblock all) and nil (no change).
+ var blockedCountries *[]string
+ if configModel.BlockedCountries != nil {
+ // Use a temporary slice
+ tempBlockedCountries := []string{}
+
+ for _, blockedCountry := range *configModel.BlockedCountries {
+ validatedBlockedCountry, err := validateCountryCode(blockedCountry)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Update CDN distribution", fmt.Sprintf("Blocked countries: %v", err))
+ return
+ }
+ tempBlockedCountries = append(tempBlockedCountries, validatedBlockedCountry)
+ }
+
+ // Point to the populated slice
+ blockedCountries = &tempBlockedCountries
+ }
+
configPatch := &cdn.ConfigPatch{
Backend: &cdn.ConfigPatchBackend{
HttpBackendPatch: &cdn.HttpBackendPatch{
@@ -386,7 +416,8 @@ func (r *distributionResource) Update(ctx context.Context, req resource.UpdateRe
Type: &configModel.Backend.Type,
},
},
- Regions: ®ions,
+ Regions: ®ions,
+ BlockedCountries: blockedCountries,
}
if !utils.IsUndefined(configModel.Optimizer) {
@@ -411,7 +442,9 @@ func (r *distributionResource) Update(ctx context.Context, req resource.UpdateRe
}).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Update CDN distribution", fmt.Sprintf("Patch distribution: %v", err))
+ return
}
+
waitResp, err := wait.UpdateDistributionWaitHandler(ctx, r.client, projectId, distributionId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Update CDN distribution", fmt.Sprintf("Waiting for update: %v", err))
@@ -423,6 +456,7 @@ func (r *distributionResource) Update(ctx context.Context, req resource.UpdateRe
core.LogAndAddError(ctx, &resp.Diagnostics, "Update CDN distribution", fmt.Sprintf("Processing API payload: %v", err))
return
}
+
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
@@ -501,6 +535,7 @@ func mapFields(distribution *cdn.Distribution, model *Model) error {
model.CreatedAt = types.StringValue(distribution.CreatedAt.String())
model.UpdatedAt = types.StringValue(distribution.UpdatedAt.String())
+ // distributionErrors
distributionErrors := []attr.Value{}
if distribution.Errors != nil {
for _, e := range *distribution.Errors {
@@ -513,6 +548,7 @@ func mapFields(distribution *cdn.Distribution, model *Model) error {
}
model.Errors = modelErrors
+ // regions
regions := []attr.Value{}
for _, r := range *distribution.Config.Regions {
regions = append(regions, types.StringValue(string(r)))
@@ -521,6 +557,21 @@ func mapFields(distribution *cdn.Distribution, model *Model) error {
if diags.HasError() {
return core.DiagsToError(diags)
}
+
+ // blockedCountries
+ var blockedCountries []attr.Value
+ if distribution.Config != nil && distribution.Config.BlockedCountries != nil {
+ for _, c := range *distribution.Config.BlockedCountries {
+ blockedCountries = append(blockedCountries, types.StringValue(string(c)))
+ }
+ }
+
+ modelBlockedCountries, diags := types.ListValue(types.StringType, blockedCountries)
+ if diags.HasError() {
+ return core.DiagsToError(diags)
+ }
+
+ // originRequestHeaders
originRequestHeaders := types.MapNull(types.StringType)
if origHeaders := distribution.Config.Backend.HttpBackend.OriginRequestHeaders; origHeaders != nil && len(*origHeaders) > 0 {
headers := map[string]attr.Value{}
@@ -557,9 +608,10 @@ func mapFields(distribution *cdn.Distribution, model *Model) error {
}
}
cfg, diags := types.ObjectValue(configTypes, map[string]attr.Value{
- "backend": backend,
- "regions": modelRegions,
- "optimizer": optimizerVal,
+ "backend": backend,
+ "regions": modelRegions,
+ "blocked_countries": modelBlockedCountries,
+ "optimizer": optimizerVal,
})
if diags.HasError() {
return core.DiagsToError(diags)
@@ -624,6 +676,7 @@ func toCreatePayload(ctx context.Context, model *Model) (*cdn.CreateDistribution
IntentId: cdn.PtrString(uuid.NewString()),
OriginUrl: cfg.Backend.HttpBackend.OriginUrl,
Regions: cfg.Regions,
+ BlockedCountries: cfg.BlockedCountries,
OriginRequestHeaders: cfg.Backend.HttpBackend.OriginRequestHeaders,
Optimizer: optimizer,
}
@@ -646,6 +699,8 @@ func convertConfig(ctx context.Context, model *Model) (*cdn.Config, error) {
if diags.HasError() {
return nil, core.DiagsToError(diags)
}
+
+ // regions
regions := []cdn.Region{}
for _, r := range *configModel.Regions {
regionEnum, err := cdn.NewRegionFromValue(r)
@@ -655,6 +710,19 @@ func convertConfig(ctx context.Context, model *Model) (*cdn.Config, error) {
regions = append(regions, *regionEnum)
}
+ // blockedCountries
+ var blockedCountries []string
+ if configModel.BlockedCountries != nil {
+ for _, blockedCountry := range *configModel.BlockedCountries {
+ validatedBlockedCountry, err := validateCountryCode(blockedCountry)
+ if err != nil {
+ return nil, err
+ }
+ blockedCountries = append(blockedCountries, validatedBlockedCountry)
+ }
+ }
+
+ // originRequestHeaders
originRequestHeaders := map[string]string{}
if configModel.Backend.OriginRequestHeaders != nil {
for k, v := range *configModel.Backend.OriginRequestHeaders {
@@ -670,7 +738,8 @@ func convertConfig(ctx context.Context, model *Model) (*cdn.Config, error) {
Type: &configModel.Backend.Type,
},
},
- Regions: ®ions,
+ Regions: ®ions,
+ BlockedCountries: &blockedCountries,
}
if !utils.IsUndefined(configModel.Optimizer) {
@@ -687,3 +756,25 @@ func convertConfig(ctx context.Context, model *Model) (*cdn.Config, error) {
return cdnConfig, nil
}
+
+// validateCountryCode checks for a valid country user input. This is just a quick check
+// since the API already does a more thorough check.
+func validateCountryCode(country string) (string, error) {
+ if len(country) != 2 {
+ return "", errors.New("country code must be exactly 2 characters long")
+ }
+
+ upperCountry := strings.ToUpper(country)
+
+ // Check if both characters are alphabetical letters within the ASCII range A-Z.
+ // Yes, we could use the unicode package, but we are only targeting ASCII letters specifically, so
+ // let's omit this dependency.
+ char1 := upperCountry[0]
+ char2 := upperCountry[1]
+
+ if !((char1 >= 'A' && char1 <= 'Z') && (char2 >= 'A' && char2 <= 'Z')) {
+ return "", fmt.Errorf("country code '%s' must consist of two alphabetical letters (A-Z or a-z)", country)
+ }
+
+ return upperCountry, nil
+}
diff --git a/stackit/internal/services/cdn/distribution/resource_test.go b/stackit/internal/services/cdn/distribution/resource_test.go
index 970c7535..13e55c7a 100644
--- a/stackit/internal/services/cdn/distribution/resource_test.go
+++ b/stackit/internal/services/cdn/distribution/resource_test.go
@@ -24,13 +24,16 @@ func TestToCreatePayload(t *testing.T) {
})
regions := []attr.Value{types.StringValue("EU"), types.StringValue("US")}
regionsFixture := types.ListValueMust(types.StringType, regions)
+ blockedCountries := []attr.Value{types.StringValue("XX"), types.StringValue("YY"), types.StringValue("ZZ")}
+ blockedCountriesFixture := types.ListValueMust(types.StringType, blockedCountries)
optimizer := types.ObjectValueMust(optimizerTypes, map[string]attr.Value{
"enabled": types.BoolValue(true),
})
config := types.ObjectValueMust(configTypes, map[string]attr.Value{
- "backend": backend,
- "regions": regionsFixture,
- "optimizer": types.ObjectNull(optimizerTypes),
+ "backend": backend,
+ "regions": regionsFixture,
+ "blocked_countries": blockedCountriesFixture,
+ "optimizer": types.ObjectNull(optimizerTypes),
})
modelFixture := func(mods ...func(*Model)) *Model {
model := &Model{
@@ -55,17 +58,19 @@ func TestToCreatePayload(t *testing.T) {
"testHeader0": "testHeaderValue0",
"testHeader1": "testHeaderValue1",
},
- OriginUrl: cdn.PtrString("https://www.mycoolapp.com"),
- Regions: &[]cdn.Region{"EU", "US"},
+ OriginUrl: cdn.PtrString("https://www.mycoolapp.com"),
+ Regions: &[]cdn.Region{"EU", "US"},
+ BlockedCountries: &[]string{"XX", "YY", "ZZ"},
},
IsValid: true,
},
"happy_path_with_optimizer": {
Input: modelFixture(func(m *Model) {
m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{
- "backend": backend,
- "regions": regionsFixture,
- "optimizer": optimizer,
+ "backend": backend,
+ "regions": regionsFixture,
+ "optimizer": optimizer,
+ "blocked_countries": blockedCountriesFixture,
})
}),
Expected: &cdn.CreateDistributionPayload{
@@ -73,9 +78,10 @@ func TestToCreatePayload(t *testing.T) {
"testHeader0": "testHeaderValue0",
"testHeader1": "testHeaderValue1",
},
- OriginUrl: cdn.PtrString("https://www.mycoolapp.com"),
- Regions: &[]cdn.Region{"EU", "US"},
- Optimizer: cdn.NewOptimizer(true),
+ OriginUrl: cdn.PtrString("https://www.mycoolapp.com"),
+ Regions: &[]cdn.Region{"EU", "US"},
+ Optimizer: cdn.NewOptimizer(true),
+ BlockedCountries: &[]string{"XX", "YY", "ZZ"},
},
IsValid: true,
},
@@ -127,11 +133,14 @@ func TestConvertConfig(t *testing.T) {
})
regions := []attr.Value{types.StringValue("EU"), types.StringValue("US")}
regionsFixture := types.ListValueMust(types.StringType, regions)
+ blockedCountries := []attr.Value{types.StringValue("XX"), types.StringValue("YY"), types.StringValue("ZZ")}
+ blockedCountriesFixture := types.ListValueMust(types.StringType, blockedCountries)
optimizer := types.ObjectValueMust(optimizerTypes, map[string]attr.Value{"enabled": types.BoolValue(true)})
config := types.ObjectValueMust(configTypes, map[string]attr.Value{
- "backend": backend,
- "regions": regionsFixture,
- "optimizer": types.ObjectNull(optimizerTypes),
+ "backend": backend,
+ "regions": regionsFixture,
+ "optimizer": types.ObjectNull(optimizerTypes),
+ "blocked_countries": blockedCountriesFixture,
})
modelFixture := func(mods ...func(*Model)) *Model {
model := &Model{
@@ -162,16 +171,18 @@ func TestConvertConfig(t *testing.T) {
Type: cdn.PtrString("http"),
},
},
- Regions: &[]cdn.Region{"EU", "US"},
+ Regions: &[]cdn.Region{"EU", "US"},
+ BlockedCountries: &[]string{"XX", "YY", "ZZ"},
},
IsValid: true,
},
"happy_path_with_optimizer": {
Input: modelFixture(func(m *Model) {
m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{
- "backend": backend,
- "regions": regionsFixture,
- "optimizer": optimizer,
+ "backend": backend,
+ "regions": regionsFixture,
+ "optimizer": optimizer,
+ "blocked_countries": blockedCountriesFixture,
})
}),
Expected: &cdn.Config{
@@ -185,8 +196,9 @@ func TestConvertConfig(t *testing.T) {
Type: cdn.PtrString("http"),
},
},
- Regions: &[]cdn.Region{"EU", "US"},
- Optimizer: cdn.NewOptimizer(true),
+ Regions: &[]cdn.Region{"EU", "US"},
+ Optimizer: cdn.NewOptimizer(true),
+ BlockedCountries: &[]string{"XX", "YY", "ZZ"},
},
IsValid: true,
},
@@ -237,13 +249,16 @@ func TestMapFields(t *testing.T) {
})
regions := []attr.Value{types.StringValue("EU"), types.StringValue("US")}
regionsFixture := types.ListValueMust(types.StringType, regions)
+ blockedCountries := []attr.Value{types.StringValue("XX"), types.StringValue("YY"), types.StringValue("ZZ")}
+ blockedCountriesFixture := types.ListValueMust(types.StringType, blockedCountries)
optimizer := types.ObjectValueMust(optimizerTypes, map[string]attr.Value{
"enabled": types.BoolValue(true),
})
config := types.ObjectValueMust(configTypes, map[string]attr.Value{
- "backend": backend,
- "regions": regionsFixture,
- "optimizer": types.ObjectNull(optimizerTypes),
+ "backend": backend,
+ "regions": regionsFixture,
+ "blocked_countries": blockedCountriesFixture,
+ "optimizer": types.ObjectNull(optimizerTypes),
})
emtpyErrorsList := types.ListValueMust(types.StringType, []attr.Value{})
@@ -284,8 +299,9 @@ func TestMapFields(t *testing.T) {
Type: cdn.PtrString("http"),
},
},
- Regions: &[]cdn.Region{"EU", "US"},
- Optimizer: nil,
+ Regions: &[]cdn.Region{"EU", "US"},
+ BlockedCountries: &[]string{"XX", "YY", "ZZ"},
+ Optimizer: nil,
},
CreatedAt: &createdAt,
Domains: &[]cdn.Domain{
@@ -318,9 +334,10 @@ func TestMapFields(t *testing.T) {
"happy_path_with_optimizer": {
Expected: expectedModel(func(m *Model) {
m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{
- "backend": backend,
- "regions": regionsFixture,
- "optimizer": optimizer,
+ "backend": backend,
+ "regions": regionsFixture,
+ "optimizer": optimizer,
+ "blocked_countries": blockedCountriesFixture,
})
}),
Input: distributionFixture(func(d *cdn.Distribution) {
@@ -411,3 +428,108 @@ func TestMapFields(t *testing.T) {
})
}
}
+
+// TestValidateCountryCode tests the validateCountryCode function with a variety of inputs.
+func TestValidateCountryCode(t *testing.T) {
+ testCases := []struct {
+ name string
+ inputCountry string
+ wantOutput string
+ expectError bool
+ expectedError string
+ }{
+ // Happy Path
+ {
+ name: "Valid lowercase",
+ inputCountry: "us",
+ wantOutput: "US",
+ expectError: false,
+ },
+ {
+ name: "Valid uppercase",
+ inputCountry: "DE",
+ wantOutput: "DE",
+ expectError: false,
+ },
+ {
+ name: "Valid mixed case",
+ inputCountry: "cA",
+ wantOutput: "CA",
+ expectError: false,
+ },
+ {
+ name: "Valid country code FR",
+ inputCountry: "fr",
+ wantOutput: "FR",
+ expectError: false,
+ },
+
+ // Error Scenarios
+ {
+ name: "Invalid length - too short",
+ inputCountry: "a",
+ wantOutput: "",
+ expectError: true,
+ expectedError: "country code must be exactly 2 characters long",
+ },
+ {
+ name: "Invalid length - too long",
+ inputCountry: "USA",
+ wantOutput: "",
+ expectError: true,
+ expectedError: "country code must be exactly 2 characters long",
+ },
+ {
+ name: "Invalid characters - contains number",
+ inputCountry: "U1",
+ wantOutput: "",
+ expectError: true,
+ expectedError: "country code 'U1' must consist of two alphabetical letters (A-Z or a-z)",
+ },
+ {
+ name: "Invalid characters - contains symbol",
+ inputCountry: "D!",
+ wantOutput: "",
+ expectError: true,
+ expectedError: "country code 'D!' must consist of two alphabetical letters (A-Z or a-z)",
+ },
+ {
+ name: "Invalid characters - both are numbers",
+ inputCountry: "42",
+ wantOutput: "",
+ expectError: true,
+ expectedError: "country code '42' must consist of two alphabetical letters (A-Z or a-z)",
+ },
+ {
+ name: "Empty string",
+ inputCountry: "",
+ wantOutput: "",
+ expectError: true,
+ expectedError: "country code must be exactly 2 characters long",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ gotOutput, err := validateCountryCode(tc.inputCountry)
+
+ if tc.expectError {
+ if err == nil {
+ t.Errorf("expected an error for input '%s', but got none", tc.inputCountry)
+ } else if err.Error() != tc.expectedError {
+ t.Errorf("for input '%s', expected error '%s', but got '%s'", tc.inputCountry, tc.expectedError, err.Error())
+ }
+ if gotOutput != "" {
+ t.Errorf("expected empty string on error, but got '%s'", gotOutput)
+ }
+ } else {
+ if err != nil {
+ t.Errorf("did not expect an error for input '%s', but got: %v", tc.inputCountry, err)
+ }
+ if gotOutput != tc.wantOutput {
+ t.Errorf("for input '%s', expected output '%s', but got '%s'", tc.inputCountry, tc.wantOutput, gotOutput)
+ }
+ }
+ })
+ }
+}
diff --git a/templates/guides/stackit_cdn_with_custom_domain.md.tmpl b/templates/guides/stackit_cdn_with_custom_domain.md.tmpl
index aa915136..0c90a88d 100644
--- a/templates/guides/stackit_cdn_with_custom_domain.md.tmpl
+++ b/templates/guides/stackit_cdn_with_custom_domain.md.tmpl
@@ -21,7 +21,8 @@ This guide outlines the process of creating a STACKIT CDN distribution and confi
type = "http"
origin_url = "mybackend.onstackit.cloud"
}
- regions = ["EU", "US", "ASIA", "AF", "SA"]
+ regions = ["EU", "US", "ASIA", "AF", "SA"]
+ blocked_countries = ["DE", "AT", "CH"]
}
}