Skip to content

Commit

Permalink
RBAC: resource for RBAC role assignments (#647)
Browse files Browse the repository at this point in the history
* role assignment resource

* clean up

* remove possibility to do global assignments for now

* remove possibility to do global assignments for now

* Fix variable reference

* Append `.vscode` and `vendor` to `.gitignore`

* Fix typo

* tests attempt 1

* set id correctly

* add tests

* change id

* Update grafana/resource_role_assignment.go

Co-authored-by: Julien Duchesne <[email protected]>

* Update grafana/resource_role_assignment.go

Co-authored-by: Vardan Torosyan <[email protected]>

* fix IDs

* update API client dependency

* Generate docs

* Read after update

* try to update api client dependency

* improve the test

* attempt to refactor annotation test

* uncomment

Co-authored-by: linoman <[email protected]>
Co-authored-by: Julien Duchesne <[email protected]>
Co-authored-by: Vardan Torosyan <[email protected]>
  • Loading branch information
4 people committed Sep 23, 2022
1 parent 42573f3 commit f487ec3
Show file tree
Hide file tree
Showing 9 changed files with 301 additions and 16 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ website/vendor
!command/test-fixtures/**/*.tfstate
!command/test-fixtures/**/.terraform/

terraform-provider-grafana
terraform-provider-grafana

.vscode
vendor/
36 changes: 36 additions & 0 deletions docs/resources/role_assignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "grafana_role_assignment Resource - terraform-provider-grafana"
subcategory: "Grafana Enteprise"
description: |-
Note: This resource is available only with Grafana Enterprise 9.2+.
* Official documentation https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/
* HTTP API https://grafana.com/docs/grafana/latest/developers/http_api/access_control/
---

# grafana_role_assignment (Resource)

**Note:** This resource is available only with Grafana Enterprise 9.2+.
* [Official documentation](https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/)
* [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/access_control/)



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `role_uid` (String) Grafana RBAC role UID.

### Optional

- `service_accounts` (Set of Number) IDs of service accounts that the role should be assigned to.
- `teams` (Set of Number) IDs of teams that the role should be assigned to.
- `users` (Set of Number) IDs of users that the role should be assigned to.

### Read-Only

- `id` (String) The ID of this resource.


2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.18
require (
github.com/Masterminds/semver/v3 v3.1.1
github.com/grafana/amixr-api-go-client v0.0.5
github.com/grafana/grafana-api-golang-client v0.11.2
github.com/grafana/grafana-api-golang-client v0.12.0
github.com/grafana/machine-learning-go-client v0.1.1
github.com/grafana/synthetic-monitoring-agent v0.9.4
github.com/grafana/synthetic-monitoring-api-go-client v0.6.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/amixr-api-go-client v0.0.5 h1:jqmljnd5FozuOsCNuyhZVpooxmj0BW9MmeLA7PaLK6U=
github.com/grafana/amixr-api-go-client v0.0.5/go.mod h1:N6x26XUrM5zGtK5zL5vNJnAn2JFMxLFPPLTw/6pDkFE=
github.com/grafana/grafana-api-golang-client v0.11.2 h1:wzT/TfaPWgOA0xHJ7fdIgmcnyXJLgMgbU62zbbUmIkI=
github.com/grafana/grafana-api-golang-client v0.11.2/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E=
github.com/grafana/grafana-api-golang-client v0.12.0 h1:EqoBKAeDeWQdb9PXrTc7vvVMzJTLTFYR+MbbEJhua+o=
github.com/grafana/grafana-api-golang-client v0.12.0/go.mod h1:24W29gPe9yl0/3A9X624TPkAOR8DpHno490cPwnkv8E=
github.com/grafana/machine-learning-go-client v0.1.1 h1:Gw6cX8xAd6IVF2LApkXOIdBK8Gzz07B3jQPukecw7fc=
github.com/grafana/machine-learning-go-client v0.1.1/go.mod h1:QFfZz8NkqVF8++skjkKQXJEZfpCYd8S0yTWJUpsLLTA=
github.com/grafana/synthetic-monitoring-agent v0.9.4 h1:Enx5s6gFbc/RAzL5KDX/00catAlbcY7/1IFPBe5lo/c=
Expand Down
1 change: 1 addition & 0 deletions grafana/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func Provider(version string) func() *schema.Provider {
"grafana_playlist": ResourcePlaylist(),
"grafana_report": ResourceReport(),
"grafana_role": ResourceRole(),
"grafana_role_assignment": ResourceRoleAssignment(),
"grafana_rule_group": ResourceRuleGroup(),
"grafana_team": ResourceTeam(),
"grafana_team_preferences": ResourceTeamPreferences(),
Expand Down
17 changes: 5 additions & 12 deletions grafana/resource_annotation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,31 +164,24 @@ resource "grafana_annotation" "test_with_dashboard_id" {
}

func testAnnotationConfigWithPanelID(text string) string {
panelID := 123

return fmt.Sprintf(`
resource "grafana_dashboard" "test_with_panel_id" {
config_json = <<EOD
{
"title": "%s",
"panels": [{
"name": "%s",
"id": 123
"id": %d
}]
}
EOD
}
data "grafana_dashboard" "test_with_panel_id" {
dashboard_id = grafana_dashboard.test_with_panel_id.dashboard_id
}
locals {
dashboard_json = jsondecode(data.grafana_dashboard.test_with_panel_id.config_json)
panel_id = local.dashboard_json.panels[0].id
}
resource "grafana_annotation" "test_with_panel_id" {
text = "%s"
panel_id = local.panel_id
panel_id = %d
}
`, text, text, text)
`, text, text, panelID, text, panelID)
}
145 changes: 145 additions & 0 deletions grafana/resource_role_assignment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package grafana

import (
"context"
"fmt"

gapi "github.com/grafana/grafana-api-golang-client"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func ResourceRoleAssignment() *schema.Resource {
return &schema.Resource{
Description: `
**Note:** This resource is available only with Grafana Enterprise 9.2+.
* [Official documentation](https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/)
* [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/access_control/)
`,
CreateContext: UpdateRoleAssignments,
UpdateContext: UpdateRoleAssignments,
ReadContext: ReadRoleAssignments,
DeleteContext: UpdateRoleAssignments,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
"role_uid": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Grafana RBAC role UID.",
},
"users": {
Type: schema.TypeSet,
Optional: true,
ForceNew: false,
Description: "IDs of users that the role should be assigned to.",
Elem: &schema.Schema{
Type: schema.TypeInt,
},
},
"teams": {
Type: schema.TypeSet,
Optional: true,
ForceNew: false,
Description: "IDs of teams that the role should be assigned to.",
Elem: &schema.Schema{
Type: schema.TypeInt,
},
},
"service_accounts": {
Type: schema.TypeSet,
Optional: true,
ForceNew: false,
Description: "IDs of service accounts that the role should be assigned to.",
Elem: &schema.Schema{
Type: schema.TypeInt,
},
},
},
}
}

func ReadRoleAssignments(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*client).gapi
uid := d.Id()
assignments, err := client.GetRoleAssignments(uid)
if err != nil {
return diag.FromErr(err)
}

if err := setRoleAssignments(assignments, d); err != nil {
return diag.FromErr(err)
}
return nil
}

func UpdateRoleAssignments(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
if !d.IsNewResource() && !d.HasChange("users") && !d.HasChange("teams") && !d.HasChange("service_accounts") {
return nil
}

client := meta.(*client).gapi

uid := d.Get("role_uid").(string)
users, err := collectRoleAssignmentsToFn(d.Get("users"))
if err != nil {
return diag.Errorf("invalid user IDs specified %v", err)
}
teams, err := collectRoleAssignmentsToFn(d.Get("teams"))
if err != nil {
return diag.Errorf("invalid team IDs specified %v", err)
}
serviceAccounts, err := collectRoleAssignmentsToFn(d.Get("service_accounts"))
if err != nil {
return diag.Errorf("invalid service account IDs specified %v", err)
}

ra := &gapi.RoleAssignments{
RoleUID: uid,
Users: users,
Teams: teams,
ServiceAccounts: serviceAccounts,
}
assignments, err := client.UpdateRoleAssignments(ra)
if err != nil {
return diag.FromErr(err)
}

if err := setRoleAssignments(assignments, d); err != nil {
return diag.FromErr(err)
}

return ReadRoleAssignments(ctx, d, meta)
}

func setRoleAssignments(assignments *gapi.RoleAssignments, d *schema.ResourceData) error {
d.SetId(assignments.RoleUID)
if err := d.Set("role_uid", assignments.RoleUID); err != nil {
return err
}
if err := d.Set("users", assignments.Users); err != nil {
return err
}
if err := d.Set("teams", assignments.Teams); err != nil {
return err
}
if err := d.Set("service_accounts", assignments.ServiceAccounts); err != nil {
return err
}

return nil
}

func collectRoleAssignmentsToFn(r interface{}) ([]int, error) {
output := make([]int, 0)
for _, rID := range r.(*schema.Set).List() {
id, ok := rID.(int)
if !ok {
return []int{}, fmt.Errorf("%s is not a valid id", rID)
}
output = append(output, id)
}
return output, nil
}
106 changes: 106 additions & 0 deletions grafana/resource_role_assignment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package grafana

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"

gapi "github.com/grafana/grafana-api-golang-client"
)

func TestRoleAssignments(t *testing.T) {
CheckEnterpriseTestsEnabled(t)
var roleAssignment gapi.RoleAssignments

resource.Test(t, resource.TestCase{
ProviderFactories: testAccProviderFactories,
CheckDestroy: testRoleAssignmentCheckDestroy(&roleAssignment),
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(roleAssignmentConfig, roleUID),
Check: resource.ComposeTestCheckFunc(
testRoleAssignmentCheckExists("grafana_role_assignment.test", &roleAssignment),
resource.TestCheckResourceAttr(
"grafana_role_assignment.test", "role_uid", roleUID,
),
resource.TestCheckResourceAttr(
"grafana_role_assignment.test", "users.#", "2",
),
resource.TestCheckResourceAttr(
"grafana_role_assignment.test", "service_accounts.#", "0",
),
resource.TestCheckResourceAttr(
"grafana_role_assignment.test", "teams.#", "1",
),
),
},
{
Config: fmt.Sprintf(roleAssignmentConfig, roleUID),
Destroy: true,
},
},
})
}

func testRoleAssignmentCheckExists(rn string, ra *gapi.RoleAssignments) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[rn]
if !ok {
return fmt.Errorf("resource not found: %s", rn)
}

uid, ok := rs.Primary.Attributes["role_uid"]
if !ok {
return fmt.Errorf("resource UID not set")
}

client := testAccProvider.Meta().(*client).gapi
role, err := client.GetRoleAssignments(uid)
if err != nil {
return fmt.Errorf("error getting role assignments: %s", err)
}

*ra = *role

return nil
}
}

func testRoleAssignmentCheckDestroy(ra *gapi.RoleAssignments) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*client).gapi
role, err := client.GetRoleAssignments(ra.RoleUID)
if err == nil && (len(role.Users) > 0 || len(role.ServiceAccounts) > 0 || len(role.Teams) > 0) {
return fmt.Errorf("role is still assigned")
}
return nil
}
}

var roleUID = "terraform_test_role"

var roleAssignmentConfig = `
resource "grafana_team" "test_team" {
name = "terraform_test_team"
}
resource "grafana_user" "test_user" {
email = "[email protected]"
login = "[email protected]"
password = "12345"
}
resource "grafana_user" "test_user2" {
email = "[email protected]"
login = "[email protected]"
password = "12345"
}
resource "grafana_role_assignment" "test" {
role_uid = "%s"
users = [grafana_user.test_user.id, grafana_user.test_user2.id]
teams = [grafana_team.test_team.id]
}
`
1 change: 1 addition & 0 deletions tools/subcategories.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"resources/folder_permission": "Grafana Enteprise",
"resources/report": "Grafana Enteprise",
"resources/role": "Grafana Enteprise",
"resources/role_assignment": "Grafana Enteprise",
"resources/team_external_group": "Grafana Enteprise",
"resources/cloud_api_key": "Cloud",
"resources/cloud_plugin_installation": "Cloud",
Expand Down

0 comments on commit f487ec3

Please sign in to comment.