Skip to content

Commit 4c86b85

Browse files
authored
Update resource_builtin_role_assignment resource to ignore assignments defined in the server side (#369)
When Grafana starts, by default it creates built-in role assignments for fixed roles. Additionally, users can create built-in role assignments via API. In these scenarios, when the resource is not properly imported, terraform will try to destroy what is in server side, but not in the configuration. The change ensures that this does not happen as it can cause unintended behaviour for users.
1 parent 9cb8815 commit 4c86b85

File tree

2 files changed

+269
-11
lines changed

2 files changed

+269
-11
lines changed

grafana/resource_builtin_role_assignment.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,20 @@ func ReadBuiltInRole(ctx context.Context, d *schema.ResourceData, meta interface
102102
return nil
103103
}
104104

105+
stateRoles, configRoles, err := collectRoles(d)
106+
if err != nil {
107+
return diag.FromErr(err)
108+
}
105109
roles := make([]interface{}, 0)
106110
for _, br := range brRole {
111+
// It is possible that in the server side there are roles assigned to the built-in role which were never in Terraform state,
112+
// and are not in the current configuration. The following check ensures that we only consider roles which are either in the state or in the config.
113+
// This prevents unintended behaviour, such as destroying the assignments which were never managed by Terraform.
114+
_, isInState := stateRoles[br.UID]
115+
_, isInConfig := configRoles[br.UID]
116+
if !isInState && !isInConfig {
117+
continue
118+
}
107119
rm := map[string]interface{}{
108120
"uid": br.UID,
109121
"global": br.Global,

grafana/resource_builtin_role_assignment_test.go

Lines changed: 257 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,45 @@ import (
1010
gapi "github.com/grafana/grafana-api-golang-client"
1111
)
1212

13+
const (
14+
roleUID1 = "reportviewer"
15+
roleUID2 = "createuser"
16+
17+
roleUID3 = "testroletwouid"
18+
roleUID4 = "testroleuid"
19+
20+
roleUID5 = "viewer_test"
21+
roleUID6 = "viewer_test_2"
22+
)
23+
1324
func TestAccBuiltInRoleAssignment(t *testing.T) {
1425
CheckEnterpriseTestsEnabled(t)
1526

16-
var br gapi.BuiltInRoleAssignment
27+
var assignments map[string][]*gapi.Role
1728

1829
resource.Test(t, resource.TestCase{
1930
PreCheck: func() { testAccPreCheck(t) },
2031
ProviderFactories: testAccProviderFactories,
21-
CheckDestroy: testAccBuiltInRoleAssignmentCheckDestroy(&br),
32+
CheckDestroy: testAccBuiltInRoleAssignmentCheckDestroy(&assignments, []string{roleUID3, roleUID4}, nil),
2233
Steps: []resource.TestStep{
2334
{
2435
Config: builtInRoleAssignmentConfig,
2536
Check: resource.ComposeTestCheckFunc(
26-
testAccBuiltInRoleAssignmentCheckExists("grafana_builtin_role_assignment.test_assignment"),
37+
testAccBuiltInRoleAssignmentCheckExists("grafana_builtin_role_assignment.test_assignment", &assignments),
2738
resource.TestCheckResourceAttr(
28-
"grafana_builtin_role_assignment.test_assignment", "builtin_role", "Viewer",
39+
"grafana_builtin_role_assignment.test_assignment", "builtin_role", "Editor",
2940
),
3041
resource.TestCheckResourceAttr(
3142
"grafana_builtin_role_assignment.test_assignment", "roles.#", "2",
3243
),
3344
resource.TestCheckResourceAttr(
34-
"grafana_builtin_role_assignment.test_assignment", "roles.0.uid", "testroletwouid",
45+
"grafana_builtin_role_assignment.test_assignment", "roles.0.uid", roleUID3,
3546
),
3647
resource.TestCheckResourceAttr(
3748
"grafana_builtin_role_assignment.test_assignment", "roles.0.global", "false",
3849
),
3950
resource.TestCheckResourceAttr(
40-
"grafana_builtin_role_assignment.test_assignment", "roles.1.uid", "testroleuid",
51+
"grafana_builtin_role_assignment.test_assignment", "roles.1.uid", roleUID4,
4152
),
4253
resource.TestCheckResourceAttr(
4354
"grafana_builtin_role_assignment.test_assignment", "roles.1.global", "true",
@@ -48,7 +59,136 @@ func TestAccBuiltInRoleAssignment(t *testing.T) {
4859
})
4960
}
5061

51-
func testAccBuiltInRoleAssignmentCheckExists(rn string) resource.TestCheckFunc {
62+
func TestAccBuiltInRoleAssignmentUpdate(t *testing.T) {
63+
CheckEnterpriseTestsEnabled(t)
64+
65+
var assignments map[string][]*gapi.Role
66+
67+
resource.Test(t, resource.TestCase{
68+
PreCheck: func() { testAccPreCheck(t) },
69+
ProviderFactories: testAccProviderFactories,
70+
CheckDestroy: testAccBuiltInRoleAssignmentCheckDestroy(&assignments, []string{roleUID5, roleUID6}, []string{roleUID1, roleUID2}),
71+
Steps: []resource.TestStep{
72+
{
73+
PreConfig: func() {
74+
err := prepareDefaultAssignments()
75+
if err != nil {
76+
t.Errorf("error when creating built-in role ssignments %s", err)
77+
}
78+
},
79+
Config: builtInRoleAssignmentUpdatePreConfig,
80+
Check: resource.ComposeTestCheckFunc(
81+
testAccBuiltInRoleAssignmentCheckExists("grafana_builtin_role_assignment.test_builtin_assignment", &assignments),
82+
resource.TestCheckResourceAttr(
83+
"grafana_builtin_role_assignment.test_builtin_assignment", "builtin_role", "Viewer",
84+
),
85+
resource.TestCheckResourceAttr(
86+
"grafana_builtin_role_assignment.test_builtin_assignment", "roles.#", "1",
87+
),
88+
resource.TestCheckResourceAttr(
89+
"grafana_builtin_role_assignment.test_builtin_assignment", "roles.0.uid", roleUID5,
90+
),
91+
resource.TestCheckResourceAttr(
92+
"grafana_builtin_role_assignment.test_builtin_assignment", "roles.0.global", "true",
93+
),
94+
),
95+
},
96+
{
97+
Config: builtInRoleAssignmentUpdateConfig,
98+
Check: resource.ComposeTestCheckFunc(
99+
testAccBuiltInRoleAssignmentCheckExists("grafana_builtin_role_assignment.test_builtin_assignment", &assignments),
100+
resource.TestCheckResourceAttr(
101+
"grafana_builtin_role_assignment.test_builtin_assignment", "builtin_role", "Viewer",
102+
),
103+
resource.TestCheckResourceAttr(
104+
"grafana_builtin_role_assignment.test_builtin_assignment", "roles.#", "2",
105+
),
106+
resource.TestCheckResourceAttr(
107+
"grafana_builtin_role_assignment.test_builtin_assignment", "roles.0.uid", roleUID5,
108+
),
109+
resource.TestCheckResourceAttr(
110+
"grafana_builtin_role_assignment.test_builtin_assignment", "roles.0.global", "true",
111+
),
112+
resource.TestCheckResourceAttr(
113+
"grafana_builtin_role_assignment.test_builtin_assignment", "roles.1.uid", roleUID6,
114+
),
115+
resource.TestCheckResourceAttr(
116+
"grafana_builtin_role_assignment.test_builtin_assignment", "roles.1.global", "true",
117+
),
118+
testAccBuiltInRoleAssignmentWereNotDestroyed("Viewer", roleUID1, roleUID2),
119+
),
120+
},
121+
},
122+
})
123+
}
124+
125+
func prepareDefaultAssignments() error {
126+
client := testAccProvider.Meta().(*client).gapi
127+
r1 := gapi.Role{
128+
UID: roleUID1,
129+
Version: 1,
130+
Name: "Test Report Viewer",
131+
Global: true,
132+
Permissions: []gapi.Permission{
133+
{
134+
Action: "reports:read",
135+
Scope: "reports:*",
136+
},
137+
{
138+
Action: "users:create",
139+
},
140+
},
141+
}
142+
r2 := gapi.Role{
143+
UID: roleUID2,
144+
Version: 1,
145+
Name: "Test Create User",
146+
Global: true,
147+
Permissions: []gapi.Permission{
148+
{
149+
Action: "users:create",
150+
},
151+
},
152+
}
153+
role1, err := client.NewRole(r1)
154+
if err != nil {
155+
return fmt.Errorf("error creating role: %w", err)
156+
}
157+
role2, err := client.NewRole(r2)
158+
if err != nil {
159+
return fmt.Errorf("error creating role: %w", err)
160+
}
161+
_, err = client.NewBuiltInRoleAssignment(gapi.BuiltInRoleAssignment{
162+
BuiltinRole: "Viewer",
163+
RoleUID: role1.UID,
164+
Global: false,
165+
})
166+
if err != nil {
167+
return fmt.Errorf("error creating built-in role assigntment: %w", err)
168+
}
169+
_, err = client.NewBuiltInRoleAssignment(gapi.BuiltInRoleAssignment{
170+
BuiltinRole: "Viewer",
171+
RoleUID: role2.UID,
172+
Global: false,
173+
})
174+
if err != nil {
175+
return fmt.Errorf("error creating built-in role assigntment: %w", err)
176+
}
177+
return nil
178+
}
179+
180+
func testAccBuiltInRoleAssignmentWereNotDestroyed(brName string, roleUIDs ...string) resource.TestCheckFunc {
181+
return func(s *terraform.State) error {
182+
client := testAccProvider.Meta().(*client).gapi
183+
assignments, err := client.GetBuiltInRoleAssignments()
184+
if err != nil || assignments[brName] == nil {
185+
return fmt.Errorf("built-in assignments were destroyed, but expected to exist: %v", err)
186+
}
187+
return checkAssignmentsExists(assignments[brName], roleUIDs...)
188+
}
189+
}
190+
191+
func testAccBuiltInRoleAssignmentCheckExists(rn string, brAssignments *map[string][]*gapi.Role) resource.TestCheckFunc {
52192
return func(s *terraform.State) error {
53193
rs, ok := s.RootModule().Resources[rn]
54194
if !ok {
@@ -65,21 +205,70 @@ func testAccBuiltInRoleAssignmentCheckExists(rn string) resource.TestCheckFunc {
65205
return fmt.Errorf("error getting built-in role assignments: %s", err)
66206
}
67207

208+
*brAssignments = map[string][]*gapi.Role{
209+
rs.Primary.ID: assignments[rs.Primary.ID],
210+
}
68211
return nil
69212
}
70213
}
71214

72-
func testAccBuiltInRoleAssignmentCheckDestroy(br *gapi.BuiltInRoleAssignment) resource.TestCheckFunc {
215+
func testAccBuiltInRoleAssignmentCheckDestroy(brAssignments *map[string][]*gapi.Role, destroyedUIDs []string, preservedRoleUIDs []string) resource.TestCheckFunc {
73216
return func(s *terraform.State) error {
74217
client := testAccProvider.Meta().(*client).gapi
75218
bra, err := client.GetBuiltInRoleAssignments()
76-
if err == nil && bra[br.BuiltinRole] != nil {
77-
return fmt.Errorf("assignment still exists")
219+
if err != nil {
220+
return fmt.Errorf("error getting built-in role assignments: %s", err)
78221
}
222+
223+
if preservedRoleUIDs != nil {
224+
for br := range *brAssignments {
225+
err := checkAssignmentsExists(bra[br], preservedRoleUIDs...)
226+
if err != nil {
227+
return fmt.Errorf("assignments were entirely destroyed, but expected to have roles with UID %v assigned to %s built-in role", preservedRoleUIDs, br)
228+
}
229+
}
230+
}
231+
232+
if destroyedUIDs != nil {
233+
for br := range *brAssignments {
234+
err := checkAssignmentsDoNotExist(bra[br], destroyedUIDs...)
235+
if err != nil {
236+
return fmt.Errorf("assignments were supped to destroyed, but have roles with UID %v assigned to %s built-in role", destroyedUIDs, br)
237+
}
238+
}
239+
}
240+
79241
return nil
80242
}
81243
}
82244

245+
func checkAssignmentsDoNotExist(roles []*gapi.Role, roleUIDs ...string) error {
246+
for _, uid := range roleUIDs {
247+
if contains(roles, uid) {
248+
return fmt.Errorf("built-in assignments still exists for a role UID: %s", uid)
249+
}
250+
}
251+
return nil
252+
}
253+
254+
func checkAssignmentsExists(roles []*gapi.Role, roleUIDs ...string) error {
255+
for _, uid := range roleUIDs {
256+
if !contains(roles, uid) {
257+
return fmt.Errorf("built-in assignments do not exist for a role UID: %s", uid)
258+
}
259+
}
260+
return nil
261+
}
262+
263+
func contains(roles []*gapi.Role, uid string) bool {
264+
for _, r := range roles {
265+
if r.UID == uid {
266+
return true
267+
}
268+
}
269+
return false
270+
}
271+
83272
const builtInRoleAssignmentConfig = `
84273
resource "grafana_role" "test_role" {
85274
name = "test_role"
@@ -112,7 +301,7 @@ resource "grafana_role" "test_role_two" {
112301
}
113302
114303
resource "grafana_builtin_role_assignment" "test_assignment" {
115-
builtin_role = "Viewer"
304+
builtin_role = "Editor"
116305
roles {
117306
uid = grafana_role.test_role.id
118307
global = true
@@ -123,3 +312,60 @@ resource "grafana_builtin_role_assignment" "test_assignment" {
123312
}
124313
}
125314
`
315+
316+
const builtInRoleAssignmentUpdatePreConfig = `
317+
resource "grafana_role" "viewer_test" {
318+
name = "viewer_test"
319+
description = "test desc"
320+
version = 1
321+
uid = "viewer_test"
322+
global = true
323+
permissions {
324+
action = "users:create"
325+
}
326+
}
327+
328+
resource "grafana_builtin_role_assignment" "test_builtin_assignment" {
329+
builtin_role = "Viewer"
330+
roles {
331+
uid = grafana_role.viewer_test.id
332+
global = true
333+
}
334+
}
335+
`
336+
337+
const builtInRoleAssignmentUpdateConfig = `
338+
resource "grafana_role" "viewer_test" {
339+
name = "viewer_test"
340+
description = "test desc"
341+
version = 1
342+
uid = "viewer_test"
343+
global = true
344+
permissions {
345+
action = "users:create"
346+
}
347+
}
348+
349+
resource "grafana_role" "viewer_test_2" {
350+
name = "viewer_test_2"
351+
description = "test desc"
352+
version = 1
353+
uid = "viewer_test_2"
354+
global = true
355+
permissions {
356+
action = "users:create"
357+
}
358+
}
359+
360+
resource "grafana_builtin_role_assignment" "test_builtin_assignment" {
361+
builtin_role = "Viewer"
362+
roles {
363+
uid = grafana_role.viewer_test.id
364+
global = true
365+
}
366+
roles {
367+
uid = grafana_role.viewer_test_2.id
368+
global = true
369+
}
370+
}
371+
`

0 commit comments

Comments
 (0)