cdn add geofence feature (#1020)
* add geofencing attribute to "stackit_cdn_distribution"
This commit is contained in:
parent
87bc7415fc
commit
f0433984f4
9 changed files with 324 additions and 23 deletions
|
|
@ -58,6 +58,7 @@ Read-Only:
|
|||
|
||||
Read-Only:
|
||||
|
||||
- `geofencing` (Map of List of String) A map of URLs to a list of countries where content is allowed.
|
||||
- `origin_request_headers` (Map of String) The configured origin request headers for the backend
|
||||
- `origin_url` (String) The configured backend type for the distribution
|
||||
- `type` (String) The configured backend type. Supported values are: `http`.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ resource "stackit_cdn_distribution" "example_distribution" {
|
|||
backend = {
|
||||
type = "http"
|
||||
origin_url = "https://mybackend.onstackit.cloud"
|
||||
geofencing = {
|
||||
"https://mybackend.onstackit.cloud" = ["DE"]
|
||||
}
|
||||
}
|
||||
regions = ["EU", "US", "ASIA", "AF", "SA"]
|
||||
blocked_countries = ["DE", "AT", "CH"]
|
||||
|
|
@ -80,6 +83,7 @@ Required:
|
|||
|
||||
Optional:
|
||||
|
||||
- `geofencing` (Map of List of String) A map of URLs to a list of countries where content is allowed.
|
||||
- `origin_request_headers` (Map of String) The configured origin request headers for the backend
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ resource "stackit_cdn_distribution" "example_distribution" {
|
|||
backend = {
|
||||
type = "http"
|
||||
origin_url = "https://mybackend.onstackit.cloud"
|
||||
geofencing = {
|
||||
"https://mybackend.onstackit.cloud" = ["DE"]
|
||||
}
|
||||
}
|
||||
regions = ["EU", "US", "ASIA", "AF", "SA"]
|
||||
blocked_countries = ["DE", "AT", "CH"]
|
||||
|
|
|
|||
|
|
@ -35,7 +35,13 @@ var instanceResource = map[string]string{
|
|||
"dns_name": fmt.Sprintf("tf-acc-%s.stackit.gg", strings.Split(uuid.NewString(), "-")[0]),
|
||||
}
|
||||
|
||||
func configResources(regions string) string {
|
||||
func configResources(regions string, geofencingCountries []string) string {
|
||||
var quotedCountries []string
|
||||
for _, country := range geofencingCountries {
|
||||
quotedCountries = append(quotedCountries, fmt.Sprintf(`%q`, country))
|
||||
}
|
||||
|
||||
geofencingList := strings.Join(quotedCountries, ",")
|
||||
return fmt.Sprintf(`
|
||||
%s
|
||||
|
||||
|
|
@ -45,6 +51,9 @@ func configResources(regions string) string {
|
|||
backend = {
|
||||
type = "http"
|
||||
origin_url = "%s"
|
||||
geofencing = {
|
||||
"%s" = [%s]
|
||||
}
|
||||
}
|
||||
regions = [%s]
|
||||
blocked_countries = [%s]
|
||||
|
|
@ -70,12 +79,12 @@ func configResources(regions string) string {
|
|||
type = "CNAME"
|
||||
records = ["${stackit_cdn_distribution.distribution.domains[0].name}."]
|
||||
}
|
||||
`, testutil.CdnProviderConfig(), testutil.ProjectId, instanceResource["config_backend_origin_url"],
|
||||
`, testutil.CdnProviderConfig(), testutil.ProjectId, instanceResource["config_backend_origin_url"], instanceResource["config_backend_origin_url"], geofencingList,
|
||||
regions, instanceResource["blocked_countries"], testutil.ProjectId, instanceResource["dns_name"],
|
||||
testutil.ProjectId, instanceResource["custom_domain_prefix"])
|
||||
}
|
||||
|
||||
func configCustomDomainResources(regions, cert, key string) string {
|
||||
func configCustomDomainResources(regions, cert, key string, geofencingCountries []string) string {
|
||||
return fmt.Sprintf(`
|
||||
%s
|
||||
|
||||
|
|
@ -88,10 +97,10 @@ func configCustomDomainResources(regions, cert, key string) string {
|
|||
private_key = %q
|
||||
}
|
||||
}
|
||||
`, configResources(regions), cert, key)
|
||||
`, configResources(regions, geofencingCountries), cert, key)
|
||||
}
|
||||
|
||||
func configDatasources(regions, cert, key string) string {
|
||||
func configDatasources(regions, cert, key string, geofencingCountries []string) string {
|
||||
return fmt.Sprintf(`
|
||||
%s
|
||||
|
||||
|
|
@ -106,7 +115,7 @@ func configDatasources(regions, cert, key string) string {
|
|||
name = stackit_cdn_custom_domain.custom_domain.name
|
||||
|
||||
}
|
||||
`, configCustomDomainResources(regions, cert, key))
|
||||
`, configCustomDomainResources(regions, cert, key, geofencingCountries))
|
||||
}
|
||||
func makeCertAndKey(t *testing.T, organization string) (cert, key []byte) {
|
||||
privateKey, err := rsa.GenerateKey(cryptoRand.Reader, 2048)
|
||||
|
|
@ -149,6 +158,7 @@ func TestAccCDNDistributionResource(t *testing.T) {
|
|||
fullDomainName := fmt.Sprintf("%s.%s", instanceResource["custom_domain_prefix"], instanceResource["dns_name"])
|
||||
organization := fmt.Sprintf("organization-%s", uuid.NewString())
|
||||
cert, key := makeCertAndKey(t, organization)
|
||||
geofencing := []string{"DE", "ES"}
|
||||
|
||||
organization_updated := fmt.Sprintf("organization-updated-%s", uuid.NewString())
|
||||
cert_updated, key_updated := makeCertAndKey(t, organization_updated)
|
||||
|
|
@ -158,7 +168,7 @@ func TestAccCDNDistributionResource(t *testing.T) {
|
|||
Steps: []resource.TestStep{
|
||||
// Distribution Create
|
||||
{
|
||||
Config: configResources(instanceResource["config_regions"]),
|
||||
Config: configResources(instanceResource["config_regions"], geofencing),
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttrSet("stackit_cdn_distribution.distribution", "distribution_id"),
|
||||
resource.TestCheckResourceAttrSet("stackit_cdn_distribution.distribution", "created_at"),
|
||||
|
|
@ -173,6 +183,16 @@ func TestAccCDNDistributionResource(t *testing.T) {
|
|||
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",
|
||||
fmt.Sprintf("config.backend.geofencing.%s.0", instanceResource["config_backend_origin_url"]),
|
||||
"DE",
|
||||
),
|
||||
resource.TestCheckResourceAttr(
|
||||
"stackit_cdn_distribution.distribution",
|
||||
fmt.Sprintf("config.backend.geofencing.%s.1", instanceResource["config_backend_origin_url"]),
|
||||
"ES",
|
||||
),
|
||||
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"),
|
||||
|
|
@ -180,7 +200,7 @@ func TestAccCDNDistributionResource(t *testing.T) {
|
|||
},
|
||||
// Wait step, that confirms the CNAME record has "propagated"
|
||||
{
|
||||
Config: configResources(instanceResource["config_regions"]),
|
||||
Config: configResources(instanceResource["config_regions"], geofencing),
|
||||
Check: func(_ *terraform.State) error {
|
||||
_, err := blockUntilDomainResolves(fullDomainName)
|
||||
return err
|
||||
|
|
@ -188,7 +208,7 @@ func TestAccCDNDistributionResource(t *testing.T) {
|
|||
},
|
||||
// Custom Domain Create
|
||||
{
|
||||
Config: configCustomDomainResources(instanceResource["config_regions"], string(cert), string(key)),
|
||||
Config: configCustomDomainResources(instanceResource["config_regions"], string(cert), string(key), geofencing),
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("stackit_cdn_custom_domain.custom_domain", "status", "ACTIVE"),
|
||||
resource.TestCheckResourceAttr("stackit_cdn_custom_domain.custom_domain", "name", fullDomainName),
|
||||
|
|
@ -242,7 +262,7 @@ func TestAccCDNDistributionResource(t *testing.T) {
|
|||
},
|
||||
// Data Source
|
||||
{
|
||||
Config: configDatasources(instanceResource["config_regions"], string(cert), string(key)),
|
||||
Config: configDatasources(instanceResource["config_regions"], string(cert), string(key), geofencing),
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttrSet("data.stackit_cdn_distribution.distribution", "distribution_id"),
|
||||
resource.TestCheckResourceAttrSet("data.stackit_cdn_distribution.distribution", "created_at"),
|
||||
|
|
@ -255,6 +275,16 @@ func TestAccCDNDistributionResource(t *testing.T) {
|
|||
resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "domains.0.type", "managed"),
|
||||
resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "domains.1.type", "custom"),
|
||||
resource.TestCheckResourceAttr("data.stackit_cdn_distribution.distribution", "config.regions.#", "2"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"data.stackit_cdn_distribution.distribution",
|
||||
fmt.Sprintf("config.backend.geofencing.%s.0", instanceResource["config_backend_origin_url"]),
|
||||
"DE",
|
||||
),
|
||||
resource.TestCheckResourceAttr(
|
||||
"data.stackit_cdn_distribution.distribution",
|
||||
fmt.Sprintf("config.backend.geofencing.%s.1", instanceResource["config_backend_origin_url"]),
|
||||
"ES",
|
||||
),
|
||||
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"),
|
||||
|
|
@ -271,7 +301,7 @@ func TestAccCDNDistributionResource(t *testing.T) {
|
|||
},
|
||||
// Update
|
||||
{
|
||||
Config: configCustomDomainResources(instanceResource["config_regions_updated"], string(cert_updated), string(key_updated)),
|
||||
Config: configCustomDomainResources(instanceResource["config_regions_updated"], string(cert_updated), string(key_updated), geofencing),
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttrSet("stackit_cdn_distribution.distribution", "distribution_id"),
|
||||
resource.TestCheckResourceAttrSet("stackit_cdn_distribution.distribution", "created_at"),
|
||||
|
|
|
|||
|
|
@ -142,6 +142,13 @@ func (r *distributionDataSource) Schema(_ context.Context, _ datasource.SchemaRe
|
|||
Description: schemaDescriptions["config_backend_origin_request_headers"],
|
||||
ElementType: types.StringType,
|
||||
},
|
||||
"geofencing": schema.MapAttribute{
|
||||
Description: "A map of URLs to a list of countries where content is allowed.",
|
||||
Computed: true,
|
||||
ElementType: types.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"regions": schema.ListAttribute{
|
||||
|
|
@ -192,7 +199,7 @@ func (r *distributionDataSource) Read(ctx context.Context, req datasource.ReadRe
|
|||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
err = mapFields(distributionResp.Distribution, &model)
|
||||
err = mapFields(ctx, distributionResp.Distribution, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading CDN distribution", fmt.Sprintf("Error processing API response: %v", err))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -8,10 +8,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
cdnUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/cdn/utils"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
|
|
@ -28,8 +26,10 @@ import (
|
|||
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/cdn"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/cdn/wait"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
|
||||
cdnUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/cdn/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
|
||||
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
|
||||
)
|
||||
|
|
@ -88,9 +88,10 @@ type optimizerConfig struct {
|
|||
}
|
||||
|
||||
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
|
||||
OriginRequestHeaders *map[string]string `tfsdk:"origin_request_headers"` // Request headers that should be added by the CDN distribution to incoming requests
|
||||
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
|
||||
OriginRequestHeaders *map[string]string `tfsdk:"origin_request_headers"` // Request headers that should be added by the CDN distribution to incoming requests
|
||||
Geofencing *map[string][]*string `tfsdk:"geofencing"` // The geofencing is an object mapping multiple alternative origins to country codes.
|
||||
}
|
||||
|
||||
var configTypes = map[string]attr.Type{
|
||||
|
|
@ -106,10 +107,15 @@ var optimizerTypes = map[string]attr.Type{
|
|||
"enabled": types.BoolType,
|
||||
}
|
||||
|
||||
var geofencingTypes = types.MapType{ElemType: types.ListType{
|
||||
ElemType: types.StringType,
|
||||
}}
|
||||
|
||||
var backendTypes = map[string]attr.Type{
|
||||
"type": types.StringType,
|
||||
"origin_url": types.StringType,
|
||||
"origin_request_headers": types.MapType{ElemType: types.StringType},
|
||||
"geofencing": geofencingTypes,
|
||||
}
|
||||
|
||||
var domainTypes = map[string]attr.Type{
|
||||
|
|
@ -256,6 +262,16 @@ func (r *distributionResource) Schema(_ context.Context, _ resource.SchemaReques
|
|||
Description: schemaDescriptions["config_backend_origin_request_headers"],
|
||||
ElementType: types.StringType,
|
||||
},
|
||||
"geofencing": schema.MapAttribute{
|
||||
Description: "A map of URLs to a list of countries where content is allowed.",
|
||||
Optional: true,
|
||||
ElementType: types.ListType{
|
||||
ElemType: types.StringType,
|
||||
},
|
||||
Validators: []validator.Map{
|
||||
mapvalidator.SizeAtLeast(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"regions": schema.ListAttribute{
|
||||
|
|
@ -274,6 +290,43 @@ func (r *distributionResource) Schema(_ context.Context, _ resource.SchemaReques
|
|||
}
|
||||
}
|
||||
|
||||
func (r *distributionResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
|
||||
var model Model
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.IsUndefined(model.Config) {
|
||||
var config distributionConfig
|
||||
if !model.Config.IsNull() {
|
||||
diags := model.Config.As(ctx, &config, basetypes.ObjectAsOptions{})
|
||||
if diags.HasError() {
|
||||
return
|
||||
}
|
||||
if geofencing := config.Backend.Geofencing; geofencing != nil {
|
||||
for url, region := range *geofencing {
|
||||
if region == nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Invalid geofencing config", fmt.Sprintf("The list of countries for URL %q must not be null.", url))
|
||||
continue
|
||||
}
|
||||
if len(region) == 0 {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Invalid geofencing config", fmt.Sprintf("The list of countries for URL %q must not be empty.", url))
|
||||
continue
|
||||
}
|
||||
|
||||
for i, countryPtr := range region {
|
||||
if countryPtr == nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Invalid geofencing config", fmt.Sprintf("Found a null value in the country list for URL %q at index %d.", url, i))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *distributionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
var model Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
|
|
@ -301,7 +354,7 @@ func (r *distributionResource) Create(ctx context.Context, req resource.CreateRe
|
|||
return
|
||||
}
|
||||
|
||||
err = mapFields(waitResp.Distribution, &model)
|
||||
err = mapFields(ctx, waitResp.Distribution, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating CDN distribution", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -341,7 +394,7 @@ func (r *distributionResource) Read(ctx context.Context, req resource.ReadReques
|
|||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading CDN distribution", fmt.Sprintf("Calling API: %v", err))
|
||||
return
|
||||
}
|
||||
err = mapFields(cdnResp.Distribution, &model)
|
||||
err = mapFields(ctx, cdnResp.Distribution, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading CDN ditribution", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -408,12 +461,30 @@ func (r *distributionResource) Update(ctx context.Context, req resource.UpdateRe
|
|||
blockedCountries = &tempBlockedCountries
|
||||
}
|
||||
|
||||
geofencingPatch := map[string][]string{}
|
||||
if configModel.Backend.Geofencing != nil {
|
||||
gf := make(map[string][]string)
|
||||
for url, countries := range *configModel.Backend.Geofencing {
|
||||
countryStrings := make([]string, len(countries))
|
||||
for i, countryPtr := range countries {
|
||||
if countryPtr == nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Update CDN distribution", fmt.Sprintf("Geofencing url %q has a null value", url))
|
||||
return
|
||||
}
|
||||
countryStrings[i] = *countryPtr
|
||||
}
|
||||
gf[url] = countryStrings
|
||||
}
|
||||
geofencingPatch = gf
|
||||
}
|
||||
|
||||
configPatch := &cdn.ConfigPatch{
|
||||
Backend: &cdn.ConfigPatchBackend{
|
||||
HttpBackendPatch: &cdn.HttpBackendPatch{
|
||||
OriginRequestHeaders: configModel.Backend.OriginRequestHeaders,
|
||||
OriginUrl: &configModel.Backend.OriginURL,
|
||||
Type: &configModel.Backend.Type,
|
||||
Geofencing: &geofencingPatch, // Use the converted variable
|
||||
},
|
||||
},
|
||||
Regions: ®ions,
|
||||
|
|
@ -451,7 +522,7 @@ func (r *distributionResource) Update(ctx context.Context, req resource.UpdateRe
|
|||
return
|
||||
}
|
||||
|
||||
err = mapFields(waitResp.Distribution, &model)
|
||||
err = mapFields(ctx, waitResp.Distribution, &model)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Update CDN distribution", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -500,7 +571,7 @@ func (r *distributionResource) ImportState(ctx context.Context, req resource.Imp
|
|||
tflog.Info(ctx, "CDN distribution state imported")
|
||||
}
|
||||
|
||||
func mapFields(distribution *cdn.Distribution, model *Model) error {
|
||||
func mapFields(ctx context.Context, distribution *cdn.Distribution, model *Model) error {
|
||||
if distribution == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
|
@ -584,11 +655,58 @@ func mapFields(distribution *cdn.Distribution, model *Model) error {
|
|||
return core.DiagsToError(diags)
|
||||
}
|
||||
}
|
||||
|
||||
// geofencing
|
||||
var oldConfig distributionConfig
|
||||
oldGeofencingMap := make(map[string][]*string)
|
||||
if !model.Config.IsNull() {
|
||||
diags = model.Config.As(ctx, &oldConfig, basetypes.ObjectAsOptions{})
|
||||
if diags.HasError() {
|
||||
return core.DiagsToError(diags)
|
||||
}
|
||||
if oldConfig.Backend.Geofencing != nil {
|
||||
oldGeofencingMap = *oldConfig.Backend.Geofencing
|
||||
}
|
||||
}
|
||||
|
||||
reconciledGeofencingData := make(map[string][]string)
|
||||
if geofencingAPI := distribution.Config.Backend.HttpBackend.Geofencing; geofencingAPI != nil && len(*geofencingAPI) > 0 {
|
||||
newGeofencingMap := *geofencingAPI
|
||||
for url, newCountries := range newGeofencingMap {
|
||||
oldCountriesPtrs := oldGeofencingMap[url]
|
||||
|
||||
oldCountries := utils.ConvertPointerSliceToStringSlice(oldCountriesPtrs)
|
||||
|
||||
reconciledCountries := utils.ReconcileStringSlices(oldCountries, newCountries)
|
||||
reconciledGeofencingData[url] = reconciledCountries
|
||||
}
|
||||
}
|
||||
|
||||
geofencingVal := types.MapNull(geofencingTypes.ElemType)
|
||||
if len(reconciledGeofencingData) > 0 {
|
||||
geofencingMapElems := make(map[string]attr.Value)
|
||||
for url, countries := range reconciledGeofencingData {
|
||||
listVal, diags := types.ListValueFrom(ctx, types.StringType, countries)
|
||||
if diags.HasError() {
|
||||
return core.DiagsToError(diags)
|
||||
}
|
||||
geofencingMapElems[url] = listVal
|
||||
}
|
||||
|
||||
var mappedGeofencing basetypes.MapValue
|
||||
mappedGeofencing, diags = types.MapValue(geofencingTypes.ElemType, geofencingMapElems)
|
||||
if diags.HasError() {
|
||||
return core.DiagsToError(diags)
|
||||
}
|
||||
geofencingVal = mappedGeofencing
|
||||
}
|
||||
|
||||
// note that httpbackend is hardcoded here as long as it is the only available backend
|
||||
backend, diags := types.ObjectValue(backendTypes, map[string]attr.Value{
|
||||
"type": types.StringValue(*distribution.Config.Backend.HttpBackend.Type),
|
||||
"origin_url": types.StringValue(*distribution.Config.Backend.HttpBackend.OriginUrl),
|
||||
"origin_request_headers": originRequestHeaders,
|
||||
"geofencing": geofencingVal,
|
||||
})
|
||||
if diags.HasError() {
|
||||
return core.DiagsToError(diags)
|
||||
|
|
@ -678,6 +796,7 @@ func toCreatePayload(ctx context.Context, model *Model) (*cdn.CreateDistribution
|
|||
Regions: cfg.Regions,
|
||||
BlockedCountries: cfg.BlockedCountries,
|
||||
OriginRequestHeaders: cfg.Backend.HttpBackend.OriginRequestHeaders,
|
||||
Geofencing: cfg.Backend.HttpBackend.Geofencing,
|
||||
Optimizer: optimizer,
|
||||
}
|
||||
|
||||
|
|
@ -722,6 +841,25 @@ func convertConfig(ctx context.Context, model *Model) (*cdn.Config, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// geofencing
|
||||
geofencing := map[string][]string{}
|
||||
if configModel.Backend.Geofencing != nil {
|
||||
for endpoint, countryCodes := range *configModel.Backend.Geofencing {
|
||||
geofencingCountry := make([]string, len(countryCodes))
|
||||
for i, countryCodePtr := range countryCodes {
|
||||
if countryCodePtr == nil {
|
||||
return nil, fmt.Errorf("geofencing url %q has a null value", endpoint)
|
||||
}
|
||||
validatedCountry, err := validateCountryCode(*countryCodePtr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
geofencingCountry[i] = validatedCountry
|
||||
}
|
||||
geofencing[endpoint] = geofencingCountry
|
||||
}
|
||||
}
|
||||
|
||||
// originRequestHeaders
|
||||
originRequestHeaders := map[string]string{}
|
||||
if configModel.Backend.OriginRequestHeaders != nil {
|
||||
|
|
@ -736,6 +874,7 @@ func convertConfig(ctx context.Context, model *Model) (*cdn.Config, error) {
|
|||
OriginRequestHeaders: &originRequestHeaders,
|
||||
OriginUrl: &configModel.Backend.OriginURL,
|
||||
Type: &configModel.Backend.Type,
|
||||
Geofencing: &geofencing,
|
||||
},
|
||||
},
|
||||
Regions: ®ions,
|
||||
|
|
|
|||
|
|
@ -17,10 +17,18 @@ func TestToCreatePayload(t *testing.T) {
|
|||
"testHeader1": types.StringValue("testHeaderValue1"),
|
||||
}
|
||||
originRequestHeaders := types.MapValueMust(types.StringType, headers)
|
||||
geofencingCountries := types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("DE"),
|
||||
types.StringValue("FR"),
|
||||
})
|
||||
geofencing := types.MapValueMust(geofencingTypes.ElemType, map[string]attr.Value{
|
||||
"https://de.mycoolapp.com": geofencingCountries,
|
||||
})
|
||||
backend := types.ObjectValueMust(backendTypes, map[string]attr.Value{
|
||||
"type": types.StringValue("http"),
|
||||
"origin_url": types.StringValue("https://www.mycoolapp.com"),
|
||||
"origin_request_headers": originRequestHeaders,
|
||||
"geofencing": geofencing,
|
||||
})
|
||||
regions := []attr.Value{types.StringValue("EU"), types.StringValue("US")}
|
||||
regionsFixture := types.ListValueMust(types.StringType, regions)
|
||||
|
|
@ -61,6 +69,9 @@ func TestToCreatePayload(t *testing.T) {
|
|||
OriginUrl: cdn.PtrString("https://www.mycoolapp.com"),
|
||||
Regions: &[]cdn.Region{"EU", "US"},
|
||||
BlockedCountries: &[]string{"XX", "YY", "ZZ"},
|
||||
Geofencing: &map[string][]string{
|
||||
"https://de.mycoolapp.com": {"DE", "FR"},
|
||||
},
|
||||
},
|
||||
IsValid: true,
|
||||
},
|
||||
|
|
@ -82,6 +93,9 @@ func TestToCreatePayload(t *testing.T) {
|
|||
Regions: &[]cdn.Region{"EU", "US"},
|
||||
Optimizer: cdn.NewOptimizer(true),
|
||||
BlockedCountries: &[]string{"XX", "YY", "ZZ"},
|
||||
Geofencing: &map[string][]string{
|
||||
"https://de.mycoolapp.com": {"DE", "FR"},
|
||||
},
|
||||
},
|
||||
IsValid: true,
|
||||
},
|
||||
|
|
@ -126,10 +140,18 @@ func TestConvertConfig(t *testing.T) {
|
|||
"testHeader1": types.StringValue("testHeaderValue1"),
|
||||
}
|
||||
originRequestHeaders := types.MapValueMust(types.StringType, headers)
|
||||
geofencingCountries := types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("DE"),
|
||||
types.StringValue("FR"),
|
||||
})
|
||||
geofencing := types.MapValueMust(geofencingTypes.ElemType, map[string]attr.Value{
|
||||
"https://de.mycoolapp.com": geofencingCountries,
|
||||
})
|
||||
backend := types.ObjectValueMust(backendTypes, map[string]attr.Value{
|
||||
"type": types.StringValue("http"),
|
||||
"origin_url": types.StringValue("https://www.mycoolapp.com"),
|
||||
"origin_request_headers": originRequestHeaders,
|
||||
"geofencing": geofencing,
|
||||
})
|
||||
regions := []attr.Value{types.StringValue("EU"), types.StringValue("US")}
|
||||
regionsFixture := types.ListValueMust(types.StringType, regions)
|
||||
|
|
@ -169,6 +191,9 @@ func TestConvertConfig(t *testing.T) {
|
|||
},
|
||||
OriginUrl: cdn.PtrString("https://www.mycoolapp.com"),
|
||||
Type: cdn.PtrString("http"),
|
||||
Geofencing: &map[string][]string{
|
||||
"https://de.mycoolapp.com": {"DE", "FR"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Regions: &[]cdn.Region{"EU", "US"},
|
||||
|
|
@ -194,6 +219,9 @@ func TestConvertConfig(t *testing.T) {
|
|||
},
|
||||
OriginUrl: cdn.PtrString("https://www.mycoolapp.com"),
|
||||
Type: cdn.PtrString("http"),
|
||||
Geofencing: &map[string][]string{
|
||||
"https://de.mycoolapp.com": {"DE", "FR"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Regions: &[]cdn.Region{"EU", "US"},
|
||||
|
|
@ -246,11 +274,17 @@ func TestMapFields(t *testing.T) {
|
|||
"type": types.StringValue("http"),
|
||||
"origin_url": types.StringValue("https://www.mycoolapp.com"),
|
||||
"origin_request_headers": originRequestHeaders,
|
||||
"geofencing": types.MapNull(geofencingTypes.ElemType),
|
||||
})
|
||||
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)
|
||||
geofencingCountries := types.ListValueMust(types.StringType, []attr.Value{types.StringValue("DE"), types.StringValue("BR")})
|
||||
geofencing := types.MapValueMust(geofencingTypes.ElemType, map[string]attr.Value{
|
||||
"test/": geofencingCountries,
|
||||
})
|
||||
geofencingInput := map[string][]string{"test/": {"DE", "BR"}}
|
||||
optimizer := types.ObjectValueMust(optimizerTypes, map[string]attr.Value{
|
||||
"enabled": types.BoolValue(true),
|
||||
})
|
||||
|
|
@ -347,6 +381,26 @@ func TestMapFields(t *testing.T) {
|
|||
}),
|
||||
IsValid: true,
|
||||
},
|
||||
"happy_path_with_geofencing": {
|
||||
Expected: expectedModel(func(m *Model) {
|
||||
backendWithGeofencing := types.ObjectValueMust(backendTypes, map[string]attr.Value{
|
||||
"type": types.StringValue("http"),
|
||||
"origin_url": types.StringValue("https://www.mycoolapp.com"),
|
||||
"origin_request_headers": originRequestHeaders,
|
||||
"geofencing": geofencing,
|
||||
})
|
||||
m.Config = types.ObjectValueMust(configTypes, map[string]attr.Value{
|
||||
"backend": backendWithGeofencing,
|
||||
"regions": regionsFixture,
|
||||
"optimizer": types.ObjectNull(optimizerTypes),
|
||||
"blocked_countries": blockedCountriesFixture,
|
||||
})
|
||||
}),
|
||||
Input: distributionFixture(func(d *cdn.Distribution) {
|
||||
d.Config.Backend.HttpBackend.Geofencing = &geofencingInput
|
||||
}),
|
||||
IsValid: true,
|
||||
},
|
||||
"happy_path_status_error": {
|
||||
Expected: expectedModel(func(m *Model) {
|
||||
m.Status = types.StringValue("ERROR")
|
||||
|
|
@ -412,7 +466,7 @@ func TestMapFields(t *testing.T) {
|
|||
for tn, tc := range tests {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
model := &Model{}
|
||||
err := mapFields(tc.Input, model)
|
||||
err := mapFields(context.Background(), tc.Input, model)
|
||||
if err != nil && tc.IsValid {
|
||||
t.Fatalf("Error mapping fields: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,19 @@ func SimplifyBackupSchedule(schedule string) string {
|
|||
return simplifiedSchedule
|
||||
}
|
||||
|
||||
// ConvertPointerSliceToStringSlice safely converts a slice of string pointers to a slice of strings.
|
||||
func ConvertPointerSliceToStringSlice(pointerSlice []*string) []string {
|
||||
if pointerSlice == nil {
|
||||
return []string{}
|
||||
}
|
||||
stringSlice := make([]string, 0, len(pointerSlice))
|
||||
for _, strPtr := range pointerSlice {
|
||||
if strPtr != nil { // Safely skip any nil pointers in the list
|
||||
stringSlice = append(stringSlice, *strPtr)
|
||||
}
|
||||
}
|
||||
return stringSlice
|
||||
}
|
||||
func SupportedValuesDocumentation(values []string) string {
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
)
|
||||
|
||||
func TestReconcileStrLists(t *testing.T) {
|
||||
|
|
@ -128,6 +129,55 @@ func TestListValuetoStrSlice(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestConvertPointerSliceToStringSlice(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input []*string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
description: "nil slice",
|
||||
input: nil,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
description: "empty slice",
|
||||
input: []*string{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
description: "slice with valid pointers",
|
||||
input: []*string{utils.Ptr("apple"), utils.Ptr("banana"), utils.Ptr("cherry")},
|
||||
expected: []string{"apple", "banana", "cherry"},
|
||||
},
|
||||
{
|
||||
description: "slice with some nil pointers",
|
||||
input: []*string{utils.Ptr("apple"), nil, utils.Ptr("cherry"), nil},
|
||||
expected: []string{"apple", "cherry"},
|
||||
},
|
||||
{
|
||||
description: "slice with all nil pointers",
|
||||
input: []*string{nil, nil, nil},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
description: "slice with a pointer to an empty string",
|
||||
input: []*string{utils.Ptr("apple"), utils.Ptr(""), utils.Ptr("cherry")},
|
||||
expected: []string{"apple", "", "cherry"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
output := ConvertPointerSliceToStringSlice(tt.input)
|
||||
diff := cmp.Diff(output, tt.expected)
|
||||
if diff != "" {
|
||||
t.Fatalf("Data does not match: %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimplifyBackupSchedule(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue