Repository: honeycombio/terraform-provider-honeycombio (language: python)

Issue:
Resource: marker
A resource to create markers.

More information: https://docs.honeycomb.io/working-with-your-data/customizing-your-query/markers/
Available API: https://docs.honeycomb.io/api/markers/

HCL code:

```hcl
resource "honeycombio_marker" "marker" {
  message = "Message"
  type    = "deploy"
  url     = "https://www.honeycomb.io/"
}
```


Discussion / hints:
This is already implemented and has been released [with v0.0.1](https://github.com/kvrhdn/terraform-provider-honeycombio/releases/tag/v0.0.1)!

Fix patch:
diff --git a/docs/resources/query.md b/docs/resources/query.md
new file mode 100644
index 00000000..e7117730
--- /dev/null
+++ b/docs/resources/query.md
@@ -0,0 +1,48 @@
+# Resource: honeycombio_query
+
+Creates a query in a dataset.
+
+-> **Note** Queries can only be created or read. Any changes will result in a new query object being created, and destroying it does nothing.
+
+## Example Usage
+
+```hcl
+variable "dataset" {
+  type = string
+}
+
+data "honeycombio_query" "test_query" {
+  calculation {
+    op     = "AVG"
+    column = "duration_ms"
+  }
+
+  filter {
+    column = "duration_ms"
+    op     = ">"
+    value  = "200"
+  }
+}
+
+resource "honeycombio_query" "test_query" {
+  dataset = "%s"
+  query_json = data.honeycombio_query.test_query.json
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `dataset` - (Required) The dataset this query is added to.
+* `query_json` - (Required) A JSON object describing the query according to the [Query Specification](https://docs.honeycomb.io/api/query-specification/#fields-on-a-query-specification). While the JSON can be constructed manually, it is easiest to use the [`honeycombio_query`](../data-sources/query.md) data source.
+
+## Attribute Reference
+
+In addition to all arguments above, the following attributes are exported:
+
+* `id` - ID of the query. Useful for adding it to a board and/or creating a query annotation.
+
+## Import
+
+Queries cannot be imported.
diff --git a/docs/resources/query_annotation.md b/docs/resources/query_annotation.md
new file mode 100644
index 00000000..b7118b91
--- /dev/null
+++ b/docs/resources/query_annotation.md
@@ -0,0 +1,59 @@
+# Resource: honeycombio_query_annotation
+
+Creates a query annotation in a dataset.
+
+-> **Note** A query annotation points to a specific query. Any change to the query will result in a new query ID and the annotation will no longer apply.
+If you use the "honeycombio_query" to determine the `query_id` parameter (as in the example below), Terraform will destroy the old query annotation and create a new one.
+If this is wrong for your use case, please open an issue in [kvrhdn/terraform-provider-honeycombio](https://github.com/kvrhdn/terraform-provider-honeycombio).
+
+## Example Usage
+
+```hcl
+variable "dataset" {
+  type = string
+}
+
+data "honeycombio_query" "test_query" {
+  calculation {
+    op     = "AVG"
+    column = "duration_ms"
+  }
+
+  filter {
+    column = "duration_ms"
+    op     = ">"
+    value  = 10
+  }
+}
+
+resource "honeycombio_query" "test_query" {
+  dataset    = var.dataset
+  query_json = data.honeycombio_query.test_query.json
+}
+
+resource "honeycombio_query_annotation" "test_annotation" {
+	dataset     = var.dataset
+	query_id    = honeycombio_query.test_query.id
+	name        = "My Cool Query"
+	description = "Describes my cool query (optional)"
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `dataset` - (Required) The dataset this query is added to.
+* `query_id` - (Required) The ID of the query that the annotation will be created on. Note that a query can have more than one annotation.
+* `name` - (Required) The name of the query annotation that will display in the Honeycomb UI.
+* `description` - (Optional) The description for the query annotation.
+
+## Attribute Reference
+
+In addition to all arguments above, the following attributes are exported:
+
+* `id` - ID of the query annotation. Useful for adding it to a board.
+
+## Import
+
+Query annotations cannot be imported.
diff --git a/go.mod b/go.mod
index 1154257f..4c0159fc 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,6 @@ require (
 	github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
 	github.com/hashicorp/terraform-plugin-sdk/v2 v2.4.3
 	github.com/joho/godotenv v1.3.0
-	github.com/kvrhdn/go-honeycombio v0.2.1
+	github.com/kvrhdn/go-honeycombio v0.2.2
 	github.com/stretchr/testify v1.7.0
 )
diff --git a/go.sum b/go.sum
index 7437ecf8..f1c25092 100644
--- a/go.sum
+++ b/go.sum
@@ -214,8 +214,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/kvrhdn/go-honeycombio v0.2.1 h1:rvT9dzMnrIMj5pk2WfTtJPgxEdI1C/I/rNh80Z7jV54=
-github.com/kvrhdn/go-honeycombio v0.2.1/go.mod h1:ZvOwJXuMuBxdtYYVvjwZDJu/UNpGez7pQ4aGBuuHpFE=
+github.com/kvrhdn/go-honeycombio v0.2.2 h1:0iXkxEiYVEOdMyGB0tcS8AdgfmcuP0alf8+IXeBsRDY=
+github.com/kvrhdn/go-honeycombio v0.2.2/go.mod h1:ZvOwJXuMuBxdtYYVvjwZDJu/UNpGez7pQ4aGBuuHpFE=
 github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
diff --git a/honeycombio/provider.go b/honeycombio/provider.go
index 40e3bcd7..f5fbbd73 100644
--- a/honeycombio/provider.go
+++ b/honeycombio/provider.go
@@ -49,12 +49,14 @@ func Provider() *schema.Provider {
 			"honeycombio_trigger_recipient": dataSourceHoneycombioSlackRecipient(),
 		},
 		ResourcesMap: map[string]*schema.Resource{
-			"honeycombio_board":          newBoard(),
-			"honeycombio_column":         newColumn(),
-			"honeycombio_dataset":        newDataset(),
-			"honeycombio_derived_column": newDerivedColumn(),
-			"honeycombio_marker":         newMarker(),
-			"honeycombio_trigger":        newTrigger(),
+			"honeycombio_board":            newBoard(),
+			"honeycombio_column":           newColumn(),
+			"honeycombio_dataset":          newDataset(),
+			"honeycombio_derived_column":   newDerivedColumn(),
+			"honeycombio_marker":           newMarker(),
+			"honeycombio_query":            newQuery(),
+			"honeycombio_query_annotation": newQueryAnnotation(),
+			"honeycombio_trigger":          newTrigger(),
 		},
 	}
 
diff --git a/honeycombio/resource_query.go b/honeycombio/resource_query.go
new file mode 100644
index 00000000..e2eb4356
--- /dev/null
+++ b/honeycombio/resource_query.go
@@ -0,0 +1,81 @@
+package honeycombio
+
+import (
+	"context"
+	"encoding/json"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/kvrhdn/go-honeycombio"
+)
+
+func newQuery() *schema.Resource {
+	return &schema.Resource{
+		CreateContext: resourceQueryCreate,
+		ReadContext:   resourceQueryRead,
+		UpdateContext: nil,
+		DeleteContext: schema.NoopContext,
+		Importer:      nil,
+
+		Schema: map[string]*schema.Schema{
+			"query_json": {
+				Type:             schema.TypeString,
+				Required:         true,
+				ForceNew:         true,
+				ValidateDiagFunc: validateQueryJSON(),
+			},
+			"dataset": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+		},
+	}
+}
+
+func resourceQueryCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	client := meta.(*honeycombio.Client)
+
+	dataset := d.Get("dataset").(string)
+	var querySpec honeycombio.QuerySpec
+	err := json.Unmarshal([]byte(d.Get("query_json").(string)), &querySpec)
+	if err != nil {
+		return diag.FromErr(err)
+	}
+
+	query, err := client.Queries.Create(ctx, dataset, &querySpec)
+	if err != nil {
+		return diag.FromErr(err)
+	}
+
+	d.SetId(*query.ID)
+	return resourceQueryRead(ctx, d, meta)
+}
+
+func resourceQueryRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	client := meta.(*honeycombio.Client)
+
+	dataset := d.Get("dataset").(string)
+
+	query, err := client.Queries.Get(ctx, dataset, d.Id())
+	if err != nil {
+		if err == honeycombio.ErrNotFound {
+			d.SetId("")
+			return nil
+		}
+		return diag.FromErr(err)
+	}
+
+	d.SetId(*query.ID)
+	// Normalize the query before converting it to JSON to avoid unwanted diffs
+	query.ID = nil
+	if query.FilterCombination == "" {
+		query.FilterCombination = honeycombio.FilterCombinationAnd
+	}
+	queryJSON, err := encodeQuery(query)
+	if err != nil {
+		return diag.FromErr(err)
+	}
+	d.Set("query_json", queryJSON)
+	return nil
+}
diff --git a/honeycombio/resource_query_annotation.go b/honeycombio/resource_query_annotation.go
new file mode 100644
index 00000000..5eedc558
--- /dev/null
+++ b/honeycombio/resource_query_annotation.go
@@ -0,0 +1,106 @@
+package honeycombio
+
+import (
+	"context"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	"github.com/kvrhdn/go-honeycombio"
+)
+
+func newQueryAnnotation() *schema.Resource {
+	return &schema.Resource{
+		CreateContext: resourceQueryAnnotationCreate,
+		ReadContext:   resourceQueryAnnotationRead,
+		UpdateContext: resourceQueryAnnotationUpdate,
+		DeleteContext: resourceQueryAnnotationDestroy,
+		Importer:      nil,
+
+		Schema: map[string]*schema.Schema{
+			"dataset": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"query_id": {
+				Type:     schema.TypeString,
+				Required: true,
+				ForceNew: true,
+			},
+			"name": {
+				Type:     schema.TypeString,
+				Required: true,
+			},
+			"description": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
+		},
+	}
+}
+
+func buildQueryAnnotation(d *schema.ResourceData) *honeycombio.QueryAnnotation {
+	return &honeycombio.QueryAnnotation{
+		ID:          d.Id(),
+		Name:        d.Get("name").(string),
+		Description: d.Get("description").(string),
+		QueryID:     d.Get("query_id").(string),
+	}
+}
+
+func resourceQueryAnnotationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	client := meta.(*honeycombio.Client)
+	dataset := d.Get("dataset").(string)
+	queryAnnotation := buildQueryAnnotation(d)
+
+	queryAnnotation, err := client.QueryAnnotations.Create(ctx, dataset, queryAnnotation)
+	if err != nil {
+		return diag.FromErr(err)
+	}
+
+	d.SetId(queryAnnotation.ID)
+	return resourceQueryAnnotationRead(ctx, d, meta)
+}
+
+func resourceQueryAnnotationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	client := meta.(*honeycombio.Client)
+	dataset := d.Get("dataset").(string)
+	queryAnnotation := buildQueryAnnotation(d)
+
+	_, err := client.QueryAnnotations.Update(ctx, dataset, queryAnnotation)
+	if err != nil {
+		return diag.FromErr(err)
+	}
+
+	return resourceQueryAnnotationRead(ctx, d, meta)
+}
+
+func resourceQueryAnnotationDestroy(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	client := meta.(*honeycombio.Client)
+	dataset := d.Get("dataset").(string)
+
+	err := client.QueryAnnotations.Delete(ctx, dataset, d.Id())
+	if err != nil {
+		return diag.FromErr(err)
+	}
+	return nil
+}
+
+func resourceQueryAnnotationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	client := meta.(*honeycombio.Client)
+	dataset := d.Get("dataset").(string)
+
+	queryAnnotation, err := client.QueryAnnotations.Get(ctx, dataset, d.Id())
+	if err != nil {
+		if err == honeycombio.ErrNotFound {
+			d.SetId("")
+			return nil
+		}
+		return diag.FromErr(err)
+	}
+
+	d.Set("name", queryAnnotation.Name)
+	d.Set("description", queryAnnotation.Description)
+	d.Set("query_id", queryAnnotation.QueryID)
+	return nil
+}


Test patch:
diff --git a/honeycombio/resource_query_annotation_test.go b/honeycombio/resource_query_annotation_test.go
new file mode 100644
index 00000000..14fa750f
--- /dev/null
+++ b/honeycombio/resource_query_annotation_test.go
@@ -0,0 +1,86 @@
+package honeycombio
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAccHoneycombioQueryAnnotation_update(t *testing.T) {
+	dataset := testAccDataset()
+	firstName := "first annotation name"
+	secondName := "second annotation name"
+
+	resource.Test(t, resource.TestCase{
+		PreCheck:          testAccPreCheck(t),
+		ProviderFactories: testAccProviderFactories,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccResourceQueryAnnotationConfig(dataset, firstName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckQueryAnnotationExists(t, dataset, "honeycombio_query_annotation.test", firstName),
+				),
+			},
+			{
+				Config: testAccResourceQueryAnnotationConfig(dataset, secondName),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckQueryAnnotationExists(t, dataset, "honeycombio_query_annotation.test", secondName),
+				),
+			},
+		},
+	})
+}
+
+func testAccResourceQueryAnnotationConfig(dataset string, name string) string {
+	return fmt.Sprintf(`
+data "honeycombio_query" "test" {
+  calculation {
+    op     = "AVG"
+    column = "duration_ms"
+  }
+
+  filter {
+    column = "duration_ms"
+    op     = ">"
+    value  = 10
+  }
+}
+
+resource "honeycombio_query" "test" {
+  dataset = "%s"
+  query_json = data.honeycombio_query.test.json
+}
+
+resource "honeycombio_query_annotation" "test" {
+	dataset = "%s"
+	query_id = honeycombio_query.test.id
+	name = "%s"
+	description = "Test query annotation description"
+}
+`, dataset, dataset, name)
+}
+
+func testAccCheckQueryAnnotationExists(t *testing.T, dataset string, resourceName string, name string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		resourceState, ok := s.RootModule().Resources[resourceName]
+		if !ok {
+			return fmt.Errorf("not found: %s", resourceName)
+		}
+
+		client := testAccClient(t)
+		createdQueryAnnotation, err := client.QueryAnnotations.Get(context.Background(), dataset, resourceState.Primary.ID)
+		if err != nil {
+			return fmt.Errorf("could not find created query: %w", err)
+		}
+
+		assert.Equal(t, resourceState.Primary.ID, createdQueryAnnotation.ID)
+		assert.Equal(t, name, createdQueryAnnotation.Name)
+		assert.Equal(t, resourceState.Primary.Attributes["query_id"], createdQueryAnnotation.QueryID)
+
+		return nil
+	}
+}
diff --git a/honeycombio/resource_query_test.go b/honeycombio/resource_query_test.go
new file mode 100644
index 00000000..6547aa6e
--- /dev/null
+++ b/honeycombio/resource_query_test.go
@@ -0,0 +1,100 @@
+package honeycombio
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"strconv"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+	"github.com/kvrhdn/go-honeycombio"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAccHoneycombioQuery_update(t *testing.T) {
+	dataset := testAccDataset()
+	firstDuration := 20
+	secondDuration := 40
+
+	resource.Test(t, resource.TestCase{
+		PreCheck:          testAccPreCheck(t),
+		ProviderFactories: testAccProviderFactories,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccResourceQueryConfig(dataset, firstDuration),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckQueryExists(t, dataset, "honeycombio_query.test", firstDuration),
+				),
+			},
+			{
+				Config: testAccResourceQueryConfig(dataset, secondDuration),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckQueryExists(t, dataset, "honeycombio_query.test", secondDuration),
+				),
+			},
+		},
+	})
+}
+
+func testAccResourceQueryConfig(dataset string, duration int) string {
+	return fmt.Sprintf(`
+data "honeycombio_query" "test" {
+  calculation {
+    op     = "AVG"
+    column = "duration_ms"
+  }
+
+  filter {
+    column = "duration_ms"
+    op     = ">"
+    value  = %d
+  }
+}
+
+resource "honeycombio_query" "test" {
+  dataset = "%s"
+  query_json = data.honeycombio_query.test.json
+}
+`, duration, dataset)
+}
+
+func testAccCheckQueryExists(t *testing.T, dataset string, name string, duration int) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		resourceState, ok := s.RootModule().Resources[name]
+		if !ok {
+			return fmt.Errorf("not found: %s", name)
+		}
+
+		client := testAccClient(t)
+		createdQuery, err := client.Queries.Get(context.Background(), dataset, resourceState.Primary.ID)
+		if err != nil {
+			return fmt.Errorf("could not find created query: %w", err)
+		}
+
+		expectedQuery := &honeycombio.QuerySpec{
+			ID: &resourceState.Primary.ID,
+			Calculations: []honeycombio.CalculationSpec{
+				{
+					Op:     honeycombio.CalculationOpAvg,
+					Column: honeycombio.StringPtr("duration_ms"),
+				},
+			},
+			Filters: []honeycombio.FilterSpec{
+				{
+					Column: "duration_ms",
+					Op:     ">",
+					Value:  strconv.Itoa(duration),
+				},
+			},
+			TimeRange: honeycombio.IntPtr(7200),
+		}
+
+		ok = assert.Equal(t, expectedQuery, createdQuery)
+		if !ok {
+			return errors.New("created query did not match expected query")
+		}
+		return nil
+	}
+}