provider/fastly: Add support for Conditions for Fastly Services (#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
This commit is contained in:
parent
dbdf9f6c84
commit
3eee40cd98
|
@ -55,6 +55,39 @@ func resourceServiceV1() *schema.Resource {
|
|||
},
|
||||
},
|
||||
|
||||
"condition": &schema.Schema{
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"statement": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "The statement used to determine if the condition is met",
|
||||
StateFunc: func(v interface{}) string {
|
||||
value := v.(string)
|
||||
// Trim newlines and spaces, to match Fastly API
|
||||
return strings.TrimSpace(value)
|
||||
},
|
||||
},
|
||||
"priority": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Required: true,
|
||||
Description: "A number used to determine the order in which multiple conditions execute. Lower numbers execute first",
|
||||
},
|
||||
"type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Type of the condition, either `REQUEST`, `RESPONSE`, or `CACHE`",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"default_ttl": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
|
@ -409,6 +442,7 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
|||
"header",
|
||||
"gzip",
|
||||
"s3logging",
|
||||
"condition",
|
||||
} {
|
||||
if d.HasChange(v) {
|
||||
needsChange = true
|
||||
|
@ -463,13 +497,70 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Conditions need to be updated first, as they can be referenced by other
|
||||
// configuraiton objects (Backends, Request Headers, etc)
|
||||
|
||||
// Find difference in Conditions
|
||||
if d.HasChange("condition") {
|
||||
// Note: we don't utilize the PUT endpoint to update these objects, we simply
|
||||
// destroy any that have changed, and create new ones with the updated
|
||||
// values. This is how Terraform works with nested sub resources, we only
|
||||
// get the full diff not a partial set item diff. Because this is done
|
||||
// on a new version of the Fastly Service configuration, this is considered safe
|
||||
|
||||
oc, nc := d.GetChange("condition")
|
||||
if oc == nil {
|
||||
oc = new(schema.Set)
|
||||
}
|
||||
if nc == nil {
|
||||
nc = new(schema.Set)
|
||||
}
|
||||
|
||||
ocs := oc.(*schema.Set)
|
||||
ncs := nc.(*schema.Set)
|
||||
removeConditions := ocs.Difference(ncs).List()
|
||||
addConditions := ncs.Difference(ocs).List()
|
||||
|
||||
// DELETE old Conditions
|
||||
for _, cRaw := range removeConditions {
|
||||
cf := cRaw.(map[string]interface{})
|
||||
opts := gofastly.DeleteConditionInput{
|
||||
Service: d.Id(),
|
||||
Version: latestVersion,
|
||||
Name: cf["name"].(string),
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Fastly Conditions Removal opts: %#v", opts)
|
||||
err := conn.DeleteCondition(&opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// POST new Conditions
|
||||
for _, cRaw := range addConditions {
|
||||
cf := cRaw.(map[string]interface{})
|
||||
opts := gofastly.CreateConditionInput{
|
||||
Service: d.Id(),
|
||||
Version: latestVersion,
|
||||
Name: cf["name"].(string),
|
||||
Type: cf["type"].(string),
|
||||
// need to trim leading/tailing spaces, incase the config has HEREDOC
|
||||
// formatting and contains a trailing new line
|
||||
Statement: strings.TrimSpace(cf["statement"].(string)),
|
||||
Priority: cf["priority"].(int),
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Create Conditions Opts: %#v", opts)
|
||||
_, err := conn.CreateCondition(&opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find differences in domains
|
||||
if d.HasChange("domain") {
|
||||
// Note: we don't utilize the PUT endpoint to update a Domain, we simply
|
||||
// destroy it and create a new one. This is how Terraform works with nested
|
||||
// sub resources, we only get the full diff not a partial set item diff.
|
||||
// Because this is done on a new version of the configuration, this is
|
||||
// considered safe
|
||||
od, nd := d.GetChange("domain")
|
||||
if od == nil {
|
||||
od = new(schema.Set)
|
||||
|
@ -523,12 +614,6 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
|||
|
||||
// find difference in backends
|
||||
if d.HasChange("backend") {
|
||||
// POST new Backends
|
||||
// Note: we don't utilize the PUT endpoint to update a Backend, we simply
|
||||
// destroy it and create a new one. This is how Terraform works with nested
|
||||
// sub resources, we only get the full diff not a partial set item diff.
|
||||
// Because this is done on a new version of the configuration, this is
|
||||
// considered safe
|
||||
ob, nb := d.GetChange("backend")
|
||||
if ob == nil {
|
||||
ob = new(schema.Set)
|
||||
|
@ -558,6 +643,7 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Find and post new Backends
|
||||
for _, dRaw := range addBackends {
|
||||
df := dRaw.(map[string]interface{})
|
||||
opts := gofastly.CreateBackendInput{
|
||||
|
@ -585,11 +671,6 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
|||
}
|
||||
|
||||
if d.HasChange("header") {
|
||||
// Note: we don't utilize the PUT endpoint to update a Header, we simply
|
||||
// destroy it and create a new one. This is how Terraform works with nested
|
||||
// sub resources, we only get the full diff not a partial set item diff.
|
||||
// Because this is done on a new version of the configuration, this is
|
||||
// considered safe
|
||||
oh, nh := d.GetChange("header")
|
||||
if oh == nil {
|
||||
oh = new(schema.Set)
|
||||
|
@ -640,11 +721,6 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
|||
|
||||
// Find differences in Gzips
|
||||
if d.HasChange("gzip") {
|
||||
// Note: we don't utilize the PUT endpoint to update a Gzip rule, we simply
|
||||
// destroy it and create a new one. This is how Terraform works with nested
|
||||
// sub resources, we only get the full diff not a partial set item diff.
|
||||
// Because this is done on a new version of the configuration, this is
|
||||
// considered safe
|
||||
og, ng := d.GetChange("gzip")
|
||||
if og == nil {
|
||||
og = new(schema.Set)
|
||||
|
@ -714,12 +790,6 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
|||
|
||||
// find difference in s3logging
|
||||
if d.HasChange("s3logging") {
|
||||
// POST new Logging
|
||||
// Note: we don't utilize the PUT endpoint to update a S3 Logs, we simply
|
||||
// destroy it and create a new one. This is how Terraform works with nested
|
||||
// sub resources, we only get the full diff not a partial set item diff.
|
||||
// Because this is done on a new version of the configuration, this is
|
||||
// considered safe
|
||||
os, ns := d.GetChange("s3logging")
|
||||
if os == nil {
|
||||
os = new(schema.Set)
|
||||
|
@ -947,6 +1017,23 @@ func resourceServiceV1Read(d *schema.ResourceData, meta interface{}) error {
|
|||
log.Printf("[WARN] Error setting S3 Logging for (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
// refresh Conditions
|
||||
log.Printf("[DEBUG] Refreshing Conditions for (%s)", d.Id())
|
||||
conditionList, err := conn.ListConditions(&gofastly.ListConditionsInput{
|
||||
Service: d.Id(),
|
||||
Version: s.ActiveVersion.Number,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("[ERR] Error looking up Conditions for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
|
||||
}
|
||||
|
||||
cl := flattenConditions(conditionList)
|
||||
|
||||
if err := d.Set("condition", cl); err != nil {
|
||||
log.Printf("[WARN] Error setting Conditions for (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
} else {
|
||||
log.Printf("[DEBUG] Active Version for Service (%s) is empty, no state to refresh", d.Id())
|
||||
}
|
||||
|
@ -1215,3 +1302,27 @@ func flattenS3s(s3List []*gofastly.S3) []map[string]interface{} {
|
|||
|
||||
return sl
|
||||
}
|
||||
|
||||
func flattenConditions(conditionList []*gofastly.Condition) []map[string]interface{} {
|
||||
var cl []map[string]interface{}
|
||||
for _, c := range conditionList {
|
||||
// Convert Conditions to a map for saving to state.
|
||||
nc := map[string]interface{}{
|
||||
"name": c.Name,
|
||||
"statement": c.Statement,
|
||||
"type": c.Type,
|
||||
"priority": c.Priority,
|
||||
}
|
||||
|
||||
// prune any empty values that come from the default string value in structs
|
||||
for k, v := range nc {
|
||||
if v == "" {
|
||||
delete(nc, k)
|
||||
}
|
||||
}
|
||||
|
||||
cl = append(cl, nc)
|
||||
}
|
||||
|
||||
return cl
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
package fastly
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
gofastly "github.com/sethvargo/go-fastly"
|
||||
)
|
||||
|
||||
func TestAccFastlyServiceV1_conditional_basic(t *testing.T) {
|
||||
var service gofastly.ServiceDetail
|
||||
name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
|
||||
domainName1 := fmt.Sprintf("%s.notadomain.com", acctest.RandString(10))
|
||||
|
||||
con1 := gofastly.Condition{
|
||||
Name: "some amz condition",
|
||||
Priority: 10,
|
||||
Type: "REQUEST",
|
||||
Statement: `req.url ~ "^/yolo/"`,
|
||||
}
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckServiceV1Destroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccServiceV1ConditionConfig(name, domainName1),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckServiceV1Exists("fastly_service_v1.foo", &service),
|
||||
testAccCheckFastlyServiceV1ConditionalAttributes(&service, name, []*gofastly.Condition{&con1}),
|
||||
resource.TestCheckResourceAttr(
|
||||
"fastly_service_v1.foo", "name", name),
|
||||
resource.TestCheckResourceAttr(
|
||||
"fastly_service_v1.foo", "condition.#", "1"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckFastlyServiceV1ConditionalAttributes(service *gofastly.ServiceDetail, name string, conditions []*gofastly.Condition) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
|
||||
if service.Name != name {
|
||||
return fmt.Errorf("Bad name, expected (%s), got (%s)", name, service.Name)
|
||||
}
|
||||
|
||||
conn := testAccProvider.Meta().(*FastlyClient).conn
|
||||
conditionList, err := conn.ListConditions(&gofastly.ListConditionsInput{
|
||||
Service: service.ID,
|
||||
Version: service.ActiveVersion.Number,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("[ERR] Error looking up Conditions for (%s), version (%s): %s", service.Name, service.ActiveVersion.Number, err)
|
||||
}
|
||||
|
||||
if len(conditionList) != len(conditions) {
|
||||
return fmt.Errorf("Error: mis match count of conditions, expected (%d), got (%d)", len(conditions), len(conditionList))
|
||||
}
|
||||
|
||||
var found int
|
||||
for _, c := range conditions {
|
||||
for _, lc := range conditionList {
|
||||
if c.Name == lc.Name {
|
||||
// we don't know these things ahead of time, so populate them now
|
||||
c.ServiceID = service.ID
|
||||
c.Version = service.ActiveVersion.Number
|
||||
if !reflect.DeepEqual(c, lc) {
|
||||
return fmt.Errorf("Bad match Conditions match, expected (%#v), got (%#v)", c, lc)
|
||||
}
|
||||
found++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if found != len(conditions) {
|
||||
return fmt.Errorf("Error matching Conditions rules")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccServiceV1ConditionConfig(name, domain string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "fastly_service_v1" "foo" {
|
||||
name = "%s"
|
||||
|
||||
domain {
|
||||
name = "%s"
|
||||
comment = "tf-testing-domain"
|
||||
}
|
||||
|
||||
backend {
|
||||
address = "aws.amazon.com"
|
||||
name = "amazon docs"
|
||||
}
|
||||
|
||||
header {
|
||||
destination = "http.x-amz-request-id"
|
||||
type = "cache"
|
||||
action = "delete"
|
||||
name = "remove x-amz-request-id"
|
||||
}
|
||||
|
||||
condition {
|
||||
name = "some amz condition"
|
||||
type = "REQUEST"
|
||||
|
||||
statement = "req.url ~ \"^/yolo/\""
|
||||
|
||||
priority = 10
|
||||
}
|
||||
|
||||
force_destroy = true
|
||||
}`, name, domain)
|
||||
}
|
|
@ -100,6 +100,8 @@ The following arguments are supported:
|
|||
Service. Defined below
|
||||
* `backend` - (Required) A set of Backends to service requests from your Domains.
|
||||
Defined below
|
||||
* `condition` - (Optional) A set of conditions to add logic to any basic
|
||||
configuration object in this service. Defined below
|
||||
* `gzip` - (Required) A set of gzip rules to control automatic gzipping of
|
||||
content. Defined below
|
||||
* `header` - (Optional) A set of Headers to manipulate for each request. Defined
|
||||
|
@ -135,6 +137,20 @@ Default `200`
|
|||
* `ssl_check_cert` - (Optional) Be strict on checking SSL certs. Default `true`
|
||||
* `weight` - (Optional) The [portion of traffic](https://docs.fastly.com/guides/performance-tuning/load-balancing-configuration.html#how-weight-affects-load-balancing) to send to this Backend. Each Backend receives `weight / total` of the traffic. Default `100`
|
||||
|
||||
The `condition` block supports allows you to add logic to any basic configuration
|
||||
object in a service. See Fastly's documentation
|
||||
["About Conditions"](https://docs.fastly.com/guides/conditions/about-conditions)
|
||||
for more detailed information on using Conditions. The Condition `name` can be
|
||||
used in the `request_condition`, `response_condition`, or
|
||||
`cache_condition` attributes of other block settings
|
||||
|
||||
* `name` - (Required) A unique name of the condition
|
||||
* `statement` - (Required) The statement used to determine if the condition is met
|
||||
* `priority` - (Required) A number used to determine the order in which multiple
|
||||
conditions execute. Lower numbers execute first
|
||||
* `type` - (Required) Type of the condition, either `REQUEST` (req), `RESPONSE`
|
||||
(req, resp), or `CACHE` (req, beresp)
|
||||
|
||||
The `gzip` block supports:
|
||||
|
||||
* `name` - (Required) A unique name
|
||||
|
|
Loading…
Reference in New Issue