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:
Traver Tischio 2017-02-17 11:36:05 -05:00 committed by Clint
parent 0f058b661b
commit 84308439aa
3 changed files with 387 additions and 0 deletions

View File

@ -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 {

View File

@ -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)
}

View File

@ -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.