Repository: honeycombio/terraform-provider-honeycombio
Pull request #485 (2024-06-13 21:39:24)

r/board: add SLO support
Adds support to place SLOs on boards.

Following the board's existing `query {}` blocks, this adds `slo {}` and takes an ID.

SLOs on Board are under active development and do not yet have an order but are visually displayed by their creation date. Future releases will support ordered placement.

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/honeycombio/terraform-provider-honeycombio/pull/485?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=honeycombio) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 82.91%. Comparing base [(`92f7561`)](https://app.codecov.io/gh/honeycombio/terraform-provider-honeycombio/commit/92f7561efc504ad03d6af865aedc2bf4bd5ff42a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=honeycombio) to head [(`6861deb`)](https://app.codecov.io/gh/honeycombio/terraform-provider-honeycombio/commit/6861debf36e742a5a41a4d8830571d0b3cbbda81?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=honeycombio).


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #485      +/-   ##
==========================================
+ Coverage   82.89%   82.91%   +0.02%     
==========================================
  Files          63       63              
  Lines        4308     4332      +24     
==========================================
+ Hits         3571     3592      +21     
- Misses        576      579       +3     
  Partials      161      161              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/honeycombio/terraform-provider-honeycombio/pull/485?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=honeycombio).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=honeycombio).


Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index c43e0ff..8aaeff7 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -79,7 +79,7 @@ jobs:
         run: go test -v -coverprofile=tf-coverage.txt -covermode=atomic ./internal/... ./honeycombio/...
 
       - name: Generate Coverage Report
-        uses: codecov/codecov-action@v4.4.1
+        uses: codecov/codecov-action@v4.5.0
         with:
           token: ${{ secrets.CODECOV_TOKEN }}
 
@@ -131,6 +131,6 @@ jobs:
         run: go test -v -coverprofile=tf-coverage.txt -covermode=atomic ./internal/... ./honeycombio/...
 
       - name: Generate Coverage Report
-        uses: codecov/codecov-action@v4.4.1
+        uses: codecov/codecov-action@v4.5.0
         with:
           token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/client/board.go b/client/board.go
index d602dfc..477385e 100644
--- a/client/board.go
+++ b/client/board.go
@@ -57,6 +57,8 @@ type Board struct {
 	Links BoardLinks `json:"links,omitempty"`
 	// A list of queries displayed on the board, in order of appearance.
 	Queries []BoardQuery `json:"queries"`
+	// A list of SLO IDs to be added to the board
+	SLOs []string `json:"slos"`
 }
 
 // BoardStyle determines how a Board should be displayed within the Honeycomb UI.
diff --git a/docs/resources/board.md b/docs/resources/board.md
index 2a09a89..f82b926 100644
--- a/docs/resources/board.md
+++ b/docs/resources/board.md
@@ -100,8 +100,9 @@ The following arguments are supported:
 * `column_layout` - (Optional) the number of columns to layout on the board, either `multi` (the default) or `single`. Only `visual` style boards (see below) have a column layout.
 * `style` - (Optional) Deprecated: All Boards are now displayed as `visual` style. How the board should be displayed in the UI, either `visual` (the default) or `list`.
 * `query` - (Optional) Zero or more configurations blocks (described below) with the queries of the board.
+* `slo` - (Optional) Up to six configuration blocks (described below) to place SLOs on the board.
 
-Each board configuration may have zero or more `query` blocks, which accepts the following arguments:
+Each board configuration may have zero or more `query` blocks, which accept the following arguments:
 
 * `query_id` - (Required) The ID of the Query to run.
 * `query_annotation_id` - (Optional) The ID of the Query Annotation to associate with this query.
@@ -120,6 +121,10 @@ Currently supported toggles are:
   See [Graph Settings](https://docs.honeycomb.io/working-with-your-data/graph-settings/) in the documentation for more information on any individual setting.
 * `query_style` - (Optional) How the query should be displayed within the board, either `graph` (the default), `table` or `combo`.
 
+Each board configuration may have up to six `slo` blocks, which take the following arguments:
+
+* `id` - (Required) The ID of the SLO to place on the board.
+
 ## Attribute Reference
 
 In addition to all arguments above, the following attributes are exported:
diff --git a/honeycombio/resource_board.go b/honeycombio/resource_board.go
index de22f8f..0619a60 100644
--- a/honeycombio/resource_board.go
+++ b/honeycombio/resource_board.go
@@ -144,6 +144,21 @@ See [Graph Settings](https://docs.honeycomb.io/working-with-your-data/graph-sett
 					},
 				},
 			},
+			"slo": {
+				Type:        schema.TypeSet,
+				Optional:    true,
+				Description: "An SLO to added to the board.",
+				MaxItems:    6,
+				Elem: &schema.Resource{
+					Schema: map[string]*schema.Schema{
+						"id": {
+							Type:        schema.TypeString,
+							Required:    true,
+							Description: "The ID of the SLO.",
+						},
+					},
+				},
+			},
 		},
 	}
 }
@@ -200,9 +215,16 @@ func resourceBoardRead(ctx context.Context, d *schema.ResourceData, meta interfa
 			"query_annotation_id": q.QueryAnnotationID,
 		}
 	}
-
 	d.Set("query", queries)
 
+	slos := make([]map[string]interface{}, len(b.SLOs))
+	for i, s := range b.SLOs {
+		slos[i] = map[string]interface{}{
+			"id": s,
+		}
+	}
+	d.Set("slo", slos)
+
 	return nil
 }
 
@@ -258,6 +280,12 @@ func expandBoard(d *schema.ResourceData) (*honeycombio.Board, error) {
 		})
 	}
 
+	slos := []string{}
+	// Board SLOs don't currently have an order: the API returns them sorted by creation time
+	for _, s := range d.Get("slo").(*schema.Set).List() {
+		slos = append(slos, s.(map[string]interface{})["id"].(string))
+	}
+
 	board := &honeycombio.Board{
 		ID:           d.Id(),
 		Name:         d.Get("name").(string),
@@ -265,6 +293,7 @@ func expandBoard(d *schema.ResourceData) (*honeycombio.Board, error) {
 		Style:        honeycombio.BoardStyle(d.Get("style").(string)),
 		ColumnLayout: honeycombio.BoardColumnStyle(d.Get("column_layout").(string)),
 		Queries:      queries,
+		SLOs:         slos,
 	}
 
 	if board.Style == honeycombio.BoardStyleList && board.ColumnLayout != "" {


Test patch:
diff --git a/client/board_test.go b/client/board_test.go
index 2b3d9e9..a1624f1 100644
--- a/client/board_test.go
+++ b/client/board_test.go
@@ -42,6 +42,24 @@ func TestBoards(t *testing.T) {
 	})
 	require.NoError(t, err)
 
+	sli, err := c.DerivedColumns.Create(ctx, dataset, &client.DerivedColumn{
+		Alias:      test.RandomStringWithPrefix("test.", 8),
+		Expression: "BOOL(1)",
+	})
+	require.NoError(t, err)
+	slo, err := c.SLOs.Create(ctx, dataset, &client.SLO{
+		Name:             test.RandomStringWithPrefix("test.", 8),
+		TimePeriodDays:   7,
+		TargetPerMillion: 990000,
+		SLI:              client.SLIRef{Alias: sli.Alias},
+	})
+	require.NoError(t, err)
+	t.Cleanup(func() {
+		//nolint:errcheck
+		c.SLOs.Delete(ctx, dataset, slo.ID)
+		c.DerivedColumns.Delete(ctx, dataset, sli.ID)
+	})
+
 	t.Run("Create", func(t *testing.T) {
 		data := &client.Board{
 			Name:         test.RandomStringWithPrefix("test.", 8),
@@ -57,6 +75,7 @@ func TestBoards(t *testing.T) {
 					GraphSettings: client.BoardGraphSettings{OmitMissingValues: true, UseUTCXAxis: true},
 				},
 			},
+			SLOs: []string{slo.ID},
 		}
 		b, err = c.Boards.Create(ctx, data)
 		require.NoError(t, err)
@@ -104,6 +123,7 @@ func TestBoards(t *testing.T) {
 			QueryID:       *newQuery.ID,
 			GraphSettings: client.BoardGraphSettings{UseUTCXAxis: true},
 		})
+		b.SLOs = []string{}
 
 		result, err := c.Boards.Update(ctx, b)
 		require.NoError(t, err)
diff --git a/honeycombio/resource_board_test.go b/honeycombio/resource_board_test.go
index 5d0ffc7..c0f308b 100644
--- a/honeycombio/resource_board_test.go
+++ b/honeycombio/resource_board_test.go
@@ -7,6 +7,10 @@ import (
 
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+	"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+	"github.com/stretchr/testify/require"
+
+	"github.com/honeycombio/terraform-provider-honeycombio/client"
 )
 
 func TestAccHoneycombioBoard_basic(t *testing.T) {
@@ -183,6 +187,146 @@ resource "honeycombio_board" "test" {
 	})
 }
 
+func TestAccBoard_withSLOs(t *testing.T) {
+	ctx := context.Background()
+	dataset := testAccDataset()
+	c := testAccClient(t)
+
+	sli1, err := c.DerivedColumns.Create(ctx, dataset, &client.DerivedColumn{
+		Alias:      "sli." + acctest.RandString(8),
+		Expression: "BOOL(1)",
+	})
+	require.NoError(t, err)
+	slo1, err := c.SLOs.Create(ctx, dataset, &client.SLO{
+		Name:             acctest.RandString(8) + " SLO",
+		TimePeriodDays:   14,
+		TargetPerMillion: 995000,
+		SLI:              client.SLIRef{Alias: sli1.Alias},
+	})
+	require.NoError(t, err)
+
+	sli2, err := c.DerivedColumns.Create(ctx, dataset, &client.DerivedColumn{
+		Alias:      "sli." + acctest.RandString(8),
+		Expression: "BOOL(1)",
+	})
+	require.NoError(t, err)
+	slo2, err := c.SLOs.Create(ctx, dataset, &client.SLO{
+		Name:             acctest.RandString(8) + " SLO",
+		TimePeriodDays:   14,
+		TargetPerMillion: 995000,
+		SLI:              client.SLIRef{Alias: sli2.Alias},
+	})
+	require.NoError(t, err)
+
+	//nolint:errcheck
+	t.Cleanup(func() {
+		// remove SLOs, and SLIs at end of test run
+		c.SLOs.Delete(ctx, dataset, slo1.ID)
+		c.SLOs.Delete(ctx, dataset, slo2.ID)
+		c.DerivedColumns.Delete(ctx, dataset, sli1.ID)
+		c.DerivedColumns.Delete(ctx, dataset, sli2.ID)
+	})
+
+	resource.Test(t, resource.TestCase{
+		PreCheck:                 testAccPreCheck(t),
+		ProtoV5ProviderFactories: testAccProtoV5ProviderFactory,
+		Steps: []resource.TestStep{
+			// create a board with one SLO on it
+			{
+				Config: fmt.Sprintf(`
+data "honeycombio_query_specification" "test" {
+  calculation {
+    op = "COUNT"
+  }
+}
+
+resource "honeycombio_query" "test" {
+  dataset    = "%s"
+  query_json = data.honeycombio_query_specification.test.json
+}
+
+resource "honeycombio_board" "test" {
+  name          = "board with some SLOs"
+
+  query {
+    query_id = honeycombio_query.test.id
+  }
+
+  slo {
+    id = "%s"
+  }
+}
+`, dataset, slo1.ID),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckBoardExists(t, "honeycombio_board.test"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "slo.#", "1"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "slo.0.id", slo1.ID),
+				),
+			},
+			{
+				// update that board to have two SLOs
+				Config: fmt.Sprintf(`
+data "honeycombio_query_specification" "test" {
+  calculation {
+    op = "COUNT"
+  }
+}
+
+resource "honeycombio_query" "test" {
+  dataset    = "%s"
+  query_json = data.honeycombio_query_specification.test.json
+}
+
+resource "honeycombio_board" "test" {
+  name          = "board with some SLOs"
+
+  query {
+    query_id = honeycombio_query.test.id
+  }
+
+  slo {
+    id = "%s"
+  }
+
+  slo {
+    id = "%s"
+  }
+}`, dataset, slo2.ID, slo1.ID),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckBoardExists(t, "honeycombio_board.test"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "slo.#", "2"),
+				),
+			},
+			{
+				// remove all SLOs from that board
+				Config: fmt.Sprintf(`
+data "honeycombio_query_specification" "test" {
+  calculation {
+    op = "COUNT"
+  }
+}
+
+resource "honeycombio_query" "test" {
+  dataset    = "%s"
+  query_json = data.honeycombio_query_specification.test.json
+}
+
+resource "honeycombio_board" "test" {
+  name          = "board with no SLOs"
+
+  query {
+    query_id = honeycombio_query.test.id
+  }
+}`, dataset),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckBoardExists(t, "honeycombio_board.test"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "slo.#", "0"),
+				),
+			},
+		},
+	})
+}
+
 func testAccBoardConfig(dataset string) string {
 	return fmt.Sprintf(`
 data "honeycombio_query_specification" "test" {
diff --git a/internal/provider/slos_data_source_test.go b/internal/provider/slos_data_source_test.go
index 9acb1f9..590e2c2 100644
--- a/internal/provider/slos_data_source_test.go
+++ b/internal/provider/slos_data_source_test.go
@@ -111,7 +111,7 @@ data "honeycombio_slos" "exact" {
 }
 `, dataset, testPrefix),
 				Check: resource.ComposeAggregateTestCheckFunc(
-					resource.TestCheckResourceAttr("data.honeycombio_slos.all", "ids.#", "3"),
+					resource.TestCheckResourceAttr("data.honeycombio_slos.all", "ids.#", "5"),
 					resource.TestCheckResourceAttr("data.honeycombio_slos.regex", "ids.#", "2"),
 					resource.TestCheckResourceAttr("data.honeycombio_slos.exact", "ids.#", "1"),
 				),