Skip to content

Commit 3eee40c

Browse files
catsbyjen20
authored andcommitted
provider/fastly: Add support for Conditions for Fastly Services (hashicorp#6481)
* provider/fastly: Add support for Conditions for Fastly Services Docs here: - https://docs.fastly.com/guides/conditions/ Also Bump go-fastly version for domain support in S3 Logging
1 parent dbdf9f6 commit 3eee40c

4 files changed

Lines changed: 284 additions & 35 deletions

File tree

builtin/providers/fastly/resource_fastly_service_v1.go

Lines changed: 138 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,39 @@ func resourceServiceV1() *schema.Resource {
5555
},
5656
},
5757

58+
"condition": &schema.Schema{
59+
Type: schema.TypeSet,
60+
Optional: true,
61+
Elem: &schema.Resource{
62+
Schema: map[string]*schema.Schema{
63+
"name": &schema.Schema{
64+
Type: schema.TypeString,
65+
Required: true,
66+
},
67+
"statement": &schema.Schema{
68+
Type: schema.TypeString,
69+
Required: true,
70+
Description: "The statement used to determine if the condition is met",
71+
StateFunc: func(v interface{}) string {
72+
value := v.(string)
73+
// Trim newlines and spaces, to match Fastly API
74+
return strings.TrimSpace(value)
75+
},
76+
},
77+
"priority": &schema.Schema{
78+
Type: schema.TypeInt,
79+
Required: true,
80+
Description: "A number used to determine the order in which multiple conditions execute. Lower numbers execute first",
81+
},
82+
"type": &schema.Schema{
83+
Type: schema.TypeString,
84+
Required: true,
85+
Description: "Type of the condition, either `REQUEST`, `RESPONSE`, or `CACHE`",
86+
},
87+
},
88+
},
89+
},
90+
5891
"default_ttl": &schema.Schema{
5992
Type: schema.TypeInt,
6093
Optional: true,
@@ -409,6 +442,7 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
409442
"header",
410443
"gzip",
411444
"s3logging",
445+
"condition",
412446
} {
413447
if d.HasChange(v) {
414448
needsChange = true
@@ -463,13 +497,70 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
463497
}
464498
}
465499

500+
// Conditions need to be updated first, as they can be referenced by other
501+
// configuraiton objects (Backends, Request Headers, etc)
502+
503+
// Find difference in Conditions
504+
if d.HasChange("condition") {
505+
// Note: we don't utilize the PUT endpoint to update these objects, we simply
506+
// destroy any that have changed, and create new ones with the updated
507+
// values. This is how Terraform works with nested sub resources, we only
508+
// get the full diff not a partial set item diff. Because this is done
509+
// on a new version of the Fastly Service configuration, this is considered safe
510+
511+
oc, nc := d.GetChange("condition")
512+
if oc == nil {
513+
oc = new(schema.Set)
514+
}
515+
if nc == nil {
516+
nc = new(schema.Set)
517+
}
518+
519+
ocs := oc.(*schema.Set)
520+
ncs := nc.(*schema.Set)
521+
removeConditions := ocs.Difference(ncs).List()
522+
addConditions := ncs.Difference(ocs).List()
523+
524+
// DELETE old Conditions
525+
for _, cRaw := range removeConditions {
526+
cf := cRaw.(map[string]interface{})
527+
opts := gofastly.DeleteConditionInput{
528+
Service: d.Id(),
529+
Version: latestVersion,
530+
Name: cf["name"].(string),
531+
}
532+
533+
log.Printf("[DEBUG] Fastly Conditions Removal opts: %#v", opts)
534+
err := conn.DeleteCondition(&opts)
535+
if err != nil {
536+
return err
537+
}
538+
}
539+
540+
// POST new Conditions
541+
for _, cRaw := range addConditions {
542+
cf := cRaw.(map[string]interface{})
543+
opts := gofastly.CreateConditionInput{
544+
Service: d.Id(),
545+
Version: latestVersion,
546+
Name: cf["name"].(string),
547+
Type: cf["type"].(string),
548+
// need to trim leading/tailing spaces, incase the config has HEREDOC
549+
// formatting and contains a trailing new line
550+
Statement: strings.TrimSpace(cf["statement"].(string)),
551+
Priority: cf["priority"].(int),
552+
}
553+
554+
log.Printf("[DEBUG] Create Conditions Opts: %#v", opts)
555+
_, err := conn.CreateCondition(&opts)
556+
if err != nil {
557+
return err
558+
}
559+
}
560+
}
561+
466562
// Find differences in domains
467563
if d.HasChange("domain") {
468-
// Note: we don't utilize the PUT endpoint to update a Domain, we simply
469-
// destroy it and create a new one. This is how Terraform works with nested
470-
// sub resources, we only get the full diff not a partial set item diff.
471-
// Because this is done on a new version of the configuration, this is
472-
// considered safe
473564
od, nd := d.GetChange("domain")
474565
if od == nil {
475566
od = new(schema.Set)
@@ -523,12 +614,6 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
523614

524615
// find difference in backends
525616
if d.HasChange("backend") {
526-
// POST new Backends
527-
// Note: we don't utilize the PUT endpoint to update a Backend, we simply
528-
// destroy it and create a new one. This is how Terraform works with nested
529-
// sub resources, we only get the full diff not a partial set item diff.
530-
// Because this is done on a new version of the configuration, this is
531-
// considered safe
532617
ob, nb := d.GetChange("backend")
533618
if ob == nil {
534619
ob = new(schema.Set)
@@ -558,6 +643,7 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
558643
}
559644
}
560645

646+
// Find and post new Backends
561647
for _, dRaw := range addBackends {
562648
df := dRaw.(map[string]interface{})
563649
opts := gofastly.CreateBackendInput{
@@ -585,11 +671,6 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
585671
}
586672

587673
if d.HasChange("header") {
588-
// Note: we don't utilize the PUT endpoint to update a Header, we simply
589-
// destroy it and create a new one. This is how Terraform works with nested
590-
// sub resources, we only get the full diff not a partial set item diff.
591-
// Because this is done on a new version of the configuration, this is
592-
// considered safe
593674
oh, nh := d.GetChange("header")
594675
if oh == nil {
595676
oh = new(schema.Set)
@@ -640,11 +721,6 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
640721

641722
// Find differences in Gzips
642723
if d.HasChange("gzip") {
643-
// Note: we don't utilize the PUT endpoint to update a Gzip rule, we simply
644-
// destroy it and create a new one. This is how Terraform works with nested
645-
// sub resources, we only get the full diff not a partial set item diff.
646-
// Because this is done on a new version of the configuration, this is
647-
// considered safe
648724
og, ng := d.GetChange("gzip")
649725
if og == nil {
650726
og = new(schema.Set)
@@ -714,12 +790,6 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
714790

715791
// find difference in s3logging
716792
if d.HasChange("s3logging") {
717-
// POST new Logging
718-
// Note: we don't utilize the PUT endpoint to update a S3 Logs, we simply
719-
// destroy it and create a new one. This is how Terraform works with nested
720-
// sub resources, we only get the full diff not a partial set item diff.
721-
// Because this is done on a new version of the configuration, this is
722-
// considered safe
723793
os, ns := d.GetChange("s3logging")
724794
if os == nil {
725795
os = new(schema.Set)
@@ -947,6 +1017,23 @@ func resourceServiceV1Read(d *schema.ResourceData, meta interface{}) error {
9471017
log.Printf("[WARN] Error setting S3 Logging for (%s): %s", d.Id(), err)
9481018
}
9491019

1020+
// refresh Conditions
1021+
log.Printf("[DEBUG] Refreshing Conditions for (%s)", d.Id())
1022+
conditionList, err := conn.ListConditions(&gofastly.ListConditionsInput{
1023+
Service: d.Id(),
1024+
Version: s.ActiveVersion.Number,
1025+
})
1026+
1027+
if err != nil {
1028+
return fmt.Errorf("[ERR] Error looking up Conditions for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
1029+
}
1030+
1031+
cl := flattenConditions(conditionList)
1032+
1033+
if err := d.Set("condition", cl); err != nil {
1034+
log.Printf("[WARN] Error setting Conditions for (%s): %s", d.Id(), err)
1035+
}
1036+
9501037
} else {
9511038
log.Printf("[DEBUG] Active Version for Service (%s) is empty, no state to refresh", d.Id())
9521039
}
@@ -1215,3 +1302,27 @@ func flattenS3s(s3List []*gofastly.S3) []map[string]interface{} {
12151302

12161303
return sl
12171304
}
1305+
1306+
func flattenConditions(conditionList []*gofastly.Condition) []map[string]interface{} {
1307+
var cl []map[string]interface{}
1308+
for _, c := range conditionList {
1309+
// Convert Conditions to a map for saving to state.
1310+
nc := map[string]interface{}{
1311+
"name": c.Name,
1312+
"statement": c.Statement,
1313+
"type": c.Type,
1314+
"priority": c.Priority,
1315+
}
1316+
1317+
// prune any empty values that come from the default string value in structs
1318+
for k, v := range nc {
1319+
if v == "" {
1320+
delete(nc, k)
1321+
}
1322+
}
1323+
1324+
cl = append(cl, nc)
1325+
}
1326+
1327+
return cl
1328+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package fastly
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"testing"
7+
8+
"github.com/hashicorp/terraform/helper/acctest"
9+
"github.com/hashicorp/terraform/helper/resource"
10+
"github.com/hashicorp/terraform/terraform"
11+
gofastly "github.com/sethvargo/go-fastly"
12+
)
13+
14+
func TestAccFastlyServiceV1_conditional_basic(t *testing.T) {
15+
var service gofastly.ServiceDetail
16+
name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
17+
domainName1 := fmt.Sprintf("%s.notadomain.com", acctest.RandString(10))
18+
19+
con1 := gofastly.Condition{
20+
Name: "some amz condition",
21+
Priority: 10,
22+
Type: "REQUEST",
23+
Statement: `req.url ~ "^/yolo/"`,
24+
}
25+
26+
resource.Test(t, resource.TestCase{
27+
PreCheck: func() { testAccPreCheck(t) },
28+
Providers: testAccProviders,
29+
CheckDestroy: testAccCheckServiceV1Destroy,
30+
Steps: []resource.TestStep{
31+
resource.TestStep{
32+
Config: testAccServiceV1ConditionConfig(name, domainName1),
33+
Check: resource.ComposeTestCheckFunc(
34+
testAccCheckServiceV1Exists("fastly_service_v1.foo", &service),
35+
testAccCheckFastlyServiceV1ConditionalAttributes(&service, name, []*gofastly.Condition{&con1}),
36+
resource.TestCheckResourceAttr(
37+
"fastly_service_v1.foo", "name", name),
38+
resource.TestCheckResourceAttr(
39+
"fastly_service_v1.foo", "condition.#", "1"),
40+
),
41+
},
42+
},
43+
})
44+
}
45+
46+
func testAccCheckFastlyServiceV1ConditionalAttributes(service *gofastly.ServiceDetail, name string, conditions []*gofastly.Condition) resource.TestCheckFunc {
47+
return func(s *terraform.State) error {
48+
49+
if service.Name != name {
50+
return fmt.Errorf("Bad name, expected (%s), got (%s)", name, service.Name)
51+
}
52+
53+
conn := testAccProvider.Meta().(*FastlyClient).conn
54+
conditionList, err := conn.ListConditions(&gofastly.ListConditionsInput{
55+
Service: service.ID,
56+
Version: service.ActiveVersion.Number,
57+
})
58+
59+
if err != nil {
60+
return fmt.Errorf("[ERR] Error looking up Conditions for (%s), version (%s): %s", service.Name, service.ActiveVersion.Number, err)
61+
}
62+
63+
if len(conditionList) != len(conditions) {
64+
return fmt.Errorf("Error: mis match count of conditions, expected (%d), got (%d)", len(conditions), len(conditionList))
65+
}
66+
67+
var found int
68+
for _, c := range conditions {
69+
for _, lc := range conditionList {
70+
if c.Name == lc.Name {
71+
// we don't know these things ahead of time, so populate them now
72+
c.ServiceID = service.ID
73+
c.Version = service.ActiveVersion.Number
74+
if !reflect.DeepEqual(c, lc) {
75+
return fmt.Errorf("Bad match Conditions match, expected (%#v), got (%#v)", c, lc)
76+
}
77+
found++
78+
}
79+
}
80+
}
81+
82+
if found != len(conditions) {
83+
return fmt.Errorf("Error matching Conditions rules")
84+
}
85+
return nil
86+
}
87+
}
88+
89+
func testAccServiceV1ConditionConfig(name, domain string) string {
90+
return fmt.Sprintf(`
91+
resource "fastly_service_v1" "foo" {
92+
name = "%s"
93+
94+
domain {
95+
name = "%s"
96+
comment = "tf-testing-domain"
97+
}
98+
99+
backend {
100+
address = "aws.amazon.com"
101+
name = "amazon docs"
102+
}
103+
104+
header {
105+
destination = "http.x-amz-request-id"
106+
type = "cache"
107+
action = "delete"
108+
name = "remove x-amz-request-id"
109+
}
110+
111+
condition {
112+
name = "some amz condition"
113+
type = "REQUEST"
114+
115+
statement = "req.url ~ \"^/yolo/\""
116+
117+
priority = 10
118+
}
119+
120+
force_destroy = true
121+
}`, name, domain)
122+
}

vendor/github.com/sethvargo/go-fastly/version.go

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)