@@ -15,6 +15,7 @@ import (
1515 "time"
1616
1717 "github.com/google/uuid"
18+ "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
1819 "github.com/hashicorp/terraform-plugin-testing/helper/resource"
1920 "github.com/hashicorp/terraform-plugin-testing/terraform"
2021 "github.com/stackitcloud/stackit-sdk-go/core/config"
@@ -357,6 +358,182 @@ func TestAccCDNDistributionResource(t *testing.T) {
357358 },
358359 })
359360}
361+
362+ func configBucketResources (bucketName , credentialsGroupName string ) string {
363+ return fmt .Sprintf (`
364+ %s
365+
366+ resource "stackit_objectstorage_bucket" "bucket" {
367+ project_id = "%s"
368+ name = "%s"
369+ }
370+
371+ resource "stackit_objectstorage_credentials_group" "group" {
372+ project_id = "%s"
373+ name = "%s"
374+ }
375+
376+ resource "stackit_objectstorage_credential" "creds" {
377+ project_id = "%s"
378+ credentials_group_id = stackit_objectstorage_credentials_group.group.credentials_group_id
379+ }
380+
381+ resource "stackit_cdn_distribution" "distribution" {
382+ project_id = "%s"
383+ config = {
384+ backend = {
385+ type = "bucket"
386+ # Construct the URL dynamically using the bucket name
387+ bucket_url = "https://${stackit_objectstorage_bucket.bucket.name}.object.storage.eu01.onstackit.cloud"
388+ region = "eu01"
389+
390+ # Pass the keys via credentials block
391+ credentials = {
392+ access_key_id = stackit_objectstorage_credential.creds.access_key
393+ secret_access_key = stackit_objectstorage_credential.creds.secret_access_key
394+ }
395+ }
396+ regions = ["EU", "US"]
397+ blocked_countries = ["CN", "RU"]
398+
399+ optimizer = {
400+ enabled = false
401+ }
402+ }
403+ }
404+ ` , testutil .CdnProviderConfig (),
405+ testutil .ProjectId , bucketName ,
406+ testutil .ProjectId , credentialsGroupName ,
407+ testutil .ProjectId ,
408+ testutil .ProjectId ,
409+ )
410+ }
411+
412+ func configBucketDatasource (bucketName , credentialsGroupName string ) string {
413+ return fmt .Sprintf (`
414+ %s
415+
416+ data "stackit_cdn_distribution" "bucket_ds" {
417+ project_id = stackit_cdn_distribution.distribution.project_id
418+ distribution_id = stackit_cdn_distribution.distribution.distribution_id
419+ }
420+ ` , configBucketResources (bucketName , credentialsGroupName ))
421+ }
422+
423+ func TestAccCDNDistributionBucketResource (t * testing.T ) {
424+ bucketName := fmt .Sprintf ("tf-acc-bucket-%s" , acctest .RandStringFromCharSet (5 , acctest .CharSetAlphaNum ))
425+ credentialsGroupName := fmt .Sprintf ("tf-acc-group-%s" , acctest .RandStringFromCharSet (5 , acctest .CharSetAlphaNum ))
426+ bucketNameUpdated := fmt .Sprintf ("tf-acc-bucket-upd-%s" , acctest .RandStringFromCharSet (5 , acctest .CharSetAlphaNum ))
427+ credentialsGroupNameUpdated := fmt .Sprintf ("tf-acc-group-upd-%s" , acctest .RandStringFromCharSet (5 , acctest .CharSetAlphaNum ))
428+
429+ expectedBucketUrl := fmt .Sprintf ("https://%s.object.storage.eu01.onstackit.cloud" , bucketName )
430+ expectedBucketUrlUpdated := fmt .Sprintf ("https://%s.object.storage.eu01.onstackit.cloud" , bucketNameUpdated )
431+
432+ resource .Test (t , resource.TestCase {
433+ ProtoV6ProviderFactories : testutil .TestAccProtoV6ProviderFactories ,
434+ CheckDestroy : testAccCheckCDNDistributionDestroy ,
435+ Steps : []resource.TestStep {
436+ // Step 1: Create Resource (Real Bucket & Creds)
437+ {
438+ Config : configBucketResources (bucketName , credentialsGroupName ),
439+ Check : resource .ComposeAggregateTestCheckFunc (
440+ resource .TestCheckResourceAttrSet ("stackit_cdn_distribution.distribution" , "distribution_id" ),
441+ resource .TestCheckResourceAttrSet ("stackit_cdn_distribution.distribution" , "created_at" ),
442+ resource .TestCheckResourceAttrSet ("stackit_cdn_distribution.distribution" , "updated_at" ),
443+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "project_id" , testutil .ProjectId ),
444+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "status" , "ACTIVE" ),
445+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "domains.#" , "1" ),
446+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "domains.0.type" , "managed" ),
447+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "domains.0.status" , "ACTIVE" ),
448+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "config.regions.#" , "2" ),
449+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "config.regions.0" , "EU" ),
450+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "config.regions.1" , "US" ),
451+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "config.blocked_countries.#" , "2" ),
452+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "config.blocked_countries.0" , "CN" ),
453+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "config.blocked_countries.1" , "RU" ),
454+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "config.optimizer.enabled" , "false" ),
455+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "config.backend.type" , "bucket" ),
456+
457+ // Verify the Bucket URL matches the one we constructed
458+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "config.backend.bucket_url" , expectedBucketUrl ),
459+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "config.backend.region" , "eu01" ),
460+
461+ // CRITICAL: Verify that the CDN keys match the Object Storage keys
462+ // We use AttrPair because the values are generated dynamically on the server side
463+ resource .TestCheckResourceAttrPair (
464+ "stackit_cdn_distribution.distribution" , "config.backend.credentials.access_key_id" ,
465+ "stackit_objectstorage_credential.creds" , "access_key" ,
466+ ),
467+ resource .TestCheckResourceAttrPair (
468+ "stackit_cdn_distribution.distribution" , "config.backend.credentials.secret_access_key" ,
469+ "stackit_objectstorage_credential.creds" , "secret_access_key" ,
470+ ),
471+ ),
472+ },
473+ {
474+ ResourceName : "stackit_cdn_distribution.distribution" ,
475+ ImportStateIdFunc : func (s * terraform.State ) (string , error ) {
476+ r , ok := s .RootModule ().Resources ["stackit_cdn_distribution.distribution" ]
477+ if ! ok {
478+ return "" , fmt .Errorf ("couldn't find resource" )
479+ }
480+ return fmt .Sprintf ("%s,%s" , testutil .ProjectId , r .Primary .Attributes ["distribution_id" ]), nil
481+ },
482+ ImportState : true ,
483+ ImportStateVerify : true ,
484+ // We MUST ignore credentials on import verification
485+ // 1. API doesn't return them (security).
486+ // 2. State has them (from resource creation).
487+ ImportStateVerifyIgnore : []string {"config.backend.credentials" },
488+ },
489+ // Step 3: Data Source
490+ {
491+ Config : configBucketDatasource (bucketName , credentialsGroupName ),
492+ Check : resource .ComposeAggregateTestCheckFunc (
493+ resource .TestCheckResourceAttrSet ("data.stackit_cdn_distribution.bucket_ds" , "distribution_id" ),
494+ resource .TestCheckResourceAttrSet ("data.stackit_cdn_distribution.bucket_ds" , "created_at" ),
495+ resource .TestCheckResourceAttrSet ("data.stackit_cdn_distribution.bucket_ds" , "updated_at" ),
496+ resource .TestCheckResourceAttrPair ("data.stackit_cdn_distribution.bucket_ds" , "project_id" , "stackit_cdn_distribution.distribution" , "project_id" ),
497+ resource .TestCheckResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "status" , "ACTIVE" ),
498+ resource .TestCheckResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "domains.#" , "1" ),
499+ resource .TestCheckResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "domains.0.type" , "managed" ),
500+ resource .TestCheckResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "domains.0.status" , "ACTIVE" ),
501+ resource .TestCheckResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "config.regions.#" , "2" ),
502+ resource .TestCheckResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "config.regions.0" , "EU" ),
503+ resource .TestCheckResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "config.regions.1" , "US" ),
504+ resource .TestCheckResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "config.blocked_countries.#" , "2" ),
505+ resource .TestCheckResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "config.blocked_countries.0" , "CN" ),
506+ resource .TestCheckResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "config.blocked_countries.1" , "RU" ),
507+ resource .TestCheckResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "config.optimizer.enabled" , "false" ),
508+ resource .TestCheckResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "config.backend.type" , "bucket" ),
509+ resource .TestCheckResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "config.backend.bucket_url" , expectedBucketUrl ),
510+
511+ // Security Check: Secrets should NOT be in Data Source
512+ resource .TestCheckNoResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "config.backend.credentials.access_key_id" ),
513+ resource .TestCheckNoResourceAttr ("data.stackit_cdn_distribution.bucket_ds" , "config.backend.credentials.secret_access_key" ),
514+ ),
515+ },
516+ // Step 4: Update Resource (Change Bucket & Creds)
517+ {
518+ Config : configBucketResources (bucketNameUpdated , credentialsGroupNameUpdated ),
519+ Check : resource .ComposeAggregateTestCheckFunc (
520+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "status" , "ACTIVE" ),
521+ resource .TestCheckResourceAttr ("stackit_cdn_distribution.distribution" , "config.backend.bucket_url" , expectedBucketUrlUpdated ),
522+
523+ // Verify that keys have been updated to the new credentials
524+ resource .TestCheckResourceAttrPair (
525+ "stackit_cdn_distribution.distribution" , "config.backend.credentials.access_key_id" ,
526+ "stackit_objectstorage_credential.creds" , "access_key" ,
527+ ),
528+ resource .TestCheckResourceAttrPair (
529+ "stackit_cdn_distribution.distribution" , "config.backend.credentials.secret_access_key" ,
530+ "stackit_objectstorage_credential.creds" , "secret_access_key" ,
531+ ),
532+ ),
533+ },
534+ },
535+ })
536+ }
360537func testAccCheckCDNDistributionDestroy (s * terraform.State ) error {
361538 ctx := context .Background ()
362539 var client * cdn.APIClient
@@ -400,9 +577,26 @@ const (
400577)
401578
402579func blockUntilDomainResolves (domain string ) (net.IP , error ) {
580+ // Create a custom resolver that bypasses the local system DNS settings/cache
581+ // and queries Google DNS (8.8.8.8) directly.
582+ r := & net.Resolver {
583+ PreferGo : true ,
584+ Dial : func (ctx context.Context , network , _ string ) (net.Conn , error ) {
585+ d := net.Dialer {
586+ Timeout : time .Millisecond * time .Duration (10000 ),
587+ }
588+ // Force query to Google DNS
589+ return d .DialContext (ctx , network , "8.8.8.8:53" )
590+ },
591+ }
592+
403593 // wait until it becomes ready
404594 isReady := func () (net.IP , error ) {
405- ips , err := net .LookupIP (domain )
595+ // Use a context for the individual query timeout
596+ ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Second )
597+ defer cancel ()
598+
599+ ips , err := r .LookupIP (ctx , "ip" , domain )
406600 if err != nil {
407601 return nil , fmt .Errorf ("error looking up IP for domain %s: %w" , domain , err )
408602 }
@@ -413,6 +607,7 @@ func blockUntilDomainResolves(domain string) (net.IP, error) {
413607 }
414608 return nil , fmt .Errorf ("no IP for domain: %v" , domain )
415609 }
610+
416611 return retry (recordCheckAttempts , recordCheckInterval , isReady )
417612}
418613
0 commit comments