Skip to content

Commit a7fbb01

Browse files
Folder: Use UID instead of numerical ID (#1469)
The numerical ID is deprecated since v9. Let's remove it on next major version
1 parent e481fd5 commit a7fbb01

16 files changed

+125
-130
lines changed

.golangci.toml

-10
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,3 @@ text = "deprecatedComment: the proper format is `Deprecated: <text>`"
4242
[[issues.exclude-rules]]
4343
linters = ["gosec"]
4444
text = "G402: TLS MinVersion too low."
45-
46-
# TODO: Use Folder UID instead where possible
47-
[[issues.exclude-rules]]
48-
linters = ["staticcheck"]
49-
text = "use FolderUID instead"
50-
51-
# TODO: Use Folder UID instead where possible
52-
[[issues.exclude-rules]]
53-
linters = ["staticcheck"]
54-
text = "use UID instead"

docs/data-sources/dashboard.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ data "grafana_dashboard" "from_uid" {
5252
### Read-Only
5353

5454
- `config_json` (String) The complete dashboard model JSON.
55-
- `folder` (Number) The numerical ID of the folder where the Grafana dashboard is found.
55+
- `folder` (Number, Deprecated) Deprecated. Use `folder_uid` instead
56+
- `folder_uid` (String) The UID of the folder where the Grafana dashboard is found.
5657
- `id` (String) The ID of this resource.
5758
- `is_starred` (Boolean) Whether or not the Grafana dashboard is starred. Starred Dashboards will show up on your own Home Dashboard by default, and are a convenient way to mark Dashboards that you’re interested in.
5859
- `slug` (String) URL slug of the dashboard (deprecated).

docs/data-sources/dashboards.md

+9-8
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,15 @@ data "grafana_dashboards" "tags" {
5454
tags = jsondecode(grafana_dashboard.data_source_dashboards1.config_json)["tags"]
5555
}
5656
57-
data "grafana_dashboards" "folder_ids" {
58-
org_id = grafana_organization.test.id
59-
folder_ids = [grafana_dashboard.data_source_dashboards1.folder]
57+
data "grafana_dashboards" "folder_uids" {
58+
org_id = grafana_organization.test.id
59+
folder_uids = [grafana_dashboard.data_source_dashboards1.folder]
6060
}
6161
62-
data "grafana_dashboards" "folder_ids_tags" {
63-
org_id = grafana_organization.test.id
64-
folder_ids = [grafana_dashboard.data_source_dashboards1.folder]
65-
tags = jsondecode(grafana_dashboard.data_source_dashboards1.config_json)["tags"]
62+
data "grafana_dashboards" "folder_uids_tags" {
63+
org_id = grafana_organization.test.id
64+
folder_uids = [grafana_dashboard.data_source_dashboards1.folder]
65+
tags = jsondecode(grafana_dashboard.data_source_dashboards1.config_json)["tags"]
6666
}
6767
6868
// use depends_on to wait for dashboard resource to be created before searching
@@ -98,7 +98,8 @@ data "grafana_dashboards" "wrong_org" {
9898

9999
### Optional
100100

101-
- `folder_ids` (List of Number) Numerical IDs of Grafana folders containing dashboards. Specify to filter for dashboards by folder (eg. `[0]` for General folder), or leave blank to get all dashboards in all folders.
101+
- `folder_ids` (List of Number, Deprecated) Deprecated, use `folder_uids` instead.
102+
- `folder_uids` (List of String) UIDs of Grafana folders containing dashboards. Specify to filter for dashboards by folder (eg. `["General"]` for General folder), or leave blank to get all dashboards in all folders.
102103
- `limit` (Number) Maximum number of dashboard search results to return. Defaults to `5000`.
103104
- `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used.
104105
- `tags` (List of String) List of string Grafana dashboard tags to search for, eg. `["prod"]`. Used only as search input, i.e., attribute value will remain unchanged.

docs/data-sources/library_panel.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ data "grafana_dashboard" "from_library_panel_connection" {
9797
- `created` (String) Timestamp when the library panel was created.
9898
- `dashboard_ids` (List of Number) Numerical IDs of Grafana dashboards containing the library panel.
9999
- `description` (String) Description of the library panel.
100-
- `folder_id` (String) ID of the folder where the library panel is stored.
100+
- `folder_id` (String, Deprecated) Deprecated. Use `folder_uid` instead
101101
- `folder_name` (String) Name of the folder containing the library panel.
102102
- `folder_uid` (String) Unique ID (UID) of the folder containing the library panel.
103103
- `id` (String) The ID of this resource.

docs/resources/library_panel.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ resource "grafana_library_panel" "test" {
4343

4444
### Optional
4545

46-
- `folder_id` (String) ID of the folder where the library panel is stored.
46+
- `folder_id` (String, Deprecated) Deprecated. Use `folder_uid` instead
47+
- `folder_uid` (String) Unique ID (UID) of the folder containing the library panel.
4748
- `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used.
4849
- `uid` (String) The unique identifier (UID) of a library panel uniquely identifies library panels between multiple Grafana installs. It’s automatically generated unless you specify it during library panel creation.The UID provides consistent URLs for accessing library panels and when syncing library panels between multiple Grafana installs.
4950

@@ -53,7 +54,6 @@ resource "grafana_library_panel" "test" {
5354
- `dashboard_ids` (List of Number) Numerical IDs of Grafana dashboards containing the library panel.
5455
- `description` (String) Description of the library panel.
5556
- `folder_name` (String) Name of the folder containing the library panel.
56-
- `folder_uid` (String) Unique ID (UID) of the folder containing the library panel.
5757
- `id` (String) The ID of this resource.
5858
- `panel_id` (Number) The numeric ID of the library panel computed by Grafana.
5959
- `type` (String) Type of the library panel (eg. text).

examples/data-sources/grafana_dashboards/data-source.tf

+7-7
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ data "grafana_dashboards" "tags" {
3434
tags = jsondecode(grafana_dashboard.data_source_dashboards1.config_json)["tags"]
3535
}
3636

37-
data "grafana_dashboards" "folder_ids" {
38-
org_id = grafana_organization.test.id
39-
folder_ids = [grafana_dashboard.data_source_dashboards1.folder]
37+
data "grafana_dashboards" "folder_uids" {
38+
org_id = grafana_organization.test.id
39+
folder_uids = [grafana_dashboard.data_source_dashboards1.folder]
4040
}
4141

42-
data "grafana_dashboards" "folder_ids_tags" {
43-
org_id = grafana_organization.test.id
44-
folder_ids = [grafana_dashboard.data_source_dashboards1.folder]
45-
tags = jsondecode(grafana_dashboard.data_source_dashboards1.config_json)["tags"]
42+
data "grafana_dashboards" "folder_uids_tags" {
43+
org_id = grafana_organization.test.id
44+
folder_uids = [grafana_dashboard.data_source_dashboards1.folder]
45+
tags = jsondecode(grafana_dashboard.data_source_dashboards1.config_json)["tags"]
4646
}
4747

4848
// use depends_on to wait for dashboard resource to be created before searching

internal/resources/grafana/common_check_exists_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,9 @@ var (
144144
},
145145
)
146146
folderCheckExists = newCheckExistsHelper(
147-
func(f *models.Folder) string { return strconv.FormatInt(f.ID, 10) },
148-
func(client *goapi.GrafanaHTTPAPI, id string) (*models.Folder, error) {
149-
resp, err := client.Folders.GetFolderByID(mustParseInt64(id))
147+
func(f *models.Folder) string { return f.UID },
148+
func(client *goapi.GrafanaHTTPAPI, uid string) (*models.Folder, error) {
149+
resp, err := client.Folders.GetFolderByUID(uid)
150150
return payloadOrError(resp, err)
151151
},
152152
)

internal/resources/grafana/data_source_dashboard.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,13 @@ func datasourceDashboard() *schema.Resource {
5353
"folder": {
5454
Type: schema.TypeInt,
5555
Computed: true,
56-
Description: "The numerical ID of the folder where the Grafana dashboard is found.",
56+
Description: "Deprecated. Use `folder_uid` instead",
57+
Deprecated: "Use `folder_uid` instead",
58+
},
59+
"folder_uid": {
60+
Type: schema.TypeString,
61+
Computed: true,
62+
Description: "The UID of the folder where the Grafana dashboard is found.",
5763
},
5864
"is_starred": {
5965
Type: schema.TypeBool,
@@ -120,7 +126,9 @@ func dataSourceDashboardRead(ctx context.Context, d *schema.ResourceData, meta i
120126
d.Set("config_json", string(configJSONBytes))
121127
d.Set("version", int64(model["version"].(float64)))
122128
d.Set("title", model["title"].(string))
129+
// nolint:staticcheck
123130
d.Set("folder", dashboard.Meta.FolderID)
131+
d.Set("folder_uid", dashboard.Meta.FolderUID)
124132
d.Set("is_starred", dashboard.Meta.IsStarred)
125133
d.Set("slug", dashboard.Meta.Slug)
126134
d.Set("url", metaClient.GrafanaSubpath(dashboard.Meta.URL))

internal/resources/grafana/data_source_dashboards.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,15 @@ Datasource for retrieving all dashboards. Specify list of folder IDs to search i
2626
"folder_ids": {
2727
Type: schema.TypeList,
2828
Optional: true,
29-
Description: "Numerical IDs of Grafana folders containing dashboards. Specify to filter for dashboards by folder (eg. `[0]` for General folder), or leave blank to get all dashboards in all folders.",
29+
Description: "Deprecated, use `folder_uids` instead.",
3030
Elem: &schema.Schema{Type: schema.TypeInt},
31+
Deprecated: "Use `folder_uids` instead.",
32+
},
33+
"folder_uids": {
34+
Type: schema.TypeList,
35+
Optional: true,
36+
Description: "UIDs of Grafana folders containing dashboards. Specify to filter for dashboards by folder (eg. `[\"General\"]` for General folder), or leave blank to get all dashboards in all folders.",
37+
Elem: &schema.Schema{Type: schema.TypeString},
3138
},
3239
"limit": {
3340
Type: schema.TypeInt,
@@ -81,6 +88,11 @@ func dataSourceReadDashboards(ctx context.Context, d *schema.ResourceData, meta
8188
id.Write([]byte(fmt.Sprintf("%v", params.FolderIds)))
8289
}
8390

91+
if list, ok := d.GetOk("folder_uids"); ok {
92+
params.FolderUIDs = common.ListToStringSlice(list.([]interface{}))
93+
id.Write([]byte(fmt.Sprintf("%v", params.FolderUIDs)))
94+
}
95+
8496
if list, ok := d.GetOk("tags"); ok {
8597
params.Tag = common.ListToStringSlice(list.([]interface{}))
8698
id.Write([]byte(fmt.Sprintf("%v", params.Tag)))

internal/resources/grafana/data_source_dashboards_test.go

+10-10
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import (
77
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
88
)
99

10-
func TestAccDataSourceDashboardsAllAndByFolderID(t *testing.T) {
11-
testutils.CheckOSSTestsEnabled(t)
10+
func TestAccDataSourceDashboardsAllAndByFolderUID(t *testing.T) {
11+
testutils.CheckOSSTestsEnabled(t, ">=10.0.0")
1212

1313
// Do not use parallel tests here because it tests a listing datasource on the default org
1414
resource.Test(t, resource.TestCase{
@@ -22,15 +22,15 @@ func TestAccDataSourceDashboardsAllAndByFolderID(t *testing.T) {
2222
resource.TestCheckResourceAttr("data.grafana_dashboards.tags", "dashboards.0.title", "data_source_dashboards 1"),
2323
resource.TestCheckResourceAttr("data.grafana_dashboards.tags", "dashboards.0.folder_title", "test folder data_source_dashboards"),
2424

25-
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids", "dashboards.#", "1"),
26-
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids", "dashboards.0.uid", "data-source-dashboards-1"),
27-
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids", "dashboards.0.title", "data_source_dashboards 1"),
28-
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids", "dashboards.0.folder_title", "test folder data_source_dashboards"),
25+
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_uids", "dashboards.#", "1"),
26+
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_uids", "dashboards.0.uid", "data-source-dashboards-1"),
27+
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_uids", "dashboards.0.title", "data_source_dashboards 1"),
28+
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_uids", "dashboards.0.folder_title", "test folder data_source_dashboards"),
2929

30-
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids_tags", "dashboards.#", "1"),
31-
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids_tags", "dashboards.0.uid", "data-source-dashboards-1"),
32-
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids_tags", "dashboards.0.title", "data_source_dashboards 1"),
33-
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_ids_tags", "dashboards.0.folder_title", "test folder data_source_dashboards"),
30+
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_uids_tags", "dashboards.#", "1"),
31+
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_uids_tags", "dashboards.0.uid", "data-source-dashboards-1"),
32+
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_uids_tags", "dashboards.0.title", "data_source_dashboards 1"),
33+
resource.TestCheckResourceAttr("data.grafana_dashboards.folder_uids_tags", "dashboards.0.folder_title", "test folder data_source_dashboards"),
3434

3535
resource.TestCheckResourceAttr("data.grafana_dashboards.limit_one", "dashboards.#", "1"),
3636
resource.TestCheckResourceAttr("data.grafana_dashboards.limit_one", "dashboards.0.uid", "data-source-dashboards-1"),

internal/resources/grafana/resource_dashboard.go

+5
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,10 @@ func ReadDashboard(ctx context.Context, d *schema.ResourceData, meta interface{}
135135
// If the folder was originally set to a numeric ID, we read the folder ID
136136
// Othwerwise, we read the folder UID
137137
_, folderID := SplitOrgResourceID(d.Get("folder").(string))
138+
// nolint:staticcheck
138139
if common.IDRegexp.MatchString(folderID) && dashboard.Meta.FolderID > 0 {
140+
// TODO: Remove on next major release
141+
// nolint:staticcheck
139142
d.Set("folder", strconv.FormatInt(dashboard.Meta.FolderID, 10))
140143
} else {
141144
d.Set("folder", dashboard.Meta.FolderUID)
@@ -205,6 +208,8 @@ func makeDashboard(d *schema.ResourceData) (models.SaveDashboardCommand, error)
205208

206209
_, folderID := SplitOrgResourceID(d.Get("folder").(string))
207210
if folderInt, err := strconv.ParseInt(folderID, 10, 64); err == nil {
211+
// TODO: Remove on next major release
212+
// nolint:staticcheck
208213
dashboard.FolderID = folderInt
209214
} else {
210215
dashboard.FolderUID = folderID

internal/resources/grafana/resource_dashboard_test.go

+2-45
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"testing"
88

99
"github.com/grafana/grafana-openapi-client-go/models"
10-
"github.com/grafana/terraform-provider-grafana/v2/internal/common"
1110
"github.com/grafana/terraform-provider-grafana/v2/internal/resources/grafana"
1211
"github.com/grafana/terraform-provider-grafana/v2/internal/testutils"
1312

@@ -159,48 +158,6 @@ func TestAccDashboard_computed_config(t *testing.T) {
159158
})
160159
}
161160

162-
func TestAccDashboard_folder(t *testing.T) {
163-
testutils.CheckOSSTestsEnabled(t)
164-
165-
uid := acctest.RandString(10)
166-
167-
var dashboard models.DashboardFullWithMeta
168-
var folder models.Folder
169-
170-
resource.ParallelTest(t, resource.TestCase{
171-
ProtoV5ProviderFactories: testutils.ProtoV5ProviderFactories,
172-
CheckDestroy: resource.ComposeTestCheckFunc(
173-
dashboardCheckExists.destroyed(&dashboard, nil),
174-
folderCheckExists.destroyed(&folder, nil),
175-
),
176-
Steps: []resource.TestStep{
177-
{
178-
Config: testAccDashboardFolder(uid, "grafana_folder.test_folder1.id"),
179-
Check: resource.ComposeTestCheckFunc(
180-
dashboardCheckExists.exists("grafana_dashboard.test_folder", &dashboard),
181-
folderCheckExists.exists("grafana_folder.test_folder1", &folder),
182-
testAccDashboardCheckExistsInFolder(&dashboard, &folder),
183-
resource.TestCheckResourceAttr("grafana_dashboard.test_folder", "id", "1:"+uid), // <org id>:<uid>
184-
resource.TestCheckResourceAttr("grafana_dashboard.test_folder", "uid", uid),
185-
resource.TestMatchResourceAttr("grafana_dashboard.test_folder", "folder", common.IDRegexp),
186-
),
187-
},
188-
// Update folder
189-
{
190-
Config: testAccDashboardFolder(uid, "grafana_folder.test_folder2.id"),
191-
Check: resource.ComposeTestCheckFunc(
192-
dashboardCheckExists.exists("grafana_dashboard.test_folder", &dashboard),
193-
folderCheckExists.exists("grafana_folder.test_folder2", &folder),
194-
testAccDashboardCheckExistsInFolder(&dashboard, &folder),
195-
resource.TestCheckResourceAttr("grafana_dashboard.test_folder", "id", "1:"+uid), // <org id>:<uid>
196-
resource.TestCheckResourceAttr("grafana_dashboard.test_folder", "uid", uid),
197-
resource.TestMatchResourceAttr("grafana_dashboard.test_folder", "folder", common.IDRegexp),
198-
),
199-
},
200-
},
201-
})
202-
}
203-
204161
func TestAccDashboard_folder_uid(t *testing.T) {
205162
testutils.CheckOSSTestsEnabled(t, ">=8.0.0") // UID in folders were added in v8
206163

@@ -289,8 +246,8 @@ func TestAccDashboard_inOrg(t *testing.T) {
289246

290247
func testAccDashboardCheckExistsInFolder(dashboard *models.DashboardFullWithMeta, folder *models.Folder) resource.TestCheckFunc {
291248
return func(s *terraform.State) error {
292-
if dashboard.Meta.FolderID != folder.ID && folder.ID != 0 {
293-
return fmt.Errorf("dashboard.Folder(%d) does not match folder.ID(%d)", dashboard.Meta.FolderID, folder.ID)
249+
if dashboard.Meta.FolderUID != folder.UID && folder.UID != "" {
250+
return fmt.Errorf("dashboard.Folder(%s) does not match folder.ID(%s)", dashboard.Meta.FolderUID, folder.UID)
294251
}
295252
return nil
296253
}

internal/resources/grafana/resource_folder.go

+6-12
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@ func CreateFolder(ctx context.Context, d *schema.ResourceData, meta interface{})
9696
}
9797

9898
folder := resp.GetPayload()
99-
// TODO: Switch to UID
100-
d.SetId(MakeOrgResourceID(orgID, folder.ID))
99+
d.SetId(MakeOrgResourceID(orgID, folder.UID))
101100

102101
return ReadFolder(ctx, d, meta)
103102
}
@@ -131,7 +130,7 @@ func ReadFolder(ctx context.Context, d *schema.ResourceData, meta interface{}) d
131130
return err
132131
}
133132

134-
d.SetId(MakeOrgResourceID(orgID, folder.ID))
133+
d.SetId(MakeOrgResourceID(orgID, folder.UID))
135134
d.Set("org_id", strconv.FormatInt(orgID, 10))
136135
d.Set("title", folder.Title)
137136
d.Set("uid", folder.UID)
@@ -142,16 +141,11 @@ func ReadFolder(ctx context.Context, d *schema.ResourceData, meta interface{}) d
142141
}
143142

144143
func DeleteFolder(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
145-
client, _, idStr := OAPIClientFromExistingOrgResource(meta, d.Id())
146-
deleteParams := folders.NewDeleteFolderParams().WithFolderUID(d.Get("uid").(string))
144+
client, _, uid := OAPIClientFromExistingOrgResource(meta, d.Id())
145+
deleteParams := folders.NewDeleteFolderParams().WithFolderUID(uid)
147146
if d.Get("prevent_destroy_if_not_empty").(bool) {
148-
// Search for dashboards and fail if any are found
149-
folderID, err := strconv.ParseInt(idStr, 10, 64)
150-
if err != nil {
151-
return diag.Errorf("failed to parse folder ID: %s", err)
152-
}
153147
searchType := "dash-db"
154-
searchParams := search.NewSearchParams().WithFolderIds([]int64{folderID}).WithType(&searchType)
148+
searchParams := search.NewSearchParams().WithFolderUIDs([]string{uid}).WithType(&searchType)
155149
searchResp, err := client.Search.Search(searchParams)
156150
if err != nil {
157151
return diag.Errorf("failed to search for dashboards in folder: %s", err)
@@ -161,7 +155,7 @@ func DeleteFolder(ctx context.Context, d *schema.ResourceData, meta interface{})
161155
for _, dashboard := range searchResp.GetPayload() {
162156
dashboardNames = append(dashboardNames, dashboard.Title)
163157
}
164-
return diag.Errorf("folder %s is not empty and prevent_destroy_if_not_empty is set. It contains the following dashboards: %v", d.Get("uid").(string), dashboardNames)
158+
return diag.Errorf("folder %s is not empty and prevent_destroy_if_not_empty is set. It contains the following dashboards: %v", uid, dashboardNames)
165159
}
166160
} else {
167161
// If we're not preventing destroys, then we can force delete folders that have alert rules

0 commit comments

Comments
 (0)