From 781157709dbf0ba97e6a1562ea03acf03d0dd916 Mon Sep 17 00:00:00 2001 From: Gary James Date: Mon, 25 Nov 2024 19:31:03 +0000 Subject: [PATCH] feature - Add data source for all Tableau users --- docs/data-sources/datasources.md | 5 + docs/data-sources/groups.md | 5 + docs/data-sources/users.md | 36 ++++++ .../tableau_datasources/data-source.tf | 2 + .../tableau_groups/data-source.tf | 2 + .../data-sources/tableau_users/data-source.tf | 2 + tableau/provider.go | 1 + tableau/user.go | 61 ++++++++++ tableau/users_data_source.go | 115 ++++++++++++++++++ tableau/users_data_source_test.go | 24 ++++ 10 files changed, 253 insertions(+) create mode 100644 docs/data-sources/users.md create mode 100644 examples/data-sources/tableau_datasources/data-source.tf create mode 100644 examples/data-sources/tableau_groups/data-source.tf create mode 100644 examples/data-sources/tableau_users/data-source.tf create mode 100644 tableau/users_data_source.go create mode 100644 tableau/users_data_source_test.go diff --git a/docs/data-sources/datasources.md b/docs/data-sources/datasources.md index d5887aa..23ca101 100644 --- a/docs/data-sources/datasources.md +++ b/docs/data-sources/datasources.md @@ -10,7 +10,12 @@ description: |- Retrieve datasource details as a list of datasources available to read +## Example Usage +```terraform +data "tableau_groups" "example" { +} +``` ## Schema diff --git a/docs/data-sources/groups.md b/docs/data-sources/groups.md index d820e82..54bd7ff 100644 --- a/docs/data-sources/groups.md +++ b/docs/data-sources/groups.md @@ -10,7 +10,12 @@ description: |- Retrieve groups details +## Example Usage +```terraform +data "tableau_datasources" "example" { +} +``` ## Schema diff --git a/docs/data-sources/users.md b/docs/data-sources/users.md new file mode 100644 index 0000000..6aee46f --- /dev/null +++ b/docs/data-sources/users.md @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "tableau_users Data Source - terraform-provider-tableau" +subcategory: "" +description: |- + Retrieve user details as a list of users available to read +--- + +# tableau_users (Data Source) + +Retrieve user details as a list of users available to read + +## Example Usage + +```terraform +data "tableau_users" "example" { +} +``` + + +## Schema + +### Read-Only + +- `id` (String) ID of the users +- `users` (Attributes List) List of users and their attributes (see [below for nested schema](#nestedatt--users)) + + +### Nested Schema for `users` + +Read-Only: + +- `email` (String) User email +- `id` (String) ID of the user +- `name` (String) Name for the user +- `site_role` (String) Site role for the user diff --git a/examples/data-sources/tableau_datasources/data-source.tf b/examples/data-sources/tableau_datasources/data-source.tf new file mode 100644 index 0000000..1b9294a --- /dev/null +++ b/examples/data-sources/tableau_datasources/data-source.tf @@ -0,0 +1,2 @@ +data "tableau_groups" "example" { +} diff --git a/examples/data-sources/tableau_groups/data-source.tf b/examples/data-sources/tableau_groups/data-source.tf new file mode 100644 index 0000000..e843b62 --- /dev/null +++ b/examples/data-sources/tableau_groups/data-source.tf @@ -0,0 +1,2 @@ +data "tableau_datasources" "example" { +} diff --git a/examples/data-sources/tableau_users/data-source.tf b/examples/data-sources/tableau_users/data-source.tf new file mode 100644 index 0000000..ee308d8 --- /dev/null +++ b/examples/data-sources/tableau_users/data-source.tf @@ -0,0 +1,2 @@ +data "tableau_users" "example" { +} diff --git a/tableau/provider.go b/tableau/provider.go index fb2ecd2..4cac0c0 100644 --- a/tableau/provider.go +++ b/tableau/provider.go @@ -251,6 +251,7 @@ func (p *tableauProvider) DataSources(_ context.Context) []func() datasource.Dat GroupDataSource, GroupsDataSource, UserDataSource, + UsersDataSource, ProjectDataSource, ProjectsDataSource, SiteDataSource, diff --git a/tableau/user.go b/tableau/user.go index f091dd1..260674f 100644 --- a/tableau/user.go +++ b/tableau/user.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "strings" ) @@ -24,6 +25,66 @@ type UserResponse struct { User User `json:"user"` } +type UsersResponse struct { + Users []User `json:"user"` +} + +type UserListResponse struct { + UsersResponse UsersResponse `json:"users"` + Pagination PaginationDetails `json:"pagination"` +} + +func (c *Client) GetUsers() ([]User, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/users", c.ApiUrl), nil) + if err != nil { + return nil, err + } + + body, err := c.doRequest(req) + if err != nil { + return nil, err + } + + userListResponse := UserListResponse{} + err = json.Unmarshal(body, &userListResponse) + if err != nil { + return nil, err + } + + // TODO: Generalise pagination handling and use elsewhere + pageNumber, totalPageCount, totalAvailable, err := GetPaginationNumbers(userListResponse.Pagination) + if err != nil { + return nil, err + } + + allUsers := make([]User, 0, totalAvailable) + for _, user := range userListResponse.UsersResponse.Users { + allUsers = append(allUsers, user) + } + + for page := pageNumber + 1; page <= totalPageCount; page++ { + fmt.Printf("Searching page %d", page) + req, err = http.NewRequest("GET", fmt.Sprintf("%s/users?pageNumber=%s", c.ApiUrl, strconv.Itoa(page)), nil) + if err != nil { + return nil, err + } + body, err = c.doRequest(req) + if err != nil { + return nil, err + } + userListResponse = UserListResponse{} + err = json.Unmarshal(body, &userListResponse) + if err != nil { + return nil, err + } + for _, user := range userListResponse.UsersResponse.Users { + allUsers = append(allUsers, user) + } + } + + return allUsers, nil +} + func (c *Client) GetUser(userID string) (*User, error) { req, err := http.NewRequest("GET", fmt.Sprintf("%s/users/%s/", c.ApiUrl, userID), nil) if err != nil { diff --git a/tableau/users_data_source.go b/tableau/users_data_source.go new file mode 100644 index 0000000..13dd91e --- /dev/null +++ b/tableau/users_data_source.go @@ -0,0 +1,115 @@ +package tableau + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = &usersDataSource{} + _ datasource.DataSourceWithConfigure = &usersDataSource{} +) + +func UsersDataSource() datasource.DataSource { + return &usersDataSource{} +} + +type usersDataSource struct { + client *Client +} + +type usersNestedDataModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Email types.String `tfsdk:"email"` + SiteRole types.String `tfsdk:"site_role"` +} + +type usersDataSourceModel struct { + ID types.String `tfsdk:"id"` + Users []usersNestedDataModel `tfsdk:"users"` +} + +func (d *usersDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_users" +} + +func (d *usersDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Retrieve user details as a list of users available to read", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "ID of the users", + }, + "users": schema.ListNestedAttribute{ + Description: "List of users and their attributes", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "ID of the user", + }, + "name": schema.StringAttribute{ + Computed: true, + Description: "Name for the user", + }, + "email": schema.StringAttribute{ + Computed: true, + Description: "User email", + }, + "site_role": schema.StringAttribute{ + Computed: true, + Description: "Site role for the user", + }, + }, + }, + }, + }, + } +} + +func (d *usersDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var state usersDataSourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) + + users, err := d.client.GetUsers() + if err != nil { + resp.Diagnostics.AddError( + "Unable to Read Tableau Users", + err.Error(), + ) + return + } + + for _, user := range users { + userDataSourceModel := usersNestedDataModel{ + ID: types.StringValue(user.ID), + Name: types.StringValue(user.Name), + Email: types.StringValue(user.Email), + SiteRole: types.StringValue(user.SiteRole), + } + state.Users = append(state.Users, userDataSourceModel) + } + + state.ID = types.StringValue("allUsers") + + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (d *usersDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + d.client = req.ProviderData.(*Client) +} diff --git a/tableau/users_data_source_test.go b/tableau/users_data_source_test.go new file mode 100644 index 0000000..9a59a2f --- /dev/null +++ b/tableau/users_data_source_test.go @@ -0,0 +1,24 @@ +package tableau + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccUsersDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: providerConfig + ` + data "tableau_users" "test" { + }`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("data.tableau_users.test", "id"), + resource.TestCheckResourceAttrSet("data.tableau_users.test", "users.#"), + ), + }, + }, + }) +}