mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
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{
|
"default_ttl": &schema.Schema{
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
@ -409,6 +442,7 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
|||||||
"header",
|
"header",
|
||||||
"gzip",
|
"gzip",
|
||||||
"s3logging",
|
"s3logging",
|
||||||
|
"condition",
|
||||||
} {
|
} {
|
||||||
if d.HasChange(v) {
|
if d.HasChange(v) {
|
||||||
needsChange = true
|
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
|
// Find differences in domains
|
||||||
if d.HasChange("domain") {
|
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")
|
od, nd := d.GetChange("domain")
|
||||||
if od == nil {
|
if od == nil {
|
||||||
od = new(schema.Set)
|
od = new(schema.Set)
|
||||||
@ -523,12 +614,6 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
|||||||
|
|
||||||
// find difference in backends
|
// find difference in backends
|
||||||
if d.HasChange("backend") {
|
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")
|
ob, nb := d.GetChange("backend")
|
||||||
if ob == nil {
|
if ob == nil {
|
||||||
ob = new(schema.Set)
|
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 {
|
for _, dRaw := range addBackends {
|
||||||
df := dRaw.(map[string]interface{})
|
df := dRaw.(map[string]interface{})
|
||||||
opts := gofastly.CreateBackendInput{
|
opts := gofastly.CreateBackendInput{
|
||||||
@ -585,11 +671,6 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if d.HasChange("header") {
|
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")
|
oh, nh := d.GetChange("header")
|
||||||
if oh == nil {
|
if oh == nil {
|
||||||
oh = new(schema.Set)
|
oh = new(schema.Set)
|
||||||
@ -640,11 +721,6 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
|||||||
|
|
||||||
// Find differences in Gzips
|
// Find differences in Gzips
|
||||||
if d.HasChange("gzip") {
|
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")
|
og, ng := d.GetChange("gzip")
|
||||||
if og == nil {
|
if og == nil {
|
||||||
og = new(schema.Set)
|
og = new(schema.Set)
|
||||||
@ -714,12 +790,6 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
|||||||
|
|
||||||
// find difference in s3logging
|
// find difference in s3logging
|
||||||
if d.HasChange("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")
|
os, ns := d.GetChange("s3logging")
|
||||||
if os == nil {
|
if os == nil {
|
||||||
os = new(schema.Set)
|
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)
|
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 {
|
} else {
|
||||||
log.Printf("[DEBUG] Active Version for Service (%s) is empty, no state to refresh", d.Id())
|
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
|
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)
|
||||||
|
}
|
16
vendor/github.com/sethvargo/go-fastly/version.go
generated
vendored
16
vendor/github.com/sethvargo/go-fastly/version.go
generated
vendored
@ -7,14 +7,14 @@ import (
|
|||||||
|
|
||||||
// Version represents a distinct configuration version.
|
// Version represents a distinct configuration version.
|
||||||
type Version struct {
|
type Version struct {
|
||||||
Number string `mapstructure:"number"`
|
Number string `mapstructure:"number"`
|
||||||
Comment string `mapstructure:"comment"`
|
Comment string `mapstructure:"comment"`
|
||||||
ServiceID string `mapstructure:"service_id"`
|
ServiceID string `mapstructure:"service_id"`
|
||||||
Active bool `mapstructure:"active"`
|
Active bool `mapstructure:"active"`
|
||||||
Locked bool `mapstructure:"locked"`
|
Locked bool `mapstructure:"locked"`
|
||||||
Deployed bool `mapstructure:"deployed"`
|
Deployed bool `mapstructure:"deployed"`
|
||||||
Staging bool `mapstructure:"staging"`
|
Staging bool `mapstructure:"staging"`
|
||||||
Testing bool `mapstructure:"testing"`
|
Testing bool `mapstructure:"testing"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// versionsByNumber is a sortable list of versions. This is used by the version
|
// versionsByNumber is a sortable list of versions. This is used by the version
|
||||||
|
@ -100,6 +100,8 @@ The following arguments are supported:
|
|||||||
Service. Defined below
|
Service. Defined below
|
||||||
* `backend` - (Required) A set of Backends to service requests from your Domains.
|
* `backend` - (Required) A set of Backends to service requests from your Domains.
|
||||||
Defined below
|
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
|
* `gzip` - (Required) A set of gzip rules to control automatic gzipping of
|
||||||
content. Defined below
|
content. Defined below
|
||||||
* `header` - (Optional) A set of Headers to manipulate for each request. Defined
|
* `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`
|
* `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`
|
* `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:
|
The `gzip` block supports:
|
||||||
|
|
||||||
* `name` - (Required) A unique name
|
* `name` - (Required) A unique name
|
||||||
|
Loading…
Reference in New Issue
Block a user