diff --git a/builtin/providers/fastly/resource_fastly_service_v1.go b/builtin/providers/fastly/resource_fastly_service_v1.go index 13e397ebc..4e0cf8bea 100644 --- a/builtin/providers/fastly/resource_fastly_service_v1.go +++ b/builtin/providers/fastly/resource_fastly_service_v1.go @@ -457,6 +457,38 @@ func resourceServiceV1() *schema.Resource { }, }, + "papertrail": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + // Required fields + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Unique name to refer to this logging setup", + }, + "address": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "The address of the papertrail service", + }, + "port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + Description: "The port of the papertrail service", + }, + // Optional + "format": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "%h %l %u %t %r %>s", + Description: "Apache-style string or VCL variables to use for log formatting", + }, + }, + }, + }, + "request_setting": &schema.Schema{ Type: schema.TypeSet, Optional: true, @@ -616,6 +648,7 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error { "header", "gzip", "s3logging", + "papertrail", "condition", "request_setting", "cache_setting", @@ -1034,6 +1067,58 @@ func resourceServiceV1Update(d *schema.ResourceData, meta interface{}) error { } } + // find difference in Papertrail + if d.HasChange("papertrail") { + os, ns := d.GetChange("papertrail") + if os == nil { + os = new(schema.Set) + } + if ns == nil { + ns = new(schema.Set) + } + + oss := os.(*schema.Set) + nss := ns.(*schema.Set) + removePapertrail := oss.Difference(nss).List() + addPapertrail := nss.Difference(oss).List() + + // DELETE old papertrail configurations + for _, pRaw := range removePapertrail { + pf := pRaw.(map[string]interface{}) + opts := gofastly.DeletePapertrailInput{ + Service: d.Id(), + Version: latestVersion, + Name: pf["name"].(string), + } + + log.Printf("[DEBUG] Fastly Papertrail removal opts: %#v", opts) + err := conn.DeletePapertrail(&opts) + if err != nil { + return err + } + } + + // POST new/updated Papertrail + for _, pRaw := range addPapertrail { + pf := pRaw.(map[string]interface{}) + + opts := gofastly.CreatePapertrailInput{ + Service: d.Id(), + Version: latestVersion, + Name: pf["name"].(string), + Address: pf["address"].(string), + Port: uint(pf["port"].(int)), + Format: pf["format"].(string), + } + + log.Printf("[DEBUG] Create Papertrail Opts: %#v", opts) + _, err := conn.CreatePapertrail(&opts) + if err != nil { + return err + } + } + } + // find difference in request settings if d.HasChange("request_setting") { os, ns := d.GetChange("request_setting") @@ -1362,6 +1447,23 @@ func resourceServiceV1Read(d *schema.ResourceData, meta interface{}) error { log.Printf("[WARN] Error setting S3 Logging for (%s): %s", d.Id(), err) } + // refresh Papertrail Logging + log.Printf("[DEBUG] Refreshing Papertrail for (%s)", d.Id()) + papertrailList, err := conn.ListPapertrails(&gofastly.ListPapertrailsInput{ + Service: d.Id(), + Version: s.ActiveVersion.Number, + }) + + if err != nil { + return fmt.Errorf("[ERR] Error looking up Papertrail for (%s), version (%s): %s", d.Id(), s.ActiveVersion.Number, err) + } + + pl := flattenPapertrails(papertrailList) + + if err := d.Set("papertrail", pl); err != nil { + log.Printf("[WARN] Error setting Papertrail for (%s): %s", d.Id(), err) + } + // refresh Conditions log.Printf("[DEBUG] Refreshing Conditions for (%s)", d.Id()) conditionList, err := conn.ListConditions(&gofastly.ListConditionsInput{ @@ -1725,6 +1827,30 @@ func flattenS3s(s3List []*gofastly.S3) []map[string]interface{} { return sl } +func flattenPapertrails(papertrailList []*gofastly.Papertrail) []map[string]interface{} { + var pl []map[string]interface{} + for _, p := range papertrailList { + // Convert S3s to a map for saving to state. + ns := map[string]interface{}{ + "name": p.Name, + "address": p.Address, + "port": p.Port, + "format": p.Format, + } + + // prune any empty values that come from the default string value in structs + for k, v := range ns { + if v == "" { + delete(ns, k) + } + } + + pl = append(pl, ns) + } + + return pl +} + func flattenConditions(conditionList []*gofastly.Condition) []map[string]interface{} { var cl []map[string]interface{} for _, c := range conditionList { diff --git a/builtin/providers/fastly/resource_fastly_service_v1_papertrail_test.go b/builtin/providers/fastly/resource_fastly_service_v1_papertrail_test.go new file mode 100644 index 000000000..6fb3bfd3b --- /dev/null +++ b/builtin/providers/fastly/resource_fastly_service_v1_papertrail_test.go @@ -0,0 +1,165 @@ +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_papertrail_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.Papertrail{ + Version: "1", + Name: "papertrailtesting", + Address: "test1.papertrailapp.com", + Port: uint(3600), + Format: "%h %l %u %t %r %>s", + } + + log2 := gofastly.Papertrail{ + Version: "1", + Name: "papertrailtesting2", + Address: "test2.papertrailapp.com", + Port: uint(8080), + Format: "%h %l %u %t %r %>s", + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckServiceV1Destroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccServiceV1PapertrailConfig(name, domainName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceV1Exists("fastly_service_v1.foo", &service), + testAccCheckFastlyServiceV1PapertrailAttributes(&service, []*gofastly.Papertrail{&log1}), + resource.TestCheckResourceAttr( + "fastly_service_v1.foo", "name", name), + resource.TestCheckResourceAttr( + "fastly_service_v1.foo", "papertrail.#", "1"), + ), + }, + + resource.TestStep{ + Config: testAccServiceV1PapertrailConfig_update(name, domainName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceV1Exists("fastly_service_v1.foo", &service), + testAccCheckFastlyServiceV1PapertrailAttributes(&service, []*gofastly.Papertrail{&log1, &log2}), + resource.TestCheckResourceAttr( + "fastly_service_v1.foo", "name", name), + resource.TestCheckResourceAttr( + "fastly_service_v1.foo", "papertrail.#", "2"), + ), + }, + }, + }) +} + +func testAccCheckFastlyServiceV1PapertrailAttributes(service *gofastly.ServiceDetail, papertrails []*gofastly.Papertrail) resource.TestCheckFunc { + return func(s *terraform.State) error { + + conn := testAccProvider.Meta().(*FastlyClient).conn + papertrailList, err := conn.ListPapertrails(&gofastly.ListPapertrailsInput{ + Service: service.ID, + Version: service.ActiveVersion.Number, + }) + + if err != nil { + return fmt.Errorf("[ERR] Error looking up Papertrail for (%s), version (%s): %s", service.Name, service.ActiveVersion.Number, err) + } + + if len(papertrailList) != len(papertrails) { + return fmt.Errorf("Papertrail List count mismatch, expected (%d), got (%d)", len(papertrails), len(papertrailList)) + } + + var found int + for _, p := range papertrails { + for _, lp := range papertrailList { + 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 + // We don't track these, so clear them out because we also wont know + // these ahead of time + lp.CreatedAt = nil + lp.UpdatedAt = nil + if !reflect.DeepEqual(p, lp) { + return fmt.Errorf("Bad match Papertrail match, expected (%#v), got (%#v)", p, lp) + } + found++ + } + } + } + + if found != len(papertrails) { + return fmt.Errorf("Error matching Papertrail rules") + } + + return nil + } +} + +func testAccServiceV1PapertrailConfig(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" + } + + papertrail { + name = "papertrailtesting" + address = "test1.papertrailapp.com" + port = 3600 + } + + force_destroy = true +}`, name, domain) +} + +func testAccServiceV1PapertrailConfig_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" + } + + papertrail { + name = "papertrailtesting" + address = "test1.papertrailapp.com" + port = 3600 + } + + papertrail { + name = "papertrailtesting2" + address = "test2.papertrailapp.com" + port = 8080 + } + + force_destroy = true +}`, name, domain) +} diff --git a/website/source/docs/providers/fastly/r/service_v1.html.markdown b/website/source/docs/providers/fastly/r/service_v1.html.markdown index bad602291..6ecb0c68d 100644 --- a/website/source/docs/providers/fastly/r/service_v1.html.markdown +++ b/website/source/docs/providers/fastly/r/service_v1.html.markdown @@ -150,6 +150,8 @@ order to destroy the Service, set `force_destroy` to `true`. Default `false`. * `request_setting` - (Optional) A set of Request modifiers. Defined below * `s3logging` - (Optional) A set of S3 Buckets to send streaming logs too. Defined below. +* `papertrail` - (Optional) A Papertrail endpoint to send streaming logs too. +Defined below. * `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). @@ -174,7 +176,7 @@ Default `1000` * `max_conn` - (Optional) Maximum number of connections for this Backend. Default `200`. * `port` - (Optional) The port number on which the Backend responds. Default `80`. -* `request_condition` - (Optional, string) Condition, which if met, will select this backend during a request. +* `request_condition` - (Optional, string) Condition, which if met, will select this backend during a request. * `ssl_check_cert` - (Optional) Be strict about checking SSL certs. Default `true`. * `ssl_hostname` - (Optional) Used for both SNI during the TLS handshake and to validate the cert. * `shield` - (Optional) The POP of the shield designated to reduce inbound load. @@ -282,6 +284,16 @@ compressed. Default `0`. Request Setting should be applied. For detailed information about Conditionals, see [Fastly's Documentation on Conditionals][fastly-conditionals]. +The `papertrail` block supports: + +* `name` - (Required) A unique name to identify this Papertrail endpoint. +* `address` - (Required) The address of the Papertrail endpoint. +* `port` - (Required) The port associated with the address where the Papertrail endpoint can be accessed. +* `format` - (Optional) Apache-style string or VCL variables to use for log formatting. Defaults to Apache Common Log format (`%h %l %u %t %r %>s`) +* `request_condition` - (Optional) The VCL request condition to check if this +Request Setting should be applied. 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. @@ -302,6 +314,7 @@ Service. * `backend` – Set of Backends. See above for details. * `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. * `vcl` – Set of custom VCL configurations. See above for details. * `default_host` – Default host specified. * `default_ttl` - Default TTL.