From 9936ddc442e90db66a82924c07f945a7f950d543 Mon Sep 17 00:00:00 2001 From: Matheus Politano Date: Tue, 21 Jul 2026 15:45:55 +0200 Subject: [PATCH 1/2] feat(cdn) - Feature parity --- docs/data-sources/cdn_distribution.md | 3 + docs/resources/cdn_distribution.md | 3 + stackit/internal/services/cdn/cdn_acc_test.go | 19 ++++ .../services/cdn/distribution/datasource.go | 52 +++++++++- .../cdn/distribution/datasource_test.go | 41 ++++++-- .../services/cdn/distribution/resource.go | 97 ++++++++++++++++++- .../cdn/distribution/resource_test.go | 61 +++++++----- .../cdn/testdata/resource-http-base.tf | 6 ++ 8 files changed, 243 insertions(+), 39 deletions(-) diff --git a/docs/data-sources/cdn_distribution.md b/docs/data-sources/cdn_distribution.md index e9f592bd6..6f13cc25e 100644 --- a/docs/data-sources/cdn_distribution.md +++ b/docs/data-sources/cdn_distribution.md @@ -50,7 +50,10 @@ Optional: Read-Only: - `backend` (Attributes) The configured backend for the distribution (see [below for nested schema](#nestedatt--config--backend)) +- `blocked_ips` (List of String) Restricts access to your content by specifying a list of blocked IPv4 addresses. This feature enhances security and privacy by preventing these addresses from accessing your distribution. +- `default_cache_duration` (String) Sets the default cache duration for the distribution. The default cache duration is applied when a 'Cache-Control' header is not presented in the origin's response. We use ISO8601 duration format for cache duration (e.g. P1DT2H30M) - `forward_host_header` (Boolean) Enable this allows the 'Host' header to be passed through to the origin. +- `monthly_limit_bytes` (Number) Sets the monthly limit of bandwidth in bytes that the pullzone is allowed to use. - `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)) - `redirects` (Attributes) A wrapper for a list of redirect rules that allows for redirect settings on a distribution (see [below for nested schema](#nestedatt--config--redirects)) - `regions` (List of String) The configured regions where content will be hosted diff --git a/docs/resources/cdn_distribution.md b/docs/resources/cdn_distribution.md index cf24cf2cf..c95907d1d 100644 --- a/docs/resources/cdn_distribution.md +++ b/docs/resources/cdn_distribution.md @@ -147,7 +147,10 @@ Required: Optional: - `blocked_countries` (List of String) The configured countries where distribution of content is blocked +- `blocked_ips` (List of String) Restricts access to your content by specifying a list of blocked IPv4 addresses. This feature enhances security and privacy by preventing these addresses from accessing your distribution. +- `default_cache_duration` (String) Sets the default cache duration for the distribution. The default cache duration is applied when a 'Cache-Control' header is not presented in the origin's response. We use ISO8601 duration format for cache duration (e.g. P1DT2H30M) - `forward_host_header` (Boolean) Enable this allows the 'Host' header to be passed through to the origin. +- `monthly_limit_bytes` (Number) Sets the monthly limit of bandwidth in bytes that the pullzone is allowed to use. - `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)) - `redirects` (Attributes) A wrapper for a list of redirect rules that allows for redirect settings on a distribution (see [below for nested schema](#nestedatt--config--redirects)) - `strip_response_cookies` (Boolean) Enable this to prevent origin-level cookies from being forwarded to the end user. diff --git a/stackit/internal/services/cdn/cdn_acc_test.go b/stackit/internal/services/cdn/cdn_acc_test.go index c5fdee11a..effc74592 100644 --- a/stackit/internal/services/cdn/cdn_acc_test.go +++ b/stackit/internal/services/cdn/cdn_acc_test.go @@ -94,6 +94,7 @@ var testConfigVarsHttp = config.Variables{ "optimizer": config.BoolVariable(true), "backend_http_type": config.StringVariable("http"), "blocked_countries": config.ListVariable(config.StringVariable("CU")), + "blocked_ips": config.ListVariable(config.StringVariable("1.1.1.1")), "backend_origin_url": config.StringVariable("https://test-backend-1.cdn-dev.runs.onstackit.cloud"), "geofencing_list": config.ListVariable(config.StringVariable("DE")), "origin_request_headers_name": config.StringVariable("X-Custom-Header"), @@ -126,12 +127,17 @@ var testConfigVarsHttp = config.Variables{ "tls_enable_tls_11": config.BoolVariable(true), "strip_response_cookies": config.BoolVariable(false), "forward_host_header": config.BoolVariable(true), + "monthly_limit_bytes": config.IntegerVariable(104857600), + "default_cache_duration": config.StringVariable("PT2H"), } func configVarsHttpUpdated() config.Variables { updatedConfig := maps.Clone(testConfigVarsHttp) updatedConfig["regions"] = config.ListVariable(config.StringVariable("EU"), config.StringVariable("US"), config.StringVariable("ASIA")) updatedConfig["redirect_target_url"] = config.StringVariable("https://example.com/updated") + updatedConfig["monthly_limit_bytes"] = config.IntegerVariable(209715200) + updatedConfig["default_cache_duration"] = config.StringVariable("PT4H") + updatedConfig["blocked_ips"] = config.ListVariable(config.StringVariable("1.1.1.1"), config.StringVariable("2.2.2.2")) // Update WAF configuration to test mutation updatedConfig["waf_mode"] = config.StringVariable("LOG_ONLY") @@ -241,6 +247,8 @@ func TestAccCDNDistributionHttp(t *testing.T) { resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.regions.1", "US"), resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_countries.#", "1"), resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_countries.0", "CU"), + resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_ips.#", "1"), + resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_ips.0", "1.1.1.1"), resource.TestCheckResourceAttr( "stackit_cdn_distribution.distribution", fmt.Sprintf("config.backend.origin_request_headers.%s", testutil.ConvertConfigVariable(testConfigVarsHttp["origin_request_headers_name"])), @@ -258,6 +266,8 @@ func TestAccCDNDistributionHttp(t *testing.T) { resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.tls.enable_tls_11", testutil.ConvertConfigVariable(testConfigVarsHttp["tls_enable_tls_11"])), resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.forward_host_header", testutil.ConvertConfigVariable(testConfigVarsHttp["forward_host_header"])), resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.strip_response_cookies", testutil.ConvertConfigVariable(testConfigVarsHttp["strip_response_cookies"])), + resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.monthly_limit_bytes", testutil.ConvertConfigVariable(testConfigVarsHttp["monthly_limit_bytes"])), + resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.default_cache_duration", testutil.ConvertConfigVariable(testConfigVarsHttp["default_cache_duration"])), // WAF Checks resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.waf.mode", testutil.ConvertConfigVariable(testConfigVarsHttp["waf_mode"])), @@ -394,6 +404,8 @@ func TestAccCDNDistributionHttp(t *testing.T) { resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.regions.1", "US"), resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.blocked_countries.#", "1"), resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.blocked_countries.0", "CU"), + resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.blocked_ips.#", "1"), + resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.blocked_ips.0", "1.1.1.1"), resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.optimizer.enabled", testutil.ConvertConfigVariable(testConfigVarsHttp["optimizer"])), // TLS Checks inside Data Source @@ -401,6 +413,8 @@ func TestAccCDNDistributionHttp(t *testing.T) { resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.tls.enable_tls_11", testutil.ConvertConfigVariable(testConfigVarsHttp["tls_enable_tls_11"])), resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.forward_host_header", testutil.ConvertConfigVariable(testConfigVarsHttp["forward_host_header"])), resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.strip_response_cookies", testutil.ConvertConfigVariable(testConfigVarsHttp["strip_response_cookies"])), + resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.monthly_limit_bytes", testutil.ConvertConfigVariable(testConfigVarsHttp["monthly_limit_bytes"])), + resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.default_cache_duration", testutil.ConvertConfigVariable(testConfigVarsHttp["default_cache_duration"])), // WAF Checks inside Data Source resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.waf.mode", testutil.ConvertConfigVariable(testConfigVarsHttp["waf_mode"])), @@ -474,6 +488,9 @@ func TestAccCDNDistributionHttp(t *testing.T) { resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.regions.2", "ASIA"), resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_countries.#", "1"), resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_countries.0", "CU"), + resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_ips.#", "2"), + resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_ips.0", "1.1.1.1"), + resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.blocked_ips.1", "2.2.2.2"), resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.optimizer.enabled", testutil.ConvertConfigVariable(testConfigVarsHttp["optimizer"])), // TLS Configuration @@ -481,6 +498,8 @@ func TestAccCDNDistributionHttp(t *testing.T) { resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.tls.enable_tls_11", testutil.ConvertConfigVariable(configVarsHttpUpdated()["tls_enable_tls_11"])), resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.forward_host_header", testutil.ConvertConfigVariable(configVarsHttpUpdated()["forward_host_header"])), resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.strip_response_cookies", testutil.ConvertConfigVariable(configVarsHttpUpdated()["strip_response_cookies"])), + resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.monthly_limit_bytes", testutil.ConvertConfigVariable(configVarsHttpUpdated()["monthly_limit_bytes"])), + resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.default_cache_duration", testutil.ConvertConfigVariable(configVarsHttpUpdated()["default_cache_duration"])), // Checking WAF Mutated Configurations resource.TestCheckResourceAttr("stackit_cdn_distribution.distribution", "config.waf.mode", testutil.ConvertConfigVariable(configVarsHttpUpdated()["waf_mode"])), diff --git a/stackit/internal/services/cdn/distribution/datasource.go b/stackit/internal/services/cdn/distribution/datasource.go index e8be210fa..d3d6680f0 100644 --- a/stackit/internal/services/cdn/distribution/datasource.go +++ b/stackit/internal/services/cdn/distribution/datasource.go @@ -34,9 +34,12 @@ var dataSourceBackendTypes = map[string]attr.Type{ } var dataSourceConfigTypes = map[string]attr.Type{ - "backend": types.ObjectType{AttrTypes: dataSourceBackendTypes}, - "regions": types.ListType{ElemType: types.StringType}, - "blocked_countries": types.ListType{ElemType: types.StringType}, + "backend": types.ObjectType{AttrTypes: dataSourceBackendTypes}, + "regions": types.ListType{ElemType: types.StringType}, + "blocked_countries": types.ListType{ElemType: types.StringType}, + "blocked_ips": types.ListType{ElemType: types.StringType}, + "default_cache_duration": types.StringType, + "monthly_limit_bytes": types.Int64Type, "optimizer": types.ObjectType{ AttrTypes: optimizerTypes, // Shared from resource.go }, @@ -203,6 +206,19 @@ func (r *distributionDataSource) Schema(_ context.Context, _ datasource.SchemaRe Description: schemaDescriptions["config_blocked_countries"], ElementType: types.StringType, }, + "blocked_ips": schema.ListAttribute{ + Computed: true, + Description: schemaDescriptions["config_blocked_ips"], + ElementType: types.StringType, + }, + "default_cache_duration": schema.StringAttribute{ + Computed: true, + Description: schemaDescriptions["config_default_cache_duration"], + }, + "monthly_limit_bytes": schema.Int64Attribute{ + Computed: true, + Description: schemaDescriptions["config_monthly_limit_bytes"], + }, "optimizer": schema.SingleNestedAttribute{ Description: schemaDescriptions["config_optimizer"], Computed: true, @@ -678,11 +694,41 @@ func mapDataSourceFields(ctx context.Context, distribution *cdnSdk.Distribution, return core.DiagsToError(diagWaf) } + // blockedIps + var blockedIps []attr.Value + if distribution.Config.BlockedIps != nil { + for _, ip := range distribution.Config.BlockedIps { + blockedIps = append(blockedIps, types.StringValue(ip)) + } + } + + modelBlockedIps, diags := types.ListValue(types.StringType, blockedIps) + if diags.HasError() { + return core.DiagsToError(diags) + } + + var defaultCacheDuration types.String + if distribution.Config.DefaultCacheDuration.IsSet() { + defaultCacheDuration = types.StringPointerValue(distribution.Config.DefaultCacheDuration.Get()) + } else { + defaultCacheDuration = types.StringNull() + } + + var monthlyLimitBytes types.Int64 + if distribution.Config.MonthlyLimitBytes.IsSet() { + monthlyLimitBytes = types.Int64PointerValue(distribution.Config.MonthlyLimitBytes.Get()) + } else { + monthlyLimitBytes = types.Int64Null() + } + // Use dataSourceConfigTypes cfg, diags := types.ObjectValue(dataSourceConfigTypes, map[string]attr.Value{ "backend": backend, "regions": modelRegions, "blocked_countries": modelBlockedCountries, + "blocked_ips": modelBlockedIps, + "default_cache_duration": defaultCacheDuration, + "monthly_limit_bytes": monthlyLimitBytes, "optimizer": optimizerVal, "redirects": redirectsVal, "waf": wafVal, diff --git a/stackit/internal/services/cdn/distribution/datasource_test.go b/stackit/internal/services/cdn/distribution/datasource_test.go index 911957675..67b56d6fc 100644 --- a/stackit/internal/services/cdn/distribution/datasource_test.go +++ b/stackit/internal/services/cdn/distribution/datasource_test.go @@ -66,6 +66,9 @@ func TestMapDataSourceFields(t *testing.T) { "backend": backend, "regions": regionsFixture, "blocked_countries": blockedCountriesFixture, + "blocked_ips": types.ListValueMust(types.StringType, []attr.Value{}), + "default_cache_duration": types.StringNull(), + "monthly_limit_bytes": types.Int64Null(), "optimizer": types.ObjectNull(optimizerTypes), "redirects": types.ObjectNull(redirectsTypes), "waf": emptyWaf, @@ -241,8 +244,11 @@ func TestMapDataSourceFields(t *testing.T) { m.Config = types.ObjectValueMust(dataSourceConfigTypes, map[string]attr.Value{ "backend": backend, "regions": regionsFixture, - "optimizer": optimizer, "blocked_countries": blockedCountriesFixture, + "blocked_ips": types.ListValueMust(types.StringType, []attr.Value{}), + "default_cache_duration": types.StringNull(), + "monthly_limit_bytes": types.Int64Null(), + "optimizer": optimizer, "redirects": types.ObjectNull(redirectsTypes), "waf": emptyWaf, "tls": defaultTls, @@ -272,6 +278,9 @@ func TestMapDataSourceFields(t *testing.T) { "backend": bucketBackendExpected, "regions": regionsFixture, "blocked_countries": blockedCountriesFixture, + "blocked_ips": types.ListValueMust(types.StringType, []attr.Value{}), + "default_cache_duration": types.StringNull(), + "monthly_limit_bytes": types.Int64Null(), "optimizer": types.ObjectNull(optimizerTypes), "redirects": types.ObjectNull(redirectsTypes), "waf": emptyWaf, @@ -295,8 +304,11 @@ func TestMapDataSourceFields(t *testing.T) { m.Config = types.ObjectValueMust(dataSourceConfigTypes, map[string]attr.Value{ "backend": backendWithGeofencing, "regions": regionsFixture, - "optimizer": types.ObjectNull(optimizerTypes), "blocked_countries": blockedCountriesFixture, + "blocked_ips": types.ListValueMust(types.StringType, []attr.Value{}), + "default_cache_duration": types.StringNull(), + "monthly_limit_bytes": types.Int64Null(), + "optimizer": types.ObjectNull(optimizerTypes), "redirects": types.ObjectNull(redirectsTypes), "waf": emptyWaf, "tls": defaultTls, @@ -323,8 +335,11 @@ func TestMapDataSourceFields(t *testing.T) { m.Config = types.ObjectValueMust(dataSourceConfigTypes, map[string]attr.Value{ "backend": backend, "regions": regionsFixture, - "optimizer": types.ObjectNull(optimizerTypes), "blocked_countries": blockedCountriesFixture, + "blocked_ips": types.ListValueMust(types.StringType, []attr.Value{}), + "default_cache_duration": types.StringNull(), + "monthly_limit_bytes": types.Int64Null(), + "optimizer": types.ObjectNull(optimizerTypes), "redirects": redirectsConfigExpected, "waf": emptyWaf, "tls": defaultTls, @@ -342,8 +357,11 @@ func TestMapDataSourceFields(t *testing.T) { m.Config = types.ObjectValueMust(dataSourceConfigTypes, map[string]attr.Value{ "backend": backend, "regions": regionsFixture, - "optimizer": types.ObjectNull(optimizerTypes), "blocked_countries": blockedCountriesFixture, + "blocked_ips": types.ListValueMust(types.StringType, []attr.Value{}), + "default_cache_duration": types.StringNull(), + "monthly_limit_bytes": types.Int64Null(), + "optimizer": types.ObjectNull(optimizerTypes), "redirects": types.ObjectNull(redirectsTypes), "waf": populatedWaf, "tls": defaultTls, @@ -392,12 +410,15 @@ func TestMapDataSourceFields(t *testing.T) { "happy_path_with_tls_and_strip_response_and_cookies_forward": { Expected: expectedModel(func(m *Model) { m.Config = types.ObjectValueMust(dataSourceConfigTypes, map[string]attr.Value{ - "backend": backend, - "regions": regionsFixture, - "optimizer": types.ObjectNull(optimizerTypes), - "blocked_countries": blockedCountriesFixture, - "redirects": types.ObjectNull(redirectsTypes), - "waf": emptyWaf, + "backend": backend, + "regions": regionsFixture, + "blocked_countries": blockedCountriesFixture, + "blocked_ips": types.ListValueMust(types.StringType, []attr.Value{}), + "default_cache_duration": types.StringNull(), + "monthly_limit_bytes": types.Int64Null(), + "optimizer": types.ObjectNull(optimizerTypes), + "redirects": types.ObjectNull(redirectsTypes), + "waf": emptyWaf, "tls": types.ObjectValueMust(tlsTypes, map[string]attr.Value{ "enable_tls_10": types.BoolValue(true), "enable_tls_11": types.BoolValue(true), diff --git a/stackit/internal/services/cdn/distribution/resource.go b/stackit/internal/services/cdn/distribution/resource.go index bae5e5c41..88336454c 100644 --- a/stackit/internal/services/cdn/distribution/resource.go +++ b/stackit/internal/services/cdn/distribution/resource.go @@ -68,6 +68,9 @@ var schemaDescriptions = map[string]string{ "config_backend_origin_request_headers": "The configured type http origin request headers for the backend", "config_backend_geofencing": "The configured type http to configure countries where content is allowed. A map of URLs to a list of countries", "config_blocked_countries": "The configured countries where distribution of content is blocked", + "config_blocked_ips": "Restricts access to your content by specifying a list of blocked IPv4 addresses. This feature enhances security and privacy by preventing these addresses from accessing your distribution.", + "config_default_cache_duration": "Sets the default cache duration for the distribution. The default cache duration is applied when a 'Cache-Control' header is not presented in the origin's response. We use ISO8601 duration format for cache duration (e.g. P1DT2H30M)", + "config_monthly_limit_bytes": "Sets the monthly limit of bandwidth in bytes that the pullzone is allowed to use.", "config_redirects": "A wrapper for a list of redirect rules that allows for redirect settings on a distribution", "config_redirects_rules": "A list of redirect rules. The order of rules matters for evaluation", "config_redirects_rule_description": "An optional description for the redirect rule", @@ -144,6 +147,9 @@ type distributionConfig struct { Redirects *redirectConfig `tfsdk:"redirects"` // A wrapper for a list of redirect rules that allows for redirect settings on a 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 + BlockedIps *[]string `tfsdk:"blocked_ips"` // Restricts access to your content by specifying a list of blocked IPv4 addresses. + DefaultCacheDuration types.String `tfsdk:"default_cache_duration"` // Sets the default cache duration for the distribution. + MonthlyLimitBytes types.Int64 `tfsdk:"monthly_limit_bytes"` // Sets the monthly limit of bandwidth in bytes. Optimizer types.Object `tfsdk:"optimizer"` // The optimizer configuration Waf types.Object `tfsdk:"waf"` // The WAF configuration Tls types.Object `tfsdk:"tls"` // The TLS configuration @@ -194,9 +200,12 @@ type backendCredentials struct { } var configTypes = map[string]attr.Type{ - "backend": types.ObjectType{AttrTypes: backendTypes}, - "regions": types.ListType{ElemType: types.StringType}, - "blocked_countries": types.ListType{ElemType: types.StringType}, + "backend": types.ObjectType{AttrTypes: backendTypes}, + "regions": types.ListType{ElemType: types.StringType}, + "blocked_countries": types.ListType{ElemType: types.StringType}, + "blocked_ips": types.ListType{ElemType: types.StringType}, + "default_cache_duration": types.StringType, + "monthly_limit_bytes": types.Int64Type, "optimizer": types.ObjectType{ AttrTypes: optimizerTypes, }, @@ -714,6 +723,23 @@ func (r *distributionResource) Schema(_ context.Context, _ resource.SchemaReques // API response (empty list). Default: listdefault.StaticValue(types.ListValueMust(types.StringType, []attr.Value{})), }, + "blocked_ips": schema.ListAttribute{ + Optional: true, + Computed: true, // Required when using Default + Description: schemaDescriptions["config_blocked_ips"], + ElementType: types.StringType, + Default: listdefault.StaticValue(types.ListValueMust(types.StringType, []attr.Value{})), + }, + "default_cache_duration": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: schemaDescriptions["config_default_cache_duration"], + }, + "monthly_limit_bytes": schema.Int64Attribute{ + Optional: true, + Computed: true, + Description: schemaDescriptions["config_monthly_limit_bytes"], + }, }, }, }, @@ -918,6 +944,12 @@ func (r *distributionResource) Update(ctx context.Context, req resource.UpdateRe blockedCountries = tempBlockedCountries } + // blockedIps + var blockedIps []string + if configModel.BlockedIps != nil { + blockedIps = *configModel.BlockedIps + } + // tls var tls *cdnSdk.TlsConfigPatch if !utils.IsUndefined(configModel.Tls) { @@ -981,6 +1013,7 @@ func (r *distributionResource) Update(ctx context.Context, req resource.UpdateRe Backend: configPatchBackend, Regions: regions, BlockedCountries: blockedCountries, + BlockedIps: blockedIps, Redirects: redirectsConfig, Tls: tls, } @@ -993,6 +1026,12 @@ func (r *distributionResource) Update(ctx context.Context, req resource.UpdateRe if !utils.IsUndefined(configModel.StripResponseCookies) { configPatch.StripResponseCookies = configModel.StripResponseCookies.ValueBoolPointer() } + if !utils.IsUndefined(configModel.DefaultCacheDuration) { + configPatch.DefaultCacheDuration = *cdnSdk.NewNullableString(conversion.StringValueToPointer(configModel.DefaultCacheDuration)) + } + if !utils.IsUndefined(configModel.MonthlyLimitBytes) { + configPatch.MonthlyLimitBytes = *cdnSdk.NewNullableInt64(conversion.Int64ValueToPointer(configModel.MonthlyLimitBytes)) + } configPatch.Waf = &cdnSdk.WafConfigPatch{ Mode: new(cdnSdk.WAFMODE_DISABLED), @@ -1446,10 +1485,40 @@ func mapFields(ctx context.Context, distribution *cdnSdk.Distribution, model *Mo return core.DiagsToError(diagWaf) } + // blockedIps + var blockedIps []attr.Value + if distribution.Config.BlockedIps != nil { + for _, ip := range distribution.Config.BlockedIps { + blockedIps = append(blockedIps, types.StringValue(ip)) + } + } + + modelBlockedIps, diags := types.ListValue(types.StringType, blockedIps) + if diags.HasError() { + return core.DiagsToError(diags) + } + + var defaultCacheDuration types.String + if distribution.Config.DefaultCacheDuration.IsSet() { + defaultCacheDuration = types.StringPointerValue(distribution.Config.DefaultCacheDuration.Get()) + } else { + defaultCacheDuration = types.StringNull() + } + + var monthlyLimitBytes types.Int64 + if distribution.Config.MonthlyLimitBytes.IsSet() { + monthlyLimitBytes = types.Int64PointerValue(distribution.Config.MonthlyLimitBytes.Get()) + } else { + monthlyLimitBytes = types.Int64Null() + } + cfg, diags := types.ObjectValue(configTypes, map[string]attr.Value{ "backend": backend, "regions": modelRegions, "blocked_countries": modelBlockedCountries, + "blocked_ips": modelBlockedIps, + "default_cache_duration": defaultCacheDuration, + "monthly_limit_bytes": monthlyLimitBytes, "optimizer": optimizerVal, "redirects": redirectsVal, "waf": wafVal, @@ -1572,6 +1641,7 @@ func toCreatePayload(ctx context.Context, model *Model) (*cdnSdk.CreateDistribut Regions: cfg.Regions, Backend: *backend, BlockedCountries: cfg.BlockedCountries, + BlockedIps: cfg.BlockedIps, Optimizer: optimizer, Redirects: cfg.Redirects, Waf: wafPayload, @@ -1584,6 +1654,12 @@ func toCreatePayload(ctx context.Context, model *Model) (*cdnSdk.CreateDistribut if !utils.IsUndefined(rawConfig.StripResponseCookies) { payload.StripResponseCookies = rawConfig.StripResponseCookies.ValueBoolPointer() } + if !utils.IsUndefined(rawConfig.DefaultCacheDuration) { + payload.DefaultCacheDuration = conversion.StringValueToPointer(rawConfig.DefaultCacheDuration) + } + if !utils.IsUndefined(rawConfig.MonthlyLimitBytes) { + payload.MonthlyLimitBytes = conversion.Int64ValueToPointer(rawConfig.MonthlyLimitBytes) + } return payload, nil } @@ -1747,14 +1823,29 @@ func convertConfig(ctx context.Context, model *Model) (*cdnSdk.Config, error) { } } + // blockedIps + var blockedIps []string + if configModel.BlockedIps != nil { + blockedIps = *configModel.BlockedIps + } + cdnConfig := &cdnSdk.Config{ Backend: cdnSdk.ConfigBackend{}, Regions: regions, BlockedCountries: blockedCountries, + BlockedIps: blockedIps, Redirects: redirectsConfig, Tls: tls, } + if !utils.IsUndefined(configModel.DefaultCacheDuration) { + cdnConfig.DefaultCacheDuration = *cdnSdk.NewNullableString(conversion.StringValueToPointer(configModel.DefaultCacheDuration)) + } + + if !utils.IsUndefined(configModel.MonthlyLimitBytes) { + cdnConfig.MonthlyLimitBytes = *cdnSdk.NewNullableInt64(conversion.Int64ValueToPointer(configModel.MonthlyLimitBytes)) + } + if !utils.IsUndefined(configModel.Waf) { var wafModel wafConfig diags := configModel.Waf.As(ctx, &wafModel, basetypes.ObjectAsOptions{}) diff --git a/stackit/internal/services/cdn/distribution/resource_test.go b/stackit/internal/services/cdn/distribution/resource_test.go index 7752039d3..7bd19bbfc 100644 --- a/stackit/internal/services/cdn/distribution/resource_test.go +++ b/stackit/internal/services/cdn/distribution/resource_test.go @@ -13,6 +13,19 @@ import ( cdnSdk "github.com/stackitcloud/stackit-sdk-go/services/cdn/v1api" ) +func createTestConfig(vals map[string]attr.Value) types.Object { + if _, ok := vals["blocked_ips"]; !ok { + vals["blocked_ips"] = types.ListValueMust(types.StringType, []attr.Value{}) + } + if _, ok := vals["default_cache_duration"]; !ok { + vals["default_cache_duration"] = types.StringNull() + } + if _, ok := vals["monthly_limit_bytes"]; !ok { + vals["monthly_limit_bytes"] = types.Int64Null() + } + return types.ObjectValueMust(configTypes, vals) +} + func TestToCreatePayload(t *testing.T) { headers := map[string]attr.Value{ "testHeader0": types.StringValue("testHeaderValue0"), @@ -84,7 +97,7 @@ func TestToCreatePayload(t *testing.T) { } redirectsAttrTypes := redirectsObjType.AttrTypes - config := types.ObjectValueMust(configTypes, map[string]attr.Value{ + config := createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "blocked_countries": blockedCountriesFixture, @@ -194,7 +207,7 @@ func TestToCreatePayload(t *testing.T) { }, "happy_path_with_optimizer": { Input: modelFixture(func(m *Model) { - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "optimizer": optimizer, @@ -224,7 +237,7 @@ func TestToCreatePayload(t *testing.T) { }, "happy_path_with_redirects": { Input: modelFixture(func(m *Model) { - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "optimizer": types.ObjectNull(optimizerTypes), @@ -283,7 +296,7 @@ func TestToCreatePayload(t *testing.T) { "origin_request_headers": types.MapNull(types.StringType), "geofencing": types.MapNull(geofencingTypes.ElemType), }) - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": bucketBackend, "regions": regionsFixture, // reusing the existing one "blocked_countries": blockedCountriesFixture, @@ -315,7 +328,7 @@ func TestToCreatePayload(t *testing.T) { }, "happy_path_with_waf": { Input: modelFixture(func(m *Model) { - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "optimizer": types.ObjectNull(optimizerTypes), @@ -344,7 +357,7 @@ func TestToCreatePayload(t *testing.T) { }, "happy_path_with_strip_response_and_cookies_forward": { Input: modelFixture(func(m *Model) { - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "optimizer": types.ObjectNull(optimizerTypes), @@ -375,7 +388,7 @@ func TestToCreatePayload(t *testing.T) { }, "happy_path_with_tls": { Input: modelFixture(func(m *Model) { - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "optimizer": types.ObjectNull(optimizerTypes), @@ -435,7 +448,7 @@ func TestToCreatePayload(t *testing.T) { // set generated ID before diffing tc.Expected.IntentId = res.IntentId - diff := cmp.Diff(res, tc.Expected) + diff := cmp.Diff(res, tc.Expected, cmpopts.EquateEmpty()) if diff != "" { t.Fatalf("Create Payload not as expected: %s", diff) } @@ -472,7 +485,7 @@ func TestConvertConfig(t *testing.T) { 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{ + config := createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "optimizer": types.ObjectNull(optimizerTypes), @@ -587,7 +600,7 @@ func TestConvertConfig(t *testing.T) { }, "happy_path_with_optimizer": { Input: modelFixture(func(m *Model) { - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "optimizer": optimizer, @@ -621,7 +634,7 @@ func TestConvertConfig(t *testing.T) { }, "happy_path_with_tls": { Input: modelFixture(func(m *Model) { - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "optimizer": types.ObjectNull(optimizerTypes), @@ -661,7 +674,7 @@ func TestConvertConfig(t *testing.T) { }, "happy_path_with_waf": { Input: modelFixture(func(m *Model) { - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "optimizer": types.ObjectNull(optimizerTypes), @@ -695,7 +708,7 @@ func TestConvertConfig(t *testing.T) { }, "happy_path_with_redirects": { Input: modelFixture(func(m *Model) { - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "optimizer": types.ObjectNull(optimizerTypes), @@ -758,7 +771,7 @@ func TestConvertConfig(t *testing.T) { "origin_request_headers": types.MapNull(types.StringType), "geofencing": types.MapNull(geofencingTypes.ElemType), }) - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": bucketBackend, "regions": regionsFixture, "blocked_countries": blockedCountriesFixture, @@ -816,7 +829,9 @@ func TestConvertConfig(t *testing.T) { cmpopts.IgnoreUnexported( cdnSdk.NullableString{}, cdnSdk.NullableInt64{}, - )) + ), + cmpopts.EquateEmpty(), + ) if diff != "" { t.Fatalf("Create Payload not as expected: %s", diff) } @@ -922,7 +937,7 @@ func TestMapFields(t *testing.T) { "enable_tls_10": types.BoolValue(false), "enable_tls_11": types.BoolValue(false), }) - config := types.ObjectValueMust(configTypes, map[string]attr.Value{ + config := createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "blocked_countries": blockedCountriesFixture, @@ -1057,7 +1072,7 @@ func TestMapFields(t *testing.T) { "origin_request_headers": types.MapNull(types.StringType), "geofencing": types.MapNull(geofencingTypes.ElemType), }) - configOld := types.ObjectValueMust(configTypes, map[string]attr.Value{ + configOld := createTestConfig(map[string]attr.Value{ "backend": bucketBackendOld, "regions": regionsFixture, "blocked_countries": blockedCountriesFixture, @@ -1081,7 +1096,7 @@ func TestMapFields(t *testing.T) { }, "happy_path_with_optimizer": { Expected: expectedModel(func(m *Model) { - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "optimizer": optimizer, @@ -1111,7 +1126,7 @@ func TestMapFields(t *testing.T) { "region": types.StringNull(), "credentials": types.ObjectNull(backendCredentialsTypes), }) - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": backendWithGeofencing, "regions": regionsFixture, "optimizer": types.ObjectNull(optimizerTypes), @@ -1130,7 +1145,7 @@ func TestMapFields(t *testing.T) { }, "happy_path_with_redirects": { Expected: expectedModel(func(m *Model) { - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "optimizer": types.ObjectNull(optimizerTypes), @@ -1158,7 +1173,7 @@ func TestMapFields(t *testing.T) { }, "happy_path_with_waf": { Expected: expectedModel(func(m *Model) { - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "optimizer": types.ObjectNull(optimizerTypes), @@ -1177,7 +1192,7 @@ func TestMapFields(t *testing.T) { }, "happy_path_with_tls_and_strip_response_and_cookies_forward": { Expected: expectedModel(func(m *Model) { - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": backend, "regions": regionsFixture, "optimizer": types.ObjectNull(optimizerTypes), @@ -1249,7 +1264,7 @@ func TestMapFields(t *testing.T) { m.Config = configOld }), Expected: expectedModel(func(m *Model) { - m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{ + m.Config = createTestConfig(map[string]attr.Value{ "backend": bucketBackendOld, "regions": regionsFixture, "blocked_countries": blockedCountriesFixture, diff --git a/stackit/internal/services/cdn/testdata/resource-http-base.tf b/stackit/internal/services/cdn/testdata/resource-http-base.tf index 08ff46e09..ba9077417 100644 --- a/stackit/internal/services/cdn/testdata/resource-http-base.tf +++ b/stackit/internal/services/cdn/testdata/resource-http-base.tf @@ -4,6 +4,7 @@ variable "backend_http_type" {} variable "backend_origin_url" {} variable "geofencing_list" {} variable "blocked_countries" {} +variable "blocked_ips" {} variable "optimizer" {} variable "origin_request_headers_name" {} variable "origin_request_headers_value" {} @@ -35,6 +36,8 @@ variable "tls_enable_tls_10" {} variable "tls_enable_tls_11" {} variable "strip_response_cookies" {} variable "forward_host_header" {} +variable "monthly_limit_bytes" {} +variable "default_cache_duration" {} # dns variable "dns_zone_name" {} @@ -88,6 +91,8 @@ resource "stackit_cdn_distribution" "distribution" { } strip_response_cookies = var.strip_response_cookies forward_host_header = var.forward_host_header + monthly_limit_bytes = var.monthly_limit_bytes + default_cache_duration = var.default_cache_duration waf = { mode = var.waf_mode type = var.waf_type @@ -116,6 +121,7 @@ resource "stackit_cdn_distribution" "distribution" { } } blocked_countries = var.blocked_countries + blocked_ips = var.blocked_ips } } From 312983a080a613a7399ce5edc6afd7e6e2139177 Mon Sep 17 00:00:00 2001 From: Matheus Politano Date: Tue, 21 Jul 2026 15:56:45 +0200 Subject: [PATCH 2/2] chore(cdn) - add doc and example to new fields --- docs/resources/cdn_distribution.md | 8 ++++++++ examples/resources/stackit_cdn_distribution/resource.tf | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/docs/resources/cdn_distribution.md b/docs/resources/cdn_distribution.md index c95907d1d..1afd88157 100644 --- a/docs/resources/cdn_distribution.md +++ b/docs/resources/cdn_distribution.md @@ -28,6 +28,10 @@ resource "stackit_cdn_distribution" "example_distribution" { } regions = ["EU", "US", "ASIA", "AF", "SA"] blocked_countries = ["DE", "AT", "CH"] + blocked_ips = ["1.1.1.1"] + + default_cache_duration = "P1DT2H30M" + monthly_limit_bytes = 1048576 optimizer = { enabled = true @@ -52,6 +56,10 @@ resource "stackit_cdn_distribution" "example_bucket_distribution" { } regions = ["EU", "US"] blocked_countries = ["CN", "RU"] + blocked_ips = ["1.1.1.1"] + + default_cache_duration = "P1DT2H30M" + monthly_limit_bytes = 1048576 optimizer = { enabled = false diff --git a/examples/resources/stackit_cdn_distribution/resource.tf b/examples/resources/stackit_cdn_distribution/resource.tf index 65c6c560b..96ca7caae 100644 --- a/examples/resources/stackit_cdn_distribution/resource.tf +++ b/examples/resources/stackit_cdn_distribution/resource.tf @@ -10,6 +10,10 @@ resource "stackit_cdn_distribution" "example_distribution" { } regions = ["EU", "US", "ASIA", "AF", "SA"] blocked_countries = ["DE", "AT", "CH"] + blocked_ips = ["1.1.1.1"] + + default_cache_duration = "P1DT2H30M" + monthly_limit_bytes = 1048576 optimizer = { enabled = true @@ -34,6 +38,10 @@ resource "stackit_cdn_distribution" "example_bucket_distribution" { } regions = ["EU", "US"] blocked_countries = ["CN", "RU"] + blocked_ips = ["1.1.1.1"] + + default_cache_duration = "P1DT2H30M" + monthly_limit_bytes = 1048576 optimizer = { enabled = false