Skip to content

Commit

Permalink
grafana_oncall_user_notification_rule: Remove hardcoded user in test (
Browse files Browse the repository at this point in the history
#1671)

* `grafana_oncall_user_notification_rule`: Remove hardcoded user in test
To do so, I added a new datasource that lists the oncall users. We can use that datasource to grab the first user and use them for tests
I also converted the current `_user` resource to the new plugin framework while I was at it

* Generate docs again
  • Loading branch information
julienduchesne authored Jul 9, 2024
1 parent 6beb516 commit 07d2b91
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 53 deletions.
2 changes: 1 addition & 1 deletion docs/data-sources/oncall_user.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ data "grafana_oncall_user" "alex" {
### Read-Only

- `email` (String) The email of the user.
- `id` (String) The ID of this resource.
- `id` (String) The ID of the user.
- `role` (String) The role of the user.
31 changes: 31 additions & 0 deletions docs/data-sources/oncall_users.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "grafana_oncall_users Data Source - terraform-provider-grafana"
subcategory: "OnCall"
description: |-
HTTP API https://grafana.com/docs/oncall/latest/oncall-api-reference/users/
---

# grafana_oncall_users (Data Source)

* [HTTP API](https://grafana.com/docs/oncall/latest/oncall-api-reference/users/)



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

### Read-Only

- `id` (String) The ID of this resource.
- `users` (List of Object) (see [below for nested schema](#nestedatt--users))

<a id="nestedatt--users"></a>
### Nested Schema for `users`

Read-Only:

- `email` (String)
- `id` (String)
- `role` (String)
- `username` (String)
78 changes: 47 additions & 31 deletions internal/resources/oncall/data_source_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,80 @@ package oncall

import (
"context"
"fmt"

onCallAPI "github.com/grafana/amixr-api-go-client"
"github.com/grafana/terraform-provider-grafana/v3/internal/common"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)

var dataSourceUserName = "grafana_oncall_user"

func dataSourceUser() *common.DataSource {
schema := &schema.Resource{
Description: `
* [HTTP API](https://grafana.com/docs/oncall/latest/oncall-api-reference/users/)
`,
ReadContext: withClient[schema.ReadContextFunc](dataSourceUserRead),
Schema: map[string]*schema.Schema{
"username": {
Type: schema.TypeString,
return common.NewDataSource(common.CategoryOnCall, dataSourceUserName, &userDataSource{})
}

type userDataSource struct {
basePluginFrameworkDataSource
}

func (r *userDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = dataSourceUserName
}

func (r *userDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "* [HTTP API](https://grafana.com/docs/oncall/latest/oncall-api-reference/users/)",
Attributes: map[string]schema.Attribute{
"username": schema.StringAttribute{
Required: true,
Description: "The username of the user.",
},
"email": {
Type: schema.TypeString,
"id": schema.StringAttribute{
Computed: true,
Description: "The ID of the user.",
},
"email": schema.StringAttribute{
Computed: true,
Description: "The email of the user.",
},
"role": {
Type: schema.TypeString,
"role": schema.StringAttribute{
Computed: true,
Description: "The role of the user.",
},
},
}
return common.NewLegacySDKDataSource(common.CategoryOnCall, "grafana_oncall_user", schema)
}

func dataSourceUserRead(ctx context.Context, d *schema.ResourceData, client *onCallAPI.Client) diag.Diagnostics {
options := &onCallAPI.ListUserOptions{}
usernameData := d.Get("username").(string)

options.Username = usernameData
func (r *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
// Read Terraform state data into the model
var data userDataSourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

usersResponse, _, err := client.Users.ListUsers(options)
options := &onCallAPI.ListUserOptions{
Username: data.Username.ValueString(),
}
usersResponse, _, err := r.client.Users.ListUsers(options)
if err != nil {
return diag.FromErr(err)
resp.Diagnostics.AddError("Failed to list users", err.Error())
return
}

if len(usersResponse.Users) == 0 {
return diag.Errorf("couldn't find a user matching: %s", options.Username)
resp.Diagnostics.AddError("user not found", fmt.Sprintf("couldn't find a user matching: %s", options.Username))
return
} else if len(usersResponse.Users) != 1 {
return diag.Errorf("more than one user found matching: %s", options.Username)
resp.Diagnostics.AddError("more than one user found", fmt.Sprintf("more than one user found matching: %s", options.Username))
return
}

user := usersResponse.Users[0]
data.ID = basetypes.NewStringValue(user.ID)
data.Email = basetypes.NewStringValue(user.Email)
data.Role = basetypes.NewStringValue(user.Role)

d.Set("email", user.Email)
d.Set("username", user.Username)
d.Set("role", user.Role)

d.SetId(user.ID)

return nil
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, data)...)
}
101 changes: 101 additions & 0 deletions internal/resources/oncall/data_source_users.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package oncall

import (
"context"

onCallAPI "github.com/grafana/amixr-api-go-client"
"github.com/grafana/terraform-provider-grafana/v3/internal/common"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)

var dataSourceUsersName = "grafana_oncall_users"

func dataSourceUsers() *common.DataSource {
return common.NewDataSource(common.CategoryOnCall, dataSourceUsersName, &usersDataSource{})
}

type usersDataSource struct {
basePluginFrameworkDataSource
}

func (r *usersDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = dataSourceUsersName
}

func (r *usersDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "* [HTTP API](https://grafana.com/docs/oncall/latest/oncall-api-reference/users/)",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
"users": schema.ListAttribute{
ElementType: types.ObjectType{
AttrTypes: map[string]attr.Type{
"id": types.StringType,
"username": types.StringType,
"email": types.StringType,
"role": types.StringType,
},
},
Computed: true,
},
},
}
}

type userDataSourceModel struct {
ID basetypes.StringValue `tfsdk:"id"`
Username basetypes.StringValue `tfsdk:"username"`
Email basetypes.StringValue `tfsdk:"email"`
Role basetypes.StringValue `tfsdk:"role"`
}

type usersDataSourceModel struct {
ID basetypes.StringValue `tfsdk:"id"`
Users []userDataSourceModel `tfsdk:"users"`
}

func (r *usersDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
// Read Terraform state data into the model
var data usersDataSourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

allUsers := []userDataSourceModel{}
page := 1
for {
options := &onCallAPI.ListUserOptions{
ListOptions: onCallAPI.ListOptions{
Page: page,
},
}
usersResponse, _, err := r.client.Users.ListUsers(options)
if err != nil {
resp.Diagnostics.AddError("Failed to list users", err.Error())
return
}

for _, user := range usersResponse.Users {
allUsers = append(allUsers, userDataSourceModel{
ID: basetypes.NewStringValue(user.ID),
Username: basetypes.NewStringValue(user.Username),
Email: basetypes.NewStringValue(user.Email),
Role: basetypes.NewStringValue(user.Role),
})
}

if usersResponse.PaginatedResponse.Next == nil {
break
}
}

data.ID = basetypes.NewStringValue("oncall_users") // singleton
data.Users = allUsers

// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, data)...)
}
33 changes: 13 additions & 20 deletions internal/resources/oncall/resource_user_notification_rule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,8 @@ func TestAccUserNotificationRule_basic(t *testing.T) {
testutils.CheckCloudInstanceTestsEnabled(t)

var (
// We need an actual user to test the resource
// This is a user created from my personal email, but it can be replaced by any existing user
userID = "joeyorlando"
resourceName = "grafana_oncall_user_notification_rule.test-acc-user_notification_rule"

testSteps []resource.TestStep
testSteps []resource.TestStep

ruleTypes = []string{
"wait",
Expand All @@ -41,18 +37,17 @@ func TestAccUserNotificationRule_basic(t *testing.T) {
config string
testCheckFuncFunctions = []resource.TestCheckFunc{
testAccCheckOnCallUserNotificationRuleResourceExists(resourceName),
// resource.TestCheckResourceAttr(resourceName, "user_id", userID),
resource.TestCheckResourceAttr(resourceName, "position", "1"),
resource.TestCheckResourceAttr(resourceName, "type", ruleType),
resource.TestCheckResourceAttr(resourceName, "important", fmt.Sprintf("%t", important)),
}
)

if ruleType == "wait" {
config = testAccOnCallUserNotificationRuleWait(userID, important)
config = testAccOnCallUserNotificationRuleWait(important)
testCheckFuncFunctions = append(testCheckFuncFunctions, resource.TestCheckResourceAttr(resourceName, "duration", "300"))
} else {
config = testAccOnCallUserNotificationRuleNotificationStep(ruleType, userID, important)
config = testAccOnCallUserNotificationRuleNotificationStep(ruleType, important)
}

testSteps = append(testSteps, resource.TestStep{
Expand Down Expand Up @@ -89,35 +84,33 @@ func testAccCheckOnCallUserNotificationRuleResourceDestroy(s *terraform.State) e
return nil
}

func testAccOnCallUserNotificationRuleWait(userName string, important bool) string {
func testAccOnCallUserNotificationRuleWait(important bool) string {
return fmt.Sprintf(`
data "grafana_oncall_user" "user" {
username = "%s"
}
# Grab the first user from the full list of users
data "grafana_oncall_users" "all" {}
resource "grafana_oncall_user_notification_rule" "test-acc-user_notification_rule" {
user_id = data.grafana_oncall_user.user.id
user_id = data.grafana_oncall_users.all.users[0].id
type = "wait"
position = 1
duration = 300
important = %t
}
`, userName, important)
`, important)
}

func testAccOnCallUserNotificationRuleNotificationStep(ruleType, userName string, important bool) string {
func testAccOnCallUserNotificationRuleNotificationStep(ruleType string, important bool) string {
return fmt.Sprintf(`
data "grafana_oncall_user" "user" {
username = "%s"
}
# Grab the first user from the full list of users
data "grafana_oncall_users" "all" {}
resource "grafana_oncall_user_notification_rule" "test-acc-user_notification_rule" {
user_id = data.grafana_oncall_user.user.id
user_id = data.grafana_oncall_users.all.users[0].id
type = "%s"
position = 1
important = %t
}
`, userName, ruleType, important)
`, ruleType, important)
}

func testAccCheckOnCallUserNotificationRuleResourceExists(name string) resource.TestCheckFunc {
Expand Down
28 changes: 27 additions & 1 deletion internal/resources/oncall/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

onCallAPI "github.com/grafana/amixr-api-go-client"
"github.com/grafana/terraform-provider-grafana/v3/internal/common"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand Down Expand Up @@ -38,6 +39,30 @@ func (r *basePluginFrameworkResource) Configure(ctx context.Context, req resourc
r.client = client.OnCallClient
}

type basePluginFrameworkDataSource struct {
client *onCallAPI.Client
}

func (r *basePluginFrameworkDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
// Configure is called multiple times (sometimes when ProviderData is not yet available), we only want to configure once
if req.ProviderData == nil || r.client != nil {
return
}

client, ok := req.ProviderData.(*common.Client)

if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *common.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

r.client = client.OnCallClient
}

type crudWithClientFunc func(ctx context.Context, d *schema.ResourceData, client *onCallAPI.Client) diag.Diagnostics

func withClient[T schema.CreateContextFunc | schema.UpdateContextFunc | schema.ReadContextFunc | schema.DeleteContextFunc](f crudWithClientFunc) T {
Expand All @@ -51,14 +76,15 @@ func withClient[T schema.CreateContextFunc | schema.UpdateContextFunc | schema.R
}

var DataSources = []*common.DataSource{
dataSourceUser(),
dataSourceEscalationChain(),
dataSourceSchedule(),
dataSourceSlackChannel(),
dataSourceOutgoingWebhook(),
dataSourceUserGroup(),
dataSourceTeam(),
dataSourceIntegration(),
dataSourceUser(),
dataSourceUsers(),
}

var Resources = []*common.Resource{
Expand Down

0 comments on commit 07d2b91

Please sign in to comment.