From eee8b24b63aff31786235614b2f0a4c268d5a661 Mon Sep 17 00:00:00 2001 From: atoni <50518795+bosc0@users.noreply.github.com> Date: Thu, 9 Jan 2025 13:19:06 +0100 Subject: [PATCH] feat: implement conversation datasource --- docs/data-sources/conversation.md | 32 ++++ internal/provider/data_source_conversation.go | 145 ++++++++++++++++++ internal/provider/provider.go | 1 + internal/slackExt/client.go | 4 + internal/slackExt/client_impl.go | 7 + internal/slackExt/client_rate_limit.go | 12 +- 6 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 docs/data-sources/conversation.md create mode 100644 internal/provider/data_source_conversation.go diff --git a/docs/data-sources/conversation.md b/docs/data-sources/conversation.md new file mode 100644 index 0000000..518ee95 --- /dev/null +++ b/docs/data-sources/conversation.md @@ -0,0 +1,32 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "slack_conversation Data Source - slack" +subcategory: "" +description: |- + Retrieve information about a Slack conversation (channel) by its ID. +--- + +# slack_conversation (Data Source) + +Retrieve information about a Slack conversation (channel) by its ID. + + + + +## Schema + +### Required + +- `channel_id` (String) The Slack channel ID to look up. + +### Read-Only + +- `created` (Number) UNIX timestamp when the channel was created. +- `creator` (String) User ID of the channel creator. +- `is_archived` (Boolean) True if the channel is archived. +- `is_ext_shared` (Boolean) True if the channel is externally shared. +- `is_general` (Boolean) True if this is the #general channel. +- `is_org_shared` (Boolean) True if the channel is shared across the org. +- `is_shared` (Boolean) True if the channel is shared. +- `purpose` (String) The channel purpose. +- `topic` (String) The channel topic. diff --git a/internal/provider/data_source_conversation.go b/internal/provider/data_source_conversation.go new file mode 100644 index 0000000..c568160 --- /dev/null +++ b/internal/provider/data_source_conversation.go @@ -0,0 +1,145 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + + "github.com/essent/terraform-provider-slack/internal/slackExt" + + "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 = &ConversationDataSource{} + +func NewConversationDataSource() datasource.DataSource { + return &ConversationDataSource{} +} + +type ConversationDataSource struct { + client slackExt.Client +} + +type ConversationDataSourceModel struct { + ChannelID types.String `tfsdk:"channel_id"` + Topic types.String `tfsdk:"topic"` + Purpose types.String `tfsdk:"purpose"` + Created types.Int64 `tfsdk:"created"` + Creator types.String `tfsdk:"creator"` + IsArchived types.Bool `tfsdk:"is_archived"` + IsShared types.Bool `tfsdk:"is_shared"` + IsExtShared types.Bool `tfsdk:"is_ext_shared"` + IsOrgShared types.Bool `tfsdk:"is_org_shared"` + IsGeneral types.Bool `tfsdk:"is_general"` +} + +func (d *ConversationDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_conversation" +} + +func (d *ConversationDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Retrieve information about a Slack conversation (channel) by its ID.", + Attributes: map[string]schema.Attribute{ + "channel_id": schema.StringAttribute{ + MarkdownDescription: "The Slack channel ID to look up.", + Required: true, + }, + "topic": schema.StringAttribute{ + MarkdownDescription: "The channel topic.", + Computed: true, + }, + "purpose": schema.StringAttribute{ + MarkdownDescription: "The channel purpose.", + Computed: true, + }, + "created": schema.Int64Attribute{ + MarkdownDescription: "UNIX timestamp when the channel was created.", + Computed: true, + }, + "creator": schema.StringAttribute{ + MarkdownDescription: "User ID of the channel creator.", + Computed: true, + }, + "is_archived": schema.BoolAttribute{ + MarkdownDescription: "True if the channel is archived.", + Computed: true, + }, + "is_shared": schema.BoolAttribute{ + MarkdownDescription: "True if the channel is shared.", + Computed: true, + }, + "is_ext_shared": schema.BoolAttribute{ + MarkdownDescription: "True if the channel is externally shared.", + Computed: true, + }, + "is_org_shared": schema.BoolAttribute{ + MarkdownDescription: "True if the channel is shared across the org.", + Computed: true, + }, + "is_general": schema.BoolAttribute{ + MarkdownDescription: "True if this is the #general channel.", + Computed: true, + }, + }, + } +} + +func (d *ConversationDataSource) 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 *ConversationDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data ConversationDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + channel, err := d.client.GetConversationInfo(ctx, &slack.GetConversationInfoInput{ + ChannelID: data.ChannelID.ValueString(), + IncludeLocale: false, + IncludeNumMembers: false, + }) + if err != nil { + resp.Diagnostics.AddError( + "Client Error", + fmt.Sprintf("Could not get conversation info for channel %s: %s", data.ChannelID.ValueString(), err), + ) + return + } + + data.ChannelID = types.StringValue(channel.ID) + data.Topic = types.StringValue(channel.Topic.Value) + data.Purpose = types.StringValue(channel.Purpose.Value) + data.Created = types.Int64Value(int64(channel.Created)) + data.Creator = types.StringValue(channel.Creator) + data.IsArchived = types.BoolValue(channel.IsArchived) + data.IsShared = types.BoolValue(channel.IsShared) + data.IsExtShared = types.BoolValue(channel.IsExtShared) + data.IsOrgShared = types.BoolValue(channel.IsOrgShared) + data.IsGeneral = types.BoolValue(channel.IsGeneral) + + tflog.Trace(ctx, "Fetched Slack channel data", map[string]any{"channel_id": channel.ID}) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 17b8304..43c565e 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -100,6 +100,7 @@ func (p *SlackProvider) DataSources(ctx context.Context) []func() datasource.Dat NewUserDataSource, NewAllUsersDataSource, NewAllUserGroupsDataSource, + NewConversationDataSource, } } diff --git a/internal/slackExt/client.go b/internal/slackExt/client.go index 84170ed..596a5f2 100644 --- a/internal/slackExt/client.go +++ b/internal/slackExt/client.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package slackExt import ( @@ -11,6 +14,7 @@ type Client interface { GetUserByEmail(ctx context.Context, email string) (*slack.User, error) GetUsersContext(ctx context.Context) ([]slack.User, error) GetUserGroups(ctx context.Context, options ...slack.GetUserGroupsOption) ([]slack.UserGroup, error) + GetConversationInfo(ctx context.Context, input *slack.GetConversationInfoInput) (*slack.Channel, error) } func New(base *slack.Client) Client { diff --git a/internal/slackExt/client_impl.go b/internal/slackExt/client_impl.go index 2e62d9c..160e4b0 100644 --- a/internal/slackExt/client_impl.go +++ b/internal/slackExt/client_impl.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package slackExt import ( @@ -25,3 +28,7 @@ func (c *clientImpl) GetUsersContext(ctx context.Context) ([]slack.User, error) func (c *clientImpl) GetUserGroups(ctx context.Context, options ...slack.GetUserGroupsOption) ([]slack.UserGroup, error) { return c.base.GetUserGroupsContext(ctx, options...) } + +func (c *clientImpl) GetConversationInfo(ctx context.Context, input *slack.GetConversationInfoInput) (*slack.Channel, error) { + return c.base.GetConversationInfoContext(ctx, input) +} diff --git a/internal/slackExt/client_rate_limit.go b/internal/slackExt/client_rate_limit.go index 261251b..b3c51b4 100644 --- a/internal/slackExt/client_rate_limit.go +++ b/internal/slackExt/client_rate_limit.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package slackExt import ( @@ -14,7 +17,6 @@ type clientRateLimit struct { func rateLimit[R any](ctx context.Context, f func() (R, error), getZeroValue func() R) (result R, err error) { for { result, err = f() - if err == nil { return result, nil } @@ -31,7 +33,7 @@ func rateLimit[R any](ctx context.Context, f func() (R, error), getZeroValue fun } } -func (c *clientRateLimit) GetUserInfo(ctx context.Context, user string) (result *slack.User, err error) { +func (c *clientRateLimit) GetUserInfo(ctx context.Context, user string) (*slack.User, error) { return rateLimit(ctx, func() (*slack.User, error) { return c.base.GetUserInfo(ctx, user) }, func() *slack.User { return nil }) @@ -52,3 +54,9 @@ func (c *clientRateLimit) GetUserGroups(ctx context.Context, options ...slack.Ge return c.base.GetUserGroups(ctx, options...) }, func() []slack.UserGroup { return []slack.UserGroup{} }) } + +func (c *clientRateLimit) GetConversationInfo(ctx context.Context, input *slack.GetConversationInfoInput) (*slack.Channel, error) { + return rateLimit(ctx, func() (*slack.Channel, error) { + return c.base.GetConversationInfo(ctx, input) + }, func() *slack.Channel { return nil }) +}