Skip to content

Commit 7ea1195

Browse files
committed
Fix Spectrum drift from optional attributes
Add new Spectrum tests to cover - TLS - ProxyProtocol - IPFirewall - ArgoSmartRouting - TrafficType - IPv6Connectivity - UDP
1 parent 642f70a commit 7ea1195

11 files changed

+438
-9
lines changed

internal/services/spectrum_application/resource.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ func (r *SpectrumApplicationResource) Read(ctx context.Context, req resource.Rea
174174
return
175175
}
176176
bytes, _ := io.ReadAll(res.Body)
177-
err = apijson.Unmarshal(bytes, &env)
177+
err = apijson.UnmarshalComputed(bytes, &env)
178178
if err != nil {
179179
resp.Diagnostics.AddError("failed to deserialize http request", err.Error())
180180
return
@@ -244,7 +244,7 @@ func (r *SpectrumApplicationResource) ImportState(ctx context.Context, req resou
244244
return
245245
}
246246
bytes, _ := io.ReadAll(res.Body)
247-
err = apijson.Unmarshal(bytes, &env)
247+
err = apijson.UnmarshalComputed(bytes, &env)
248248
if err != nil {
249249
resp.Diagnostics.AddError("failed to deserialize http request", err.Error())
250250
return

internal/services/spectrum_application/resource_test.go

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ func TestAccCloudflareSpectrumApplication_OriginDNS(t *testing.T) {
121121
resource.TestCheckResourceAttr(name, "origin_dns.name", fmt.Sprintf("%s.origin.%s", rnd, domain)),
122122
resource.TestCheckResourceAttr(name, "origin_port", "22"),
123123
),
124+
// ExpectNonEmptyPlan due to DNS record drift
125+
ExpectNonEmptyPlan: true,
124126
},
125127
},
126128
})
@@ -314,6 +316,234 @@ func TestAccCloudflareSpectrumApplication_BasicMinecraft(t *testing.T) {
314316
})
315317
}
316318

319+
func TestAccCloudflareSpectrumApplication_TLS(t *testing.T) {
320+
var spectrumApp cloudflare.SpectrumApplication
321+
domain := os.Getenv("CLOUDFLARE_DOMAIN")
322+
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
323+
rnd := utils.GenerateRandomResourceName()
324+
name := "cloudflare_spectrum_application." + rnd
325+
326+
resource.Test(t, resource.TestCase{
327+
PreCheck: func() { acctest.TestAccPreCheck(t) },
328+
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
329+
Steps: []resource.TestStep{
330+
{
331+
Config: testAccCheckCloudflareSpectrumApplicationConfigTLS(zoneID, domain, rnd, "flexible"),
332+
Check: resource.ComposeTestCheckFunc(
333+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
334+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
335+
resource.TestCheckResourceAttr(name, "tls", "flexible"),
336+
),
337+
},
338+
{
339+
Config: testAccCheckCloudflareSpectrumApplicationConfigTLS(zoneID, domain, rnd, "full"),
340+
Check: resource.ComposeTestCheckFunc(
341+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
342+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
343+
resource.TestCheckResourceAttr(name, "tls", "full"),
344+
),
345+
},
346+
{
347+
Config: testAccCheckCloudflareSpectrumApplicationConfigTLS(zoneID, domain, rnd, "strict"),
348+
Check: resource.ComposeTestCheckFunc(
349+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
350+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
351+
resource.TestCheckResourceAttr(name, "tls", "strict"),
352+
),
353+
},
354+
},
355+
})
356+
}
357+
358+
func TestAccCloudflareSpectrumApplication_ProxyProtocol(t *testing.T) {
359+
var spectrumApp cloudflare.SpectrumApplication
360+
domain := os.Getenv("CLOUDFLARE_DOMAIN")
361+
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
362+
rnd := utils.GenerateRandomResourceName()
363+
name := "cloudflare_spectrum_application." + rnd
364+
365+
resource.Test(t, resource.TestCase{
366+
PreCheck: func() { acctest.TestAccPreCheck(t) },
367+
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
368+
Steps: []resource.TestStep{
369+
{
370+
Config: testAccCheckCloudflareSpectrumApplicationConfigProxyProtocol(zoneID, domain, rnd, "v1"),
371+
Check: resource.ComposeTestCheckFunc(
372+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
373+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
374+
resource.TestCheckResourceAttr(name, "proxy_protocol", "v1"),
375+
),
376+
},
377+
{
378+
Config: testAccCheckCloudflareSpectrumApplicationConfigProxyProtocol(zoneID, domain, rnd, "v2"),
379+
Check: resource.ComposeTestCheckFunc(
380+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
381+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
382+
resource.TestCheckResourceAttr(name, "proxy_protocol", "v2"),
383+
),
384+
},
385+
},
386+
})
387+
}
388+
389+
func TestAccCloudflareSpectrumApplication_IPFirewall(t *testing.T) {
390+
var spectrumApp cloudflare.SpectrumApplication
391+
domain := os.Getenv("CLOUDFLARE_DOMAIN")
392+
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
393+
rnd := utils.GenerateRandomResourceName()
394+
name := "cloudflare_spectrum_application." + rnd
395+
396+
resource.Test(t, resource.TestCase{
397+
PreCheck: func() { acctest.TestAccPreCheck(t) },
398+
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
399+
Steps: []resource.TestStep{
400+
{
401+
Config: testAccCheckCloudflareSpectrumApplicationConfigIPFirewall(zoneID, domain, rnd, "true"),
402+
Check: resource.ComposeTestCheckFunc(
403+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
404+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
405+
resource.TestCheckResourceAttr(name, "ip_firewall", "true"),
406+
),
407+
},
408+
{
409+
Config: testAccCheckCloudflareSpectrumApplicationConfigIPFirewall(zoneID, domain, rnd, "false"),
410+
Check: resource.ComposeTestCheckFunc(
411+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
412+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
413+
resource.TestCheckResourceAttr(name, "ip_firewall", "false"),
414+
),
415+
},
416+
},
417+
})
418+
}
419+
420+
func TestAccCloudflareSpectrumApplication_ArgoSmartRouting(t *testing.T) {
421+
var spectrumApp cloudflare.SpectrumApplication
422+
domain := os.Getenv("CLOUDFLARE_DOMAIN")
423+
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
424+
rnd := utils.GenerateRandomResourceName()
425+
name := "cloudflare_spectrum_application." + rnd
426+
427+
resource.Test(t, resource.TestCase{
428+
PreCheck: func() { acctest.TestAccPreCheck(t) },
429+
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
430+
Steps: []resource.TestStep{
431+
{
432+
Config: testAccCheckCloudflareSpectrumApplicationConfigArgoSmartRouting(zoneID, domain, rnd, "true"),
433+
Check: resource.ComposeTestCheckFunc(
434+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
435+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
436+
resource.TestCheckResourceAttr(name, "argo_smart_routing", "true"),
437+
resource.TestCheckResourceAttr(name, "traffic_type", "direct"),
438+
),
439+
},
440+
{
441+
Config: testAccCheckCloudflareSpectrumApplicationConfigArgoSmartRouting(zoneID, domain, rnd, "false"),
442+
Check: resource.ComposeTestCheckFunc(
443+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
444+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
445+
resource.TestCheckResourceAttr(name, "argo_smart_routing", "false"),
446+
resource.TestCheckResourceAttr(name, "traffic_type", "direct"),
447+
),
448+
},
449+
},
450+
})
451+
}
452+
453+
func TestAccCloudflareSpectrumApplication_TrafficType(t *testing.T) {
454+
var spectrumApp cloudflare.SpectrumApplication
455+
domain := os.Getenv("CLOUDFLARE_DOMAIN")
456+
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
457+
rnd := utils.GenerateRandomResourceName()
458+
name := "cloudflare_spectrum_application." + rnd
459+
460+
resource.Test(t, resource.TestCase{
461+
PreCheck: func() { acctest.TestAccPreCheck(t) },
462+
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
463+
Steps: []resource.TestStep{
464+
{
465+
Config: testAccCheckCloudflareSpectrumApplicationConfigTrafficType(zoneID, domain, rnd, "http"),
466+
Check: resource.ComposeTestCheckFunc(
467+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
468+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
469+
resource.TestCheckResourceAttr(name, "traffic_type", "http"),
470+
),
471+
},
472+
{
473+
Config: testAccCheckCloudflareSpectrumApplicationConfigTrafficType(zoneID, domain, rnd, "https"),
474+
Check: resource.ComposeTestCheckFunc(
475+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
476+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
477+
resource.TestCheckResourceAttr(name, "traffic_type", "https"),
478+
),
479+
},
480+
{
481+
Config: testAccCheckCloudflareSpectrumApplicationConfigTrafficType(zoneID, domain, rnd, "direct"),
482+
Check: resource.ComposeTestCheckFunc(
483+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
484+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
485+
resource.TestCheckResourceAttr(name, "traffic_type", "direct"),
486+
),
487+
},
488+
},
489+
})
490+
}
491+
492+
func TestAccCloudflareSpectrumApplication_IPv6Connectivity(t *testing.T) {
493+
var spectrumApp cloudflare.SpectrumApplication
494+
domain := os.Getenv("CLOUDFLARE_DOMAIN")
495+
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
496+
rnd := utils.GenerateRandomResourceName()
497+
name := "cloudflare_spectrum_application." + rnd
498+
499+
resource.Test(t, resource.TestCase{
500+
PreCheck: func() { acctest.TestAccPreCheck(t) },
501+
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
502+
Steps: []resource.TestStep{
503+
{
504+
Config: testAccCheckCloudflareSpectrumApplicationConfigIPv6(zoneID, domain, rnd),
505+
Check: resource.ComposeTestCheckFunc(
506+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
507+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
508+
resource.TestCheckResourceAttr(name, "edge_ips.connectivity", "ipv6"),
509+
),
510+
},
511+
},
512+
})
513+
}
514+
515+
func TestAccCloudflareSpectrumApplication_UDP(t *testing.T) {
516+
var spectrumApp cloudflare.SpectrumApplication
517+
domain := os.Getenv("CLOUDFLARE_DOMAIN")
518+
zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")
519+
rnd := utils.GenerateRandomResourceName()
520+
name := "cloudflare_spectrum_application." + rnd
521+
522+
resource.Test(t, resource.TestCase{
523+
PreCheck: func() { acctest.TestAccPreCheck(t) },
524+
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
525+
Steps: []resource.TestStep{
526+
{
527+
Config: testAccCheckCloudflareSpectrumApplicationConfigUDP(zoneID, domain, rnd),
528+
Check: resource.ComposeTestCheckFunc(
529+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
530+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
531+
resource.TestCheckResourceAttr(name, "protocol", "udp/53"),
532+
resource.TestCheckResourceAttr(name, "traffic_type", "direct"),
533+
),
534+
},
535+
{
536+
Config: testAccCheckCloudflareSpectrumApplicationConfigSimpleProxyProtocol(zoneID, domain, rnd),
537+
Check: resource.ComposeTestCheckFunc(
538+
testAccCheckCloudflareSpectrumApplicationExists(name, &spectrumApp),
539+
testAccCheckCloudflareSpectrumApplicationIDIsValid(name),
540+
resource.TestCheckResourceAttr(name, "proxy_protocol", "simple"),
541+
),
542+
},
543+
},
544+
})
545+
}
546+
317547
func testAccCheckCloudflareSpectrumApplicationExists(n string, spectrumApp *cloudflare.SpectrumApplication) resource.TestCheckFunc {
318548
return func(s *terraform.State) error {
319549
rs, ok := s.RootModule().Resources[n]
@@ -409,3 +639,35 @@ func testAccCheckCloudflareSpectrumApplicationConfigMultipleEdgeIPs(zoneID, zone
409639
func testAccCheckCloudflareSpectrumApplicationConfigBasicTypes(zoneID, zoneName, ID, protocol string, port int) string {
410640
return acctest.LoadTestCase("spectrumapplicationconfigbasictypes.tf", zoneID, zoneName, ID, protocol, port)
411641
}
642+
643+
func testAccCheckCloudflareSpectrumApplicationConfigTLS(zoneID, zoneName, ID, tls string) string {
644+
return acctest.LoadTestCase("spectrumapplicationconfigtls.tf", zoneID, zoneName, ID, tls)
645+
}
646+
647+
func testAccCheckCloudflareSpectrumApplicationConfigProxyProtocol(zoneID, zoneName, ID, proxyProtocol string) string {
648+
return acctest.LoadTestCase("spectrumapplicationconfigproxyprotocol.tf", zoneID, zoneName, ID, proxyProtocol)
649+
}
650+
651+
func testAccCheckCloudflareSpectrumApplicationConfigSimpleProxyProtocol(zoneID, zoneName, ID string) string {
652+
return acctest.LoadTestCase("spectrumapplicationconfigsimpleproxyprotocol.tf", zoneID, zoneName, ID)
653+
}
654+
655+
func testAccCheckCloudflareSpectrumApplicationConfigIPFirewall(zoneID, zoneName, ID, ipFirewall string) string {
656+
return acctest.LoadTestCase("spectrumapplicationconfigipfirewall.tf", zoneID, zoneName, ID, ipFirewall)
657+
}
658+
659+
func testAccCheckCloudflareSpectrumApplicationConfigArgoSmartRouting(zoneID, zoneName, ID, argoSmartRouting string) string {
660+
return acctest.LoadTestCase("spectrumapplicationconfigargosmartrouting.tf", zoneID, zoneName, ID, argoSmartRouting)
661+
}
662+
663+
func testAccCheckCloudflareSpectrumApplicationConfigTrafficType(zoneID, zoneName, ID, trafficType string) string {
664+
return acctest.LoadTestCase("spectrumapplicationconfigtraffictype.tf", zoneID, zoneName, ID, trafficType)
665+
}
666+
667+
func testAccCheckCloudflareSpectrumApplicationConfigIPv6(zoneID, zoneName, ID string) string {
668+
return acctest.LoadTestCase("spectrumapplicationconfigipv6.tf", zoneID, zoneName, ID)
669+
}
670+
671+
func testAccCheckCloudflareSpectrumApplicationConfigUDP(zoneID, zoneName, ID string) string {
672+
return acctest.LoadTestCase("spectrumapplicationconfigudp.tf", zoneID, zoneName, ID)
673+
}

internal/services/spectrum_application/schema.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/hashicorp/terraform-plugin-framework/resource"
1414
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
1515
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
16+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
1617
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
1718
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
1819
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
@@ -58,11 +59,15 @@ func ResourceSchema(ctx context.Context) schema.Schema {
5859
},
5960
},
6061
"ip_firewall": schema.BoolAttribute{
62+
Computed: true,
6163
Description: "Enables IP Access Rules for this application.\nNotes: Only available for TCP applications.",
64+
Default: booldefault.StaticBool(false),
6265
Optional: true,
6366
},
6467
"tls": schema.StringAttribute{
68+
Computed: true,
6569
Description: "The type of TLS termination associated with the application.\nAvailable values: \"off\", \"flexible\", \"full\", \"strict\".",
70+
Default: stringdefault.StaticString("off"),
6671
Optional: true,
6772
Validators: []validator.String{
6873
stringvalidator.OneOfCaseInsensitive(
@@ -115,14 +120,16 @@ func ResourceSchema(ctx context.Context) schema.Schema {
115120
},
116121
},
117122
"argo_smart_routing": schema.BoolAttribute{
118-
Description: "Enables Argo Smart Routing for this application.\nNotes: Only available for TCP applications with traffic_type set to \"direct\".",
119-
Computed: true,
120-
Optional: true,
121-
Default: booldefault.StaticBool(false),
123+
Computed: true,
124+
Default: booldefault.StaticBool(false),
125+
Description: "Enables Argo Smart Routing for this application.\nNotes: Only available for TCP applications with traffic_type set to \"direct\".",
126+
Optional: true,
127+
PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()},
122128
},
123129
"proxy_protocol": schema.StringAttribute{
124-
Description: "Enables Proxy Protocol to the origin. Refer to [Enable Proxy protocol](https://developers.cloudflare.com/spectrum/getting-started/proxy-protocol/) for implementation details on PROXY Protocol V1, PROXY Protocol V2, and Simple Proxy Protocol.\nAvailable values: \"off\", \"v1\", \"v2\", \"simple\".",
125130
Computed: true,
131+
Default: stringdefault.StaticString("off"),
132+
Description: "Enables Proxy Protocol to the origin. Refer to [Enable Proxy protocol](https://developers.cloudflare.com/spectrum/getting-started/proxy-protocol/) for implementation details on PROXY Protocol V1, PROXY Protocol V2, and Simple Proxy Protocol.\nAvailable values: \"off\", \"v1\", \"v2\", \"simple\".",
126133
Optional: true,
127134
Validators: []validator.String{
128135
stringvalidator.OneOfCaseInsensitive(
@@ -132,7 +139,7 @@ func ResourceSchema(ctx context.Context) schema.Schema {
132139
"simple",
133140
),
134141
},
135-
Default: stringdefault.StaticString("off"),
142+
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
136143
},
137144
"traffic_type": schema.StringAttribute{
138145
Description: "Determines how data travels from the edge to your origin. When set to \"direct\", Spectrum will send traffic directly to your origin, and the application's type is derived from the `protocol`. When set to \"http\" or \"https\", Spectrum will apply Cloudflare's HTTP/HTTPS features as it sends traffic to your origin, and the application type matches this property exactly.\nAvailable values: \"direct\", \"http\", \"https\".",
@@ -145,7 +152,8 @@ func ResourceSchema(ctx context.Context) schema.Schema {
145152
"https",
146153
),
147154
},
148-
Default: stringdefault.StaticString("direct"),
155+
Default: stringdefault.StaticString("direct"),
156+
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
149157
},
150158
"edge_ips": schema.SingleNestedAttribute{
151159
Description: "The anycast edge IP configuration for the hostname of this application.",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
resource "cloudflare_spectrum_application" "%[3]s" {
2+
zone_id = "%[1]s"
3+
protocol = "tcp/22"
4+
5+
dns = {
6+
type = "CNAME"
7+
name = "%[3]s.%[2]s"
8+
}
9+
10+
origin_direct = ["tcp://128.66.0.8:22"]
11+
origin_port = 22
12+
13+
# Test Argo Smart Routing configuration
14+
argo_smart_routing = %[4]s # true or false
15+
traffic_type = "direct" # Required for Argo Smart Routing
16+
17+
edge_ips = {
18+
type = "dynamic"
19+
connectivity = "all"
20+
}
21+
}

0 commit comments

Comments
 (0)