From 70f2635db824a9f1124cfd67b0e39d5998b18abf Mon Sep 17 00:00:00 2001 From: atoni <50518795+bosc0@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:09:05 +0100 Subject: [PATCH 1/7] data_source_user.go: allow either email or userid to be set --- docs/data-sources/user_data.md | 8 ++++---- go.mod | 1 + go.sum | 2 ++ internal/provider/data_source_user.go | 29 ++++++++++++++++++++++----- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/docs/data-sources/user_data.md b/docs/data-sources/user_data.md index 01aa7a9..99847e8 100644 --- a/docs/data-sources/user_data.md +++ b/docs/data-sources/user_data.md @@ -3,25 +3,25 @@ page_title: "slack_user_data Data Source - slack" subcategory: "" description: |- - Retrieve Slack user information. + Retrieve Slack user information. Either user_id or email must be specified, but not both. --- # slack_user_data (Data Source) -Retrieve Slack user information. +Retrieve Slack user information. Either `user_id` or `email` must be specified, but not both. ## Schema -### Required +### Optional +- `email` (String) Email of the user to look up. - `user_id` (String) Slack user ID to look up. ### Read-Only - `display_name` (String) User's display name. -- `email` (String) Email of the user. - `id` (String) Unique identifier for Terraform state. - `real_name` (String) User's real name. diff --git a/go.mod b/go.mod index a5816e0..b2e2357 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.23.4 require ( github.com/hashicorp/terraform-plugin-framework v1.13.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.16.0 github.com/hashicorp/terraform-plugin-go v0.25.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.11.0 diff --git a/go.sum b/go.sum index 8680fdf..e8342f7 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,8 @@ github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2 github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw= github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU= +github.com/hashicorp/terraform-plugin-framework-validators v0.16.0 h1:O9QqGoYDzQT7lwTXUsZEtgabeWW96zUBh47Smn2lkFA= +github.com/hashicorp/terraform-plugin-framework-validators v0.16.0/go.mod h1:Bh89/hNmqsEWug4/XWKYBwtnw3tbz5BAy1L1OgvbIaY= github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974rdTxjqEhXJjbAyks= github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= diff --git a/internal/provider/data_source_user.go b/internal/provider/data_source_user.go index 536a508..0ad2108 100644 --- a/internal/provider/data_source_user.go +++ b/internal/provider/data_source_user.go @@ -7,8 +7,11 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/slack-go/slack" @@ -38,15 +41,21 @@ func (d *UserDataSource) Metadata(ctx context.Context, req datasource.MetadataRe func (d *UserDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: "Retrieve Slack user information.", + MarkdownDescription: "Retrieve Slack user information. Either `user_id` or `email` must be specified, but not both.", Attributes: map[string]schema.Attribute{ "user_id": schema.StringAttribute{ MarkdownDescription: "Slack user ID to look up.", - Required: true, + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("email")), + }, }, "email": schema.StringAttribute{ - MarkdownDescription: "Email of the user.", - Computed: true, + MarkdownDescription: "Email of the user to look up.", + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("user_id")), + }, }, "real_name": schema.StringAttribute{ MarkdownDescription: "User's real name.", @@ -90,7 +99,17 @@ func (d *UserDataSource) Read(ctx context.Context, req datasource.ReadRequest, r return } - user, err := d.client.GetUserInfo(data.UserID.ValueString()) + var ( + user *slack.User + err error + ) + + if !data.UserID.IsNull() { + user, err = d.client.GetUserInfo(data.UserID.ValueString()) + } else { + user, err = d.client.GetUserByEmail(data.Email.ValueString()) + } + if err != nil { resp.Diagnostics.AddError( "Client Error", From 1cb83c8d3966bccd704cd34fd17d0b8d5a5e868d Mon Sep 17 00:00:00 2001 From: atoni <50518795+bosc0@users.noreply.github.com> Date: Thu, 2 Jan 2025 12:29:16 +0100 Subject: [PATCH 2/7] data_source_user.go: dont return deactivated users --- internal/provider/data_source_user.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/provider/data_source_user.go b/internal/provider/data_source_user.go index 0ad2108..8f32752 100644 --- a/internal/provider/data_source_user.go +++ b/internal/provider/data_source_user.go @@ -118,6 +118,14 @@ func (d *UserDataSource) Read(ctx context.Context, req datasource.ReadRequest, r return } + if user.Deleted { + resp.Diagnostics.AddError( + "User is deactivated", + "User is deactivated in Slack", + ) + return + } + data.Email = types.StringValue(user.Profile.Email) data.RealName = types.StringValue(user.RealName) data.DisplayName = types.StringValue(user.Profile.DisplayName) From 395b674552eb33cba10c1b9a30d15a19eb5d8bf3 Mon Sep 17 00:00:00 2001 From: atoni <50518795+bosc0@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:44:32 +0100 Subject: [PATCH 3/7] refactor: simplify data_source_user.go --- docs/data-sources/user.md | 25 ++++++++++++++++++ docs/data-sources/user_data.md | 27 ------------------- internal/provider/data_source_user.go | 37 ++++++++++----------------- 3 files changed, 38 insertions(+), 51 deletions(-) create mode 100644 docs/data-sources/user.md delete mode 100644 docs/data-sources/user_data.md diff --git a/docs/data-sources/user.md b/docs/data-sources/user.md new file mode 100644 index 0000000..8fb0cb4 --- /dev/null +++ b/docs/data-sources/user.md @@ -0,0 +1,25 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "slack_user Data Source - slack" +subcategory: "" +description: |- + Retrieve Slack user information. Either id or email must be specified, but not both. +--- + +# slack_user (Data Source) + +Retrieve Slack user information. Either `id` or `email` must be specified, but not both. + + + + +## Schema + +### Optional + +- `email` (String) Email of the user to look up. +- `id` (String) Slack user ID to look up. + +### Read-Only + +- `name` (String) User's name. diff --git a/docs/data-sources/user_data.md b/docs/data-sources/user_data.md deleted file mode 100644 index 99847e8..0000000 --- a/docs/data-sources/user_data.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "slack_user_data Data Source - slack" -subcategory: "" -description: |- - Retrieve Slack user information. Either user_id or email must be specified, but not both. ---- - -# slack_user_data (Data Source) - -Retrieve Slack user information. Either `user_id` or `email` must be specified, but not both. - - - - -## Schema - -### Optional - -- `email` (String) Email of the user to look up. -- `user_id` (String) Slack user ID to look up. - -### Read-Only - -- `display_name` (String) User's display name. -- `id` (String) Unique identifier for Terraform state. -- `real_name` (String) User's real name. diff --git a/internal/provider/data_source_user.go b/internal/provider/data_source_user.go index 8f32752..f263932 100644 --- a/internal/provider/data_source_user.go +++ b/internal/provider/data_source_user.go @@ -28,22 +28,20 @@ type UserDataSource struct { } type UserDataSourceModel struct { - UserID types.String `tfsdk:"user_id"` - Email types.String `tfsdk:"email"` - RealName types.String `tfsdk:"real_name"` - DisplayName types.String `tfsdk:"display_name"` - ID types.String `tfsdk:"id"` + Email types.String `tfsdk:"email"` + Name types.String `tfsdk:"name"` + ID types.String `tfsdk:"id"` } func (d *UserDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_user_data" + resp.TypeName = req.ProviderTypeName + "_user" } func (d *UserDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: "Retrieve Slack user information. Either `user_id` or `email` must be specified, but not both.", + MarkdownDescription: "Retrieve Slack user information. Either `id` or `email` must be specified, but not both.", Attributes: map[string]schema.Attribute{ - "user_id": schema.StringAttribute{ + "id": schema.StringAttribute{ MarkdownDescription: "Slack user ID to look up.", Optional: true, Validators: []validator.String{ @@ -54,19 +52,11 @@ func (d *UserDataSource) Schema(ctx context.Context, req datasource.SchemaReques MarkdownDescription: "Email of the user to look up.", Optional: true, Validators: []validator.String{ - stringvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("user_id")), + stringvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("id")), }, }, - "real_name": schema.StringAttribute{ - MarkdownDescription: "User's real name.", - Computed: true, - }, - "display_name": schema.StringAttribute{ - MarkdownDescription: "User's display name.", - Computed: true, - }, - "id": schema.StringAttribute{ - MarkdownDescription: "Unique identifier for Terraform state.", + "name": schema.StringAttribute{ + MarkdownDescription: "User's name.", Computed: true, }, }, @@ -104,8 +94,8 @@ func (d *UserDataSource) Read(ctx context.Context, req datasource.ReadRequest, r err error ) - if !data.UserID.IsNull() { - user, err = d.client.GetUserInfo(data.UserID.ValueString()) + if !data.ID.IsNull() { + user, err = d.client.GetUserInfo(data.ID.ValueString()) } else { user, err = d.client.GetUserByEmail(data.Email.ValueString()) } @@ -127,11 +117,10 @@ func (d *UserDataSource) Read(ctx context.Context, req datasource.ReadRequest, r } data.Email = types.StringValue(user.Profile.Email) - data.RealName = types.StringValue(user.RealName) - data.DisplayName = types.StringValue(user.Profile.DisplayName) + data.Name = types.StringValue(user.Profile.DisplayNameNormalized) data.ID = types.StringValue(user.ID) - tflog.Trace(ctx, "Fetched Slack user data", map[string]any{"user_id": user.ID}) + tflog.Trace(ctx, "Fetched Slack user data", map[string]any{"id": user.ID}) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } From eaeac1287ffc7d6084dda0b700a93e9e8b114dcb Mon Sep 17 00:00:00 2001 From: atoni <50518795+bosc0@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:44:56 +0100 Subject: [PATCH 4/7] feat: introduce data_source_all_users.go --- docs/data-sources/all_users.md | 30 +++++ internal/provider/data_source_all_users.go | 122 +++++++++++++++++++++ internal/provider/provider.go | 1 + 3 files changed, 153 insertions(+) create mode 100644 docs/data-sources/all_users.md create mode 100644 internal/provider/data_source_all_users.go diff --git a/docs/data-sources/all_users.md b/docs/data-sources/all_users.md new file mode 100644 index 0000000..50fc63c --- /dev/null +++ b/docs/data-sources/all_users.md @@ -0,0 +1,30 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "slack_all_users Data Source - slack" +subcategory: "" +description: |- + +--- + +# slack_all_users (Data Source) + + + + + + +## Schema + +### Read-Only + +- `total_users` (Number) Number of users returned. +- `users` (Attributes List) List of activated and non-bot Slack users. (see [below for nested schema](#nestedatt--users)) + + +### Nested Schema for `users` + +Read-Only: + +- `email` (String) User's email address. +- `id` (String) User's Slack ID. +- `name` (String) User's name. diff --git a/internal/provider/data_source_all_users.go b/internal/provider/data_source_all_users.go new file mode 100644 index 0000000..0970ffc --- /dev/null +++ b/internal/provider/data_source_all_users.go @@ -0,0 +1,122 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + + "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-log/tflog" + "github.com/slack-go/slack" +) + +var _ datasource.DataSource = &AllUsersDataSource{} + +func NewAllUsersDataSource() datasource.DataSource { + return &AllUsersDataSource{} +} + +type AllUsersDataSource struct { + client *slack.Client +} + +type AllUsersDataSourceModel struct { + Totalusers types.Int64 `tfsdk:"total_users"` + Users []AllUsersDataSourceModelUserItem `tfsdk:"users"` +} + +type AllUsersDataSourceModelUserItem struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Email types.String `tfsdk:"email"` +} + +func (d *AllUsersDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_all_users" +} + +func (d *AllUsersDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "total_users": schema.Int64Attribute{ + Description: "Number of users returned.", + Computed: true, + }, + "users": schema.ListNestedAttribute{ + Description: "List of activated and non-bot Slack users.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "User's Slack ID.", + Computed: true, + }, + "name": schema.StringAttribute{ + Description: "User's name.", + Computed: true, + }, + "email": schema.StringAttribute{ + Description: "User's email address.", + Computed: true, + }, + }, + }, + }, + }, + } +} + +func (d *AllUsersDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + providerData, ok := req.ProviderData.(*SlackProviderData) + if !ok || providerData.Client == nil { + resp.Diagnostics.AddError( + "Invalid Provider Data", + fmt.Sprintf("Expected *SlackProviderData with initialized client, got: %T", req.ProviderData), + ) + return + } + d.client = providerData.Client +} + +func (d *AllUsersDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data AllUsersDataSourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + users, err := d.client.GetUsersContext(ctx) + if err != nil { + resp.Diagnostics.AddError( + "Client Error", + fmt.Sprintf("Unable to fetch Slack users: %s", err), + ) + return + } + + tflog.Trace(ctx, "Fetched Slack users", map[string]any{"total_users": len(users)}) + + var resultingList []AllUsersDataSourceModelUserItem + for _, user := range users { + if !user.Deleted && !user.IsBot { + resultingList = append(resultingList, AllUsersDataSourceModelUserItem{ + ID: types.StringValue(user.ID), + Name: types.StringValue(user.Profile.RealName), + Email: types.StringValue(user.Profile.Email), + }) + } + } + + data.Users = resultingList + data.Totalusers = types.Int64Value(int64(len(resultingList))) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index ea2d037..e27be37 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -87,6 +87,7 @@ func (p *SlackProvider) Resources(ctx context.Context) []func() resource.Resourc func (p *SlackProvider) DataSources(ctx context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ NewUserDataSource, + NewAllUsersDataSource, } } From 64e6e9d60973423f188015d1f8a0d0428219618f Mon Sep 17 00:00:00 2001 From: atoni <50518795+bosc0@users.noreply.github.com> Date: Fri, 3 Jan 2025 11:53:54 +0100 Subject: [PATCH 5/7] feat: allow providing api key as env var --- docs/index.md | 4 ++-- internal/provider/provider.go | 27 ++++++++++++++++++--------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/index.md b/docs/index.md index 7ec4404..637e08c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -21,6 +21,6 @@ provider "scaffolding" { ## Schema -### Required +### Optional -- `slack_token` (String, Sensitive) Slack token to authenticate API calls. +- `slack_token` (String, Sensitive) Slack token to authenticate API calls. Can also be set with the `SLACK_TOKEN` environment variable. diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e27be37..40b50a7 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -6,6 +6,7 @@ package provider import ( "context" "fmt" + "os" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/function" @@ -41,8 +42,8 @@ func (p *SlackProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "slack_token": schema.StringAttribute{ - MarkdownDescription: "Slack token to authenticate API calls.", - Required: true, + MarkdownDescription: "Slack token to authenticate API calls. Can also be set with the `SLACK_TOKEN` environment variable.", + Optional: true, Sensitive: true, }, }, @@ -56,15 +57,23 @@ func (p *SlackProvider) Configure(ctx context.Context, req provider.ConfigureReq return } - if data.SlackToken.IsNull() || data.SlackToken.IsUnknown() { - resp.Diagnostics.AddError( - "Missing Slack Token", - "The `slack_token` must be provided to authenticate API calls.", - ) - return + slackToken := data.SlackToken.ValueString() + + // If slack_token was not set in the provider block, check the environment variable. + if slackToken == "" { + envToken, ok := os.LookupEnv("SLACK_TOKEN") + if !ok || envToken == "" { + resp.Diagnostics.AddError( + "Missing Slack Token", + "`slack_token` was not set in the provider block, and `SLACK_TOKEN` is not set in the environment.", + ) + return + } + slackToken = envToken } + tflog.Info(ctx, "Configuring slack client") - client := slack.New(data.SlackToken.ValueString()) + client := slack.New(slackToken) _, err := client.AuthTest() if err != nil { resp.Diagnostics.AddError( From 3b1259f6840859993b5173c85afa1ba8314ca20a Mon Sep 17 00:00:00 2001 From: atoni <50518795+bosc0@users.noreply.github.com> Date: Fri, 3 Jan 2025 12:01:46 +0100 Subject: [PATCH 6/7] fix: use correct name field --- internal/provider/data_source_all_users.go | 2 +- internal/provider/data_source_user.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/provider/data_source_all_users.go b/internal/provider/data_source_all_users.go index 0970ffc..c28c0cb 100644 --- a/internal/provider/data_source_all_users.go +++ b/internal/provider/data_source_all_users.go @@ -109,7 +109,7 @@ func (d *AllUsersDataSource) Read(ctx context.Context, req datasource.ReadReques if !user.Deleted && !user.IsBot { resultingList = append(resultingList, AllUsersDataSourceModelUserItem{ ID: types.StringValue(user.ID), - Name: types.StringValue(user.Profile.RealName), + Name: types.StringValue(user.Name), Email: types.StringValue(user.Profile.Email), }) } diff --git a/internal/provider/data_source_user.go b/internal/provider/data_source_user.go index f263932..163fceb 100644 --- a/internal/provider/data_source_user.go +++ b/internal/provider/data_source_user.go @@ -117,7 +117,7 @@ func (d *UserDataSource) Read(ctx context.Context, req datasource.ReadRequest, r } data.Email = types.StringValue(user.Profile.Email) - data.Name = types.StringValue(user.Profile.DisplayNameNormalized) + data.Name = types.StringValue(user.Name) data.ID = types.StringValue(user.ID) tflog.Trace(ctx, "Fetched Slack user data", map[string]any{"id": user.ID}) From 101931db71e1599694aaf168fbffaff18420bd07 Mon Sep 17 00:00:00 2001 From: atoni <50518795+bosc0@users.noreply.github.com> Date: Fri, 3 Jan 2025 14:32:11 +0100 Subject: [PATCH 7/7] feat: add data_source_all_usergroups.go --- docs/data-sources/all_usergroups.md | 33 ++++ .../provider/data_source_all_usergroups.go | 155 ++++++++++++++++++ internal/provider/provider.go | 1 + 3 files changed, 189 insertions(+) create mode 100644 docs/data-sources/all_usergroups.md create mode 100644 internal/provider/data_source_all_usergroups.go diff --git a/docs/data-sources/all_usergroups.md b/docs/data-sources/all_usergroups.md new file mode 100644 index 0000000..0fb9d7f --- /dev/null +++ b/docs/data-sources/all_usergroups.md @@ -0,0 +1,33 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "slack_all_usergroups Data Source - slack" +subcategory: "" +description: |- + Retrieve all Slack user groups. +--- + +# slack_all_usergroups (Data Source) + +Retrieve all Slack user groups. + + + + +## Schema + +### Read-Only + +- `total_usergroups` (Number) Total number of user groups retrieved. +- `usergroups` (Attributes List) List of Slack user groups. (see [below for nested schema](#nestedatt--usergroups)) + + +### Nested Schema for `usergroups` + +Read-Only: + +- `channels` (List of String) Channels shared by the user group. +- `description` (String) Description of the user group. +- `handle` (String) Handle of the user group (unique identifier). +- `id` (String) User group's Slack ID. +- `name` (String) Name of the user group. +- `users` (List of String) List of user IDs in the user group. diff --git a/internal/provider/data_source_all_usergroups.go b/internal/provider/data_source_all_usergroups.go new file mode 100644 index 0000000..4504347 --- /dev/null +++ b/internal/provider/data_source_all_usergroups.go @@ -0,0 +1,155 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + + "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-log/tflog" + "github.com/slack-go/slack" +) + +var _ datasource.DataSource = &AllUserGroupsDataSource{} + +func NewAllUserGroupsDataSource() datasource.DataSource { + return &AllUserGroupsDataSource{} +} + +type AllUserGroupsDataSource struct { + client *slack.Client +} + +type AllUserGroupsDataSourceModel struct { + TotalUserGroups types.Int64 `tfsdk:"total_usergroups"` + UserGroups []AllUserGroupsDataSourceGroupItem `tfsdk:"usergroups"` +} + +type AllUserGroupsDataSourceGroupItem struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Handle types.String `tfsdk:"handle"` + Channels []types.String `tfsdk:"channels"` + Users []types.String `tfsdk:"users"` +} + +func (d *AllUserGroupsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_all_usergroups" +} + +func (d *AllUserGroupsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Retrieve all Slack user groups.", + Attributes: map[string]schema.Attribute{ + "total_usergroups": schema.Int64Attribute{ + Description: "Total number of user groups retrieved.", + Computed: true, + }, + "usergroups": schema.ListNestedAttribute{ + Description: "List of Slack user groups.", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "User group's Slack ID.", + Computed: true, + }, + "name": schema.StringAttribute{ + Description: "Name of the user group.", + Computed: true, + }, + "description": schema.StringAttribute{ + Description: "Description of the user group.", + Computed: true, + }, + "handle": schema.StringAttribute{ + Description: "Handle of the user group (unique identifier).", + Computed: true, + }, + "channels": schema.ListAttribute{ + Description: "Channels shared by the user group.", + ElementType: types.StringType, + Computed: true, + }, + "users": schema.ListAttribute{ + Description: "List of user IDs in the user group.", + ElementType: types.StringType, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func (d *AllUserGroupsDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + providerData, ok := req.ProviderData.(*SlackProviderData) + if !ok || providerData.Client == nil { + resp.Diagnostics.AddError( + "Invalid Provider Data", + fmt.Sprintf("Expected *SlackProviderData with initialized client, got: %T", req.ProviderData), + ) + return + } + + d.client = providerData.Client +} + +func (d *AllUserGroupsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data AllUserGroupsDataSourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + userGroups, err := d.client.GetUserGroups(slack.GetUserGroupsOptionIncludeUsers(true)) + if err != nil { + resp.Diagnostics.AddError( + "Client Error", + fmt.Sprintf("Unable to fetch Slack user groups: %s", err), + ) + return + } + + tflog.Trace(ctx, "Fetched Slack user groups", map[string]any{"total_usergroups": len(userGroups)}) + + var resultingList []AllUserGroupsDataSourceGroupItem + for _, group := range userGroups { + groupItem := AllUserGroupsDataSourceGroupItem{ + ID: types.StringValue(group.ID), + Name: types.StringValue(group.Name), + Description: types.StringValue(group.Description), + Handle: types.StringValue(group.Handle), + } + + channels := make([]types.String, len(group.Prefs.Channels)) + for i, ch := range group.Prefs.Channels { + channels[i] = types.StringValue(ch) + } + groupItem.Channels = channels + + users := make([]types.String, len(group.Users)) + for i, u := range group.Users { + users[i] = types.StringValue(u) + } + groupItem.Users = users + + resultingList = append(resultingList, groupItem) + } + + data.UserGroups = resultingList + data.TotalUserGroups = types.Int64Value(int64(len(resultingList))) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 40b50a7..a7bce2d 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -97,6 +97,7 @@ func (p *SlackProvider) DataSources(ctx context.Context) []func() datasource.Dat return []func() datasource.DataSource{ NewUserDataSource, NewAllUsersDataSource, + NewAllUserGroupsDataSource, } }