Skip to content

grafana_oncall_user_notification_rule: Remove hardcoded user in test #1671

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading