diff --git a/docs/resources/service_compute.md b/docs/resources/service_compute.md
index a601edf40..7c3ad0daf 100644
--- a/docs/resources/service_compute.md
+++ b/docs/resources/service_compute.md
@@ -94,6 +94,7 @@ $ terraform import fastly_service_compute.demo xxxxxxxxxxxxxxxxxxxx@2
- `logging_ftp` (Block Set) (see [below for nested schema](#nestedblock--logging_ftp))
- `logging_gcs` (Block Set) (see [below for nested schema](#nestedblock--logging_gcs))
- `logging_googlepubsub` (Block Set) (see [below for nested schema](#nestedblock--logging_googlepubsub))
+- `logging_grafanacloudlogs` (Block Set) (see [below for nested schema](#nestedblock--logging_grafanacloudlogs))
- `logging_heroku` (Block Set) (see [below for nested schema](#nestedblock--logging_heroku))
- `logging_honeycomb` (Block Set) (see [below for nested schema](#nestedblock--logging_honeycomb))
- `logging_https` (Block Set) (see [below for nested schema](#nestedblock--logging_https))
@@ -390,6 +391,18 @@ Optional:
- `user` (String) Your Google Cloud Platform service account email address. The `client_email` field in your service account authentication JSON. You may optionally provide this via an environment variable, `FASTLY_GOOGLE_PUBSUB_EMAIL`.
+
+### Nested Schema for `logging_grafanacloudlogs`
+
+Required:
+
+- `index` (String) The stream identifier as a JSON string
+- `name` (String) The unique name of the GrafanaCloudLogs logging endpoint. It is important to note that changing this attribute will delete and recreate the resource
+- `token` (String, Sensitive) The Access Policy Token key for your GrafanaCloudLogs account
+- `url` (String) The URL to stream logs to
+- `user` (String) The Grafana User ID
+
+
### Nested Schema for `logging_heroku`
diff --git a/docs/resources/service_vcl.md b/docs/resources/service_vcl.md
index 58e631237..31276bf01 100644
--- a/docs/resources/service_vcl.md
+++ b/docs/resources/service_vcl.md
@@ -270,6 +270,7 @@ $ terraform import fastly_service_vcl.demo xxxxxxxxxxxxxxxxxxxx@2
- `logging_ftp` (Block Set) (see [below for nested schema](#nestedblock--logging_ftp))
- `logging_gcs` (Block Set) (see [below for nested schema](#nestedblock--logging_gcs))
- `logging_googlepubsub` (Block Set) (see [below for nested schema](#nestedblock--logging_googlepubsub))
+- `logging_grafanacloudlogs` (Block Set) (see [below for nested schema](#nestedblock--logging_grafanacloudlogs))
- `logging_heroku` (Block Set) (see [below for nested schema](#nestedblock--logging_heroku))
- `logging_honeycomb` (Block Set) (see [below for nested schema](#nestedblock--logging_honeycomb))
- `logging_https` (Block Set) (see [below for nested schema](#nestedblock--logging_https))
@@ -748,6 +749,25 @@ Optional:
- `user` (String) Your Google Cloud Platform service account email address. The `client_email` field in your service account authentication JSON. You may optionally provide this via an environment variable, `FASTLY_GOOGLE_PUBSUB_EMAIL`.
+
+### Nested Schema for `logging_grafanacloudlogs`
+
+Required:
+
+- `index` (String) The stream identifier as a JSON string
+- `name` (String) The unique name of the GrafanaCloudLogs logging endpoint. It is important to note that changing this attribute will delete and recreate the resource
+- `token` (String, Sensitive) The Access Policy Token key for your GrafanaCloudLogs account
+- `url` (String) The URL to stream logs to
+- `user` (String) The Grafana User ID
+
+Optional:
+
+- `format` (String) Apache-style string or VCL variables to use for log formatting.
+- `format_version` (Number) The version of the custom logging format used for the configured endpoint. Can be either `1` or `2`. (default: `2`).
+- `placement` (String) Where in the generated VCL the logging call should be placed.
+- `response_condition` (String) The name of the condition to apply.
+
+
### Nested Schema for `logging_heroku`
diff --git a/fastly/block_fastly_service_logging_grafanacloudlogs.go b/fastly/block_fastly_service_logging_grafanacloudlogs.go
new file mode 100644
index 000000000..0d9288599
--- /dev/null
+++ b/fastly/block_fastly_service_logging_grafanacloudlogs.go
@@ -0,0 +1,293 @@
+package fastly
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ gofastly "github.com/fastly/go-fastly/v9/fastly"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+// GrafanaCloudLogsServiceAttributeHandler provides a base implementation for ServiceAttributeDefinition.
+type GrafanaCloudLogsServiceAttributeHandler struct {
+ *DefaultServiceAttributeHandler
+}
+
+// NewServiceLoggingGrafanaCloudLogs returns a new resource.
+func NewServiceLoggingGrafanaCloudLogs(sa ServiceMetadata) ServiceAttributeDefinition {
+ return ToServiceAttributeDefinition(&GrafanaCloudLogsServiceAttributeHandler{
+ &DefaultServiceAttributeHandler{
+ key: "logging_grafanacloudlogs",
+ serviceMetadata: sa,
+ },
+ })
+}
+
+// Key returns the resource key.
+func (h *GrafanaCloudLogsServiceAttributeHandler) Key() string {
+ return h.key
+}
+
+// GetSchema returns the resource schema.
+func (h *GrafanaCloudLogsServiceAttributeHandler) GetSchema() *schema.Schema {
+ blockAttributes := map[string]*schema.Schema{
+ "index": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The stream identifier as a JSON string",
+ },
+ "name": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The unique name of the GrafanaCloudLogs logging endpoint. It is important to note that changing this attribute will delete and recreate the resource",
+ },
+ "token": {
+ Type: schema.TypeString,
+ Required: true,
+ Sensitive: true,
+ Description: "The Access Policy Token key for your GrafanaCloudLogs account",
+ },
+ "url": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The URL to stream logs to",
+ },
+ "user": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The Grafana User ID",
+ },
+ }
+
+ if h.GetServiceMetadata().serviceType == ServiceTypeVCL {
+ blockAttributes["format"] = &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "Apache-style string or VCL variables to use for log formatting.",
+ }
+ blockAttributes["format_version"] = &schema.Schema{
+ Type: schema.TypeInt,
+ Optional: true,
+ Default: 2,
+ Description: "The version of the custom logging format used for the configured endpoint. Can be either `1` or `2`. (default: `2`).",
+ ValidateDiagFunc: validateLoggingFormatVersion(),
+ }
+ blockAttributes["response_condition"] = &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "The name of the condition to apply.",
+ }
+ blockAttributes["placement"] = &schema.Schema{
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "Where in the generated VCL the logging call should be placed.",
+ ValidateDiagFunc: validateLoggingPlacement(),
+ }
+ }
+
+ return &schema.Schema{
+ Type: schema.TypeSet,
+ Optional: true,
+ Elem: &schema.Resource{
+ Schema: blockAttributes,
+ },
+ }
+}
+
+// Create creates the resource.
+func (h *GrafanaCloudLogsServiceAttributeHandler) Create(_ context.Context, d *schema.ResourceData, resource map[string]any, serviceVersion int, conn *gofastly.Client) error {
+ opts := h.buildCreate(resource, d.Id(), serviceVersion)
+
+ log.Printf("[DEBUG] Fastly GrafanaCloudLogs logging addition opts: %#v", opts)
+
+ return createGrafanaCloudLogs(conn, opts)
+}
+
+// Read refreshes the resource.
+func (h *GrafanaCloudLogsServiceAttributeHandler) Read(_ context.Context, d *schema.ResourceData, _ map[string]any, serviceVersion int, conn *gofastly.Client) error {
+ localState := d.Get(h.GetKey()).(*schema.Set).List()
+
+ if len(localState) > 0 || d.Get("imported").(bool) || d.Get("force_refresh").(bool) {
+ log.Printf("[DEBUG] Refreshing GrafanaCloudLogs logging endpoints for (%s)", d.Id())
+ remoteState, err := conn.ListGrafanaCloudLogs(&gofastly.ListGrafanaCloudLogsInput{
+ ServiceID: d.Id(),
+ ServiceVersion: serviceVersion,
+ })
+ if err != nil {
+ return fmt.Errorf("error looking up GrafanaCloudLogs logging endpoints for (%s), version (%v): %s", d.Id(), serviceVersion, err)
+ }
+
+ dll := flattenGrafanaCloudLogs(remoteState)
+
+ for _, element := range dll {
+ h.pruneVCLLoggingAttributes(element)
+ }
+
+ if err := d.Set(h.GetKey(), dll); err != nil {
+ log.Printf("[WARN] Error setting GrafanaCloudLogs logging endpoints for (%s): %s", d.Id(), err)
+ }
+ }
+
+ return nil
+}
+
+// Update updates the resource.
+func (h *GrafanaCloudLogsServiceAttributeHandler) Update(_ context.Context, d *schema.ResourceData, resource, modified map[string]any, serviceVersion int, conn *gofastly.Client) error {
+ opts := gofastly.UpdateGrafanaCloudLogsInput{
+ ServiceID: d.Id(),
+ ServiceVersion: serviceVersion,
+ Name: resource["name"].(string),
+ }
+
+ // NOTE: When converting from an interface{} we lose the underlying type.
+ // Converting to the wrong type will result in a runtime panic.
+ if v, ok := modified["user"]; ok {
+ opts.User = gofastly.ToPointer(v.(string))
+ }
+ if v, ok := modified["token"]; ok {
+ opts.Token = gofastly.ToPointer(v.(string))
+ }
+ if v, ok := modified["url"]; ok {
+ opts.URL = gofastly.ToPointer(v.(string))
+ }
+ if v, ok := modified["index"]; ok {
+ opts.Index = gofastly.ToPointer(v.(string))
+ }
+ if v, ok := modified["format"]; ok {
+ opts.Format = gofastly.ToPointer(v.(string))
+ }
+ if v, ok := modified["format_version"]; ok {
+ opts.FormatVersion = gofastly.ToPointer(v.(int))
+ }
+ if v, ok := modified["response_condition"]; ok {
+ opts.ResponseCondition = gofastly.ToPointer(v.(string))
+ }
+ if v, ok := modified["placement"]; ok {
+ opts.Placement = gofastly.ToPointer(v.(string))
+ }
+
+ log.Printf("[DEBUG] Update GrafanaCloudLogs Opts: %#v", opts)
+ _, err := conn.UpdateGrafanaCloudLogs(&opts)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// Delete deletes the resource.
+func (h *GrafanaCloudLogsServiceAttributeHandler) Delete(_ context.Context, d *schema.ResourceData, resource map[string]any, serviceVersion int, conn *gofastly.Client) error {
+ opts := h.buildDelete(resource, d.Id(), serviceVersion)
+
+ log.Printf("[DEBUG] Fastly GrafanaCloudLogs logging endpoint removal opts: %#v", opts)
+
+ return deleteGrafanaCloudLogs(conn, opts)
+}
+
+func createGrafanaCloudLogs(conn *gofastly.Client, i *gofastly.CreateGrafanaCloudLogsInput) error {
+ _, err := conn.CreateGrafanaCloudLogs(i)
+ return err
+}
+
+func deleteGrafanaCloudLogs(conn *gofastly.Client, i *gofastly.DeleteGrafanaCloudLogsInput) error {
+ err := conn.DeleteGrafanaCloudLogs(i)
+
+ errRes, ok := err.(*gofastly.HTTPError)
+ if !ok {
+ return err
+ }
+
+ // 404 response codes don't result in an error propagating because a 404 could
+ // indicate that a resource was deleted elsewhere.
+ if !errRes.IsNotFound() {
+ return err
+ }
+
+ return nil
+}
+
+// flattenGrafanaCloudLogs models data into format suitable for saving to Terraform state.
+func flattenGrafanaCloudLogs(remoteState []*gofastly.GrafanaCloudLogs) []map[string]any {
+ var result []map[string]any
+ for _, resource := range remoteState {
+ data := map[string]any{}
+
+ if resource.Name != nil {
+ data["name"] = *resource.Name
+ }
+ if resource.User != nil {
+ data["user"] = *resource.User
+ }
+ if resource.Token != nil {
+ data["token"] = *resource.Token
+ }
+ if resource.URL != nil {
+ data["url"] = *resource.URL
+ }
+ if resource.Index != nil {
+ data["index"] = *resource.Index
+ }
+ if resource.Format != nil {
+ data["format"] = *resource.Format
+ }
+ if resource.FormatVersion != nil {
+ data["format_version"] = *resource.FormatVersion
+ }
+ if resource.Placement != nil {
+ data["placement"] = *resource.Placement
+ }
+ if resource.ResponseCondition != nil {
+ data["response_condition"] = *resource.ResponseCondition
+ }
+
+ // Prune any empty values that come from the default string value in structs.
+ for k, v := range data {
+ if v == "" {
+ delete(data, k)
+ }
+ }
+
+ result = append(result, data)
+ }
+
+ return result
+}
+
+func (h *GrafanaCloudLogsServiceAttributeHandler) buildCreate(grafanacloudlogsMap any, serviceID string, serviceVersion int) *gofastly.CreateGrafanaCloudLogsInput {
+ resource := grafanacloudlogsMap.(map[string]any)
+
+ vla := h.getVCLLoggingAttributes(resource)
+ opts := &gofastly.CreateGrafanaCloudLogsInput{
+ Format: gofastly.ToPointer(vla.format),
+ FormatVersion: vla.formatVersion,
+ Name: gofastly.ToPointer(resource["name"].(string)),
+ ServiceID: serviceID,
+ ServiceVersion: serviceVersion,
+ User: gofastly.ToPointer(resource["user"].(string)),
+ Token: gofastly.ToPointer(resource["token"].(string)),
+ URL: gofastly.ToPointer(resource["url"].(string)),
+ Index: gofastly.ToPointer(resource["index"].(string)),
+ }
+
+ // WARNING: The following fields shouldn't have an empty string passed.
+ // As it will cause the Fastly API to return an error.
+ // This is because go-fastly v7+ will not 'omitempty' due to pointer type.
+ if vla.placement != "" {
+ opts.Placement = gofastly.ToPointer(vla.placement)
+ }
+ if vla.responseCondition != "" {
+ opts.ResponseCondition = gofastly.ToPointer(vla.responseCondition)
+ }
+
+ return opts
+}
+
+func (h *GrafanaCloudLogsServiceAttributeHandler) buildDelete(grafanacloudlogsMap any, serviceID string, serviceVersion int) *gofastly.DeleteGrafanaCloudLogsInput {
+ resource := grafanacloudlogsMap.(map[string]any)
+
+ return &gofastly.DeleteGrafanaCloudLogsInput{
+ ServiceID: serviceID,
+ ServiceVersion: serviceVersion,
+ Name: resource["name"].(string),
+ }
+}
diff --git a/fastly/block_fastly_service_logging_grafanacloudlogs_test.go b/fastly/block_fastly_service_logging_grafanacloudlogs_test.go
new file mode 100644
index 000000000..39ca16040
--- /dev/null
+++ b/fastly/block_fastly_service_logging_grafanacloudlogs_test.go
@@ -0,0 +1,337 @@
+package fastly
+
+import (
+ "fmt"
+ "log"
+ "testing"
+
+ gofastly "github.com/fastly/go-fastly/v9/fastly"
+ "github.com/google/go-cmp/cmp"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+)
+
+func TestResourceFastlyFlattenGrafanaCloudLogs(t *testing.T) {
+ cases := []struct {
+ remote []*gofastly.GrafanaCloudLogs
+ local []map[string]any
+ }{
+ {
+ remote: []*gofastly.GrafanaCloudLogs{
+ {
+ ServiceVersion: gofastly.ToPointer(1),
+ Name: gofastly.ToPointer("grafanacloudlogs-endpoint"),
+ User: gofastly.ToPointer("123456"),
+ Token: gofastly.ToPointer("token"),
+ URL: gofastly.ToPointer("https://test123.grafana.net"),
+ Index: gofastly.ToPointer("{\"label\": \"value\"}"),
+
+ FormatVersion: gofastly.ToPointer(2),
+ },
+ },
+ local: []map[string]any{
+ {
+ "name": "grafanacloudlogs-endpoint",
+ "user": "123456",
+ "token": "token",
+ "url": "https://test123.grafana.net",
+ "index": "{\"label\": \"value\"}",
+ "format_version": 2,
+ },
+ },
+ },
+ }
+
+ for _, c := range cases {
+ out := flattenGrafanaCloudLogs(c.remote)
+ if diff := cmp.Diff(out, c.local); diff != "" {
+ t.Fatalf("Error matching: %s", diff)
+ }
+ }
+}
+
+var grafanacloudlogsDefaultFormat = `{
+ "timestamp": "%{strftime(\{"%Y-%m-%dT%H:%M:%S"\}, time.start)}V",
+ "client_ip": "%{req.http.Fastly-Client-IP}V",
+ "geo_country": "%{client.geo.country_name}V",
+ "geo_city": "%{client.geo.city}V",
+ "host": "%{if(req.http.Fastly-Orig-Host, req.http.Fastly-Orig-Host, req.http.Host)}V",
+ "url": "%{json.escape(req.url)}V",
+ "request_method": "%{json.escape(req.method)}V",
+ "request_protocol": "%{json.escape(req.proto)}V",
+ "request_referer": "%{json.escape(req.http.referer)}V",
+ "request_user_agent": "%{json.escape(req.http.User-Agent)}V",
+ "response_state": "%{json.escape(fastly_info.state)}V",
+ "response_status": %{resp.status}V,
+ "response_reason": %{if(resp.response, "%22"+json.escape(resp.response)+"%22", "null")}V,
+ "response_body_size": %{resp.body_bytes_written}V,
+ "fastly_server": "%{json.escape(server.identity)}V",
+ "fastly_is_edge": %{if(fastly.ff.visits_this_service == 0, "true", "false")}V
+}`
+
+func TestAccFastlyServiceVCL_logging_grafanacloudlogs_basic(t *testing.T) {
+ var service gofastly.ServiceDetail
+ name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
+ domain := fmt.Sprintf("fastly-test.%s.com", name)
+
+ log1 := gofastly.GrafanaCloudLogs{
+ Format: gofastly.ToPointer("%h %l %u %t \"%r\" %>s %b"),
+ FormatVersion: gofastly.ToPointer(2),
+ Name: gofastly.ToPointer("grafanacloudlogs-endpoint"),
+ ResponseCondition: gofastly.ToPointer(""),
+ ServiceVersion: gofastly.ToPointer(1),
+ User: gofastly.ToPointer("123456"),
+ Token: gofastly.ToPointer("token"),
+ URL: gofastly.ToPointer("https://test123.grafana.net"),
+ Index: gofastly.ToPointer("{\"label\": \"value\"}"),
+ }
+
+ log1AfterUpdate := gofastly.GrafanaCloudLogs{
+ Format: gofastly.ToPointer("%h %l %u %t \"%r\" %>s %b %T"),
+ FormatVersion: gofastly.ToPointer(2),
+ Name: gofastly.ToPointer("grafanacloudlogs-endpoint"),
+ ResponseCondition: gofastly.ToPointer(""),
+ ServiceVersion: gofastly.ToPointer(1),
+ User: gofastly.ToPointer("987654"),
+ Token: gofastly.ToPointer("t0k3n"),
+ URL: gofastly.ToPointer("https://test456.grafana.net"),
+ Index: gofastly.ToPointer("{\"label2\": \"value2\"}"),
+ }
+
+ log2 := gofastly.GrafanaCloudLogs{
+ Format: gofastly.ToPointer(grafanacloudlogsDefaultFormat + "\n"),
+ FormatVersion: gofastly.ToPointer(2),
+ Name: gofastly.ToPointer("another-grafanacloudlogs-endpoint"),
+ ResponseCondition: gofastly.ToPointer(""),
+ ServiceVersion: gofastly.ToPointer(1),
+ User: gofastly.ToPointer("123456"),
+ URL: gofastly.ToPointer("https://test789.grafana.net"),
+ Index: gofastly.ToPointer("{\"label3\": \"value3\"}"),
+ Token: gofastly.ToPointer("another-token"),
+ }
+
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() {
+ testAccPreCheck(t)
+ },
+ ProviderFactories: testAccProviders,
+ CheckDestroy: testAccCheckServiceVCLDestroy,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccServiceVCLGrafanaCloudLogsConfig(name, domain),
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckServiceExists("fastly_service_vcl.foo", &service),
+ testAccCheckFastlyServiceVCLGrafanaCloudLogsAttributes(&service, []*gofastly.GrafanaCloudLogs{&log1}, ServiceTypeVCL),
+ resource.TestCheckResourceAttr("fastly_service_vcl.foo", "name", name),
+ resource.TestCheckResourceAttr("fastly_service_vcl.foo", "logging_grafanacloudlogs.#", "1"),
+ ),
+ },
+
+ {
+ Config: testAccServiceVCLGrafanaCloudLogsConfigUpdate(name, domain),
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckServiceExists("fastly_service_vcl.foo", &service),
+ testAccCheckFastlyServiceVCLGrafanaCloudLogsAttributes(&service, []*gofastly.GrafanaCloudLogs{&log1AfterUpdate, &log2}, ServiceTypeVCL),
+ resource.TestCheckResourceAttr("fastly_service_vcl.foo", "name", name),
+ resource.TestCheckResourceAttr("fastly_service_vcl.foo", "logging_grafanacloudlogs.#", "2"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccFastlyServiceVCL_logging_grafanacloudlogs_basic_compute(t *testing.T) {
+ var service gofastly.ServiceDetail
+ name := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
+ domain := fmt.Sprintf("fastly-test.%s.com", name)
+
+ log1 := gofastly.GrafanaCloudLogs{
+ ServiceVersion: gofastly.ToPointer(1),
+ Name: gofastly.ToPointer("grafanacloudlogs-endpoint"),
+ User: gofastly.ToPointer("123456"),
+ Token: gofastly.ToPointer("token"),
+ URL: gofastly.ToPointer("https://test123.grafana.net"),
+ Index: gofastly.ToPointer("{\"label\": \"value\"}"),
+ }
+
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() {
+ testAccPreCheck(t)
+ },
+ ProviderFactories: testAccProviders,
+ CheckDestroy: testAccCheckServiceVCLDestroy,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccServiceVCLGrafanaCloudLogsComputeConfig(name, domain),
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckServiceExists("fastly_service_compute.foo", &service),
+ testAccCheckFastlyServiceVCLGrafanaCloudLogsAttributes(&service, []*gofastly.GrafanaCloudLogs{&log1}, ServiceTypeCompute),
+ resource.TestCheckResourceAttr(
+ "fastly_service_compute.foo", "name", name),
+ resource.TestCheckResourceAttr(
+ "fastly_service_compute.foo", "logging_grafanacloudlogs.#", "1"),
+ ),
+ },
+ },
+ })
+}
+
+func testAccCheckFastlyServiceVCLGrafanaCloudLogsAttributes(service *gofastly.ServiceDetail, grafanacloudlogs []*gofastly.GrafanaCloudLogs, serviceType string) resource.TestCheckFunc {
+ return func(_ *terraform.State) error {
+ conn := testAccProvider.Meta().(*APIClient).conn
+ grafanacloudlogsList, err := conn.ListGrafanaCloudLogs(&gofastly.ListGrafanaCloudLogsInput{
+ ServiceID: gofastly.ToValue(service.ServiceID),
+ ServiceVersion: gofastly.ToValue(service.ActiveVersion.Number),
+ })
+ if err != nil {
+ return fmt.Errorf("error looking up GrafanaCloudLogs Logging for (%s), version (%d): %s", gofastly.ToValue(service.Name), gofastly.ToValue(service.ActiveVersion.Number), err)
+ }
+
+ if len(grafanacloudlogsList) != len(grafanacloudlogs) {
+ return fmt.Errorf("grafanacloudlogs List count mismatch, expected (%d), got (%d)", len(grafanacloudlogs), len(grafanacloudlogsList))
+ }
+
+ log.Printf("[DEBUG] grafanacloudlogsList = %#v\n", grafanacloudlogsList)
+
+ var found int
+ for _, d := range grafanacloudlogs {
+ for _, dl := range grafanacloudlogsList {
+ if gofastly.ToValue(d.Name) == gofastly.ToValue(dl.Name) {
+ // we don't know these things ahead of time, so populate them now
+ d.ServiceID = service.ServiceID
+ d.ServiceVersion = service.ActiveVersion.Number
+ // We don't track these, so clear them out because we also won't know
+ // these ahead of time
+ dl.CreatedAt = nil
+ dl.UpdatedAt = nil
+
+ // Ignore VCL attributes for Compute and set to whatever is returned from the API.
+ if serviceType == ServiceTypeCompute {
+ dl.FormatVersion = d.FormatVersion
+ dl.Format = d.Format
+ dl.ResponseCondition = d.ResponseCondition
+ dl.Placement = d.Placement
+ }
+
+ if diff := cmp.Diff(d, dl); diff != "" {
+ return fmt.Errorf("bad match GrafanaCloudLogs logging match: %s", diff)
+ }
+ found++
+ }
+ }
+ }
+
+ if found != len(grafanacloudlogs) {
+ return fmt.Errorf("error matching GrafanaCloudLogs Logging rules")
+ }
+
+ return nil
+ }
+}
+
+func testAccServiceVCLGrafanaCloudLogsConfig(name string, domain string) string {
+ return fmt.Sprintf(`
+resource "fastly_service_vcl" "foo" {
+ name = "%s"
+
+ domain {
+ name = "%s"
+ comment = "tf-grafanacloudlogs-logging"
+ }
+
+ backend {
+ address = "aws.amazon.com"
+ name = "amazon docs"
+ }
+
+ logging_grafanacloudlogs {
+ name = "grafanacloudlogs-endpoint"
+ user = "123456"
+ token = "token"
+ url = "https://test123.grafana.net"
+ index = "{\"label\": \"value\"}"
+ format = "%%h %%l %%u %%t \"%%r\" %%>s %%b"
+ }
+
+ force_destroy = true
+}
+`, name, domain)
+}
+
+func testAccServiceVCLGrafanaCloudLogsConfigUpdate(name, domain string) string {
+ return fmt.Sprintf(`
+resource "fastly_service_vcl" "foo" {
+ name = "%s"
+
+ domain {
+ name = "%s"
+ comment = "tf-grafanacloudlogs-logging"
+ }
+
+ backend {
+ address = "aws.amazon.com"
+ name = "amazon docs"
+ }
+
+ logging_grafanacloudlogs {
+ name = "grafanacloudlogs-endpoint"
+ user = "987654"
+ token = "t0k3n"
+ url = "https://test456.grafana.net"
+ index = "{\"label2\": \"value2\"}"
+ format = "%%h %%l %%u %%t \"%%r\" %%>s %%b %%T"
+ }
+
+ logging_grafanacloudlogs {
+ name = "another-grafanacloudlogs-endpoint"
+ token = "another-token"
+ user = "123456"
+ url = "https://test789.grafana.net"
+ index = "{\"label3\": \"value3\"}"
+ format = <