Skip to content

Commit 5407a02

Browse files
authored
fix(cdn): error when optional blocked_countries field is not defined as empty list (#1171)
relates to STACKITCDN-1014
1 parent 10dc583 commit 5407a02

3 files changed

Lines changed: 81 additions & 12 deletions

File tree

stackit/internal/services/cdn/cdn_acc_test.go

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,19 @@ var instanceResource = map[string]string{
3535
"dns_name": fmt.Sprintf("tf-acc-%s.stackit.gg", strings.Split(uuid.NewString(), "-")[0]),
3636
}
3737

38-
func configResources(regions string, geofencingCountries []string) string {
38+
func configResources(regions string, geofencingCountries []string, blockedCountries *string) string {
3939
var quotedCountries []string
4040
for _, country := range geofencingCountries {
4141
quotedCountries = append(quotedCountries, fmt.Sprintf(`%q`, country))
4242
}
4343

4444
geofencingList := strings.Join(quotedCountries, ",")
45+
46+
blockedCountriesConfig := ""
47+
if blockedCountries != nil {
48+
blockedCountriesConfig = fmt.Sprintf("blocked_countries = [%s]", *blockedCountries)
49+
}
50+
4551
return fmt.Sprintf(`
4652
%s
4753
@@ -56,7 +62,7 @@ func configResources(regions string, geofencingCountries []string) string {
5662
}
5763
}
5864
regions = [%s]
59-
blocked_countries = [%s]
65+
%s
6066
6167
optimizer = {
6268
enabled = true
@@ -80,11 +86,11 @@ func configResources(regions string, geofencingCountries []string) string {
8086
records = ["${stackit_cdn_distribution.distribution.domains[0].name}."]
8187
}
8288
`, testutil.CdnProviderConfig(), testutil.ProjectId, instanceResource["config_backend_origin_url"], instanceResource["config_backend_origin_url"], geofencingList,
83-
regions, instanceResource["blocked_countries"], testutil.ProjectId, instanceResource["dns_name"],
89+
regions, blockedCountriesConfig, testutil.ProjectId, instanceResource["dns_name"],
8490
testutil.ProjectId, instanceResource["custom_domain_prefix"])
8591
}
8692

87-
func configCustomDomainResources(regions, cert, key string, geofencingCountries []string) string {
93+
func configCustomDomainResources(regions, cert, key string, geofencingCountries []string, blockedCountries *string) string {
8894
return fmt.Sprintf(`
8995
%s
9096
@@ -97,10 +103,10 @@ func configCustomDomainResources(regions, cert, key string, geofencingCountries
97103
private_key = %q
98104
}
99105
}
100-
`, configResources(regions, geofencingCountries), cert, key)
106+
`, configResources(regions, geofencingCountries, blockedCountries), cert, key)
101107
}
102108

103-
func configDatasources(regions, cert, key string, geofencingCountries []string) string {
109+
func configDatasources(regions, cert, key string, geofencingCountries []string, blockedCountries *string) string {
104110
return fmt.Sprintf(`
105111
%s
106112
@@ -115,7 +121,7 @@ func configDatasources(regions, cert, key string, geofencingCountries []string)
115121
name = stackit_cdn_custom_domain.custom_domain.name
116122
117123
}
118-
`, configCustomDomainResources(regions, cert, key, geofencingCountries))
124+
`, configCustomDomainResources(regions, cert, key, geofencingCountries, blockedCountries))
119125
}
120126
func makeCertAndKey(t *testing.T, organization string) (cert, key []byte) {
121127
privateKey, err := rsa.GenerateKey(cryptoRand.Reader, 2048)
@@ -162,13 +168,17 @@ func TestAccCDNDistributionResource(t *testing.T) {
162168

163169
organization_updated := fmt.Sprintf("organization-updated-%s", uuid.NewString())
164170
cert_updated, key_updated := makeCertAndKey(t, organization_updated)
171+
172+
// Helper for default blocked countries
173+
defaultBlockedCountries := cdn.PtrString(instanceResource["blocked_countries"])
174+
165175
resource.Test(t, resource.TestCase{
166176
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
167177
CheckDestroy: testAccCheckCDNDistributionDestroy,
168178
Steps: []resource.TestStep{
169179
// Distribution Create
170180
{
171-
Config: configResources(instanceResource["config_regions"], geofencing),
181+
Config: configResources(instanceResource["config_regions"], geofencing, defaultBlockedCountries),
172182
Check: resource.ComposeAggregateTestCheckFunc(
173183
resource.TestCheckResourceAttrSet("stackit_cdn_distribution.distribution", "distribution_id"),
174184
resource.TestCheckResourceAttrSet("stackit_cdn_distribution.distribution", "created_at"),
@@ -200,15 +210,15 @@ func TestAccCDNDistributionResource(t *testing.T) {
200210
},
201211
// Wait step, that confirms the CNAME record has "propagated"
202212
{
203-
Config: configResources(instanceResource["config_regions"], geofencing),
213+
Config: configResources(instanceResource["config_regions"], geofencing, defaultBlockedCountries),
204214
Check: func(_ *terraform.State) error {
205215
_, err := blockUntilDomainResolves(fullDomainName)
206216
return err
207217
},
208218
},
209219
// Custom Domain Create
210220
{
211-
Config: configCustomDomainResources(instanceResource["config_regions"], string(cert), string(key), geofencing),
221+
Config: configCustomDomainResources(instanceResource["config_regions"], string(cert), string(key), geofencing, defaultBlockedCountries),
212222
Check: resource.ComposeAggregateTestCheckFunc(
213223
resource.TestCheckResourceAttr("stackit_cdn_custom_domain.custom_domain", "status", "ACTIVE"),
214224
resource.TestCheckResourceAttr("stackit_cdn_custom_domain.custom_domain", "name", fullDomainName),
@@ -262,7 +272,7 @@ func TestAccCDNDistributionResource(t *testing.T) {
262272
},
263273
// Data Source
264274
{
265-
Config: configDatasources(instanceResource["config_regions"], string(cert), string(key), geofencing),
275+
Config: configDatasources(instanceResource["config_regions"], string(cert), string(key), geofencing, defaultBlockedCountries),
266276
Check: resource.ComposeAggregateTestCheckFunc(
267277
resource.TestCheckResourceAttrSet("data.stackit_cdn_distribution.distribution", "distribution_id"),
268278
resource.TestCheckResourceAttrSet("data.stackit_cdn_distribution.distribution", "created_at"),
@@ -301,7 +311,7 @@ func TestAccCDNDistributionResource(t *testing.T) {
301311
},
302312
// Update
303313
{
304-
Config: configCustomDomainResources(instanceResource["config_regions_updated"], string(cert_updated), string(key_updated), geofencing),
314+
Config: configCustomDomainResources(instanceResource["config_regions_updated"], string(cert_updated), string(key_updated), geofencing, defaultBlockedCountries),
305315
Check: resource.ComposeAggregateTestCheckFunc(
306316
resource.TestCheckResourceAttrSet("stackit_cdn_distribution.distribution", "distribution_id"),
307317
resource.TestCheckResourceAttrSet("stackit_cdn_distribution.distribution", "created_at"),
@@ -330,6 +340,20 @@ func TestAccCDNDistributionResource(t *testing.T) {
330340
resource.TestCheckResourceAttrPair("stackit_cdn_distribution.distribution", "project_id", "stackit_cdn_custom_domain.custom_domain", "project_id"),
331341
),
332342
},
343+
// Bug Fix Verification: Omitted Field Handling
344+
//
345+
// This step verifies that omitting 'blocked_countries' from the Terraform configuration
346+
// (by setting the pointer to nil) does not cause an "inconsistent result" error.
347+
//
348+
// Previously, omitting the field resulted in a 'null' config, but the API returned an
349+
// empty list '[]', causing a state mismatch. The 'Default' modifier in the schema now
350+
// ensures the missing config is treated as an empty list, matching the API response.
351+
{
352+
Config: configResources(instanceResource["config_regions"], geofencing, nil),
353+
Check: resource.ComposeAggregateTestCheckFunc(
354+
resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_countries.#", "0"),
355+
),
356+
},
333357
},
334358
})
335359
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/hashicorp/terraform-plugin-framework/path"
1818
"github.com/hashicorp/terraform-plugin-framework/resource"
1919
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
20+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault"
2021
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
2122
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
2223
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
@@ -281,8 +282,18 @@ func (r *distributionResource) Schema(_ context.Context, _ resource.SchemaReques
281282
},
282283
"blocked_countries": schema.ListAttribute{
283284
Optional: true,
285+
Computed: true, // Required when using Default
284286
Description: schemaDescriptions["config_blocked_countries"],
285287
ElementType: types.StringType,
288+
// The API returns an empty list for blocked_countries even if the field is omitted
289+
// (null) in the request. This causes an "inconsistent result" error in Terraform
290+
// because the config is null but the state is [].
291+
//
292+
// By setting a Default value of an empty list, we tell Terraform to treat a missing
293+
// blocked_countries block in the HCL as if the user explicitly defined
294+
// blocked_countries = []. This ensures the config (empty list) matches the
295+
// API response (empty list).
296+
Default: listdefault.StaticValue(types.ListValueMust(types.StringType, []attr.Value{})),
286297
},
287298
},
288299
},
@@ -812,6 +823,14 @@ func toCreatePayload(ctx context.Context, model *Model) (*cdn.CreateDistribution
812823
}
813824

814825
payload := &cdn.CreateDistributionPayload{
826+
Backend: &cdn.CreateDistributionPayloadBackend{
827+
HttpBackendCreate: &cdn.HttpBackendCreate{
828+
OriginUrl: cfg.Backend.HttpBackend.OriginUrl,
829+
OriginRequestHeaders: cfg.Backend.HttpBackend.OriginRequestHeaders,
830+
Geofencing: cfg.Backend.HttpBackend.Geofencing,
831+
Type: cfg.Backend.HttpBackend.Type,
832+
},
833+
},
815834
IntentId: cdn.PtrString(uuid.NewString()),
816835
Regions: cfg.Regions,
817836
BlockedCountries: cfg.BlockedCountries,

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ func TestToCreatePayload(t *testing.T) {
6262
"happy_path": {
6363
Input: modelFixture(),
6464
Expected: &cdn.CreateDistributionPayload{
65+
Backend: &cdn.CreateDistributionPayloadBackend{
66+
HttpBackendCreate: &cdn.HttpBackendCreate{
67+
OriginUrl: cdn.PtrString("https://www.mycoolapp.com"),
68+
OriginRequestHeaders: &map[string]string{
69+
"testHeader0": "testHeaderValue0",
70+
"testHeader1": "testHeaderValue1",
71+
},
72+
Geofencing: &map[string][]string{
73+
"https://de.mycoolapp.com": {"DE", "FR"},
74+
},
75+
Type: cdn.PtrString("http"),
76+
},
77+
},
6578
Regions: &[]cdn.Region{"EU", "US"},
6679
BlockedCountries: &[]string{"XX", "YY", "ZZ"},
6780
},
@@ -77,6 +90,19 @@ func TestToCreatePayload(t *testing.T) {
7790
})
7891
}),
7992
Expected: &cdn.CreateDistributionPayload{
93+
Backend: &cdn.CreateDistributionPayloadBackend{
94+
HttpBackendCreate: &cdn.HttpBackendCreate{
95+
OriginUrl: cdn.PtrString("https://www.mycoolapp.com"),
96+
OriginRequestHeaders: &map[string]string{
97+
"testHeader0": "testHeaderValue0",
98+
"testHeader1": "testHeaderValue1",
99+
},
100+
Geofencing: &map[string][]string{
101+
"https://de.mycoolapp.com": {"DE", "FR"},
102+
},
103+
Type: cdn.PtrString("http"),
104+
},
105+
},
80106
Regions: &[]cdn.Region{"EU", "US"},
81107
Optimizer: cdn.NewOptimizer(true),
82108
BlockedCountries: &[]string{"XX", "YY", "ZZ"},

0 commit comments

Comments
 (0)