mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 18:01:01 -06:00
provider/fastly Adds fastly response object (#12032)
* Adds basic schema for response object * Updates based on differences in fastly response objects * Refreshes and flattens fastly response object * Tests fastly response object * Adds documentation for a fastly response object
This commit is contained in:
parent
0f058b661b
commit
84308439aa
@ -582,6 +582,58 @@ func resourceServiceV1() *schema.Resource {
|
||||
},
|
||||
},
|
||||
|
||||
"response_object": {
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
// Required
|
||||
"name": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "Unique name to refer to this request object",
|
||||
},
|
||||
// Optional fields
|
||||
"status": {
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Default: 200,
|
||||
Description: "The HTTP Status Code of the object",
|
||||
},
|
||||
"response": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "OK",
|
||||
Description: "The HTTP Response of the object",
|
||||
},
|
||||
"content": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: "The content to deliver for the response object",
|
||||
},
|
||||
"content_type": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: "The MIME type of the content",
|
||||
},
|
||||
"request_condition": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: "Name of the condition to be checked during the request phase to see if the object should be delivered",
|
||||
},
|
||||
"cache_condition": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "",
|
||||
Description: "Name of the condition checked after we have retrieved an object. If the condition passes then deliver this Request Object instead.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"request_setting": {
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
@ -743,6 +795,7 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
||||
"healthcheck",
|
||||
"s3logging",
|
||||
"papertrail",
|
||||
"response_object",
|
||||
"condition",
|
||||
"request_setting",
|
||||
"cache_setting",
|
||||
@ -1276,6 +1329,61 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
// find difference in Response Object
|
||||
if d.HasChange("response_object") {
|
||||
or, nr := d.GetChange("response_object")
|
||||
if or == nil {
|
||||
or = new(schema.Set)
|
||||
}
|
||||
if nr == nil {
|
||||
nr = new(schema.Set)
|
||||
}
|
||||
|
||||
ors := or.(*schema.Set)
|
||||
nrs := nr.(*schema.Set)
|
||||
removeResponseObject := ors.Difference(nrs).List()
|
||||
addResponseObject := nrs.Difference(ors).List()
|
||||
|
||||
// DELETE old response object configurations
|
||||
for _, rRaw := range removeResponseObject {
|
||||
rf := rRaw.(map[string]interface{})
|
||||
opts := gofastly.DeleteResponseObjectInput{
|
||||
Service: d.Id(),
|
||||
Version: latestVersion,
|
||||
Name: rf["name"].(string),
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Fastly Response Object removal opts: %#v", opts)
|
||||
err := conn.DeleteResponseObject(&opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// POST new/updated Response Object
|
||||
for _, rRaw := range addResponseObject {
|
||||
rf := rRaw.(map[string]interface{})
|
||||
|
||||
opts := gofastly.CreateResponseObjectInput{
|
||||
Service: d.Id(),
|
||||
Version: latestVersion,
|
||||
Name: rf["name"].(string),
|
||||
Status: uint(rf["status"].(int)),
|
||||
Response: rf["response"].(string),
|
||||
Content: rf["content"].(string),
|
||||
ContentType: rf["content_type"].(string),
|
||||
RequestCondition: rf["request_condition"].(string),
|
||||
CacheCondition: rf["cache_condition"].(string),
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Create Response Object Opts: %#v", opts)
|
||||
_, err := conn.CreateResponseObject(&opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find difference in request settings
|
||||
if d.HasChange("request_setting") {
|
||||
os, ns := d.GetChange("request_setting")
|
||||
@ -1638,6 +1746,23 @@ func resourceServiceV1Read(d *schema.ResourceData, meta interface{}) error {
|
||||
log.Printf("[WARN] Error setting Papertrail for (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
// refresh Response Objects
|
||||
log.Printf("[DEBUG] Refreshing Response Object for (%s)", d.Id())
|
||||
responseObjectList, err := conn.ListResponseObjects(&gofastly.ListResponseObjectsInput{
|
||||
Service: d.Id(),
|
||||
Version: s.ActiveVersion.Number,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("[ERR] Error looking up Response Object for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err)
|
||||
}
|
||||
|
||||
rol := flattenResponseObjects(responseObjectList)
|
||||
|
||||
if err := d.Set("response_object", rol); err != nil {
|
||||
log.Printf("[WARN] Error setting Response Object for (%s): %s", d.Id(), err)
|
||||
}
|
||||
|
||||
// refresh Conditions
|
||||
log.Printf("[DEBUG] Refreshing Conditions for (%s)", d.Id())
|
||||
conditionList, err := conn.ListConditions(&gofastly.ListConditionsInput{
|
||||
@ -2059,6 +2184,33 @@ func flattenPapertrails(papertrailList []*gofastly.Papertrail) []map[string]inte
|
||||
return pl
|
||||
}
|
||||
|
||||
func flattenResponseObjects(responseObjectList []*gofastly.ResponseObject) []map[string]interface{} {
|
||||
var rol []map[string]interface{}
|
||||
for _, ro := range responseObjectList {
|
||||
// Convert ResponseObjects to a map for saving to state.
|
||||
nro := map[string]interface{}{
|
||||
"name": ro.Name,
|
||||
"status": ro.Status,
|
||||
"response": ro.Response,
|
||||
"content": ro.Content,
|
||||
"content_type": ro.ContentType,
|
||||
"request_condition": ro.RequestCondition,
|
||||
"cache_condition": ro.CacheCondition,
|
||||
}
|
||||
|
||||
// prune any empty values that come from the default string value in structs
|
||||
for k, v := range nro {
|
||||
if v == "" {
|
||||
delete(nro, k)
|
||||
}
|
||||
}
|
||||
|
||||
rol = append(rol, nro)
|
||||
}
|
||||
|
||||
return rol
|
||||
}
|
||||
|
||||
func flattenConditions(conditionList []*gofastly.Condition) []map[string]interface{} {
|
||||
var cl []map[string]interface{}
|
||||
for _, c := range conditionList {
|
||||
|
@ -0,0 +1,221 @@
|
||||
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_response_object_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))
|
||||
|
||||
log1 := gofastly.ResponseObject{
|
||||
Version: "1",
|
||||
Name: "responseObjecttesting",
|
||||
Status: 200,
|
||||
Response: "OK",
|
||||
Content: "test content",
|
||||
ContentType: "text/html",
|
||||
RequestCondition: "test-request-condition",
|
||||
CacheCondition: "test-cache-condition",
|
||||
}
|
||||
|
||||
log2 := gofastly.ResponseObject{
|
||||
Version: "1",
|
||||
Name: "responseObjecttesting2",
|
||||
Status: 404,
|
||||
Response: "Not Found",
|
||||
Content: "some, other, content",
|
||||
ContentType: "text/csv",
|
||||
RequestCondition: "another-test-request-condition",
|
||||
CacheCondition: "another-test-cache-condition",
|
||||
}
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckServiceV1Destroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccServiceV1ResponseObjectConfig(name, domainName1),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckServiceV1Exists("fastly_service_v1.foo", &service),
|
||||
testAccCheckFastlyServiceV1ResponseObjectAttributes(&service, []*gofastly.ResponseObject{&log1}),
|
||||
resource.TestCheckResourceAttr(
|
||||
"fastly_service_v1.foo", "name", name),
|
||||
resource.TestCheckResourceAttr(
|
||||
"fastly_service_v1.foo", "response_object.#", "1"),
|
||||
),
|
||||
},
|
||||
|
||||
resource.TestStep{
|
||||
Config: testAccServiceV1ResponseObjectConfig_update(name, domainName1),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckServiceV1Exists("fastly_service_v1.foo", &service),
|
||||
testAccCheckFastlyServiceV1ResponseObjectAttributes(&service, []*gofastly.ResponseObject{&log1, &log2}),
|
||||
resource.TestCheckResourceAttr(
|
||||
"fastly_service_v1.foo", "name", name),
|
||||
resource.TestCheckResourceAttr(
|
||||
"fastly_service_v1.foo", "response_object.#", "2"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckFastlyServiceV1ResponseObjectAttributes(service *gofastly.ServiceDetail, responseObjects []*gofastly.ResponseObject) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
|
||||
conn := testAccProvider.Meta().(*FastlyClient).conn
|
||||
responseObjectList, err := conn.ListResponseObjects(&gofastly.ListResponseObjectsInput{
|
||||
Service: service.ID,
|
||||
Version: service.ActiveVersion.Number,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("[ERR] Error looking up Response Object for (%s), version (%s): %s", service.Name, service.ActiveVersion.Number, err)
|
||||
}
|
||||
|
||||
if len(responseObjectList) != len(responseObjects) {
|
||||
return fmt.Errorf("Response Object List count mismatch, expected (%d), got (%d)", len(responseObjects), len(responseObjectList))
|
||||
}
|
||||
|
||||
var found int
|
||||
for _, p := range responseObjects {
|
||||
for _, lp := range responseObjectList {
|
||||
if p.Name == lp.Name {
|
||||
// we don't know these things ahead of time, so populate them now
|
||||
p.ServiceID = service.ID
|
||||
p.Version = service.ActiveVersion.Number
|
||||
if !reflect.DeepEqual(p, lp) {
|
||||
return fmt.Errorf("Bad match Response Object match, expected (%#v), got (%#v)", p, lp)
|
||||
}
|
||||
found++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if found != len(responseObjects) {
|
||||
return fmt.Errorf("Error matching Response Object rules")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccServiceV1ResponseObjectConfig(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"
|
||||
}
|
||||
|
||||
condition {
|
||||
name = "test-request-condition"
|
||||
type = "REQUEST"
|
||||
priority = 5
|
||||
statement = "req.url ~ \"^/foo/bar$\""
|
||||
}
|
||||
|
||||
condition {
|
||||
name = "test-cache-condition"
|
||||
type = "CACHE"
|
||||
priority = 9
|
||||
statement = "req.url ~ \"^/articles/\""
|
||||
}
|
||||
|
||||
response_object {
|
||||
name = "responseObjecttesting"
|
||||
status = 200
|
||||
response = "OK"
|
||||
content = "test content"
|
||||
content_type = "text/html"
|
||||
request_condition = "test-request-condition"
|
||||
cache_condition = "test-cache-condition"
|
||||
}
|
||||
|
||||
force_destroy = true
|
||||
}`, name, domain)
|
||||
}
|
||||
|
||||
func testAccServiceV1ResponseObjectConfig_update(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"
|
||||
}
|
||||
|
||||
condition {
|
||||
name = "test-cache-condition"
|
||||
type = "CACHE"
|
||||
priority = 9
|
||||
statement = "req.url ~ \"^/articles/\""
|
||||
}
|
||||
|
||||
condition {
|
||||
name = "another-test-cache-condition"
|
||||
type = "CACHE"
|
||||
priority = 7
|
||||
statement = "req.url ~ \"^/stories/\""
|
||||
}
|
||||
|
||||
condition {
|
||||
name = "test-request-condition"
|
||||
type = "REQUEST"
|
||||
priority = 5
|
||||
statement = "req.url ~ \"^/foo/bar$\""
|
||||
}
|
||||
|
||||
condition {
|
||||
name = "another-test-request-condition"
|
||||
type = "REQUEST"
|
||||
priority = 10
|
||||
statement = "req.url ~ \"^/articles$\""
|
||||
}
|
||||
|
||||
response_object {
|
||||
name = "responseObjecttesting"
|
||||
status = 200
|
||||
response = "OK"
|
||||
content = "test content"
|
||||
content_type = "text/html"
|
||||
request_condition = "test-request-condition"
|
||||
cache_condition = "test-cache-condition"
|
||||
}
|
||||
|
||||
response_object {
|
||||
name = "responseObjecttesting2"
|
||||
status = 404
|
||||
response = "Not Found"
|
||||
content = "some, other, content"
|
||||
content_type = "text/csv"
|
||||
request_condition = "another-test-request-condition"
|
||||
cache_condition = "another-test-cache-condition"
|
||||
}
|
||||
|
||||
force_destroy = true
|
||||
}`, name, domain)
|
||||
}
|
@ -154,6 +154,7 @@ order to destroy the Service, set `force_destroy` to `true`. Default `false`.
|
||||
Defined below.
|
||||
* `papertrail` - (Optional) A Papertrail endpoint to send streaming logs too.
|
||||
Defined below.
|
||||
* `response_object` - (Optional) Allows you to create synthetic responses that exist entirely on the varnish machine. Useful for creating error or maintenance pages that exists outside the scope of your datacenter. Best when used with Condition objects.
|
||||
* `vcl` - (Optional) A set of custom VCL configuration blocks. The
|
||||
ability to upload custom VCL code is not enabled by default for new Fastly
|
||||
accounts (see the [Fastly documentation](https://docs.fastly.com/guides/vcl/uploading-custom-vcl) for details).
|
||||
@ -313,6 +314,18 @@ The `papertrail` block supports:
|
||||
* `response_condition` - (Optional) Name of already defined `condition` to apply. This `condition` must be of type `RESPONSE`. For detailed information about Conditionals,
|
||||
see [Fastly's Documentation on Conditionals][fastly-conditionals].
|
||||
|
||||
The `response_object` block supports:
|
||||
|
||||
* `name` - (Required) A unique name to identify this Response Object.
|
||||
* `status` - (Optional) The HTTP Status Code. Default `200`.
|
||||
* `response` - (Optional) The HTTP Response. Default `Ok`.
|
||||
* `content` - (Optional) The content to deliver for the response object.
|
||||
* `content_type` - (Optional) The MIME type of the content.
|
||||
* `request_condition` - (Optional) Name of already defined `condition` to be checked during the request phase. If the condition passes then this object will be delivered. This `condition` must be of type `REQUEST`.
|
||||
* `cache_condition` - (Optional) Name of already defined `condition` to check after we have retrieved an object. If the condition passes then deliver this Request Object instead. This `condition` must be of type `CACHE`. For detailed information about Conditionals,
|
||||
see [Fastly's Documentation on Conditionals][fastly-conditionals].
|
||||
|
||||
|
||||
The `vcl` block supports:
|
||||
|
||||
* `name` - (Required) A unique name for this configuration block.
|
||||
@ -334,6 +347,7 @@ Service.
|
||||
* `header` – Set of Headers. See above for details.
|
||||
* `s3logging` – Set of S3 Logging configurations. See above for details.
|
||||
* `papertrail` – Set of Papertrail configurations. See above for details.
|
||||
* `response_object` - Set of Response Object configurations. See above for details.
|
||||
* `vcl` – Set of custom VCL configurations. See above for details.
|
||||
* `default_host` – Default host specified.
|
||||
* `default_ttl` - Default TTL.
|
||||
|
Loading…
Reference in New Issue
Block a user